├── lib ├── led_strip │ ├── component.mk │ ├── CMakeLists.txt │ ├── README.md │ ├── include │ │ └── led_strip.h │ └── led_strip_rmt_ws2812.c ├── infrared_tools │ ├── component.mk │ ├── CMakeLists.txt │ ├── include │ │ ├── ir_timings.h │ │ └── ir_tools.h │ └── src │ │ ├── ir_parser_rmt_rc5.c │ │ ├── ir_builder_rmt_rc5.c │ │ ├── ir_parser_rmt_nec.c │ │ └── ir_builder_rmt_nec.c └── README ├── .gitignore ├── README.md ├── test └── README ├── platformio.ini ├── LICENSE ├── include └── README └── src └── main.cpp /lib/led_strip/component.mk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | -------------------------------------------------------------------------------- /lib/infrared_tools/component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS := include 2 | 3 | COMPONENT_SRCDIRS := src 4 | -------------------------------------------------------------------------------- /lib/led_strip/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "led_strip_rmt_ws2812.c" 2 | INCLUDE_DIRS "include" 3 | PRIV_REQUIRES "driver" 4 | ) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5AtomS3-UserDemo 2 | M5AtomS3 user demo for hardware evaluation. 3 | 4 | ## Tool Chains 5 | 6 | [PlatformIO](https://platformio.org/) 7 | 8 | ## Build 9 | 10 | - Upload firmware 11 | 12 | -------------------------------------------------------------------------------- /lib/infrared_tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(component_srcs "src/ir_builder_rmt_nec.c" 2 | "src/ir_builder_rmt_rc5.c" 3 | "src/ir_parser_rmt_nec.c" 4 | "src/ir_parser_rmt_rc5.c") 5 | 6 | idf_component_register(SRCS "${component_srcs}" 7 | INCLUDE_DIRS "include" 8 | PRIV_INCLUDE_DIRS "" 9 | PRIV_REQUIRES "driver" 10 | REQUIRES "") 11 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | -------------------------------------------------------------------------------- /lib/led_strip/README.md: -------------------------------------------------------------------------------- 1 | # LED Strip Component 2 | 3 | This directory contains an implementation for addressable LEDs using the RMT peripheral. 4 | 5 | It's compatible with: 6 | 7 | * [WS2812](http://www.world-semi.com/Certifications/WS2812B.html) 8 | * SK68XX 9 | 10 | This component is used as part of the following ESP-IDF examples: 11 | - [Blink Example](../../get-started/blink). 12 | - [LED Strip Example](../../peripherals/rmt/led_strip). 13 | 14 | To learn more about how to use this component, please check API Documentation from header file [led_strip.h](./include/led_strip.h). 15 | 16 | Please note that this component is not considered to be a part of ESP-IDF stable API. It may change and it may be removed in the future releases. 17 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:ATOMS3] 12 | platform = espressif32@5.3.0 13 | framework = arduino 14 | ; platform_packages = platformio/framework-arduinoespressif32@^3.20005.220925 15 | board = esp32-s3-devkitc-1 16 | lib_ldf_mode = deep 17 | monitor_speed = 115200 18 | upload_speed = 1500000 19 | board_build.f_cpu = 240000000L 20 | board_build.f_flash = 80000000L 21 | board_build.flash_mode = dio 22 | build_flags = 23 | -DCORE_DEBUG_LEVEL=0 24 | -Iinclude 25 | lib_deps = 26 | lib/infrared_tools 27 | lib/led_strip 28 | tanakamasayuki/I2C MPU6886 IMU@^1.0.0 29 | arduino-libraries/Madgwick@^1.2.0 30 | m5stack/M5GFX@^0.1.4 31 | m5stack/M5Unified@^0.1.4 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 M5Stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /lib/infrared_tools/include/ir_timings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | /** 22 | * @brief Timings for NEC protocol 23 | * 24 | */ 25 | #define NEC_LEADING_CODE_HIGH_US (9000) 26 | #define NEC_LEADING_CODE_LOW_US (4500) 27 | #define NEC_PAYLOAD_ONE_HIGH_US (560) 28 | #define NEC_PAYLOAD_ONE_LOW_US (1690) 29 | #define NEC_PAYLOAD_ZERO_HIGH_US (560) 30 | #define NEC_PAYLOAD_ZERO_LOW_US (560) 31 | #define NEC_REPEAT_CODE_HIGH_US (9000) 32 | #define NEC_REPEAT_CODE_LOW_US (2250) 33 | #define NEC_ENDING_CODE_HIGH_US (560) 34 | 35 | /** 36 | * @brief Timings for RC5 protocol 37 | * 38 | */ 39 | #define RC5_PULSE_DURATION_US (889) 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/led_strip/include/led_strip.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #pragma once 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include "esp_err.h" 21 | 22 | /** 23 | * @brief LED Strip Type 24 | * 25 | */ 26 | typedef struct led_strip_s led_strip_t; 27 | 28 | /** 29 | * @brief LED Strip Device Type 30 | * 31 | */ 32 | typedef void *led_strip_dev_t; 33 | 34 | /** 35 | * @brief Declare of LED Strip Type 36 | * 37 | */ 38 | struct led_strip_s { 39 | /** 40 | * @brief Set RGB for a specific pixel 41 | * 42 | * @param strip: LED strip 43 | * @param index: index of pixel to set 44 | * @param red: red part of color 45 | * @param green: green part of color 46 | * @param blue: blue part of color 47 | * 48 | * @return 49 | * - ESP_OK: Set RGB for a specific pixel successfully 50 | * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters 51 | * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred 52 | */ 53 | esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); 54 | 55 | /** 56 | * @brief Refresh memory colors to LEDs 57 | * 58 | * @param strip: LED strip 59 | * @param timeout_ms: timeout value for refreshing task 60 | * 61 | * @return 62 | * - ESP_OK: Refresh successfully 63 | * - ESP_ERR_TIMEOUT: Refresh failed because of timeout 64 | * - ESP_FAIL: Refresh failed because some other error occurred 65 | * 66 | * @note: 67 | * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. 68 | */ 69 | esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms); 70 | 71 | /** 72 | * @brief Clear LED strip (turn off all LEDs) 73 | * 74 | * @param strip: LED strip 75 | * @param timeout_ms: timeout value for clearing task 76 | * 77 | * @return 78 | * - ESP_OK: Clear LEDs successfully 79 | * - ESP_ERR_TIMEOUT: Clear LEDs failed because of timeout 80 | * - ESP_FAIL: Clear LEDs failed because some other error occurred 81 | */ 82 | esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms); 83 | 84 | /** 85 | * @brief Free LED strip resources 86 | * 87 | * @param strip: LED strip 88 | * 89 | * @return 90 | * - ESP_OK: Free resources successfully 91 | * - ESP_FAIL: Free resources failed because error occurred 92 | */ 93 | esp_err_t (*del)(led_strip_t *strip); 94 | }; 95 | 96 | /** 97 | * @brief LED Strip Configuration Type 98 | * 99 | */ 100 | typedef struct { 101 | uint32_t max_leds; /*!< Maximum LEDs in a single strip */ 102 | led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */ 103 | } led_strip_config_t; 104 | 105 | /** 106 | * @brief Default configuration for LED strip 107 | * 108 | */ 109 | #define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \ 110 | { \ 111 | .max_leds = number, \ 112 | .dev = dev_hdl, \ 113 | } 114 | 115 | /** 116 | * @brief Install a new ws2812 driver (based on RMT peripheral) 117 | * 118 | * @param config: LED strip configuration 119 | * @return 120 | * LED strip instance or NULL 121 | */ 122 | led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config); 123 | 124 | /** 125 | * @brief Init the RMT peripheral and LED strip configuration. 126 | * 127 | * @param[in] channel: RMT peripheral channel number. 128 | * @param[in] gpio: GPIO number for the RMT data output. 129 | * @param[in] led_num: number of addressable LEDs. 130 | * @return 131 | * LED strip instance or NULL 132 | */ 133 | led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num); 134 | 135 | /** 136 | * @brief Denit the RMT peripheral. 137 | * 138 | * @param[in] strip: LED strip 139 | * @return 140 | * - ESP_OK 141 | * - ESP_FAIL 142 | */ 143 | esp_err_t led_strip_denit(led_strip_t *strip); 144 | 145 | #ifdef __cplusplus 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /lib/infrared_tools/src/ir_parser_rmt_rc5.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include "esp_log.h" 17 | #include "ir_tools.h" 18 | #include "ir_timings.h" 19 | #include "driver/rmt.h" 20 | 21 | static const char *TAG = "rc5_parser"; 22 | #define RC5_CHECK(a, str, goto_tag, ret_value, ...) \ 23 | do \ 24 | { \ 25 | if (!(a)) \ 26 | { \ 27 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 28 | ret = ret_value; \ 29 | goto goto_tag; \ 30 | } \ 31 | } while (0) 32 | 33 | #define RC5_MAX_FRAME_RMT_WORDS (14) // S1+S2+T+ADDR(5)+CMD(6) 34 | 35 | typedef struct { 36 | ir_parser_t parent; 37 | uint32_t flags; 38 | uint32_t pulse_duration_ticks; 39 | uint32_t margin_ticks; 40 | rmt_item32_t *buffer; 41 | uint32_t buffer_len; 42 | uint32_t last_command; 43 | uint32_t last_address; 44 | bool last_t_bit; 45 | } rc5_parser_t; 46 | 47 | static inline bool rc5_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) 48 | { 49 | return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks)); 50 | } 51 | 52 | static esp_err_t rc5_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) 53 | { 54 | esp_err_t ret = ESP_OK; 55 | rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent); 56 | rc5_parser->buffer = raw_data; 57 | rc5_parser->buffer_len = length; 58 | if (length > RC5_MAX_FRAME_RMT_WORDS) { 59 | ret = ESP_FAIL; 60 | } 61 | return ret; 62 | } 63 | 64 | static inline bool rc5_duration_one_unit(rc5_parser_t *rc5_parser, uint32_t duration) 65 | { 66 | return (duration < (rc5_parser->pulse_duration_ticks + rc5_parser->margin_ticks)) && 67 | (duration > (rc5_parser->pulse_duration_ticks - rc5_parser->margin_ticks)); 68 | } 69 | 70 | static inline bool rc5_duration_two_unit(rc5_parser_t *rc5_parser, uint32_t duration) 71 | { 72 | return (duration < (rc5_parser->pulse_duration_ticks * 2 + rc5_parser->margin_ticks)) && 73 | (duration > (rc5_parser->pulse_duration_ticks * 2 - rc5_parser->margin_ticks)); 74 | } 75 | 76 | static esp_err_t rc5_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) 77 | { 78 | esp_err_t ret = ESP_FAIL; 79 | uint32_t parse_result = 0; // 32 bit is enough to hold the parse result of one RC5 frame 80 | uint32_t addr = 0; 81 | uint32_t cmd = 0; 82 | bool s1 = true; 83 | bool s2 = true; 84 | bool t = false; 85 | bool exchange = false; 86 | rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent); 87 | RC5_CHECK(address && command && repeat, "address, command and repeat can't be null", out, ESP_ERR_INVALID_ARG); 88 | for (int i = 0; i < rc5_parser->buffer_len; i++) { 89 | if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration0)) { 90 | parse_result <<= 1; 91 | parse_result |= exchange; 92 | if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration1)) { 93 | exchange = !exchange; 94 | } 95 | } else if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration0)) { 96 | parse_result <<= 1; 97 | parse_result |= rc5_parser->buffer[i].level0; 98 | parse_result <<= 1; 99 | parse_result |= !rc5_parser->buffer[i].level0; 100 | if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration1)) { 101 | exchange = !exchange; 102 | } 103 | } else { 104 | goto out; 105 | } 106 | } 107 | if (!(rc5_parser->flags & IR_TOOLS_FLAGS_INVERSE)) { 108 | parse_result = ~parse_result; 109 | } 110 | s1 = ((parse_result & 0x2000) >> 13) & 0x01; 111 | s2 = ((parse_result & 0x1000) >> 12) & 0x01; 112 | t = ((parse_result & 0x800) >> 11) & 0x01; 113 | // Check S1, must be 1 114 | if (s1) { 115 | if (!(rc5_parser->flags & IR_TOOLS_FLAGS_PROTO_EXT) && !s2) { 116 | // Not standard RC5 protocol, but S2 is 0 117 | goto out; 118 | } 119 | addr = (parse_result & 0x7C0) >> 6; 120 | cmd = (parse_result & 0x3F); 121 | if (!s2) { 122 | cmd |= 1 << 6; 123 | } 124 | *repeat = (t == rc5_parser->last_t_bit && addr == rc5_parser->last_address && cmd == rc5_parser->last_command); 125 | *address = addr; 126 | *command = cmd; 127 | rc5_parser->last_address = addr; 128 | rc5_parser->last_command = cmd; 129 | rc5_parser->last_t_bit = t; 130 | ret = ESP_OK; 131 | } 132 | out: 133 | return ret; 134 | } 135 | 136 | static esp_err_t rc5_parser_del(ir_parser_t *parser) 137 | { 138 | rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent); 139 | free(rc5_parser); 140 | return ESP_OK; 141 | } 142 | 143 | ir_parser_t *ir_parser_rmt_new_rc5(const ir_parser_config_t *config) 144 | { 145 | ir_parser_t *ret = NULL; 146 | RC5_CHECK(config, "rc5 configuration can't be null", err, NULL); 147 | 148 | rc5_parser_t *rc5_parser = calloc(1, sizeof(rc5_parser_t)); 149 | RC5_CHECK(rc5_parser, "request memory for rc5_parser failed", err, NULL); 150 | 151 | rc5_parser->flags = config->flags; 152 | 153 | uint32_t counter_clk_hz = 0; 154 | RC5_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK, 155 | "get rmt counter clock failed", err, NULL); 156 | float ratio = (float)counter_clk_hz / 1e6; 157 | rc5_parser->pulse_duration_ticks = (uint32_t)(ratio * RC5_PULSE_DURATION_US); 158 | rc5_parser->margin_ticks = (uint32_t)(ratio * config->margin_us); 159 | rc5_parser->parent.input = rc5_parser_input; 160 | rc5_parser->parent.get_scan_code = rc5_parser_get_scan_code; 161 | rc5_parser->parent.del = rc5_parser_del; 162 | return &rc5_parser->parent; 163 | err: 164 | return ret; 165 | } 166 | -------------------------------------------------------------------------------- /lib/led_strip/led_strip_rmt_ws2812.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include 17 | #include "esp_log.h" 18 | #include "esp_attr.h" 19 | #include "led_strip.h" 20 | #include "driver/rmt.h" 21 | 22 | static const char *TAG = "ws2812"; 23 | #define STRIP_CHECK(a, str, goto_tag, ret_value, ...) \ 24 | do \ 25 | { \ 26 | if (!(a)) \ 27 | { \ 28 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 29 | ret = ret_value; \ 30 | goto goto_tag; \ 31 | } \ 32 | } while (0) 33 | 34 | #define WS2812_T0H_NS (350) 35 | #define WS2812_T0L_NS (800) 36 | #define WS2812_T1H_NS (600) 37 | #define WS2812_T1L_NS (700) 38 | #define WS2812_RESET_US (80000) 39 | 40 | // #define WS2812_T0H_NS (350) 41 | // #define WS2812_T0L_NS (1000) 42 | // #define WS2812_T1H_NS (1000) 43 | // #define WS2812_T1L_NS (350) 44 | // #define WS2812_RESET_US (280) 45 | 46 | static uint32_t ws2812_t0h_ticks = 0; 47 | static uint32_t ws2812_t1h_ticks = 0; 48 | static uint32_t ws2812_t0l_ticks = 0; 49 | static uint32_t ws2812_t1l_ticks = 0; 50 | 51 | typedef struct { 52 | led_strip_t parent; 53 | rmt_channel_t rmt_channel; 54 | uint32_t strip_len; 55 | uint8_t buffer[0]; 56 | } ws2812_t; 57 | 58 | /** 59 | * @brief Conver RGB data to RMT format. 60 | * 61 | * @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t) 62 | * 63 | * @param[in] src: source data, to converted to RMT format 64 | * @param[in] dest: place where to store the convert result 65 | * @param[in] src_size: size of source data 66 | * @param[in] wanted_num: number of RMT items that want to get 67 | * @param[out] translated_size: number of source data that got converted 68 | * @param[out] item_num: number of RMT items which are converted from source data 69 | */ 70 | static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, 71 | size_t wanted_num, size_t *translated_size, size_t *item_num) 72 | { 73 | if (src == NULL || dest == NULL) { 74 | *translated_size = 0; 75 | *item_num = 0; 76 | return; 77 | } 78 | const rmt_item32_t bit0 = {{{ ws2812_t0h_ticks, 1, ws2812_t0l_ticks, 0 }}}; //Logical 0 79 | const rmt_item32_t bit1 = {{{ ws2812_t1h_ticks, 1, ws2812_t1l_ticks, 0 }}}; //Logical 1 80 | size_t size = 0; 81 | size_t num = 0; 82 | uint8_t *psrc = (uint8_t *)src; 83 | rmt_item32_t *pdest = dest; 84 | while (size < src_size && num < wanted_num) { 85 | for (int i = 0; i < 8; i++) { 86 | // MSB first 87 | if (*psrc & (1 << (7 - i))) { 88 | pdest->val = bit1.val; 89 | } else { 90 | pdest->val = bit0.val; 91 | } 92 | num++; 93 | pdest++; 94 | } 95 | size++; 96 | psrc++; 97 | } 98 | *translated_size = size; 99 | *item_num = num; 100 | } 101 | 102 | static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) 103 | { 104 | esp_err_t ret = ESP_OK; 105 | ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); 106 | STRIP_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG); 107 | uint32_t start = index * 3; 108 | // In thr order of GRB 109 | ws2812->buffer[start + 0] = green & 0xFF; 110 | ws2812->buffer[start + 1] = red & 0xFF; 111 | ws2812->buffer[start + 2] = blue & 0xFF; 112 | return ESP_OK; 113 | err: 114 | return ret; 115 | } 116 | 117 | static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms) 118 | { 119 | esp_err_t ret = ESP_OK; 120 | ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); 121 | STRIP_CHECK(rmt_write_sample(ws2812->rmt_channel, ws2812->buffer, ws2812->strip_len * 3, true) == ESP_OK, 122 | "transmit RMT samples failed", err, ESP_FAIL); 123 | return rmt_wait_tx_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms)); 124 | err: 125 | return ret; 126 | } 127 | 128 | static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms) 129 | { 130 | ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); 131 | // Write zero to turn off all leds 132 | memset(ws2812->buffer, 0, ws2812->strip_len * 3); 133 | return ws2812_refresh(strip, timeout_ms); 134 | } 135 | 136 | static esp_err_t ws2812_del(led_strip_t *strip) 137 | { 138 | ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); 139 | free(ws2812); 140 | return ESP_OK; 141 | } 142 | 143 | led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) 144 | { 145 | led_strip_t *ret = NULL; 146 | STRIP_CHECK(config, "configuration can't be null", err, NULL); 147 | 148 | // 24 bits per led 149 | uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3; 150 | ws2812_t *ws2812 = calloc(1, ws2812_size); 151 | STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL); 152 | 153 | uint32_t counter_clk_hz = 0; 154 | STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK, 155 | "get rmt counter clock failed", err, NULL); 156 | // ns -> ticks 157 | float ratio = (float)counter_clk_hz / 1e9; 158 | ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); 159 | ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); 160 | ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); 161 | ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); 162 | 163 | // set ws2812 to rmt adapter 164 | rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter); 165 | 166 | ws2812->rmt_channel = (rmt_channel_t)config->dev; 167 | ws2812->strip_len = config->max_leds; 168 | 169 | ws2812->parent.set_pixel = ws2812_set_pixel; 170 | ws2812->parent.refresh = ws2812_refresh; 171 | ws2812->parent.clear = ws2812_clear; 172 | ws2812->parent.del = ws2812_del; 173 | 174 | return &ws2812->parent; 175 | err: 176 | return ret; 177 | } 178 | -------------------------------------------------------------------------------- /lib/infrared_tools/src/ir_builder_rmt_rc5.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include "esp_log.h" 17 | #include "ir_tools.h" 18 | #include "ir_timings.h" 19 | #include "driver/rmt.h" 20 | 21 | static const char *TAG = "rc5_builder"; 22 | #define RC5_CHECK(a, str, goto_tag, ret_value, ...) \ 23 | do \ 24 | { \ 25 | if (!(a)) \ 26 | { \ 27 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 28 | ret = ret_value; \ 29 | goto goto_tag; \ 30 | } \ 31 | } while (0) 32 | 33 | typedef struct { 34 | ir_builder_t parent; 35 | uint32_t buffer_size; 36 | uint32_t cursor; 37 | uint32_t pulse_duration_ticks; 38 | uint32_t flags; 39 | bool toggle; 40 | bool s2_bit; 41 | bool inverse; 42 | rmt_item32_t buffer[0]; 43 | } rc5_builder_t; 44 | 45 | static esp_err_t rc5_builder_make_head(ir_builder_t *builder) 46 | { 47 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 48 | rc5_builder->cursor = 0; 49 | rc5_builder->toggle = !rc5_builder->toggle; 50 | // S1 default (not inverse) is 0 51 | rc5_builder->buffer[rc5_builder->cursor].level0 = rc5_builder->inverse; 52 | rc5_builder->buffer[rc5_builder->cursor].duration0 = rc5_builder->pulse_duration_ticks; 53 | rc5_builder->buffer[rc5_builder->cursor].level1 = !rc5_builder->inverse; 54 | rc5_builder->buffer[rc5_builder->cursor].duration1 = rc5_builder->pulse_duration_ticks; 55 | rc5_builder->cursor += 1; 56 | // S2 default (not inverse) is depend on whether use extended protocol 57 | rc5_builder->buffer[rc5_builder->cursor].level0 = rc5_builder->s2_bit ^ rc5_builder->inverse; 58 | rc5_builder->buffer[rc5_builder->cursor].duration0 = rc5_builder->pulse_duration_ticks; 59 | rc5_builder->buffer[rc5_builder->cursor].level1 = !(rc5_builder->s2_bit ^ rc5_builder->inverse); 60 | rc5_builder->buffer[rc5_builder->cursor].duration1 = rc5_builder->pulse_duration_ticks; 61 | rc5_builder->cursor += 1; 62 | // T 63 | rc5_builder->buffer[rc5_builder->cursor].level0 = rc5_builder->toggle; 64 | rc5_builder->buffer[rc5_builder->cursor].duration0 = rc5_builder->pulse_duration_ticks; 65 | rc5_builder->buffer[rc5_builder->cursor].level1 = !rc5_builder->toggle; 66 | rc5_builder->buffer[rc5_builder->cursor].duration1 = rc5_builder->pulse_duration_ticks; 67 | rc5_builder->cursor += 1; 68 | return ESP_OK; 69 | } 70 | 71 | static esp_err_t rc5_builder_make_logic0(ir_builder_t *builder) 72 | { 73 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 74 | rc5_builder->buffer[rc5_builder->cursor].level0 = !rc5_builder->inverse; 75 | rc5_builder->buffer[rc5_builder->cursor].duration0 = rc5_builder->pulse_duration_ticks; 76 | rc5_builder->buffer[rc5_builder->cursor].level1 = rc5_builder->inverse; 77 | rc5_builder->buffer[rc5_builder->cursor].duration1 = rc5_builder->pulse_duration_ticks; 78 | rc5_builder->cursor += 1; 79 | return ESP_OK; 80 | } 81 | 82 | static esp_err_t rc5_builder_make_logic1(ir_builder_t *builder) 83 | { 84 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 85 | rc5_builder->buffer[rc5_builder->cursor].level0 = rc5_builder->inverse; 86 | rc5_builder->buffer[rc5_builder->cursor].duration0 = rc5_builder->pulse_duration_ticks; 87 | rc5_builder->buffer[rc5_builder->cursor].level1 = !rc5_builder->inverse; 88 | rc5_builder->buffer[rc5_builder->cursor].duration1 = rc5_builder->pulse_duration_ticks; 89 | rc5_builder->cursor += 1; 90 | return ESP_OK; 91 | } 92 | 93 | static esp_err_t rc5_builder_make_end(ir_builder_t *builder) 94 | { 95 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 96 | rc5_builder->buffer[rc5_builder->cursor].val = 0; 97 | rc5_builder->cursor += 1; 98 | return ESP_OK; 99 | } 100 | 101 | static esp_err_t rc5_build_frame(ir_builder_t *builder, uint32_t address, uint32_t command) 102 | { 103 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 104 | if (rc5_builder->flags & IR_TOOLS_FLAGS_PROTO_EXT) { 105 | // RC5-extended protocol uses S2 bit as a 7th command bit (MSB of a command) 106 | if (command > 63) { 107 | rc5_builder->s2_bit = true; 108 | } else { 109 | rc5_builder->s2_bit = false; 110 | } 111 | } 112 | builder->make_head(builder); 113 | // MSB -> LSB 114 | for (int i = 4; i >= 0; i--) { 115 | if (address & (1 << i)) { 116 | builder->make_logic1(builder); 117 | } else { 118 | builder->make_logic0(builder); 119 | } 120 | } 121 | for (int i = 5; i >= 0; i--) { 122 | if (command & (1 << i)) { 123 | builder->make_logic1(builder); 124 | } else { 125 | builder->make_logic0(builder); 126 | } 127 | } 128 | builder->make_end(builder); 129 | return ESP_OK; 130 | } 131 | 132 | static esp_err_t rc5_build_repeat_frame(ir_builder_t *builder) 133 | { 134 | // repeat frame is just the latest build frame, so do nothing here 135 | return ESP_OK; 136 | } 137 | 138 | static esp_err_t rc5_builder_get_result(ir_builder_t *builder, void *result, size_t *length) 139 | { 140 | esp_err_t ret = ESP_OK; 141 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 142 | RC5_CHECK(result && length, "result and length can't be null", err, ESP_ERR_INVALID_ARG); 143 | *(rmt_item32_t **)result = rc5_builder->buffer; 144 | *length = rc5_builder->cursor; 145 | return ESP_OK; 146 | err: 147 | return ret; 148 | } 149 | 150 | static esp_err_t rc5_builder_del(ir_builder_t *builder) 151 | { 152 | rc5_builder_t *rc5_builder = __containerof(builder, rc5_builder_t, parent); 153 | free(rc5_builder); 154 | return ESP_OK; 155 | } 156 | 157 | ir_builder_t *ir_builder_rmt_new_rc5(const ir_builder_config_t *config) 158 | { 159 | ir_builder_t *ret = NULL; 160 | RC5_CHECK(config, "rc5 configuration can't be null", err, NULL); 161 | RC5_CHECK(config->buffer_size, "buffer size can't be zero", err, NULL); 162 | 163 | uint32_t builder_size = sizeof(rc5_builder_t) + config->buffer_size * sizeof(rmt_item32_t); 164 | rc5_builder_t *rc5_builder = calloc(1, builder_size); 165 | RC5_CHECK(rc5_builder, "request memory for rc5_builder failed", err, NULL); 166 | 167 | rc5_builder->buffer_size = config->buffer_size; 168 | rc5_builder->flags = config->flags; 169 | if (config->flags & IR_TOOLS_FLAGS_INVERSE) { 170 | rc5_builder->inverse = true; 171 | } 172 | 173 | uint32_t counter_clk_hz = 0; 174 | RC5_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK, 175 | "get rmt counter clock failed", err, NULL); 176 | float ratio = (float)counter_clk_hz / 1e6; 177 | rc5_builder->pulse_duration_ticks = (uint32_t)(ratio * RC5_PULSE_DURATION_US); 178 | rc5_builder->parent.make_head = rc5_builder_make_head; 179 | rc5_builder->parent.make_logic0 = rc5_builder_make_logic0; 180 | rc5_builder->parent.make_logic1 = rc5_builder_make_logic1; 181 | rc5_builder->parent.make_end = rc5_builder_make_end; 182 | rc5_builder->parent.build_frame = rc5_build_frame; 183 | rc5_builder->parent.build_repeat_frame = rc5_build_repeat_frame; 184 | rc5_builder->parent.get_result = rc5_builder_get_result; 185 | rc5_builder->parent.del = rc5_builder_del; 186 | rc5_builder->parent.repeat_period_ms = 114; 187 | return &rc5_builder->parent; 188 | err: 189 | return ret; 190 | } 191 | -------------------------------------------------------------------------------- /lib/infrared_tools/include/ir_tools.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include "esp_err.h" 22 | 23 | #define IR_TOOLS_FLAGS_PROTO_EXT (1 << 0) /*!< Enable Extended IR protocol */ 24 | #define IR_TOOLS_FLAGS_INVERSE (1 << 1) /*!< Inverse the IR signal, i.e. take high level as low, and vice versa */ 25 | 26 | /** 27 | * @brief IR device type 28 | * 29 | */ 30 | typedef void *ir_dev_t; 31 | 32 | /** 33 | * @brief IR builder type 34 | * 35 | */ 36 | typedef struct ir_builder_s ir_builder_t; 37 | 38 | /** 39 | * @brief IR parser type 40 | * 41 | */ 42 | typedef struct ir_parser_s ir_parser_t; 43 | 44 | /** 45 | * @brief Type definition of IR builder 46 | * 47 | */ 48 | struct ir_builder_s { 49 | /** 50 | * @brief Period time of sending repeat code 51 | * 52 | */ 53 | uint32_t repeat_period_ms; 54 | 55 | /** 56 | * @brief Build frame header 57 | * 58 | * @param[in] builder: Handle of IR builder 59 | * 60 | * @return 61 | * - ESP_OK: Build frame header successfully 62 | * - ESP_FAIL: Build frame header failed because some error occurred 63 | */ 64 | esp_err_t (*make_head)(ir_builder_t *builder); 65 | 66 | /** 67 | * @brief Build logic bit zero 68 | * 69 | * @param[in] builder: Handle of IR builder 70 | * 71 | * @return 72 | * - ESP_OK: Build logic bit zero successfully 73 | * - ESP_FAIL: Build logic bit zero failed because some error occurred 74 | */ 75 | esp_err_t (*make_logic0)(ir_builder_t *builder); 76 | 77 | /** 78 | * @brief Build logic bit one 79 | * 80 | * @param[in] builder: Handle of IR builder 81 | * 82 | * @return 83 | * ESP_OK: Build logic bit one successfully 84 | * ESP_FAIL: Build logic bit one failed because some error occurred 85 | */ 86 | esp_err_t (*make_logic1)(ir_builder_t *builder); 87 | 88 | /** 89 | * @brief Build frame tail 90 | * 91 | * @param[in] builder: Handle of IR builder 92 | * 93 | * @return 94 | * - ESP_OK: Build frame tail successfully 95 | * - ESP_FAIL: Build frame tail failed because some error occurred 96 | */ 97 | esp_err_t (*make_end)(ir_builder_t *builder); 98 | 99 | /** 100 | * @brief Build a complete frame 101 | * 102 | * @param[in] builder: Handle of IR builder 103 | * 104 | * @return 105 | * - ESP_OK: Build a complete frame successfully 106 | * - ESP_FAIL: Build a complete frame failed because some error occurred 107 | */ 108 | esp_err_t (*build_frame)(ir_builder_t *builder, uint32_t address, uint32_t command); 109 | 110 | /** 111 | * @brief Build a repeat frame 112 | * 113 | * @param[in] builder: Handle of IR builder 114 | * 115 | * @return 116 | * - ESP_OK: Build a repeat frame successfully 117 | * - ESP_FAIL: Build a repeat frame failed because some error occurred 118 | */ 119 | esp_err_t (*build_repeat_frame)(ir_builder_t *builder); 120 | 121 | /** 122 | * @brief Get the result frame after a series of building steps 123 | * 124 | * @param[in] builder: Handle of IR builder 125 | * @param[out] result: Result of frame building, which contains all of the raw data that could be send directly 126 | * @param[out] length: Length of result data 127 | * 128 | * @return 129 | * - ESP_OK: Get result data successfully 130 | * - ESP_ERR_INVALID_ARG: Get result data failed because of invalid arguments 131 | * - ESP_FAIL: Get result data failed because some other errors occurred 132 | */ 133 | esp_err_t (*get_result)(ir_builder_t *builder, void *result, size_t *length); 134 | 135 | /** 136 | * @brief Free resources used by IR builder 137 | * 138 | * @param[in] builder: Handle of IR builder 139 | * 140 | * @return 141 | * - ESP_OK: Free resources successfully 142 | * - ESP_FAIL: Free resources failed because some error occurred 143 | */ 144 | esp_err_t (*del)(ir_builder_t *builder); 145 | }; 146 | 147 | /** 148 | * @brief Type definition of IR parser 149 | * 150 | */ 151 | struct ir_parser_s { 152 | /** 153 | * @brief Input raw data to IR parser 154 | * 155 | * @param[in] parser: Handle of IR parser 156 | * @param[in] raw_data: Raw data which need decoding by IR parser 157 | * @param[in] length: Length of raw data 158 | * 159 | * @return 160 | * - ESP_OK: Input raw data successfully 161 | * - ESP_ERR_INVALID_ARG: Input raw data failed because of invalid argument 162 | * - ESP_FAIL: Input raw data failed because some other error occurred 163 | */ 164 | esp_err_t (*input)(ir_parser_t *parser, void *raw_data, uint32_t length); 165 | 166 | /** 167 | * @brief Get the scan code after decoding of raw data 168 | * 169 | * @param[in] parser: Handle of IR parser 170 | * @param[out] address: Address of the scan code 171 | * @param[out] command: Command of the scan code 172 | * @param[out] repeat: Indicate if it's a repeat code 173 | * 174 | * @return 175 | * - ESP_OK: Get scan code successfully 176 | * - ESP_ERR_INVALID_ARG: Get scan code failed because of invalid arguments 177 | * - ESP_FAIL: Get scan code failed because some error occurred 178 | */ 179 | esp_err_t (*get_scan_code)(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat); 180 | 181 | /** 182 | * @brief Free resources used by IR parser 183 | * 184 | * @param[in] parser: Handle of IR parser 185 | * 186 | * @return 187 | * - ESP_OK: Free resource successfully 188 | * - ESP_FAIL: Free resources fail failed because some error occurred 189 | */ 190 | esp_err_t (*del)(ir_parser_t *parser); 191 | }; 192 | 193 | /** 194 | * @brief Configuration type of IR builder 195 | * 196 | */ 197 | typedef struct { 198 | uint32_t buffer_size; /*!< Size of the internal buffer used by IR builder */ 199 | ir_dev_t dev_hdl; /*!< IR device handle */ 200 | uint32_t flags; /*!< Flags for IR builder, different flags will enable different features */ 201 | } ir_builder_config_t; 202 | 203 | /** 204 | * @brief Configuration type of IR parser 205 | * 206 | */ 207 | typedef struct { 208 | ir_dev_t dev_hdl; /*!< IR device handle */ 209 | uint32_t flags; /*!< Flags for IR parser, different flags will enable different features */ 210 | uint32_t margin_us; /*!< Timing parameter, indicating the tolerance to environment noise */ 211 | } ir_parser_config_t; 212 | 213 | /** 214 | * @brief Default configuration for IR builder 215 | * 216 | */ 217 | #define IR_BUILDER_DEFAULT_CONFIG(dev) \ 218 | { \ 219 | .buffer_size = 64, \ 220 | .dev_hdl = dev, \ 221 | .flags = 0, \ 222 | } 223 | 224 | /** 225 | * @brief Default configuration for IR parser 226 | * 227 | */ 228 | #define IR_PARSER_DEFAULT_CONFIG(dev) \ 229 | { \ 230 | .dev_hdl = dev, \ 231 | .flags = 0, \ 232 | .margin_us = 200, \ 233 | } 234 | 235 | /** 236 | * @brief Creat a NEC protocol builder 237 | * 238 | * @param config: configuration of NEC builder 239 | * @return 240 | * Handle of NEC builder or NULL 241 | */ 242 | ir_builder_t *ir_builder_rmt_new_nec(const ir_builder_config_t *config); 243 | 244 | /** 245 | * @brief Creat a RC5 protocol builder 246 | * 247 | * @param config: configuration of RC5 builder 248 | * @return 249 | * Handle of RC5 builder or NULL 250 | */ 251 | ir_builder_t *ir_builder_rmt_new_rc5(const ir_builder_config_t *config); 252 | 253 | /** 254 | * @brief Creat a NEC protocol parser 255 | * 256 | * @param config: configuration of NEC parser 257 | * @return 258 | * Handle of NEC parser or NULL 259 | */ 260 | ir_parser_t *ir_parser_rmt_new_nec(const ir_parser_config_t *config); 261 | 262 | /** 263 | * @brief Creat a RC5 protocol parser 264 | * 265 | * @param config: configuration of RC5 parser 266 | * @return 267 | * Handle of RC5 parser or NULL 268 | */ 269 | ir_parser_t *ir_parser_rmt_new_rc5(const ir_parser_config_t *config); 270 | #ifdef __cplusplus 271 | } 272 | #endif 273 | -------------------------------------------------------------------------------- /lib/infrared_tools/src/ir_parser_rmt_nec.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include 15 | #include 16 | #include "esp_log.h" 17 | #include "ir_tools.h" 18 | #include "ir_timings.h" 19 | #include "driver/rmt.h" 20 | 21 | static const char *TAG = "nec_parser"; 22 | #define NEC_CHECK(a, str, goto_tag, ret_value, ...) \ 23 | do \ 24 | { \ 25 | if (!(a)) \ 26 | { \ 27 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 28 | ret = ret_value; \ 29 | goto goto_tag; \ 30 | } \ 31 | } while (0) 32 | 33 | #define NEC_DATA_FRAME_RMT_WORDS (34) 34 | #define NEC_REPEAT_FRAME_RMT_WORDS (2) 35 | 36 | typedef struct { 37 | ir_parser_t parent; 38 | uint32_t flags; 39 | uint32_t leading_code_high_ticks; 40 | uint32_t leading_code_low_ticks; 41 | uint32_t repeat_code_high_ticks; 42 | uint32_t repeat_code_low_ticks; 43 | uint32_t payload_logic0_high_ticks; 44 | uint32_t payload_logic0_low_ticks; 45 | uint32_t payload_logic1_high_ticks; 46 | uint32_t payload_logic1_low_ticks; 47 | uint32_t margin_ticks; 48 | rmt_item32_t *buffer; 49 | uint32_t cursor; 50 | uint32_t last_address; 51 | uint32_t last_command; 52 | bool repeat; 53 | bool inverse; 54 | } nec_parser_t; 55 | 56 | static inline bool nec_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) 57 | { 58 | return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks)); 59 | } 60 | 61 | static bool nec_parse_head(nec_parser_t *nec_parser) 62 | { 63 | nec_parser->cursor = 0; 64 | rmt_item32_t item = nec_parser->buffer[nec_parser->cursor]; 65 | bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) && 66 | nec_check_in_range(item.duration0, nec_parser->leading_code_high_ticks, nec_parser->margin_ticks) && 67 | nec_check_in_range(item.duration1, nec_parser->leading_code_low_ticks, nec_parser->margin_ticks); 68 | nec_parser->cursor += 1; 69 | return ret; 70 | } 71 | 72 | static bool nec_parse_logic0(nec_parser_t *nec_parser) 73 | { 74 | rmt_item32_t item = nec_parser->buffer[nec_parser->cursor]; 75 | bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) && 76 | nec_check_in_range(item.duration0, nec_parser->payload_logic0_high_ticks, nec_parser->margin_ticks) && 77 | nec_check_in_range(item.duration1, nec_parser->payload_logic0_low_ticks, nec_parser->margin_ticks); 78 | return ret; 79 | } 80 | 81 | static bool nec_parse_logic1(nec_parser_t *nec_parser) 82 | { 83 | rmt_item32_t item = nec_parser->buffer[nec_parser->cursor]; 84 | bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) && 85 | nec_check_in_range(item.duration0, nec_parser->payload_logic1_high_ticks, nec_parser->margin_ticks) && 86 | nec_check_in_range(item.duration1, nec_parser->payload_logic1_low_ticks, nec_parser->margin_ticks); 87 | return ret; 88 | } 89 | 90 | static esp_err_t nec_parse_logic(ir_parser_t *parser, bool *logic) 91 | { 92 | esp_err_t ret = ESP_FAIL; 93 | bool logic_value = false; 94 | nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent); 95 | if (nec_parse_logic0(nec_parser)) { 96 | logic_value = false; 97 | ret = ESP_OK; 98 | } else if (nec_parse_logic1(nec_parser)) { 99 | logic_value = true; 100 | ret = ESP_OK; 101 | } 102 | if (ret == ESP_OK) { 103 | *logic = logic_value; 104 | } 105 | nec_parser->cursor += 1; 106 | return ret; 107 | } 108 | 109 | static bool nec_parse_repeat_frame(nec_parser_t *nec_parser) 110 | { 111 | nec_parser->cursor = 0; 112 | rmt_item32_t item = nec_parser->buffer[nec_parser->cursor]; 113 | bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) && 114 | nec_check_in_range(item.duration0, nec_parser->repeat_code_high_ticks, nec_parser->margin_ticks) && 115 | nec_check_in_range(item.duration1, nec_parser->repeat_code_low_ticks, nec_parser->margin_ticks); 116 | nec_parser->cursor += 1; 117 | return ret; 118 | } 119 | 120 | static esp_err_t nec_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) 121 | { 122 | esp_err_t ret = ESP_OK; 123 | nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent); 124 | NEC_CHECK(raw_data, "input data can't be null", err, ESP_ERR_INVALID_ARG); 125 | nec_parser->buffer = raw_data; 126 | // Data Frame costs 34 items and Repeat Frame costs 2 items 127 | if (length == NEC_DATA_FRAME_RMT_WORDS) { 128 | nec_parser->repeat = false; 129 | } else if (length == NEC_REPEAT_FRAME_RMT_WORDS) { 130 | nec_parser->repeat = true; 131 | } else { 132 | ret = ESP_FAIL; 133 | } 134 | return ret; 135 | err: 136 | return ret; 137 | } 138 | 139 | static esp_err_t nec_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) 140 | { 141 | esp_err_t ret = ESP_FAIL; 142 | uint32_t addr = 0; 143 | uint32_t cmd = 0; 144 | bool logic_value = false; 145 | nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent); 146 | NEC_CHECK(address && command && repeat, "address, command and repeat can't be null", out, ESP_ERR_INVALID_ARG); 147 | if (nec_parser->repeat) { 148 | if (nec_parse_repeat_frame(nec_parser)) { 149 | *address = nec_parser->last_address; 150 | *command = nec_parser->last_command; 151 | *repeat = true; 152 | ret = ESP_OK; 153 | } 154 | } else { 155 | if (nec_parse_head(nec_parser)) { 156 | for (int i = 0; i < 16; i++) { 157 | if (nec_parse_logic(parser, &logic_value) == ESP_OK) { 158 | addr |= (logic_value << i); 159 | } 160 | } 161 | for (int i = 0; i < 16; i++) { 162 | if (nec_parse_logic(parser, &logic_value) == ESP_OK) { 163 | cmd |= (logic_value << i); 164 | } 165 | } 166 | *address = addr; 167 | *command = cmd; 168 | *repeat = false; 169 | // keep it as potential repeat code 170 | nec_parser->last_address = addr; 171 | nec_parser->last_command = cmd; 172 | ret = ESP_OK; 173 | } 174 | } 175 | out: 176 | return ret; 177 | } 178 | 179 | static esp_err_t nec_parser_del(ir_parser_t *parser) 180 | { 181 | nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent); 182 | free(nec_parser); 183 | return ESP_OK; 184 | } 185 | 186 | ir_parser_t *ir_parser_rmt_new_nec(const ir_parser_config_t *config) 187 | { 188 | ir_parser_t *ret = NULL; 189 | NEC_CHECK(config, "nec configuration can't be null", err, NULL); 190 | 191 | nec_parser_t *nec_parser = calloc(1, sizeof(nec_parser_t)); 192 | NEC_CHECK(nec_parser, "request memory for nec_parser failed", err, NULL); 193 | 194 | nec_parser->flags = config->flags; 195 | if (config->flags & IR_TOOLS_FLAGS_INVERSE) { 196 | nec_parser->inverse = true; 197 | } 198 | 199 | uint32_t counter_clk_hz = 0; 200 | NEC_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK, 201 | "get rmt counter clock failed", err, NULL); 202 | float ratio = (float)counter_clk_hz / 1e6; 203 | nec_parser->leading_code_high_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_HIGH_US); 204 | nec_parser->leading_code_low_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_LOW_US); 205 | nec_parser->repeat_code_high_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_HIGH_US); 206 | nec_parser->repeat_code_low_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_LOW_US); 207 | nec_parser->payload_logic0_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_HIGH_US); 208 | nec_parser->payload_logic0_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_LOW_US); 209 | nec_parser->payload_logic1_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_HIGH_US); 210 | nec_parser->payload_logic1_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_LOW_US); 211 | nec_parser->margin_ticks = (uint32_t)(ratio * config->margin_us); 212 | nec_parser->parent.input = nec_parser_input; 213 | nec_parser->parent.get_scan_code = nec_parser_get_scan_code; 214 | nec_parser->parent.del = nec_parser_del; 215 | return &nec_parser->parent; 216 | err: 217 | return ret; 218 | } 219 | -------------------------------------------------------------------------------- /lib/infrared_tools/src/ir_builder_rmt_nec.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License.#include 14 | #include 15 | #include "esp_log.h" 16 | #include "ir_tools.h" 17 | #include "ir_timings.h" 18 | #include "driver/rmt.h" 19 | 20 | static const char *TAG = "nec_builder"; 21 | #define NEC_CHECK(a, str, goto_tag, ret_value, ...) \ 22 | do \ 23 | { \ 24 | if (!(a)) \ 25 | { \ 26 | ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ 27 | ret = ret_value; \ 28 | goto goto_tag; \ 29 | } \ 30 | } while (0) 31 | 32 | typedef struct { 33 | ir_builder_t parent; 34 | uint32_t buffer_size; 35 | uint32_t cursor; 36 | uint32_t flags; 37 | uint32_t leading_code_high_ticks; 38 | uint32_t leading_code_low_ticks; 39 | uint32_t repeat_code_high_ticks; 40 | uint32_t repeat_code_low_ticks; 41 | uint32_t payload_logic0_high_ticks; 42 | uint32_t payload_logic0_low_ticks; 43 | uint32_t payload_logic1_high_ticks; 44 | uint32_t payload_logic1_low_ticks; 45 | uint32_t ending_code_high_ticks; 46 | uint32_t ending_code_low_ticks; 47 | bool inverse; 48 | rmt_item32_t buffer[0]; 49 | } nec_builder_t; 50 | 51 | static esp_err_t nec_builder_make_head(ir_builder_t *builder) 52 | { 53 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 54 | nec_builder->cursor = 0; 55 | nec_builder->buffer[nec_builder->cursor].level0 = !nec_builder->inverse; 56 | nec_builder->buffer[nec_builder->cursor].duration0 = nec_builder->leading_code_high_ticks; 57 | nec_builder->buffer[nec_builder->cursor].level1 = nec_builder->inverse; 58 | nec_builder->buffer[nec_builder->cursor].duration1 = nec_builder->leading_code_low_ticks; 59 | nec_builder->cursor += 1; 60 | return ESP_OK; 61 | } 62 | 63 | static esp_err_t nec_builder_make_logic0(ir_builder_t *builder) 64 | { 65 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 66 | nec_builder->buffer[nec_builder->cursor].level0 = !nec_builder->inverse; 67 | nec_builder->buffer[nec_builder->cursor].duration0 = nec_builder->payload_logic0_high_ticks; 68 | nec_builder->buffer[nec_builder->cursor].level1 = nec_builder->inverse; 69 | nec_builder->buffer[nec_builder->cursor].duration1 = nec_builder->payload_logic0_low_ticks; 70 | nec_builder->cursor += 1; 71 | return ESP_OK; 72 | } 73 | 74 | static esp_err_t nec_builder_make_logic1(ir_builder_t *builder) 75 | { 76 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 77 | nec_builder->buffer[nec_builder->cursor].level0 = !nec_builder->inverse; 78 | nec_builder->buffer[nec_builder->cursor].duration0 = nec_builder->payload_logic1_high_ticks; 79 | nec_builder->buffer[nec_builder->cursor].level1 = nec_builder->inverse; 80 | nec_builder->buffer[nec_builder->cursor].duration1 = nec_builder->payload_logic1_low_ticks; 81 | nec_builder->cursor += 1; 82 | return ESP_OK; 83 | } 84 | 85 | static esp_err_t nec_builder_make_end(ir_builder_t *builder) 86 | { 87 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 88 | nec_builder->buffer[nec_builder->cursor].level0 = !nec_builder->inverse; 89 | nec_builder->buffer[nec_builder->cursor].duration0 = nec_builder->ending_code_high_ticks; 90 | nec_builder->buffer[nec_builder->cursor].level1 = nec_builder->inverse; 91 | nec_builder->buffer[nec_builder->cursor].duration1 = nec_builder->ending_code_low_ticks; 92 | nec_builder->cursor += 1; 93 | nec_builder->buffer[nec_builder->cursor].val = 0; 94 | nec_builder->cursor += 1; 95 | return ESP_OK; 96 | } 97 | 98 | static esp_err_t nec_build_frame(ir_builder_t *builder, uint32_t address, uint32_t command) 99 | { 100 | esp_err_t ret = ESP_OK; 101 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 102 | if (!nec_builder->flags & IR_TOOLS_FLAGS_PROTO_EXT) { 103 | uint8_t low_byte = address & 0xFF; 104 | uint8_t high_byte = (address >> 8) & 0xFF; 105 | NEC_CHECK(low_byte == (~high_byte & 0xFF), "address not match standard NEC protocol", err, ESP_ERR_INVALID_ARG); 106 | low_byte = command & 0xFF; 107 | high_byte = (command >> 8) & 0xFF; 108 | NEC_CHECK(low_byte == (~high_byte & 0xFF), "command not match standard NEC protocol", err, ESP_ERR_INVALID_ARG); 109 | } 110 | builder->make_head(builder); 111 | // LSB -> MSB 112 | for (int i = 0; i < 16; i++) { 113 | if (address & (1 << i)) { 114 | builder->make_logic1(builder); 115 | } else { 116 | builder->make_logic0(builder); 117 | } 118 | } 119 | for (int i = 0; i < 16; i++) { 120 | if (command & (1 << i)) { 121 | builder->make_logic1(builder); 122 | } else { 123 | builder->make_logic0(builder); 124 | } 125 | } 126 | builder->make_end(builder); 127 | return ESP_OK; 128 | err: 129 | return ret; 130 | } 131 | 132 | static esp_err_t nec_build_repeat_frame(ir_builder_t *builder) 133 | { 134 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 135 | nec_builder->cursor = 0; 136 | nec_builder->buffer[nec_builder->cursor].level0 = !nec_builder->inverse; 137 | nec_builder->buffer[nec_builder->cursor].duration0 = nec_builder->repeat_code_high_ticks; 138 | nec_builder->buffer[nec_builder->cursor].level1 = nec_builder->inverse; 139 | nec_builder->buffer[nec_builder->cursor].duration1 = nec_builder->repeat_code_low_ticks; 140 | nec_builder->cursor += 1; 141 | nec_builder_make_end(builder); 142 | return ESP_OK; 143 | } 144 | 145 | static esp_err_t nec_builder_get_result(ir_builder_t *builder, void *result, size_t *length) 146 | { 147 | esp_err_t ret = ESP_OK; 148 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 149 | NEC_CHECK(result && length, "result and length can't be null", err, ESP_ERR_INVALID_ARG); 150 | *(rmt_item32_t **)result = nec_builder->buffer; 151 | *length = nec_builder->cursor; 152 | return ESP_OK; 153 | err: 154 | return ret; 155 | } 156 | 157 | static esp_err_t nec_builder_del(ir_builder_t *builder) 158 | { 159 | nec_builder_t *nec_builder = __containerof(builder, nec_builder_t, parent); 160 | free(nec_builder); 161 | return ESP_OK; 162 | } 163 | 164 | ir_builder_t *ir_builder_rmt_new_nec(const ir_builder_config_t *config) 165 | { 166 | ir_builder_t *ret = NULL; 167 | NEC_CHECK(config, "nec configuration can't be null", err, NULL); 168 | NEC_CHECK(config->buffer_size, "buffer size can't be zero", err, NULL); 169 | 170 | uint32_t builder_size = sizeof(nec_builder_t) + config->buffer_size * sizeof(rmt_item32_t); 171 | nec_builder_t *nec_builder = calloc(1, builder_size); 172 | NEC_CHECK(nec_builder, "request memory for nec_builder failed", err, NULL); 173 | 174 | nec_builder->buffer_size = config->buffer_size; 175 | nec_builder->flags = config->flags; 176 | if (config->flags & IR_TOOLS_FLAGS_INVERSE) { 177 | nec_builder->inverse = true; 178 | } 179 | 180 | uint32_t counter_clk_hz = 0; 181 | NEC_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK, 182 | "get rmt counter clock failed", err, NULL); 183 | float ratio = (float)counter_clk_hz / 1e6; 184 | nec_builder->leading_code_high_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_HIGH_US); 185 | nec_builder->leading_code_low_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_LOW_US); 186 | nec_builder->repeat_code_high_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_HIGH_US); 187 | nec_builder->repeat_code_low_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_LOW_US); 188 | nec_builder->payload_logic0_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_HIGH_US); 189 | nec_builder->payload_logic0_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_LOW_US); 190 | nec_builder->payload_logic1_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_HIGH_US); 191 | nec_builder->payload_logic1_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_LOW_US); 192 | nec_builder->ending_code_high_ticks = (uint32_t)(ratio * NEC_ENDING_CODE_HIGH_US); 193 | nec_builder->ending_code_low_ticks = 0x7FFF; 194 | nec_builder->parent.make_head = nec_builder_make_head; 195 | nec_builder->parent.make_logic0 = nec_builder_make_logic0; 196 | nec_builder->parent.make_logic1 = nec_builder_make_logic1; 197 | nec_builder->parent.make_end = nec_builder_make_end; 198 | nec_builder->parent.build_frame = nec_build_frame; 199 | nec_builder->parent.build_repeat_frame = nec_build_repeat_frame; 200 | nec_builder->parent.get_result = nec_builder_get_result; 201 | nec_builder->parent.del = nec_builder_del; 202 | nec_builder->parent.repeat_period_ms = 110; 203 | return &nec_builder->parent; 204 | err: 205 | return ret; 206 | } 207 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "M5GFX.h" 10 | #include "M5Unified.h" 11 | #include "I2C_MPU6886.h" 12 | #include "MadgwickAHRS.h" 13 | 14 | #include "img_res.c" 15 | 16 | #define VERSION 0.1 17 | #define LAYOUT_OFFSET_Y 30 18 | 19 | #define IR_RMT_TX_CHANNEL RMT_CHANNEL_2 20 | #define IR_GPIO 4 21 | 22 | typedef enum { 23 | FUNC_WIFI_SCAN, 24 | FUNC_I2C_SCAN, 25 | FUNC_UART_MON, 26 | FUNC_PWM_TEST, 27 | FUNC_ADC_TEST, 28 | FUNC_IR_SEND, 29 | FUNC_IMU_TEST, 30 | FUNC_MAX 31 | } func_index_t; 32 | 33 | static void boot_animation(void); 34 | static void ir_tx_init(void); 35 | static void ir_tx_send(uint32_t ir_cmd); 36 | 37 | static char func_name_text[][16] = { 38 | "func_wifi_scan", "func_i2c_scan", "func_uart_mon", "func_pwm_test", 39 | "func_adc_test", "func_ir_test", "func_imu_test"}; 40 | 41 | const unsigned char* func_img_list[] = { 42 | wifi_scan_img, i2c_scan_img, uart_mon_img, io_pwm_img, 43 | io_adc_img, ir_send_img, imu_test_img, 44 | }; 45 | 46 | I2C_MPU6886 imu(I2C_MPU6886_DEFAULT_ADDRESS, Wire1); 47 | Madgwick filter; 48 | 49 | class func_base_t { 50 | public: 51 | M5Canvas* _canvas; 52 | bool _draw_flag; 53 | bool _btn_clicked; 54 | 55 | public: 56 | void entry(M5Canvas* canvas_) { 57 | _canvas = canvas_; 58 | _canvas->fillRect(0, 0, _canvas->width(), _canvas->height(), TFT_BLACK); 59 | needDraw(); 60 | start(); 61 | }; 62 | 63 | virtual void start(); 64 | virtual void update(bool btn_click); 65 | virtual void stop(); 66 | 67 | void needDraw() { 68 | _draw_flag = true; 69 | } 70 | 71 | virtual void draw() { 72 | if (_draw_flag) { 73 | _draw_flag = false; 74 | _canvas->pushSprite(0, LAYOUT_OFFSET_Y); 75 | } 76 | }; 77 | 78 | void leave(void) { 79 | stop(); 80 | } 81 | }; 82 | 83 | class func_wifi_t : public func_base_t { 84 | time_t last_time_show = 0; 85 | int16_t wifi_scan_count = 0; 86 | int16_t wifi_show_index = 0; 87 | bool wifi_scan_done = false; 88 | 89 | void start() { 90 | wifi_scan_count = 0; 91 | wifi_show_index = 0; 92 | wifi_scan_done = false; 93 | _btn_clicked = false; 94 | 95 | WiFi.mode(WIFI_MODE_STA); 96 | WiFi.scanNetworks(true); 97 | 98 | _canvas->setTextWrap(false); 99 | _canvas->setTextScroll(true); 100 | _canvas->clear(TFT_BLACK); 101 | _canvas->setFont(&fonts::efontCN_16); 102 | _canvas->drawCenterString("Scaning...", _canvas->width() / 2, 103 | _canvas->height() / 2 - 12); 104 | _canvas->setFont(&fonts::efontCN_12); 105 | needDraw(); 106 | } 107 | 108 | void update(bool btn_click) { 109 | if (!wifi_scan_done) { 110 | int16_t result = WiFi.scanComplete(); 111 | if (result == WIFI_SCAN_RUNNING || result == WIFI_SCAN_FAILED) { 112 | return; 113 | } else if (result > 0) { 114 | wifi_scan_done = true; 115 | wifi_scan_count = result; 116 | _canvas->setCursor(0, 0); 117 | _canvas->clear(TFT_BLACK); 118 | } 119 | } else { 120 | if (btn_click) { 121 | _btn_clicked = !_btn_clicked; 122 | } 123 | 124 | if (!_btn_clicked) { 125 | if (millis() - last_time_show > 500) { 126 | if (wifi_show_index < 127 | (wifi_scan_count < 5 ? wifi_scan_count : 5)) { 128 | _canvas->printf("%d. %s %d\r\n", wifi_show_index + 1, 129 | WiFi.SSID(wifi_show_index).c_str(), 130 | WiFi.RSSI(wifi_show_index)); 131 | wifi_show_index++; 132 | } else { 133 | _canvas->printf("\r\n\r\n"); 134 | _canvas->setFont(&fonts::efontCN_14); 135 | _canvas->printf("Top 5 list:\r\n"); 136 | _canvas->setFont(&fonts::efontCN_12); 137 | wifi_show_index = 0; 138 | } 139 | last_time_show = millis(); 140 | needDraw(); 141 | } 142 | } else { 143 | // update pause 144 | } 145 | } 146 | } 147 | 148 | void stop() { 149 | WiFi.scanDelete(); 150 | _canvas->setFont(&fonts::Font0); 151 | } 152 | }; 153 | 154 | class func_i2c_t : public func_base_t { 155 | uint8_t addr_list[6] = {}; 156 | uint8_t device_count = 0; 157 | 158 | void start() { 159 | Wire.begin(2, 1); 160 | 161 | _canvas->setTextWrap(false); 162 | _canvas->setTextScroll(true); 163 | _canvas->clear(TFT_BLACK); 164 | _canvas->setFont(&fonts::efontCN_16); 165 | _canvas->drawCenterString("Scaning...", _canvas->width() / 2, 166 | _canvas->height() / 2 - 12); 167 | needDraw(); 168 | _btn_clicked = false; 169 | } 170 | 171 | void update(bool btn_click) { 172 | if (btn_click) { 173 | _btn_clicked = !_btn_clicked; 174 | _canvas->clear(TFT_BLACK); 175 | _canvas->setTextSize(1); 176 | _canvas->setTextColor(TFT_WHITE); 177 | _canvas->setFont(&fonts::efontCN_16); 178 | _canvas->drawCenterString("Scaning...", _canvas->width() / 2, 179 | _canvas->height() / 2 - 12); 180 | needDraw(); 181 | draw(); 182 | } 183 | 184 | if (!_btn_clicked) { 185 | _btn_clicked = true; 186 | needDraw(); 187 | 188 | uint8_t address = 0; 189 | device_count = 0; 190 | memset(addr_list, sizeof(addr_list), 0); 191 | for (address = 1; address < 127; address++) { 192 | Wire.beginTransmission(address); 193 | uint8_t error = Wire.endTransmission(); 194 | if (error == 0) { 195 | addr_list[device_count] = address; 196 | device_count++; 197 | 198 | if (device_count > 5) { 199 | break; 200 | } 201 | } 202 | } 203 | _canvas->clear(); 204 | if (device_count == 0) { 205 | _canvas->setFont(&fonts::efontCN_24); 206 | _canvas->setTextSize(1); 207 | _canvas->setTextColor(TFT_RED); 208 | _canvas->drawCenterString("Not found", 64, 209 | _canvas->height() / 2 - 16); 210 | return; 211 | } 212 | 213 | char addr_buf[4]; 214 | _canvas->setFont(&fonts::efontCN_16); 215 | draw_form(); 216 | for (size_t i = 0; i < device_count; i++) { 217 | sprintf(addr_buf, "%d. 0x%02X", i + 1, addr_list[i]); 218 | _canvas->setTextColor((random(0, 255) << 16 | 219 | random(0, 255) << 8 | random(0, 255))); 220 | _canvas->drawCenterString(addr_buf, (32 * i > 2 ? 1 : 0) + 32, 221 | (32 * (i % 3)) + 8); 222 | } 223 | } 224 | } 225 | 226 | void stop() { 227 | Wire.end(); 228 | _canvas->setTextColor(TFT_WHITE); 229 | } 230 | 231 | void draw_form() { 232 | _canvas->drawFastHLine(0, _canvas->height() / 3 * 0 + 1, 233 | _canvas->width(), TFT_WHITE); 234 | _canvas->drawFastHLine(0, _canvas->height() / 3 * 1, _canvas->width(), 235 | TFT_WHITE); 236 | _canvas->drawFastHLine(0, _canvas->height() / 3 * 2, _canvas->width(), 237 | TFT_WHITE); 238 | _canvas->drawFastHLine(0, _canvas->height() / 3 * 3, _canvas->width(), 239 | TFT_WHITE); 240 | _canvas->drawFastVLine(_canvas->width() / 2 * 0, 1, 241 | _canvas->height() - 2, TFT_WHITE); 242 | _canvas->drawFastVLine(_canvas->width() / 2 * 1, 1, 243 | _canvas->height() - 2, TFT_WHITE); 244 | _canvas->drawFastVLine(_canvas->width() / 2 * 2 - 1, 1, 245 | _canvas->height() - 2, TFT_WHITE); 246 | } 247 | }; 248 | 249 | class func_uart_t : public func_base_t { 250 | const unsigned char* uart_mon_img_list[4] = { 251 | uart_mon_01_img, uart_mon_02_img, uart_mon_03_img, uart_mon_04_img}; 252 | const uint8_t uart_io_list[4][2] = {{2, 1}, {2, 1}, {1, 2}, {1, 2}}; 253 | const uint32_t uart_baud_list[4] = {9600, 115200, 9600, 115200}; 254 | uint8_t uart_mon_mode_index = 0; 255 | 256 | void start() { 257 | Serial.begin(uart_baud_list[uart_mon_mode_index], SERIAL_8N1, 258 | uart_io_list[uart_mon_mode_index][0], 259 | uart_io_list[uart_mon_mode_index][1]); 260 | M5.Display.drawPng(uart_mon_img_list[uart_mon_mode_index], ~0u, 0, 0); 261 | M5.Display.setFont(&fonts::Font0); 262 | } 263 | 264 | void update(bool btn_click) { 265 | if (btn_click) { 266 | uart_mon_mode_index++; 267 | if (uart_mon_mode_index > 3) { 268 | uart_mon_mode_index = 0; 269 | } 270 | 271 | Serial.end(); 272 | Serial.begin(uart_baud_list[uart_mon_mode_index], SERIAL_8N1, 273 | uart_io_list[uart_mon_mode_index][0], 274 | uart_io_list[uart_mon_mode_index][1]); 275 | M5.Display.drawPng(uart_mon_img_list[uart_mon_mode_index], ~0u, 0, 276 | 0); 277 | } 278 | 279 | // Grove => USB 280 | size_t rx_len = Serial.available(); 281 | if (rx_len) { 282 | for (size_t i = 0; i < rx_len; i++) { 283 | uint8_t c = Serial.read(); 284 | USBSerial.write(c); 285 | M5.Display.fillRect(86, 31, 128 - 86, 9); 286 | M5.Display.setCursor(93, 33); 287 | M5.Display.setTextColor((random(0, 255) << 16 | 288 | random(0, 255) << 8 | random(0, 255)), 289 | TFT_BLACK); 290 | M5.Display.printf("0x%02X", c); 291 | } 292 | } 293 | 294 | // USB => Grove 295 | size_t tx_len = USBSerial.available(); 296 | if (tx_len) { 297 | for (size_t i = 0; i < tx_len; i++) { 298 | uint8_t c = USBSerial.read(); 299 | Serial.write(c); 300 | M5.Display.fillRect(86, 41, 128 - 86, 9, TFT_BLACK); 301 | M5.Display.setCursor(93, 43); 302 | M5.Display.setTextColor((random(0, 255) << 16 | 303 | random(0, 255) << 8 | random(0, 255)), 304 | TFT_BLACK); 305 | M5.Display.printf("0x%02X", c); 306 | } 307 | } 308 | } 309 | 310 | void stop() { 311 | Serial.end(); 312 | M5.Display.setTextColor(TFT_WHITE, TFT_BLACK); 313 | gpio_reset_pin((gpio_num_t)1); 314 | gpio_reset_pin((gpio_num_t)2); 315 | } 316 | 317 | // not use canvas :) 318 | void draw() { 319 | } 320 | }; 321 | 322 | class func_pwm_t : public func_base_t { 323 | const unsigned char* pwm_img_list[4] = {io_pwm_01_img, io_pwm_02_img, 324 | io_pwm_03_img, io_pwm_04_img}; 325 | uint8_t pwm_mode_index = 0; 326 | uint8_t pwm_duty = 128; 327 | 328 | void start() { 329 | M5.Display.drawPng(pwm_img_list[pwm_mode_index], ~0u, 0, 0); 330 | M5.Display.setFont(&fonts::efontCN_16_b); 331 | M5.Display.drawCenterString("F: 1Khz", 90, 52); 332 | 333 | ledcSetup(0, 1000, 8); 334 | ledcSetup(1, 1000, 8); 335 | ledcAttachPin(1, 0); 336 | ledcAttachPin(2, 1); 337 | ledcWrite(0, pwm_duty); 338 | ledcWrite(1, pwm_duty); 339 | 340 | drawDuty(pwm_duty); 341 | } 342 | 343 | void update(bool btn_click) { 344 | if (btn_click) { 345 | pwm_mode_index++; 346 | if (pwm_mode_index > 3) { 347 | pwm_mode_index = 0; 348 | } 349 | M5.Display.drawPng(pwm_img_list[pwm_mode_index], ~0u, 0, 0); 350 | M5.Display.drawCenterString("F: 1Khz", 90, 52); 351 | if (pwm_mode_index != 0) { 352 | pwm_duty = 0; 353 | } else { 354 | pwm_duty = 0xF; 355 | } 356 | ledcWrite(0, pwm_duty); 357 | ledcWrite(1, pwm_duty); 358 | drawDuty(pwm_duty); 359 | } 360 | 361 | if (USBSerial.available()) { 362 | pwm_duty = (uint8_t)USBSerial.read(); 363 | if (pwm_mode_index <= 1) { 364 | ledcWrite(0, pwm_duty); 365 | ledcWrite(1, pwm_duty); 366 | } 367 | 368 | if (pwm_mode_index == 2) { 369 | ledcWrite(1, 0); 370 | ledcWrite(0, pwm_duty); 371 | } 372 | 373 | if (pwm_mode_index == 3) { 374 | ledcWrite(1, pwm_duty); 375 | ledcWrite(0, 0); 376 | } 377 | drawDuty(pwm_duty); 378 | } 379 | } 380 | 381 | void stop() { 382 | ledcDetachPin(1); 383 | ledcDetachPin(2); 384 | gpio_reset_pin((gpio_num_t)1); 385 | gpio_reset_pin((gpio_num_t)2); 386 | } 387 | 388 | void drawDuty(int duty) { 389 | M5.Display.fillRect(80, 30, 45, 18, TFT_BLACK); 390 | M5.Display.setCursor(85, 31); 391 | M5.Display.setTextColor(TFT_WHITE); 392 | M5.Display.printf("0x%02X", duty); 393 | } 394 | 395 | // not use canvas :) 396 | void draw() { 397 | } 398 | }; 399 | 400 | class func_adc_t : public func_base_t { 401 | time_t last_update_time = 0; 402 | 403 | uint32_t ch1_vol = 0; 404 | uint32_t ch2_vol = 0; 405 | 406 | void start() { 407 | M5.Display.drawPng(io_adc_01_img, ~0u, 0, 0); 408 | M5.Display.setFont(&fonts::efontCN_16_b); 409 | } 410 | 411 | void update(bool btn_click) { 412 | if (millis() - last_update_time < 90) { 413 | return; 414 | } 415 | 416 | ch1_vol = 0; 417 | ch2_vol = 0; 418 | for (size_t i = 0; i < 32; i++) { 419 | ch1_vol += analogRead(1); 420 | ch2_vol += analogRead(2); 421 | } 422 | drawVolValue((uint32_t)(ch1_vol / 32), (uint32_t)(ch2_vol / 32)); 423 | last_update_time = millis(); 424 | } 425 | 426 | void stop() { 427 | } 428 | 429 | void drawVolValue(uint32_t ch1, uint32_t ch2) { 430 | M5.Display.fillRect(0, 30, 50, 16, TFT_BLACK); 431 | M5.Display.fillRect(28, 50, 50, 16, TFT_BLACK); 432 | 433 | char buf[16] = {0}; 434 | sprintf(buf, "%.01fV", ((ch1 / 4095.0f) * 3.3f)); 435 | M5.Display.drawCenterString(buf, 30, 31); 436 | 437 | sprintf(buf, "%.01fV", ((ch2 / 4095.0f) * 3.3)); 438 | M5.Display.drawCenterString(buf, 55, 51); 439 | } 440 | 441 | // not use canvas :) 442 | void draw() { 443 | } 444 | }; 445 | 446 | class func_ir_t : public func_base_t { 447 | const unsigned char* ir_img_list[2] = {ir_send_01_img, ir_send_02_img}; 448 | uint8_t ir_send_mode_index = 0; 449 | 450 | uint32_t ir_cmd = 0; 451 | time_t ir_last_send_time = 0; 452 | ; 453 | void start() { 454 | M5.Display.drawPng(ir_img_list[ir_send_mode_index], ~0u, 0, 0); 455 | M5.Display.setFont(&fonts::efontCN_16_b); 456 | M5.Display.setTextColor(TFT_PURPLE); 457 | } 458 | 459 | void update(bool btn_click) { 460 | if (btn_click) { 461 | ir_send_mode_index++; 462 | if (ir_send_mode_index > 1) { 463 | ir_send_mode_index = 0; 464 | } 465 | M5.Display.drawPng(ir_img_list[ir_send_mode_index], ~0u, 0, 0); 466 | } 467 | 468 | // USB => IR 469 | if (ir_send_mode_index == 0) { 470 | if (USBSerial.available()) { 471 | uint8_t c = USBSerial.read(); 472 | ir_tx_send((uint32_t)c); 473 | drawIrData((uint32_t)c); 474 | } 475 | } else { 476 | if (millis() - ir_last_send_time > 1000) { 477 | ir_cmd++; 478 | ir_tx_send(ir_cmd); 479 | drawIrData(ir_cmd); 480 | ir_last_send_time = millis(); 481 | } 482 | } 483 | } 484 | 485 | void stop() { 486 | M5.Display.setTextColor(TFT_WHITE); 487 | } 488 | 489 | void drawIrData(uint32_t _ir_cmd) { 490 | M5.Display.fillRect(25, 105, 75, 20, TFT_WHITE); 491 | char buf[8] = {0}; 492 | sprintf(buf, "0x%02X", _ir_cmd); 493 | M5.Display.drawCenterString(buf, 64, 107); 494 | } 495 | 496 | // not use canvas :) 497 | void draw() { 498 | } 499 | }; 500 | 501 | class func_imu_t : public func_base_t { 502 | struct ball_t { 503 | uint16_t x; 504 | uint16_t y; 505 | uint16_t r; 506 | }; 507 | 508 | time_t last_imu_sample_time = 0; 509 | uint16_t center_x, center_y, c1_r, c2_r; 510 | float roll, pitch, yaw; 511 | ball_t ball; 512 | 513 | void start() { 514 | Wire1.begin(38, 39); 515 | imu.begin(); 516 | filter.begin(20); // 20hz 517 | 518 | center_x = (uint16_t)(_canvas->width() / 2); 519 | center_y = (uint16_t)(_canvas->height() / 2); 520 | c1_r = (uint16_t)(_canvas->height() / 2 - 4); 521 | c2_r = (uint16_t)(_canvas->height() / 4 - 4); 522 | 523 | ball.x = (uint16_t)(_canvas->width() / 2); 524 | ball.y = (uint16_t)(_canvas->height() / 2); 525 | ball.r = 5; 526 | } 527 | 528 | void update(bool btn_click) { 529 | if ((millis() - last_imu_sample_time) > (1000 / 25)) { 530 | float ax, ay, az, gx, gy, gz; 531 | 532 | imu.getAccel(&ax, &ay, &az); 533 | imu.getGyro(&gx, &gy, &gz); 534 | 535 | // update the filter, which computes orientation 536 | filter.updateIMU(gx, gy, gz, ax, ay, az); 537 | 538 | roll = filter.getRoll(); 539 | pitch = filter.getPitch(); 540 | yaw = filter.getYaw(); 541 | 542 | // USBSerial.printf("[%ld] roll:%.01f pitch:%.01f yaw:%.01f\r\n", 543 | // millis(), roll, pitch, yaw); 544 | calculateBall(roll, pitch); 545 | drawCircle(); 546 | needDraw(); 547 | last_imu_sample_time = millis(); 548 | } 549 | } 550 | 551 | void stop() { 552 | Wire1.end(); 553 | } 554 | 555 | void drawCircle() { 556 | _canvas->drawCircle(center_x, center_y, c1_r, 557 | _canvas->color24to16(0xCCCC33)); 558 | _canvas->drawCircle(center_x, center_y, c2_r, 559 | _canvas->color24to16(0xCCCC33)); 560 | _canvas->drawFastHLine((_canvas->width() - _canvas->height()) / 2 + 4, 561 | center_y, _canvas->height() - 8, 562 | _canvas->color24to16(0xCCCC33)); 563 | _canvas->drawFastVLine(center_x, 4, _canvas->height() - 8, 564 | _canvas->color24to16(0xCCCC33)); 565 | } 566 | 567 | void calculateBall(float _roll, float _pitch) { 568 | if (abs(_roll) > 90) { 569 | if (_roll < -90) { 570 | _roll = -90; 571 | } else { 572 | _roll = 90; 573 | } 574 | } 575 | if (abs(_pitch) > 90) { 576 | if (_pitch < -90) { 577 | _pitch = -90; 578 | } else { 579 | _pitch = 90; 580 | } 581 | } 582 | uint16_t x = (uint16_t)map(_pitch, -90.0f, 90.0f, center_x - c1_r, 583 | center_x + c1_r); 584 | uint16_t y = (uint16_t)map(_roll, -90.0f, 90.0f, center_y - c1_r, 585 | center_y + c1_r); 586 | drawBall(x, y); 587 | } 588 | 589 | void drawBall(int16_t x, float y) { 590 | // erase old 591 | _canvas->fillCircle(ball.x, ball.y, ball.r, TFT_BLACK); 592 | ball.x = x; 593 | ball.y = y; 594 | // draw new 595 | _canvas->fillCircle(ball.x, ball.y, ball.r, 596 | _canvas->color24to16(0xCCCC33)); 597 | } 598 | }; 599 | 600 | func_wifi_t func_wifi_scan; 601 | func_i2c_t func_i2c_scan; 602 | func_uart_t func_uart_mon; 603 | func_pwm_t func_pwm_test; 604 | func_adc_t func_adc_test; 605 | func_ir_t func_ir_send; 606 | func_imu_t func_imu_test; 607 | 608 | func_base_t* func_list[7] = {&func_wifi_scan, &func_i2c_scan, &func_uart_mon, 609 | &func_pwm_test, &func_adc_test, &func_ir_send, 610 | &func_imu_test}; 611 | 612 | static char btn_state_text[][16] = {"", "wasHold", "wasClicked"}; 613 | 614 | static func_index_t func_index = FUNC_WIFI_SCAN; 615 | static bool is_entry_func = false; 616 | 617 | static uint16_t ir_addr = 0; 618 | static rmt_item32_t* ir_items = NULL; 619 | static size_t ir_length = 0; 620 | static ir_builder_t* ir_builder = NULL; 621 | 622 | static uint8_t mac_addr[6]; 623 | 624 | M5Canvas canvas(&M5.Display); 625 | 626 | void setup() { 627 | M5.begin(); 628 | USBSerial.begin(115200); 629 | 630 | esp_efuse_mac_get_default(mac_addr); 631 | ir_addr = (mac_addr[2] << 24) | (mac_addr[3] << 16) | (mac_addr[4] << 8) | 632 | mac_addr[5]; 633 | 634 | M5.Display.begin(); 635 | 636 | canvas.setColorDepth(16); 637 | canvas.createSprite(M5.Display.width(), 638 | M5.Display.height() - LAYOUT_OFFSET_Y); 639 | 640 | boot_animation(); 641 | delay(500); 642 | 643 | // Start entry funciton list 644 | M5.Display.drawPng(func_img_list[func_index], ~0u, 0, 0); 645 | 646 | ir_tx_init(); 647 | } 648 | 649 | void loop() { 650 | M5.update(); 651 | 652 | int btn_state = M5.BtnA.wasHold() ? 1 : M5.BtnA.wasClicked() ? 2 : 0; 653 | 654 | if (btn_state != 0) { 655 | // USBSerial.printf("BTN %s\r\n", btn_state_text[btn_state]); 656 | } 657 | 658 | if ((btn_state == 2) && !is_entry_func) { 659 | // next function 660 | func_index = (func_index_t)(func_index + 1); 661 | if (func_index == FUNC_MAX) { 662 | func_index = FUNC_WIFI_SCAN; 663 | } 664 | M5.Display.drawPng(func_img_list[func_index], ~0u, 0, 0); 665 | } 666 | 667 | if (btn_state == 1) { 668 | if (!is_entry_func) { 669 | is_entry_func = true; 670 | // USBSerial.printf("Entry function <%s>\r\n", 671 | // func_name_text[func_index]); 672 | // entry function 673 | func_list[func_index]->entry(&canvas); 674 | } else { 675 | is_entry_func = false; 676 | // USBSerial.printf("Leave function <%s>\r\n", 677 | // func_name_text[func_index]); 678 | // leave function 679 | func_list[func_index]->leave(); 680 | M5.Display.drawPng(func_img_list[func_index], ~0u, 0, 0); 681 | } 682 | } 683 | 684 | if (is_entry_func) { 685 | func_list[func_index]->update(btn_state == 2 ? true : false); 686 | func_list[func_index]->draw(); 687 | } 688 | 689 | delay(10); 690 | } 691 | 692 | uint32_t circle_color_list[8] = {0xcc3300, 0xff6633, 0xffff66, 0x33cc33, 693 | 0x00ffff, 0x0000ff, 0xff3399, 0x990099}; 694 | static void boot_animation(void) { 695 | for (size_t c = 0; c < 8; c++) { 696 | M5.Display.fillArc(0, M5.Display.height(), c * 23, (c + 1) * 23, 270, 0, 697 | circle_color_list[c]); 698 | delay(200); 699 | } 700 | } 701 | 702 | static void ir_tx_init(void) { 703 | rmt_config_t rmt_tx_config = {.rmt_mode = RMT_MODE_TX, 704 | .channel = RMT_CHANNEL_2, 705 | .gpio_num = (gpio_num_t)IR_GPIO, 706 | .clk_div = 80, 707 | .mem_block_num = 1, 708 | .flags = 0, 709 | .tx_config = { 710 | .carrier_freq_hz = 38000, 711 | .carrier_level = RMT_CARRIER_LEVEL_HIGH, 712 | .idle_level = RMT_IDLE_LEVEL_LOW, 713 | .carrier_duty_percent = 33, 714 | .carrier_en = false, 715 | .loop_en = false, 716 | .idle_output_en = true, 717 | }}; 718 | 719 | rmt_tx_config.tx_config.carrier_en = true; 720 | rmt_config(&rmt_tx_config); 721 | rmt_driver_install(IR_RMT_TX_CHANNEL, 0, 0); 722 | ir_builder_config_t ir_builder_config = 723 | IR_BUILDER_DEFAULT_CONFIG((ir_dev_t)IR_RMT_TX_CHANNEL); 724 | ir_builder_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; 725 | ir_builder = ir_builder_rmt_new_nec(&ir_builder_config); 726 | } 727 | 728 | static void ir_tx_send(uint32_t _ir_cmd) { 729 | // Send new key code 730 | ESP_ERROR_CHECK(ir_builder->build_frame(ir_builder, ir_addr, _ir_cmd)); 731 | ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &ir_items, &ir_length)); 732 | // To send data according to the waveform items. 733 | rmt_write_items(IR_RMT_TX_CHANNEL, ir_items, ir_length, false); 734 | // Send repeat code 735 | vTaskDelay(ir_builder->repeat_period_ms / portTICK_PERIOD_MS); 736 | ESP_ERROR_CHECK(ir_builder->build_repeat_frame(ir_builder)); 737 | ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &ir_items, &ir_length)); 738 | rmt_write_items(IR_RMT_TX_CHANNEL, ir_items, ir_length, false); 739 | } 740 | --------------------------------------------------------------------------------