diff --git a/.github/scripts/check_inl_sources.py b/.github/scripts/check_inl_sources.py new file mode 100644 index 00000000..48e80034 --- /dev/null +++ b/.github/scripts/check_inl_sources.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +"""Consistency checks for a library built as a single translation unit. + +The implementation lives in *.inl files that a single hub *.cpp includes, +directly or through other *.inl files. This script checks that the hub and +the *.inl files agree: + + 1. every *.inl include reachable from the hub names an existing file under + the source root, no file includes the same *.inl twice, and *.inl files + are included with the quoted form only + 2. every *.inl under the source root is reachable from the hub + 3. every *.inl starts with #ifndef / #error / #endif so it cannot + be included by user code on its own + 4. no *.c / *.cpp other than the hub exists under the source root (a stray + one would compile as a separate translation unit and defeat the purpose) + 5. (--compile) every *.inl the hub includes directly compiles on its own with + only the guard macro defined, so no file silently depends on what an + earlier include brought in (files included by other *.inl files are + fragments of those and are not compiled separately) + +The checks are lexical: comments, string literals and line splices are +handled like the preprocessor does, but #if conditions are not evaluated, so +an include inside a disabled #if block still counts as reachable, a file +included from several places (the platform directories each include the +same bit-bang helpers, under mutually exclusive conditions) is not reported +as a duplicate, and a platform file that compiles to nothing on the host +still passes the compile check. The script guards against mistakes, not +against code written to evade it. + +Usage: + check_inl_sources.py --hub src/lgfx/v1/lgfx_v1.cpp --root src/lgfx/v1 \ + --macro LGFX_V1_IMPLEMENTATION [--compile CXX -std=c++17 -DLGFX_SDL -Isrc ...] + +Everything after --compile is the compiler command line; the script appends +-fsyntax-only -x c++ -D and the file. +""" + +import argparse +import concurrent.futures +import os +import re +import subprocess +import sys + +WS = r'[ \t\f\v]' +INCLUDE_RE = re.compile(r'^' + WS + r'*#' + WS + r'*include' + WS + r'*(?:"([^"\n]+)"|<([^>\n]+)>)' + WS + r'*$', re.M) +DIRECTIVE_RE = re.compile(r'^' + WS + r'*#' + WS + r'*(\w+)(.*)$', re.M) +RAW_STRING_RE = re.compile(r'(?:u8|u|U|L)?R"([^()\\ \t\f\v\n]{0,16})\(') + + +def digit_separator_at(text, i): + """True when the quote at text[i] separates digits of a numeric literal (1'000, 0xFF'FF).""" + k = i - 1 + while k >= 0 and (text[k].isalnum() or text[k] in '_.'): + k -= 1 + token = text[k + 1:i].lstrip('.') + return bool(token) and token[0].isdigit() and i + 1 < len(text) and (text[i + 1].isalnum()) + + +def raw_string_at(text, i): + """Match object when the quote at text[i] opens a raw string literal, else None.""" + for start in (i - 1, i - 2, i - 3): + if start < 0: + break + if start > 0 and (text[start - 1].isalnum() or text[start - 1] == '_'): + continue + m = RAW_STRING_RE.match(text, start) + if m and text[m.start():].startswith(m.group(0)) and m.group(0).index('"') == i - start: + return m + return None + + +def read_code(path): + """File contents prepared for line-based directive matching. + + Line splices are joined (except inside raw string literals), comments + are replaced by a space, raw string literals are blanked, and ordinary + string and character literals are copied through (a comment opener + inside one is not a comment). Newlines are kept so each directive still + sits on its own line. + """ + with open(path, encoding='utf-8', errors='replace') as f: + text = f.read().replace('\r\n', '\n') + out = [] + i = 0 + n = len(text) + while i < n: + c = text[i] + if text.startswith('\\\n', i): + i += 2 + elif text.startswith('//', i): + j = text.find('\n', i) + while 0 < j and text[j - 1] == '\\': + j = text.find('\n', j + 1) + i = n if j < 0 else j + elif c == "'" and digit_separator_at(text, i): + i += 1 + elif text.startswith('/*', i): + j = text.find('*/', i + 2) + j = n if j < 0 else j + 2 + out.append(' ' + '\n' * text.count('\n', i, j)) + i = j + elif c == '"' and (m := raw_string_at(text, i)): + # raw string literal R"delim( ... )delim": blank it, keep its newlines + close = ')' + m.group(1) + '"' + j = text.find(close, m.end()) + j = n if j < 0 else j + len(close) + out.append('""' + '\n' * text.count('\n', i, j)) + i = j + elif c in '"\'': + # ordinary string or character literal: copy it. Line splices are removed + # before escape sequences are read, as in translation phase 2. + lit = [c] + j = i + 1 + while j < n: + if text.startswith('\\\n', j): + j += 2 + continue + ch = text[j] + if ch == '\n': + break + j += 1 + if ch == c: + lit.append(c) + break + if ch == '\\': + while text.startswith('\\\n', j): + j += 2 + if j < n and text[j] != '\n': + lit.append('\\' + text[j]) + j += 1 + continue + lit.append(ch) + out.append(''.join(lit)) + i = j + else: + out.append(c) + i += 1 + return ''.join(out) + + +def rel(path, base): + return os.path.relpath(path, base).replace(os.sep, '/') + + +def walk(root, exts): + for dirpath, _dirs, files in os.walk(root): + for name in sorted(files): + if os.path.splitext(name)[1] in exts: + yield os.path.normpath(os.path.join(dirpath, name)) + + +def inl_includes(path): + """(form, include text) for every *.inl include directive in the file. + + form is '"' or '<' for a well-formed directive, and '?' with the raw + directive text when an #include mentions .inl but does not parse. + """ + found = [] + code = read_code(path) + for m in DIRECTIVE_RE.finditer(code): + if m.group(1) != 'include': + continue + inc = INCLUDE_RE.match(code, m.start()) + if inc: + form, name = ('"', inc.group(1)) if inc.group(1) is not None else ('<', inc.group(2)) + if name.endswith('.inl'): + found.append((form, name)) + elif '.inl' in m.group(2): + found.append(('?', m.group(0).strip())) + return found + + +def is_under(path, root): + """True when the file (symlinks resolved) lies under root.""" + real_root = os.path.realpath(root) + return os.path.commonpath([os.path.realpath(path), real_root]) == real_root + + +def check_reachability(hub, root, problems): + """Walks the *.inl include graph from the hub. + + Returns (direct, reachable): the files the hub includes directly, in hub + order, and every *.inl reachable from the hub. + """ + direct = [] + reachable = set() + visited = set() + + def visit(src, chain): + key = os.path.realpath(src) + if key in visited: + return + visited.add(key) + seen_here = set() + for form, inc in inl_includes(src): + if form == '?': + problems.append(f'{rel(src, root)}: malformed include directive: {inc}') + continue + if form == '<': + problems.append(f'{rel(src, root)}: includes <{inc}>; use the quoted form for *.inl files') + continue + path = os.path.normpath(os.path.join(os.path.dirname(src), inc)) + if not os.path.isfile(path): + problems.append(f'{rel(src, root)}: includes missing file "{inc}"') + continue + if not is_under(path, root): + problems.append(f'{rel(src, root)}: includes "{inc}", which is outside {rel(root, os.getcwd())}') + continue + real = os.path.realpath(path) + if real in seen_here: + problems.append(f'{rel(src, root)}: includes "{inc}" twice') + continue + seen_here.add(real) + if real in chain: + problems.append(f'{rel(src, root)}: includes "{inc}", which already includes this file (cycle)') + continue + if src == hub: + direct.append(path) + reachable.add(path) + visit(path, chain + (real,)) + + visit(hub, (os.path.realpath(hub),)) + for inl in walk(root, {'.inl'}): + if inl not in reachable: + problems.append(f'{rel(inl, root)}: not reachable from the hub') + return direct, reachable + + +def check_guards(root, macro, problems): + """The first three directives of every *.inl must be #ifndef MACRO / #error / #endif.""" + for inl in walk(root, {'.inl'}): + directives = [(m.group(1), m.group(2).strip()) for m in DIRECTIVE_RE.finditer(read_code(inl))] + ok = (len(directives) >= 3 + and directives[0] == ('ifndef', macro) + and directives[1][0] == 'error' + and directives[2][0] == 'endif') + if not ok: + problems.append(f'{rel(inl, root)}: does not start with "#ifndef {macro}" / "#error" / "#endif"') + + +def check_stray_sources(root, hub, problems): + for src in walk(root, {'.c', '.cpp'}): + if os.path.abspath(src) != os.path.abspath(hub): + problems.append(f'{rel(src, root)}: source file outside the hub (add it as *.inl to the hub instead)') + + +def compile_one(cmd, macro, path): + full = cmd + ['-fsyntax-only', '-x', 'c++', '-D' + macro, path] + proc = subprocess.run(full, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + return path, proc.returncode, proc.stdout + + +def check_compile(direct, root, macro, cmd, problems): + workers = os.cpu_count() or 2 + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool: + futures = [pool.submit(compile_one, cmd, macro, p) for p in direct] + for fut in futures: + path, rc, out = fut.result() + if rc != 0: + head = '\n'.join(out.strip().splitlines()[:12]) + problems.append(f'{rel(path, root)}: does not compile on its own\n{head}') + + +def main(): + argv = sys.argv[1:] + compile_cmd = None + if '--compile' in argv: + i = argv.index('--compile') + compile_cmd = argv[i + 1:] + argv = argv[:i] + if not compile_cmd: + sys.exit('--compile needs a compiler command line') + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('--hub', required=True, help='the *.cpp that includes the *.inl files') + ap.add_argument('--root', required=True, help='directory whose *.inl files must all be reachable from the hub') + ap.add_argument('--macro', required=True, help='guard macro the hub defines around its includes') + args = ap.parse_args(argv) + + hub = os.path.normpath(os.path.abspath(args.hub)) + root = os.path.normpath(os.path.abspath(args.root)) + problems = [] + + direct, reachable = check_reachability(hub, root, problems) + check_guards(root, args.macro, problems) + check_stray_sources(root, hub, problems) + if compile_cmd: + check_compile(direct, root, args.macro, compile_cmd, problems) + + print(f'{rel(hub, root)}: {len(direct)} files included directly, {len(reachable)} reachable, ' + f'{sum(1 for _ in walk(root, {".inl"}))} *.inl under {rel(root, os.getcwd())}' + + (f', compiled with: {" ".join(compile_cmd)}' if compile_cmd else '')) + if problems: + print(f'{len(problems)} problem(s):') + for p in problems: + print(' - ' + p.replace('\n', '\n ')) + sys.exit(1) + print('OK') + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/SourceCheck.yml b/.github/workflows/SourceCheck.yml new file mode 100644 index 00000000..fcf9c10f --- /dev/null +++ b/.github/workflows/SourceCheck.yml @@ -0,0 +1,29 @@ +# SourceCheck.yml +name: SourceCheck + +on: + push: + paths: + - 'src/**' + - '**check_inl_sources.py' + - '**SourceCheck.yml' + pull_request: + + workflow_dispatch: + +jobs: + + inl-sources: + + name: single translation unit consistency + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Check the hub and the *.inl files + run: | + python3 .github/scripts/check_inl_sources.py \ + --hub src/M5Unified.cpp --root src --macro M5UNIFIED_IMPLEMENTATION diff --git a/CMakeLists.txt b/CMakeLists.txt index aec782e1..47531d1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,8 @@ set(COMPONENT_ADD_INCLUDEDIRS src ) -file(GLOB SRCS - src/*.cpp - src/utility/*.cpp - src/utility/imu/*.cpp - src/utility/led/*.cpp - src/utility/power/*.cpp - src/utility/rtc/*.cpp - ) -set(COMPONENT_SRCS ${SRCS}) +# Single translation unit: src/M5Unified.cpp includes every *.inl (see the comment there). +set(COMPONENT_SRCS src/M5Unified.cpp) # ESP-IDF uses the directory name as the component name (case sensitive). # Use "M5GFX" when a sibling components/M5GFX checkout exists, otherwise "m5gfx" diff --git a/idf_component.yml b/idf_component.yml index abcff0ff..b6784925 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -2,7 +2,7 @@ description: Unified library for M5Stack series issues: https://github.com/m5stack/M5Unified/issues repository: https://github.com/m5stack/M5Unified.git url: https://github.com/m5stack/M5Unified.git -version: 0.2.22 +version: 0.2.23 dependencies: m5stack/m5gfx: - version: ">=0.2.29" + version: ">=0.2.30" diff --git a/library.json b/library.json index aaa37615..e6fd755b 100644 --- a/library.json +++ b/library.json @@ -13,10 +13,10 @@ "dependencies": [ { "name": "M5GFX", - "version": ">=0.2.29" + "version": ">=0.2.30" } ], - "version": "0.2.22", + "version": "0.2.23", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5Unified.h" diff --git a/library.properties b/library.properties index d16acc23..34433b80 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5Unified -version=0.2.22 +version=0.2.23 author=M5Stack maintainer=M5Stack sentence=Unified library for M5Stack series @@ -8,4 +8,4 @@ category=Display url=https://github.com/m5stack/M5Unified.git architectures=esp32 includes=M5Unified.h -depends=M5GFX (>=0.2.29) +depends=M5GFX (>=0.2.30) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 139b9da0..bd3bb63a 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1,3883 +1,65 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -#include "M5Unified.hpp" -#include "utility/PI4IOE5V6408_Class.hpp" -#include "utility/M5IOE1_Class.hpp" - -#if !defined (M5UNIFIED_PC_BUILD) -#include -#include -#include -#if __has_include () -#include -#endif - -#if defined (CONFIG_IDF_TARGET_ESP32P4) -#include -#endif - -#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - #if __has_include () - #include - #elif __has_include () - #include - #endif -#endif - -#if __has_include () -#include -#endif - -#if __has_include () - #include - #if ESP_IDF_VERSION_MAJOR >= 4 - /// [[fallthrough]]; - #define NON_BREAK ;[[fallthrough]]; - #endif - -#endif - -#include "utility/m5unified_i2s.h" - -#include "utility/led/LED_Strip_Class.hpp" -#include "utility/led/LED_PMIC_Class.hpp" -#include "utility/led/LED_PowerHub_Class.hpp" -#include "utility/led/LED_PaperMono_Class.hpp" - -#if defined (ARDUINO) && defined (CONFIG_IDF_TARGET_ESP32P4) -// ESP-Hosted (Arduino core HAL) の SDIO ピン設定。弱参照にして、WiFi/BLE を使わないビルドには -// ESP-Hosted のコードを引き込まない (リンカが解決しなければ nullptr になる)。 -extern "C" bool hostedSetPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst) __attribute__((weak)); -extern "C" bool hostedIsInitialized(void) __attribute__((weak)); -#endif - -#endif - -/// [[fallthrough]]; -#ifndef NON_BREAK -#define NON_BREAK ; -#endif - -/// global instance. -m5::M5Unified M5; - -void __attribute((weak)) adc_power_acquire(void) -{ -#if !defined (M5UNIFIED_PC_BUILD) -#if defined (ESP_IDF_VERSION_VAL) - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(3, 3, 4) - adc_power_on(); - #endif -#else - adc_power_on(); -#endif -#endif -} - -namespace m5 -{ -int8_t M5Unified::_get_pin_table[pin_name_max]; - -#if defined (M5UNIFIED_PC_BUILD) - void M5Unified::_setup_pinmap(board_t) - { - std::fill(_get_pin_table, _get_pin_table + pin_name_max, 255); - } -#else -// Pin number table. Place unknown at the end of the table. -// If there is no corresponding value, the value of unknown is used. -// ピン番号テーブル。 unknownをテーブルの最後に配置する。該当が無い場合はunknownの値が使用される。 -static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { - // In SCL,SDA, EX SCL,SDA -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, GPIO_NUM_11,GPIO_NUM_12 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5StackCoreS3SE,GPIO_NUM_11,GPIO_NUM_12, GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5StackChan , GPIO_NUM_11, GPIO_NUM_12, GPIO_NUM_1, GPIO_NUM_2 }, -{ board_t::board_M5StickS3 , GPIO_NUM_48,GPIO_NUM_47 , GPIO_NUM_10,GPIO_NUM_9 }, -{ board_t::board_M5StampS3 , 255 ,255 , GPIO_NUM_15,GPIO_NUM_13 }, -{ board_t::board_M5Capsule , GPIO_NUM_10,GPIO_NUM_8 , GPIO_NUM_15,GPIO_NUM_13 }, -{ board_t::board_M5Dial , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, -{ board_t::board_M5DinMeter , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, -{ board_t::board_M5AirQ , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, -{ board_t::board_M5Cardputer , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5CardputerADV,GPIO_NUM_9 ,GPIO_NUM_8 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5VAMeter , GPIO_NUM_6 ,GPIO_NUM_5 , GPIO_NUM_9 ,GPIO_NUM_8 }, -{ board_t::board_M5AtomS3R , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5AtomS3RExt , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5AtomVoiceS3R,GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5AtomS3RCam , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5PaperS3 , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5PaperDIY , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_42,GPIO_NUM_41 }, -{ board_t::board_M5StampPLC , GPIO_NUM_15,GPIO_NUM_13 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5PowerHub , GPIO_NUM_48,GPIO_NUM_45 , GPIO_NUM_16,GPIO_NUM_15 }, -{ board_t::board_M5StampS3Bat , GPIO_NUM_47,GPIO_NUM_48 , 255 ,255 }, -{ board_t::board_M5PaperColor , GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_5 ,GPIO_NUM_4 }, -{ board_t::board_M5ChainCaptain,GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_6 ,GPIO_NUM_7 }, -{ board_t::board_M5PaperMono , GPIO_NUM_48,GPIO_NUM_47 , 255 ,255 }, -{ board_t::board_M5StopWatch , GPIO_NUM_48,GPIO_NUM_47 , GPIO_NUM_11,GPIO_NUM_10 }, -{ board_t::board_unknown , GPIO_NUM_39,GPIO_NUM_38 , GPIO_NUM_1 ,GPIO_NUM_2 }, // AtomS3,AtomS3Lite,AtomS3U -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -{ board_t::board_unknown , 255 ,255 , GPIO_NUM_0 ,GPIO_NUM_1 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -{ board_t::board_M5UnitC6L ,GPIO_NUM_8 ,GPIO_NUM_10 , 255 ,255 }, -{ board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, -{ board_t::board_M5NanoC6 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -{ board_t::board_M5CoreMatrix , GPIO_NUM_1 ,GPIO_NUM_0 , GPIO_NUM_1 ,GPIO_NUM_0 }, // Grove shares the internal bus (level-shifted) -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -{ board_t::board_M5NanoH2 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32P4) -{ board_t::board_M5CoreP4X , GPIO_NUM_9 ,GPIO_NUM_11 , GPIO_NUM_16,GPIO_NUM_18 }, // CoreP4X -{ board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31 , GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 -{ board_t::board_M5UnitPoEP4 , GPIO_NUM_1 ,GPIO_NUM_0 , GPIO_NUM_54,GPIO_NUM_53 }, -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -{ board_t::board_M5StampC5 , 255 ,255 , 255 ,255 }, -{ board_t::board_M5ToughC5 , GPIO_NUM_3 ,GPIO_NUM_2 , GPIO_NUM_3 ,GPIO_NUM_2 }, // PortA は内部バスと同一 (レベルシフタ経由の物理分配) -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -#else -{ board_t::board_M5Stack , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_22,GPIO_NUM_21 }, -{ board_t::board_M5Paper , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_32,GPIO_NUM_25 }, -{ board_t::board_M5TimerCam , GPIO_NUM_14,GPIO_NUM_12 , GPIO_NUM_13,GPIO_NUM_4 }, -{ board_t::board_M5AtomLite , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, -{ board_t::board_M5AtomMatrix , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, -{ board_t::board_M5AtomVoice , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, -{ board_t::board_M5AtomU , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, -{ board_t::board_M5AtomPsram , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, -{ board_t::board_unknown , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_33,GPIO_NUM_32 }, // Core2,Tough,StickC,CoreInk,Station,StampPico -#endif -}; - -static constexpr const uint8_t _pin_table_port_bc[][5] = { - //pB p1,p2, pC p1,p2 (p1 close to 5V) -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, GPIO_NUM_8 ,GPIO_NUM_9 , GPIO_NUM_18,GPIO_NUM_17 }, -{ board_t::board_M5StackCoreS3SE,GPIO_NUM_8,GPIO_NUM_9 , GPIO_NUM_18,GPIO_NUM_17 }, -{ board_t::board_M5StackChan , GPIO_NUM_8, GPIO_NUM_9, GPIO_NUM_18, GPIO_NUM_17 }, -{ board_t::board_M5Dial , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, -{ board_t::board_M5DinMeter , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, -{ board_t::board_M5PowerHub , 255 , 255 , GPIO_NUM_1 ,GPIO_NUM_2 }, -{ board_t::board_M5ChainCaptain,GPIO_NUM_17,GPIO_NUM_18, 255 ,255 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -{ board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, -{ board_t::board_ArduinoNessoN1,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -#elif defined (CONFIG_IDF_TARGET_ESP32P4) -{ board_t::board_M5Tab5 , GPIO_NUM_17,GPIO_NUM_52, GPIO_NUM_7 ,GPIO_NUM_6 }, // Tab5 -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -{ board_t::board_M5ToughC5 , GPIO_NUM_1 ,GPIO_NUM_6 , GPIO_NUM_12,GPIO_NUM_11 }, -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else -{ board_t::board_M5Stack , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_16,GPIO_NUM_17 }, -{ board_t::board_M5StackCore2 , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_13,GPIO_NUM_14 }, -{ board_t::board_M5Tough , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_13,GPIO_NUM_14 }, -{ board_t::board_M5Paper , GPIO_NUM_33,GPIO_NUM_26 , GPIO_NUM_19,GPIO_NUM_18 }, -{ board_t::board_M5Station , GPIO_NUM_35,GPIO_NUM_25 , GPIO_NUM_13,GPIO_NUM_14 }, -#endif -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -}; - -static constexpr const uint8_t _pin_table_port_de[][5] = { - //pD p1,p2, pE p1,p2 -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, 14,10, 18,17 }, -{ board_t::board_M5StackCoreS3SE,14,10,18,17 }, -{ board_t::board_M5StackChan, 14, 10, 18, 17 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else -{ board_t::board_M5Stack , GPIO_NUM_34,GPIO_NUM_35 , GPIO_NUM_5 ,GPIO_NUM_13 }, -{ board_t::board_M5StackCore2 , GPIO_NUM_34,GPIO_NUM_35 , GPIO_NUM_27,GPIO_NUM_19 }, -{ board_t::board_M5Station , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_16,GPIO_NUM_17 }, // B2 / C2 -#endif -{ board_t::board_unknown , 255 ,255 , 255 ,255 }, -}; - -static constexpr const uint8_t _pin_table_sd[][7] = { - // clk,cmd(MOSI),D0(MISO),D1,D2,D3(CS) -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5StackCoreS3SE,GPIO_NUM_36,GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5StackChan , GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5Capsule , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_39, 255 , 255 , GPIO_NUM_11 }, -{ board_t::board_M5Cardputer , GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, -{ board_t::board_M5CardputerADV,GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, -{ board_t::board_M5PaperS3 , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, -{ board_t::board_M5PaperDIY , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, -{ board_t::board_M5StampPLC , GPIO_NUM_7, GPIO_NUM_8, GPIO_NUM_9, 255 , 255 , GPIO_NUM_10 }, -{ board_t::board_M5PaperColor , GPIO_NUM_15, GPIO_NUM_13, GPIO_NUM_14, 255 , 255 , GPIO_NUM_47 }, -{ board_t::board_M5PaperMono , GPIO_NUM_13, GPIO_NUM_12, GPIO_NUM_11, GPIO_NUM_10, GPIO_NUM_9, GPIO_NUM_8 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -{ board_t::board_M5CoreMatrix , GPIO_NUM_25, GPIO_NUM_27, GPIO_NUM_26, 255 , 255 , GPIO_NUM_28 }, -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -#elif defined (CONFIG_IDF_TARGET_ESP32P4) -{ board_t::board_M5CoreP4X , GPIO_NUM_10, GPIO_NUM_7 , GPIO_NUM_8 , 255 , 255 , GPIO_NUM_50 }, -{ board_t::board_M5Tab5 , GPIO_NUM_43, GPIO_NUM_44, GPIO_NUM_39, GPIO_NUM_40, GPIO_NUM_41, GPIO_NUM_42 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -{ board_t::board_M5ToughC5 , GPIO_NUM_9 , GPIO_NUM_7 , GPIO_NUM_8 , 255 , 255 , GPIO_NUM_10 }, -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else -{ board_t::board_M5Stack , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_19, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5StackCore2 , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5Tough , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, 255 , 255 , GPIO_NUM_4 }, -{ board_t::board_M5Paper , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_13, 255 , 255 , GPIO_NUM_4 }, -#endif -{ board_t::board_unknown , 255 , 255 , 255 , 255 , 255 , 255 }, -}; - -static constexpr const uint8_t _pin_table_other0[][2] = { - //RGBLED -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5AtomS3U , GPIO_NUM_35 }, -{ board_t::board_M5AtomS3Lite , GPIO_NUM_35 }, -{ board_t::board_M5StampS3 , GPIO_NUM_21 }, -{ board_t::board_M5StampPLC , GPIO_NUM_21 }, -{ board_t::board_M5AirQ , GPIO_NUM_21 }, -{ board_t::board_M5Dial , GPIO_NUM_21 }, -{ board_t::board_M5DinMeter , GPIO_NUM_21 }, -{ board_t::board_M5Capsule , GPIO_NUM_21 }, -{ board_t::board_M5Cardputer , GPIO_NUM_21 }, -{ board_t::board_M5CardputerADV,GPIO_NUM_21 }, -{ board_t::board_M5PaperColor , GPIO_NUM_21 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -{ board_t::board_M5StampC3 , GPIO_NUM_2 }, -{ board_t::board_M5StampC3U , GPIO_NUM_2 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -{ board_t::board_M5NanoC6 , GPIO_NUM_20 }, -{ board_t::board_M5UnitC6L , GPIO_NUM_2 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -{ board_t::board_M5NanoH2 , GPIO_NUM_11 }, -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else -{ board_t::board_M5Stack , GPIO_NUM_15 }, -{ board_t::board_M5StackCore2 , GPIO_NUM_25 }, -{ board_t::board_M5Station , GPIO_NUM_4 }, -{ board_t::board_M5AtomLite , GPIO_NUM_27 }, -{ board_t::board_M5AtomMatrix , GPIO_NUM_27 }, -{ board_t::board_M5AtomVoice , GPIO_NUM_27 }, -{ board_t::board_M5AtomU , GPIO_NUM_27 }, -{ board_t::board_M5AtomPsram , GPIO_NUM_27 }, -{ board_t::board_M5StampPico , GPIO_NUM_27 }, -#endif -{ board_t::board_unknown , 255 }, -}; - -static constexpr const uint8_t _pin_table_other1[][2] = { - //POWER_HOLD -#if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5Dial , GPIO_NUM_46 }, -{ board_t::board_M5Capsule , GPIO_NUM_46 }, -{ board_t::board_M5AirQ , GPIO_NUM_46 }, -{ board_t::board_M5DinMeter , GPIO_NUM_46 }, -{ board_t::board_M5PaperS3 , GPIO_NUM_44 }, - -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else - -{ board_t::board_M5StickCPlus2 , GPIO_NUM_4 }, -{ board_t::board_M5Paper , GPIO_NUM_2 }, -{ board_t::board_M5StackCoreInk, GPIO_NUM_12 }, -{ board_t::board_M5TimerCam , GPIO_NUM_33 }, - -#endif -{ board_t::board_unknown , 255 }, -}; - -static constexpr const uint8_t _pin_table_mbus[][31] = { -#if defined (CONFIG_IDF_TARGET_ESP32P4) -{ board_t::board_M5CoreP4X, - 255 , GPIO_NUM_17, - 255 , GPIO_NUM_20, - 255 , 255 , - GPIO_NUM_7 , GPIO_NUM_21, - GPIO_NUM_8 , GPIO_NUM_22, - GPIO_NUM_10, 255 , - GPIO_NUM_38, GPIO_NUM_37, - GPIO_NUM_15, GPIO_NUM_14, - GPIO_NUM_11, GPIO_NUM_9 , - GPIO_NUM_18, GPIO_NUM_16, - GPIO_NUM_39, GPIO_NUM_12, - GPIO_NUM_34, GPIO_NUM_23, - 255 , GPIO_NUM_19, - 255 , 255 , - 255 , 255 , -}, -{ board_t::board_M5Tab5 , - 255 , GPIO_NUM_16, - 255 , GPIO_NUM_17, - 255 , 255 , - GPIO_NUM_18, GPIO_NUM_45, - GPIO_NUM_19, GPIO_NUM_52, - GPIO_NUM_5 , 255 , - GPIO_NUM_38, GPIO_NUM_37, - GPIO_NUM_7 , GPIO_NUM_6 , - GPIO_NUM_31, GPIO_NUM_32, - GPIO_NUM_3 , GPIO_NUM_4 , - GPIO_NUM_2 , GPIO_NUM_48, - GPIO_NUM_47, GPIO_NUM_35, - 255 , GPIO_NUM_51, - 255 , 255 , - 255 , 255 , -}, -#elif defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, - 255 , GPIO_NUM_10, - 255 , GPIO_NUM_8 , - 255 , 255 , - GPIO_NUM_37, GPIO_NUM_5 , - GPIO_NUM_35, GPIO_NUM_9 , - GPIO_NUM_36, 255 , - GPIO_NUM_44, GPIO_NUM_43, - GPIO_NUM_18, GPIO_NUM_17, - GPIO_NUM_12, GPIO_NUM_11, - GPIO_NUM_2 , GPIO_NUM_1 , - GPIO_NUM_6 , GPIO_NUM_7 , - GPIO_NUM_13, GPIO_NUM_0 , - 255 , GPIO_NUM_14, - 255 , 255 , - 255 , 255 , -}, -{ board_t::board_M5StackCoreS3SE, - 255 , GPIO_NUM_10, - 255 , GPIO_NUM_8 , - 255 , 255 , - GPIO_NUM_37, GPIO_NUM_5 , - GPIO_NUM_35, GPIO_NUM_9 , - GPIO_NUM_36, 255 , - GPIO_NUM_44, GPIO_NUM_43, - GPIO_NUM_18, GPIO_NUM_17, - GPIO_NUM_12, GPIO_NUM_11, - GPIO_NUM_2 , GPIO_NUM_1 , - GPIO_NUM_6 , GPIO_NUM_7 , - GPIO_NUM_13, GPIO_NUM_0 , - 255 , GPIO_NUM_14, - 255 , 255 , - 255 , 255 , -}, -{ board_t::board_M5StackChan, - 255 , GPIO_NUM_10, - 255 , GPIO_NUM_8 , - 255 , 255 , - GPIO_NUM_37, GPIO_NUM_5 , - GPIO_NUM_35, GPIO_NUM_9 , - GPIO_NUM_36, 255 , - GPIO_NUM_44, GPIO_NUM_43, - GPIO_NUM_18, GPIO_NUM_17, - GPIO_NUM_12, GPIO_NUM_11, - GPIO_NUM_2 , GPIO_NUM_1 , - GPIO_NUM_6 , GPIO_NUM_7 , - GPIO_NUM_13, GPIO_NUM_0 , - 255 , GPIO_NUM_14, - 255 , 255 , - 255 , 255 , -}, -#elif defined (CONFIG_IDF_TARGET_ESP32C3) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) -#elif defined (CONFIG_IDF_TARGET_ESP32C61) -{ board_t::board_M5CoreMatrix, - 255 , GPIO_NUM_3 , - 255 , GPIO_NUM_4 , - 255 , 255 , - GPIO_NUM_27, GPIO_NUM_5 , - GPIO_NUM_26, GPIO_NUM_6 , - GPIO_NUM_25, 255 , - GPIO_NUM_10, GPIO_NUM_11, - GPIO_NUM_7 , GPIO_NUM_8 , - GPIO_NUM_0 , GPIO_NUM_1 , - GPIO_NUM_0 , GPIO_NUM_1 , - GPIO_NUM_23, GPIO_NUM_22, - GPIO_NUM_24, GPIO_NUM_9 , - 255 , GPIO_NUM_29, - 255 , 255 , - 255 , 255 , -}, -#elif defined (CONFIG_IDF_TARGET_ESP32H2) -#elif defined (CONFIG_IDF_TARGET_ESP32C5) -#elif defined (CONFIG_IDF_TARGET_ESP32S2) -#else -{ board_t::board_M5Stack , - 255 , GPIO_NUM_35, - 255 , GPIO_NUM_36, - 255 , 255 , - GPIO_NUM_23, GPIO_NUM_25, - GPIO_NUM_19, GPIO_NUM_26, - GPIO_NUM_18, 255 , - GPIO_NUM_3 , GPIO_NUM_1 , - GPIO_NUM_16, GPIO_NUM_17, - GPIO_NUM_21, GPIO_NUM_22, - GPIO_NUM_2 , GPIO_NUM_5 , - GPIO_NUM_12, GPIO_NUM_13, - GPIO_NUM_15, GPIO_NUM_0 , - 255 , GPIO_NUM_34, - 255 , 255 , - 255 , 255 , -}, -{ board_t::board_M5StackCore2, - 255 , GPIO_NUM_35, - 255 , GPIO_NUM_36, - 255 , 255 , - GPIO_NUM_23, GPIO_NUM_25, - GPIO_NUM_38, GPIO_NUM_26, - GPIO_NUM_18, 255 , - GPIO_NUM_3 , GPIO_NUM_1 , - GPIO_NUM_13, GPIO_NUM_14, - GPIO_NUM_21, GPIO_NUM_22, - GPIO_NUM_32, GPIO_NUM_33, - GPIO_NUM_27, GPIO_NUM_19, - GPIO_NUM_2 , GPIO_NUM_0 , - 255 , GPIO_NUM_34, - 255 , 255 , - 255 , 255 , -}, -{ board_t::board_M5Tough, - 255 , GPIO_NUM_35, - 255 , GPIO_NUM_36, - 255 , 255 , - GPIO_NUM_23, GPIO_NUM_25, - GPIO_NUM_38, GPIO_NUM_26, - GPIO_NUM_18, 255 , - GPIO_NUM_3 , GPIO_NUM_1 , - GPIO_NUM_13, GPIO_NUM_14, - GPIO_NUM_21, GPIO_NUM_22, - GPIO_NUM_32, GPIO_NUM_33, - GPIO_NUM_27, GPIO_NUM_19, - GPIO_NUM_2 , GPIO_NUM_0 , - 255 , GPIO_NUM_34, - 255 , 255 , - 255 , 255 , -}, -#endif -{ board_t::board_unknown , 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, -}; - - void M5Unified::_setup_pinmap(board_t id) - { - if (id == board_t::board_M5Tab5X) { - id = board_t::board_M5Tab5; - } - - constexpr const std::pair tbl[] = { - { _pin_table_i2c_ex_in, sizeof(_pin_table_i2c_ex_in[0]) }, - { _pin_table_port_bc, sizeof(_pin_table_port_bc[0]) }, - { _pin_table_port_de, sizeof(_pin_table_port_de[0]) }, - { _pin_table_sd, sizeof(_pin_table_sd[0]) }, - { _pin_table_other0, sizeof(_pin_table_other0[0]) }, - { _pin_table_other1, sizeof(_pin_table_other1[0]) }, - { _pin_table_mbus, sizeof(_pin_table_mbus[0]) }, - }; - - int8_t* dst = _get_pin_table; - for (auto &p : tbl) { - const uint8_t* t = (uint8_t*)p.first; - size_t len = p.second; - while (t[0] != id && t[0] != board_t::board_unknown) { t += len; } - memcpy(dst, &t[1], len - 1); - dst += len - 1; - } - } -#endif - - /// @return true when every write in the table was acknowledged. - static bool in_i2c_bulk_write(const uint8_t i2c_addr, const uint8_t* bulk_data, const uint32_t i2c_freq = 100000u, const uint8_t retry = 0) - { - // bulk_data example.. - // const uint8_t bulk_data[] = { - // 2, 0x00, 0x00, // <- datalen = 2, reg = 0x00, data = 0x00 - // 3, 0x01, 0x00, 0x02, // <- datalen = 3, reg = 0x01, data = 0x00, 0x02 - // 0 }; // <- datalen 0 is end of data. - - bool all_ok = true; - while (*bulk_data) { - uint8_t len = *bulk_data++; - uint8_t r = retry + 1; - while (!M5.In_I2C.writeRegister(i2c_addr, bulk_data[0], &bulk_data[1], len - 1, i2c_freq) && --r) { m5gfx::delay(1); } - all_ok &= (r != 0); - bulk_data += len; - } - return all_ok; - } - - static constexpr uint8_t es7210_i2c_addr = 0x40; - static constexpr uint8_t es8311_i2c_addr0 = 0x18; - static constexpr uint8_t es8311_i2c_addr1 = 0x19; - -#if defined (CONFIG_IDF_TARGET_ESP32S3) - /// The ES8311 arms its capture path only through a SYSTEM(0x0D) write done - /// while the I2S clock is running; a pre-clock write is absorbed silently - /// (it even reads back). The arming is needed once per codec power-on - /// reset and survives I2C power-down/up cycles. - static std::atomic es8311_capture_armed { false }; - - /// Post-start callback: arms the capture path once the I2S clock runs. - /// The analog stage then warms up on its own (~1 s after power-up); that - /// is chip physics, so begin() pays only for the arming here. - static bool _microphone_post_start_cb_stopwatch(void* args) - { - (void)args; - if (es8311_capture_armed.load(std::memory_order_acquire)) { return true; } - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); - bool ok = false; - bool last_ok = false; - for (int i = 0; i < 3; ++i) - { // writes at ~+30/+60/+90 ms after the clock start; the earliest arming - // observed on hardware is ~+25 ms, the later points are the margin - m5gfx::delay(30); - last_ok = M5.In_I2C.writeRegister8(es8311_i2c_addr0, 0x0D, 0x01, 100000); - ok |= last_ok; - } - backup_i2c_setting.restore(); - /// an acknowledge proves only the transport, so the latch requires the - /// latest (most conservative) write to have been acknowledged - if (last_ok) { es8311_capture_armed.store(true, std::memory_order_release); } - return ok; - } -#endif - static constexpr uint8_t es8388_i2c_addr = 0x10; - static constexpr uint8_t pi4io1_i2c_addr = 0x43; - static constexpr uint8_t m5pm1_i2c_addr = 0x6E; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr uint8_t aw88298_i2c_addr = 0x36; - static constexpr uint8_t aw9523_i2c_addr = 0x58; - static constexpr uint8_t powerhub_i2c_addr = 0x50; - static void aw88298_write_reg(uint8_t reg, uint16_t value) - { - value = __builtin_bswap16(value); - M5.In_I2C.writeRegister(aw88298_i2c_addr, reg, (const uint8_t*)&value, 2, 400000); - } - - static void es7210_write_reg(uint8_t reg, uint8_t value) - { - M5.In_I2C.writeRegister(es7210_i2c_addr, reg, &value, 1, 400000); - } - -#endif - - bool M5Unified::_speaker_enabled_cb_core2(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32) - auto self = (M5Unified*)args; - auto spk_cfg = self->Speaker.config(); - if (spk_cfg.pin_bck == GPIO_NUM_12 - && spk_cfg.pin_ws == GPIO_NUM_0 - && spk_cfg.pin_data_out == GPIO_NUM_2 - ) { - switch (self->Power.getType()) { - case m5::Power_Class::pmic_axp192: - self->Power.Axp192.setGPIO2(enabled); - break; - case m5::Power_Class::pmic_axp2101: - self->Power.Axp2101.setALDO3(enabled * 3300); - break; - default: - break; - } - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_cores3(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto spk_cfg = self->Speaker.config(); - if (spk_cfg.pin_bck == GPIO_NUM_34 && enabled) - { - Power_Class::_core_s3_aw9523_bit(0x02, 0b00000100, true); - /// サンプリングレートに応じてAW88298のレジスタの設定値を変える; - static constexpr uint8_t rate_tbl[] = {4,5,6,8,10,11,15,20,22,44}; - size_t reg0x06_value = 0; - size_t rate = (spk_cfg.sample_rate + 1102) / 2205; - while (rate > rate_tbl[reg0x06_value] && ++reg0x06_value < sizeof(rate_tbl)) {} - - reg0x06_value |= 0x14C0; // I2SBCK=0 (BCK mode 16*2) - aw88298_write_reg( 0x61, 0x0673 ); // boost mode disabled - aw88298_write_reg( 0x04, 0x4040 ); // I2SEN=1 AMPPD=0 PWDN=0 - aw88298_write_reg( 0x05, 0x0008 ); // RMSE=0 HAGCE=0 HDCCE=0 HMUTE=0 - aw88298_write_reg( 0x06, reg0x06_value ); - aw88298_write_reg( 0x0C, 0x0064 ); // volume setting (full volume) - } - else /// disableにする場合および内蔵スピーカ以外を操作対象とした場合、内蔵スピーカを停止する。 - { - aw88298_write_reg( 0x04, 0x4000 ); // I2SEN=0 AMPPD=0 PWDN=0 - Power_Class::_core_s3_aw9523_bit(0x02, 0b00000100, false); - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_sticks3(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto spk_cfg = self->Speaker.config(); - if (spk_cfg.pin_bck == GPIO_NUM_17) - { - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - if (enabled) - { - self->In_I2C.bitOn(m5pm1_i2c_addr, 0x11, 0b00001000, 100000); - ESP_LOGD("M5Unified", "enabling es8311\n"); - in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - } - else /// disableにする場合および内蔵スピーカ以外を操作対象とした場合、内蔵スピーカを停止する。 - { - ESP_LOGD("M5Unified", "disabling es8311\n"); - self->In_I2C.bitOff(m5pm1_i2c_addr, 0x11, 0b00001000, 100000); - } -//*/ - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_papercolor(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto spk_cfg = self->Speaker.config(); - gpio_num_t codec_en_pin = GPIO_NUM_45; - gpio_num_t spk_en_pin = GPIO_NUM_46; - m5gfx::pinMode(codec_en_pin, m5gfx::pin_mode_t::output); - m5gfx::pinMode(spk_en_pin, m5gfx::pin_mode_t::output); - if (spk_cfg.pin_bck == GPIO_NUM_40) - { - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xCF, // 0x32 DAC/ DAC volume (0xCF == +16 dB ) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - if (enabled) - { - m5gfx::gpio_hi(codec_en_pin); - m5gfx::gpio_hi(spk_en_pin); - in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - } - else - { - m5gfx::gpio_lo(codec_en_pin); - m5gfx::gpio_lo(spk_en_pin); - } - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_stopwatch(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xCB, // 0x32 DAC/ DAC volume +6 dB (0xBF == 0 dB, 0.5 dB/step). Reaches full-scale at max master volume; higher values clip digitally without adding loudness - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - if (enabled) - { - auto& ioe1 = self->getIOExpander(0); - ioe1.digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) - self->delay(10); - in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - ioe1.digitalWrite(M5IOE1_Class::gpio10, true); // Enable PA (M5IOE1_G10) - } - else - { /// Keep Audio Power (M5IOE1_G3) on: cutting it forces another codec - /// reset, re-arming and re-warm-up on the next capture (and a quickly - /// cycled rail misfires). Only the DAC is powered down; Mic disable - /// does the deeper I2C power-down. - auto& ioe1 = self->getIOExpander(0); - ioe1.digitalWrite(M5IOE1_Class::gpio10, false); // Disable PA (M5IOE1_G10) - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x12, 0x02, // 0x12 SYSTEM/ power-down DAC - 0 - }; - in_i2c_bulk_write(es8311_i2c_addr0, disabled_bulk_data, 100000, 3); - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_chain_captain(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xEF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - if (enabled) - { - self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail - self->delay(10); - in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - m5gfx::gpio_hi(GPIO_NUM_21); // AW8737A one-wire enable, default mode - } - else - { - m5gfx::gpio_lo(GPIO_NUM_21); - self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, false); - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_tab5(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32P4) - auto self = (M5Unified*)args; - auto spk_cfg = self->Speaker.config(); - if (spk_cfg.pin_data_out != GPIO_NUM_26) { return false; } - - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0, 0x80, // RESET/ CSM POWER ON - 2, 0, 0x00, - 2, 0, 0x00, - 2, 0, 0x0E, - 2, 1, 0x00, - 2, 2, 0x0A, //CHIP POWER: power up all - 2, 3, 0xFF, //ADC POWER: power down all - 2, 4, 0x3C, //DAC POWER: power up and LOUT1/ROUT1/LOUT2/ROUT2 enable - 2, 5, 0x00, //ChipLowPower1 - 2, 6, 0x00, //ChipLowPower2 - 2, 7, 0x7C, //VSEL - 2, 8, 0x00, //set I2S slave mode - // reg9-22 == adc - 2, 23, 0x18, //I2S format (16bit) - 2, 24, 0x00, //I2S MCLK ratio (128) - 2, 25, 0x20, //DAC unmute - 2, 26, 0x00, //LDACVOL 0x00~0xC0 - 2, 27, 0x00, //RDACVOL 0x00~0xC0 - 2, 28, 0x08, //enable digital click free power up and down - 2, 29, 0x00, - 2, 38, 0x00, //DAC CTRL16 - 2, 39, 0xB8, //LEFT Ch MIX - 2, 42, 0xB8, //RIGHTCh MIX - 2, 43, 0x08, //ADC and DAC separate - 2, 45, 0x00, // 0x00=1.5k VREF analog output / 0x10=40kVREF analog output - 2, 46, 0x21, - 2, 47, 0x21, - 2, 48, 0x21, - 2, 49, 0x21, - 0 - }; - if (enabled) - { - in_i2c_bulk_write(es8388_i2c_addr, enabled_bulk_data); - // AMP on - M5.In_I2C.bitOn(pi4io1_i2c_addr, 0x05, 0b00000010, 400000); - } - else - { // 正規の power-down シーケンス。end() は cb(false) を I2S 停止より先に呼ぶため、 - // ここは MCLK/BCLK が生きているうちに実行される。旧実装 (reg8 のみ書き込み) では - // DAC 稼働状態のままクロックが絶たれ、次回 enable の reset が毎回異なる残留状態 - // から行われて受信位相が begin ごとに不定になっていた。毎回同一の power-down - // 状態へ落としてから終了することで、次回 enable を常に定義済み状態から始める。 - M5.In_I2C.bitOff(pi4io1_i2c_addr, 0x05, 0b00000010, 400000); // AMP off (過渡音を出さない) - M5.In_I2C.writeRegister8(es8388_i2c_addr, 25, 0x24, 400000); // DACCONTROL3: mute (SoftRamp 維持) - m5gfx::delay(1); // soft-ramp 遷移待ち - M5.In_I2C.writeRegister8(es8388_i2c_addr, 4, 0xC0, 400000); // DACPOWER: DAC L/R down + 全出力 off - M5.In_I2C.writeRegister8(es8388_i2c_addr, 2, 0xFF, 400000); // CHIPPOWER: 全停止 (ADF deinit と同一の終端状態) - } -#endif - return true; - } - - static void _corep4x_audio_power(M5Unified* self, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32P4) - // Speaker and microphone share M5IOE1_G1, so keep the rail enabled at runtime. - if (!enabled) { return; } - auto& ioe1 = self->getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); - ioe1.setDirection(M5IOE1_Class::gpio1, true); - ioe1.digitalWrite(M5IOE1_Class::gpio1, true); - self->delay(20); -#else - (void)self; - (void)enabled; -#endif - } - - bool M5Unified::_speaker_enabled_cb_corep4x(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32P4) - auto self = (M5Unified*)args; - static constexpr const uint8_t enabled_bulk_data[] = { - // ES8311 slave, 24 kHz, 16-bit I2S, MCLK = 256 * sample rate. - 2, 0x0D, 0xFA, - 2, 0x44, 0x08, - 2, 0x44, 0x08, - 2, 0x01, 0x30, - 2, 0x02, 0x00, - 2, 0x03, 0x10, - 2, 0x16, 0x24, - 2, 0x04, 0x10, - 2, 0x05, 0x00, - 2, 0x0B, 0x00, - 2, 0x0C, 0x00, - 2, 0x10, 0x1F, - 2, 0x11, 0x7F, - 2, 0x00, 0x80, - 2, 0x01, 0x3F, - 2, 0x06, 0x03, - 2, 0x13, 0x10, - 2, 0x1B, 0x0A, - 2, 0x1C, 0x6A, - 2, 0x44, 0x58, - 2, 0x09, 0x00, - 2, 0x17, 0xBF, - 2, 0x0E, 0x02, - 2, 0x12, 0x00, - 2, 0x14, 0x1A, - 2, 0x0D, 0x01, - 2, 0x15, 0x40, - 2, 0x37, 0x08, - 2, 0x45, 0x00, - 2, 0x07, 0x00, - 2, 0x08, 0xFF, - 2, 0x32, 0xBF, - 0 - }; - _corep4x_audio_power(self, enabled); - auto& ioe1 = self->getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); - ioe1.setDirection(M5IOE1_Class::gpio3, true); - ioe1.digitalWrite(M5IOE1_Class::gpio3, enabled); - if (enabled) - { - in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - } -#else - (void)args; - (void)enabled; -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_hat_spk(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32) - auto self = (M5Unified*)args; - gpio_num_t pin_en = self->_board == board_t::board_M5StackCoreInk ? GPIO_NUM_25 : GPIO_NUM_0; - if (enabled) - { - m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); - m5gfx::gpio_hi(pin_en); - } - else - { m5gfx::gpio_lo(pin_en); } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_atomic_echo(void* args, bool enabled) - { - (void)args; - (void)enabled; - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xFF, // 0x32 DAC/ DAC volume (full volume) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 0 - }; - - static constexpr const uint8_t enabled_pi4ioe_bulk_data[] = { - 2, 0x03, 0xFF, // PI4IOE direction:OUTPUT - 2, 0x05, 0xFF, // PI4IOE output HIGH - 2, 0x07, 0x00, // PI4IOE set push-pull - 2, 0x0B, 0x00, // Disable pull (up and down) - 0 - }; - static constexpr const uint8_t disabled_pi4ioe_bulk_data[] = { - 2, 0x05, 0x00, - 0 - }; - -#if defined (CONFIG_IDF_TARGET_ESP32S3) - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_38, GPIO_NUM_39); -#endif - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); - in_i2c_bulk_write(pi4io1_i2c_addr, enabled ? enabled_pi4ioe_bulk_data : disabled_pi4ioe_bulk_data); -#if defined (CONFIG_IDF_TARGET_ESP32S3) - backup_i2c_setting.restore(); -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_stickc(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32) - auto self = (M5Unified*)args; - self->Power.Axp192.setLDO0(enabled ? 2800 : 0); -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_cores3(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto cfg = self->Mic.config(); - if (cfg.pin_bck == GPIO_NUM_34) - { - es7210_write_reg(0x00, 0xFF); // RESET_CTL - struct __attribute__((packed)) reg_data_t - { - uint8_t reg; - uint8_t value; - }; - if (enabled) - { - static constexpr reg_data_t data[] = - { - { 0x00, 0x41 }, // RESET_CTL - { 0x01, 0x1f }, // CLK_ON_OFF - { 0x06, 0x00 }, // DIGITAL_PDN - { 0x07, 0x20 }, // ADC_OSR - { 0x08, 0x10 }, // MODE_CFG - { 0x09, 0x30 }, // TCT0_CHPINI - { 0x0A, 0x30 }, // TCT1_CHPINI - { 0x20, 0x0a }, // ADC34_HPF2 - { 0x21, 0x2a }, // ADC34_HPF1 - { 0x22, 0x0a }, // ADC12_HPF2 - { 0x23, 0x2a }, // ADC12_HPF1 - { 0x02, 0xC1 }, - { 0x04, 0x01 }, - { 0x05, 0x00 }, - { 0x11, 0x60 }, - { 0x40, 0x42 }, // ANALOG_SYS - { 0x41, 0x70 }, // MICBIAS12 - { 0x42, 0x70 }, // MICBIAS34 - { 0x43, 0x1B }, // MIC1_GAIN - { 0x44, 0x1B }, // MIC2_GAIN - { 0x45, 0x00 }, // MIC3_GAIN - { 0x46, 0x00 }, // MIC4_GAIN - { 0x47, 0x00 }, // MIC1_LP - { 0x48, 0x00 }, // MIC2_LP - { 0x49, 0x00 }, // MIC3_LP - { 0x4A, 0x00 }, // MIC4_LP - { 0x4B, 0x00 }, // MIC12_PDN - { 0x4C, 0xFF }, // MIC34_PDN - { 0x01, 0x14 }, // CLK_ON_OFF - }; - for (auto& d: data) - { - es7210_write_reg(d.reg, d.value); - } - } - } -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_cardputer_adv(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 0 - }; - - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); -#endif - return true; - } - - - bool M5Unified::_microphone_enabled_cb_atomic_echo(void* args, bool enabled) - { - (void)args; - (void)enabled; - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator - 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) - 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) - 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry - 2, 0x0E, 0x6A, // 0x0E SYSTEM - 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN - 0 - }; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_38, GPIO_NUM_39); -#endif - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); -#if defined (CONFIG_IDF_TARGET_ESP32S3) - backup_i2c_setting.restore(); -#endif - - return true; - } - - bool M5Unified::_microphone_enabled_cb_atom_echos3r(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator - 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) - 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) - 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry - 2, 0x0E, 0x6A, // 0x0E SYSTEM - 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN - 0 - }; - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_45, GPIO_NUM_0); - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); - backup_i2c_setting.restore(); -#endif - return true; - } - - bool M5Unified::_speaker_enabled_cb_atom_echos3r(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default - 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xFF, // 0x32 DAC/ DAC volume (full volume) - 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 0 - }; - - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_45, GPIO_NUM_0); - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); - gpio_num_t pin_en = GPIO_NUM_18; - if (enabled) - { - m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); - m5gfx::gpio_hi(pin_en); - } - else - { m5gfx::gpio_lo(pin_en); } - backup_i2c_setting.restore(); -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_sticks3(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator - 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) - 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) - 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry - 2, 0x0E, 0x6A, // 0x0E SYSTEM - 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN - 0 - }; - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); // sda, scl - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); - backup_i2c_setting.restore(); -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_papercolor(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto cfg = self->Mic.config(); - gpio_num_t codec_en_pin = GPIO_NUM_45; - m5gfx::pinMode(codec_en_pin, m5gfx::pin_mode_t::output); - m5gfx::gpio_hi(codec_en_pin); - delay(50); - if (cfg.pin_bck == GPIO_NUM_40) - { - es7210_write_reg(0x00, 0xFF); // RESET_CTL - struct __attribute__((packed)) reg_data_t - { - uint8_t reg; - uint8_t value; - }; - if (enabled) - { - static constexpr reg_data_t data[] = - { - { 0x00, 0x41 }, // RESET_CTL - { 0x01, 0x1f }, // CLK_ON_OFF - { 0x06, 0x00 }, // DIGITAL_PDN - { 0x07, 0x20 }, // ADC_OSR - { 0x08, 0x10 }, // MODE_CFG - { 0x09, 0x30 }, // TCT0_CHPINI - { 0x0A, 0x30 }, // TCT1_CHPINI - { 0x20, 0x0a }, // ADC34_HPF2 - { 0x21, 0x2a }, // ADC34_HPF1 - { 0x22, 0x0a }, // ADC12_HPF2 - { 0x23, 0x2a }, // ADC12_HPF1 - { 0x02, 0xC1 }, - { 0x04, 0x01 }, - { 0x05, 0x00 }, - { 0x11, 0x60 }, - { 0x40, 0x42 }, // ANALOG_SYS - { 0x41, 0x70 }, // MICBIAS12 - { 0x42, 0x70 }, // MICBIAS34 - { 0x43, 0x1B }, // MIC1_GAIN - { 0x44, 0x00 }, // MIC2_GAIN - { 0x45, 0x00 }, // MIC3_GAIN - { 0x46, 0x00 }, // MIC4_GAIN - { 0x47, 0x00 }, // MIC1_LP - { 0x48, 0x00 }, // MIC2_LP - { 0x49, 0x00 }, // MIC3_LP - { 0x4A, 0x00 }, // MIC4_LP - { 0x4B, 0x00 }, // MIC12_PDN - { 0x4C, 0xFF }, // MIC34_PDN - { 0x01, 0x14 }, // CLK_ON_OFF - }; - for (auto& d: data) - { - es7210_write_reg(d.reg, d.value); - } - } - } -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_papermono(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - auto& ioe1 = self->getIOExpander(0); - bool result = true; - if (enabled) - { - // The factory firmware enables PM1 BOOST before powering the PDM mic. - result = self->Power.M5pm1.setExtOutput(true); - } - // M5IOE1 GPIO12 is the Paper Mono PDM microphone power enable. - ioe1.setHighImpedance(M5IOE1_Class::gpio12, false); - ioe1.setDirection(M5IOE1_Class::gpio12, true); - ioe1.digitalWrite(M5IOE1_Class::gpio12, enabled); - return result; -#else - (void)args; - (void)enabled; -#endif - return true; - } - - - bool M5Unified::_microphone_enabled_cb_stopwatch(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator - 2, 0x14, 0x17, // ES8311_ADC_REG14 : select Mic1p-Mic1n / analog PGA +21 dB (gain code 7, 3 dB/code; SNR plateaus by this level) - 2, 0x17, 0xCB, // ES8311_ADC_REG17 : ADC_VOLUME +6 dB (0xBF == 0 dB, 0.5 dB/step) - 2, 0x1C, 0x64, // ES8311_ADC_REG1C : ADC EQ bypass, dynamic HPF, HPF stage-2 coeff 4 (cuts sub-200 Hz rumble at the default 16 kHz rate) - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry - 2, 0x0E, 0x6A, // 0x0E SYSTEM - 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN - 0 - }; - if (enabled) - { - self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) - self->delay(5); - } - m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); - if (enabled) - { /// 0x17 loses the value a previous setup wrote on any codec reset, - /// so it witnesses a reset done outside this library: re-arm then. - uint8_t v = 0; - if (!M5.In_I2C.readRegister(es8311_i2c_addr0, 0x17, &v, 1, 100000) || v != 0xCB) - { - es8311_capture_armed.store(false, std::memory_order_release); - } - } - bool setup_ok = in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); - backup_i2c_setting.restore(); - /// a codec with an incomplete setup must not be published as working - if (enabled && !setup_ok) { return false; } -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_chain_captain(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - auto self = (M5Unified*)args; - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // RESET / CSM power on - 2, 0x01, 0xBA, // MCLK from BCLK - 2, 0x02, 0x18, // clock multiplier - 2, 0x0D, 0x01, // power up analog circuitry - 2, 0x0E, 0x02, // enable analog PGA and ADC modulator - 2, 0x14, 0x10, // differential microphone input, minimum PGA gain - 2, 0x17, 0xFF, // ADC volume - 2, 0x1C, 0x6A, // bypass ADC equalizer and cancel DC offset - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, - 2, 0x0E, 0x6A, - 2, 0x00, 0x00, - 0 - }; - if (enabled) - { - self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail - self->delay(5); - } - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); -#endif - return true; - } - -#if defined (CONFIG_IDF_TARGET_ESP32) && SOC_TOUCH_SENSOR_SUPPORTED - /// @param channel touch channel ids. (ESP-IDF v6 removed the touch_pad_t enum, so plain integers are used here) - /// @return true = all channels were read successfully. - static bool _read_touch_pad(uint32_t* results, const int* channel, const size_t channel_count) - { - for (size_t i = 0; i < channel_count; ++i) { results[i] = 0; } -#if defined ( TOUCH_SENSOR_DEFAULT_FILTER_CONFIG ) - if (channel_count > TOUCH_TOTAL_CHAN_NUM) { return false; } - - /* Step 1: Create a new touch sensor controller handle with default sample configuration */ - touch_sensor_sample_config_t sample_cfg = {}; - sample_cfg.charge_duration_ms = 5.0f; - sample_cfg.charge_volt_lim_h = TOUCH_VOLT_LIM_H_1V7; - sample_cfg.charge_volt_lim_l = TOUCH_VOLT_LIM_L_0V5; - - touch_sensor_config_t sens_cfg = {}; - sens_cfg.power_on_wait_us = 256; - sens_cfg.meas_interval_us = 320.0; - sens_cfg.intr_trig_mode = TOUCH_INTR_TRIG_ON_BELOW_THRESH; - sens_cfg.intr_trig_group = TOUCH_INTR_TRIG_GROUP_BOTH; - sens_cfg.sample_cfg_num = 1; - sens_cfg.sample_cfg = &sample_cfg; - touch_sensor_handle_t sens_handle = nullptr; - if (touch_sensor_new_controller(&sens_cfg, &sens_handle) != ESP_OK) { return false; } - - touch_channel_config_t chan_cfg = {}; - chan_cfg.abs_active_thresh[0] = 1024; - chan_cfg.charge_speed = TOUCH_CHARGE_SPEED_7; - chan_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT; - chan_cfg.group = TOUCH_CHAN_TRIG_GROUP_BOTH; - touch_channel_handle_t chan_handle[TOUCH_TOTAL_CHAN_NUM] = { nullptr, }; - size_t created = 0; - while (created < channel_count - && touch_sensor_new_channel(sens_handle, channel[created], &chan_cfg, &chan_handle[created]) == ESP_OK) { - ++created; - } - bool result = false; - if (created == channel_count) { - touch_sensor_filter_config_t filter_cfg = TOUCH_SENSOR_DEFAULT_FILTER_CONFIG(); - if (touch_sensor_config_filter(sens_handle, &filter_cfg) == ESP_OK) { - bool scanned = false; - if (touch_sensor_enable(sens_handle) == ESP_OK) { - scanned = (touch_sensor_trigger_oneshot_scanning(sens_handle, 64) == ESP_OK); - touch_sensor_disable(sens_handle); - } - if (scanned) { - result = true; - for (size_t i = 0; i < channel_count; i++) { - result &= (touch_channel_read_data(chan_handle[i], TOUCH_CHAN_DATA_TYPE_SMOOTH, &results[i]) == ESP_OK); - } - } - // Passing nullptr releases the software filter timer. (del_controller does not) - touch_sensor_config_filter(sens_handle, nullptr); - } - } - while (created) { - touch_sensor_del_channel(chan_handle[--created]); - } - touch_sensor_del_controller(sens_handle); - return result; -#else - if (touch_pad_init() != ESP_OK) { return false; } - bool result = true; - for (size_t i = 0; i < channel_count; i++) { - result &= (touch_pad_config((touch_pad_t)channel[i], TOUCH_PAD_THRESHOLD_MAX) == ESP_OK); - } - for (size_t i = 0; i < channel_count; i++) { - uint16_t tmp = 0; - result &= (touch_pad_read((touch_pad_t)channel[i], &tmp) == ESP_OK); - results[i] = tmp; - } - touch_pad_deinit(); - return result; -#endif - } -#endif - - bool M5Unified::_microphone_enabled_cb_tab5(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32P4) - auto self = (M5Unified*)args; - auto cfg = self->Mic.config(); - if (cfg.pin_data_in != GPIO_NUM_28) { return false; } - - M5.In_I2C.writeRegister8(es7210_i2c_addr, 0x00, 0xFF, 400000); - if (enabled) - { - static constexpr uint8_t data[] = - { - 2, 0x00, 0x41, // RESET_CTL - 2, 0x01, 0x1f, // CLK_ON_OFF - 2, 0x06, 0x00, // DIGITAL_PDN - 2, 0x07, 0x20, // ADC_OSR - 2, 0x08, 0x10, // MODE_CFG - 2, 0x09, 0x30, // TCT0_CHPINI - 2, 0x0A, 0x30, // TCT1_CHPINI - 2, 0x20, 0x0a, // ADC34_HPF2 - 2, 0x21, 0x2a, // ADC34_HPF1 - 2, 0x22, 0x0a, // ADC12_HPF2 - 2, 0x23, 0x2a, // ADC12_HPF1 - 2, 0x02, 0xC1, - 2, 0x04, 0x01, - 2, 0x05, 0x00, - 2, 0x11, 0x60, - 2, 0x40, 0x42, // ANALOG_SYS - 2, 0x41, 0x70, // MICBIAS12 - 2, 0x42, 0x70, // MICBIAS34 - 2, 0x43, 0x1B, // MIC1_GAIN - 2, 0x44, 0x1B, // MIC2_GAIN - 2, 0x45, 0x00, // MIC3_GAIN - 2, 0x46, 0x00, // MIC4_GAIN - 2, 0x47, 0x00, // MIC1_LP - 2, 0x48, 0x00, // MIC2_LP - 2, 0x49, 0x00, // MIC3_LP - 2, 0x4A, 0x00, // MIC4_LP - 2, 0x4B, 0x00, // MIC12_PDN - 2, 0x4C, 0xFF, // MIC34_PDN - 2, 0x01, 0x14, // CLK_ON_OFF - 0, - }; - in_i2c_bulk_write(es7210_i2c_addr, data); - } -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_corep4x(void* args, bool enabled) - { -#if defined (CONFIG_IDF_TARGET_ESP32P4) - auto self = (M5Unified*)args; - _corep4x_audio_power(self, enabled); - self->In_I2C.writeRegister8(es7210_i2c_addr, 0x00, 0xFF, 400000); - if (enabled) - { - static constexpr uint8_t data[] = - { - 2, 0x00, 0x41, // RESET_CTL - 2, 0x01, 0x1f, // CLK_ON_OFF - 2, 0x06, 0x00, // DIGITAL_PDN - 2, 0x07, 0x20, // ADC_OSR - 2, 0x08, 0x10, // MODE_CFG - 2, 0x09, 0x30, // TCT0_CHPINI - 2, 0x0A, 0x30, // TCT1_CHPINI - 2, 0x20, 0x0a, // ADC34_HPF2 - 2, 0x21, 0x2a, // ADC34_HPF1 - 2, 0x22, 0x0a, // ADC12_HPF2 - 2, 0x23, 0x2a, // ADC12_HPF1 - 2, 0x02, 0xC1, - 2, 0x04, 0x01, - 2, 0x05, 0x00, - 2, 0x11, 0x60, - 2, 0x40, 0x42, // ANALOG_SYS - 2, 0x41, 0x70, // MICBIAS12 - 2, 0x42, 0x70, // MICBIAS34 - 2, 0x43, 0x1B, // MIC1_GAIN - 2, 0x44, 0x1B, // MIC2_GAIN - 2, 0x45, 0x1B, // MIC3_GAIN (AEC input) - 2, 0x46, 0x1B, // MIC4_GAIN (TDM slot 4) - 2, 0x47, 0x00, // MIC1_LP - 2, 0x48, 0x00, // MIC2_LP - 2, 0x49, 0x00, // MIC3_LP - 2, 0x4A, 0x00, // MIC4_LP - 2, 0x4B, 0x00, // MIC12_PDN - 2, 0x4C, 0x00, // MIC34_PDN - 2, 0x01, 0x14, // CLK_ON_OFF - 0, - }; - in_i2c_bulk_write(es7210_i2c_addr, data, 100000, 3); - } -#else - (void)args; - (void)enabled; -#endif - return true; - } - - bool M5Unified::_microphone_enabled_cb_cardputer_adv(void* args, bool enabled) - { - (void)args; - (void)enabled; -#if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr const uint8_t enabled_bulk_data[] = { - 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON - 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK - 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 - 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry - 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator - 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) - 2, 0x17, 0xBF, // ES8311_ADC_REG17 : ADC_VOLUME 0xBF == ± 0 dB - 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain - 0 - }; - static constexpr const uint8_t disabled_bulk_data[] = { - 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry - 2, 0x0E, 0x6A, // 0x0E SYSTEM - 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN - 0 - }; - - in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); -#endif - return true; - } - -#if defined (M5UNIFIED_PC_BUILD) -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - static constexpr gpio_num_t TFCARD_CS_PIN = GPIO_NUM_4; - static constexpr gpio_num_t CoreInk_BUTTON_EXT_PIN = GPIO_NUM_5; - static constexpr gpio_num_t CoreInk_BUTTON_PWR_PIN = GPIO_NUM_27; -#endif - - /// probe を始める前に一度だけ、デバイスの電源が安定するのを待つ。 - /// 待ちが要るのは「電源投入から間もない」ことであってアドレスごとの事情ではないので、 - /// 判定の入口で一度払う。旧実装は最初の probe が常に真を返して連鎖が止まっていたため - /// 結果的に 1 回しか待っていなかった。それを意図として書き直したもの。 - void M5Unified::_wait_i2c_device_power(void) - { -#if !defined(M5UNIFIED_PC_BUILD) - static bool waited = false; - if (!waited) - { - waited = true; - m5gfx::delay(50); - } -#endif - } - - bool M5Unified::_probe_i2c_addr(uint8_t sda, uint8_t scl, uint8_t addr) - { -#if defined(M5UNIFIED_PC_BUILD) - return false; -#else - /// アドレスの存在確認はソフトウェア I2C ポートで行う (オープンドレイン駆動で - /// ACK 競合が起きず、ハードウェアのペリフェラルにも触れない)。 - /// ポートは M5GFX の autodetect probe (-1) と分ける。ソフト I2C のスロットは - /// 所有権を持たず、init が既存の設定を黙って奪う作りなので、ライブラリ同士で - /// 同じスロットを共有しない。 - static constexpr int_fast16_t probe_i2c_port = -2; - - _wait_i2c_device_power(); - - m5gfx::gpio::pin_backup_t pin_backup[] = { scl, sda }; - - // ここでは待たない。電源安定待ちは判定の入口で一度だけ行う (_wait_i2c_device_power)。 - // アドレスごとに待つと、判定が空振りするたびに数十 ms が起動時間へ積み上がる。 - m5gfx::pinMode(scl, m5gfx::pin_mode_t::input_pullup); - m5gfx::pinMode(sda, m5gfx::pin_mode_t::input_pullup); - - // 指定ピンが「外部プルアップの載った I2C バス」かどうかを先に確かめる。 - // ここは I2C ピンとは限らない場所を駆動する機種判別なので、判定を外すと - // 機種の読みが変わる。判定方式は M5GFX の autodetect probe と同一のものを使う - // (実機での実績がある形から動かさない。変えるなら該当機種すべてで再検証が要る)。 - // - // 4 回の read のうち、後半 2 回は入力プルダウンにしても High になること - // (= 外部プルアップが内部プルダウンに勝つこと) を確認するもの。 - // 弱いプルアップ (内部プルダウンと同程度の抵抗値) では通らないが、 - // 「強いプルアップがある = I2C バスである」を要求するのがこの判定の趣旨。 - const uint8_t cmd_bus_check_list[] = { - m5gfx::gpio::command_write_low , scl, - m5gfx::gpio::command_read , scl, // low チェック - m5gfx::gpio::command_write_low , sda, - m5gfx::gpio::command_read , sda, // low チェック - m5gfx::gpio::command_mode_input_pulldown, scl, - m5gfx::gpio::command_delay_usec , 10, - m5gfx::gpio::command_read , scl, // 外部プルアップがあるなら High - m5gfx::gpio::command_mode_input_pullup , scl, - m5gfx::gpio::command_mode_input_pulldown, sda, - m5gfx::gpio::command_delay_usec , 10, - m5gfx::gpio::command_read , sda, // 外部プルアップがあるなら High - m5gfx::gpio::command_mode_input_pullup , sda, - m5gfx::gpio::command_end - }; - auto bus_check = [&](void) -> uint32_t - { - // ラッチを Low にしてから出力へ切り替える。直前は input_pullup (ラッチ High) - // なので、先に出力にすると一瞬 push-pull で High を駆動してしまう。 - // ここは相手が何か分からないピンなので、High は一度も駆動しない。 - m5gfx::gpio_lo(scl); m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); - m5gfx::gpio_lo(sda); m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); - return m5gfx::gpio::command(cmd_bus_check_list); - }; - - // 0x02 は「SCL は外部プルアップで戻るのに SDA だけ Low のまま」。前回の通信の - // 途中で止まったデバイスがデータ線を握っている典型で (ソフトリセットでは - // デバイスの電源が切れないため実際に起きる)、プルアップの無いただの Low ピンとは - // このシグネチャで区別できる。この場合だけは STOP を見せて握りを解かせ、 - // もう一度確かめる。それ以外の不一致は I2C バスではないとみなして即座に帰る。 - uint32_t check = bus_check(); - if (check != 0x03 && check != 0x02) - { - for (auto& backup : pin_backup) { backup.restore(); } - return false; - } - - // 前回の通信の途中で止まっているデバイスに STOP を見せて論理状態を戻す。 - // START だけで戻らないデバイスが実在する (同ファイルの StampS3/Capsule 判別に - // 「STOP を出さないと正しく動作しないデバイスがあった (UnitHEART MAX30100)」の記録がある)。 - // 線を Low へ駆動するか解放するかの 2 状態しか使わない (High を駆動しない) ので、 - // デバイスが線を握っていてもパッド同士の衝突にはならない。 - { - auto line_lo = [](uint8_t pin) - { m5gfx::gpio_lo(pin); m5gfx::pinMode(pin, m5gfx::pin_mode_t::output); }; - auto line_hi = [](uint8_t pin) - { m5gfx::pinMode(pin, m5gfx::pin_mode_t::input_pullup); }; - for (int i = 0; i < 8; ++i) - { - line_lo(scl); m5gfx::delayMicroseconds(5); - line_lo(sda); m5gfx::delayMicroseconds(5); - line_hi(scl); m5gfx::delayMicroseconds(5); - line_hi(sda); m5gfx::delayMicroseconds(5); // SCL High 中の SDA Low->High = STOP - } - } - - if (check != 0x03 && bus_check() != 0x03) - { // 握りが解けなかった。ここから先は probe しても意味がない。 - for (auto& backup : pin_backup) { backup.restore(); } - return false; - } - - bool hit = false; - if (m5gfx::i2c::init(probe_i2c_port, sda, scl).has_value()) - { - // クロックが上がらないバス、前の通信の途中でデバイスがデータ線を握っている - // バスは、いずれも beginTransaction 側が検出して復旧または中断する。 - // NACK の場合も beginTransaction 自体は成功を返し (内部で STOP を出して - // エラーをラッチする)、そのエラーは endTransaction が報告する。 - // したがって両方の成功をもって「ACK が返った」と判定する。 - hit = m5gfx::i2c::beginTransaction(probe_i2c_port, addr, 100000, false).has_value() - && m5gfx::i2c::endTransaction(probe_i2c_port).has_value(); - m5gfx::i2c::release(probe_i2c_port); - } - for (auto& backup : pin_backup) { - backup.restore(); - } - return hit; -#endif - } - - board_t M5Unified::_check_boardtype(board_t board) - { -#if defined (M5UNIFIED_PC_BUILD) -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - if (board == board_t::board_unknown) - { - switch (m5gfx::get_pkg_ver()) - { - case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ6: - board = board_t::board_M5TimerCam; - break; - - case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4: - { - m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_2, GPIO_NUM_13, GPIO_NUM_19, GPIO_NUM_22, GPIO_NUM_27, GPIO_NUM_33, GPIO_NUM_34 }; - m5gfx::pinMode(GPIO_NUM_34, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input_pullup); - board = board_t::board_M5StampPico; - if (m5gfx::gpio_in(GPIO_NUM_2)) // Branches other than StampPico ( StampPico G2 is always LOW ) - { - board = board_t::board_M5AtomU; - if (m5gfx::gpio_in(GPIO_NUM_34)) { // Branches other than AtomU ( AtomU G34 is always LOW ) - board = board_t::board_M5AtomMatrix; -#if SOC_TOUCH_SENSOR_SUPPORTED -/* G27(RGBLED)に対してタッチセンサを用い、容量の差に基づいて Matrix の識別を行う。 - G27に対してタッチセンサを使用すると、得られる値は Lite/ECHOの方が大きく、Matrixの方が小さい。 - なおタッチセンサの値には個体差があるため、判定の基準として絶対値ではなく G13(NC)のタッチセンサ値を比較に用いる。 -*/ - uint32_t results[2] = { 0, 0 }; - static constexpr int s_channel_id[] = { - 4, //Touch pad channel 4 is GPIO13(ESP32) - 7, //Touch pad channel 7 is GPIO27(ESP32) - }; - bool touch_ok = _read_touch_pad(results, s_channel_id, 2); - - int diff = (results[1] * 3 - results[0]); - // M5_LOGV("G13 = %d / G27 = %d / diff = %d", results[0], results[1], diff); - // true==(Lite/ECHO) / false==AtomMatrix (on read failure, keep the AtomMatrix default) - if (touch_ok && diff >= 0) -#else -/* - タッチセンサAPIが使えない場合の処理 (ESP-IDFのバージョンに依る) - GPIOの立上り速度の差を用いて LiteとMatrix の識別を行う。 - (Matrixの方がinput_pullupでHIGHになるまでの時間が長いため、この性質を利用して判定する) -*/ - portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; - uint32_t g27 = 0; - // 8回読み取って立上り速度の差を見る - for (int i = 0; i < 8; ++i) - { - lgfx::pinMode(GPIO_NUM_27, lgfx::pin_mode_t::input_pulldown); - delay(1); - taskENTER_CRITICAL(&mux); - lgfx::pinMode(GPIO_NUM_27, lgfx::pin_mode_t::input_pullup); - g27 += lgfx::gpio_in(GPIO_NUM_27); - taskEXIT_CRITICAL(&mux); - } - - // Branches other than AtomMatrix ( AtomMatrix G27 is delayed from becoming HIGH ) - if (g27 > 4) -#endif - { - auto result = m5gfx::gpio::command( - (const uint8_t[]) { - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_22, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_19, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_33, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_33, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_19, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_22, - m5gfx::gpio::command_read , GPIO_NUM_33, - m5gfx::gpio::command_read , GPIO_NUM_19, - m5gfx::gpio::command_read , GPIO_NUM_22, - m5gfx::gpio::command_mode_input , GPIO_NUM_33, - m5gfx::gpio::command_mode_input , GPIO_NUM_19, - m5gfx::gpio::command_mode_input , GPIO_NUM_22, - m5gfx::gpio::command_delay , 1, - m5gfx::gpio::command_read , GPIO_NUM_33, - m5gfx::gpio::command_read , GPIO_NUM_19, - m5gfx::gpio::command_read , GPIO_NUM_22, - m5gfx::gpio::command_end - } - ); - // G19 G22 G33 = ECHOのI2Sスピーカ用ピン。プルアップを無効化するとすぐにLOWになるため、この性質を利用して判定する。 - // なお当該ピンに何かを外付けしている場合は判定に失敗する可能性がある。 - board = board_t::board_M5AtomLite; - if ((result) == 0b111000) - { // Branches for AtomECHO - board = board_t::board_M5AtomVoice; - } - } - } - } - for (auto &backup : pin_backup) { - backup.restore(); - } - } - break; - - case 6: // EFUSE_RD_CHIP_VER_PKG_ESP32PICOV3_02: // ATOM PSRAM - board = board_t::board_M5AtomPsram; - break; - - default: - -#if defined ( ARDUINO_M5STACK_CORE_ESP32 ) || defined ( ARDUINO_M5STACK_FIRE ) || defined ( ARDUINO_M5Stack_Core_ESP32 ) - - board = board_t::board_M5Stack; - -#elif defined ( ARDUINO_M5STACK_CORE2 ) || defined ( ARDUINO_M5STACK_Core2 ) - - board = board_t::board_M5StackCore2; - -#elif defined ( ARDUINO_M5STICK_C ) || defined ( ARDUINO_M5Stick_C ) - - board = board_t::board_M5StickC; - -#elif defined ( ARDUINO_M5STICK_C_PLUS ) || defined ( ARDUINO_M5Stick_C_Plus ) - - board = board_t::board_M5StickCPlus; - -#elif defined ( ARDUINO_M5STACK_COREINK ) || defined ( ARDUINO_M5Stack_CoreInk ) - - board = board_t::board_M5StackCoreInk; - -#elif defined ( ARDUINO_M5STACK_PAPER ) || defined ( ARDUINO_M5STACK_Paper ) - - board = board_t::board_M5Paper; - -#elif defined ( ARDUINO_M5STACK_TOUGH ) - - board = board_t::board_M5Tough; - -#elif defined ( ARDUINO_M5STACK_ATOM ) || defined ( ARDUINO_M5Stack_ATOM ) - - board = board_t::board_M5AtomLite; - -#elif defined ( ARDUINO_M5STACK_TIMER_CAM ) || defined ( ARDUINO_M5Stack_Timer_CAM ) - - board = board_t::board_M5TimerCam; - -#endif - break; - } - } - -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - - switch (m5gfx::get_pkg_ver()) - { - default: - case 0: // EFUSE_PKG_VERSION_ESP32S3: // QFN56 - if (board == board_t::board_unknown) - { - #if defined (BOARD_ID) && BOARD_ID == 147 - board = board_t::board_M5DualKey; - // !!!NOTE: Set to input mode to prevent the device from failing to shut down. - m5gfx::pinMode(GPIO_NUM_7, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_8, m5gfx::pin_mode_t::input); - break; - #endif - /// StampS3 or AtomS3Lite,S3U ? - /// After setting GPIO38 to INPUT PULL-UP, change to INPUT and read it. - /// In the case of STAMPS3: Returns 0. Charge is sucked by SGM2578. - /// In the case of ATOMS3Lite/S3U : Returns 1. Charge remains. ( Since it is not connected to anywhere. ) - /// - /// AtomS3Lite or AtomS3U ? - /// After setting GPIO4 to INPUT PULL-UP, read it. - /// In the case of ATOMS3Lite : Returns 0. Charge is sucked by InfraRed. - /// In the case of ATOMS3U : Returns 1. Charge remains. ( Since it is not connected to anywhere. ) - - m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_4, GPIO_NUM_8, GPIO_NUM_10, GPIO_NUM_12, GPIO_NUM_38 }; - auto result = m5gfx::gpio::command( - (const uint8_t[]) { - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_4, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_12, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_38, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_8, - m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_10, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_4, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_12, - m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_38, - m5gfx::gpio::command_read , GPIO_NUM_8, - m5gfx::gpio::command_read , GPIO_NUM_10, - m5gfx::gpio::command_read , GPIO_NUM_4, - m5gfx::gpio::command_read , GPIO_NUM_12, - m5gfx::gpio::command_read , GPIO_NUM_38, - m5gfx::gpio::command_mode_input , GPIO_NUM_38, - m5gfx::gpio::command_delay , 1, - m5gfx::gpio::command_read , GPIO_NUM_38, - m5gfx::gpio::command_end - } - ); - /// result には、command_read で得たGPIOの状態が1bitずつ4回分入っている。 - board = ((const board_t[]) - { // ↓StampS3 pattern↓ - board_t::board_unknown, board_t::board_unknown, board_t::board_M5StampS3, board_t::board_unknown, // ← unknown - board_t::board_M5AtomS3Lite,board_t::board_M5AtomS3Lite,board_t::board_unknown , board_t::board_M5AtomS3Lite, // ← AtomS3Lite pattern - board_t::board_M5AtomS3U, board_t::board_M5AtomS3U, board_t::board_M5StampS3, board_t::board_M5AtomS3U, // ← AtomS3U pattern - board_t::board_unknown, board_t::board_unknown, board_t::board_M5StampS3, board_t::board_unknown, // ← unknown - })[result&15]; - if ((result & 3) == 2) { // StampS3 pattern - if ((result >> 3) == 0b110) { - board = board_t::board_M5Capsule; - // 自動検出の際。PortAに余分な波形が出ているので、一度 I2C STOPコンディションを出しておく。 - // ※ これをしないと正しく動作しないデバイスが存在した。UnitHEART MAX30100 - m5gfx::gpio::command( - (const uint8_t[]) { - m5gfx::gpio::command_mode_output, GPIO_NUM_15, - m5gfx::gpio::command_write_low , GPIO_NUM_15, - m5gfx::gpio::command_mode_output, GPIO_NUM_13, - m5gfx::gpio::command_write_low , GPIO_NUM_13, - m5gfx::gpio::command_write_high , GPIO_NUM_15, - m5gfx::gpio::command_write_high , GPIO_NUM_13, - m5gfx::gpio::command_end - } - ); - } - } - for (auto &backup : pin_backup) { - backup.restore(); - } - } - - if (board == board_t::board_unknown) { - /// PowerHub ? - if (_probe_i2c_addr(45, 48, 0x50)) { - board = board_t::board_M5PowerHub; - } - } - break; - - case 1: // EFUSE_PKG_VERSION_ESP32S3PICO: // LGA56 - if (board == board_t::board_unknown) { - /// 内部 I2C バス (SDA45/SCL0) に載るデバイスで先に決める。他ピンの probe を - /// 間に挟まないので、ここで確定する機種は 48/47 に一切触れずに済む - /// (AtomVoiceS3R では GPIO48 が I2S の DOUT)。 - /// - /// AtomS3RExt / AtomS3RCam have a BMI270 on the internal I2C bus. - /// この 2 機種は基板が共通で、カメラ部がユーザーの扱えるブレッドボードに - /// なっているため、内部バスにユーザーのデバイスが載りうる。オンボードで - /// 必ず存在する BMI270 を先に見て、後から載ったアドレスに identity を - /// 奪われないようにする。 - if (_probe_i2c_addr(45, 0, 0x68) - || _probe_i2c_addr(45, 0, 0x69)) { - board = board_t::board_M5AtomS3RExt; - } - /// AtomVoiceS3R ? - else if (_probe_i2c_addr(45, 0, 0x18)) { - board = board_t::board_M5AtomVoiceS3R; - } - /// Stamp-S3Bat ? (内部バスに何も居なかったときだけ別のピンを触る) - else if (_probe_i2c_addr(48, 47, 0x6E)) { - board = board_t::board_M5StampS3Bat; - } - /// StampS3Mini has no other onboard device that can identify it. - else { - board = board_t::board_M5StampS3Mini; - } - } - - if (board == board_t::board_M5AtomS3RExt) - { /// AtomS3RCam or AtomS3RExt ? - // Cam = GC0308 = I2C 7bit addr = 0x21 - // CamM12 = OV3660 = I2C 7bit addr = 0x3C - m5gfx::gpio_lo(GPIO_NUM_18); - m5gfx::pinMode(GPIO_NUM_18, m5gfx::pin_mode_t::output); - m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_9, GPIO_NUM_12, GPIO_NUM_21 }; - { // G9=SCL, G12=SDA, G21=XCLK - m5gfx::gpio::command( - (const uint8_t[]) { - m5gfx::gpio::command_write_low, GPIO_NUM_9, - m5gfx::gpio::command_mode_output, GPIO_NUM_9, // SCL - m5gfx::gpio::command_write_low, GPIO_NUM_12, - m5gfx::gpio::command_mode_output, GPIO_NUM_12, // SDA - m5gfx::gpio::command_mode_output, GPIO_NUM_21, // XCL - m5gfx::gpio::command_write_high, GPIO_NUM_9, - m5gfx::gpio::command_write_high, GPIO_NUM_21, - m5gfx::gpio::command_write_high, GPIO_NUM_12, - }); - auto lo_reg = m5gfx::get_gpio_lo_reg(GPIO_NUM_21); - auto hi_reg = m5gfx::get_gpio_hi_reg(GPIO_NUM_21); - - // prepare camera module (need XCLK signal) - for (int xclk = 32768 * 54; xclk != 0; --xclk) - { - *lo_reg = 1 << GPIO_NUM_21; - *hi_reg = 1 << GPIO_NUM_21; - } - uint32_t result = 0; - for (uint8_t i2caddr: (const uint8_t[]){ 0x3C << 1, 0x21 << 1 }) { - for (int xclk = 32768 * 2; xclk != 0; --xclk) { - *lo_reg = 1 << GPIO_NUM_21; - *hi_reg = 1 << GPIO_NUM_21; - } - bool nack = true; - // The camera module is identified using I2C communication via GPIO self-operation. - *lo_reg = 1 << GPIO_NUM_12; // SDA LOW = START - for (int cycle = 0; cycle < 20; ++cycle) { - for (int j = 0; j < 2; ++j) { - for (int xclk = 8; xclk != 0; --xclk) { - *lo_reg = 1 << GPIO_NUM_21; - *hi_reg = 1 << GPIO_NUM_21; - } - *((cycle & 1) ? hi_reg : lo_reg) = 1 << GPIO_NUM_9; // SCL - } - if (cycle & 1) { - if (cycle == 17) { - nack = m5gfx::gpio_in(GPIO_NUM_12); - } - } else { - *((i2caddr & 0x80) ? hi_reg : lo_reg) = 1 << GPIO_NUM_12; // SDA - i2caddr <<= 1; - if (cycle >= 16) { - m5gfx::pinMode(GPIO_NUM_12, (cycle == 16) ? m5gfx::pin_mode_t::input : m5gfx::pin_mode_t::output); - } - } - } - *hi_reg = 1 << GPIO_NUM_12; // SDA HIGH = STOP - result = result << 1 | nack; - } - // printf("CAM TEST RESULT: %08x \r\n", (int)result); - if (result == 1 || result == 2) { - // result == 1 : OV3660 - // result == 2 : GC0308 - board = board_t::board_M5AtomS3RCam; - } - } - for (auto &backup : pin_backup) { - backup.restore(); - } - } - } - - -#elif defined (CONFIG_IDF_TARGET_ESP32C3) - if (board == board_t::board_unknown) - { // StampC3 or StampC3U ? - uint32_t tmp = *((volatile uint32_t *)(IO_MUX_GPIO20_REG)); - m5gfx::pinMode(GPIO_NUM_20, m5gfx::pin_mode_t::input_pulldown); - // StampC3 has a strong external pull-up on GPIO20, which is HIGH even when input_pulldown is set. - // Therefore, if it is LOW, it is not StampC3 and can be assumed to be StampC3U. - // However, even if it goes HIGH, something may be connected to GPIO20 by StampC3U, so it is treated as unknown. - // The StampC3U determination uses the fallback_board setting. - if (m5gfx::gpio_in(GPIO_NUM_20) == false) - { - board = board_t::board_M5StampC3U; - } - *((volatile uint32_t *)(IO_MUX_GPIO20_REG)) = tmp; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32C6) - if (board == board_t::board_unknown) - { - if (m5gfx::get_pkg_ver() == 1) - { // QFN32 : NanoC6 = C6FH4 (FLASH_CAP=1) / StampC6 = C6FH8 (FLASH_CAP=2) - if (REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_4_REG, EFUSE_FLASH_CAP) == 2) - { - board = board_t::board_M5StampC6; - } - else - { // NanoC6 - board = board_t::board_M5NanoC6; - } - } - // QFN40 (PKG_VERSION=0) : NessoN1 / UnitC6L carry displays and are - // identified by M5GFX; an undetected QFN40 board stays unknown. - } - -#elif defined (CONFIG_IDF_TARGET_ESP32C5) - if (board == board_t::board_unknown) - { -#if defined (BOARD_ID) && BOARD_ID == 153 - board = board_t::board_M5StampC5; -#else - // StampC5 = ESP32-C5HF4 (in-package 4MB flash, no PSRAM) : FLASH_CAP=1, PSRAM_CAP=0 - // ToughC5 = ESP32-C5HR8 (8MB PSRAM) carries a display and is identified by M5GFX. - std::uint32_t sys2 = REG_READ(EFUSE_RD_MAC_SYS2_REG); - if (((sys2 >> EFUSE_FLASH_CAP_S) & EFUSE_FLASH_CAP_V) == 1 - && ((sys2 >> EFUSE_PSRAM_CAP_S) & EFUSE_PSRAM_CAP_V) == 0) - { - board = board_t::board_M5StampC5; - } -#endif - } - -#elif defined (CONFIG_IDF_TARGET_ESP32H2) - if (board == board_t::board_unknown) - { // NanoH2 - board = board_t::board_M5NanoH2; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32P4) - if (board == board_t::board_unknown) - { -#if defined (BOARD_ID) && BOARD_ID == 31 - board = board_t::board_M5CoreP4X; -#else - m5gfx::pinMode(GPIO_NUM_32, m5gfx::pin_mode_t::input_pulldown); - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input_pulldown); - if (m5gfx::gpio_in(GPIO_NUM_32)) // M5Tab5 and M5Tab5X G32 always High - { - esp_chip_info_t chip_info; - esp_chip_info(&chip_info); - board = chip_info.revision >= 300 - ? board_t::board_M5Tab5X - : board_t::board_M5Tab5; - } - else if(m5gfx::gpio_in(GPIO_NUM_0)) // M5UnitPoEP4 G0 always High - board = board_t::board_M5UnitPoEP4; - else - { - esp_chip_info_t chip_info; - esp_chip_info(&chip_info); - board = chip_info.revision >= 300 - ? board_t::board_M5StampP4X - : board_t::board_M5StampP4; - } -#endif - } - -#endif - - return board; - } - - void M5Unified::_setup_i2c(board_t board) - { -#if defined (M5UNIFIED_PC_BUILD) - (void)board; -#else - - gpio_num_t in_scl = (gpio_num_t)getPin(pin_name_t::in_i2c_scl); - gpio_num_t in_sda = (gpio_num_t)getPin(pin_name_t::in_i2c_sda); - gpio_num_t ex_scl = (gpio_num_t)getPin(pin_name_t::ex_i2c_scl); - gpio_num_t ex_sda = (gpio_num_t)getPin(pin_name_t::ex_i2c_sda); - - i2c_port_t ex_port = I2C_NUM_0; -#if SOC_I2C_NUM == 1 || defined (CONFIG_IDF_TARGET_ESP32C6) || defined (CONFIG_IDF_TARGET_ESP32C5) - i2c_port_t in_port = I2C_NUM_0; -// M5GFX が LP_I2C 対応をコンパイルする条件と同一に保つこと -// (条件を満たさない SDK では LP ポートを開けないため HP のまま運用する) -#if defined (CONFIG_IDF_TARGET_ESP32C5) && defined ( SOC_LP_I2C_NUM ) && ( SOC_LP_I2C_NUM > 0 ) \ - && __has_include ( ) && defined ( ESP_IDF_VERSION_VAL ) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) - if (board == board_t::board_M5ToughC5) - { /// 内部バス (G2/G3) は LP_I2C の固定パッドと一致するため LP ポートへ割り当てる。 - /// PortA も同じバスの物理分配なので Ex_I2C は同ポートを共有し (初代 BASIC と - /// 同じ形)、HP の I2C0 は丸ごと空く。 - in_port = LP_I2C_NUM_0; - ex_port = LP_I2C_NUM_0; - } -#endif -#else - i2c_port_t in_port = I2C_NUM_1; - if (in_scl == ex_scl && in_sda == ex_sda) { - in_port = ex_port; - } -#endif - if ((uint_fast8_t)in_scl < GPIO_NUM_MAX) - { - In_I2C.begin(in_port, in_sda, in_scl); - } - else - { - In_I2C.setPort(I2C_NUM_MAX, in_sda, in_scl); - } - - if ((uint_fast8_t)ex_scl < GPIO_NUM_MAX) - { - if ((in_port != ex_port) || (in_sda == ex_sda && in_scl == ex_scl) || ((uint_fast8_t)in_scl >= GPIO_NUM_MAX)) { - Ex_I2C.setPort(ex_port, ex_sda, ex_scl); - } - } - - switch (board) { -#if defined (CONFIG_IDF_TARGET_ESP32P4) - case board_t::board_M5CoreP4X: - { - auto ioexp = new M5IOE1_Class(0x4F); - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: - for (int i = 0; i < 2; ++i) - { - auto ioexp = new PI4IOE5V6408_Class(0x43 + i); - ioexp->begin(); - _io_expander[i].reset(ioexp); - } - break; -#elif defined (CONFIG_IDF_TARGET_ESP32C6) - case board_t::board_M5UnitC6L: - { - auto ioexp = new PI4IOE5V6408_Class(0x43); - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; - case board_t::board_ArduinoNessoN1: - for (int i = 0; i < 2; ++i) - { - auto ioexp = new PI4IOE5V6408_Class(0x43 + i); - ioexp->begin(); - _io_expander[i].reset(ioexp); - } - break; -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StampPLC: - { - auto ioexp = new PI4IOE5V6408_Class; - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; - case board_t::board_M5ChainCaptain: - case board_t::board_M5PaperMono: - case board_t::board_M5StopWatch: - { - auto ioexp = new M5IOE1_Class; - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; -#elif defined (CONFIG_IDF_TARGET_ESP32C5) - case board_t::board_M5ToughC5: - { /// LCD 電源/リセット/バックライトのほか TF 電源 (PYG6) と - /// TF カード検出 (PYG14) がこの IOE にぶら下がる - auto ioexp = new M5IOE1_Class; - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; -#elif defined (CONFIG_IDF_TARGET_ESP32C61) - case board_t::board_M5CoreMatrix: - { /// Controls the LED matrix / TF / Grove / buzzer power rails, - /// the charge current selector and the buzzer PWM. - auto ioexp = new M5IOE1_Class; - ioexp->begin(); - _io_expander[0].reset(ioexp); - } - break; -#endif - default: - break; - } -#endif - } - void M5Unified::_setup_led(board_t board) - { -#if !defined (M5UNIFIED_PC_BUILD) - int led_count = 1; - int byte_per_led = 3; - switch (board) - { -#if defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5PowerHub: - Led.setLedInstance(std::make_shared()); - return; - case board_t::board_M5StampS3Bat: - { - auto busled = std::make_shared(); - auto buscfg = busled->getConfig(); - buscfg.pin_data = 0; - buscfg.led_count = 1; - busled->setConfig(buscfg); - Led.setLedInstance(busled); - return; - } - case board_t::board_M5PaperMono: - Led.setLedInstance(std::make_shared()); - return; - - case board_t::board_M5PaperColor: - led_count = 2; - break; -#else - case board_t::board_M5AtomMatrix: - led_count = 25; - break; -#endif - default: - break; - } - - auto pin_rgb_led = M5.getPin(m5::pin_name_t::rgb_led); //Line: 181 - if (pin_rgb_led >= 0) - { - auto busled = std::make_shared(); - auto buscfg = busled->getConfig(); - buscfg.pin_data = pin_rgb_led; - busled->setConfig(buscfg); - auto led_strip = std::make_shared(); - auto ledcfg = led_strip->getConfig(); - ledcfg.led_count = led_count; - ledcfg.byte_per_led = byte_per_led; - led_strip->setBus(busled); - led_strip->setConfig(ledcfg); - Led.setLedInstance(led_strip); - } -#endif - } - - void M5Unified::_begin(const config_t& cfg) - { - /// setup power management ic - Power.begin(); - Power.setExtOutput(cfg.output_power); - if (cfg.led_brightness) - { - M5.Power.setLed(cfg.led_brightness); - } - auto pmic_type = Power.getType(); - if (pmic_type == Power_Class::pmic_t::pmic_axp2101 - || pmic_type == Power_Class::pmic_t::pmic_axp192 - || pmic_type == Power_Class::pmic_t::pmic_m5pm1) - { - _use_pmic_button = cfg.pmic_button; - /// Slightly lengthen the acceptance time of the AXP192 power button multiclick. - BtnPWR.setHoldThresh(BtnPWR.getHoldThresh() * 1.2); - } - - if (cfg.clear_display) - { - Display.clear(); - } - -#if defined (M5UNIFIED_PC_BUILD) -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - switch (_board) - { - case board_t::board_M5Stack: - // Countermeasure to the problem that GPIO15 affects WiFi sensitivity when M5GO bottom is connected. - m5gfx::pinMode(GPIO_NUM_15, m5gfx::pin_mode_t::output); - m5gfx::gpio_lo(GPIO_NUM_15); - - // M5Stack Core v2.6 has a problem that SPI communication speed cannot be increased. - // This problem can be solved by increasing the GPIO drive current. - // ※ This allows SunDisk SD cards to communicate at 20 MHz. (without M5GO bottom.) - // This allows communication with ModuleDisplay at 80 MHz. - for (auto gpio: (const gpio_num_t[]){ GPIO_NUM_18, GPIO_NUM_19, GPIO_NUM_23 }) - { - uint32_t tmp = *(volatile uint32_t*)(GPIO_PIN_MUX_REG[gpio]); - *(volatile uint32_t*)(GPIO_PIN_MUX_REG[gpio]) = tmp | FUN_DRV_M; // gpio drive current set to 40mA. - gpio_pulldown_dis(gpio); // disable pulldown. - gpio_pullup_en(gpio); // enable pullup. - } - break; - - case board_t::board_M5StickC: - case board_t::board_M5StickCPlus: - case board_t::board_M5AtomLite: - case board_t::board_M5AtomMatrix: - case board_t::board_M5AtomVoice: - case board_t::board_M5AtomU: - // Countermeasure to the problem that CH552 applies 4v to GPIO0, thus reducing WiFi sensitivity. - // Setting output_high adds a bias of 3.3v and suppresses overvoltage. - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::output); - m5gfx::gpio_hi(GPIO_NUM_0); - break; - - default: - break; - } -#endif - - switch (_board) /// setup Hardware Buttons - { -#if defined (M5UNIFIED_PC_BUILD) -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - case board_t::board_M5StackCoreInk: - m5gfx::pinMode(CoreInk_BUTTON_EXT_PIN, m5gfx::pin_mode_t::input); // TopButton - m5gfx::pinMode(CoreInk_BUTTON_PWR_PIN, m5gfx::pin_mode_t::input); // PowerButton - NON_BREAK; /// don't break; - - case board_t::board_M5Paper: - case board_t::board_M5Station: - case board_t::board_M5Stack: - m5gfx::pinMode(GPIO_NUM_38, m5gfx::pin_mode_t::input); - NON_BREAK; /// don't break; - - case board_t::board_M5StickC: - case board_t::board_M5StickCPlus: - m5gfx::pinMode(GPIO_NUM_37, m5gfx::pin_mode_t::input); - NON_BREAK; /// don't break; - - case board_t::board_M5AtomLite: - case board_t::board_M5AtomMatrix: - case board_t::board_M5AtomVoice: - case board_t::board_M5AtomPsram: - case board_t::board_M5AtomU: - case board_t::board_M5StampPico: - m5gfx::pinMode(GPIO_NUM_39, m5gfx::pin_mode_t::input); - NON_BREAK; /// don't break; - - case board_t::board_M5StackCore2: - case board_t::board_M5Tough: - /// for GPIO 36,39 Chattering prevention. - adc_power_acquire(); - break; - - case board_t::board_M5StickCPlus2: - m5gfx::pinMode(GPIO_NUM_35, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_37, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_39, m5gfx::pin_mode_t::input); - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32C3) - - case board_t::board_M5StampC3: - m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input_pullup); - break; - - case board_t::board_M5StampC3U: - m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32C6) - - case board_t::board_M5NanoC6: - m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32H2) - - case board_t::board_M5NanoH2: - m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5AtomS3: - case board_t::board_M5AtomS3Lite: - case board_t::board_M5AtomS3U: - case board_t::board_M5AtomS3R: - case board_t::board_M5AtomVoiceS3R: - m5gfx::pinMode(GPIO_NUM_41, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5AirQ: - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_8, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5VAMeter: - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5StampS3: - case board_t::board_M5Cardputer: - case board_t::board_M5CardputerADV: - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5Capsule: - case board_t::board_M5Dial: - case board_t::board_M5DinMeter: - m5gfx::pinMode(GPIO_NUM_42, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5StampPLC: - { - auto& ioexp = getIOExpander(0); - // lcd backlight - ioexp.setDirection(7, true); - ioexp.setPullMode(7, IOExpander_Base::pull_down); - ioexp.setHighImpedance(7, false); - - for (int i = 0; i < 3; ++i) { - // button a~c - ioexp.setDirection(i, false); - ioexp.setPullMode(i, IOExpander_Base::pull_up); - ioexp.setHighImpedance(i, false); - } - delay(100); - } - break; - - case board_t::board_M5PowerHub: - m5gfx::pinMode(GPIO_NUM_11, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5DualKey: - m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_17, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5StickS3: - m5gfx::pinMode(GPIO_NUM_11, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_12, m5gfx::pin_mode_t::input); - // PA Control Pin Init - this->In_I2C.bitOff(m5pm1_i2c_addr, 0x16, 1 << 3, 100000); // Set pin gpio3 as gpio function - this->In_I2C.bitOn(m5pm1_i2c_addr, 0x10, 1 << 3, 100000); // Set pin gpio3 mode: output - this->In_I2C.bitOff(m5pm1_i2c_addr, 0x13, 1 << 3, 100000); // Set gpio3 push-pull mode - this->In_I2C.bitOff(m5pm1_i2c_addr, 0x11, 1 << 3, 100000); // Set gpio3 output low - break; - - case board_t::board_M5PaperDIY: - m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5PaperColor: - m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_10, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5ChainCaptain: - m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_5, m5gfx::pin_mode_t::input); - // Audio PA -- G21 - m5gfx::pinMode(GPIO_NUM_21, m5gfx::pin_mode_t::output); - m5gfx::gpio_lo(GPIO_NUM_21); - // Audio Power -- M5IO1_G5 - { - auto& ioe1 = getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); - ioe1.setDirection(M5IOE1_Class::gpio5, true); - ioe1.digitalWrite(M5IOE1_Class::gpio5, false); - } - break; - - case board_t::board_M5PaperMono: - m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5StopWatch: - m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); - m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); - // M5IOE1 PIN4: display power - { - auto& ioe1 = getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio4, false); - ioe1.setDirection(M5IOE1_Class::gpio4, true); - ioe1.digitalWrite(M5IOE1_Class::gpio4, true); - } - // M5IOE1_G3 codec power / M5IOE1_G10 PA - { - auto& ioe1 = getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); - ioe1.setHighImpedance(M5IOE1_Class::gpio10, false); - ioe1.setDirection(M5IOE1_Class::gpio3, true); - ioe1.setDirection(M5IOE1_Class::gpio10, true); - ioe1.digitalWrite(M5IOE1_Class::gpio3, false); - ioe1.digitalWrite(M5IOE1_Class::gpio10, false); - } - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32P4) - case board_t::board_M5CoreP4X: - { - auto& ioe1 = getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); - ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); - ioe1.setDirection(M5IOE1_Class::gpio1, true); - ioe1.setDirection(M5IOE1_Class::gpio3, true); - ioe1.digitalWrite(M5IOE1_Class::gpio1, false); - ioe1.digitalWrite(M5IOE1_Class::gpio3, false); - } - break; - - case board_t::board_M5UnitPoEP4: - m5gfx::pinMode(GPIO_NUM_45, m5gfx::pin_mode_t::input); - break; - - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: -#if defined (ARDUINO) - // 汎用 esp32p4 ボード選択時、core 既定の SDIO ピンでは C6 (WiFi/BLE) に届かない。 - // WiFi.begin より前 (ESP-Hosted 初期化前) に Tab5 の配線へ差し替える。 - if (hostedSetPins && !(hostedIsInitialized && hostedIsInitialized())) - { - hostedSetPins(GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_11, GPIO_NUM_10, GPIO_NUM_9, GPIO_NUM_8, GPIO_NUM_15); - } -#endif - break; - -#endif - - default: - break; - } - -#if defined ( ARDUINO ) - #ifdef HardwareSerial_h - - if (cfg.serial_baudrate) - { // Wait with delay to prevent startup log output from disappearing. - delay(16); - Serial.begin(cfg.serial_baudrate); - } - - #endif -#endif - } - - void M5Unified::_begin_audio(config_t& cfg) - { - bool(*mic_enable_cb)(void*, bool) = nullptr; - bool(*mic_post_start_cb)(void*) = nullptr; - auto mic_cfg = Mic.config(); - - bool(*spk_enable_cb)(void*, bool) = nullptr; - auto spk_cfg = Speaker.config(); - - if (cfg.internal_mic) - { - mic_cfg.over_sampling = 1; - mic_cfg.i2s_port = I2S_NUM_0; - switch (_board) - { -#if defined (M5UNIFIED_PC_BUILD) -#elif defined (CONFIG_IDF_TARGET_ESP32P4) - case board_t::board_M5CoreP4X: - if (cfg.internal_mic) - { - mic_cfg.pin_mck = GPIO_NUM_2; - mic_cfg.pin_bck = GPIO_NUM_6; - mic_cfg.pin_ws = GPIO_NUM_4; - mic_cfg.pin_data_in = GPIO_NUM_5; - mic_cfg.magnification = 2; - mic_cfg.sample_rate = 24000; - mic_cfg.input_channel = input_channel_t::input_stereo; - mic_cfg.i2s_port = I2S_NUM_0; - mic_enable_cb = _microphone_enabled_cb_corep4x; - } - break; - - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: - if (cfg.internal_mic) - { - mic_cfg.pin_mck = GPIO_NUM_30; - mic_cfg.pin_bck = GPIO_NUM_27; - mic_cfg.pin_ws = GPIO_NUM_29; - mic_cfg.pin_data_in = GPIO_NUM_28; - // mic_cfg.pin_data_out = GPIO_NUM_26; - mic_cfg.magnification = 2; - mic_cfg.input_channel = input_channel_t::input_stereo; - mic_cfg.i2s_port = I2S_NUM_0; - mic_enable_cb = _microphone_enabled_cb_tab5; - } - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StackCoreS3: - case board_t::board_M5StackCoreS3SE: - case board_t::board_M5StackChan: - if (cfg.internal_mic) - { - mic_cfg.magnification = 2; - mic_cfg.over_sampling = 1; - mic_cfg.pin_mck = GPIO_NUM_0; - mic_cfg.pin_bck = GPIO_NUM_34; - mic_cfg.pin_ws = GPIO_NUM_33; - mic_cfg.pin_data_in = GPIO_NUM_14; - mic_cfg.i2s_port = I2S_NUM_1; - mic_cfg.input_channel = input_channel_t::input_stereo; - mic_enable_cb = _microphone_enabled_cb_cores3; - } - break; - - case board_t::board_M5StickS3: - if (cfg.internal_mic) - { - mic_cfg.pin_mck = GPIO_NUM_18; - mic_cfg.pin_bck = GPIO_NUM_17; - mic_cfg.pin_ws = GPIO_NUM_15; - mic_cfg.pin_data_in = GPIO_NUM_16; - mic_cfg.i2s_port = I2S_NUM_1; - mic_enable_cb = _microphone_enabled_cb_sticks3; - } - break; - - case board_t::board_M5PaperColor: - if (cfg.internal_mic) - { - mic_cfg.over_sampling = 1; - mic_cfg.pin_mck = GPIO_NUM_42; - mic_cfg.pin_bck = GPIO_NUM_40; - mic_cfg.pin_ws = GPIO_NUM_41; - mic_cfg.pin_data_in = GPIO_NUM_39; // data in from mic output - mic_cfg.i2s_port = I2S_NUM_1; - mic_cfg.input_channel = input_channel_t::input_only_left; - mic_enable_cb = _microphone_enabled_cb_papercolor; - } - break; - - case board_t::board_M5PaperMono: - if (cfg.internal_mic) - { /// builtin PDM mic - mic_cfg.pin_ws = GPIO_NUM_45; - mic_cfg.pin_data_in = GPIO_NUM_46; - mic_enable_cb = _microphone_enabled_cb_papermono; - } - break; - - case board_t::board_M5StopWatch: - if (cfg.internal_mic) - { - mic_cfg.pin_mck = GPIO_NUM_18; - mic_cfg.pin_bck = GPIO_NUM_17; - mic_cfg.pin_ws = GPIO_NUM_15; - mic_cfg.pin_data_in = GPIO_NUM_16; - mic_cfg.i2s_port = I2S_NUM_1; - mic_enable_cb = _microphone_enabled_cb_stopwatch; - mic_post_start_cb = _microphone_post_start_cb_stopwatch; - } - break; - - case board_t::board_M5ChainCaptain: - if (cfg.internal_mic) - { - mic_cfg.pin_mck = GPIO_NUM_40; - mic_cfg.pin_bck = GPIO_NUM_38; - mic_cfg.pin_ws = GPIO_NUM_41; - mic_cfg.pin_data_in = GPIO_NUM_39; - mic_cfg.i2s_port = I2S_NUM_1; - mic_cfg.sample_rate = 16000; - mic_enable_cb = _microphone_enabled_cb_chain_captain; - } - break; - - case board_t::board_M5AtomS3U: - if (cfg.internal_mic) - { - mic_cfg.pin_data_in = GPIO_NUM_38; - mic_cfg.pin_ws = GPIO_NUM_39; - } - break; - - case board_t::board_M5Cardputer: - if (cfg.internal_mic) - { - mic_cfg.pin_data_in = GPIO_NUM_46; - mic_cfg.pin_ws = GPIO_NUM_43; - } - break; - - case board_t::board_M5CardputerADV: - if (cfg.internal_mic) - { - mic_cfg.pin_data_in = GPIO_NUM_46; - mic_cfg.pin_ws = GPIO_NUM_43; - mic_cfg.pin_bck = GPIO_NUM_41; - mic_enable_cb = _microphone_enabled_cb_cardputer_adv; - } - break; - - case board_t::board_M5Capsule: - if (cfg.internal_mic) - { - mic_cfg.pin_data_in = GPIO_NUM_41; - mic_cfg.pin_ws = GPIO_NUM_40; - } - break; - -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - case board_t::board_M5Stack: - if (cfg.internal_mic) - { - mic_cfg.pin_data_in = GPIO_NUM_34; // M5GO bottom MIC - mic_cfg.i2s_port = I2S_NUM_0; - mic_cfg.use_adc = true; // use ADC analog input - mic_cfg.over_sampling = 4; - } - break; - - case board_t::board_M5StickC: - case board_t::board_M5StickCPlus: - if (cfg.internal_mic) - { /// builtin PDM mic - mic_cfg.pin_data_in = GPIO_NUM_34; - mic_cfg.pin_ws = GPIO_NUM_0; - mic_enable_cb = _microphone_enabled_cb_stickc; - } - break; - - case board_t::board_M5StickCPlus2: - case board_t::board_M5Tough: - case board_t::board_M5StackCore2: - if (cfg.internal_mic) - { /// builtin PDM mic - mic_cfg.pin_data_in = GPIO_NUM_34; - mic_cfg.pin_ws = GPIO_NUM_0; - } - break; - - case board_t::board_M5AtomU: - { /// ATOM U builtin PDM mic - mic_cfg.pin_data_in = GPIO_NUM_19; - mic_cfg.pin_ws = GPIO_NUM_5; - } - break; - - case board_t::board_M5AtomVoice: - { /// ATOM ECHO builtin PDM mic - mic_cfg.pin_data_in = GPIO_NUM_23; - mic_cfg.pin_ws = GPIO_NUM_33; - } - break; -#endif - default: - break; - } - } - - if (cfg.external_spk_detail.enabled && cfg.external_speaker_value == 0) { - cfg.external_speaker.atomic_spk = false==cfg.external_spk_detail.omit_atomic_spk; - cfg.external_speaker.hat_spk = false==cfg.external_spk_detail.omit_spk_hat; - } - - if (cfg.internal_spk || cfg.external_speaker_value) - { - // set default speaker gain. - spk_cfg.magnification = 16; -#if defined M5UNIFIED_I2S_PORT_COUNT - spk_cfg.i2s_port = (i2s_port_t)(M5UNIFIED_I2S_PORT_COUNT - 1); -#else - spk_cfg.i2s_port = (i2s_port_t)(I2S_NUM_MAX - 1); -#endif - switch (_board) - { -#if defined (M5UNIFIED_PC_BUILD) -#elif defined (CONFIG_IDF_TARGET_ESP32C6) - case board_t::board_M5UnitC6L: - case board_t::board_ArduinoNessoN1: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_11; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32P4) - case board_t::board_M5CoreP4X: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_2; - spk_cfg.pin_bck = GPIO_NUM_6; - spk_cfg.pin_ws = GPIO_NUM_4; - spk_cfg.pin_data_out = GPIO_NUM_3; - spk_cfg.magnification = 4; - spk_cfg.sample_rate = 24000; - spk_cfg.i2s_port = I2S_NUM_0; - spk_enable_cb = _speaker_enabled_cb_corep4x; - } - break; - - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_30; - spk_cfg.pin_bck = GPIO_NUM_27; - spk_cfg.pin_ws = GPIO_NUM_29; -// spk_cfg.pin_data_in = GPIO_NUM_28; - spk_cfg.pin_data_out = GPIO_NUM_26; - spk_cfg.magnification = 4; - spk_cfg.i2s_port = I2S_NUM_0; - spk_enable_cb = _speaker_enabled_cb_tab5; - } - break; - -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StackCoreS3: - case board_t::board_M5StackCoreS3SE: - case board_t::board_M5StackChan: - if (cfg.internal_spk) - { - spk_cfg.pin_bck = GPIO_NUM_34; - spk_cfg.pin_ws = GPIO_NUM_33; - spk_cfg.pin_data_out = GPIO_NUM_13; - spk_cfg.magnification = 4; - spk_cfg.i2s_port = I2S_NUM_1; - spk_enable_cb = _speaker_enabled_cb_cores3; - } - break; - - case board_t::board_M5StickS3: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_18; - spk_cfg.pin_bck = GPIO_NUM_17; - spk_cfg.pin_ws = GPIO_NUM_15; - spk_cfg.pin_data_out = GPIO_NUM_14; - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.magnification = 1; - spk_cfg.sample_rate = 22050; - spk_cfg.stereo = true; - spk_cfg.buzzer = false; - spk_cfg.use_dac = false; - spk_cfg.dac_zero_level = 0; - spk_enable_cb = _speaker_enabled_cb_sticks3; - } - break; - - case board_t::board_M5AtomS3: - case board_t::board_M5AtomS3Lite: - case board_t::board_M5AtomS3R: - case board_t::board_M5AtomS3RCam: - case board_t::board_M5AtomS3RExt: - if (cfg.external_speaker.atomic_spk || cfg.external_speaker.atomic_echo) - { // for ATOMIC SPK / ATOMIC ECHO BASE - bool atomdisplay = false; - for (int i = 0; i < getDisplayCount(); ++i) { - if (Displays(i).getBoard() == board_t::board_M5AtomDisplay) { - atomdisplay = true; - break; - } - } - if (!atomdisplay) { - bool flg_atomic_spk = false; - if (cfg.external_speaker.atomic_spk) { - m5gfx::pinMode(GPIO_NUM_6, m5gfx::pin_mode_t::input_pulldown); // MOSI - m5gfx::pinMode(GPIO_NUM_7, m5gfx::pin_mode_t::input_pulldown); // SCLK - if (m5gfx::gpio_in(GPIO_NUM_6) - && m5gfx::gpio_in(GPIO_NUM_7)) - { - flg_atomic_spk = true; - ESP_LOGD("M5Unified", "ATOMIC SPK"); - // atomic_spkのSDカード用ピンを割当 - _get_pin_table[sd_spi_sclk] = GPIO_NUM_7; - _get_pin_table[sd_spi_copi] = GPIO_NUM_6; - _get_pin_table[sd_spi_cipo] = GPIO_NUM_8; - cfg.internal_imu = false; /// avoid conflict with i2c - cfg.internal_rtc = false; /// avoid conflict with i2c - spk_cfg.pin_bck = GPIO_NUM_5; - spk_cfg.pin_ws = GPIO_NUM_39; - spk_cfg.pin_data_out = GPIO_NUM_38; - spk_cfg.magnification = 16; - } - } - if (cfg.external_speaker.atomic_echo && !flg_atomic_spk) { - spk_cfg.pin_bck = GPIO_NUM_8; - spk_cfg.pin_ws = GPIO_NUM_6; - spk_cfg.pin_data_out = GPIO_NUM_5; - spk_cfg.magnification = 1; - spk_enable_cb = _speaker_enabled_cb_atomic_echo; - - mic_cfg.i2s_port = spk_cfg.i2s_port; - mic_cfg.pin_bck = GPIO_NUM_8; - mic_cfg.pin_ws = GPIO_NUM_6; - mic_cfg.pin_data_in = GPIO_NUM_7; - mic_cfg.magnification = 1; - mic_cfg.over_sampling = 1; - mic_cfg.pin_mck = GPIO_NUM_NC; - mic_cfg.stereo = false; - mic_enable_cb = _microphone_enabled_cb_atomic_echo; - } - } - } - break; - - case board_t::board_M5AtomVoiceS3R: - if (cfg.internal_mic) { - cfg.internal_imu = false; - - // spk_cfg.pin_mck = GPIO_NUM_11; - spk_cfg.pin_bck = GPIO_NUM_17; - spk_cfg.pin_ws = GPIO_NUM_3; - spk_cfg.pin_data_out = GPIO_NUM_48; - spk_cfg.magnification = 1; - spk_cfg.i2s_port = I2S_NUM_1; - spk_enable_cb = _speaker_enabled_cb_atom_echos3r; - - mic_cfg.i2s_port = spk_cfg.i2s_port; - mic_cfg.pin_mck = GPIO_NUM_11; - mic_cfg.pin_bck = GPIO_NUM_17; - mic_cfg.pin_ws = GPIO_NUM_3; - mic_cfg.pin_data_in = GPIO_NUM_4; - mic_cfg.magnification = 1; - mic_cfg.over_sampling = 1; - mic_cfg.stereo = true; - mic_enable_cb = _microphone_enabled_cb_atom_echos3r; - } - break; - - case board_t::board_M5Capsule: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_2; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5Dial: - case board_t::board_M5DinMeter: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_3; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5AirQ: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_9; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5VAMeter: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_14; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5PaperS3: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_21; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5PaperMono: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_42; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - - case board_t::board_M5PaperColor: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_42; - spk_cfg.pin_bck = GPIO_NUM_40; - spk_cfg.pin_ws = GPIO_NUM_41; - spk_cfg.pin_data_out = GPIO_NUM_38; // data out to spk - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.magnification = 1; - spk_cfg.sample_rate = 44100; - spk_cfg.stereo = true; - spk_enable_cb = _speaker_enabled_cb_papercolor; - } - break; - - case board_t::board_M5StopWatch: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_18; - spk_cfg.pin_bck = GPIO_NUM_17; - spk_cfg.pin_ws = GPIO_NUM_15; - spk_cfg.pin_data_out = GPIO_NUM_21; - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.magnification = 4; - spk_cfg.sample_rate = 44100; - spk_cfg.stereo = true; - spk_cfg.buzzer = false; - spk_cfg.use_dac = false; - spk_cfg.dac_zero_level = 0; - spk_enable_cb = _speaker_enabled_cb_stopwatch; - } - break; - - case board_t::board_M5ChainCaptain: - if (cfg.internal_spk) - { - spk_cfg.pin_mck = GPIO_NUM_40; - spk_cfg.pin_bck = GPIO_NUM_38; - spk_cfg.pin_ws = GPIO_NUM_41; - spk_cfg.pin_data_out = GPIO_NUM_42; - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.magnification = 1; - spk_cfg.sample_rate = 44100; - spk_cfg.stereo = true; - spk_cfg.buzzer = false; - spk_cfg.use_dac = false; - spk_cfg.dac_zero_level = 0; - spk_enable_cb = _speaker_enabled_cb_chain_captain; - } - break; - - case board_t::board_M5Cardputer: - case board_t::board_M5CardputerADV: - if (cfg.internal_spk) - { - spk_cfg.pin_bck = GPIO_NUM_41; - spk_cfg.pin_ws = GPIO_NUM_43; - spk_cfg.pin_data_out = GPIO_NUM_42; - spk_cfg.magnification = 16; - spk_cfg.i2s_port = I2S_NUM_1; - if (_board == board_t::board_M5CardputerADV) { - spk_enable_cb = _speaker_enabled_cb_cardputer_adv; - } - } - break; - - case board_t::board_M5StampPLC: - if (cfg.internal_spk) - { - spk_cfg.pin_data_out = GPIO_NUM_44; - spk_cfg.buzzer = true; - spk_cfg.magnification = 48; - } - break; - -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - case board_t::board_M5Stack: - if (cfg.internal_spk) - { - m5gfx::gpio_lo(GPIO_NUM_25); - m5gfx::pinMode(GPIO_NUM_25, m5gfx::pin_mode_t::output); - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.use_dac = true; - spk_cfg.pin_data_out = GPIO_NUM_25; - spk_cfg.magnification = 8; - spk_cfg.sample_rate *= 2; - } - break; - - case board_t::board_M5StackCoreInk: - case board_t::board_M5StickCPlus: - case board_t::board_M5StickCPlus2: - if (cfg.internal_spk) - { - spk_cfg.buzzer = true; - spk_cfg.pin_data_out = GPIO_NUM_2; - spk_cfg.magnification = 48; - } - NON_BREAK; - - case board_t::board_M5StickC: - if (cfg.external_speaker.hat_spk2 && (_board != board_t::board_M5StackCoreInk)) - { /// for HAT SPK2 (for StickC/StickCPlus. CoreInk does not support.) - spk_cfg.pin_data_out = GPIO_NUM_25; - spk_cfg.pin_bck = GPIO_NUM_26; - spk_cfg.pin_ws = GPIO_NUM_0; - spk_cfg.i2s_port = I2S_NUM_1; - spk_cfg.use_dac = false; - spk_cfg.buzzer = false; - spk_cfg.magnification = 16; - } - else if (cfg.external_speaker.hat_spk) - { /// for HAT SPK - gpio_num_t pin_en = _board == board_t::board_M5StackCoreInk ? GPIO_NUM_25 : GPIO_NUM_0; - m5gfx::gpio_lo(pin_en); - m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); - m5gfx::gpio_lo(GPIO_NUM_26); - m5gfx::pinMode(GPIO_NUM_26, m5gfx::pin_mode_t::output); - spk_cfg.pin_data_out = GPIO_NUM_26; - spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.use_dac = true; - spk_cfg.buzzer = false; - spk_cfg.magnification = 32; - spk_enable_cb = _speaker_enabled_cb_hat_spk; - } - break; - - case board_t::board_M5Tough: - // The magnification is set higher than Core2 here because the waterproof case reduces the sound.; - spk_cfg.magnification = 24; - NON_BREAK; - case board_t::board_M5StackCore2: - if (cfg.internal_spk) - { - spk_cfg.pin_bck = GPIO_NUM_12; - spk_cfg.pin_ws = GPIO_NUM_0; - spk_cfg.pin_data_out = GPIO_NUM_2; - spk_enable_cb = _speaker_enabled_cb_core2; - } - break; - - case board_t::board_M5AtomVoice: - if (cfg.internal_spk && (Display.getBoard() != board_t::board_M5AtomDisplay)) - { // for ATOM ECHO - spk_cfg.pin_bck = GPIO_NUM_19; - spk_cfg.pin_ws = GPIO_NUM_33; - spk_cfg.pin_data_out = GPIO_NUM_22; - spk_cfg.magnification = 12; - } - NON_BREAK; - case board_t::board_M5AtomLite: - case board_t::board_M5AtomMatrix: - case board_t::board_M5AtomPsram: - if (cfg.external_speaker.atomic_spk || cfg.external_speaker.atomic_echo) - { // for ATOMIC SPK / ATOMIC ECHO BASE - bool atomdisplay = false; - for (int i = 0; i < getDisplayCount(); ++i) { - if (Displays(i).getBoard() == board_t::board_M5AtomDisplay) { - atomdisplay = true; - break; - } - } - if (!atomdisplay) { - bool flg_atomic_spk = false; - if (cfg.external_speaker.atomic_spk) { - // 19,23 pulldown read check ( all high = ATOMIC_SPK ? ) // MISO is not used for judgment as it changes depending on the state of the SD card. - gpio_num_t pin = (_board == board_t::board_M5AtomPsram) ? GPIO_NUM_5 : GPIO_NUM_23; - m5gfx::pinMode(GPIO_NUM_19, m5gfx::pin_mode_t::input_pulldown); // MOSI - m5gfx::pinMode(pin , m5gfx::pin_mode_t::input_pulldown); // SCLK - if (m5gfx::gpio_in(GPIO_NUM_19) - && m5gfx::gpio_in(pin )) - { - flg_atomic_spk = true; - ESP_LOGD("M5Unified", "ATOMIC SPK"); - // atomic_spkのSDカード用ピンを割当 - _get_pin_table[sd_spi_sclk] = pin; - _get_pin_table[sd_spi_copi] = GPIO_NUM_19; - _get_pin_table[sd_spi_cipo] = GPIO_NUM_33; - cfg.internal_imu = false; /// avoid conflict with i2c - cfg.internal_rtc = false; /// avoid conflict with i2c - spk_cfg.pin_bck = GPIO_NUM_22; - spk_cfg.pin_ws = GPIO_NUM_21; - spk_cfg.pin_data_out = GPIO_NUM_25; - spk_cfg.magnification = 16; - auto mic = Mic.config(); - mic.pin_data_in = -1; // disable mic for ATOMECHO - Mic.config(mic); - } - } - if (cfg.external_speaker.atomic_echo && !flg_atomic_spk) { - spk_cfg.pin_bck = GPIO_NUM_33; - spk_cfg.pin_ws = GPIO_NUM_19; - spk_cfg.pin_data_out = GPIO_NUM_22; - spk_cfg.magnification = 1; - spk_enable_cb = _speaker_enabled_cb_atomic_echo; - - mic_cfg.i2s_port = spk_cfg.i2s_port; - mic_cfg.pin_bck = GPIO_NUM_33; - mic_cfg.pin_ws = GPIO_NUM_19; - mic_cfg.pin_data_in = GPIO_NUM_23; - mic_cfg.magnification = 1; - mic_cfg.over_sampling = 1; - mic_cfg.pin_mck = GPIO_NUM_NC; - mic_cfg.stereo = false; - mic_enable_cb = _microphone_enabled_cb_atomic_echo; - } - } - } - break; -#endif - default: - break; - } - - if (cfg.external_speaker_value) - { -#if defined (M5UNIFIED_PC_BUILD) -#elif defined ( CONFIG_IDF_TARGET_ESP32P4 ) - #define ENABLE_M5MODULE - if (_board == board_t::board_M5Tab5 - || _board == board_t::board_M5Tab5X - || _board == board_t::board_M5CoreP4X) -#elif defined ( CONFIG_IDF_TARGET_ESP32S3 ) - #define ENABLE_M5MODULE - if (_board == board_t::board_M5StackCoreS3 - || _board == board_t::board_M5StackCoreS3SE - || _board == board_t::board_M5StackChan) -#elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) - #define ENABLE_M5MODULE - if ( _board == board_t::board_M5Stack - || _board == board_t::board_M5StackCore2 - || _board == board_t::board_M5Tough) -#endif - { -#ifdef ENABLE_M5MODULE - bool use_module_display = cfg.external_speaker.module_display - && (0 <= getDisplayIndex(m5gfx::board_M5ModuleDisplay)); - if (use_module_display || cfg.external_speaker.module_rca) - { - if (use_module_display) { - spk_cfg.sample_rate = 48000; // Module Display audio output is fixed at 48 kHz - } - // ModuleDisplay or Module RCA - spk_cfg.pin_bck = getPin(use_module_display ? pin_name_t::mbus_pin21 : pin_name_t::mbus_pin22); - spk_cfg.pin_data_out = getPin(pin_name_t::mbus_pin23); - spk_cfg.pin_ws = getPin(pin_name_t::mbus_pin24); // LRCK - - spk_cfg.i2s_port = I2S_NUM_1; - spk_cfg.magnification = 16; - spk_cfg.stereo = true; - spk_cfg.buzzer = false; - spk_cfg.use_dac = false; - spk_enable_cb = nullptr; - } - #undef ENABLE_M5MODULE -#endif - } - } - } - if (mic_cfg.pin_data_in >= 0) - { - Mic.setCallback(this, mic_enable_cb); - Mic.setPostStartCallback(this, mic_post_start_cb); - Mic.config(mic_cfg); - } - if (spk_cfg.pin_data_out >= 0) - { - Speaker.setCallback(this, spk_enable_cb); - Speaker.config(spk_cfg); - } - } - - bool M5Unified::_clearWakeupInterrupt(void) - { - bool res = true; - // A touch panel holds its INT asserted until the touch data is read. Every board that - // uses the touch INT as its wakeup pin therefore has to consume the data here. - // ( M5Paper = GPIO36 , M5PaperS3 = GPIO48 , Core2 / Tough = GPIO39 , CoreS3 = via AW9523 ) - if (!_displays.empty() && _displays.front().touch()) - { // Same source as Touch_Class, see Touch.begin() in _begin(). - m5gfx::touch_point_t tp; - _displays.front().getTouchRaw(&tp, 1); - } - -#if defined (CONFIG_IDF_TARGET_ESP32S3) - switch (getBoard()) - { - case board_t::board_M5StackCoreS3: - case board_t::board_M5StackCoreS3SE: - case board_t::board_M5StackChan: - { // TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN -> I2C_INT -> GPIO21. - // The AW9523 reports input changes only, so releasing the touch INT is not enough: - // its input would stay in the touched state and a later touch would not produce - // any change, leaving the wakeup source dead. - uint8_t buf[2]; - res = In_I2C.readRegister(aw9523_i2c_addr, 0x00, buf, sizeof(buf), 400000); - } - break; - - default: - break; - } -#elif defined (CONFIG_IDF_TARGET_ESP32C5) - switch (getBoard()) - { - case board_t::board_M5ToughC5: - { // TOUCH_INT や RTC_INT は PM1 に集約され、PM1 の IRQ 出力 -> GPIO4 が - // 唯一の wakeup ピンになる。IRQ 出力は IRQ ステータス (0x40-0x42) が - // 全て 0 になるまで Low を保つため、ここでクリアして解放する。 - // WAKE_SRC が残っていると IRQ Status 3 の WAKEUP ビットが再セット - // され続けるので、先に WAKE_SRC を消す。 - res = Power.M5pm1.clearWakeSource(); - res &= Power.M5pm1.clearIRQStatus(); - } - break; - - default: - break; - } -#elif defined (CONFIG_IDF_TARGET_ESP32C61) - switch (getBoard()) - { - case board_t::board_M5CoreMatrix: - { // KEY and IMU wake events are funneled into the PM1, whose IRQ output - // (GPIO2) is the only wakeup pin. The IRQ output stays low until every - // IRQ status bit is cleared, so clear them here to release the pin. - // Clear WAKE_SRC first: while it is set, the WAKEUP bit of IRQ status 3 - // keeps getting re-asserted. - res = Power.M5pm1.clearWakeSource(); - res &= Power.M5pm1.clearIRQStatus(); - } - break; - - default: - break; - } -#endif - return res; - } - - bool M5Unified::_begin_rtc_imu(const config_t& cfg) - { - bool port_a_used = false; - if (cfg.external_rtc || cfg.external_imu) - { - M5.Ex_I2C.begin(); - } - - if (cfg.internal_rtc && In_I2C.isEnabled()) - { - M5.Rtc.begin(&M5.In_I2C, M5.getBoard()); - } - if (!M5.Rtc.isEnabled() && cfg.external_rtc && Ex_I2C.isEnabled()) - { - port_a_used = M5.Rtc.begin(&M5.Ex_I2C); - } - if (M5.Rtc.isEnabled()) - { - M5.Rtc.setSystemTimeFromRtc(); - if (cfg.disable_rtc_irq) { - M5.Rtc.disableIRQ(); - } - } - - if (cfg.internal_imu && In_I2C.isEnabled()) - { - M5.Imu.begin(&M5.In_I2C, M5.getBoard()); - } - if (!M5.Imu.isEnabled() && cfg.external_imu && Ex_I2C.isEnabled()) - { - port_a_used = M5.Imu.begin(&M5.Ex_I2C) || port_a_used; - } - return port_a_used; - } - - void M5Unified::update( void ) - { - auto ms = m5gfx::millis(); - _updateMsec = ms; - - // 1=BtnA / 2=BtnB / 4=BtnC / 8=BtnEXT / 16=BtnPWR - uint_fast8_t use_rawstate_bits = 0; - uint_fast8_t btn_rawstate_bits = 0; - - if (Touch.isEnabled()) - { - Touch.update(ms); - - int tb_y = 0; - int tb_k = 0; - switch (_board) - { - case board_t::board_M5StackCore2: - case board_t::board_M5Tough: - case board_t::board_M5ToughC5: - case board_t::board_M5StackCoreS3SE: - case board_t::board_M5StackCoreS3: - case board_t::board_M5StackChan: - tb_y = 240; - tb_k = 614; // (65536*3/320) - break; - case board_t::board_M5Paper: - case board_t::board_M5PaperS3: - tb_y = 960; - tb_k = 364; // (65536*3/540) - break; - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: - tb_y = 1280; - tb_k = 273; // (65536*3/540) - break; - default: - break; - } - - if (tb_k) - { - tb_y -= _touch_button_height; - if (tb_y < 0) { tb_y = 0; } - - use_rawstate_bits = 0b00111; - int i = Touch.getCount(); - while (--i >= 0) - { - auto raw = Touch.getTouchPointRaw(i); - if (raw.y >= tb_y) - { - auto det = Touch.getDetail(i); - if (det.state & touch_state_t::touch) - { - if (BtnA.isPressed()) { btn_rawstate_bits |= 1 << 0; } - if (BtnB.isPressed()) { btn_rawstate_bits |= 1 << 1; } - if (BtnC.isPressed()) { btn_rawstate_bits |= 1 << 2; } - if (btn_rawstate_bits || !(det.state & touch_state_t::mask_moving)) - { - btn_rawstate_bits |= 1 << ((raw.x * tb_k) >> 16); - } - } - } - } - } - } - -#if defined (M5UNIFIED_PC_BUILD) - use_rawstate_bits = 0b10111; - btn_rawstate_bits = (!m5gfx::gpio_in(39) ? 0b00001 : 0) // LEFT=BtnA - | (!m5gfx::gpio_in(38) ? 0b00010 : 0) // DOWN=BtnB - | (!m5gfx::gpio_in(37) ? 0b00100 : 0) // RIGHT=BtnC - | (!m5gfx::gpio_in(36) ? 0b10000 : 0) // UP=BtnPWR - ; -#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - - uint_fast8_t raw_gpio32_39 = ~GPIO.in1.data; - switch (_board) - { - case board_t::board_M5StackCoreInk: - { - uint32_t raw_gpio0_31 = ~GPIO.in; - use_rawstate_bits = 0b11000; - btn_rawstate_bits = (((raw_gpio0_31 >> CoreInk_BUTTON_EXT_PIN) & 1) << 3) - | (((raw_gpio0_31 >> CoreInk_BUTTON_PWR_PIN) & 1) << 4); - } - NON_BREAK; /// don't break; - - case board_t::board_M5Paper: - case board_t::board_M5Station: - use_rawstate_bits |= 0b00111; - btn_rawstate_bits |= (raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 0x07; // gpio37 A / gpio38 B / gpio39 C - break; - - case board_t::board_M5Stack: - use_rawstate_bits = 0b00111; - btn_rawstate_bits = (((raw_gpio32_39 >> (GPIO_NUM_38 & 31)) & 1) << 1) // gpio38 B - | (((raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 1) << 2); // gpio37 C - NON_BREAK; /// don't break; - - case board_t::board_M5AtomLite: - case board_t::board_M5AtomMatrix: - case board_t::board_M5AtomVoice: - case board_t::board_M5AtomPsram: - case board_t::board_M5AtomU: - case board_t::board_M5StampPico: - use_rawstate_bits |= 0b00001; - btn_rawstate_bits |= (raw_gpio32_39 >> (GPIO_NUM_39 & 31)) & 1; // gpio39 A - break; - - case board_t::board_M5StickCPlus2: - use_rawstate_bits = 0b10000; - btn_rawstate_bits = (((raw_gpio32_39 >> (GPIO_NUM_35 & 31)) & 1)<<4); // gpio35 PWR - NON_BREAK; /// don't break; - - case board_t::board_M5StickC: - case board_t::board_M5StickCPlus: - use_rawstate_bits |= 0b00011; - btn_rawstate_bits |= (( raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 1 ) // gpio37 A - | (((raw_gpio32_39 >> (GPIO_NUM_39 & 31)) & 1)<<1); // gpio39 B - break; - - default: - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32S3) - - switch (_board) - { - case board_t::board_M5AirQ: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_8)) & 1) << 1; - break; - - case board_t::board_M5VAMeter: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; - break; - - case board_t::board_M5StampS3: - case board_t::board_M5Cardputer: - case board_t::board_M5CardputerADV: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_0)) & 1; - break; - - case board_t::board_M5AtomS3: - case board_t::board_M5AtomS3Lite: - case board_t::board_M5AtomS3U: - case board_t::board_M5AtomS3R: - case board_t::board_M5AtomVoiceS3R: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_41)) & 1; - break; - - case board_t::board_M5Capsule: - case board_t::board_M5Dial: - case board_t::board_M5DinMeter: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_42)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; - break; - - case board_t::board_M5StampPLC: - { - use_rawstate_bits = 0b00111; - uint8_t value = 0xFF; - if (_io_expander[0]->readRegister(0x0F, &value, 1)) { - btn_rawstate_bits = (!(value & 0b100) ? 0b00001 : 0) // BtnA - | (!(value & 0b010) ? 0b00010 : 0) // BtnB - | (!(value & 0b001) ? 0b00100 : 0) // BtnC - ; - } - } - break; - - case board_t::board_M5PowerHub: - { - use_rawstate_bits = 0b00011; - auto value = M5.In_I2C.readRegister8(powerhub_i2c_addr, 0xA0, 100000); - // ESP_LOGI("M5Unified", "M5PowerHub Btn read: %02X", value); - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_11) & 1) // BtnA - | (!(value & 0b001) ? 0b00010 : 0) // BtnB - ; - break; - } - - case board_t::board_M5DualKey: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_17)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; - break; - - case board_t::board_M5StickS3: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_11)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_12)) & 1) << 1; - break; - - case board_t::board_M5PaperDIY: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_3)) & 1) << 1; - break; - - case board_t::board_M5PaperColor: - use_rawstate_bits = 0b00111; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_10)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_9)) & 1) << 1 - | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 2; - break; - - case board_t::board_M5ChainCaptain: - use_rawstate_bits = 0b00111; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) << 1 - | ((!m5gfx::gpio_in(GPIO_NUM_5)) & 1) << 2; - break; - - case board_t::board_M5PaperMono: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_3)) & 1) << 1; - break; - - case board_t::board_M5StopWatch: - use_rawstate_bits = 0b00011; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 1; - break; - - default: - - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32C3) - - switch (_board) - { - case board_t::board_M5StampC3: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_3)) & 1; - break; - - case board_t::board_M5StampC3U: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9)) & 1; - break; - - default: - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32C6) - - switch (_board) - { - case board_t::board_M5NanoC6: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9) ? 0b00001 : 0); - break; - - case board_t::board_M5UnitC6L: - { - use_rawstate_bits = 0b00001; - uint8_t value = 0xFF; - if (_io_expander[0]->readRegister(0x0F, &value, 1)) { - btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0); // BtnA - } - } - break; - - case board_t::board_ArduinoNessoN1: - { - use_rawstate_bits = 0b00011; - uint8_t value = 0xFF; - if (_io_expander[0]->readRegister(0x0F, &value, 1)) { - btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0) // BtnA - | (!(value & 0b010) ? 0b00010 : 0) // BtnB - ; - } - } - break; - - default: - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32H2) - - switch (_board) - { - case board_t::board_M5NanoH2: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9) ? 0b00001 : 0); - break; - - default: - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32P4) - - switch (_board) - { - case board_t::board_M5UnitPoEP4: - use_rawstate_bits = 0b00001; - btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_45)) & 1; - break; - - default: - break; - } - -#elif defined (CONFIG_IDF_TARGET_ESP32C61) - - switch (_board) - { - case board_t::board_M5CoreMatrix: - /// KEY1/2/3 are wired to PM1 GPIO0/1/2 (pressed = LOW), not to the ESP, - /// so they are read by I2C polling. Skip the update on an I2C failure so - /// a bus error is not reported as a button press. - { - uint8_t in; - if (Power.M5pm1.getGPIOInputBits(&in)) - { - use_rawstate_bits = 0b00111; - btn_rawstate_bits = (~in) & 0b00111; - } - } - break; - - default: - break; - } - -#endif - - if (use_rawstate_bits) { - for (int i = 0; i < 5; ++i) { - if (use_rawstate_bits & (1 << i)) { - _buttons[i].setRawState(ms, btn_rawstate_bits & (1 << i)); - } - } - } - -#if defined (CONFIG_IDF_TARGET_ESP32) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) - if (_use_pmic_button) - { - Button_Class::button_state_t state = Button_Class::button_state_t::state_nochange; - bool read_axp = (ms - BtnPWR.getUpdateMsec()) >= BTNPWR_MIN_UPDATE_MSEC; - if (read_axp || BtnPWR.getState()) - { - switch (Power.getKeyState()) - { - case 0: break; - case 2: state = Button_Class::button_state_t::state_clicked; break; - default: state = Button_Class::button_state_t::state_hold; break; - } - BtnPWR.setState(ms, state); - } - } -#endif - } - - void M5Unified::setTouchButtonHeightByRatio(uint8_t ratio) - { - uint32_t height = 0; - switch (_board) - { - case board_t::board_M5StackCore2: - case board_t::board_M5Tough: - case board_t::board_M5ToughC5: - case board_t::board_M5StackCoreS3SE: - case board_t::board_M5StackCoreS3: - case board_t::board_M5StackChan: - height = 240; - break; - case board_t::board_M5Paper: - case board_t::board_M5PaperS3: - height = 960; - break; - case board_t::board_M5Tab5: - case board_t::board_M5Tab5X: - height = 1280; - break; - default: - break; - } - _touch_button_height = height * ratio / 255; - } - - M5GFX& M5Unified::getDisplay(size_t index) - { - return index != _primary_display_index && index < this->_displays.size() ? this->_displays[index] : Display; - } - - std::size_t M5Unified::addDisplay(M5GFX& dsp) - { - this->_displays.push_back(dsp); - auto res = this->_displays.size() - 1; - setPrimaryDisplay(res == 0 ? 0 : _primary_display_index); - - // Touch screen operation is always limited to the first display. - Touch.begin(_displays.front().touch() ? &_displays.front() : nullptr); - - return res; - } - - int32_t M5Unified::getDisplayIndex(m5gfx::board_t board) { - int i = 0; - for (auto &d : _displays) - { - if (board == d.getBoard()) { return i; } - ++i; - } - return -1; - } - - int32_t M5Unified::getDisplayIndex(std::initializer_list board_list) - { - for (auto b : board_list) - { - int32_t i = getDisplayIndex(b); - if (i >= 0) { return i; } - } - return -1; - } - - bool M5Unified::setPrimaryDisplay(std::size_t index) - { - if (index >= _displays.size()) { return false; } - std::size_t pdi = _primary_display_index; - - if (pdi < _displays.size()) - { - _displays[pdi] = Display; - } - _primary_display_index = index; - Display = _displays[index]; - return true; - } - - bool M5Unified::setPrimaryDisplayType(std::initializer_list board_list) - { - auto i = getDisplayIndex(board_list); - bool res = (i >= 0); - if (res) { setPrimaryDisplay(i); } - return res; - } - - void M5Unified::setLogDisplayIndex(size_t index) - { - Log.setDisplay(getDisplay(index)); - } - - void M5Unified::setLogDisplayType(std::initializer_list board_list) - { - auto i = getDisplayIndex(board_list); - if (i >= 0) { Log.setDisplay(getDisplay(i)); } - } -} +// This library is built as a single translation unit: this file includes every +// implementation file (*.inl), and those are not compiled on their own. +// +// Why: every implementation file pulls in the same framework headers, and parsing them +// once instead of once per file cuts the library build time several-fold (the Arduino +// build also pre-scans each source file for includes, which doubled that cost). +// +// Maintenance notes: +// - Adding an implementation file: create it as *.inl and add an #include below. Do not +// add *.cpp files under src/. +// - File-local names are visible to every file that follows: static functions and +// constants, macros, and anonymous namespaces do not isolate files here. Use a name that +// identifies the file (or a class member), and #undef helper macros at the end of the +// file that defines them. Constants shared by several files go in a header +// (utility/m5unified_i2c_addr.hpp for the on-board I2C addresses). +// - M5Unified.inl (the former M5Unified.cpp) comes last; it uses the classes above. +// - .github/scripts/check_inl_sources.py checks these rules (every *.inl is reachable from +// this file, directly or through another *.inl, and starts with the guard below); CI runs +// it (SourceCheck.yml). + +#define __STDC_FORMAT_MACROS + +#define M5UNIFIED_IMPLEMENTATION + +#include "utility/Button_Class.inl" +#include "utility/I2C_Class.inl" +#include "utility/Log_Class.inl" +#include "utility/M5Timer.inl" +#include "utility/Touch_Class.inl" +#include "utility/M5IOE1_Class.inl" +#include "utility/PI4IOE5V6408_Class.inl" +#include "utility/LED_Class.inl" +#include "utility/led/LED_PMIC_Class.inl" +#include "utility/led/LED_PaperMono_Class.inl" +#include "utility/led/LED_PowerHub_Class.inl" +#include "utility/led/LED_Strip_Class.inl" +#include "utility/IMU_Class.inl" +#include "utility/imu/IMU_Base.inl" +#include "utility/imu/AK8963_Class.inl" +#include "utility/imu/BMI270_Class.inl" +#include "utility/imu/BMM150_Class.inl" +#include "utility/imu/MPU6886_Class.inl" +#include "utility/imu/SH200Q_Class.inl" +#include "utility/RTC_Class.inl" +#include "utility/rtc/RTC_Base.inl" +#include "utility/rtc/PCF8563_Class.inl" +#include "utility/rtc/RTC_PowerHub_Class.inl" +#include "utility/rtc/RX8130_Class.inl" +#include "utility/Power_Class.inl" +#include "utility/power/AW32001_Class.inl" +#include "utility/power/AXP192_Class.inl" +#include "utility/power/AXP2101_Class.inl" +#include "utility/power/BQ27220_Class.inl" +#include "utility/power/INA226_Class.inl" +#include "utility/power/INA3221_Class.inl" +#include "utility/power/IP5306_Class.inl" +#include "utility/power/M5PM1_Class.inl" +#include "utility/Speaker_Class.inl" +#include "utility/Mic_Class.inl" +#include "M5Unified.inl" + +#undef M5UNIFIED_IMPLEMENTATION diff --git a/src/M5Unified.inl b/src/M5Unified.inl new file mode 100644 index 00000000..14281fd1 --- /dev/null +++ b/src/M5Unified.inl @@ -0,0 +1,3882 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "M5Unified.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#include "M5Unified.hpp" +#include "utility/m5unified_i2c_addr.hpp" +#include "utility/PI4IOE5V6408_Class.hpp" +#include "utility/M5IOE1_Class.hpp" + +#if !defined (M5UNIFIED_PC_BUILD) +#include +#include +#include +#if __has_include () +#include +#endif + +#if defined (CONFIG_IDF_TARGET_ESP32P4) +#include +#endif + +#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + #if __has_include () + #include + #elif __has_include () + #include + #endif +#endif + +#if __has_include () +#include +#endif + +#if __has_include () + #include + #if ESP_IDF_VERSION_MAJOR >= 4 + /// [[fallthrough]]; + #define NON_BREAK ;[[fallthrough]]; + #endif + +#endif + +#include "utility/m5unified_i2s.h" + +#include "utility/led/LED_Strip_Class.hpp" +#include "utility/led/LED_PMIC_Class.hpp" +#include "utility/led/LED_PowerHub_Class.hpp" +#include "utility/led/LED_PaperMono_Class.hpp" + +#if defined (ARDUINO) && defined (CONFIG_IDF_TARGET_ESP32P4) +// ESP-Hosted (Arduino core HAL) の SDIO ピン設定。弱参照にして、WiFi/BLE を使わないビルドには +// ESP-Hosted のコードを引き込まない (リンカが解決しなければ nullptr になる)。 +extern "C" bool hostedSetPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst) __attribute__((weak)); +extern "C" bool hostedIsInitialized(void) __attribute__((weak)); +#endif + +#endif + +/// [[fallthrough]]; +#ifndef NON_BREAK +#define NON_BREAK ; +#endif + +/// global instance. +m5::M5Unified M5; + +void __attribute((weak)) adc_power_acquire(void) +{ +#if !defined (M5UNIFIED_PC_BUILD) +#if defined (ESP_IDF_VERSION_VAL) + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(3, 3, 4) + adc_power_on(); + #endif +#else + adc_power_on(); +#endif +#endif +} + +namespace m5 +{ +int8_t M5Unified::_get_pin_table[pin_name_max]; + +#if defined (M5UNIFIED_PC_BUILD) + void M5Unified::_setup_pinmap(board_t) + { + std::fill(_get_pin_table, _get_pin_table + pin_name_max, 255); + } +#else +// Pin number table. Place unknown at the end of the table. +// If there is no corresponding value, the value of unknown is used. +// ピン番号テーブル。 unknownをテーブルの最後に配置する。該当が無い場合はunknownの値が使用される。 +static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { + // In SCL,SDA, EX SCL,SDA +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5StackCoreS3, GPIO_NUM_11,GPIO_NUM_12 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5StackCoreS3SE,GPIO_NUM_11,GPIO_NUM_12, GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5StackChan , GPIO_NUM_11, GPIO_NUM_12, GPIO_NUM_1, GPIO_NUM_2 }, +{ board_t::board_M5StickS3 , GPIO_NUM_48,GPIO_NUM_47 , GPIO_NUM_10,GPIO_NUM_9 }, +{ board_t::board_M5StampS3 , 255 ,255 , GPIO_NUM_15,GPIO_NUM_13 }, +{ board_t::board_M5Capsule , GPIO_NUM_10,GPIO_NUM_8 , GPIO_NUM_15,GPIO_NUM_13 }, +{ board_t::board_M5Dial , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, +{ board_t::board_M5DinMeter , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, +{ board_t::board_M5AirQ , GPIO_NUM_12,GPIO_NUM_11 , GPIO_NUM_15,GPIO_NUM_13 }, +{ board_t::board_M5Cardputer , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5CardputerADV,GPIO_NUM_9 ,GPIO_NUM_8 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5VAMeter , GPIO_NUM_6 ,GPIO_NUM_5 , GPIO_NUM_9 ,GPIO_NUM_8 }, +{ board_t::board_M5AtomS3R , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5AtomS3RExt , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5AtomVoiceS3R,GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5AtomS3RCam , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5PaperS3 , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5PaperDIY , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_42,GPIO_NUM_41 }, +{ board_t::board_M5StampPLC , GPIO_NUM_15,GPIO_NUM_13 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5PowerHub , GPIO_NUM_48,GPIO_NUM_45 , GPIO_NUM_16,GPIO_NUM_15 }, +{ board_t::board_M5StampS3Bat , GPIO_NUM_47,GPIO_NUM_48 , 255 ,255 }, +{ board_t::board_M5PaperColor , GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_5 ,GPIO_NUM_4 }, +{ board_t::board_M5ChainCaptain,GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_6 ,GPIO_NUM_7 }, +{ board_t::board_M5PaperMono , GPIO_NUM_48,GPIO_NUM_47 , 255 ,255 }, +{ board_t::board_M5StopWatch , GPIO_NUM_48,GPIO_NUM_47 , GPIO_NUM_11,GPIO_NUM_10 }, +{ board_t::board_unknown , GPIO_NUM_39,GPIO_NUM_38 , GPIO_NUM_1 ,GPIO_NUM_2 }, // AtomS3,AtomS3Lite,AtomS3U +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +{ board_t::board_unknown , 255 ,255 , GPIO_NUM_0 ,GPIO_NUM_1 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +{ board_t::board_M5UnitC6L ,GPIO_NUM_8 ,GPIO_NUM_10 , 255 ,255 }, +{ board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, +{ board_t::board_M5NanoC6 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +{ board_t::board_M5CoreMatrix , GPIO_NUM_1 ,GPIO_NUM_0 , GPIO_NUM_1 ,GPIO_NUM_0 }, // Grove shares the internal bus (level-shifted) +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +{ board_t::board_M5NanoH2 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32P4) +{ board_t::board_M5CoreP4X , GPIO_NUM_9 ,GPIO_NUM_11 , GPIO_NUM_16,GPIO_NUM_18 }, // CoreP4X +{ board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31 , GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 +{ board_t::board_M5UnitPoEP4 , GPIO_NUM_1 ,GPIO_NUM_0 , GPIO_NUM_54,GPIO_NUM_53 }, +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +{ board_t::board_M5StampC5 , 255 ,255 , 255 ,255 }, +{ board_t::board_M5ToughC5 , GPIO_NUM_3 ,GPIO_NUM_2 , GPIO_NUM_3 ,GPIO_NUM_2 }, // PortA は内部バスと同一 (レベルシフタ経由の物理分配) +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +#else +{ board_t::board_M5Stack , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_22,GPIO_NUM_21 }, +{ board_t::board_M5Paper , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_32,GPIO_NUM_25 }, +{ board_t::board_M5TimerCam , GPIO_NUM_14,GPIO_NUM_12 , GPIO_NUM_13,GPIO_NUM_4 }, +{ board_t::board_M5AtomLite , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, +{ board_t::board_M5AtomMatrix , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, +{ board_t::board_M5AtomVoice , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, +{ board_t::board_M5AtomU , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, +{ board_t::board_M5AtomPsram , GPIO_NUM_21,GPIO_NUM_25 , GPIO_NUM_32,GPIO_NUM_26 }, +{ board_t::board_unknown , GPIO_NUM_22,GPIO_NUM_21 , GPIO_NUM_33,GPIO_NUM_32 }, // Core2,Tough,StickC,CoreInk,Station,StampPico +#endif +}; + +static constexpr const uint8_t _pin_table_port_bc[][5] = { + //pB p1,p2, pC p1,p2 (p1 close to 5V) +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5StackCoreS3, GPIO_NUM_8 ,GPIO_NUM_9 , GPIO_NUM_18,GPIO_NUM_17 }, +{ board_t::board_M5StackCoreS3SE,GPIO_NUM_8,GPIO_NUM_9 , GPIO_NUM_18,GPIO_NUM_17 }, +{ board_t::board_M5StackChan , GPIO_NUM_8, GPIO_NUM_9, GPIO_NUM_18, GPIO_NUM_17 }, +{ board_t::board_M5Dial , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, +{ board_t::board_M5DinMeter , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, +{ board_t::board_M5PowerHub , 255 , 255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5ChainCaptain,GPIO_NUM_17,GPIO_NUM_18, 255 ,255 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +{ board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, +{ board_t::board_ArduinoNessoN1,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +#elif defined (CONFIG_IDF_TARGET_ESP32P4) +{ board_t::board_M5Tab5 , GPIO_NUM_17,GPIO_NUM_52, GPIO_NUM_7 ,GPIO_NUM_6 }, // Tab5 +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +{ board_t::board_M5ToughC5 , GPIO_NUM_1 ,GPIO_NUM_6 , GPIO_NUM_12,GPIO_NUM_11 }, +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else +{ board_t::board_M5Stack , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_16,GPIO_NUM_17 }, +{ board_t::board_M5StackCore2 , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_13,GPIO_NUM_14 }, +{ board_t::board_M5Tough , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_13,GPIO_NUM_14 }, +{ board_t::board_M5Paper , GPIO_NUM_33,GPIO_NUM_26 , GPIO_NUM_19,GPIO_NUM_18 }, +{ board_t::board_M5Station , GPIO_NUM_35,GPIO_NUM_25 , GPIO_NUM_13,GPIO_NUM_14 }, +#endif +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +}; + +static constexpr const uint8_t _pin_table_port_de[][5] = { + //pD p1,p2, pE p1,p2 +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5StackCoreS3, 14,10, 18,17 }, +{ board_t::board_M5StackCoreS3SE,14,10,18,17 }, +{ board_t::board_M5StackChan, 14, 10, 18, 17 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else +{ board_t::board_M5Stack , GPIO_NUM_34,GPIO_NUM_35 , GPIO_NUM_5 ,GPIO_NUM_13 }, +{ board_t::board_M5StackCore2 , GPIO_NUM_34,GPIO_NUM_35 , GPIO_NUM_27,GPIO_NUM_19 }, +{ board_t::board_M5Station , GPIO_NUM_36,GPIO_NUM_26 , GPIO_NUM_16,GPIO_NUM_17 }, // B2 / C2 +#endif +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, +}; + +static constexpr const uint8_t _pin_table_sd[][7] = { + // clk,cmd(MOSI),D0(MISO),D1,D2,D3(CS) +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5StackCoreS3, GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackCoreS3SE,GPIO_NUM_36,GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackChan , GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5Capsule , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_39, 255 , 255 , GPIO_NUM_11 }, +{ board_t::board_M5Cardputer , GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, +{ board_t::board_M5CardputerADV,GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, +{ board_t::board_M5PaperS3 , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, +{ board_t::board_M5PaperDIY , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, +{ board_t::board_M5StampPLC , GPIO_NUM_7, GPIO_NUM_8, GPIO_NUM_9, 255 , 255 , GPIO_NUM_10 }, +{ board_t::board_M5PaperColor , GPIO_NUM_15, GPIO_NUM_13, GPIO_NUM_14, 255 , 255 , GPIO_NUM_47 }, +{ board_t::board_M5PaperMono , GPIO_NUM_13, GPIO_NUM_12, GPIO_NUM_11, GPIO_NUM_10, GPIO_NUM_9, GPIO_NUM_8 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +{ board_t::board_M5CoreMatrix , GPIO_NUM_25, GPIO_NUM_27, GPIO_NUM_26, 255 , 255 , GPIO_NUM_28 }, +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +#elif defined (CONFIG_IDF_TARGET_ESP32P4) +{ board_t::board_M5CoreP4X , GPIO_NUM_10, GPIO_NUM_7 , GPIO_NUM_8 , 255 , 255 , GPIO_NUM_50 }, +{ board_t::board_M5Tab5 , GPIO_NUM_43, GPIO_NUM_44, GPIO_NUM_39, GPIO_NUM_40, GPIO_NUM_41, GPIO_NUM_42 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +{ board_t::board_M5ToughC5 , GPIO_NUM_9 , GPIO_NUM_7 , GPIO_NUM_8 , 255 , 255 , GPIO_NUM_10 }, +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else +{ board_t::board_M5Stack , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_19, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackCore2 , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5Tough , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5Paper , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_13, 255 , 255 , GPIO_NUM_4 }, +#endif +{ board_t::board_unknown , 255 , 255 , 255 , 255 , 255 , 255 }, +}; + +static constexpr const uint8_t _pin_table_other0[][2] = { + //RGBLED +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5AtomS3U , GPIO_NUM_35 }, +{ board_t::board_M5AtomS3Lite , GPIO_NUM_35 }, +{ board_t::board_M5StampS3 , GPIO_NUM_21 }, +{ board_t::board_M5StampPLC , GPIO_NUM_21 }, +{ board_t::board_M5AirQ , GPIO_NUM_21 }, +{ board_t::board_M5Dial , GPIO_NUM_21 }, +{ board_t::board_M5DinMeter , GPIO_NUM_21 }, +{ board_t::board_M5Capsule , GPIO_NUM_21 }, +{ board_t::board_M5Cardputer , GPIO_NUM_21 }, +{ board_t::board_M5CardputerADV,GPIO_NUM_21 }, +{ board_t::board_M5PaperColor , GPIO_NUM_21 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +{ board_t::board_M5StampC3 , GPIO_NUM_2 }, +{ board_t::board_M5StampC3U , GPIO_NUM_2 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +{ board_t::board_M5NanoC6 , GPIO_NUM_20 }, +{ board_t::board_M5UnitC6L , GPIO_NUM_2 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +{ board_t::board_M5NanoH2 , GPIO_NUM_11 }, +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else +{ board_t::board_M5Stack , GPIO_NUM_15 }, +{ board_t::board_M5StackCore2 , GPIO_NUM_25 }, +{ board_t::board_M5Station , GPIO_NUM_4 }, +{ board_t::board_M5AtomLite , GPIO_NUM_27 }, +{ board_t::board_M5AtomMatrix , GPIO_NUM_27 }, +{ board_t::board_M5AtomVoice , GPIO_NUM_27 }, +{ board_t::board_M5AtomU , GPIO_NUM_27 }, +{ board_t::board_M5AtomPsram , GPIO_NUM_27 }, +{ board_t::board_M5StampPico , GPIO_NUM_27 }, +#endif +{ board_t::board_unknown , 255 }, +}; + +static constexpr const uint8_t _pin_table_other1[][2] = { + //POWER_HOLD +#if defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5Dial , GPIO_NUM_46 }, +{ board_t::board_M5Capsule , GPIO_NUM_46 }, +{ board_t::board_M5AirQ , GPIO_NUM_46 }, +{ board_t::board_M5DinMeter , GPIO_NUM_46 }, +{ board_t::board_M5PaperS3 , GPIO_NUM_44 }, + +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else + +{ board_t::board_M5StickCPlus2 , GPIO_NUM_4 }, +{ board_t::board_M5Paper , GPIO_NUM_2 }, +{ board_t::board_M5StackCoreInk, GPIO_NUM_12 }, +{ board_t::board_M5TimerCam , GPIO_NUM_33 }, + +#endif +{ board_t::board_unknown , 255 }, +}; + +static constexpr const uint8_t _pin_table_mbus[][31] = { +#if defined (CONFIG_IDF_TARGET_ESP32P4) +{ board_t::board_M5CoreP4X, + 255 , GPIO_NUM_17, + 255 , GPIO_NUM_20, + 255 , 255 , + GPIO_NUM_7 , GPIO_NUM_21, + GPIO_NUM_8 , GPIO_NUM_22, + GPIO_NUM_10, 255 , + GPIO_NUM_38, GPIO_NUM_37, + GPIO_NUM_15, GPIO_NUM_14, + GPIO_NUM_11, GPIO_NUM_9 , + GPIO_NUM_18, GPIO_NUM_16, + GPIO_NUM_39, GPIO_NUM_12, + GPIO_NUM_34, GPIO_NUM_23, + 255 , GPIO_NUM_19, + 255 , 255 , + 255 , 255 , +}, +{ board_t::board_M5Tab5 , + 255 , GPIO_NUM_16, + 255 , GPIO_NUM_17, + 255 , 255 , + GPIO_NUM_18, GPIO_NUM_45, + GPIO_NUM_19, GPIO_NUM_52, + GPIO_NUM_5 , 255 , + GPIO_NUM_38, GPIO_NUM_37, + GPIO_NUM_7 , GPIO_NUM_6 , + GPIO_NUM_31, GPIO_NUM_32, + GPIO_NUM_3 , GPIO_NUM_4 , + GPIO_NUM_2 , GPIO_NUM_48, + GPIO_NUM_47, GPIO_NUM_35, + 255 , GPIO_NUM_51, + 255 , 255 , + 255 , 255 , +}, +#elif defined (CONFIG_IDF_TARGET_ESP32S3) +{ board_t::board_M5StackCoreS3, + 255 , GPIO_NUM_10, + 255 , GPIO_NUM_8 , + 255 , 255 , + GPIO_NUM_37, GPIO_NUM_5 , + GPIO_NUM_35, GPIO_NUM_9 , + GPIO_NUM_36, 255 , + GPIO_NUM_44, GPIO_NUM_43, + GPIO_NUM_18, GPIO_NUM_17, + GPIO_NUM_12, GPIO_NUM_11, + GPIO_NUM_2 , GPIO_NUM_1 , + GPIO_NUM_6 , GPIO_NUM_7 , + GPIO_NUM_13, GPIO_NUM_0 , + 255 , GPIO_NUM_14, + 255 , 255 , + 255 , 255 , +}, +{ board_t::board_M5StackCoreS3SE, + 255 , GPIO_NUM_10, + 255 , GPIO_NUM_8 , + 255 , 255 , + GPIO_NUM_37, GPIO_NUM_5 , + GPIO_NUM_35, GPIO_NUM_9 , + GPIO_NUM_36, 255 , + GPIO_NUM_44, GPIO_NUM_43, + GPIO_NUM_18, GPIO_NUM_17, + GPIO_NUM_12, GPIO_NUM_11, + GPIO_NUM_2 , GPIO_NUM_1 , + GPIO_NUM_6 , GPIO_NUM_7 , + GPIO_NUM_13, GPIO_NUM_0 , + 255 , GPIO_NUM_14, + 255 , 255 , + 255 , 255 , +}, +{ board_t::board_M5StackChan, + 255 , GPIO_NUM_10, + 255 , GPIO_NUM_8 , + 255 , 255 , + GPIO_NUM_37, GPIO_NUM_5 , + GPIO_NUM_35, GPIO_NUM_9 , + GPIO_NUM_36, 255 , + GPIO_NUM_44, GPIO_NUM_43, + GPIO_NUM_18, GPIO_NUM_17, + GPIO_NUM_12, GPIO_NUM_11, + GPIO_NUM_2 , GPIO_NUM_1 , + GPIO_NUM_6 , GPIO_NUM_7 , + GPIO_NUM_13, GPIO_NUM_0 , + 255 , GPIO_NUM_14, + 255 , 255 , + 255 , 255 , +}, +#elif defined (CONFIG_IDF_TARGET_ESP32C3) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) +#elif defined (CONFIG_IDF_TARGET_ESP32C61) +{ board_t::board_M5CoreMatrix, + 255 , GPIO_NUM_3 , + 255 , GPIO_NUM_4 , + 255 , 255 , + GPIO_NUM_27, GPIO_NUM_5 , + GPIO_NUM_26, GPIO_NUM_6 , + GPIO_NUM_25, 255 , + GPIO_NUM_10, GPIO_NUM_11, + GPIO_NUM_7 , GPIO_NUM_8 , + GPIO_NUM_0 , GPIO_NUM_1 , + GPIO_NUM_0 , GPIO_NUM_1 , + GPIO_NUM_23, GPIO_NUM_22, + GPIO_NUM_24, GPIO_NUM_9 , + 255 , GPIO_NUM_29, + 255 , 255 , + 255 , 255 , +}, +#elif defined (CONFIG_IDF_TARGET_ESP32H2) +#elif defined (CONFIG_IDF_TARGET_ESP32C5) +#elif defined (CONFIG_IDF_TARGET_ESP32S2) +#else +{ board_t::board_M5Stack , + 255 , GPIO_NUM_35, + 255 , GPIO_NUM_36, + 255 , 255 , + GPIO_NUM_23, GPIO_NUM_25, + GPIO_NUM_19, GPIO_NUM_26, + GPIO_NUM_18, 255 , + GPIO_NUM_3 , GPIO_NUM_1 , + GPIO_NUM_16, GPIO_NUM_17, + GPIO_NUM_21, GPIO_NUM_22, + GPIO_NUM_2 , GPIO_NUM_5 , + GPIO_NUM_12, GPIO_NUM_13, + GPIO_NUM_15, GPIO_NUM_0 , + 255 , GPIO_NUM_34, + 255 , 255 , + 255 , 255 , +}, +{ board_t::board_M5StackCore2, + 255 , GPIO_NUM_35, + 255 , GPIO_NUM_36, + 255 , 255 , + GPIO_NUM_23, GPIO_NUM_25, + GPIO_NUM_38, GPIO_NUM_26, + GPIO_NUM_18, 255 , + GPIO_NUM_3 , GPIO_NUM_1 , + GPIO_NUM_13, GPIO_NUM_14, + GPIO_NUM_21, GPIO_NUM_22, + GPIO_NUM_32, GPIO_NUM_33, + GPIO_NUM_27, GPIO_NUM_19, + GPIO_NUM_2 , GPIO_NUM_0 , + 255 , GPIO_NUM_34, + 255 , 255 , + 255 , 255 , +}, +{ board_t::board_M5Tough, + 255 , GPIO_NUM_35, + 255 , GPIO_NUM_36, + 255 , 255 , + GPIO_NUM_23, GPIO_NUM_25, + GPIO_NUM_38, GPIO_NUM_26, + GPIO_NUM_18, 255 , + GPIO_NUM_3 , GPIO_NUM_1 , + GPIO_NUM_13, GPIO_NUM_14, + GPIO_NUM_21, GPIO_NUM_22, + GPIO_NUM_32, GPIO_NUM_33, + GPIO_NUM_27, GPIO_NUM_19, + GPIO_NUM_2 , GPIO_NUM_0 , + 255 , GPIO_NUM_34, + 255 , 255 , + 255 , 255 , +}, +#endif +{ board_t::board_unknown , 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, +}; + + void M5Unified::_setup_pinmap(board_t id) + { + if (id == board_t::board_M5Tab5X) { + id = board_t::board_M5Tab5; + } + + constexpr const std::pair tbl[] = { + { _pin_table_i2c_ex_in, sizeof(_pin_table_i2c_ex_in[0]) }, + { _pin_table_port_bc, sizeof(_pin_table_port_bc[0]) }, + { _pin_table_port_de, sizeof(_pin_table_port_de[0]) }, + { _pin_table_sd, sizeof(_pin_table_sd[0]) }, + { _pin_table_other0, sizeof(_pin_table_other0[0]) }, + { _pin_table_other1, sizeof(_pin_table_other1[0]) }, + { _pin_table_mbus, sizeof(_pin_table_mbus[0]) }, + }; + + int8_t* dst = _get_pin_table; + for (auto &p : tbl) { + const uint8_t* t = (uint8_t*)p.first; + size_t len = p.second; + while (t[0] != id && t[0] != board_t::board_unknown) { t += len; } + memcpy(dst, &t[1], len - 1); + dst += len - 1; + } + } +#endif + + /// @return true when every write in the table was acknowledged. + static bool in_i2c_bulk_write(const uint8_t i2c_addr, const uint8_t* bulk_data, const uint32_t i2c_freq = 100000u, const uint8_t retry = 0) + { + // bulk_data example.. + // const uint8_t bulk_data[] = { + // 2, 0x00, 0x00, // <- datalen = 2, reg = 0x00, data = 0x00 + // 3, 0x01, 0x00, 0x02, // <- datalen = 3, reg = 0x01, data = 0x00, 0x02 + // 0 }; // <- datalen 0 is end of data. + + bool all_ok = true; + while (*bulk_data) { + uint8_t len = *bulk_data++; + uint8_t r = retry + 1; + while (!M5.In_I2C.writeRegister(i2c_addr, bulk_data[0], &bulk_data[1], len - 1, i2c_freq) && --r) { m5gfx::delay(1); } + all_ok &= (r != 0); + bulk_data += len; + } + return all_ok; + } + + static constexpr uint8_t es7210_i2c_addr = 0x40; + static constexpr uint8_t es8311_i2c_addr0 = 0x18; + static constexpr uint8_t es8311_i2c_addr1 = 0x19; + +#if defined (CONFIG_IDF_TARGET_ESP32S3) + /// The ES8311 arms its capture path only through a SYSTEM(0x0D) write done + /// while the I2S clock is running; a pre-clock write is absorbed silently + /// (it even reads back). The arming is needed once per codec power-on + /// reset and survives I2C power-down/up cycles. + static std::atomic es8311_capture_armed { false }; + + /// Post-start callback: arms the capture path once the I2S clock runs. + /// The analog stage then warms up on its own (~1 s after power-up); that + /// is chip physics, so begin() pays only for the arming here. + static bool _microphone_post_start_cb_stopwatch(void* args) + { + (void)args; + if (es8311_capture_armed.load(std::memory_order_acquire)) { return true; } + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); + bool ok = false; + bool last_ok = false; + for (int i = 0; i < 3; ++i) + { // writes at ~+30/+60/+90 ms after the clock start; the earliest arming + // observed on hardware is ~+25 ms, the later points are the margin + m5gfx::delay(30); + last_ok = M5.In_I2C.writeRegister8(es8311_i2c_addr0, 0x0D, 0x01, 100000); + ok |= last_ok; + } + backup_i2c_setting.restore(); + /// an acknowledge proves only the transport, so the latch requires the + /// latest (most conservative) write to have been acknowledged + if (last_ok) { es8311_capture_armed.store(true, std::memory_order_release); } + return ok; + } +#endif + static constexpr uint8_t es8388_i2c_addr = 0x10; + static constexpr uint8_t pi4io1_i2c_addr = 0x43; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr uint8_t aw88298_i2c_addr = 0x36; + static void aw88298_write_reg(uint8_t reg, uint16_t value) + { + value = __builtin_bswap16(value); + M5.In_I2C.writeRegister(aw88298_i2c_addr, reg, (const uint8_t*)&value, 2, 400000); + } + + static void es7210_write_reg(uint8_t reg, uint8_t value) + { + M5.In_I2C.writeRegister(es7210_i2c_addr, reg, &value, 1, 400000); + } + +#endif + + bool M5Unified::_speaker_enabled_cb_core2(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32) + auto self = (M5Unified*)args; + auto spk_cfg = self->Speaker.config(); + if (spk_cfg.pin_bck == GPIO_NUM_12 + && spk_cfg.pin_ws == GPIO_NUM_0 + && spk_cfg.pin_data_out == GPIO_NUM_2 + ) { + switch (self->Power.getType()) { + case m5::Power_Class::pmic_axp192: + self->Power.Axp192.setGPIO2(enabled); + break; + case m5::Power_Class::pmic_axp2101: + self->Power.Axp2101.setALDO3(enabled * 3300); + break; + default: + break; + } + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_cores3(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto spk_cfg = self->Speaker.config(); + if (spk_cfg.pin_bck == GPIO_NUM_34 && enabled) + { + Power_Class::_core_s3_aw9523_bit(0x02, 0b00000100, true); + /// サンプリングレートに応じてAW88298のレジスタの設定値を変える; + static constexpr uint8_t rate_tbl[] = {4,5,6,8,10,11,15,20,22,44}; + size_t reg0x06_value = 0; + size_t rate = (spk_cfg.sample_rate + 1102) / 2205; + while (rate > rate_tbl[reg0x06_value] && ++reg0x06_value < sizeof(rate_tbl)) {} + + reg0x06_value |= 0x14C0; // I2SBCK=0 (BCK mode 16*2) + aw88298_write_reg( 0x61, 0x0673 ); // boost mode disabled + aw88298_write_reg( 0x04, 0x4040 ); // I2SEN=1 AMPPD=0 PWDN=0 + aw88298_write_reg( 0x05, 0x0008 ); // RMSE=0 HAGCE=0 HDCCE=0 HMUTE=0 + aw88298_write_reg( 0x06, reg0x06_value ); + aw88298_write_reg( 0x0C, 0x0064 ); // volume setting (full volume) + } + else /// disableにする場合および内蔵スピーカ以外を操作対象とした場合、内蔵スピーカを停止する。 + { + aw88298_write_reg( 0x04, 0x4000 ); // I2SEN=0 AMPPD=0 PWDN=0 + Power_Class::_core_s3_aw9523_bit(0x02, 0b00000100, false); + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_sticks3(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto spk_cfg = self->Speaker.config(); + if (spk_cfg.pin_bck == GPIO_NUM_17) + { + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + self->In_I2C.bitOn(m5pm1_i2c_addr, 0x11, 0b00001000, 100000); + ESP_LOGD("M5Unified", "enabling es8311\n"); + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + } + else /// disableにする場合および内蔵スピーカ以外を操作対象とした場合、内蔵スピーカを停止する。 + { + ESP_LOGD("M5Unified", "disabling es8311\n"); + self->In_I2C.bitOff(m5pm1_i2c_addr, 0x11, 0b00001000, 100000); + } +//*/ + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_papercolor(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto spk_cfg = self->Speaker.config(); + gpio_num_t codec_en_pin = GPIO_NUM_45; + gpio_num_t spk_en_pin = GPIO_NUM_46; + m5gfx::pinMode(codec_en_pin, m5gfx::pin_mode_t::output); + m5gfx::pinMode(spk_en_pin, m5gfx::pin_mode_t::output); + if (spk_cfg.pin_bck == GPIO_NUM_40) + { + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xCF, // 0x32 DAC/ DAC volume (0xCF == +16 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + m5gfx::gpio_hi(codec_en_pin); + m5gfx::gpio_hi(spk_en_pin); + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + } + else + { + m5gfx::gpio_lo(codec_en_pin); + m5gfx::gpio_lo(spk_en_pin); + } + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_stopwatch(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xCB, // 0x32 DAC/ DAC volume +6 dB (0xBF == 0 dB, 0.5 dB/step). Reaches full-scale at max master volume; higher values clip digitally without adding loudness + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) + self->delay(10); + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + ioe1.digitalWrite(M5IOE1_Class::gpio10, true); // Enable PA (M5IOE1_G10) + } + else + { /// Keep Audio Power (M5IOE1_G3) on: cutting it forces another codec + /// reset, re-arming and re-warm-up on the next capture (and a quickly + /// cycled rail misfires). Only the DAC is powered down; Mic disable + /// does the deeper I2C power-down. + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); // Disable PA (M5IOE1_G10) + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x12, 0x02, // 0x12 SYSTEM/ power-down DAC + 0 + }; + in_i2c_bulk_write(es8311_i2c_addr0, disabled_bulk_data, 100000, 3); + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_chain_captain(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xEF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail + self->delay(10); + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + m5gfx::gpio_hi(GPIO_NUM_21); // AW8737A one-wire enable, default mode + } + else + { + m5gfx::gpio_lo(GPIO_NUM_21); + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, false); + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_tab5(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32P4) + auto self = (M5Unified*)args; + auto spk_cfg = self->Speaker.config(); + if (spk_cfg.pin_data_out != GPIO_NUM_26) { return false; } + + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0, 0x80, // RESET/ CSM POWER ON + 2, 0, 0x00, + 2, 0, 0x00, + 2, 0, 0x0E, + 2, 1, 0x00, + 2, 2, 0x0A, //CHIP POWER: power up all + 2, 3, 0xFF, //ADC POWER: power down all + 2, 4, 0x3C, //DAC POWER: power up and LOUT1/ROUT1/LOUT2/ROUT2 enable + 2, 5, 0x00, //ChipLowPower1 + 2, 6, 0x00, //ChipLowPower2 + 2, 7, 0x7C, //VSEL + 2, 8, 0x00, //set I2S slave mode + // reg9-22 == adc + 2, 23, 0x18, //I2S format (16bit) + 2, 24, 0x00, //I2S MCLK ratio (128) + 2, 25, 0x20, //DAC unmute + 2, 26, 0x00, //LDACVOL 0x00~0xC0 + 2, 27, 0x00, //RDACVOL 0x00~0xC0 + 2, 28, 0x08, //enable digital click free power up and down + 2, 29, 0x00, + 2, 38, 0x00, //DAC CTRL16 + 2, 39, 0xB8, //LEFT Ch MIX + 2, 42, 0xB8, //RIGHTCh MIX + 2, 43, 0x08, //ADC and DAC separate + 2, 45, 0x00, // 0x00=1.5k VREF analog output / 0x10=40kVREF analog output + 2, 46, 0x21, + 2, 47, 0x21, + 2, 48, 0x21, + 2, 49, 0x21, + 0 + }; + if (enabled) + { + in_i2c_bulk_write(es8388_i2c_addr, enabled_bulk_data); + // AMP on + M5.In_I2C.bitOn(pi4io1_i2c_addr, 0x05, 0b00000010, 400000); + } + else + { // 正規の power-down シーケンス。end() は cb(false) を I2S 停止より先に呼ぶため、 + // ここは MCLK/BCLK が生きているうちに実行される。旧実装 (reg8 のみ書き込み) では + // DAC 稼働状態のままクロックが絶たれ、次回 enable の reset が毎回異なる残留状態 + // から行われて受信位相が begin ごとに不定になっていた。毎回同一の power-down + // 状態へ落としてから終了することで、次回 enable を常に定義済み状態から始める。 + M5.In_I2C.bitOff(pi4io1_i2c_addr, 0x05, 0b00000010, 400000); // AMP off (過渡音を出さない) + M5.In_I2C.writeRegister8(es8388_i2c_addr, 25, 0x24, 400000); // DACCONTROL3: mute (SoftRamp 維持) + m5gfx::delay(1); // soft-ramp 遷移待ち + M5.In_I2C.writeRegister8(es8388_i2c_addr, 4, 0xC0, 400000); // DACPOWER: DAC L/R down + 全出力 off + M5.In_I2C.writeRegister8(es8388_i2c_addr, 2, 0xFF, 400000); // CHIPPOWER: 全停止 (ADF deinit と同一の終端状態) + } +#endif + return true; + } + +#if defined (CONFIG_IDF_TARGET_ESP32P4) + // Callers are P4-only as well; keep the definition inside the guard to avoid an unused-function warning elsewhere. + static void _corep4x_audio_power(M5Unified* self, bool enabled) + { + // Speaker and microphone share M5IOE1_G1, so keep the rail enabled at runtime. + if (!enabled) { return; } + auto& ioe1 = self->getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); + ioe1.setDirection(M5IOE1_Class::gpio1, true); + ioe1.digitalWrite(M5IOE1_Class::gpio1, true); + self->delay(20); + } +#endif + + bool M5Unified::_speaker_enabled_cb_corep4x(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + auto self = (M5Unified*)args; + static constexpr const uint8_t enabled_bulk_data[] = { + // ES8311 slave, 24 kHz, 16-bit I2S, MCLK = 256 * sample rate. + 2, 0x0D, 0xFA, + 2, 0x44, 0x08, + 2, 0x44, 0x08, + 2, 0x01, 0x30, + 2, 0x02, 0x00, + 2, 0x03, 0x10, + 2, 0x16, 0x24, + 2, 0x04, 0x10, + 2, 0x05, 0x00, + 2, 0x0B, 0x00, + 2, 0x0C, 0x00, + 2, 0x10, 0x1F, + 2, 0x11, 0x7F, + 2, 0x00, 0x80, + 2, 0x01, 0x3F, + 2, 0x06, 0x03, + 2, 0x13, 0x10, + 2, 0x1B, 0x0A, + 2, 0x1C, 0x6A, + 2, 0x44, 0x58, + 2, 0x09, 0x00, + 2, 0x17, 0xBF, + 2, 0x0E, 0x02, + 2, 0x12, 0x00, + 2, 0x14, 0x1A, + 2, 0x0D, 0x01, + 2, 0x15, 0x40, + 2, 0x37, 0x08, + 2, 0x45, 0x00, + 2, 0x07, 0x00, + 2, 0x08, 0xFF, + 2, 0x32, 0xBF, + 0 + }; + _corep4x_audio_power(self, enabled); + auto& ioe1 = self->getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); + ioe1.setDirection(M5IOE1_Class::gpio3, true); + ioe1.digitalWrite(M5IOE1_Class::gpio3, enabled); + if (enabled) + { + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + } +#else + (void)args; + (void)enabled; +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_hat_spk(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32) + auto self = (M5Unified*)args; + gpio_num_t pin_en = self->_board == board_t::board_M5StackCoreInk ? GPIO_NUM_25 : GPIO_NUM_0; + if (enabled) + { + m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); + m5gfx::gpio_hi(pin_en); + } + else + { m5gfx::gpio_lo(pin_en); } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_atomic_echo(void* args, bool enabled) + { + (void)args; + (void)enabled; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xFF, // 0x32 DAC/ DAC volume (full volume) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 0 + }; + + static constexpr const uint8_t enabled_pi4ioe_bulk_data[] = { + 2, 0x03, 0xFF, // PI4IOE direction:OUTPUT + 2, 0x05, 0xFF, // PI4IOE output HIGH + 2, 0x07, 0x00, // PI4IOE set push-pull + 2, 0x0B, 0x00, // Disable pull (up and down) + 0 + }; + static constexpr const uint8_t disabled_pi4ioe_bulk_data[] = { + 2, 0x05, 0x00, + 0 + }; + +#if defined (CONFIG_IDF_TARGET_ESP32S3) + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_38, GPIO_NUM_39); +#endif + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); + in_i2c_bulk_write(pi4io1_i2c_addr, enabled ? enabled_pi4ioe_bulk_data : disabled_pi4ioe_bulk_data); +#if defined (CONFIG_IDF_TARGET_ESP32S3) + backup_i2c_setting.restore(); +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_stickc(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32) + auto self = (M5Unified*)args; + self->Power.Axp192.setLDO0(enabled ? 2800 : 0); +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_cores3(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto cfg = self->Mic.config(); + if (cfg.pin_bck == GPIO_NUM_34) + { + es7210_write_reg(0x00, 0xFF); // RESET_CTL + struct __attribute__((packed)) reg_data_t + { + uint8_t reg; + uint8_t value; + }; + if (enabled) + { + static constexpr reg_data_t data[] = + { + { 0x00, 0x41 }, // RESET_CTL + { 0x01, 0x1f }, // CLK_ON_OFF + { 0x06, 0x00 }, // DIGITAL_PDN + { 0x07, 0x20 }, // ADC_OSR + { 0x08, 0x10 }, // MODE_CFG + { 0x09, 0x30 }, // TCT0_CHPINI + { 0x0A, 0x30 }, // TCT1_CHPINI + { 0x20, 0x0a }, // ADC34_HPF2 + { 0x21, 0x2a }, // ADC34_HPF1 + { 0x22, 0x0a }, // ADC12_HPF2 + { 0x23, 0x2a }, // ADC12_HPF1 + { 0x02, 0xC1 }, + { 0x04, 0x01 }, + { 0x05, 0x00 }, + { 0x11, 0x60 }, + { 0x40, 0x42 }, // ANALOG_SYS + { 0x41, 0x70 }, // MICBIAS12 + { 0x42, 0x70 }, // MICBIAS34 + { 0x43, 0x1B }, // MIC1_GAIN + { 0x44, 0x1B }, // MIC2_GAIN + { 0x45, 0x00 }, // MIC3_GAIN + { 0x46, 0x00 }, // MIC4_GAIN + { 0x47, 0x00 }, // MIC1_LP + { 0x48, 0x00 }, // MIC2_LP + { 0x49, 0x00 }, // MIC3_LP + { 0x4A, 0x00 }, // MIC4_LP + { 0x4B, 0x00 }, // MIC12_PDN + { 0x4C, 0xFF }, // MIC34_PDN + { 0x01, 0x14 }, // CLK_ON_OFF + }; + for (auto& d: data) + { + es7210_write_reg(d.reg, d.value); + } + } + } +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_cardputer_adv(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 0 + }; + + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); +#endif + return true; + } + + + bool M5Unified::_microphone_enabled_cb_atomic_echo(void* args, bool enabled) + { + (void)args; + (void)enabled; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) + 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_38, GPIO_NUM_39); +#endif + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); +#if defined (CONFIG_IDF_TARGET_ESP32S3) + backup_i2c_setting.restore(); +#endif + + return true; + } + + bool M5Unified::_microphone_enabled_cb_atom_echos3r(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) + 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_45, GPIO_NUM_0); + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); + backup_i2c_setting.restore(); +#endif + return true; + } + + bool M5Unified::_speaker_enabled_cb_atom_echos3r(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xFF, // 0x32 DAC/ DAC volume (full volume) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 0 + }; + + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_45, GPIO_NUM_0); + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); + gpio_num_t pin_en = GPIO_NUM_18; + if (enabled) + { + m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); + m5gfx::gpio_hi(pin_en); + } + else + { m5gfx::gpio_lo(pin_en); } + backup_i2c_setting.restore(); +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_sticks3(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) + 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); // sda, scl + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); + backup_i2c_setting.restore(); +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_papercolor(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto cfg = self->Mic.config(); + gpio_num_t codec_en_pin = GPIO_NUM_45; + m5gfx::pinMode(codec_en_pin, m5gfx::pin_mode_t::output); + m5gfx::gpio_hi(codec_en_pin); + delay(50); + if (cfg.pin_bck == GPIO_NUM_40) + { + es7210_write_reg(0x00, 0xFF); // RESET_CTL + struct __attribute__((packed)) reg_data_t + { + uint8_t reg; + uint8_t value; + }; + if (enabled) + { + static constexpr reg_data_t data[] = + { + { 0x00, 0x41 }, // RESET_CTL + { 0x01, 0x1f }, // CLK_ON_OFF + { 0x06, 0x00 }, // DIGITAL_PDN + { 0x07, 0x20 }, // ADC_OSR + { 0x08, 0x10 }, // MODE_CFG + { 0x09, 0x30 }, // TCT0_CHPINI + { 0x0A, 0x30 }, // TCT1_CHPINI + { 0x20, 0x0a }, // ADC34_HPF2 + { 0x21, 0x2a }, // ADC34_HPF1 + { 0x22, 0x0a }, // ADC12_HPF2 + { 0x23, 0x2a }, // ADC12_HPF1 + { 0x02, 0xC1 }, + { 0x04, 0x01 }, + { 0x05, 0x00 }, + { 0x11, 0x60 }, + { 0x40, 0x42 }, // ANALOG_SYS + { 0x41, 0x70 }, // MICBIAS12 + { 0x42, 0x70 }, // MICBIAS34 + { 0x43, 0x1B }, // MIC1_GAIN + { 0x44, 0x00 }, // MIC2_GAIN + { 0x45, 0x00 }, // MIC3_GAIN + { 0x46, 0x00 }, // MIC4_GAIN + { 0x47, 0x00 }, // MIC1_LP + { 0x48, 0x00 }, // MIC2_LP + { 0x49, 0x00 }, // MIC3_LP + { 0x4A, 0x00 }, // MIC4_LP + { 0x4B, 0x00 }, // MIC12_PDN + { 0x4C, 0xFF }, // MIC34_PDN + { 0x01, 0x14 }, // CLK_ON_OFF + }; + for (auto& d: data) + { + es7210_write_reg(d.reg, d.value); + } + } + } +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_papermono(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto& ioe1 = self->getIOExpander(0); + bool result = true; + if (enabled) + { + // The factory firmware enables PM1 BOOST before powering the PDM mic. + result = self->Power.M5pm1.setExtOutput(true); + } + // M5IOE1 GPIO12 is the Paper Mono PDM microphone power enable. + ioe1.setHighImpedance(M5IOE1_Class::gpio12, false); + ioe1.setDirection(M5IOE1_Class::gpio12, true); + ioe1.digitalWrite(M5IOE1_Class::gpio12, enabled); + return result; +#else + (void)args; + (void)enabled; +#endif + return true; + } + + + bool M5Unified::_microphone_enabled_cb_stopwatch(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x17, // ES8311_ADC_REG14 : select Mic1p-Mic1n / analog PGA +21 dB (gain code 7, 3 dB/code; SNR plateaus by this level) + 2, 0x17, 0xCB, // ES8311_ADC_REG17 : ADC_VOLUME +6 dB (0xBF == 0 dB, 0.5 dB/step) + 2, 0x1C, 0x64, // ES8311_ADC_REG1C : ADC EQ bypass, dynamic HPF, HPF stage-2 coeff 4 (cuts sub-200 Hz rumble at the default 16 kHz rate) + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; + if (enabled) + { + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) + self->delay(5); + } + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); + if (enabled) + { /// 0x17 loses the value a previous setup wrote on any codec reset, + /// so it witnesses a reset done outside this library: re-arm then. + uint8_t v = 0; + if (!M5.In_I2C.readRegister(es8311_i2c_addr0, 0x17, &v, 1, 100000) || v != 0xCB) + { + es8311_capture_armed.store(false, std::memory_order_release); + } + } + bool setup_ok = in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); + backup_i2c_setting.restore(); + /// a codec with an incomplete setup must not be published as working + if (enabled && !setup_ok) { return false; } +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_chain_captain(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // RESET / CSM power on + 2, 0x01, 0xBA, // MCLK from BCLK + 2, 0x02, 0x18, // clock multiplier + 2, 0x0D, 0x01, // power up analog circuitry + 2, 0x0E, 0x02, // enable analog PGA and ADC modulator + 2, 0x14, 0x10, // differential microphone input, minimum PGA gain + 2, 0x17, 0xFF, // ADC volume + 2, 0x1C, 0x6A, // bypass ADC equalizer and cancel DC offset + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, + 2, 0x0E, 0x6A, + 2, 0x00, 0x00, + 0 + }; + if (enabled) + { + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail + self->delay(5); + } + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); +#endif + return true; + } + +#if defined (CONFIG_IDF_TARGET_ESP32) && SOC_TOUCH_SENSOR_SUPPORTED + /// @param channel touch channel ids. (ESP-IDF v6 removed the touch_pad_t enum, so plain integers are used here) + /// @return true = all channels were read successfully. + static bool _read_touch_pad(uint32_t* results, const int* channel, const size_t channel_count) + { + for (size_t i = 0; i < channel_count; ++i) { results[i] = 0; } +#if defined ( TOUCH_SENSOR_DEFAULT_FILTER_CONFIG ) + if (channel_count > TOUCH_TOTAL_CHAN_NUM) { return false; } + + /* Step 1: Create a new touch sensor controller handle with default sample configuration */ + touch_sensor_sample_config_t sample_cfg = {}; + sample_cfg.charge_duration_ms = 5.0f; + sample_cfg.charge_volt_lim_h = TOUCH_VOLT_LIM_H_1V7; + sample_cfg.charge_volt_lim_l = TOUCH_VOLT_LIM_L_0V5; + + touch_sensor_config_t sens_cfg = {}; + sens_cfg.power_on_wait_us = 256; + sens_cfg.meas_interval_us = 320.0; + sens_cfg.intr_trig_mode = TOUCH_INTR_TRIG_ON_BELOW_THRESH; + sens_cfg.intr_trig_group = TOUCH_INTR_TRIG_GROUP_BOTH; + sens_cfg.sample_cfg_num = 1; + sens_cfg.sample_cfg = &sample_cfg; + touch_sensor_handle_t sens_handle = nullptr; + if (touch_sensor_new_controller(&sens_cfg, &sens_handle) != ESP_OK) { return false; } + + touch_channel_config_t chan_cfg = {}; + chan_cfg.abs_active_thresh[0] = 1024; + chan_cfg.charge_speed = TOUCH_CHARGE_SPEED_7; + chan_cfg.init_charge_volt = TOUCH_INIT_CHARGE_VOLT_DEFAULT; + chan_cfg.group = TOUCH_CHAN_TRIG_GROUP_BOTH; + touch_channel_handle_t chan_handle[TOUCH_TOTAL_CHAN_NUM] = { nullptr, }; + size_t created = 0; + while (created < channel_count + && touch_sensor_new_channel(sens_handle, channel[created], &chan_cfg, &chan_handle[created]) == ESP_OK) { + ++created; + } + bool result = false; + if (created == channel_count) { + touch_sensor_filter_config_t filter_cfg = TOUCH_SENSOR_DEFAULT_FILTER_CONFIG(); + if (touch_sensor_config_filter(sens_handle, &filter_cfg) == ESP_OK) { + bool scanned = false; + if (touch_sensor_enable(sens_handle) == ESP_OK) { + scanned = (touch_sensor_trigger_oneshot_scanning(sens_handle, 64) == ESP_OK); + touch_sensor_disable(sens_handle); + } + if (scanned) { + result = true; + for (size_t i = 0; i < channel_count; i++) { + result &= (touch_channel_read_data(chan_handle[i], TOUCH_CHAN_DATA_TYPE_SMOOTH, &results[i]) == ESP_OK); + } + } + // Passing nullptr releases the software filter timer. (del_controller does not) + touch_sensor_config_filter(sens_handle, nullptr); + } + } + while (created) { + touch_sensor_del_channel(chan_handle[--created]); + } + touch_sensor_del_controller(sens_handle); + return result; +#else + if (touch_pad_init() != ESP_OK) { return false; } + bool result = true; + for (size_t i = 0; i < channel_count; i++) { + result &= (touch_pad_config((touch_pad_t)channel[i], TOUCH_PAD_THRESHOLD_MAX) == ESP_OK); + } + for (size_t i = 0; i < channel_count; i++) { + uint16_t tmp = 0; + result &= (touch_pad_read((touch_pad_t)channel[i], &tmp) == ESP_OK); + results[i] = tmp; + } + touch_pad_deinit(); + return result; +#endif + } +#endif + + bool M5Unified::_microphone_enabled_cb_tab5(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32P4) + auto self = (M5Unified*)args; + auto cfg = self->Mic.config(); + if (cfg.pin_data_in != GPIO_NUM_28) { return false; } + + M5.In_I2C.writeRegister8(es7210_i2c_addr, 0x00, 0xFF, 400000); + if (enabled) + { + static constexpr uint8_t data[] = + { + 2, 0x00, 0x41, // RESET_CTL + 2, 0x01, 0x1f, // CLK_ON_OFF + 2, 0x06, 0x00, // DIGITAL_PDN + 2, 0x07, 0x20, // ADC_OSR + 2, 0x08, 0x10, // MODE_CFG + 2, 0x09, 0x30, // TCT0_CHPINI + 2, 0x0A, 0x30, // TCT1_CHPINI + 2, 0x20, 0x0a, // ADC34_HPF2 + 2, 0x21, 0x2a, // ADC34_HPF1 + 2, 0x22, 0x0a, // ADC12_HPF2 + 2, 0x23, 0x2a, // ADC12_HPF1 + 2, 0x02, 0xC1, + 2, 0x04, 0x01, + 2, 0x05, 0x00, + 2, 0x11, 0x60, + 2, 0x40, 0x42, // ANALOG_SYS + 2, 0x41, 0x70, // MICBIAS12 + 2, 0x42, 0x70, // MICBIAS34 + 2, 0x43, 0x1B, // MIC1_GAIN + 2, 0x44, 0x1B, // MIC2_GAIN + 2, 0x45, 0x00, // MIC3_GAIN + 2, 0x46, 0x00, // MIC4_GAIN + 2, 0x47, 0x00, // MIC1_LP + 2, 0x48, 0x00, // MIC2_LP + 2, 0x49, 0x00, // MIC3_LP + 2, 0x4A, 0x00, // MIC4_LP + 2, 0x4B, 0x00, // MIC12_PDN + 2, 0x4C, 0xFF, // MIC34_PDN + 2, 0x01, 0x14, // CLK_ON_OFF + 0, + }; + in_i2c_bulk_write(es7210_i2c_addr, data); + } +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_corep4x(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + auto self = (M5Unified*)args; + _corep4x_audio_power(self, enabled); + self->In_I2C.writeRegister8(es7210_i2c_addr, 0x00, 0xFF, 400000); + if (enabled) + { + static constexpr uint8_t data[] = + { + 2, 0x00, 0x41, // RESET_CTL + 2, 0x01, 0x1f, // CLK_ON_OFF + 2, 0x06, 0x00, // DIGITAL_PDN + 2, 0x07, 0x20, // ADC_OSR + 2, 0x08, 0x10, // MODE_CFG + 2, 0x09, 0x30, // TCT0_CHPINI + 2, 0x0A, 0x30, // TCT1_CHPINI + 2, 0x20, 0x0a, // ADC34_HPF2 + 2, 0x21, 0x2a, // ADC34_HPF1 + 2, 0x22, 0x0a, // ADC12_HPF2 + 2, 0x23, 0x2a, // ADC12_HPF1 + 2, 0x02, 0xC1, + 2, 0x04, 0x01, + 2, 0x05, 0x00, + 2, 0x11, 0x60, + 2, 0x40, 0x42, // ANALOG_SYS + 2, 0x41, 0x70, // MICBIAS12 + 2, 0x42, 0x70, // MICBIAS34 + 2, 0x43, 0x1B, // MIC1_GAIN + 2, 0x44, 0x1B, // MIC2_GAIN + 2, 0x45, 0x1B, // MIC3_GAIN (AEC input) + 2, 0x46, 0x1B, // MIC4_GAIN (TDM slot 4) + 2, 0x47, 0x00, // MIC1_LP + 2, 0x48, 0x00, // MIC2_LP + 2, 0x49, 0x00, // MIC3_LP + 2, 0x4A, 0x00, // MIC4_LP + 2, 0x4B, 0x00, // MIC12_PDN + 2, 0x4C, 0x00, // MIC34_PDN + 2, 0x01, 0x14, // CLK_ON_OFF + 0, + }; + in_i2c_bulk_write(es7210_i2c_addr, data, 100000, 3); + } +#else + (void)args; + (void)enabled; +#endif + return true; + } + + bool M5Unified::_microphone_enabled_cb_cardputer_adv(void* args, bool enabled) + { + (void)args; + (void)enabled; +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 2, 0x17, 0xBF, // ES8311_ADC_REG17 : ADC_VOLUME 0xBF == ± 0 dB + 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; + + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data); +#endif + return true; + } + +#if defined (M5UNIFIED_PC_BUILD) +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + static constexpr gpio_num_t TFCARD_CS_PIN = GPIO_NUM_4; + static constexpr gpio_num_t CoreInk_BUTTON_EXT_PIN = GPIO_NUM_5; + static constexpr gpio_num_t CoreInk_BUTTON_PWR_PIN = GPIO_NUM_27; +#endif + + /// probe を始める前に一度だけ、デバイスの電源が安定するのを待つ。 + /// 待ちが要るのは「電源投入から間もない」ことであってアドレスごとの事情ではないので、 + /// 判定の入口で一度払う。旧実装は最初の probe が常に真を返して連鎖が止まっていたため + /// 結果的に 1 回しか待っていなかった。それを意図として書き直したもの。 + void M5Unified::_wait_i2c_device_power(void) + { +#if !defined(M5UNIFIED_PC_BUILD) + static bool waited = false; + if (!waited) + { + waited = true; + m5gfx::delay(50); + } +#endif + } + + bool M5Unified::_probe_i2c_addr(uint8_t sda, uint8_t scl, uint8_t addr) + { +#if defined(M5UNIFIED_PC_BUILD) + return false; +#else + /// アドレスの存在確認はソフトウェア I2C ポートで行う (オープンドレイン駆動で + /// ACK 競合が起きず、ハードウェアのペリフェラルにも触れない)。 + /// ポートは M5GFX の autodetect probe (-1) と分ける。ソフト I2C のスロットは + /// 所有権を持たず、init が既存の設定を黙って奪う作りなので、ライブラリ同士で + /// 同じスロットを共有しない。 + static constexpr int_fast16_t probe_i2c_port = -2; + + _wait_i2c_device_power(); + + m5gfx::gpio::pin_backup_t pin_backup[] = { scl, sda }; + + // ここでは待たない。電源安定待ちは判定の入口で一度だけ行う (_wait_i2c_device_power)。 + // アドレスごとに待つと、判定が空振りするたびに数十 ms が起動時間へ積み上がる。 + m5gfx::pinMode(scl, m5gfx::pin_mode_t::input_pullup); + m5gfx::pinMode(sda, m5gfx::pin_mode_t::input_pullup); + + // 指定ピンが「外部プルアップの載った I2C バス」かどうかを先に確かめる。 + // ここは I2C ピンとは限らない場所を駆動する機種判別なので、判定を外すと + // 機種の読みが変わる。判定方式は M5GFX の autodetect probe と同一のものを使う + // (実機での実績がある形から動かさない。変えるなら該当機種すべてで再検証が要る)。 + // + // 4 回の read のうち、後半 2 回は入力プルダウンにしても High になること + // (= 外部プルアップが内部プルダウンに勝つこと) を確認するもの。 + // 弱いプルアップ (内部プルダウンと同程度の抵抗値) では通らないが、 + // 「強いプルアップがある = I2C バスである」を要求するのがこの判定の趣旨。 + const uint8_t cmd_bus_check_list[] = { + m5gfx::gpio::command_write_low , scl, + m5gfx::gpio::command_read , scl, // low チェック + m5gfx::gpio::command_write_low , sda, + m5gfx::gpio::command_read , sda, // low チェック + m5gfx::gpio::command_mode_input_pulldown, scl, + m5gfx::gpio::command_delay_usec , 10, + m5gfx::gpio::command_read , scl, // 外部プルアップがあるなら High + m5gfx::gpio::command_mode_input_pullup , scl, + m5gfx::gpio::command_mode_input_pulldown, sda, + m5gfx::gpio::command_delay_usec , 10, + m5gfx::gpio::command_read , sda, // 外部プルアップがあるなら High + m5gfx::gpio::command_mode_input_pullup , sda, + m5gfx::gpio::command_end + }; + auto bus_check = [&](void) -> uint32_t + { + // ラッチを Low にしてから出力へ切り替える。直前は input_pullup (ラッチ High) + // なので、先に出力にすると一瞬 push-pull で High を駆動してしまう。 + // ここは相手が何か分からないピンなので、High は一度も駆動しない。 + m5gfx::gpio_lo(scl); m5gfx::pinMode(scl, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(sda); m5gfx::pinMode(sda, m5gfx::pin_mode_t::output); + return m5gfx::gpio::command(cmd_bus_check_list); + }; + + // 0x02 は「SCL は外部プルアップで戻るのに SDA だけ Low のまま」。前回の通信の + // 途中で止まったデバイスがデータ線を握っている典型で (ソフトリセットでは + // デバイスの電源が切れないため実際に起きる)、プルアップの無いただの Low ピンとは + // このシグネチャで区別できる。この場合だけは STOP を見せて握りを解かせ、 + // もう一度確かめる。それ以外の不一致は I2C バスではないとみなして即座に帰る。 + uint32_t check = bus_check(); + if (check != 0x03 && check != 0x02) + { + for (auto& backup : pin_backup) { backup.restore(); } + return false; + } + + // 前回の通信の途中で止まっているデバイスに STOP を見せて論理状態を戻す。 + // START だけで戻らないデバイスが実在する (同ファイルの StampS3/Capsule 判別に + // 「STOP を出さないと正しく動作しないデバイスがあった (UnitHEART MAX30100)」の記録がある)。 + // 線を Low へ駆動するか解放するかの 2 状態しか使わない (High を駆動しない) ので、 + // デバイスが線を握っていてもパッド同士の衝突にはならない。 + { + auto line_lo = [](uint8_t pin) + { m5gfx::gpio_lo(pin); m5gfx::pinMode(pin, m5gfx::pin_mode_t::output); }; + auto line_hi = [](uint8_t pin) + { m5gfx::pinMode(pin, m5gfx::pin_mode_t::input_pullup); }; + for (int i = 0; i < 8; ++i) + { + line_lo(scl); m5gfx::delayMicroseconds(5); + line_lo(sda); m5gfx::delayMicroseconds(5); + line_hi(scl); m5gfx::delayMicroseconds(5); + line_hi(sda); m5gfx::delayMicroseconds(5); // SCL High 中の SDA Low->High = STOP + } + } + + if (check != 0x03 && bus_check() != 0x03) + { // 握りが解けなかった。ここから先は probe しても意味がない。 + for (auto& backup : pin_backup) { backup.restore(); } + return false; + } + + bool hit = false; + if (m5gfx::i2c::init(probe_i2c_port, sda, scl).has_value()) + { + // クロックが上がらないバス、前の通信の途中でデバイスがデータ線を握っている + // バスは、いずれも beginTransaction 側が検出して復旧または中断する。 + // NACK の場合も beginTransaction 自体は成功を返し (内部で STOP を出して + // エラーをラッチする)、そのエラーは endTransaction が報告する。 + // したがって両方の成功をもって「ACK が返った」と判定する。 + hit = m5gfx::i2c::beginTransaction(probe_i2c_port, addr, 100000, false).has_value() + && m5gfx::i2c::endTransaction(probe_i2c_port).has_value(); + m5gfx::i2c::release(probe_i2c_port); + } + for (auto& backup : pin_backup) { + backup.restore(); + } + return hit; +#endif + } + + board_t M5Unified::_check_boardtype(board_t board) + { +#if defined (M5UNIFIED_PC_BUILD) +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + if (board == board_t::board_unknown) + { + switch (m5gfx::get_pkg_ver()) + { + case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ6: + board = board_t::board_M5TimerCam; + break; + + case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4: + { + m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_2, GPIO_NUM_13, GPIO_NUM_19, GPIO_NUM_22, GPIO_NUM_27, GPIO_NUM_33, GPIO_NUM_34 }; + m5gfx::pinMode(GPIO_NUM_34, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input_pullup); + board = board_t::board_M5StampPico; + if (m5gfx::gpio_in(GPIO_NUM_2)) // Branches other than StampPico ( StampPico G2 is always LOW ) + { + board = board_t::board_M5AtomU; + if (m5gfx::gpio_in(GPIO_NUM_34)) { // Branches other than AtomU ( AtomU G34 is always LOW ) + board = board_t::board_M5AtomMatrix; +#if SOC_TOUCH_SENSOR_SUPPORTED +/* G27(RGBLED)に対してタッチセンサを用い、容量の差に基づいて Matrix の識別を行う。 + G27に対してタッチセンサを使用すると、得られる値は Lite/ECHOの方が大きく、Matrixの方が小さい。 + なおタッチセンサの値には個体差があるため、判定の基準として絶対値ではなく G13(NC)のタッチセンサ値を比較に用いる。 +*/ + uint32_t results[2] = { 0, 0 }; + static constexpr int s_channel_id[] = { + 4, //Touch pad channel 4 is GPIO13(ESP32) + 7, //Touch pad channel 7 is GPIO27(ESP32) + }; + bool touch_ok = _read_touch_pad(results, s_channel_id, 2); + + int diff = (results[1] * 3 - results[0]); + // M5_LOGV("G13 = %d / G27 = %d / diff = %d", results[0], results[1], diff); + // true==(Lite/ECHO) / false==AtomMatrix (on read failure, keep the AtomMatrix default) + if (touch_ok && diff >= 0) +#else +/* + タッチセンサAPIが使えない場合の処理 (ESP-IDFのバージョンに依る) + GPIOの立上り速度の差を用いて LiteとMatrix の識別を行う。 + (Matrixの方がinput_pullupでHIGHになるまでの時間が長いため、この性質を利用して判定する) +*/ + portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + uint32_t g27 = 0; + // 8回読み取って立上り速度の差を見る + for (int i = 0; i < 8; ++i) + { + lgfx::pinMode(GPIO_NUM_27, lgfx::pin_mode_t::input_pulldown); + delay(1); + taskENTER_CRITICAL(&mux); + lgfx::pinMode(GPIO_NUM_27, lgfx::pin_mode_t::input_pullup); + g27 += lgfx::gpio_in(GPIO_NUM_27); + taskEXIT_CRITICAL(&mux); + } + + // Branches other than AtomMatrix ( AtomMatrix G27 is delayed from becoming HIGH ) + if (g27 > 4) +#endif + { + auto result = m5gfx::gpio::command( + (const uint8_t[]) { + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_22, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_19, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_33, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_33, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_19, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_22, + m5gfx::gpio::command_read , GPIO_NUM_33, + m5gfx::gpio::command_read , GPIO_NUM_19, + m5gfx::gpio::command_read , GPIO_NUM_22, + m5gfx::gpio::command_mode_input , GPIO_NUM_33, + m5gfx::gpio::command_mode_input , GPIO_NUM_19, + m5gfx::gpio::command_mode_input , GPIO_NUM_22, + m5gfx::gpio::command_delay , 1, + m5gfx::gpio::command_read , GPIO_NUM_33, + m5gfx::gpio::command_read , GPIO_NUM_19, + m5gfx::gpio::command_read , GPIO_NUM_22, + m5gfx::gpio::command_end + } + ); + // G19 G22 G33 = ECHOのI2Sスピーカ用ピン。プルアップを無効化するとすぐにLOWになるため、この性質を利用して判定する。 + // なお当該ピンに何かを外付けしている場合は判定に失敗する可能性がある。 + board = board_t::board_M5AtomLite; + if ((result) == 0b111000) + { // Branches for AtomECHO + board = board_t::board_M5AtomVoice; + } + } + } + } + for (auto &backup : pin_backup) { + backup.restore(); + } + } + break; + + case 6: // EFUSE_RD_CHIP_VER_PKG_ESP32PICOV3_02: // ATOM PSRAM + board = board_t::board_M5AtomPsram; + break; + + default: + +#if defined ( ARDUINO_M5STACK_CORE_ESP32 ) || defined ( ARDUINO_M5STACK_FIRE ) || defined ( ARDUINO_M5Stack_Core_ESP32 ) + + board = board_t::board_M5Stack; + +#elif defined ( ARDUINO_M5STACK_CORE2 ) || defined ( ARDUINO_M5STACK_Core2 ) + + board = board_t::board_M5StackCore2; + +#elif defined ( ARDUINO_M5STICK_C ) || defined ( ARDUINO_M5Stick_C ) + + board = board_t::board_M5StickC; + +#elif defined ( ARDUINO_M5STICK_C_PLUS ) || defined ( ARDUINO_M5Stick_C_Plus ) + + board = board_t::board_M5StickCPlus; + +#elif defined ( ARDUINO_M5STACK_COREINK ) || defined ( ARDUINO_M5Stack_CoreInk ) + + board = board_t::board_M5StackCoreInk; + +#elif defined ( ARDUINO_M5STACK_PAPER ) || defined ( ARDUINO_M5STACK_Paper ) + + board = board_t::board_M5Paper; + +#elif defined ( ARDUINO_M5STACK_TOUGH ) + + board = board_t::board_M5Tough; + +#elif defined ( ARDUINO_M5STACK_ATOM ) || defined ( ARDUINO_M5Stack_ATOM ) + + board = board_t::board_M5AtomLite; + +#elif defined ( ARDUINO_M5STACK_TIMER_CAM ) || defined ( ARDUINO_M5Stack_Timer_CAM ) + + board = board_t::board_M5TimerCam; + +#endif + break; + } + } + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + + switch (m5gfx::get_pkg_ver()) + { + default: + case 0: // EFUSE_PKG_VERSION_ESP32S3: // QFN56 + if (board == board_t::board_unknown) + { + #if defined (BOARD_ID) && BOARD_ID == 147 + board = board_t::board_M5DualKey; + // !!!NOTE: Set to input mode to prevent the device from failing to shut down. + m5gfx::pinMode(GPIO_NUM_7, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_8, m5gfx::pin_mode_t::input); + break; + #endif + /// StampS3 or AtomS3Lite,S3U ? + /// After setting GPIO38 to INPUT PULL-UP, change to INPUT and read it. + /// In the case of STAMPS3: Returns 0. Charge is sucked by SGM2578. + /// In the case of ATOMS3Lite/S3U : Returns 1. Charge remains. ( Since it is not connected to anywhere. ) + /// + /// AtomS3Lite or AtomS3U ? + /// After setting GPIO4 to INPUT PULL-UP, read it. + /// In the case of ATOMS3Lite : Returns 0. Charge is sucked by InfraRed. + /// In the case of ATOMS3U : Returns 1. Charge remains. ( Since it is not connected to anywhere. ) + + m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_4, GPIO_NUM_8, GPIO_NUM_10, GPIO_NUM_12, GPIO_NUM_38 }; + auto result = m5gfx::gpio::command( + (const uint8_t[]) { + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_4, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_12, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_38, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_8, + m5gfx::gpio::command_mode_input_pulldown, GPIO_NUM_10, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_4, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_12, + m5gfx::gpio::command_mode_input_pullup , GPIO_NUM_38, + m5gfx::gpio::command_read , GPIO_NUM_8, + m5gfx::gpio::command_read , GPIO_NUM_10, + m5gfx::gpio::command_read , GPIO_NUM_4, + m5gfx::gpio::command_read , GPIO_NUM_12, + m5gfx::gpio::command_read , GPIO_NUM_38, + m5gfx::gpio::command_mode_input , GPIO_NUM_38, + m5gfx::gpio::command_delay , 1, + m5gfx::gpio::command_read , GPIO_NUM_38, + m5gfx::gpio::command_end + } + ); + /// result には、command_read で得たGPIOの状態が1bitずつ4回分入っている。 + board = ((const board_t[]) + { // ↓StampS3 pattern↓ + board_t::board_unknown, board_t::board_unknown, board_t::board_M5StampS3, board_t::board_unknown, // ← unknown + board_t::board_M5AtomS3Lite,board_t::board_M5AtomS3Lite,board_t::board_unknown , board_t::board_M5AtomS3Lite, // ← AtomS3Lite pattern + board_t::board_M5AtomS3U, board_t::board_M5AtomS3U, board_t::board_M5StampS3, board_t::board_M5AtomS3U, // ← AtomS3U pattern + board_t::board_unknown, board_t::board_unknown, board_t::board_M5StampS3, board_t::board_unknown, // ← unknown + })[result&15]; + if ((result & 3) == 2) { // StampS3 pattern + if ((result >> 3) == 0b110) { + board = board_t::board_M5Capsule; + // 自動検出の際。PortAに余分な波形が出ているので、一度 I2C STOPコンディションを出しておく。 + // ※ これをしないと正しく動作しないデバイスが存在した。UnitHEART MAX30100 + m5gfx::gpio::command( + (const uint8_t[]) { + m5gfx::gpio::command_mode_output, GPIO_NUM_15, + m5gfx::gpio::command_write_low , GPIO_NUM_15, + m5gfx::gpio::command_mode_output, GPIO_NUM_13, + m5gfx::gpio::command_write_low , GPIO_NUM_13, + m5gfx::gpio::command_write_high , GPIO_NUM_15, + m5gfx::gpio::command_write_high , GPIO_NUM_13, + m5gfx::gpio::command_end + } + ); + } + } + for (auto &backup : pin_backup) { + backup.restore(); + } + } + + if (board == board_t::board_unknown) { + /// PowerHub ? + if (_probe_i2c_addr(45, 48, 0x50)) { + board = board_t::board_M5PowerHub; + } + } + break; + + case 1: // EFUSE_PKG_VERSION_ESP32S3PICO: // LGA56 + if (board == board_t::board_unknown) { + /// 内部 I2C バス (SDA45/SCL0) に載るデバイスで先に決める。他ピンの probe を + /// 間に挟まないので、ここで確定する機種は 48/47 に一切触れずに済む + /// (AtomVoiceS3R では GPIO48 が I2S の DOUT)。 + /// + /// AtomS3RExt / AtomS3RCam have a BMI270 on the internal I2C bus. + /// この 2 機種は基板が共通で、カメラ部がユーザーの扱えるブレッドボードに + /// なっているため、内部バスにユーザーのデバイスが載りうる。オンボードで + /// 必ず存在する BMI270 を先に見て、後から載ったアドレスに identity を + /// 奪われないようにする。 + if (_probe_i2c_addr(45, 0, 0x68) + || _probe_i2c_addr(45, 0, 0x69)) { + board = board_t::board_M5AtomS3RExt; + } + /// AtomVoiceS3R ? + else if (_probe_i2c_addr(45, 0, 0x18)) { + board = board_t::board_M5AtomVoiceS3R; + } + /// Stamp-S3Bat ? (内部バスに何も居なかったときだけ別のピンを触る) + else if (_probe_i2c_addr(48, 47, 0x6E)) { + board = board_t::board_M5StampS3Bat; + } + /// StampS3Mini has no other onboard device that can identify it. + else { + board = board_t::board_M5StampS3Mini; + } + } + + if (board == board_t::board_M5AtomS3RExt) + { /// AtomS3RCam or AtomS3RExt ? + // Cam = GC0308 = I2C 7bit addr = 0x21 + // CamM12 = OV3660 = I2C 7bit addr = 0x3C + m5gfx::gpio_lo(GPIO_NUM_18); + m5gfx::pinMode(GPIO_NUM_18, m5gfx::pin_mode_t::output); + m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_9, GPIO_NUM_12, GPIO_NUM_21 }; + { // G9=SCL, G12=SDA, G21=XCLK + m5gfx::gpio::command( + (const uint8_t[]) { + m5gfx::gpio::command_write_low, GPIO_NUM_9, + m5gfx::gpio::command_mode_output, GPIO_NUM_9, // SCL + m5gfx::gpio::command_write_low, GPIO_NUM_12, + m5gfx::gpio::command_mode_output, GPIO_NUM_12, // SDA + m5gfx::gpio::command_mode_output, GPIO_NUM_21, // XCL + m5gfx::gpio::command_write_high, GPIO_NUM_9, + m5gfx::gpio::command_write_high, GPIO_NUM_21, + m5gfx::gpio::command_write_high, GPIO_NUM_12, + }); + auto lo_reg = m5gfx::get_gpio_lo_reg(GPIO_NUM_21); + auto hi_reg = m5gfx::get_gpio_hi_reg(GPIO_NUM_21); + + // prepare camera module (need XCLK signal) + for (int xclk = 32768 * 54; xclk != 0; --xclk) + { + *lo_reg = 1 << GPIO_NUM_21; + *hi_reg = 1 << GPIO_NUM_21; + } + uint32_t result = 0; + for (uint8_t i2caddr: (const uint8_t[]){ 0x3C << 1, 0x21 << 1 }) { + for (int xclk = 32768 * 2; xclk != 0; --xclk) { + *lo_reg = 1 << GPIO_NUM_21; + *hi_reg = 1 << GPIO_NUM_21; + } + bool nack = true; + // The camera module is identified using I2C communication via GPIO self-operation. + *lo_reg = 1 << GPIO_NUM_12; // SDA LOW = START + for (int cycle = 0; cycle < 20; ++cycle) { + for (int j = 0; j < 2; ++j) { + for (int xclk = 8; xclk != 0; --xclk) { + *lo_reg = 1 << GPIO_NUM_21; + *hi_reg = 1 << GPIO_NUM_21; + } + *((cycle & 1) ? hi_reg : lo_reg) = 1 << GPIO_NUM_9; // SCL + } + if (cycle & 1) { + if (cycle == 17) { + nack = m5gfx::gpio_in(GPIO_NUM_12); + } + } else { + *((i2caddr & 0x80) ? hi_reg : lo_reg) = 1 << GPIO_NUM_12; // SDA + i2caddr <<= 1; + if (cycle >= 16) { + m5gfx::pinMode(GPIO_NUM_12, (cycle == 16) ? m5gfx::pin_mode_t::input : m5gfx::pin_mode_t::output); + } + } + } + *hi_reg = 1 << GPIO_NUM_12; // SDA HIGH = STOP + result = result << 1 | nack; + } + // printf("CAM TEST RESULT: %08x \r\n", (int)result); + if (result == 1 || result == 2) { + // result == 1 : OV3660 + // result == 2 : GC0308 + board = board_t::board_M5AtomS3RCam; + } + } + for (auto &backup : pin_backup) { + backup.restore(); + } + } + } + + +#elif defined (CONFIG_IDF_TARGET_ESP32C3) + if (board == board_t::board_unknown) + { // StampC3 or StampC3U ? + uint32_t tmp = *((volatile uint32_t *)(IO_MUX_GPIO20_REG)); + m5gfx::pinMode(GPIO_NUM_20, m5gfx::pin_mode_t::input_pulldown); + // StampC3 has a strong external pull-up on GPIO20, which is HIGH even when input_pulldown is set. + // Therefore, if it is LOW, it is not StampC3 and can be assumed to be StampC3U. + // However, even if it goes HIGH, something may be connected to GPIO20 by StampC3U, so it is treated as unknown. + // The StampC3U determination uses the fallback_board setting. + if (m5gfx::gpio_in(GPIO_NUM_20) == false) + { + board = board_t::board_M5StampC3U; + } + *((volatile uint32_t *)(IO_MUX_GPIO20_REG)) = tmp; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + if (board == board_t::board_unknown) + { + if (m5gfx::get_pkg_ver() == 1) + { // QFN32 : NanoC6 = C6FH4 (FLASH_CAP=1) / StampC6 = C6FH8 (FLASH_CAP=2) + if (REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_4_REG, EFUSE_FLASH_CAP) == 2) + { + board = board_t::board_M5StampC6; + } + else + { // NanoC6 + board = board_t::board_M5NanoC6; + } + } + // QFN40 (PKG_VERSION=0) : NessoN1 / UnitC6L carry displays and are + // identified by M5GFX; an undetected QFN40 board stays unknown. + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + if (board == board_t::board_unknown) + { +#if defined (BOARD_ID) && BOARD_ID == 153 + board = board_t::board_M5StampC5; +#else + // StampC5 = ESP32-C5HF4 (in-package 4MB flash, no PSRAM) : FLASH_CAP=1, PSRAM_CAP=0 + // ToughC5 = ESP32-C5HR8 (8MB PSRAM) carries a display and is identified by M5GFX. + std::uint32_t sys2 = REG_READ(EFUSE_RD_MAC_SYS2_REG); + if (((sys2 >> EFUSE_FLASH_CAP_S) & EFUSE_FLASH_CAP_V) == 1 + && ((sys2 >> EFUSE_PSRAM_CAP_S) & EFUSE_PSRAM_CAP_V) == 0) + { + board = board_t::board_M5StampC5; + } +#endif + } + +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + if (board == board_t::board_unknown) + { // NanoH2 + board = board_t::board_M5NanoH2; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32P4) + if (board == board_t::board_unknown) + { +#if defined (BOARD_ID) && BOARD_ID == 31 + board = board_t::board_M5CoreP4X; +#else + m5gfx::pinMode(GPIO_NUM_32, m5gfx::pin_mode_t::input_pulldown); + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input_pulldown); + if (m5gfx::gpio_in(GPIO_NUM_32)) // M5Tab5 and M5Tab5X G32 always High + { + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + board = chip_info.revision >= 300 + ? board_t::board_M5Tab5X + : board_t::board_M5Tab5; + } + else if(m5gfx::gpio_in(GPIO_NUM_0)) // M5UnitPoEP4 G0 always High + board = board_t::board_M5UnitPoEP4; + else + { + esp_chip_info_t chip_info; + esp_chip_info(&chip_info); + board = chip_info.revision >= 300 + ? board_t::board_M5StampP4X + : board_t::board_M5StampP4; + } +#endif + } + +#endif + + return board; + } + + void M5Unified::_setup_i2c(board_t board) + { +#if defined (M5UNIFIED_PC_BUILD) + (void)board; +#else + + gpio_num_t in_scl = (gpio_num_t)getPin(pin_name_t::in_i2c_scl); + gpio_num_t in_sda = (gpio_num_t)getPin(pin_name_t::in_i2c_sda); + gpio_num_t ex_scl = (gpio_num_t)getPin(pin_name_t::ex_i2c_scl); + gpio_num_t ex_sda = (gpio_num_t)getPin(pin_name_t::ex_i2c_sda); + + i2c_port_t ex_port = I2C_NUM_0; +#if SOC_I2C_NUM == 1 || defined (CONFIG_IDF_TARGET_ESP32C6) || defined (CONFIG_IDF_TARGET_ESP32C5) + i2c_port_t in_port = I2C_NUM_0; +// M5GFX が LP_I2C 対応をコンパイルする条件と同一に保つこと +// (条件を満たさない SDK では LP ポートを開けないため HP のまま運用する) +#if defined (CONFIG_IDF_TARGET_ESP32C5) && defined ( SOC_LP_I2C_NUM ) && ( SOC_LP_I2C_NUM > 0 ) \ + && __has_include ( ) && defined ( ESP_IDF_VERSION_VAL ) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + if (board == board_t::board_M5ToughC5) + { /// 内部バス (G2/G3) は LP_I2C の固定パッドと一致するため LP ポートへ割り当てる。 + /// PortA も同じバスの物理分配なので Ex_I2C は同ポートを共有し (初代 BASIC と + /// 同じ形)、HP の I2C0 は丸ごと空く。 + in_port = LP_I2C_NUM_0; + ex_port = LP_I2C_NUM_0; + } +#endif +#else + i2c_port_t in_port = I2C_NUM_1; + if (in_scl == ex_scl && in_sda == ex_sda) { + in_port = ex_port; + } +#endif + if ((uint_fast8_t)in_scl < GPIO_NUM_MAX) + { + In_I2C.begin(in_port, in_sda, in_scl); + } + else + { + In_I2C.setPort(I2C_NUM_MAX, in_sda, in_scl); + } + + if ((uint_fast8_t)ex_scl < GPIO_NUM_MAX) + { + if ((in_port != ex_port) || (in_sda == ex_sda && in_scl == ex_scl) || ((uint_fast8_t)in_scl >= GPIO_NUM_MAX)) { + Ex_I2C.setPort(ex_port, ex_sda, ex_scl); + } + } + + switch (board) { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5CoreP4X: + { + auto ioexp = new M5IOE1_Class(0x4F); + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + for (int i = 0; i < 2; ++i) + { + auto ioexp = new PI4IOE5V6408_Class(0x43 + i); + ioexp->begin(); + _io_expander[i].reset(ioexp); + } + break; +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_M5UnitC6L: + { + auto ioexp = new PI4IOE5V6408_Class(0x43); + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; + case board_t::board_ArduinoNessoN1: + for (int i = 0; i < 2; ++i) + { + auto ioexp = new PI4IOE5V6408_Class(0x43 + i); + ioexp->begin(); + _io_expander[i].reset(ioexp); + } + break; +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StampPLC: + { + auto ioexp = new PI4IOE5V6408_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; + case board_t::board_M5ChainCaptain: + case board_t::board_M5PaperMono: + case board_t::board_M5StopWatch: + { + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + case board_t::board_M5ToughC5: + { /// LCD 電源/リセット/バックライトのほか TF 電源 (PYG6) と + /// TF カード検出 (PYG14) がこの IOE にぶら下がる + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + case board_t::board_M5CoreMatrix: + { /// Controls the LED matrix / TF / Grove / buzzer power rails, + /// the charge current selector and the buzzer PWM. + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; +#endif + default: + break; + } +#endif + } + void M5Unified::_setup_led(board_t board) + { +#if !defined (M5UNIFIED_PC_BUILD) + int led_count = 1; + int byte_per_led = 3; + switch (board) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5PowerHub: + Led.setLedInstance(std::make_shared()); + return; + case board_t::board_M5StampS3Bat: + { + auto busled = std::make_shared(); + auto buscfg = busled->getConfig(); + buscfg.pin_data = 0; + buscfg.led_count = 1; + busled->setConfig(buscfg); + Led.setLedInstance(busled); + return; + } + case board_t::board_M5PaperMono: + Led.setLedInstance(std::make_shared()); + return; + + case board_t::board_M5PaperColor: + led_count = 2; + break; +#else + case board_t::board_M5AtomMatrix: + led_count = 25; + break; +#endif + default: + break; + } + + auto pin_rgb_led = M5.getPin(m5::pin_name_t::rgb_led); //Line: 181 + if (pin_rgb_led >= 0) + { + auto busled = std::make_shared(); + auto buscfg = busled->getConfig(); + buscfg.pin_data = pin_rgb_led; + busled->setConfig(buscfg); + auto led_strip = std::make_shared(); + auto ledcfg = led_strip->getConfig(); + ledcfg.led_count = led_count; + ledcfg.byte_per_led = byte_per_led; + led_strip->setBus(busled); + led_strip->setConfig(ledcfg); + Led.setLedInstance(led_strip); + } +#endif + } + + void M5Unified::_begin(const config_t& cfg) + { + /// setup power management ic + Power.begin(); + Power.setExtOutput(cfg.output_power); + if (cfg.led_brightness) + { + M5.Power.setLed(cfg.led_brightness); + } + auto pmic_type = Power.getType(); + if (pmic_type == Power_Class::pmic_t::pmic_axp2101 + || pmic_type == Power_Class::pmic_t::pmic_axp192 + || pmic_type == Power_Class::pmic_t::pmic_m5pm1) + { + _use_pmic_button = cfg.pmic_button; + /// Slightly lengthen the acceptance time of the AXP192 power button multiclick. + BtnPWR.setHoldThresh(BtnPWR.getHoldThresh() * 1.2); + } + + if (cfg.clear_display) + { + Display.clear(); + } + +#if defined (M5UNIFIED_PC_BUILD) +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + switch (_board) + { + case board_t::board_M5Stack: + // Countermeasure to the problem that GPIO15 affects WiFi sensitivity when M5GO bottom is connected. + m5gfx::pinMode(GPIO_NUM_15, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(GPIO_NUM_15); + + // M5Stack Core v2.6 has a problem that SPI communication speed cannot be increased. + // This problem can be solved by increasing the GPIO drive current. + // ※ This allows SunDisk SD cards to communicate at 20 MHz. (without M5GO bottom.) + // This allows communication with ModuleDisplay at 80 MHz. + for (auto gpio: (const gpio_num_t[]){ GPIO_NUM_18, GPIO_NUM_19, GPIO_NUM_23 }) + { + uint32_t tmp = *(volatile uint32_t*)(GPIO_PIN_MUX_REG[gpio]); + *(volatile uint32_t*)(GPIO_PIN_MUX_REG[gpio]) = tmp | FUN_DRV_M; // gpio drive current set to 40mA. + gpio_pulldown_dis(gpio); // disable pulldown. + gpio_pullup_en(gpio); // enable pullup. + } + break; + + case board_t::board_M5StickC: + case board_t::board_M5StickCPlus: + case board_t::board_M5AtomLite: + case board_t::board_M5AtomMatrix: + case board_t::board_M5AtomVoice: + case board_t::board_M5AtomU: + // Countermeasure to the problem that CH552 applies 4v to GPIO0, thus reducing WiFi sensitivity. + // Setting output_high adds a bias of 3.3v and suppresses overvoltage. + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::output); + m5gfx::gpio_hi(GPIO_NUM_0); + break; + + default: + break; + } +#endif + + switch (_board) /// setup Hardware Buttons + { +#if defined (M5UNIFIED_PC_BUILD) +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + case board_t::board_M5StackCoreInk: + m5gfx::pinMode(CoreInk_BUTTON_EXT_PIN, m5gfx::pin_mode_t::input); // TopButton + m5gfx::pinMode(CoreInk_BUTTON_PWR_PIN, m5gfx::pin_mode_t::input); // PowerButton + NON_BREAK; /// don't break; + + case board_t::board_M5Paper: + case board_t::board_M5Station: + case board_t::board_M5Stack: + m5gfx::pinMode(GPIO_NUM_38, m5gfx::pin_mode_t::input); + NON_BREAK; /// don't break; + + case board_t::board_M5StickC: + case board_t::board_M5StickCPlus: + m5gfx::pinMode(GPIO_NUM_37, m5gfx::pin_mode_t::input); + NON_BREAK; /// don't break; + + case board_t::board_M5AtomLite: + case board_t::board_M5AtomMatrix: + case board_t::board_M5AtomVoice: + case board_t::board_M5AtomPsram: + case board_t::board_M5AtomU: + case board_t::board_M5StampPico: + m5gfx::pinMode(GPIO_NUM_39, m5gfx::pin_mode_t::input); + NON_BREAK; /// don't break; + + case board_t::board_M5StackCore2: + case board_t::board_M5Tough: + /// for GPIO 36,39 Chattering prevention. + adc_power_acquire(); + break; + + case board_t::board_M5StickCPlus2: + m5gfx::pinMode(GPIO_NUM_35, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_37, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_39, m5gfx::pin_mode_t::input); + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32C3) + + case board_t::board_M5StampC3: + m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input_pullup); + break; + + case board_t::board_M5StampC3U: + m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + + case board_t::board_M5NanoC6: + m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + + case board_t::board_M5NanoH2: + m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5AtomS3: + case board_t::board_M5AtomS3Lite: + case board_t::board_M5AtomS3U: + case board_t::board_M5AtomS3R: + case board_t::board_M5AtomVoiceS3R: + m5gfx::pinMode(GPIO_NUM_41, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5AirQ: + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_8, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5VAMeter: + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5StampS3: + case board_t::board_M5Cardputer: + case board_t::board_M5CardputerADV: + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5Capsule: + case board_t::board_M5Dial: + case board_t::board_M5DinMeter: + m5gfx::pinMode(GPIO_NUM_42, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5StampPLC: + { + auto& ioexp = getIOExpander(0); + // lcd backlight + ioexp.setDirection(7, true); + ioexp.setPullMode(7, IOExpander_Base::pull_down); + ioexp.setHighImpedance(7, false); + + for (int i = 0; i < 3; ++i) { + // button a~c + ioexp.setDirection(i, false); + ioexp.setPullMode(i, IOExpander_Base::pull_up); + ioexp.setHighImpedance(i, false); + } + delay(100); + } + break; + + case board_t::board_M5PowerHub: + m5gfx::pinMode(GPIO_NUM_11, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5DualKey: + m5gfx::pinMode(GPIO_NUM_0, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_17, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5StickS3: + m5gfx::pinMode(GPIO_NUM_11, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_12, m5gfx::pin_mode_t::input); + // PA Control Pin Init + this->In_I2C.bitOff(m5pm1_i2c_addr, 0x16, 1 << 3, 100000); // Set pin gpio3 as gpio function + this->In_I2C.bitOn(m5pm1_i2c_addr, 0x10, 1 << 3, 100000); // Set pin gpio3 mode: output + this->In_I2C.bitOff(m5pm1_i2c_addr, 0x13, 1 << 3, 100000); // Set gpio3 push-pull mode + this->In_I2C.bitOff(m5pm1_i2c_addr, 0x11, 1 << 3, 100000); // Set gpio3 output low + break; + + case board_t::board_M5PaperDIY: + m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5PaperColor: + m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_10, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5ChainCaptain: + m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_5, m5gfx::pin_mode_t::input); + // Audio PA -- G21 + m5gfx::pinMode(GPIO_NUM_21, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(GPIO_NUM_21); + // Audio Power -- M5IO1_G5 + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); + ioe1.setDirection(M5IOE1_Class::gpio5, true); + ioe1.digitalWrite(M5IOE1_Class::gpio5, false); + } + break; + + case board_t::board_M5PaperMono: + m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5StopWatch: + m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); + // M5IOE1 PIN4: display power + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio4, false); + ioe1.setDirection(M5IOE1_Class::gpio4, true); + ioe1.digitalWrite(M5IOE1_Class::gpio4, true); + } + // M5IOE1_G3 codec power / M5IOE1_G10 PA + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); + ioe1.setHighImpedance(M5IOE1_Class::gpio10, false); + ioe1.setDirection(M5IOE1_Class::gpio3, true); + ioe1.setDirection(M5IOE1_Class::gpio10, true); + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); + } + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5CoreP4X: + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); + ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); + ioe1.setDirection(M5IOE1_Class::gpio1, true); + ioe1.setDirection(M5IOE1_Class::gpio3, true); + ioe1.digitalWrite(M5IOE1_Class::gpio1, false); + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); + } + break; + + case board_t::board_M5UnitPoEP4: + m5gfx::pinMode(GPIO_NUM_45, m5gfx::pin_mode_t::input); + break; + + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: +#if defined (ARDUINO) + // 汎用 esp32p4 ボード選択時、core 既定の SDIO ピンでは C6 (WiFi/BLE) に届かない。 + // WiFi.begin より前 (ESP-Hosted 初期化前) に Tab5 の配線へ差し替える。 + if (hostedSetPins && !(hostedIsInitialized && hostedIsInitialized())) + { + hostedSetPins(GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_11, GPIO_NUM_10, GPIO_NUM_9, GPIO_NUM_8, GPIO_NUM_15); + } +#endif + break; + +#endif + + default: + break; + } + +#if defined ( ARDUINO ) + #ifdef HardwareSerial_h + + if (cfg.serial_baudrate) + { // Wait with delay to prevent startup log output from disappearing. + delay(16); + Serial.begin(cfg.serial_baudrate); + } + + #endif +#endif + } + + void M5Unified::_begin_audio(config_t& cfg) + { + bool(*mic_enable_cb)(void*, bool) = nullptr; + bool(*mic_post_start_cb)(void*) = nullptr; + auto mic_cfg = Mic.config(); + + bool(*spk_enable_cb)(void*, bool) = nullptr; + auto spk_cfg = Speaker.config(); + + if (cfg.internal_mic) + { + mic_cfg.over_sampling = 1; + mic_cfg.i2s_port = I2S_NUM_0; + switch (_board) + { +#if defined (M5UNIFIED_PC_BUILD) +#elif defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5CoreP4X: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_2; + mic_cfg.pin_bck = GPIO_NUM_6; + mic_cfg.pin_ws = GPIO_NUM_4; + mic_cfg.pin_data_in = GPIO_NUM_5; + mic_cfg.magnification = 2; + mic_cfg.sample_rate = 24000; + mic_cfg.input_channel = input_channel_t::input_stereo; + mic_cfg.i2s_port = I2S_NUM_0; + mic_enable_cb = _microphone_enabled_cb_corep4x; + } + break; + + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_30; + mic_cfg.pin_bck = GPIO_NUM_27; + mic_cfg.pin_ws = GPIO_NUM_29; + mic_cfg.pin_data_in = GPIO_NUM_28; + // mic_cfg.pin_data_out = GPIO_NUM_26; + mic_cfg.magnification = 2; + mic_cfg.input_channel = input_channel_t::input_stereo; + mic_cfg.i2s_port = I2S_NUM_0; + mic_enable_cb = _microphone_enabled_cb_tab5; + } + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackChan: + if (cfg.internal_mic) + { + mic_cfg.magnification = 2; + mic_cfg.over_sampling = 1; + mic_cfg.pin_mck = GPIO_NUM_0; + mic_cfg.pin_bck = GPIO_NUM_34; + mic_cfg.pin_ws = GPIO_NUM_33; + mic_cfg.pin_data_in = GPIO_NUM_14; + mic_cfg.i2s_port = I2S_NUM_1; + mic_cfg.input_channel = input_channel_t::input_stereo; + mic_enable_cb = _microphone_enabled_cb_cores3; + } + break; + + case board_t::board_M5StickS3: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_18; + mic_cfg.pin_bck = GPIO_NUM_17; + mic_cfg.pin_ws = GPIO_NUM_15; + mic_cfg.pin_data_in = GPIO_NUM_16; + mic_cfg.i2s_port = I2S_NUM_1; + mic_enable_cb = _microphone_enabled_cb_sticks3; + } + break; + + case board_t::board_M5PaperColor: + if (cfg.internal_mic) + { + mic_cfg.over_sampling = 1; + mic_cfg.pin_mck = GPIO_NUM_42; + mic_cfg.pin_bck = GPIO_NUM_40; + mic_cfg.pin_ws = GPIO_NUM_41; + mic_cfg.pin_data_in = GPIO_NUM_39; // data in from mic output + mic_cfg.i2s_port = I2S_NUM_1; + mic_cfg.input_channel = input_channel_t::input_only_left; + mic_enable_cb = _microphone_enabled_cb_papercolor; + } + break; + + case board_t::board_M5PaperMono: + if (cfg.internal_mic) + { /// builtin PDM mic + mic_cfg.pin_ws = GPIO_NUM_45; + mic_cfg.pin_data_in = GPIO_NUM_46; + mic_enable_cb = _microphone_enabled_cb_papermono; + } + break; + + case board_t::board_M5StopWatch: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_18; + mic_cfg.pin_bck = GPIO_NUM_17; + mic_cfg.pin_ws = GPIO_NUM_15; + mic_cfg.pin_data_in = GPIO_NUM_16; + mic_cfg.i2s_port = I2S_NUM_1; + mic_enable_cb = _microphone_enabled_cb_stopwatch; + mic_post_start_cb = _microphone_post_start_cb_stopwatch; + } + break; + + case board_t::board_M5ChainCaptain: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_40; + mic_cfg.pin_bck = GPIO_NUM_38; + mic_cfg.pin_ws = GPIO_NUM_41; + mic_cfg.pin_data_in = GPIO_NUM_39; + mic_cfg.i2s_port = I2S_NUM_1; + mic_cfg.sample_rate = 16000; + mic_enable_cb = _microphone_enabled_cb_chain_captain; + } + break; + + case board_t::board_M5AtomS3U: + if (cfg.internal_mic) + { + mic_cfg.pin_data_in = GPIO_NUM_38; + mic_cfg.pin_ws = GPIO_NUM_39; + } + break; + + case board_t::board_M5Cardputer: + if (cfg.internal_mic) + { + mic_cfg.pin_data_in = GPIO_NUM_46; + mic_cfg.pin_ws = GPIO_NUM_43; + } + break; + + case board_t::board_M5CardputerADV: + if (cfg.internal_mic) + { + mic_cfg.pin_data_in = GPIO_NUM_46; + mic_cfg.pin_ws = GPIO_NUM_43; + mic_cfg.pin_bck = GPIO_NUM_41; + mic_enable_cb = _microphone_enabled_cb_cardputer_adv; + } + break; + + case board_t::board_M5Capsule: + if (cfg.internal_mic) + { + mic_cfg.pin_data_in = GPIO_NUM_41; + mic_cfg.pin_ws = GPIO_NUM_40; + } + break; + +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + case board_t::board_M5Stack: + if (cfg.internal_mic) + { + mic_cfg.pin_data_in = GPIO_NUM_34; // M5GO bottom MIC + mic_cfg.i2s_port = I2S_NUM_0; + mic_cfg.use_adc = true; // use ADC analog input + mic_cfg.over_sampling = 4; + } + break; + + case board_t::board_M5StickC: + case board_t::board_M5StickCPlus: + if (cfg.internal_mic) + { /// builtin PDM mic + mic_cfg.pin_data_in = GPIO_NUM_34; + mic_cfg.pin_ws = GPIO_NUM_0; + mic_enable_cb = _microphone_enabled_cb_stickc; + } + break; + + case board_t::board_M5StickCPlus2: + case board_t::board_M5Tough: + case board_t::board_M5StackCore2: + if (cfg.internal_mic) + { /// builtin PDM mic + mic_cfg.pin_data_in = GPIO_NUM_34; + mic_cfg.pin_ws = GPIO_NUM_0; + } + break; + + case board_t::board_M5AtomU: + { /// ATOM U builtin PDM mic + mic_cfg.pin_data_in = GPIO_NUM_19; + mic_cfg.pin_ws = GPIO_NUM_5; + } + break; + + case board_t::board_M5AtomVoice: + { /// ATOM ECHO builtin PDM mic + mic_cfg.pin_data_in = GPIO_NUM_23; + mic_cfg.pin_ws = GPIO_NUM_33; + } + break; +#endif + default: + break; + } + } + + if (cfg.external_spk_detail.enabled && cfg.external_speaker_value == 0) { + cfg.external_speaker.atomic_spk = false==cfg.external_spk_detail.omit_atomic_spk; + cfg.external_speaker.hat_spk = false==cfg.external_spk_detail.omit_spk_hat; + } + + if (cfg.internal_spk || cfg.external_speaker_value) + { + // set default speaker gain. + spk_cfg.magnification = 16; +#if defined M5UNIFIED_I2S_PORT_COUNT + spk_cfg.i2s_port = (i2s_port_t)(M5UNIFIED_I2S_PORT_COUNT - 1); +#else + spk_cfg.i2s_port = (i2s_port_t)(I2S_NUM_MAX - 1); +#endif + switch (_board) + { +#if defined (M5UNIFIED_PC_BUILD) +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_M5UnitC6L: + case board_t::board_ArduinoNessoN1: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_11; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5CoreP4X: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_2; + spk_cfg.pin_bck = GPIO_NUM_6; + spk_cfg.pin_ws = GPIO_NUM_4; + spk_cfg.pin_data_out = GPIO_NUM_3; + spk_cfg.magnification = 4; + spk_cfg.sample_rate = 24000; + spk_cfg.i2s_port = I2S_NUM_0; + spk_enable_cb = _speaker_enabled_cb_corep4x; + } + break; + + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_30; + spk_cfg.pin_bck = GPIO_NUM_27; + spk_cfg.pin_ws = GPIO_NUM_29; +// spk_cfg.pin_data_in = GPIO_NUM_28; + spk_cfg.pin_data_out = GPIO_NUM_26; + spk_cfg.magnification = 4; + spk_cfg.i2s_port = I2S_NUM_0; + spk_enable_cb = _speaker_enabled_cb_tab5; + } + break; + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackChan: + if (cfg.internal_spk) + { + spk_cfg.pin_bck = GPIO_NUM_34; + spk_cfg.pin_ws = GPIO_NUM_33; + spk_cfg.pin_data_out = GPIO_NUM_13; + spk_cfg.magnification = 4; + spk_cfg.i2s_port = I2S_NUM_1; + spk_enable_cb = _speaker_enabled_cb_cores3; + } + break; + + case board_t::board_M5StickS3: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_18; + spk_cfg.pin_bck = GPIO_NUM_17; + spk_cfg.pin_ws = GPIO_NUM_15; + spk_cfg.pin_data_out = GPIO_NUM_14; + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.magnification = 1; + spk_cfg.sample_rate = 22050; + spk_cfg.stereo = true; + spk_cfg.buzzer = false; + spk_cfg.use_dac = false; + spk_cfg.dac_zero_level = 0; + spk_enable_cb = _speaker_enabled_cb_sticks3; + } + break; + + case board_t::board_M5AtomS3: + case board_t::board_M5AtomS3Lite: + case board_t::board_M5AtomS3R: + case board_t::board_M5AtomS3RCam: + case board_t::board_M5AtomS3RExt: + if (cfg.external_speaker.atomic_spk || cfg.external_speaker.atomic_echo) + { // for ATOMIC SPK / ATOMIC ECHO BASE + bool atomdisplay = false; + for (int i = 0; i < getDisplayCount(); ++i) { + if (Displays(i).getBoard() == board_t::board_M5AtomDisplay) { + atomdisplay = true; + break; + } + } + if (!atomdisplay) { + bool flg_atomic_spk = false; + if (cfg.external_speaker.atomic_spk) { + m5gfx::pinMode(GPIO_NUM_6, m5gfx::pin_mode_t::input_pulldown); // MOSI + m5gfx::pinMode(GPIO_NUM_7, m5gfx::pin_mode_t::input_pulldown); // SCLK + if (m5gfx::gpio_in(GPIO_NUM_6) + && m5gfx::gpio_in(GPIO_NUM_7)) + { + flg_atomic_spk = true; + ESP_LOGD("M5Unified", "ATOMIC SPK"); + // atomic_spkのSDカード用ピンを割当 + _get_pin_table[sd_spi_sclk] = GPIO_NUM_7; + _get_pin_table[sd_spi_copi] = GPIO_NUM_6; + _get_pin_table[sd_spi_cipo] = GPIO_NUM_8; + cfg.internal_imu = false; /// avoid conflict with i2c + cfg.internal_rtc = false; /// avoid conflict with i2c + spk_cfg.pin_bck = GPIO_NUM_5; + spk_cfg.pin_ws = GPIO_NUM_39; + spk_cfg.pin_data_out = GPIO_NUM_38; + spk_cfg.magnification = 16; + } + } + if (cfg.external_speaker.atomic_echo && !flg_atomic_spk) { + spk_cfg.pin_bck = GPIO_NUM_8; + spk_cfg.pin_ws = GPIO_NUM_6; + spk_cfg.pin_data_out = GPIO_NUM_5; + spk_cfg.magnification = 1; + spk_enable_cb = _speaker_enabled_cb_atomic_echo; + + mic_cfg.i2s_port = spk_cfg.i2s_port; + mic_cfg.pin_bck = GPIO_NUM_8; + mic_cfg.pin_ws = GPIO_NUM_6; + mic_cfg.pin_data_in = GPIO_NUM_7; + mic_cfg.magnification = 1; + mic_cfg.over_sampling = 1; + mic_cfg.pin_mck = GPIO_NUM_NC; + mic_cfg.stereo = false; + mic_enable_cb = _microphone_enabled_cb_atomic_echo; + } + } + } + break; + + case board_t::board_M5AtomVoiceS3R: + if (cfg.internal_mic) { + cfg.internal_imu = false; + + // spk_cfg.pin_mck = GPIO_NUM_11; + spk_cfg.pin_bck = GPIO_NUM_17; + spk_cfg.pin_ws = GPIO_NUM_3; + spk_cfg.pin_data_out = GPIO_NUM_48; + spk_cfg.magnification = 1; + spk_cfg.i2s_port = I2S_NUM_1; + spk_enable_cb = _speaker_enabled_cb_atom_echos3r; + + mic_cfg.i2s_port = spk_cfg.i2s_port; + mic_cfg.pin_mck = GPIO_NUM_11; + mic_cfg.pin_bck = GPIO_NUM_17; + mic_cfg.pin_ws = GPIO_NUM_3; + mic_cfg.pin_data_in = GPIO_NUM_4; + mic_cfg.magnification = 1; + mic_cfg.over_sampling = 1; + mic_cfg.stereo = true; + mic_enable_cb = _microphone_enabled_cb_atom_echos3r; + } + break; + + case board_t::board_M5Capsule: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_2; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5Dial: + case board_t::board_M5DinMeter: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_3; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5AirQ: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_9; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5VAMeter: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_14; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5PaperS3: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_21; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5PaperMono: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_42; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + + case board_t::board_M5PaperColor: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_42; + spk_cfg.pin_bck = GPIO_NUM_40; + spk_cfg.pin_ws = GPIO_NUM_41; + spk_cfg.pin_data_out = GPIO_NUM_38; // data out to spk + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.magnification = 1; + spk_cfg.sample_rate = 44100; + spk_cfg.stereo = true; + spk_enable_cb = _speaker_enabled_cb_papercolor; + } + break; + + case board_t::board_M5StopWatch: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_18; + spk_cfg.pin_bck = GPIO_NUM_17; + spk_cfg.pin_ws = GPIO_NUM_15; + spk_cfg.pin_data_out = GPIO_NUM_21; + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.magnification = 4; + spk_cfg.sample_rate = 44100; + spk_cfg.stereo = true; + spk_cfg.buzzer = false; + spk_cfg.use_dac = false; + spk_cfg.dac_zero_level = 0; + spk_enable_cb = _speaker_enabled_cb_stopwatch; + } + break; + + case board_t::board_M5ChainCaptain: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_40; + spk_cfg.pin_bck = GPIO_NUM_38; + spk_cfg.pin_ws = GPIO_NUM_41; + spk_cfg.pin_data_out = GPIO_NUM_42; + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.magnification = 1; + spk_cfg.sample_rate = 44100; + spk_cfg.stereo = true; + spk_cfg.buzzer = false; + spk_cfg.use_dac = false; + spk_cfg.dac_zero_level = 0; + spk_enable_cb = _speaker_enabled_cb_chain_captain; + } + break; + + case board_t::board_M5Cardputer: + case board_t::board_M5CardputerADV: + if (cfg.internal_spk) + { + spk_cfg.pin_bck = GPIO_NUM_41; + spk_cfg.pin_ws = GPIO_NUM_43; + spk_cfg.pin_data_out = GPIO_NUM_42; + spk_cfg.magnification = 16; + spk_cfg.i2s_port = I2S_NUM_1; + if (_board == board_t::board_M5CardputerADV) { + spk_enable_cb = _speaker_enabled_cb_cardputer_adv; + } + } + break; + + case board_t::board_M5StampPLC: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_44; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + case board_t::board_M5Stack: + if (cfg.internal_spk) + { + m5gfx::gpio_lo(GPIO_NUM_25); + m5gfx::pinMode(GPIO_NUM_25, m5gfx::pin_mode_t::output); + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.use_dac = true; + spk_cfg.pin_data_out = GPIO_NUM_25; + spk_cfg.magnification = 8; + spk_cfg.sample_rate *= 2; + } + break; + + case board_t::board_M5StackCoreInk: + case board_t::board_M5StickCPlus: + case board_t::board_M5StickCPlus2: + if (cfg.internal_spk) + { + spk_cfg.buzzer = true; + spk_cfg.pin_data_out = GPIO_NUM_2; + spk_cfg.magnification = 48; + } + NON_BREAK; + + case board_t::board_M5StickC: + if (cfg.external_speaker.hat_spk2 && (_board != board_t::board_M5StackCoreInk)) + { /// for HAT SPK2 (for StickC/StickCPlus. CoreInk does not support.) + spk_cfg.pin_data_out = GPIO_NUM_25; + spk_cfg.pin_bck = GPIO_NUM_26; + spk_cfg.pin_ws = GPIO_NUM_0; + spk_cfg.i2s_port = I2S_NUM_1; + spk_cfg.use_dac = false; + spk_cfg.buzzer = false; + spk_cfg.magnification = 16; + } + else if (cfg.external_speaker.hat_spk) + { /// for HAT SPK + gpio_num_t pin_en = _board == board_t::board_M5StackCoreInk ? GPIO_NUM_25 : GPIO_NUM_0; + m5gfx::gpio_lo(pin_en); + m5gfx::pinMode(pin_en, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(GPIO_NUM_26); + m5gfx::pinMode(GPIO_NUM_26, m5gfx::pin_mode_t::output); + spk_cfg.pin_data_out = GPIO_NUM_26; + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.use_dac = true; + spk_cfg.buzzer = false; + spk_cfg.magnification = 32; + spk_enable_cb = _speaker_enabled_cb_hat_spk; + } + break; + + case board_t::board_M5Tough: + // The magnification is set higher than Core2 here because the waterproof case reduces the sound.; + spk_cfg.magnification = 24; + NON_BREAK; + case board_t::board_M5StackCore2: + if (cfg.internal_spk) + { + spk_cfg.pin_bck = GPIO_NUM_12; + spk_cfg.pin_ws = GPIO_NUM_0; + spk_cfg.pin_data_out = GPIO_NUM_2; + spk_enable_cb = _speaker_enabled_cb_core2; + } + break; + + case board_t::board_M5AtomVoice: + if (cfg.internal_spk && (Display.getBoard() != board_t::board_M5AtomDisplay)) + { // for ATOM ECHO + spk_cfg.pin_bck = GPIO_NUM_19; + spk_cfg.pin_ws = GPIO_NUM_33; + spk_cfg.pin_data_out = GPIO_NUM_22; + spk_cfg.magnification = 12; + } + NON_BREAK; + case board_t::board_M5AtomLite: + case board_t::board_M5AtomMatrix: + case board_t::board_M5AtomPsram: + if (cfg.external_speaker.atomic_spk || cfg.external_speaker.atomic_echo) + { // for ATOMIC SPK / ATOMIC ECHO BASE + bool atomdisplay = false; + for (int i = 0; i < getDisplayCount(); ++i) { + if (Displays(i).getBoard() == board_t::board_M5AtomDisplay) { + atomdisplay = true; + break; + } + } + if (!atomdisplay) { + bool flg_atomic_spk = false; + if (cfg.external_speaker.atomic_spk) { + // 19,23 pulldown read check ( all high = ATOMIC_SPK ? ) // MISO is not used for judgment as it changes depending on the state of the SD card. + gpio_num_t pin = (_board == board_t::board_M5AtomPsram) ? GPIO_NUM_5 : GPIO_NUM_23; + m5gfx::pinMode(GPIO_NUM_19, m5gfx::pin_mode_t::input_pulldown); // MOSI + m5gfx::pinMode(pin , m5gfx::pin_mode_t::input_pulldown); // SCLK + if (m5gfx::gpio_in(GPIO_NUM_19) + && m5gfx::gpio_in(pin )) + { + flg_atomic_spk = true; + ESP_LOGD("M5Unified", "ATOMIC SPK"); + // atomic_spkのSDカード用ピンを割当 + _get_pin_table[sd_spi_sclk] = pin; + _get_pin_table[sd_spi_copi] = GPIO_NUM_19; + _get_pin_table[sd_spi_cipo] = GPIO_NUM_33; + cfg.internal_imu = false; /// avoid conflict with i2c + cfg.internal_rtc = false; /// avoid conflict with i2c + spk_cfg.pin_bck = GPIO_NUM_22; + spk_cfg.pin_ws = GPIO_NUM_21; + spk_cfg.pin_data_out = GPIO_NUM_25; + spk_cfg.magnification = 16; + auto mic = Mic.config(); + mic.pin_data_in = -1; // disable mic for ATOMECHO + Mic.config(mic); + } + } + if (cfg.external_speaker.atomic_echo && !flg_atomic_spk) { + spk_cfg.pin_bck = GPIO_NUM_33; + spk_cfg.pin_ws = GPIO_NUM_19; + spk_cfg.pin_data_out = GPIO_NUM_22; + spk_cfg.magnification = 1; + spk_enable_cb = _speaker_enabled_cb_atomic_echo; + + mic_cfg.i2s_port = spk_cfg.i2s_port; + mic_cfg.pin_bck = GPIO_NUM_33; + mic_cfg.pin_ws = GPIO_NUM_19; + mic_cfg.pin_data_in = GPIO_NUM_23; + mic_cfg.magnification = 1; + mic_cfg.over_sampling = 1; + mic_cfg.pin_mck = GPIO_NUM_NC; + mic_cfg.stereo = false; + mic_enable_cb = _microphone_enabled_cb_atomic_echo; + } + } + } + break; +#endif + default: + break; + } + + if (cfg.external_speaker_value) + { +#if defined (M5UNIFIED_PC_BUILD) +#elif defined ( CONFIG_IDF_TARGET_ESP32P4 ) + #define ENABLE_M5MODULE + if (_board == board_t::board_M5Tab5 + || _board == board_t::board_M5Tab5X + || _board == board_t::board_M5CoreP4X) +#elif defined ( CONFIG_IDF_TARGET_ESP32S3 ) + #define ENABLE_M5MODULE + if (_board == board_t::board_M5StackCoreS3 + || _board == board_t::board_M5StackCoreS3SE + || _board == board_t::board_M5StackChan) +#elif defined ( CONFIG_IDF_TARGET_ESP32 ) || !defined ( CONFIG_IDF_TARGET ) + #define ENABLE_M5MODULE + if ( _board == board_t::board_M5Stack + || _board == board_t::board_M5StackCore2 + || _board == board_t::board_M5Tough) +#endif + { +#ifdef ENABLE_M5MODULE + bool use_module_display = cfg.external_speaker.module_display + && (0 <= getDisplayIndex(m5gfx::board_M5ModuleDisplay)); + if (use_module_display || cfg.external_speaker.module_rca) + { + if (use_module_display) { + spk_cfg.sample_rate = 48000; // Module Display audio output is fixed at 48 kHz + } + // ModuleDisplay or Module RCA + spk_cfg.pin_bck = getPin(use_module_display ? pin_name_t::mbus_pin21 : pin_name_t::mbus_pin22); + spk_cfg.pin_data_out = getPin(pin_name_t::mbus_pin23); + spk_cfg.pin_ws = getPin(pin_name_t::mbus_pin24); // LRCK + + spk_cfg.i2s_port = I2S_NUM_1; + spk_cfg.magnification = 16; + spk_cfg.stereo = true; + spk_cfg.buzzer = false; + spk_cfg.use_dac = false; + spk_enable_cb = nullptr; + } + #undef ENABLE_M5MODULE +#endif + } + } + } + if (mic_cfg.pin_data_in >= 0) + { + Mic.setCallback(this, mic_enable_cb); + Mic.setPostStartCallback(this, mic_post_start_cb); + Mic.config(mic_cfg); + } + if (spk_cfg.pin_data_out >= 0) + { + Speaker.setCallback(this, spk_enable_cb); + Speaker.config(spk_cfg); + } + } + + bool M5Unified::_clearWakeupInterrupt(void) + { + bool res = true; + // A touch panel holds its INT asserted until the touch data is read. Every board that + // uses the touch INT as its wakeup pin therefore has to consume the data here. + // ( M5Paper = GPIO36 , M5PaperS3 = GPIO48 , Core2 / Tough = GPIO39 , CoreS3 = via AW9523 ) + if (!_displays.empty() && _displays.front().touch()) + { // Same source as Touch_Class, see Touch.begin() in _begin(). + m5gfx::touch_point_t tp; + _displays.front().getTouchRaw(&tp, 1); + } + +#if defined (CONFIG_IDF_TARGET_ESP32S3) + switch (getBoard()) + { + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackChan: + { // TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN -> I2C_INT -> GPIO21. + // The AW9523 reports input changes only, so releasing the touch INT is not enough: + // its input would stay in the touched state and a later touch would not produce + // any change, leaving the wakeup source dead. + uint8_t buf[2]; + res = In_I2C.readRegister(aw9523_i2c_addr, 0x00, buf, sizeof(buf), 400000); + } + break; + + default: + break; + } +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + switch (getBoard()) + { + case board_t::board_M5ToughC5: + { // TOUCH_INT や RTC_INT は PM1 に集約され、PM1 の IRQ 出力 -> GPIO4 が + // 唯一の wakeup ピンになる。IRQ 出力は IRQ ステータス (0x40-0x42) が + // 全て 0 になるまで Low を保つため、ここでクリアして解放する。 + // WAKE_SRC が残っていると IRQ Status 3 の WAKEUP ビットが再セット + // され続けるので、先に WAKE_SRC を消す。 + res = Power.M5pm1.clearWakeSource(); + res &= Power.M5pm1.clearIRQStatus(); + } + break; + + default: + break; + } +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + switch (getBoard()) + { + case board_t::board_M5CoreMatrix: + { // KEY and IMU wake events are funneled into the PM1, whose IRQ output + // (GPIO2) is the only wakeup pin. The IRQ output stays low until every + // IRQ status bit is cleared, so clear them here to release the pin. + // Clear WAKE_SRC first: while it is set, the WAKEUP bit of IRQ status 3 + // keeps getting re-asserted. + res = Power.M5pm1.clearWakeSource(); + res &= Power.M5pm1.clearIRQStatus(); + } + break; + + default: + break; + } +#endif + return res; + } + + bool M5Unified::_begin_rtc_imu(const config_t& cfg) + { + bool port_a_used = false; + if (cfg.external_rtc || cfg.external_imu) + { + M5.Ex_I2C.begin(); + } + + if (cfg.internal_rtc && In_I2C.isEnabled()) + { + M5.Rtc.begin(&M5.In_I2C, M5.getBoard()); + } + if (!M5.Rtc.isEnabled() && cfg.external_rtc && Ex_I2C.isEnabled()) + { + port_a_used = M5.Rtc.begin(&M5.Ex_I2C); + } + if (M5.Rtc.isEnabled()) + { + M5.Rtc.setSystemTimeFromRtc(); + if (cfg.disable_rtc_irq) { + M5.Rtc.disableIRQ(); + } + } + + if (cfg.internal_imu && In_I2C.isEnabled()) + { + M5.Imu.begin(&M5.In_I2C, M5.getBoard()); + } + if (!M5.Imu.isEnabled() && cfg.external_imu && Ex_I2C.isEnabled()) + { + port_a_used = M5.Imu.begin(&M5.Ex_I2C) || port_a_used; + } + return port_a_used; + } + + void M5Unified::update( void ) + { + auto ms = m5gfx::millis(); + _updateMsec = ms; + + // 1=BtnA / 2=BtnB / 4=BtnC / 8=BtnEXT / 16=BtnPWR + uint_fast8_t use_rawstate_bits = 0; + uint_fast8_t btn_rawstate_bits = 0; + + if (Touch.isEnabled()) + { + Touch.update(ms); + + int tb_y = 0; + int tb_k = 0; + switch (_board) + { + case board_t::board_M5StackCore2: + case board_t::board_M5Tough: + case board_t::board_M5ToughC5: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackChan: + tb_y = 240; + tb_k = 614; // (65536*3/320) + break; + case board_t::board_M5Paper: + case board_t::board_M5PaperS3: + tb_y = 960; + tb_k = 364; // (65536*3/540) + break; + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + tb_y = 1280; + tb_k = 273; // (65536*3/540) + break; + default: + break; + } + + if (tb_k) + { + tb_y -= _touch_button_height; + if (tb_y < 0) { tb_y = 0; } + + use_rawstate_bits = 0b00111; + int i = Touch.getCount(); + while (--i >= 0) + { + auto raw = Touch.getTouchPointRaw(i); + if (raw.y >= tb_y) + { + auto det = Touch.getDetail(i); + if (det.state & touch_state_t::touch) + { + if (BtnA.isPressed()) { btn_rawstate_bits |= 1 << 0; } + if (BtnB.isPressed()) { btn_rawstate_bits |= 1 << 1; } + if (BtnC.isPressed()) { btn_rawstate_bits |= 1 << 2; } + if (btn_rawstate_bits || !(det.state & touch_state_t::mask_moving)) + { + btn_rawstate_bits |= 1 << ((raw.x * tb_k) >> 16); + } + } + } + } + } + } + +#if defined (M5UNIFIED_PC_BUILD) + use_rawstate_bits = 0b10111; + btn_rawstate_bits = (!m5gfx::gpio_in(39) ? 0b00001 : 0) // LEFT=BtnA + | (!m5gfx::gpio_in(38) ? 0b00010 : 0) // DOWN=BtnB + | (!m5gfx::gpio_in(37) ? 0b00100 : 0) // RIGHT=BtnC + | (!m5gfx::gpio_in(36) ? 0b10000 : 0) // UP=BtnPWR + ; +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + + uint_fast8_t raw_gpio32_39 = ~GPIO.in1.data; + switch (_board) + { + case board_t::board_M5StackCoreInk: + { + uint32_t raw_gpio0_31 = ~GPIO.in; + use_rawstate_bits = 0b11000; + btn_rawstate_bits = (((raw_gpio0_31 >> CoreInk_BUTTON_EXT_PIN) & 1) << 3) + | (((raw_gpio0_31 >> CoreInk_BUTTON_PWR_PIN) & 1) << 4); + } + NON_BREAK; /// don't break; + + case board_t::board_M5Paper: + case board_t::board_M5Station: + use_rawstate_bits |= 0b00111; + btn_rawstate_bits |= (raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 0x07; // gpio37 A / gpio38 B / gpio39 C + break; + + case board_t::board_M5Stack: + use_rawstate_bits = 0b00111; + btn_rawstate_bits = (((raw_gpio32_39 >> (GPIO_NUM_38 & 31)) & 1) << 1) // gpio38 B + | (((raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 1) << 2); // gpio37 C + NON_BREAK; /// don't break; + + case board_t::board_M5AtomLite: + case board_t::board_M5AtomMatrix: + case board_t::board_M5AtomVoice: + case board_t::board_M5AtomPsram: + case board_t::board_M5AtomU: + case board_t::board_M5StampPico: + use_rawstate_bits |= 0b00001; + btn_rawstate_bits |= (raw_gpio32_39 >> (GPIO_NUM_39 & 31)) & 1; // gpio39 A + break; + + case board_t::board_M5StickCPlus2: + use_rawstate_bits = 0b10000; + btn_rawstate_bits = (((raw_gpio32_39 >> (GPIO_NUM_35 & 31)) & 1)<<4); // gpio35 PWR + NON_BREAK; /// don't break; + + case board_t::board_M5StickC: + case board_t::board_M5StickCPlus: + use_rawstate_bits |= 0b00011; + btn_rawstate_bits |= (( raw_gpio32_39 >> (GPIO_NUM_37 & 31)) & 1 ) // gpio37 A + | (((raw_gpio32_39 >> (GPIO_NUM_39 & 31)) & 1)<<1); // gpio39 B + break; + + default: + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + + switch (_board) + { + case board_t::board_M5AirQ: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_8)) & 1) << 1; + break; + + case board_t::board_M5VAMeter: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; + break; + + case board_t::board_M5StampS3: + case board_t::board_M5Cardputer: + case board_t::board_M5CardputerADV: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_0)) & 1; + break; + + case board_t::board_M5AtomS3: + case board_t::board_M5AtomS3Lite: + case board_t::board_M5AtomS3U: + case board_t::board_M5AtomS3R: + case board_t::board_M5AtomVoiceS3R: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_41)) & 1; + break; + + case board_t::board_M5Capsule: + case board_t::board_M5Dial: + case board_t::board_M5DinMeter: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_42)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; + break; + + case board_t::board_M5StampPLC: + { + use_rawstate_bits = 0b00111; + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b100) ? 0b00001 : 0) // BtnA + | (!(value & 0b010) ? 0b00010 : 0) // BtnB + | (!(value & 0b001) ? 0b00100 : 0) // BtnC + ; + } + } + break; + + case board_t::board_M5PowerHub: + { + use_rawstate_bits = 0b00011; + auto value = M5.In_I2C.readRegister8(powerhub_i2c_addr, 0xA0, 100000); + // ESP_LOGI("M5Unified", "M5PowerHub Btn read: %02X", value); + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_11) & 1) // BtnA + | (!(value & 0b001) ? 0b00010 : 0) // BtnB + ; + break; + } + + case board_t::board_M5DualKey: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_17)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_0)) & 1) << 1; + break; + + case board_t::board_M5StickS3: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_11)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_12)) & 1) << 1; + break; + + case board_t::board_M5PaperDIY: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_3)) & 1) << 1; + break; + + case board_t::board_M5PaperColor: + use_rawstate_bits = 0b00111; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_10)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_9)) & 1) << 1 + | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 2; + break; + + case board_t::board_M5ChainCaptain: + use_rawstate_bits = 0b00111; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) << 1 + | ((!m5gfx::gpio_in(GPIO_NUM_5)) & 1) << 2; + break; + + case board_t::board_M5PaperMono: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_3)) & 1) << 1; + break; + + case board_t::board_M5StopWatch: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 1; + break; + + default: + + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C3) + + switch (_board) + { + case board_t::board_M5StampC3: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_3)) & 1; + break; + + case board_t::board_M5StampC3U: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9)) & 1; + break; + + default: + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + + switch (_board) + { + case board_t::board_M5NanoC6: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9) ? 0b00001 : 0); + break; + + case board_t::board_M5UnitC6L: + { + use_rawstate_bits = 0b00001; + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0); // BtnA + } + } + break; + + case board_t::board_ArduinoNessoN1: + { + use_rawstate_bits = 0b00011; + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0) // BtnA + | (!(value & 0b010) ? 0b00010 : 0) // BtnB + ; + } + } + break; + + default: + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + + switch (_board) + { + case board_t::board_M5NanoH2: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9) ? 0b00001 : 0); + break; + + default: + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32P4) + + switch (_board) + { + case board_t::board_M5UnitPoEP4: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_45)) & 1; + break; + + default: + break; + } + +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + + switch (_board) + { + case board_t::board_M5CoreMatrix: + /// KEY1/2/3 are wired to PM1 GPIO0/1/2 (pressed = LOW), not to the ESP, + /// so they are read by I2C polling. Skip the update on an I2C failure so + /// a bus error is not reported as a button press. + { + uint8_t in; + if (Power.M5pm1.getGPIOInputBits(&in)) + { + use_rawstate_bits = 0b00111; + btn_rawstate_bits = (~in) & 0b00111; + } + } + break; + + default: + break; + } + +#endif + + if (use_rawstate_bits) { + for (int i = 0; i < 5; ++i) { + if (use_rawstate_bits & (1 << i)) { + _buttons[i].setRawState(ms, btn_rawstate_bits & (1 << i)); + } + } + } + +#if defined (CONFIG_IDF_TARGET_ESP32) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + if (_use_pmic_button) + { + Button_Class::button_state_t state = Button_Class::button_state_t::state_nochange; + bool read_axp = (ms - BtnPWR.getUpdateMsec()) >= BTNPWR_MIN_UPDATE_MSEC; + if (read_axp || BtnPWR.getState()) + { + switch (Power.getKeyState()) + { + case 0: break; + case 2: state = Button_Class::button_state_t::state_clicked; break; + default: state = Button_Class::button_state_t::state_hold; break; + } + BtnPWR.setState(ms, state); + } + } +#endif + } + + void M5Unified::setTouchButtonHeightByRatio(uint8_t ratio) + { + uint32_t height = 0; + switch (_board) + { + case board_t::board_M5StackCore2: + case board_t::board_M5Tough: + case board_t::board_M5ToughC5: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackChan: + height = 240; + break; + case board_t::board_M5Paper: + case board_t::board_M5PaperS3: + height = 960; + break; + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + height = 1280; + break; + default: + break; + } + _touch_button_height = height * ratio / 255; + } + + M5GFX& M5Unified::getDisplay(size_t index) + { + return index != _primary_display_index && index < this->_displays.size() ? this->_displays[index] : Display; + } + + std::size_t M5Unified::addDisplay(M5GFX& dsp) + { + this->_displays.push_back(dsp); + auto res = this->_displays.size() - 1; + setPrimaryDisplay(res == 0 ? 0 : _primary_display_index); + + // Touch screen operation is always limited to the first display. + Touch.begin(_displays.front().touch() ? &_displays.front() : nullptr); + + return res; + } + + int32_t M5Unified::getDisplayIndex(m5gfx::board_t board) { + int i = 0; + for (auto &d : _displays) + { + if (board == d.getBoard()) { return i; } + ++i; + } + return -1; + } + + int32_t M5Unified::getDisplayIndex(std::initializer_list board_list) + { + for (auto b : board_list) + { + int32_t i = getDisplayIndex(b); + if (i >= 0) { return i; } + } + return -1; + } + + bool M5Unified::setPrimaryDisplay(std::size_t index) + { + if (index >= _displays.size()) { return false; } + std::size_t pdi = _primary_display_index; + + if (pdi < _displays.size()) + { + _displays[pdi] = Display; + } + _primary_display_index = index; + Display = _displays[index]; + return true; + } + + bool M5Unified::setPrimaryDisplayType(std::initializer_list board_list) + { + auto i = getDisplayIndex(board_list); + bool res = (i >= 0); + if (res) { setPrimaryDisplay(i); } + return res; + } + + void M5Unified::setLogDisplayIndex(size_t index) + { + Log.setDisplay(getDisplay(index)); + } + + void M5Unified::setLogDisplayType(std::initializer_list board_list) + { + auto i = getDisplayIndex(board_list); + if (i >= 0) { Log.setDisplay(getDisplay(i)); } + } +} diff --git a/src/gitTagVersion.h b/src/gitTagVersion.h index 47e6fd7e..95b0ed94 100644 --- a/src/gitTagVersion.h +++ b/src/gitTagVersion.h @@ -1,4 +1,4 @@ #define M5UNIFIED_VERSION_MAJOR 0 #define M5UNIFIED_VERSION_MINOR 2 -#define M5UNIFIED_VERSION_PATCH 22 +#define M5UNIFIED_VERSION_PATCH 23 #define M5UNIFIED_VERSION F( M5UNIFIED_VERSION_MAJOR "." M5UNIFIED_VERSION_MINOR "." M5UNIFIED_VERSION_PATCH ) diff --git a/src/utility/Button_Class.cpp b/src/utility/Button_Class.inl similarity index 93% rename from src/utility/Button_Class.cpp rename to src/utility/Button_Class.inl index 17c213a5..89ee3239 100644 --- a/src/utility/Button_Class.cpp +++ b/src/utility/Button_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Button_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "Button_Class.hpp" diff --git a/src/utility/I2C_Class.cpp b/src/utility/I2C_Class.inl similarity index 96% rename from src/utility/I2C_Class.cpp rename to src/utility/I2C_Class.inl index b034442c..c1691032 100644 --- a/src/utility/I2C_Class.cpp +++ b/src/utility/I2C_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "I2C_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "I2C_Class.hpp" diff --git a/src/utility/IMU_Class.cpp b/src/utility/IMU_Class.inl similarity index 99% rename from src/utility/IMU_Class.cpp rename to src/utility/IMU_Class.inl index 3645fdf6..f214f722 100644 --- a/src/utility/IMU_Class.cpp +++ b/src/utility/IMU_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "IMU_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "../M5Unified.hpp" #include "IMU_Class.hpp" diff --git a/src/utility/LED_Class.cpp b/src/utility/LED_Class.inl similarity index 93% rename from src/utility/LED_Class.cpp rename to src/utility/LED_Class.inl index d4cd4de6..ae10ab1d 100644 --- a/src/utility/LED_Class.cpp +++ b/src/utility/LED_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "LED_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "LED_Class.hpp" diff --git a/src/utility/Log_Class.cpp b/src/utility/Log_Class.inl similarity index 97% rename from src/utility/Log_Class.cpp rename to src/utility/Log_Class.inl index 8a7fcc07..946f1509 100644 --- a/src/utility/Log_Class.cpp +++ b/src/utility/Log_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Log_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "Log_Class.hpp" diff --git a/src/utility/M5IOE1_Class.cpp b/src/utility/M5IOE1_Class.inl similarity index 97% rename from src/utility/M5IOE1_Class.cpp rename to src/utility/M5IOE1_Class.inl index bb55ba11..e7d2579c 100644 --- a/src/utility/M5IOE1_Class.cpp +++ b/src/utility/M5IOE1_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "M5IOE1_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "M5IOE1_Class.hpp" diff --git a/src/utility/M5Timer.cpp b/src/utility/M5Timer.inl similarity index 94% rename from src/utility/M5Timer.cpp rename to src/utility/M5Timer.inl index c6a865b4..7b990193 100644 --- a/src/utility/M5Timer.cpp +++ b/src/utility/M5Timer.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "M5Timer.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "M5Timer.h" #include diff --git a/src/utility/Mic_Class.hpp b/src/utility/Mic_Class.hpp index c9f0fe15..890b63ed 100644 --- a/src/utility/Mic_Class.hpp +++ b/src/utility/Mic_Class.hpp @@ -134,15 +134,21 @@ namespace m5 /// @param func (args, data, length): data is the pointer given to record(), /// length its array_len. /// @attention Called from the capture task, not an ISR: keep it short, - /// never block in it, never call begin()/end() from it. + /// never block in it (the record() exception is below), never + /// call begin()/end() from it. /// @attention Requests dropped by end() do not call back. Delivery follows /// the slot release, so it can arrive after isRecording() has /// already dropped: track buffers by pointer, not by counting. /// @attention record() may be called from within at the current sample - /// rate: it never waits there and returns false if the queue is - /// full or a begin()/end() is in progress. A different rate is - /// refused there (it would rebuild the calling task) and leaves - /// the current rate untouched. + /// rate. It never waits for a slot there: false at once when + /// both slots are taken. It does wait, up to a few ticks, for + /// another task holding the request lock (normally a record() + /// that is publishing) to release it, and gives up + /// with false past that or when an end() or rate change is + /// stopping the task (whether the request would have fit is + /// unknown then). A different rate is refused there (it would + /// rebuild the calling task) and leaves the current rate + /// untouched. /// @attention Set or clear it only before the first record() or after /// end() has returned - not merely while isRecording() is 0: /// the task may still be about to call the previous function, @@ -155,36 +161,40 @@ namespace m5 void setSampleRate(uint32_t sample_rate) { _cfg.sample_rate = sample_rate; } /// record raw sound wave data. - /// @param rec_data Recording destination array. - /// @param array_len Number of data array elements. + /// A completed request has exactly array_len elements written, never + /// more. A stereo buffer holds L/R pairs; with an odd array_len the last + /// element receives the left sample only. Requests that are already + /// queued when the previous one completes are continuous whatever their + /// length (a capture step that straddles two buffers is carried over). + /// @param rec_data Recording destination array. nullptr returns false. + /// @param array_len Number of data array elements. 0 returns false + /// (nothing is queued and the release callback is not called). /// @param sample_rate the sampling rate (Hz). 0 is invalid and returns false. /// @param stereo true=data is stereo / false=data is monaural. + /// @return false when the arguments are invalid, the mic cannot start, or + /// (from the release callback only) no request slot is free, the + /// task is being stopped, another caller kept the request lock + /// for too long, or the sample rate differs from the current one. bool record(uint8_t* rec_data, size_t array_len, uint32_t sample_rate, bool stereo = false) { return sample_rate != 0 && _rec_raw(rec_data, array_len, false, sample_rate, stereo); } - /// record raw sound wave data. - /// @param rec_data Recording destination array. - /// @param array_len Number of data array elements. - /// @param sample_rate the sampling rate (Hz). 0 is invalid and returns false. - /// @param stereo true=data is stereo / false=data is monaural. + /// record raw sound wave data. See the uint8_t overload for the contract. bool record(int16_t* rec_data, size_t array_len, uint32_t sample_rate, bool stereo = false) { return sample_rate != 0 && _rec_raw(rec_data, array_len, true, sample_rate, stereo); } - /// record raw sound wave data. - /// @param rec_data Recording destination array. - /// @param array_len Number of data array elements. + /// record raw sound wave data at the current sample rate (monaural). + /// See the 4-argument overload for the contract. bool record(uint8_t* rec_data, size_t array_len) { // sample_rate 0 == keep the current rate; resolved under the lock. return _rec_raw(rec_data, array_len, false, 0, false); } - /// record raw sound wave data. - /// @param rec_data Recording destination array. - /// @param array_len Number of data array elements. + /// record raw sound wave data at the current sample rate (monaural). + /// See the 4-argument overload for the contract. bool record(int16_t* rec_data, size_t array_len) { return _rec_raw(rec_data, array_len, true, 0, false); @@ -243,6 +253,10 @@ namespace m5 /// stop request and the task's exit are actual synchronization, not a /// volatile-based data race. std::atomic _task_running { false }; + /// how long record() from the release callback waits for another caller's + /// publish to finish before giving up (a legitimate holder releases within + /// microseconds; the bound covers priority inversion on the plain atomic). + static constexpr uint32_t in_task_lock_wait_ticks = 5; /// begin() runs from whichever task records first, and setup starts by /// tearing the port down - so only one call may go through. std::atomic _begin_lock { false }; diff --git a/src/utility/Mic_Class.cpp b/src/utility/Mic_Class.inl similarity index 84% rename from src/utility/Mic_Class.cpp rename to src/utility/Mic_Class.inl index 461d425d..869c8c8d 100644 --- a/src/utility/Mic_Class.cpp +++ b/src/utility/Mic_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Mic_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "Mic_Class.hpp" @@ -87,28 +90,28 @@ namespace m5 #if __has_include() - static i2s_chan_handle_t _i2s_handle[M5UNIFIED_I2S_PORT_COUNT] = { nullptr, }; + static i2s_chan_handle_t _mic_i2s_handle[M5UNIFIED_I2S_PORT_COUNT] = { nullptr, }; - static esp_err_t _i2s_start(i2s_port_t port) { - if (_i2s_handle[port] == nullptr) { return ESP_FAIL; } - return i2s_channel_enable(_i2s_handle[port]); + static esp_err_t _mic_i2s_start(i2s_port_t port) { + if (_mic_i2s_handle[port] == nullptr) { return ESP_FAIL; } + return i2s_channel_enable(_mic_i2s_handle[port]); } - static esp_err_t _i2s_stop(i2s_port_t port) + static esp_err_t _mic_i2s_stop(i2s_port_t port) { - if (_i2s_handle[port] == nullptr) { return ESP_OK; } - return i2s_channel_disable(_i2s_handle[port]); + if (_mic_i2s_handle[port] == nullptr) { return ESP_OK; } + return i2s_channel_disable(_mic_i2s_handle[port]); } - static esp_err_t _i2s_read(i2s_port_t port, void* buf, size_t len, size_t* result, uint32_t timeout_ms) { - if (_i2s_handle[port] == nullptr) { return ESP_FAIL; } + static esp_err_t _mic_i2s_read(i2s_port_t port, void* buf, size_t len, size_t* result, uint32_t timeout_ms) { + if (_mic_i2s_handle[port] == nullptr) { return ESP_FAIL; } /// i2s_channel_read のタイムアウトはミリ秒 (旧 i2s_read の tick 数とは契約が異なる) - return i2s_channel_read(_i2s_handle[port], buf, len, result, timeout_ms); + return i2s_channel_read(_mic_i2s_handle[port], buf, len, result, timeout_ms); } - static esp_err_t _i2s_driver_uninstall(i2s_port_t port) + static esp_err_t _mic_i2s_driver_uninstall(i2s_port_t port) { - if (_i2s_handle[port] != nullptr) { - auto res = i2s_del_channel(_i2s_handle[port]); + if (_mic_i2s_handle[port] != nullptr) { + auto res = i2s_del_channel(_mic_i2s_handle[port]); /// Keep the handle when the deletion fails. (e.g. the channel is still running) - if (res == ESP_OK) { _i2s_handle[port] = nullptr; } + if (res == ESP_OK) { _mic_i2s_handle[port] = nullptr; } return res; } return ESP_OK; @@ -133,7 +136,7 @@ namespace m5 }; }; - static esp_err_t _i2s_set_adc(i2s_port_t port, gpio_num_t pin_data_in) { + static esp_err_t _mic_i2s_set_adc(i2s_port_t port, gpio_num_t pin_data_in) { if (port == I2S_NUM_0) { /// レジスタを操作してADCモードの設定を有効にする ; if (((size_t)pin_data_in) > 39) { return ESP_FAIL; } @@ -221,24 +224,24 @@ namespace m5 } #endif #else - static esp_err_t _i2s_start(i2s_port_t port) + static esp_err_t _mic_i2s_start(i2s_port_t port) { return i2s_start(port); } - static esp_err_t _i2s_stop(i2s_port_t port) + static esp_err_t _mic_i2s_stop(i2s_port_t port) { return i2s_stop(port); } - static esp_err_t _i2s_read(i2s_port_t port, void* buf, size_t len, size_t* result, uint32_t timeout_ms) + static esp_err_t _mic_i2s_read(i2s_port_t port, void* buf, size_t len, size_t* result, uint32_t timeout_ms) { return i2s_read(port, buf, len, result, pdMS_TO_TICKS(timeout_ms)); } - static esp_err_t _i2s_driver_uninstall(i2s_port_t port) + static esp_err_t _mic_i2s_driver_uninstall(i2s_port_t port) { return i2s_driver_uninstall(port); } #if defined (M5UNIFIED_I2S_ADC_DAC) - static esp_err_t _i2s_set_adc(i2s_port_t port, gpio_num_t pin_data_in) { + static esp_err_t _mic_i2s_set_adc(i2s_port_t port, gpio_num_t pin_data_in) { if (port == I2S_NUM_0) { /// レジスタを操作してADCモードの設定を有効にする ; if (((size_t)pin_data_in) > 39) { return ESP_FAIL; } @@ -305,9 +308,9 @@ namespace m5 i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(_cfg.i2s_port, I2S_ROLE_MASTER); chan_cfg.dma_desc_num = _cfg.dma_buf_count; chan_cfg.dma_frame_num = _cfg.dma_buf_len; - esp_err_t err = _i2s_driver_uninstall(_cfg.i2s_port); + esp_err_t err = _mic_i2s_driver_uninstall(_cfg.i2s_port); if (err != ESP_OK) { return err; } - err = i2s_new_channel(&chan_cfg, nullptr, &_i2s_handle[_cfg.i2s_port]); + err = i2s_new_channel(&chan_cfg, nullptr, &_mic_i2s_handle[_cfg.i2s_port]); if (err != ESP_OK) { return err; } #if SOC_I2S_SUPPORTS_PDM_RX @@ -327,7 +330,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { i2s_config.slot_cfg.slot_mask = (_cfg.stereo) ? i2s_pdm_slot_mask_t::I2S_PDM_SLOT_BOTH : (_cfg.left_channel ? i2s_pdm_slot_mask_t::I2S_PDM_SLOT_LEFT : i2s_pdm_slot_mask_t::I2S_PDM_SLOT_RIGHT); i2s_config.gpio_cfg.clk = (gpio_num_t)_cfg.pin_ws; i2s_config.gpio_cfg.din = (gpio_num_t)_cfg.pin_data_in; - err = i2s_channel_init_pdm_rx_mode(_i2s_handle[_cfg.i2s_port], &i2s_config); + err = i2s_channel_init_pdm_rx_mode(_mic_i2s_handle[_cfg.i2s_port], &i2s_config); } else #endif { @@ -376,19 +379,19 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { i2s_config.gpio_cfg.dout = (gpio_num_t)I2S_PIN_NO_CHANGE; i2s_config.gpio_cfg.mclk = (gpio_num_t)_cfg.pin_mck; i2s_config.gpio_cfg.din = (gpio_num_t)_cfg.pin_data_in; - err = i2s_channel_init_std_mode(_i2s_handle[_cfg.i2s_port], &i2s_config); + err = i2s_channel_init_std_mode(_mic_i2s_handle[_cfg.i2s_port], &i2s_config); } if (err != ESP_OK) { - _i2s_driver_uninstall(_cfg.i2s_port); + _mic_i2s_driver_uninstall(_cfg.i2s_port); return err; } #if defined (M5UNIFIED_I2S_ADC_DAC) if (_cfg.use_adc) { - err = _i2s_set_adc(_cfg.i2s_port, (gpio_num_t)_cfg.pin_data_in); - if (err != ESP_OK) { _i2s_driver_uninstall(_cfg.i2s_port); } + err = _mic_i2s_set_adc(_cfg.i2s_port, (gpio_num_t)_cfg.pin_data_in); + if (err != ESP_OK) { _mic_i2s_driver_uninstall(_cfg.i2s_port); } } #endif @@ -423,7 +426,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { esp_err_t err; if (ESP_OK != (err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr))) { - _i2s_driver_uninstall(_cfg.i2s_port); + _mic_i2s_driver_uninstall(_cfg.i2s_port); err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr); } if (err != ESP_OK) { return err; } @@ -431,7 +434,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { #if defined (M5UNIFIED_I2S_ADC_DAC) if (_cfg.use_adc) { - err = _i2s_set_adc(_cfg.i2s_port, (gpio_num_t)_cfg.pin_data_in); + err = _mic_i2s_set_adc(_cfg.i2s_port, (gpio_num_t)_cfg.pin_data_in); } else #endif @@ -455,14 +458,6 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { bool use_pdm = (self->_cfg.pin_bck < 0 && !self->_cfg.use_adc); -#if defined (CONFIG_IDF_TARGET_ESP32P4) - // ESP32-P4 の std 経路はクロックを _setup_i2s でドライバ管理により最終値に - // 設定済みのため、raw 分周の上書きを行わない (PDM 経路は従来どおり)。 - const bool skip_raw_clk = !use_pdm; -#else - const bool skip_raw_clk = false; -#endif - static constexpr uint32_t PLL_D2_CLK = M5UNIFIED_I2S_PLL_D2_HZ; uint32_t bits = (self->_cfg.use_adc) ? 1 : 16; /// 1サンプリング当たりの出力ビット数; @@ -504,6 +499,13 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { #elif defined (I2S_RX_PDM2PCM_EN) dev->rx_conf.rx_pdm2pcm_en = use_pdm; dev->rx_conf.rx_pdm_sinc_dsr_16_en = 1; +#endif +#if defined (CONFIG_IDF_TARGET_ESP32P4) + // ESP32-P4 の std 経路はクロックを _setup_i2s でドライバ管理により最終値に + // 設定済みのため、raw 分周の上書きを行わない (PDM 経路は従来どおり)。 + const bool skip_raw_clk = !use_pdm; +#else + const bool skip_raw_clk = false; #endif if (!skip_raw_clk) { @@ -604,7 +606,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { #endif - if (raw_clk_ok && ESP_OK == _i2s_start(self->_cfg.i2s_port)) + if (raw_clk_ok && ESP_OK == _mic_i2s_start(self->_cfg.i2s_port)) { self->_i2s_active.store(true, std::memory_order_release); } @@ -613,9 +615,14 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { const float f_gain = (float)gain / (oversampling << 1); size_t src_idx = ~0u; size_t src_len = 0; - int32_t sum_value[4] = { 0,0 }; + int32_t sum_value[2] = { 0, 0 }; int32_t prev_value[2] = { 0, 0 }; const bool in_stereo = self->_cfg.stereo; + // A mono capture step yields two time steps. When a buffer fills after + // the first one, the second is kept here for the next pending request so + // back-to-back recordings stay continuous whatever their length. + int32_t carry_value = 0; + bool carry_valid = false; int32_t os_remain = oversampling; const size_t dma_buf_len = self->_cfg.dma_buf_len; /// dma_buf_len は DMA descriptor のフレーム数として使われる (_setup_i2s の @@ -628,8 +635,8 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { int16_t* src_buf = (int16_t*)alloca(read_bytes); memset(src_buf, 0, read_bytes); - _i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 10); - _i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 10); + _mic_i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 10); + _mic_i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 10); while (self->_task_running) { @@ -663,9 +670,62 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { src_len = 0; sum_value[0] = 0; sum_value[1] = 0; + carry_valid = false; // buffered input is dropped here as well; the carry is only for back-to-back requests os_remain = oversampling; continue; } + + // Write one element in the request's sample format. + auto write_one = [&](int32_t value) + { + if (current_rec->is_16bit) + { + if ( value < INT16_MIN+16) { value = INT16_MIN+16; } + else if (value > INT16_MAX-16) { value = INT16_MAX-16; } + auto dst = (int16_t*)(current_rec->data); + *dst++ = value; + current_rec->data = dst; + } + else + { + value = ((value + 128) >> 8) + 128; + if ( value < 0) { value = 0; } + else if (value > 255) { value = 255; } + auto dst = (uint8_t*)(current_rec->data); + *dst++ = value; + current_rec->data = dst; + } + --dst_remain; + }; + // Write one time step (left, right). A mono request takes left only + // (the caller has already averaged a stereo capture); a stereo request + // whose last element is reached after left drops right, so an odd + // stereo length ends with a lone left sample. + auto write_step = [&](int32_t left, int32_t right) + { + write_one(left); + if (current_rec->is_stereo && dst_remain) { write_one(right); } + }; + auto release_current = [&](void) + { + // data has been walked forward while filling: step back over the + // whole buffer to hand the caller the pointer they gave record(). + const size_t total = current_rec->length.load(std::memory_order_relaxed); + void* released = (uint8_t*)current_rec->data - total * (current_rec->is_16bit ? 2 : 1); + current_rec->length.store(0, std::memory_order_release); + xSemaphoreGive(self->_task_semaphore); + if (self->_cb_buffer_release) + { + self->_cb_buffer_release(self->_cb_buffer_release_args, released, total); + } + }; + + if (carry_valid) + { + carry_valid = false; + write_step(carry_value, carry_value); + if (dst_remain == 0) { release_current(); continue; } + } for (;;) { if (src_idx >= src_len) @@ -678,7 +738,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { { src_len = 0; if (!self->_task_running) { break; } - if (ESP_OK == _i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 100) + if (ESP_OK == _mic_i2s_read(self->_cfg.i2s_port, src_buf, read_bytes, &src_len, 100) && src_len != 0 && 0 == (src_len & 3)) { break; } // An immediately-failing read must not become a tight loop: a @@ -744,66 +804,27 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { } } - int output_num = 2; - - if (in_stereo != current_rec->is_stereo) - { - if (in_stereo) - { // stereo -> mono convert. - sum_value[0] = (sum_value[0] + sum_value[1] + 1) >> 1; - output_num = 1; - } - else - { // mono -> stereo convert. - auto tmp = sum_value[1]; - sum_value[3] = tmp; - sum_value[2] = tmp; - sum_value[1] = sum_value[0]; - output_num = 4; - } + if (in_stereo) + { // one time step (L, R); a mono request takes the average + if (current_rec->is_stereo) { write_step(sum_value[0], sum_value[1]); } + else { write_one((sum_value[0] + sum_value[1] + 1) >> 1); } } - for (int i = 0; i < output_num; ++i) - { - auto value = sum_value[i]; - if (current_rec->is_16bit) - { - if ( value < INT16_MIN+16) { value = INT16_MIN+16; } - else if (value > INT16_MAX-16) { value = INT16_MAX-16; } - auto dst = (int16_t*)(current_rec->data); - *dst++ = value; - current_rec->data = dst; - } - else - { - value = ((value + 128) >> 8) + 128; - if ( value < 0) { value = 0; } - else if (value > 255) { value = 255; } - auto dst = (uint8_t*)(current_rec->data); - *dst++ = value; - current_rec->data = dst; - } + else + { // two time steps; a stereo request duplicates each into L and R + write_step(sum_value[0], sum_value[0]); + if (dst_remain) { write_step(sum_value[1], sum_value[1]); } + else { carry_value = sum_value[1]; carry_valid = true; } } sum_value[0] = 0; sum_value[1] = 0; - dst_remain -= output_num; - if ((int32_t)dst_remain <= 0) + if (dst_remain == 0) { - // data has been walked forward while filling: step back over what - // was written to hand the caller the pointer they gave record(). - const size_t total = current_rec->length.load(std::memory_order_relaxed); - const size_t written = total - dst_remain; // dst_remain may have wrapped below 0 - void* released = (uint8_t*)current_rec->data - written * (current_rec->is_16bit ? 2 : 1); - current_rec->length.store(0, std::memory_order_release); - xSemaphoreGive(self->_task_semaphore); - if (self->_cb_buffer_release) - { - self->_cb_buffer_release(self->_cb_buffer_release_args, released, total); - } + release_current(); break; } } } - _i2s_stop(self->_cfg.i2s_port); + _mic_i2s_stop(self->_cfg.i2s_port); self->_i2s_active.store(false, std::memory_order_release); // Deleting itself here would race _end_locked()'s notify against the @@ -963,7 +984,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { else { // Success means the port runs: wait for the task to enable the // channel, and fail the begin when that never happens. Without - // this, an _i2s_start() failure would leave record() returning + // this, an _mic_i2s_start() failure would leave record() returning // true for requests the task can never complete. const uint32_t start_tick = xTaskGetTickCount(); while (!_i2s_active.load(std::memory_order_acquire) @@ -1036,7 +1057,7 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { _rec_info[1].clear(); if (_cb_set_enabled) { _cb_set_enabled(_cb_set_enabled_args, false); } - _i2s_driver_uninstall(_cfg.i2s_port); + _mic_i2s_driver_uninstall(_cfg.i2s_port); } void Mic_Class::recording_info_t::clear(void) @@ -1059,19 +1080,38 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { // to drain or for the capture side to make progress. The retry has no // fairness order - competing callers that keep requesting conflicting // sample rates can hold each other off indefinitely. - // From the release callback (the capture task itself) nothing may be - // waited for: an end() holding _rec_lock waits for this task to exit, and - // a free slot only comes from this task. Try once and report. + // From the release callback (the capture task itself) a full queue is + // reported at once (a free slot only comes from this task), but the lock + // is waited for: the publisher notifies while still holding it, so the + // callback often arrives before the unlock. A delay, not a spin, so a + // one-core holder can run - and a bounded one, since the plain atomic + // has no priority inheritance and a middle-priority task can keep the + // holder off the CPU forever. A stop (end() / rate rebuild) holds the + // lock while waiting for this task, and clears _task_running first. + // Nothing to record into is a caller error, not a request: it used to + // return true without ever invoking the release callback. + if (recdata == nullptr || array_len == 0) { return false; } const bool in_task = (xTaskGetCurrentTaskHandle() == _task_handle.load(std::memory_order_acquire)); + if (in_task && _rec_info[0].length.load(std::memory_order_acquire) != 0 + && _rec_info[1].length.load(std::memory_order_acquire) != 0) + { // both slots taken; only this task frees one, so waiting is pointless. + return false; + } for (;;) { bool zero = false; + uint32_t in_task_ticks = 0; while (!_rec_lock.compare_exchange_strong(zero, true)) { - if (in_task) { return false; } + if (in_task && (!_task_running.load(std::memory_order_acquire) || ++in_task_ticks > in_task_lock_wait_ticks)) { return false; } zero = false; vTaskDelay(1); } + if (in_task && !_task_running.load(std::memory_order_acquire)) + { // a stop got the lock first and is now waiting for this task. + _rec_lock.store(false); + return false; + } int result = _rec_try_locked(recdata, array_len, flg_16bit, sample_rate, flg_stereo); _rec_lock.store(false); if (result >= 0) { return result; } @@ -1087,7 +1127,6 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { { int res = _begin_raw(sample_rate); if (res <= 0) { return res; } - if (array_len == 0) { return 1; } // Any free slot will do: the consume order comes from the sequence // number stamped below, not from which slot a request lands in. A full // queue just means "come back later". @@ -1109,6 +1148,9 @@ if (_cfg.pin_bck < 0 || _cfg.pin_ws < 0) { slot.is_16bit = flg_16bit; slot.length.store(array_len, std::memory_order_release); + // Sent under _rec_lock on purpose: end() takes the same lock before it + // deletes the task, so the handle cannot go stale between the load and + // the notify. if (this->_task_handle) { xTaskNotifyGive(this->_task_handle); diff --git a/src/utility/PI4IOE5V6408_Class.cpp b/src/utility/PI4IOE5V6408_Class.inl similarity index 93% rename from src/utility/PI4IOE5V6408_Class.cpp rename to src/utility/PI4IOE5V6408_Class.inl index 44cd71bc..6faac6d3 100644 --- a/src/utility/PI4IOE5V6408_Class.cpp +++ b/src/utility/PI4IOE5V6408_Class.inl @@ -8,6 +8,9 @@ * @copyright Copyright (c) 2024 * */ +#ifndef M5UNIFIED_IMPLEMENTATION +#error "PI4IOE5V6408_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "PI4IOE5V6408_Class.hpp" namespace m5 diff --git a/src/utility/Power_Class.hpp b/src/utility/Power_Class.hpp index 4c697250..708e7f8d 100644 --- a/src/utility/Power_Class.hpp +++ b/src/utility/Power_Class.hpp @@ -96,6 +96,15 @@ namespace m5 /// switch-over), and enabling without a battery may block for up to 1 s while the protection check /// waits for the TS reading to settle. The switch-over is serialized with setUsbOutput and the /// internal speaker enable, so those may wait for it as well. + /// @note Core2 v1.1 (AXP2101; a Tough with the AXP2101 takes the same path): disabling + /// the output while powered from USB with no battery makes the ESP32 brownout and + /// reset (the board's VBUS-to-bus switch closes before the boost stops and the + /// transient pulls VSYS down). The PMIC stays on and the board reboots with the + /// output enabled again, so a sketch that unconditionally disables it at startup + /// will reboot in a loop on such a unit. With a battery, or with 5 V supplied on + /// the bus, the transition itself does not disturb the board. Disabling an enabled + /// output blocks for about 20 ms while the PMIC's DCDC under-voltage power-off is + /// suspended; the output is left untouched when that protection cannot be suspended. void setExtOutput(bool enable, ext_port_mask_t port_mask = (ext_port_mask_t)0xFF); /// deprecated : Change to "setExtOutput" diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.inl similarity index 97% rename from src/utility/Power_Class.cpp rename to src/utility/Power_Class.inl index 6e8d6ec0..2bf560cf 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.inl @@ -1,8 +1,12 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Power_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "../M5Unified.hpp" #include "Power_Class.hpp" +#include "m5unified_i2c_addr.hpp" #include "M5IOE1_Class.hpp" #if !defined (M5UNIFIED_PC_BUILD) @@ -65,12 +69,8 @@ namespace m5 { - static constexpr const uint32_t i2c_freq = 100000; - #if !defined (M5UNIFIED_PC_BUILD) #if defined (CONFIG_IDF_TARGET_ESP32S3) - static constexpr uint8_t aw9523_i2c_addr = 0x58; - static constexpr uint8_t powerhub_i2c_addr = 0x50; static constexpr uint8_t ip2315_i2c_addr = 0x75; // M5PaperMono USB fast-charger static constexpr int M5PaperS3_CHG_STAT_PIN = GPIO_NUM_4; @@ -151,10 +151,10 @@ namespace m5 /// |||||||+-- RF_PTH_L_INT_H_EXT : antenna L=internal / H=external /// |||||||| 0x05, 0b01110000, // OUT_SET - 0x03, 0b01110011, // IO_DIR + 0x03, 0b01100011, // IO_DIR (LCD_RST input) 0x07, 0b00001000, // OUT_H_IM - 0x0D, 0b00000100, // PULL_SEL - 0x0B, 0b00000100, // PULL_EN + 0x0D, 0b00010100, // PULL_SEL (LCD_RST pull-up) + 0x0B, 0b00010100, // PULL_EN (LCD_RST pull-up enabled) }; static constexpr std::uint8_t reg_array_0x44[] = { /// +--------- CHG_EN @@ -858,6 +858,10 @@ namespace m5 // , 0x18, 0x0E }; Axp2101.writeRegister8Array(reg_data_array, sizeof(reg_data_array)); + // Re-arm the DCDC1/DCDC3 under-voltage power-off that setExtOutput(false) + // suspends; an ESP32 brownout reset during that call skips the restore. + // Forced like the table above: these bits are the chip default. + Axp2101.bitOn(0x23, 0x05); // for Core2 v1.1 (AXP2101+INA3221) if (Ina3221[0].begin()) @@ -1189,7 +1193,41 @@ namespace m5 if (_pmic == pmic_axp2101) { cancel = (enable && (Ina3221[0].getShuntVoltage(0) < 0.0f || Ina3221[0].getShuntVoltage(1) < 0.0f) && (8 >= Axp2101.getBatteryLevel())); if (!cancel) { - Axp2101.setBLDO2(enable * 3300); + if (enable) { + Axp2101.setBLDO2(3300); + break; + } + // Core2 v1.1: BLDO2 drives both the boost enable and the /EN of the + // switch that ties USB VBUS to the 5V bus. While BLDO2 falls the switch + // closes before the boost stops, and the boost output feeds back into + // VBUS -> PMIC -> boost. With no battery this sags VSYS and the AXP2101 + // powers off on DCDC under-voltage (latched until the power key). + // Suspending that power-off for the transition turns it into an ESP32 + // brownout reset instead; begin() re-arms it after such a reset. + // (A Tough with the AXP2101 shares this path; the suspend is harmless there.) + uint8_t r90, r23 = 0; + if (!Axp2101.readRegister(0x90, &r90, 1)) { r90 = 0xFF; } // unreadable: assume a transition + bool transition = (r90 & (1 << 5)) != 0; + bool suspended = false; + if (transition) { + // Fail closed: when the power-off cannot be suspended the output is left as is. + if (!Axp2101.readRegister(0x23, &r23, 1)) { + ESP_LOGW("Power", "setExtOutput(false): protection register unreadable, refused."); + break; + } + suspended = (r23 & 0x05) != 0; + if (suspended && !Axp2101.writeRegister8(0x23, r23 & ~0x05)) { + ESP_LOGW("Power", "setExtOutput(false): could not suspend the DCDC UVP power-off, refused."); + break; + } + } + Axp2101.setBLDO2(0); + if (suspended) { + m5gfx::delay(20); // the transition completes within 10 ms (measured) + if (!Axp2101.writeRegister8(0x23, r23)) { + ESP_LOGW("Power", "setExtOutput(false): DCDC UVP power-off not re-armed."); + } + } break; } } else { diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.inl similarity index 97% rename from src/utility/RTC_Class.cpp rename to src/utility/RTC_Class.inl index 57cd7301..87d62b13 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "RTC_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "../M5Unified.hpp" #include "RTC_Class.hpp" diff --git a/src/utility/Speaker_Class.hpp b/src/utility/Speaker_Class.hpp index c7e29457..5453ac50 100644 --- a/src/utility/Speaker_Class.hpp +++ b/src/utility/Speaker_Class.hpp @@ -92,7 +92,7 @@ namespace m5 void end(void); - bool isRunning(void) const { return _task_running; } + bool isRunning(void) const { return _task_running.load(std::memory_order_acquire); } bool isEnabled(void) const { @@ -130,7 +130,8 @@ namespace m5 /// playWav() the PCM chunk inside the wav, not the wav pointer; /// channel is the virtual channel it was played on. /// @attention Called from the playback task, not an ISR: keep it short, - /// never block in it, never call begin()/end() from it. The + /// never block in it, never call begin()/end() from it (both + /// wait for this very task and deadlock). The /// sound itself may still be in the I2S queue - this is buffer /// release, not end of playback. /// @attention Requests discarded without being played do not call back: @@ -140,7 +141,7 @@ namespace m5 /// it can arrive after isPlaying() has already dropped: track /// buffers by pointer, not by counting. /// @attention play*() may be called from within: it never waits there and - /// returns false if no queue slot is free. + /// returns false if no queue slot is free or end() is stopping the task. /// @attention Set or clear it only before the first play*() or after /// end() has returned - not merely while the queue looks /// empty: the task may still be about to call the previous @@ -205,7 +206,7 @@ namespace m5 /// @param channel virtual channel number (If omitted, use an available channel.) /// @param stop_current_sound true=start a new output without waiting for the current one to finish. /// @attention For data generated at runtime, two buffers used alternately are enough if the next one is filled only after the previous one is released, as reported by setBufferReleaseCallback(). Without it use three buffers in sequence (or, with a single producer and no stop() or stop_current_sound in between, wait for isPlaying(channel) to drop below 2 before refilling). - /// @return true if the request was queued; false if not (no free channel, the speaker could not be started, nothing to play). A queued request is reported by setBufferReleaseCallback() unless it is discarded first (see there). + /// @return true if queued. With an explicit channel a normal task waits for a slot; false includes an infinite request ahead, no free automatic channel, startup failure, or nothing to play. A queued request is reported by setBufferReleaseCallback() unless discarded first. /// @attention If noise is present in the output sounds, consider increasing the priority of the task that generates the data. bool playRaw(const int8_t* raw_data, size_t array_len, uint32_t sample_rate = 44100, bool stereo = false, uint32_t repeat = 1, int channel = -1, bool stop_current_sound = false) { @@ -226,7 +227,7 @@ namespace m5 /// @param channel virtual channel number (If omitted, use an available channel.) /// @param stop_current_sound true=start a new output without waiting for the current one to finish. /// @attention For data generated at runtime, two buffers used alternately are enough if the next one is filled only after the previous one is released, as reported by setBufferReleaseCallback(). Without it use three buffers in sequence (or, with a single producer and no stop() or stop_current_sound in between, wait for isPlaying(channel) to drop below 2 before refilling). - /// @return true if the request was queued; false if not (no free channel, the speaker could not be started, nothing to play). A queued request is reported by setBufferReleaseCallback() unless it is discarded first (see there). + /// @return true if queued. With an explicit channel a normal task waits for a slot; false includes an infinite request ahead, no free automatic channel, startup failure, or nothing to play. A queued request is reported by setBufferReleaseCallback() unless discarded first. /// @attention If noise is present in the output sounds, consider increasing the priority of the task that generates the data. bool playRaw(const uint8_t* raw_data, size_t array_len, uint32_t sample_rate = 44100, bool stereo = false, uint32_t repeat = 1, int channel = -1, bool stop_current_sound = false) { @@ -247,7 +248,7 @@ namespace m5 /// @param channel virtual channel number (If omitted, use an available channel.) /// @param stop_current_sound true=start a new output without waiting for the current one to finish. /// @attention For data generated at runtime, two buffers used alternately are enough if the next one is filled only after the previous one is released, as reported by setBufferReleaseCallback(). Without it use three buffers in sequence (or, with a single producer and no stop() or stop_current_sound in between, wait for isPlaying(channel) to drop below 2 before refilling). - /// @return true if the request was queued; false if not (no free channel, the speaker could not be started, nothing to play). A queued request is reported by setBufferReleaseCallback() unless it is discarded first (see there). + /// @return true if queued. With an explicit channel a normal task waits for a slot; false includes an infinite request ahead, no free automatic channel, startup failure, or nothing to play. A queued request is reported by setBufferReleaseCallback() unless discarded first. /// @attention If noise is present in the output sounds, consider increasing the priority of the task that generates the data. bool playRaw(const int16_t* raw_data, size_t array_len, uint32_t sample_rate = 44100, bool stereo = false, uint32_t repeat = 1, int channel = -1, bool stop_current_sound = false) { @@ -266,6 +267,7 @@ namespace m5 /// @param repeat number of times played repeatedly. (default = 1) /// @param channel virtual channel number (If omitted, use an available channel.) /// @param stop_current_sound true=start a new output without waiting for the current one to finish. + /// @return true if queued. With an explicit channel a normal task waits for a slot; false includes an infinite request ahead, no free automatic channel, startup failure, or invalid WAV data. bool playWav(const uint8_t* wav_data, size_t data_len = ~0u, uint32_t repeat = 1, int channel = -1, bool stop_current_sound = false); protected: @@ -274,6 +276,8 @@ namespace m5 static const uint8_t _default_tone_wav[16]; + /// Called under the request lock from begin()/end(): it must not call + /// play*(), stop(), begin() or end() itself (deadlock). void setCallback(void* args, bool(*func)(void*, bool)) { _cb_set_enabled = func; _cb_set_enabled_args = args; } struct wav_info_t @@ -346,9 +350,12 @@ namespace m5 esp_err_t _setup_i2s(void); bool _in_task(void) const; + void _lock_req(void); + bool _begin_raw(void); void _end_locked(void); bool _play_raw(const void* wav, size_t array_len, bool flg_16bit, bool flg_signed, float sample_rate, bool flg_stereo, uint32_t repeat_count, int channel, bool stop_current_sound, bool no_clear_index); bool _set_next_wav(size_t ch, const wav_info_t& wav); + int _set_next_wav_locked(size_t ch, const wav_info_t& wav); speaker_config_t _cfg; volatile uint8_t _master_volume = 64; @@ -358,16 +365,22 @@ namespace m5 void (*_cb_buffer_release)(void* args, const void* data, uint8_t channel) = nullptr; void* _cb_buffer_release_args = nullptr; - volatile bool _task_running = false; + std::atomic _task_running { false }; std::atomic _play_channel_bits = { 0 }; /// begin() runs from whichever task touches the speaker first, and setup /// starts by tearing the port down - so only one call may go through. std::atomic _begin_lock { false }; + /// Serializes request publication with lifecycle changes. + /// Lock order: _req_lock before _begin_lock. + std::atomic _req_lock { false }; /// True only once begin() has fully finished; the lock-free early return /// keys on this, so a caller can never see a half-built port as ready. std::atomic _begun { false }; #if defined (SDL_h_) std::atomic _task_handle { nullptr }; + /// _in_task() compares this, never the handle: SDL_WaitThread() frees + /// the SDL_Thread, and a reader outside the lock may still hold it. + std::atomic _task_thread_id { 0 }; #else /// atomic: _in_task() reads it from any caller of play*(), possibly /// while begin() on another task is still creating the task. diff --git a/src/utility/Speaker_Class.cpp b/src/utility/Speaker_Class.inl similarity index 88% rename from src/utility/Speaker_Class.cpp rename to src/utility/Speaker_Class.inl index f94e9396..9c3899fb 100644 --- a/src/utility/Speaker_Class.cpp +++ b/src/utility/Speaker_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Speaker_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "Speaker_Class.hpp" @@ -75,32 +78,32 @@ namespace m5 #if __has_include() - static i2s_chan_handle_t _i2s_handle[M5UNIFIED_I2S_PORT_COUNT] = { nullptr, }; + static i2s_chan_handle_t _spk_i2s_handle[M5UNIFIED_I2S_PORT_COUNT] = { nullptr, }; - static esp_err_t _i2s_start(i2s_port_t port) { - if (_i2s_handle[port] == nullptr) { return ESP_FAIL; } - return i2s_channel_enable(_i2s_handle[port]); + static esp_err_t _spk_i2s_start(i2s_port_t port) { + if (_spk_i2s_handle[port] == nullptr) { return ESP_FAIL; } + return i2s_channel_enable(_spk_i2s_handle[port]); } - static esp_err_t _i2s_stop(i2s_port_t port) + static esp_err_t _spk_i2s_stop(i2s_port_t port) { - if (_i2s_handle[port] == nullptr) { return ESP_OK; } - return i2s_channel_disable(_i2s_handle[port]); + if (_spk_i2s_handle[port] == nullptr) { return ESP_OK; } + return i2s_channel_disable(_spk_i2s_handle[port]); } - static esp_err_t _i2s_write(i2s_port_t port, void* buf, size_t len, size_t* result, TickType_t tick) { - return i2s_channel_write(_i2s_handle[port], buf, len, result, tick); + static esp_err_t _spk_i2s_write(i2s_port_t port, void* buf, size_t len, size_t* result, TickType_t tick) { + return i2s_channel_write(_spk_i2s_handle[port], buf, len, result, tick); } - static esp_err_t _i2s_driver_uninstall(i2s_port_t port) + static esp_err_t _spk_i2s_driver_uninstall(i2s_port_t port) { - if (_i2s_handle[port] != nullptr) { - auto res = i2s_del_channel(_i2s_handle[port]); + if (_spk_i2s_handle[port] != nullptr) { + auto res = i2s_del_channel(_spk_i2s_handle[port]); /// Keep the handle when the deletion fails. (e.g. the channel is still running) - if (res == ESP_OK) { _i2s_handle[port] = nullptr; } + if (res == ESP_OK) { _spk_i2s_handle[port] = nullptr; } return res; } return ESP_OK; } #if defined (M5UNIFIED_I2S_ADC_DAC) - static esp_err_t _i2s_set_dac(i2s_port_t port, bool left_en, bool right_en) { + static esp_err_t _spk_i2s_set_dac(i2s_port_t port, bool left_en, bool right_en) { if (port == I2S_NUM_0) { /// DACモードの設定を有効にする(I2S0のみ。I2S1はDAC,ADC非対応) ; bool dac_en = left_en || right_en; @@ -139,24 +142,24 @@ namespace m5 #endif #else - static esp_err_t _i2s_start(i2s_port_t port) + static esp_err_t _spk_i2s_start(i2s_port_t port) { return i2s_start(port); } - static esp_err_t _i2s_stop(i2s_port_t port) + static esp_err_t _spk_i2s_stop(i2s_port_t port) { return i2s_stop(port); } - static esp_err_t _i2s_write(i2s_port_t port, void* buf, size_t len, size_t* result, TickType_t tick) + static esp_err_t _spk_i2s_write(i2s_port_t port, void* buf, size_t len, size_t* result, TickType_t tick) { return i2s_write(port, buf, len, result, tick); } - static esp_err_t _i2s_driver_uninstall(i2s_port_t port) + static esp_err_t _spk_i2s_driver_uninstall(i2s_port_t port) { return i2s_driver_uninstall(port); } #if defined (M5UNIFIED_I2S_ADC_DAC) - static esp_err_t _i2s_set_dac(i2s_port_t port, bool left_en, bool right_en) { + static esp_err_t _spk_i2s_set_dac(i2s_port_t port, bool left_en, bool right_en) { if (port == I2S_NUM_0) { /// レジスタを操作してDACモードの設定を有効にする(I2S0のみ。I2S1はDAC,ADC非対応) ; i2s_dac_mode_t dac_mode = i2s_dac_mode_t::I2S_DAC_CHANNEL_DISABLE; @@ -192,9 +195,9 @@ namespace m5 chan_cfg.dma_desc_num = _cfg.dma_buf_count; chan_cfg.dma_frame_num = _cfg.dma_buf_len; chan_cfg.auto_clear = true; - esp_err_t err = _i2s_driver_uninstall(_cfg.i2s_port); + esp_err_t err = _spk_i2s_driver_uninstall(_cfg.i2s_port); if (err != ESP_OK) { return err; } - err = i2s_new_channel(&chan_cfg, &_i2s_handle[_cfg.i2s_port], nullptr); + err = i2s_new_channel(&chan_cfg, &_spk_i2s_handle[_cfg.i2s_port], nullptr); if (err != ESP_OK) { return err; } i2s_std_config_t i2s_config; @@ -242,10 +245,10 @@ namespace m5 i2s_config.gpio_cfg.dout = (gpio_num_t)_cfg.pin_data_out; i2s_config.gpio_cfg.mclk = (gpio_num_t)_cfg.pin_mck; i2s_config.gpio_cfg.din = (gpio_num_t)I2S_PIN_NO_CHANGE; - err = i2s_channel_init_std_mode(_i2s_handle[_cfg.i2s_port], &i2s_config); + err = i2s_channel_init_std_mode(_spk_i2s_handle[_cfg.i2s_port], &i2s_config); if (err != ESP_OK) { - _i2s_driver_uninstall(_cfg.i2s_port); + _spk_i2s_driver_uninstall(_cfg.i2s_port); return err; } @@ -254,8 +257,8 @@ namespace m5 { bool left_en = _cfg.stereo || (_cfg.pin_data_out == GPIO_NUM_26); bool right_en = _cfg.stereo || (_cfg.pin_data_out == GPIO_NUM_25); - err = _i2s_set_dac(_cfg.i2s_port, left_en, right_en); - if (err != ESP_OK) { _i2s_driver_uninstall(_cfg.i2s_port); } + err = _spk_i2s_set_dac(_cfg.i2s_port, left_en, right_en); + if (err != ESP_OK) { _spk_i2s_driver_uninstall(_cfg.i2s_port); } } #endif @@ -293,7 +296,7 @@ namespace m5 esp_err_t err; if (ESP_OK != (err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr))) { - _i2s_driver_uninstall(_cfg.i2s_port); + _spk_i2s_driver_uninstall(_cfg.i2s_port); err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr); } if (err != ESP_OK) { return err; } @@ -303,7 +306,7 @@ namespace m5 { bool right_en = _cfg.stereo || (_cfg.pin_data_out == GPIO_NUM_25); bool left_en = _cfg.stereo || (_cfg.pin_data_out == GPIO_NUM_26); - err = _i2s_set_dac(_cfg.i2s_port, left_en, right_en); + err = _spk_i2s_set_dac(_cfg.i2s_port, left_en, right_en); } else #endif @@ -562,7 +565,7 @@ namespace m5 int32_t* sound_buf32 = (int32_t*)alloca(dma_buf_len * sizeof(int32_t)); - while (self->_task_running) + while (self->_task_running.load(std::memory_order_acquire)) { if (flg_nodata) { @@ -589,7 +592,7 @@ namespace m5 sound_buf32[idx] = tmp | tmp << 16; } while (++idx < dma_buf_len); size_t write_bytes; - _i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); + _spk_i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); if (self->_cfg.dac_zero_level == 0) { dac_offset = 0; @@ -603,7 +606,7 @@ namespace m5 while (!ulTaskNotifyTake( pdTRUE, 0 ) && --retry) { size_t write_bytes; - _i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); + _spk_i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); } if (!retry) @@ -612,8 +615,8 @@ namespace m5 if (self->_cfg.use_dac) { flg_i2s_started = spk_i2s_stop; - _i2s_stop(i2s_port); - _i2s_set_dac(i2s_port, false, false); //i2s_dac_mode_t::I2S_DAC_CHANNEL_DISABLE); + _spk_i2s_stop(i2s_port); + _spk_i2s_set_dac(i2s_port, false, false); //i2s_dac_mode_t::I2S_DAC_CHANNEL_DISABLE); } #endif // 新しいデータが届くまで待機; @@ -643,10 +646,10 @@ namespace m5 { bool left_en = out_stereo || (self->_cfg.pin_data_out == GPIO_NUM_26); bool right_en = out_stereo || (self->_cfg.pin_data_out == GPIO_NUM_25); - _i2s_set_dac(i2s_port, left_en, right_en); + _spk_i2s_set_dac(i2s_port, left_en, right_en); } #endif - _i2s_start(i2s_port); + _spk_i2s_start(i2s_port); } if (self->_cfg.use_dac && self->_cfg.dac_zero_level != 0) @@ -658,7 +661,7 @@ namespace m5 sound_buf32[idx] = tmp | tmp << 16; } while (++idx < dma_buf_len); size_t write_bytes; - _i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); + _spk_i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); } flg_i2s_started = spk_i2s_run; } @@ -999,10 +1002,10 @@ namespace m5 #else size_t write_bytes; size_t data_bytes = data_length * sizeof(int16_t) << self->_cfg.buzzer; - _i2s_write(i2s_port, sound_buf32, data_bytes, &write_bytes, 0); + _spk_i2s_write(i2s_port, sound_buf32, data_bytes, &write_bytes, 0); if (write_bytes < data_bytes) { auto sb8 = (uint8_t*)sound_buf32; - _i2s_write(i2s_port, &sb8[write_bytes], data_bytes - write_bytes, &write_bytes, portMAX_DELAY); + _spk_i2s_write(i2s_port, &sb8[write_bytes], data_bytes - write_bytes, &write_bytes, portMAX_DELAY); buf_cnt = self->_cfg.dma_buf_count; } #endif @@ -1012,11 +1015,11 @@ namespace m5 SDL_CloseAudioDevice(1); SDL_CloseAudio(); #else - _i2s_stop(i2s_port); + _spk_i2s_stop(i2s_port); #if defined (M5UNIFIED_I2S_ADC_DAC) if (self->_cfg.use_dac) { - _i2s_set_dac(i2s_port, false, false); + _spk_i2s_set_dac(i2s_port, false, false); m5gfx::gpio_lo(self->_cfg.pin_data_out); m5gfx::pinMode(self->_cfg.pin_data_out, m5gfx::pin_mode_t::output); } @@ -1030,6 +1033,16 @@ namespace m5 { if (_begun.load(std::memory_order_acquire)) { return true; } + _lock_req(); + bool res = _begin_raw(); + _req_lock.store(false); + return res; + } + + bool Speaker_Class::_begin_raw(void) + { + if (_begun.load(std::memory_order_acquire)) { return true; } + // Playback calls begin() lazily from whichever task gets there first, // and _setup_i2s starts by uninstalling the port: two of these racing // rip the live channel out from under the playback task. One caller @@ -1056,9 +1069,10 @@ namespace m5 res = (ESP_OK == _setup_i2s()) && res; if (res) { - _task_running = true; + _task_running.store(true, std::memory_order_release); #if defined (SDL_h_) auto handle = SDL_CreateThread(reinterpret_cast(spk_task), "spk_task", this); + _task_thread_id.store(handle ? SDL_GetThreadID(handle) : 0, std::memory_order_release); _task_handle.store(handle, std::memory_order_release); res = (handle != nullptr); #else @@ -1091,9 +1105,9 @@ namespace m5 void Speaker_Class::end(void) { - // Serialized with begin() by the same lock: a teardown must not slip in - // between the task being created and its handle being published, or it - // would miss the task and the stale handle would be published afterwards. + // _req_lock keeps a publish/notify from another task (and a lazy begin) + // out of the teardown; _begin_lock then orders it with begin(). + _lock_req(); bool zero = false; while (!_begin_lock.compare_exchange_strong(zero, true)) { @@ -1106,24 +1120,26 @@ namespace m5 } _end_locked(); _begin_lock.store(false); + _req_lock.store(false); } void Speaker_Class::_end_locked(void) { _begun.store(false, std::memory_order_release); if (_cb_set_enabled) { _cb_set_enabled(_cb_set_enabled_args, false); } - if (_task_running) + if (_task_running.load(std::memory_order_acquire)) { // No stop() here: it would publish markers and notify a task that may // already be tearing its handle down. The slots are reset below once // the task is gone, which is all the stop would have achieved. - _task_running = false; + _task_running.store(false, std::memory_order_release); auto handle = _task_handle.load(std::memory_order_acquire); if (handle) { #if defined (SDL_h_) SDL_WaitThread(handle, nullptr); _task_handle.store(nullptr, std::memory_order_release); + _task_thread_id.store(0, std::memory_order_release); #else xTaskNotifyGive(handle); do { vTaskDelay(1); } while (_task_handle.load(std::memory_order_acquire)); @@ -1141,7 +1157,7 @@ namespace m5 chinfo->current.clear(); } #if !defined (SDL_h_) - _i2s_driver_uninstall(_cfg.i2s_port); + _spk_i2s_driver_uninstall(_cfg.i2s_port); #endif } @@ -1180,6 +1196,29 @@ namespace m5 } bool Speaker_Class::_set_next_wav(size_t ch, const wav_info_t& wav) + { + if (_in_task()) { return _set_next_wav_locked(ch, wav) > 0; } + for (;;) + { + _lock_req(); + int result = (_begun.load(std::memory_order_acquire) + && _task_handle.load(std::memory_order_acquire)) + ? _set_next_wav_locked(ch, wav) : 0; + _req_lock.store(false); + if (result >= 0) { return result; } +#if defined (SDL_h_) + SDL_Delay(1); +#else + xSemaphoreTake(_task_semaphore, 1); +#endif + } + } + + /// One claim attempt: 1 = published, 0 = refused (never behind an endless + /// request), -1 = no free slot right now; the caller waits outside the + /// lock and retries. A lost claim is retried here: whoever changed the + /// slot made progress, so it cannot spin for long. + int Speaker_Class::_set_next_wav_locked(size_t ch, const wav_info_t& wav) { auto chinfo = &_ch_info[ch]; uint8_t chmask = 1 << ch; @@ -1215,26 +1254,30 @@ namespace m5 #if !defined (SDL_h_) xTaskNotifyGive(_task_handle.load(std::memory_order_acquire)); #endif - return true; + return 1; } - continue; // lost the claim to whoever changed the state; they made - // progress, so trying again right away cannot spin for long. + continue; } if (!wav.stop_current && ((chinfo->wavinfo[!f].state.load(std::memory_order_relaxed) & (wav_phase_mask | wav_state_infinite)) == (wav_phase_playing | wav_state_infinite))) { // never a turn behind an endless request. - return false; + return 0; } - if (_in_task()) - { // called from the release callback: only the task frees slots, so - // waiting here would be waiting on ourselves. - return false; - } -#if !defined (SDL_h_) - xSemaphoreTake(_task_semaphore, 1); -#else + return -1; + } + } + + void Speaker_Class::_lock_req(void) + { + bool zero = false; + while (!_req_lock.compare_exchange_strong(zero, true)) + { + zero = false; +#if defined (SDL_h_) SDL_Delay(1); +#else + vTaskDelay(1); #endif } } @@ -1243,10 +1286,11 @@ namespace m5 // callback), where nothing may be waited for and no lifecycle started. bool Speaker_Class::_in_task(void) const { - auto handle = _task_handle.load(std::memory_order_acquire); #if defined (SDL_h_) - return handle && SDL_ThreadID() == SDL_GetThreadID(handle); + auto id = _task_thread_id.load(std::memory_order_acquire); + return id && SDL_ThreadID() == id; #else + auto handle = _task_handle.load(std::memory_order_acquire); return handle && xTaskGetCurrentTaskHandle() == handle; #endif } @@ -1256,24 +1300,12 @@ namespace m5 // Only a queued request produces a release callback, so the answer is // false whenever nothing was queued (this used to say true for a speaker // that could not start and for an empty request). - if (_in_task()) + const bool in_task = _in_task(); + if (in_task) { // from the release callback: an end() may be tearing the task down, // and begin() from here would rebuild it underneath. Never start. if (!_begun.load(std::memory_order_acquire)) { return false; } } - else if (!begin()) { return false; } - if (_task_handle.load(std::memory_order_acquire) == nullptr) { return false; } - if (array_len == 0 || data == nullptr) { return false; } - size_t ch = (size_t)channel; - if (ch >= sound_channel_max) - { - size_t bits = _play_channel_bits.load(); - for (ch = sound_channel_max - 1; ch < sound_channel_max; --ch) - { - if (0 == ((bits >> ch) & 1)) { break; } - } - if (ch >= sound_channel_max) { return false; } - } wav_info_t info; info.data = data; info.length = array_len; @@ -1285,7 +1317,36 @@ namespace m5 info.stop_current = stop_current_sound; info.no_clear_index = no_clear_index; - return _set_next_wav(ch, info); + for (;;) + { + if (!in_task) { _lock_req(); } + bool ready = in_task ? _begun.load(std::memory_order_acquire) : _begin_raw(); + int result = 0; + if (ready && _task_handle.load(std::memory_order_acquire)) + { + if (array_len != 0 && data != nullptr) + { + size_t ch = (size_t)channel; + if (ch >= sound_channel_max) + { + size_t bits = _play_channel_bits.load(); + for (ch = sound_channel_max - 1; ch < sound_channel_max; --ch) + { + if (0 == ((bits >> ch) & 1)) { break; } + } + } + if (ch < sound_channel_max) { result = _set_next_wav_locked(ch, info); } + } + } + if (!in_task) { _req_lock.store(false); } + if (result >= 0) { return result; } + if (in_task) { return false; } +#if defined (SDL_h_) + SDL_Delay(1); +#else + xSemaphoreTake(_task_semaphore, 1); +#endif + } } bool Speaker_Class::playWav(const uint8_t* wav_data, size_t data_len, uint32_t repeat, int channel, bool stop_current_sound) diff --git a/src/utility/Touch_Class.cpp b/src/utility/Touch_Class.inl similarity index 96% rename from src/utility/Touch_Class.cpp rename to src/utility/Touch_Class.inl index 3df5330f..a94c52ad 100644 --- a/src/utility/Touch_Class.cpp +++ b/src/utility/Touch_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "Touch_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "Touch_Class.hpp" diff --git a/src/utility/imu/AK8963_Class.cpp b/src/utility/imu/AK8963_Class.inl similarity index 94% rename from src/utility/imu/AK8963_Class.cpp rename to src/utility/imu/AK8963_Class.inl index a5ea830a..9abf6b09 100644 --- a/src/utility/imu/AK8963_Class.cpp +++ b/src/utility/imu/AK8963_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "AK8963_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #if defined (ESP_PLATFORM) diff --git a/src/utility/imu/BMI270_Class.cpp b/src/utility/imu/BMI270_Class.inl similarity index 97% rename from src/utility/imu/BMI270_Class.cpp rename to src/utility/imu/BMI270_Class.inl index f4d00f61..e54a5408 100644 --- a/src/utility/imu/BMI270_Class.cpp +++ b/src/utility/imu/BMI270_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "BMI270_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #if defined (ESP_PLATFORM) diff --git a/src/utility/imu/BMI270_config.inl b/src/utility/imu/BMI270_config.inl index 33f0792c..2eb601e7 100644 --- a/src/utility/imu/BMI270_config.inl +++ b/src/utility/imu/BMI270_config.inl @@ -1,3 +1,6 @@ +#ifndef M5UNIFIED_IMPLEMENTATION +#error "BMI270_config.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif static constexpr const uint8_t bmi270_config_file[] = { 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x3d, 0xb1, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x91, 0x03, 0x80, 0x2e, 0xbc, diff --git a/src/utility/imu/BMM150_Class.cpp b/src/utility/imu/BMM150_Class.inl similarity index 94% rename from src/utility/imu/BMM150_Class.cpp rename to src/utility/imu/BMM150_Class.inl index 56b8e600..89084c22 100644 --- a/src/utility/imu/BMM150_Class.cpp +++ b/src/utility/imu/BMM150_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "BMM150_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #if defined (ESP_PLATFORM) diff --git a/src/utility/imu/IMU_Base.cpp b/src/utility/imu/IMU_Base.inl similarity index 93% rename from src/utility/imu/IMU_Base.cpp rename to src/utility/imu/IMU_Base.inl index 5ee0723e..a1fb53e6 100644 --- a/src/utility/imu/IMU_Base.cpp +++ b/src/utility/imu/IMU_Base.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "IMU_Base.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "IMU_Base.hpp" diff --git a/src/utility/imu/MPU6886_Class.cpp b/src/utility/imu/MPU6886_Class.inl similarity index 98% rename from src/utility/imu/MPU6886_Class.cpp rename to src/utility/imu/MPU6886_Class.inl index d85797a8..be1bf1f7 100644 --- a/src/utility/imu/MPU6886_Class.cpp +++ b/src/utility/imu/MPU6886_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "MPU6886_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #if defined (ESP_PLATFORM) diff --git a/src/utility/imu/SH200Q_Class.cpp b/src/utility/imu/SH200Q_Class.inl similarity index 97% rename from src/utility/imu/SH200Q_Class.cpp rename to src/utility/imu/SH200Q_Class.inl index fb8db7a3..6da3b9e0 100644 --- a/src/utility/imu/SH200Q_Class.cpp +++ b/src/utility/imu/SH200Q_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "SH200Q_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #if defined (ESP_PLATFORM) diff --git a/src/utility/led/LED_PMIC_Class.cpp b/src/utility/led/LED_PMIC_Class.inl similarity index 93% rename from src/utility/led/LED_PMIC_Class.cpp rename to src/utility/led/LED_PMIC_Class.inl index 1f6f77a9..1e3cfbd1 100644 --- a/src/utility/led/LED_PMIC_Class.cpp +++ b/src/utility/led/LED_PMIC_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "LED_PMIC_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "LED_PMIC_Class.hpp" diff --git a/src/utility/led/LED_PaperMono_Class.hpp b/src/utility/led/LED_PaperMono_Class.hpp index 6f5db1e6..71b00902 100644 --- a/src/utility/led/LED_PaperMono_Class.hpp +++ b/src/utility/led/LED_PaperMono_Class.hpp @@ -16,13 +16,14 @@ namespace m5 bool begin(void) override; led_type_t getLedType(size_t index) const override { return led_type_t::led_type_fullcolor; } - size_t getCount(void) const override { return 1; } + size_t getCount(void) const override { return led_count; } void setColors(const RGBColor* values, size_t index, size_t length) override; void setBrightness(const uint8_t brightness) override; void display(void) override; RGBColor* getBuffer(void) override { return &_rgb_buffer; } private: + static constexpr size_t led_count = 1; RGBColor _rgb_buffer; uint8_t _brightness = 63; }; diff --git a/src/utility/led/LED_PaperMono_Class.cpp b/src/utility/led/LED_PaperMono_Class.inl similarity index 92% rename from src/utility/led/LED_PaperMono_Class.cpp rename to src/utility/led/LED_PaperMono_Class.inl index bb3ab26d..de6885f6 100644 --- a/src/utility/led/LED_PaperMono_Class.cpp +++ b/src/utility/led/LED_PaperMono_Class.inl @@ -1,7 +1,11 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "LED_PaperMono_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "LED_PaperMono_Class.hpp" +#include "../m5unified_i2c_addr.hpp" #include "../../M5Unified.hpp" #include "../M5IOE1_Class.hpp" @@ -13,9 +17,6 @@ namespace m5 // RGBLED_G = PYB -> G8 // RGBLED_B = PYB -> G9 - static constexpr size_t led_count = 1; - static constexpr uint8_t m5pm1_i2c_addr = 0x6E; - static constexpr uint32_t i2c_freq = 100000; static constexpr auto ioe1_led_g_pin = M5IOE1_Class::gpio8; static constexpr auto ioe1_led_b_pin = M5IOE1_Class::gpio9; diff --git a/src/utility/led/LED_PowerHub_Class.hpp b/src/utility/led/LED_PowerHub_Class.hpp index 9fe170be..13df27a4 100644 --- a/src/utility/led/LED_PowerHub_Class.hpp +++ b/src/utility/led/LED_PowerHub_Class.hpp @@ -19,13 +19,14 @@ namespace m5 bool begin(void) override; led_type_t getLedType(size_t index) const override { return led_type_t::led_type_fullcolor; } - size_t getCount(void) const override { return 8; } + size_t getCount(void) const override { return led_count; } void setColors(const RGBColor* values, size_t index, size_t length) override; void setBrightness(const uint8_t brightness) override; void display(void) override; RGBColor* getBuffer(void) override { return _rgb_buffer.data(); } private: + static constexpr size_t led_count = 8; std::vector _rgb_buffer; uint8_t _brightness = 63; }; diff --git a/src/utility/led/LED_PowerHub_Class.cpp b/src/utility/led/LED_PowerHub_Class.inl similarity index 90% rename from src/utility/led/LED_PowerHub_Class.cpp rename to src/utility/led/LED_PowerHub_Class.inl index 6940fc34..7d543745 100644 --- a/src/utility/led/LED_PowerHub_Class.cpp +++ b/src/utility/led/LED_PowerHub_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "LED_PowerHub_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "LED_PowerHub_Class.hpp" @@ -7,7 +10,6 @@ namespace m5 { - constexpr size_t led_count = 8; bool LED_PowerHub_Class::begin(void) { _rgb_buffer.resize(led_count); diff --git a/src/utility/led/LED_Strip_Class.cpp b/src/utility/led/LED_Strip_Class.inl similarity index 98% rename from src/utility/led/LED_Strip_Class.cpp rename to src/utility/led/LED_Strip_Class.inl index 9622ed18..871ddea8 100644 --- a/src/utility/led/LED_Strip_Class.cpp +++ b/src/utility/led/LED_Strip_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "LED_Strip_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "LED_Strip_Class.hpp" diff --git a/src/utility/m5unified_i2c_addr.hpp b/src/utility/m5unified_i2c_addr.hpp new file mode 100644 index 00000000..d45c0f15 --- /dev/null +++ b/src/utility/m5unified_i2c_addr.hpp @@ -0,0 +1,19 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_I2C_ADDR_HPP_ +#define M5UNIFIED_I2C_ADDR_HPP_ + +#include + +// I2C addresses of on-board devices that more than one implementation file talks to. +// (Internal use. Kept in one place so the single-translation-unit build has one definition.) +namespace m5 +{ + static constexpr uint32_t i2c_freq = 100000; + + static constexpr uint8_t m5pm1_i2c_addr = 0x6E; + static constexpr uint8_t aw9523_i2c_addr = 0x58; + static constexpr uint8_t powerhub_i2c_addr = 0x50; +} + +#endif diff --git a/src/utility/power/AW32001_Class.cpp b/src/utility/power/AW32001_Class.inl similarity index 96% rename from src/utility/power/AW32001_Class.cpp rename to src/utility/power/AW32001_Class.inl index 7243aa70..b6287601 100644 --- a/src/utility/power/AW32001_Class.cpp +++ b/src/utility/power/AW32001_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "AW32001_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "AW32001_Class.hpp" diff --git a/src/utility/power/AXP192_Class.cpp b/src/utility/power/AXP192_Class.inl similarity index 98% rename from src/utility/power/AXP192_Class.cpp rename to src/utility/power/AXP192_Class.inl index 06b20806..877b265b 100644 --- a/src/utility/power/AXP192_Class.cpp +++ b/src/utility/power/AXP192_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "AXP192_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "AXP192_Class.hpp" diff --git a/src/utility/power/AXP2101_Class.cpp b/src/utility/power/AXP2101_Class.inl similarity index 99% rename from src/utility/power/AXP2101_Class.cpp rename to src/utility/power/AXP2101_Class.inl index c46931b6..7bd7c72a 100644 --- a/src/utility/power/AXP2101_Class.cpp +++ b/src/utility/power/AXP2101_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "AXP2101_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "AXP2101_Class.hpp" @@ -606,3 +609,5 @@ return false; } } + +#undef IS_BIT_SET diff --git a/src/utility/power/BQ27220_Class.cpp b/src/utility/power/BQ27220_Class.inl similarity index 94% rename from src/utility/power/BQ27220_Class.cpp rename to src/utility/power/BQ27220_Class.inl index db2baf99..ba2a05bf 100644 --- a/src/utility/power/BQ27220_Class.cpp +++ b/src/utility/power/BQ27220_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "BQ27220_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "BQ27220_Class.hpp" diff --git a/src/utility/power/INA226_Class.cpp b/src/utility/power/INA226_Class.inl similarity index 93% rename from src/utility/power/INA226_Class.cpp rename to src/utility/power/INA226_Class.inl index 87840975..907e3c65 100644 --- a/src/utility/power/INA226_Class.cpp +++ b/src/utility/power/INA226_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "INA226_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "INA226_Class.hpp" diff --git a/src/utility/power/INA3221_Class.cpp b/src/utility/power/INA3221_Class.inl similarity index 92% rename from src/utility/power/INA3221_Class.cpp rename to src/utility/power/INA3221_Class.inl index 82e66228..12d62843 100644 --- a/src/utility/power/INA3221_Class.cpp +++ b/src/utility/power/INA3221_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "INA3221_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "INA3221_Class.hpp" diff --git a/src/utility/power/IP5306_Class.cpp b/src/utility/power/IP5306_Class.inl similarity index 97% rename from src/utility/power/IP5306_Class.cpp rename to src/utility/power/IP5306_Class.inl index 8a203792..e90a503b 100644 --- a/src/utility/power/IP5306_Class.cpp +++ b/src/utility/power/IP5306_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "IP5306_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "IP5306_Class.hpp" diff --git a/src/utility/power/M5PM1_Class.cpp b/src/utility/power/M5PM1_Class.inl similarity index 98% rename from src/utility/power/M5PM1_Class.cpp rename to src/utility/power/M5PM1_Class.inl index 76dc2118..b201ba05 100644 --- a/src/utility/power/M5PM1_Class.cpp +++ b/src/utility/power/M5PM1_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "M5PM1_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "M5PM1_Class.hpp" diff --git a/src/utility/rtc/PCF8563_Class.cpp b/src/utility/rtc/PCF8563_Class.inl similarity index 95% rename from src/utility/rtc/PCF8563_Class.cpp rename to src/utility/rtc/PCF8563_Class.inl index 1d6c5595..aa393299 100644 --- a/src/utility/rtc/PCF8563_Class.cpp +++ b/src/utility/rtc/PCF8563_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "PCF8563_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "PCF8563_Class.hpp" @@ -7,17 +10,6 @@ namespace m5 { - static std::uint8_t bcd2ToByte(std::uint8_t value) - { - return ((value >> 4) * 10) + (value & 0x0F); - } - - static std::uint8_t byteToBcd2(std::uint8_t value) - { - std::uint_fast8_t bcdhigh = value / 10; - return (bcdhigh << 4) | (value - (bcdhigh * 10)); - } - bool PCF8563_Class::begin(I2C_Class* i2c) { if (i2c) diff --git a/src/utility/rtc/RTC_Base.hpp b/src/utility/rtc/RTC_Base.hpp index 8391bf74..7bdb7a88 100644 --- a/src/utility/rtc/RTC_Base.hpp +++ b/src/utility/rtc/RTC_Base.hpp @@ -111,6 +111,17 @@ namespace m5 return ((value & 0x0F) <= 9) && ((value >> 4) <= 9); } + static std::uint8_t bcd2ToByte(std::uint8_t value) + { + return ((value >> 4) * 10) + (value & 0x0F); + } + + static std::uint8_t byteToBcd2(std::uint8_t value) + { + std::uint_fast8_t bcdhigh = value / 10; + return (bcdhigh << 4) | (value - (bcdhigh * 10)); + } + /// Range-check decoded values before committing them to the output. static bool validateDateTime(const rtc_date_t* date, const rtc_time_t* time); }; diff --git a/src/utility/rtc/RTC_Base.cpp b/src/utility/rtc/RTC_Base.inl similarity index 89% rename from src/utility/rtc/RTC_Base.cpp rename to src/utility/rtc/RTC_Base.inl index 8985ce86..a3d9ede1 100644 --- a/src/utility/rtc/RTC_Base.cpp +++ b/src/utility/rtc/RTC_Base.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "RTC_Base.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "RTC_Base.hpp" diff --git a/src/utility/rtc/RTC_PowerHub_Class.cpp b/src/utility/rtc/RTC_PowerHub_Class.inl similarity index 95% rename from src/utility/rtc/RTC_PowerHub_Class.cpp rename to src/utility/rtc/RTC_PowerHub_Class.inl index 64f70e45..2731f870 100644 --- a/src/utility/rtc/RTC_PowerHub_Class.cpp +++ b/src/utility/rtc/RTC_PowerHub_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "RTC_PowerHub_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "RTC_PowerHub_Class.hpp" #include @@ -7,12 +10,6 @@ namespace m5 { - static std::uint8_t byteToBcd2(std::uint8_t value) - { - std::uint_fast8_t bcdhigh = value / 10; - return (bcdhigh << 4) | (value - (bcdhigh * 10)); - } - static std::uint8_t weekdayToPowerHub(std::int8_t weekDay) { static constexpr std::uint8_t weekDayTable[] = { 1, 2, 4, 8, 10, 20, 40 }; diff --git a/src/utility/rtc/RX8130_Class.cpp b/src/utility/rtc/RX8130_Class.inl similarity index 97% rename from src/utility/rtc/RX8130_Class.cpp rename to src/utility/rtc/RX8130_Class.inl index 484c471b..8efde73d 100644 --- a/src/utility/rtc/RX8130_Class.cpp +++ b/src/utility/rtc/RX8130_Class.inl @@ -1,5 +1,8 @@ // Copyright (c) M5Stack. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#ifndef M5UNIFIED_IMPLEMENTATION +#error "RX8130_Class.inl is part of M5Unified.cpp and is not meant to be included on its own" +#endif #include "RX8130_Class.hpp" @@ -7,17 +10,6 @@ namespace m5 { - static std::uint8_t bcd2ToByte(std::uint8_t value) - { - return ((value >> 4) * 10) + (value & 0x0F); - } - - static std::uint8_t byteToBcd2(std::uint8_t value) - { - std::uint_fast8_t bcdhigh = value / 10; - return (bcdhigh << 4) | (value - (bcdhigh * 10)); - } - bool RX8130_Class::begin(I2C_Class* i2c) { if (i2c)