mirror of
https://github.com/m5stack/M5Unit-CardKB2-UserDemo.git
synced 2026-05-20 11:51:10 -07:00
Add source code.
This commit is contained in:
@@ -1,48 +1,35 @@
|
||||
# Product Name
|
||||
# M5Unit-CardKB2-UserDemo
|
||||
|
||||
## Overview
|
||||
**Factory firmware** for the [M5Stack Unit CardKB2](https://docs.m5stack.com/en/products/sku/u215) (**SKU: U215**), built with [ESP-IDF](https://github.com/espressif/esp-idf). It provides matrix keyboard scanning, RGB status LED, and key output over **I2C / UART / ESP-NOW / BLE HID**—including manufacturing / QA hooks such as I2C-triggered production-test mode.
|
||||
|
||||
### SKU:xxx
|
||||
-----------------
|
||||
## Operating mode
|
||||
|
||||
Description of the product
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| **I2C** (default) | I2C slave reports characters / key data to the host. |
|
||||
| **UART** | Custom frames with key index and press / release over UART. |
|
||||
| **ESP-NOW** | Broadcasts key frames (similar framing to UART). |
|
||||
| **BLE HID** | Bluetooth HID keyboard, including arrow keys via Fn combos. |
|
||||
|
||||
## Related Link
|
||||
### Switching modes
|
||||
|
||||
- [Document & Datasheet](https://docs.m5stack.com/en/unit/product_Link)
|
||||
Hold **Fn + Sym**, then press **1 / 2 / 3 / 4** on the top number row to select **I2C / UART / ESP-NOW / BLE HID** respectively.
|
||||
|
||||
## Required Libraries:
|
||||
**Note:** **Fn + 1** alone (top-left number key) sends **ESC** in most modes; it does **not** switch modes.
|
||||
|
||||
- [Adafruit_BMP280_Library](https://github.com/adafruit/Required_Libraries_Link)
|
||||
--------------------
|
||||
## Build environment
|
||||
|
||||
* idf version: ESP-IDF v6.1-dev-dirty
|
||||
|
||||
```bash
|
||||
cd src
|
||||
idf.py set-target esp32c61
|
||||
idf.py build
|
||||
idf.py -p <PORT> flash monitor
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
- [Product Name- MIT](LICENSE)
|
||||
|
||||
## Remaining steps(Editorial Staff Look,After following the steps, remember to delete all the content below)
|
||||
|
||||
1. Change [clang format check path](./.github/workflows/clang-format-check.yml#L42-L47).
|
||||
2. Add License content to [LICENSE](/LICENSE).
|
||||
3. Change link on line 78 of [bug-report.yml](./.github/ISSUE_TEMPLATE/bug-report.yml#L79).
|
||||
|
||||
```cpp
|
||||
Example
|
||||
# M5Unit-ENV
|
||||
|
||||
## Overview
|
||||
|
||||
### SKU:U001 & U001-B & U001-C
|
||||
|
||||
Contains M5Stack-**UNIT ENV** series related case programs.ENV is an environmental sensor with integrated SHT30 and QMP6988 internally to detect temperature, humidity, and atmospheric pressure data.
|
||||
|
||||
## Related Link
|
||||
|
||||
- [Document & Datasheet](https://docs.m5stack.com/en/unit/envIII)
|
||||
|
||||
## Required Libraries:
|
||||
|
||||
- [Adafruit_BMP280_Library](https://github.com/adafruit/Adafruit_BMP280_Library)
|
||||
|
||||
## License
|
||||
|
||||
- [M5Unit-ENV - MIT](LICENSE)
|
||||
```
|
||||
This project is licensed under the MIT License—see [LICENSE](LICENSE).
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
project(main)
|
||||
@@ -0,0 +1,35 @@
|
||||
dependencies:
|
||||
espressif/button:
|
||||
component_hash: 4ed25fcf354b48aa5e5680b9e7549e38d7738f9b613c94a2e39119c100db9c95
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: '*'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 4.1.4
|
||||
espressif/cmake_utilities:
|
||||
component_hash: 05165f30922b422b4b90c08845e6d449329b97370fbd06309803d8cb539d79e3
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.1'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.1.1
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 6.1.0
|
||||
direct_dependencies:
|
||||
- espressif/button
|
||||
- idf
|
||||
manifest_hash: d181cbae979e2a4195884d101aa2bd9d54059e652a36664cfc40a9fa26ec2dd7
|
||||
target: esp32c61
|
||||
version: 3.0.0
|
||||
@@ -0,0 +1,29 @@
|
||||
set(src_dirs
|
||||
.
|
||||
./app
|
||||
./bsp
|
||||
)
|
||||
|
||||
set(include_dirs
|
||||
.
|
||||
./app
|
||||
./bsp
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRC_DIRS
|
||||
${src_dirs}
|
||||
INCLUDE_DIRS
|
||||
${include_dirs}
|
||||
REQUIRES
|
||||
esp_hid
|
||||
esp_driver_ledc
|
||||
esp_driver_i2c
|
||||
esp_driver_uart
|
||||
esp_wifi
|
||||
esp_netif
|
||||
bt
|
||||
PRIV_REQUIRES
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "bsp_keyboard.h"
|
||||
|
||||
// 工作模式定义
|
||||
typedef enum {
|
||||
MODE_NONE = 0, // 未定义模式
|
||||
MODE_I2C = 1, // I2C 模式 (默认, Fn+1)
|
||||
MODE_UART = 2, // UART 模式 (Fn+2)
|
||||
MODE_ESPNOW = 3, // ESP-NOW 模式 (Fn+3)
|
||||
MODE_BLEHID = 4, // BLE HID 模式 (Fn+4)
|
||||
MODE_PRODTEST = 5, // 产测模式
|
||||
MODE_MAX
|
||||
} app_mode_t;
|
||||
|
||||
// I2C 触发产测模式切换相关变量(供 bsp_i2c ISR 使用)
|
||||
extern QueueHandle_t mode_queue;
|
||||
extern app_mode_t g_mode;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 固件版本定义
|
||||
#define FIRMWARE_VERSION 0x01
|
||||
|
||||
// 键盘状态定义
|
||||
typedef struct {
|
||||
bool caps_lock; // 大写锁定状态
|
||||
bool fn_pressed; // Fn 键状态
|
||||
bool sym_pressed; // Sym 键状态
|
||||
} keyboard_state_t;
|
||||
|
||||
// LED 状态定义
|
||||
typedef struct {
|
||||
bool caps_led_on; // 大写锁定红灯
|
||||
bool sym_led_on; // 符号模式绿灯
|
||||
bool fn_led_on; // Fn 激活蓝灯
|
||||
} led_state_t;
|
||||
|
||||
// 按键索引定义
|
||||
#define KEY_AA_TOGGLE 22 // Aa 大小写切换 (行2, 列0)
|
||||
#define KEY_DEL 21 // Del 删除键 (行1, 列10)
|
||||
#define KEY_ENTER 32 // Enter 回车键 (行2, 列10)
|
||||
#define KEY_FN 33 // Fn 功能键 (行3, 列0)
|
||||
#define KEY_SYM 34 // Sym 符号键 (行3, 列1)
|
||||
#define KEY_SPACE 42 // Space 空格键 (行3, 列9)
|
||||
|
||||
// Fn 键组合功能定义
|
||||
#define KEY_ESC 0 // ESC 键 (行0, 列0) = 0*11+0 = 0 (Fn+1)
|
||||
#define KEY_UP 25 // 上方向键 (行2, 列3) = 2*11+3 = 25 (Fn+D)
|
||||
#define KEY_LEFT 35 // 左方向键 (行3, 列2) = 3*11+2 = 35 (Fn+Z)
|
||||
#define KEY_DOWN 36 // 下方向键 (行3, 列3) = 3*11+3 = 36 (Fn+X)
|
||||
#define KEY_RIGHT 37 // 右方向键 (行3, 列4) = 3*11+4 = 37 (Fn+C)
|
||||
|
||||
// 输出模式选择:1=DEBUG模式(输出index), 0=正常模式(输出ASCII)
|
||||
#define KEYBOARD_DEBUG_MODE 0
|
||||
|
||||
// 函数声明
|
||||
void app_key_event_cb(button_event_data_t *event_data);
|
||||
void app_mode_switch(void *arg);
|
||||
void app_factory(void *arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
/**
|
||||
* @file app_factorytest.c
|
||||
* @brief 产测模式实现
|
||||
* @author Enable
|
||||
* @date 2025/12/08
|
||||
*/
|
||||
|
||||
#include "app_production_test.h"
|
||||
#include "app_factory.h"
|
||||
#include "bsp_uart.h"
|
||||
#include "bsp_rgb_led.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
|
||||
static const char *TAG = "app/production_test";
|
||||
|
||||
// WiFi 事件组
|
||||
static EventGroupHandle_t s_wifi_event_group = NULL;
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
// 状态
|
||||
static bool s_production_test_initialized = false;
|
||||
static bool s_production_test_enabled = false;
|
||||
static wifi_conn_state_t s_wifi_state = WIFI_STATE_DISCONNECTED;
|
||||
static char s_wifi_ip[16] = {0};
|
||||
static int s_retry_num = 0;
|
||||
#define WIFI_MAX_RETRY 5
|
||||
|
||||
// RGB 测试任务
|
||||
static TaskHandle_t s_rgb_test_task_handle = NULL;
|
||||
static bool s_rgb_test_running = false;
|
||||
|
||||
// RGB 闪烁测试任务(红绿蓝白黑循环)
|
||||
static void rgb_test_task(void *arg)
|
||||
{
|
||||
// RGB 颜色定义
|
||||
const uint8_t colors[5][3] = {
|
||||
{255, 0, 0}, // 红
|
||||
{0, 255, 0}, // 绿
|
||||
{0, 0, 255}, // 蓝
|
||||
{255, 255, 255}, // 白
|
||||
{0, 0, 0} // 黑
|
||||
};
|
||||
const char *color_names[] = {"红", "绿", "蓝", "白", "黑"};
|
||||
const int color_count = 5;
|
||||
const int delay_ms = 500;
|
||||
int color_index = 0;
|
||||
|
||||
ESP_LOGI(TAG, "RGB test task started");
|
||||
while (s_rgb_test_running) {
|
||||
bsp_rgb_led_set_color(colors[color_index][0],
|
||||
colors[color_index][1],
|
||||
colors[color_index][2]);
|
||||
|
||||
ESP_LOGI(TAG, "RGB test: %s (R:%d, G:%d, B:%d)",
|
||||
color_names[color_index],
|
||||
colors[color_index][0],
|
||||
colors[color_index][1],
|
||||
colors[color_index][2]);
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
color_index = (color_index + 1) % color_count;
|
||||
}
|
||||
bsp_rgb_led_set_color(0, 0, 0);
|
||||
s_rgb_test_task_handle = NULL;
|
||||
ESP_LOGI(TAG, "RGB test task stopped");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// WiFi 事件处理
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < WIFI_MAX_RETRY) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGI(TAG, "retry to connect to the AP");
|
||||
} else {
|
||||
if (s_wifi_event_group) {
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
}
|
||||
s_wifi_state = WIFI_STATE_FAILED;
|
||||
bsp_uart_printf("+WIFI:FAILED,max_retry\r\n");
|
||||
}
|
||||
ESP_LOGI(TAG, "connect to the AP fail");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
snprintf(s_wifi_ip, sizeof(s_wifi_ip), IPSTR, IP2STR(&event->ip_info.ip));
|
||||
ESP_LOGI(TAG, "Got IP: %s", s_wifi_ip);
|
||||
s_retry_num = 0;
|
||||
if (s_wifi_event_group) {
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
s_wifi_state = WIFI_STATE_CONNECTED;
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
bsp_uart_printf("+WIFI:CONNECTED,%d\r\n", ap_info.rssi);
|
||||
} else {
|
||||
bsp_uart_printf("+WIFI:CONNECTED,0\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t wifi_init(void)
|
||||
{
|
||||
static bool wifi_initialized = false;
|
||||
if (wifi_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
esp_event_handler_instance_t instance_any_id;
|
||||
esp_event_handler_instance_t instance_got_ip;
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
&instance_any_id));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
||||
IP_EVENT_STA_GOT_IP,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
&instance_got_ip));
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
|
||||
wifi_initialized = true;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void uart_cmd_handler(const char *cmd, int len)
|
||||
{
|
||||
// 去除首尾空白字符(包括 \r\n)
|
||||
while (len > 0 && (cmd[len-1] == '\r' || cmd[len-1] == '\n' || cmd[len-1] == ' ' || cmd[len-1] == '\t')) {
|
||||
len--;
|
||||
}
|
||||
const char *cmd_start = cmd;
|
||||
while (len > 0 && (*cmd_start == ' ' || *cmd_start == '\t')) {
|
||||
cmd_start++;
|
||||
len--;
|
||||
}
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "CMD: %.*s", len, cmd_start);
|
||||
|
||||
// AT+WIFI=SSID,PASSWORD
|
||||
if (strncmp(cmd_start, "AT+WIFI=", 8) == 0) {
|
||||
const char *params = cmd_start + 8;
|
||||
if (strncmp(params, "DISCONNECT", 10) == 0) {
|
||||
app_production_test_wifi_disconnect();
|
||||
bsp_uart_printf("+WIFI:DISCONNECTED\r\n");
|
||||
return;
|
||||
}
|
||||
char ssid[33] = {0};
|
||||
char password[65] = {0};
|
||||
const char *comma = strchr(params, ',');
|
||||
if (comma) {
|
||||
int ssid_len = comma - params;
|
||||
if (ssid_len > 0 && ssid_len < 33) {
|
||||
strncpy(ssid, params, ssid_len);
|
||||
const char *pwd_start = comma + 1;
|
||||
int pwd_len = strlen(pwd_start);
|
||||
while (pwd_len > 0 && (pwd_start[pwd_len-1] == '\r' || pwd_start[pwd_len-1] == '\n')) {
|
||||
pwd_len--;
|
||||
}
|
||||
if (pwd_len < 65) {
|
||||
strncpy(password, pwd_start, pwd_len);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "WiFi connect: SSID=%s, PWD=%s", ssid, password);
|
||||
bsp_uart_printf("+WIFI:CONNECTING\r\n");
|
||||
app_production_test_wifi_connect(ssid, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
// AT+WIFI?
|
||||
else if (strncmp(cmd_start, "AT+WIFI?", 8) == 0) {
|
||||
if (s_wifi_state == WIFI_STATE_CONNECTED) {
|
||||
// 获取 RSSI
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
bsp_uart_printf("+WIFI:CONNECTED,%d\r\n", ap_info.rssi);
|
||||
} else {
|
||||
bsp_uart_printf("+WIFI:CONNECTED,0\r\n");
|
||||
}
|
||||
} else if (s_wifi_state == WIFI_STATE_CONNECTING) {
|
||||
bsp_uart_printf("+WIFI:CONNECTING\r\n");
|
||||
} else {
|
||||
bsp_uart_printf("+WIFI:DISCONNECTED\r\n");
|
||||
}
|
||||
}
|
||||
// AT+VERSION?
|
||||
else if (strncmp(cmd_start, "AT+VERSION?", 11) == 0) {
|
||||
bsp_uart_printf("+VERSION:0x%02X\r\n", FIRMWARE_VERSION);
|
||||
}
|
||||
// AT+RGB=R,G,B
|
||||
else if (strncmp(cmd_start, "AT+RGB=", 7) == 0) {
|
||||
int r = 0, g = 0, b = 0;
|
||||
if (sscanf(cmd_start + 7, "%d,%d,%d", &r, &g, &b) == 3) {
|
||||
bsp_rgb_led_set_color((uint8_t)r, (uint8_t)g, (uint8_t)b);
|
||||
bsp_uart_printf("+RGB:OK\r\n");
|
||||
} else {
|
||||
bsp_uart_printf("+RGB:ERROR\r\n");
|
||||
}
|
||||
}
|
||||
// AT+FACTORY=0/1
|
||||
else if (strncmp(cmd_start, "AT+FACTORY=", 11) == 0) {
|
||||
int enable = atoi(cmd_start + 11);
|
||||
s_production_test_enabled = (enable != 0);
|
||||
bsp_uart_printf("+FACTORY:%d\r\n", s_production_test_enabled ? 1 : 0);
|
||||
ESP_LOGI(TAG, "Factory test mode: %s", s_production_test_enabled ? "ENABLED" : "DISABLED");
|
||||
}
|
||||
// AT+FACTORY?
|
||||
else if (strncmp(cmd_start, "AT+FACTORY?", 11) == 0) {
|
||||
bsp_uart_printf("+FACTORY:%d\r\n", s_production_test_enabled ? 1 : 0);
|
||||
}
|
||||
// AT+RGB_TEST=0/1 (启动/停止 RGB 闪烁测试)
|
||||
else if (strncmp(cmd_start, "AT+RGB_TEST=", 12) == 0) {
|
||||
ESP_LOGI(TAG, "RGB_TEST");
|
||||
int enable = atoi(cmd_start + 12);
|
||||
if (enable != 0) {
|
||||
if (s_rgb_test_task_handle == NULL) {
|
||||
s_rgb_test_running = true;
|
||||
ESP_LOGI(TAG, "RGB test task started");
|
||||
xTaskCreate(rgb_test_task, "rgb_test", 8192, NULL, 3, &s_rgb_test_task_handle);
|
||||
bsp_uart_printf("+RGB_TEST:STARTED\r\n");
|
||||
} else {
|
||||
bsp_uart_printf("+RGB_TEST:ALREADY_RUNNING\r\n");
|
||||
}
|
||||
} else {
|
||||
if (s_rgb_test_task_handle != NULL) {
|
||||
s_rgb_test_running = false;
|
||||
int wait_count = 0;
|
||||
while (s_rgb_test_task_handle != NULL && wait_count < 10) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
wait_count++;
|
||||
}
|
||||
if (s_rgb_test_task_handle != NULL) {
|
||||
vTaskDelete(s_rgb_test_task_handle);
|
||||
s_rgb_test_task_handle = NULL;
|
||||
}
|
||||
bsp_rgb_led_set_color(0, 0, 0);
|
||||
bsp_uart_printf("+RGB_TEST:STOPPED\r\n");
|
||||
} else {
|
||||
bsp_uart_printf("+RGB_TEST:NOT_RUNNING\r\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bsp_uart_printf("+ERROR:UNKNOWN_CMD\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t app_production_test_init(void)
|
||||
{
|
||||
if (s_production_test_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
ret = wifi_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "WiFi init failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
|
||||
ret = bsp_uart_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "UART init failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bsp_uart_set_cmd_callback(uart_cmd_handler);
|
||||
ret = bsp_uart_start_recv_task();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "UART recv task start failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_production_test_initialized = true;
|
||||
ESP_LOGI(TAG, "Production test initialized");
|
||||
bsp_uart_printf("+READY\r\n");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t app_production_test_deinit(void)
|
||||
{
|
||||
if (!s_production_test_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (s_rgb_test_task_handle != NULL) {
|
||||
s_rgb_test_running = false;
|
||||
int wait_count = 0;
|
||||
while (s_rgb_test_task_handle != NULL && wait_count < 10) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
wait_count++;
|
||||
}
|
||||
if (s_rgb_test_task_handle != NULL) {
|
||||
vTaskDelete(s_rgb_test_task_handle);
|
||||
s_rgb_test_task_handle = NULL;
|
||||
}
|
||||
bsp_rgb_led_set_color(0, 0, 0);
|
||||
}
|
||||
|
||||
app_production_test_wifi_disconnect();
|
||||
bsp_uart_deinit();
|
||||
|
||||
s_production_test_initialized = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t app_production_test_wifi_connect(const char *ssid, const char *password)
|
||||
{
|
||||
if (ssid == NULL || strlen(ssid) == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_wifi_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_wifi_stop();
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.scan_method = WIFI_ALL_CHANNEL_SCAN,
|
||||
.sort_method = WIFI_CONNECT_AP_BY_SIGNAL,
|
||||
.threshold.rssi = -70,
|
||||
.threshold.authmode = (password && strlen(password) > 0) ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN,
|
||||
.threshold.rssi_5g_adjustment = 0,
|
||||
},
|
||||
};
|
||||
|
||||
strcpy((char *)wifi_config.sta.ssid, ssid);
|
||||
if (password && strlen(password) > 0) {
|
||||
strcpy((char *)wifi_config.sta.password, password);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
s_wifi_state = WIFI_STATE_CONNECTING;
|
||||
s_retry_num = 0;
|
||||
memset(s_wifi_ip, 0, sizeof(s_wifi_ip));
|
||||
|
||||
ESP_LOGI(TAG, "WiFi connecting to SSID: %s", ssid);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t app_production_test_wifi_disconnect(void)
|
||||
{
|
||||
esp_wifi_disconnect();
|
||||
esp_wifi_stop();
|
||||
s_wifi_state = WIFI_STATE_DISCONNECTED;
|
||||
memset(s_wifi_ip, 0, sizeof(s_wifi_ip));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
wifi_conn_state_t app_production_test_wifi_get_state(void)
|
||||
{
|
||||
return s_wifi_state;
|
||||
}
|
||||
|
||||
const char *app_production_test_wifi_get_ip(void)
|
||||
{
|
||||
if (s_wifi_state == WIFI_STATE_CONNECTED && strlen(s_wifi_ip) > 0) {
|
||||
return s_wifi_ip;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool app_production_test_is_enabled(void)
|
||||
{
|
||||
return s_production_test_enabled;
|
||||
}
|
||||
|
||||
void app_production_test_set_enabled(bool enable)
|
||||
{
|
||||
s_production_test_enabled = enable;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file app_production_test.h
|
||||
* @brief 产测模式头文件
|
||||
* @author Enable
|
||||
* @date 2025/12/08
|
||||
*
|
||||
* 产测命令协议 (UART 115200):
|
||||
*
|
||||
* 1. 设置WiFi并连接:
|
||||
* 发送: AT+WIFI=SSID,PASSWORD\r\n
|
||||
* 响应: +WIFI:CONNECTING\r\n
|
||||
* +WIFI:CONNECTED,192.168.1.100\r\n (成功)
|
||||
* +WIFI:FAILED,reason\r\n (失败)
|
||||
*
|
||||
* 2. 查询WiFi状态:
|
||||
* 发送: AT+WIFI?\r\n
|
||||
* 响应: +WIFI:CONNECTED,192.168.1.100\r\n
|
||||
* +WIFI:DISCONNECTED\r\n
|
||||
*
|
||||
* 3. 断开WiFi:
|
||||
* 发送: AT+WIFI=DISCONNECT\r\n
|
||||
* 响应: +WIFI:DISCONNECTED\r\n
|
||||
*
|
||||
* 4. 获取固件版本:
|
||||
* 发送: AT+VERSION?\r\n
|
||||
* 响应: +VERSION:0xF0\r\n
|
||||
*
|
||||
* 5. RGB LED 设置颜色:
|
||||
* 发送: AT+RGB=R,G,B\r\n (0-255)
|
||||
* 响应: +RGB:OK\r\n
|
||||
*
|
||||
* 6. RGB LED 闪烁测试:
|
||||
* 发送: AT+RGB_TEST=1\r\n (启动红绿蓝白黑循环闪烁)
|
||||
* 响应: +RGB_TEST:STARTED\r\n
|
||||
* 发送: AT+RGB_TEST=0\r\n (停止闪烁)
|
||||
* 响应: +RGB_TEST:STOPPED\r\n
|
||||
*
|
||||
* 7. 进入/退出产测模式:
|
||||
* 发送: AT+FACTORY=1\r\n (进入)
|
||||
* 发送: AT+FACTORY=0\r\n (退出)
|
||||
* 响应: +FACTORY:1\r\n 或 +FACTORY:0\r\n
|
||||
*
|
||||
* 8. 按键事件上报(产测模式下自动上报):
|
||||
* 格式: +KEY:index,event\r\n
|
||||
* - index: 按键索引 (0-43)
|
||||
* - event: 事件类型 (0=按下, 1=释放)
|
||||
* 示例: +KEY:22,0\r\n (按键索引22按下)
|
||||
* +KEY:22,1\r\n (按键索引22释放)
|
||||
*/
|
||||
|
||||
#ifndef _APP_PRODUCTION_TEST_H_
|
||||
#define _APP_PRODUCTION_TEST_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// WiFi 连接状态
|
||||
typedef enum {
|
||||
WIFI_STATE_DISCONNECTED = 0,
|
||||
WIFI_STATE_CONNECTING,
|
||||
WIFI_STATE_CONNECTED,
|
||||
WIFI_STATE_FAILED,
|
||||
} wifi_conn_state_t;
|
||||
|
||||
// WiFi 状态回调
|
||||
typedef void (*wifi_state_callback_t)(wifi_conn_state_t state, const char *info);
|
||||
|
||||
/**
|
||||
* @brief 初始化产测模式
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t app_production_test_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化产测模式
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t app_production_test_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 连接 WiFi
|
||||
* @param ssid SSID
|
||||
* @param password 密码
|
||||
* @return ESP_OK 成功发起连接
|
||||
*/
|
||||
esp_err_t app_production_test_wifi_connect(const char *ssid, const char *password);
|
||||
|
||||
/**
|
||||
* @brief 断开 WiFi
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t app_production_test_wifi_disconnect(void);
|
||||
|
||||
/**
|
||||
* @brief 获取 WiFi 状态
|
||||
* @return WiFi 连接状态
|
||||
*/
|
||||
wifi_conn_state_t app_production_test_wifi_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief 获取 WiFi IP 地址
|
||||
* @return IP 地址字符串,未连接返回 NULL
|
||||
*/
|
||||
const char *app_production_test_wifi_get_ip(void);
|
||||
|
||||
/**
|
||||
* @brief 是否处于产测模式
|
||||
* @return true 产测模式
|
||||
*/
|
||||
bool app_production_test_is_enabled(void);
|
||||
|
||||
/**
|
||||
* @brief 设置产测模式
|
||||
* @param enable true 进入产测模式
|
||||
*/
|
||||
void app_production_test_set_enabled(bool enable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _APP_PRODUCTION_TEST_H_ */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
#ifndef _BSP_BLEHID_H_
|
||||
#define _BSP_BLEHID_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// BLE HID 设备名称
|
||||
#define BLE_HID_DEVICE_NAME "CardKB2"
|
||||
|
||||
// USB HID 修饰键码
|
||||
#define USB_HID_MODIFIER_NONE 0x00
|
||||
#define USB_HID_MODIFIER_LEFT_CTRL 0x01
|
||||
#define USB_HID_MODIFIER_LEFT_SHIFT 0x02
|
||||
#define USB_HID_MODIFIER_LEFT_ALT 0x04
|
||||
#define USB_HID_MODIFIER_LEFT_GUI 0x08
|
||||
#define USB_HID_MODIFIER_RIGHT_CTRL 0x10
|
||||
#define USB_HID_MODIFIER_RIGHT_SHIFT 0x20
|
||||
#define USB_HID_MODIFIER_RIGHT_ALT 0x40
|
||||
#define USB_HID_MODIFIER_RIGHT_GUI 0x80
|
||||
|
||||
// USB HID 键码定义
|
||||
#define USB_HID_KEY_A 0x04
|
||||
#define USB_HID_KEY_Z 0x1D
|
||||
#define USB_HID_KEY_1 0x1E
|
||||
#define USB_HID_KEY_0 0x27
|
||||
#define USB_HID_KEY_ENTER 0x28
|
||||
#define USB_HID_KEY_ESC 0x29
|
||||
#define USB_HID_KEY_BACKSPACE 0x2A
|
||||
#define USB_HID_KEY_TAB 0x2B
|
||||
#define USB_HID_KEY_SPACE 0x2C
|
||||
#define USB_HID_KEY_MINUS 0x2D
|
||||
#define USB_HID_KEY_EQUAL 0x2E
|
||||
#define USB_HID_KEY_LEFTBRACE 0x2F
|
||||
#define USB_HID_KEY_RIGHTBRACE 0x30
|
||||
#define USB_HID_KEY_BACKSLASH 0x31
|
||||
#define USB_HID_KEY_SEMICOLON 0x33
|
||||
#define USB_HID_KEY_APOSTROPHE 0x34
|
||||
#define USB_HID_KEY_GRAVE 0x35
|
||||
#define USB_HID_KEY_COMMA 0x36
|
||||
#define USB_HID_KEY_DOT 0x37
|
||||
#define USB_HID_KEY_SLASH 0x38
|
||||
|
||||
// 方向键
|
||||
#define USB_HID_KEY_RIGHT 0x4F
|
||||
#define USB_HID_KEY_LEFT 0x50
|
||||
#define USB_HID_KEY_DOWN 0x51
|
||||
#define USB_HID_KEY_UP 0x52
|
||||
|
||||
// BLE HID 连接状态
|
||||
typedef enum {
|
||||
BLE_HID_STATE_DISCONNECTED = 0,
|
||||
BLE_HID_STATE_ADVERTISING,
|
||||
BLE_HID_STATE_CONNECTED,
|
||||
} ble_hid_state_t;
|
||||
|
||||
// BLE HID 事件回调类型
|
||||
typedef void (*ble_hid_event_cb_t)(ble_hid_state_t state);
|
||||
|
||||
/**
|
||||
* @brief 初始化 BLE HID 设备
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化 BLE HID 设备
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 开始 BLE 广播
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_start_advertising(void);
|
||||
|
||||
/**
|
||||
* @brief 停止 BLE 广播
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_stop_advertising(void);
|
||||
|
||||
/**
|
||||
* @brief 发送键盘按键 (ASCII字符)
|
||||
* @param ch ASCII字符
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_send_key(char ch);
|
||||
|
||||
/**
|
||||
* @brief 发送键盘按键 (HID 键码)
|
||||
* @param modifier 修饰键
|
||||
* @param keycode HID 键码
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_send_keycode(uint8_t modifier, uint8_t keycode);
|
||||
|
||||
/**
|
||||
* @brief 发送键盘释放
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_send_key_release(void);
|
||||
|
||||
/**
|
||||
* @brief 获取 BLE HID 连接状态
|
||||
* @return BLE HID 状态
|
||||
*/
|
||||
ble_hid_state_t bsp_blehid_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief 检查 BLE HID 是否已连接
|
||||
* @return true 已连接, false 未连接
|
||||
*/
|
||||
bool bsp_blehid_is_connected(void);
|
||||
|
||||
/**
|
||||
* @brief 注册 BLE HID 事件回调
|
||||
* @param cb 回调函数
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_blehid_register_event_cb(ble_hid_event_cb_t cb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _BSP_BLEHID_H_ */
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @file bsp_espnow.c
|
||||
* @brief ESP-NOW 广播模式 BSP 实现
|
||||
* @author Enable
|
||||
* @date 2025/12/08
|
||||
*/
|
||||
|
||||
#include "bsp_espnow.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_now.h"
|
||||
#include "esp_netif.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "bsp/espnow";
|
||||
|
||||
// ESP-NOW 广播地址
|
||||
static const uint8_t broadcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
// Wi-Fi 信道 0
|
||||
#define WIFI_CHANNEL 0
|
||||
// 是否初始化
|
||||
static bool espnow_initialized = false;
|
||||
|
||||
/**
|
||||
* @brief ESP-NOW 发送回调
|
||||
*/
|
||||
static void espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status)
|
||||
{
|
||||
if (status == ESP_NOW_SEND_SUCCESS) {
|
||||
ESP_LOGD(TAG, "ESP-NOW 发送成功");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "ESP-NOW 发送失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ESP-NOW 初始化
|
||||
*/
|
||||
esp_err_t bsp_espnow_init(void)
|
||||
{
|
||||
if (espnow_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
esp_err_t ret = esp_now_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "ESP-NOW 初始化失败: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
esp_now_register_send_cb((esp_now_send_cb_t)espnow_send_cb);
|
||||
|
||||
// 添加广播对等点
|
||||
esp_now_peer_info_t peer = {};
|
||||
memcpy(peer.peer_addr, broadcast_mac, 6);
|
||||
peer.channel = WIFI_CHANNEL;
|
||||
peer.ifidx = WIFI_IF_STA;
|
||||
peer.encrypt = false;
|
||||
ret = esp_now_add_peer(&peer);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "添加广播对等点失败: %s", esp_err_to_name(ret));
|
||||
esp_now_deinit();
|
||||
return ret;
|
||||
}
|
||||
espnow_initialized = true;
|
||||
ESP_LOGD(TAG, "ESP-NOW 广播模式初始化成功");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ESP-NOW 反初始化
|
||||
*/
|
||||
esp_err_t bsp_espnow_deinit(void)
|
||||
{
|
||||
if (!espnow_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_now_del_peer(broadcast_mac);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGW(TAG, "delete peer failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
ret = esp_now_deinit();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "deinit esp-now failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
ret = esp_wifi_stop();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "stop wifi failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
ret = esp_wifi_deinit();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "deinit wifi failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
esp_netif_deinit();
|
||||
|
||||
espnow_initialized = false;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void bsp_espnow_add_key(char c)
|
||||
{
|
||||
if (!espnow_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t data[1] = {(uint8_t)c};
|
||||
esp_err_t ret = esp_now_send(broadcast_mac, data, 1);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGD(TAG, "send char: %c (0x%02X)", c, (uint8_t)c);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "send char failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
bool bsp_espnow_is_initialized(void)
|
||||
{
|
||||
return espnow_initialized;
|
||||
}
|
||||
|
||||
esp_err_t bsp_espnow_send(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!espnow_initialized || data == NULL || len == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t ret = esp_now_send(broadcast_mac, data, len);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGD(TAG, "send data: len=%u", (unsigned)len);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "send data failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef _BSP_ESPNOW_H_
|
||||
#define _BSP_ESPNOW_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化 ESP-NOW 广播模式
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_espnow_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化 ESP-NOW
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_espnow_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 通过 ESP-NOW 发送按键字符
|
||||
* @param c 按键字符
|
||||
*/
|
||||
void bsp_espnow_add_key(char c);
|
||||
|
||||
/**
|
||||
* @brief 检查 ESP-NOW 是否已初始化
|
||||
* @return true 已初始化, false 未初始化
|
||||
*/
|
||||
bool bsp_espnow_is_initialized(void);
|
||||
|
||||
/**
|
||||
* @brief 发送 ESP-NOW 数据包(原始数据)
|
||||
* @param data 数据缓冲区
|
||||
* @param len 数据长度
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_espnow_send(const uint8_t *data, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _BSP_ESPNOW_H_ */
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*******************************************************************************
|
||||
* M5Stack Unit CardKB2 I2C 从机 BSP
|
||||
*
|
||||
* I2C 从机配置:
|
||||
* - 地址:0x5F
|
||||
* - SCL:GPIO 25
|
||||
* - SDA:GPIO 26
|
||||
*
|
||||
* 寄存器定义:
|
||||
* - 0xF1:固件版本寄存器(写入 0xF1,然后读取获取版本号)
|
||||
* - 0xF0:产测模式控制寄存器(写入 0x01 进入产测模式,退出只能通过切换到其他模式)
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 读取按键:直接读取 I2C,返回按键 ASCII 码(无按键返回 0)
|
||||
* 2. 读取版本:先写入 0xF1,然后读取,返回版本号
|
||||
*******************************************************************************/
|
||||
#include "bsp_i2c.h"
|
||||
#include "app_factory.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/i2c_slave.h"
|
||||
#include "driver/i2c_types.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
static const char *TAG = "bsp/i2c";
|
||||
|
||||
// I2C 从机配置
|
||||
#define I2C_SLAVE_PORT 0 // I2C 端口号
|
||||
#define I2C_SLAVE_SCL_IO 25 // GPIO 25
|
||||
#define I2C_SLAVE_SDA_IO 26 // GPIO 26
|
||||
#define I2C_SLAVE_ADDR 0x5F // I2C 从机地址
|
||||
#define I2C_SLAVE_TX_BUF_LEN 32 // 发送缓冲区长度
|
||||
#define I2C_SLAVE_RX_BUF_LEN 32 // 接收缓冲区长度
|
||||
|
||||
// 按键队列配置
|
||||
#define KEY_QUEUE_SIZE 32 // 队列大小
|
||||
|
||||
// 按键队列结构(环形缓冲区)
|
||||
typedef struct {
|
||||
uint8_t buffer[KEY_QUEUE_SIZE];
|
||||
volatile uint16_t head; // 队列头(写入位置)
|
||||
volatile uint16_t tail; // 队列尾(读取位置)
|
||||
SemaphoreHandle_t mutex; // 互斥锁
|
||||
} key_queue_t;
|
||||
|
||||
static key_queue_t key_queue = {0};
|
||||
static i2c_slave_dev_handle_t i2c_slave_handle = NULL;
|
||||
static volatile uint8_t last_register = 0; // 最后写入的寄存器地址
|
||||
static bool i2c_initialized = false; // I2C 从机初始化状态
|
||||
|
||||
// 寄存器地址定义
|
||||
#define REG_FIRMWARE_VERSION 0xF1 // 固件版本寄存器
|
||||
#define REG_PRODUCTION_TEST 0xF0 // 产测模式控制寄存器
|
||||
|
||||
// 队列操作函数
|
||||
static bool key_queue_is_full(void)
|
||||
{
|
||||
return ((key_queue.head + 1) % KEY_QUEUE_SIZE) == key_queue.tail;
|
||||
}
|
||||
|
||||
static bool key_queue_is_empty(void)
|
||||
{
|
||||
return key_queue.head == key_queue.tail;
|
||||
}
|
||||
|
||||
static bool key_queue_push(uint8_t key)
|
||||
{
|
||||
if (xSemaphoreTake(key_queue.mutex, pdMS_TO_TICKS(10)) != pdTRUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (!key_queue_is_full()) {
|
||||
key_queue.buffer[key_queue.head] = key;
|
||||
key_queue.head = (key_queue.head + 1) % KEY_QUEUE_SIZE;
|
||||
result = true;
|
||||
}
|
||||
|
||||
xSemaphoreGive(key_queue.mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool key_queue_pop_from_isr(uint8_t *key)
|
||||
{
|
||||
// 在 ISR 中直接访问,不使用互斥锁
|
||||
// 使用 volatile 变量确保可见性
|
||||
if (key_queue.head == key_queue.tail) {
|
||||
return false; // 队列为空
|
||||
}
|
||||
|
||||
*key = key_queue.buffer[key_queue.tail];
|
||||
key_queue.tail = (key_queue.tail + 1) % KEY_QUEUE_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
static IRAM_ATTR bool i2c_slave_receive_callback(i2c_slave_dev_handle_t handle, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
|
||||
{
|
||||
if (edata->length > 0 && edata->buffer != NULL) {
|
||||
if (edata->length >= 1) {
|
||||
uint8_t reg = edata->buffer[0];
|
||||
last_register = reg; // 保存寄存器地址
|
||||
|
||||
if (edata->length >= 2) {
|
||||
// 如果写入 2 字节,第一个是寄存器地址,第二个是值
|
||||
uint8_t value = edata->buffer[1];
|
||||
|
||||
if (reg == REG_PRODUCTION_TEST) {
|
||||
if (value == 0x01) {
|
||||
// 触发产测模式切换:通过消息队列发送模式值
|
||||
extern QueueHandle_t mode_queue;
|
||||
app_mode_t mode = MODE_PRODTEST;
|
||||
|
||||
if (mode_queue != NULL) {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xQueueSendFromISR(mode_queue, &mode, &xHigherPriorityTaskWoken);
|
||||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// I2C 从机发送请求回调(当主机请求读取数据时调用)
|
||||
static IRAM_ATTR bool i2c_slave_request_callback(i2c_slave_dev_handle_t handle, const i2c_slave_request_event_data_t *evt_data, void *user_data)
|
||||
{
|
||||
uint8_t tx_data[1] = {0};
|
||||
uint32_t write_len = 0;
|
||||
|
||||
// 检查是否是读取寄存器(如果最后写入的寄存器是版本寄存器)
|
||||
if (last_register == REG_FIRMWARE_VERSION) {
|
||||
tx_data[0] = FIRMWARE_VERSION;
|
||||
last_register = 0;
|
||||
} else {
|
||||
uint8_t key = 0;
|
||||
if (!key_queue_pop_from_isr(&key)) {
|
||||
key = 0;
|
||||
}
|
||||
tx_data[0] = key;
|
||||
}
|
||||
|
||||
i2c_slave_write(handle, tx_data, 1, &write_len, 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t bsp_i2c_init(void)
|
||||
{
|
||||
if (i2c_initialized) {
|
||||
ESP_LOGD(TAG, "I2C slave already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGD(TAG, "initialize I2C slave...");
|
||||
|
||||
key_queue.mutex = xSemaphoreCreateMutex();
|
||||
if (key_queue.mutex == NULL) {
|
||||
ESP_LOGE(TAG, "create mutex failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
key_queue.head = 0;
|
||||
key_queue.tail = 0;
|
||||
|
||||
i2c_slave_config_t i2c_slave_config = {
|
||||
.i2c_port = I2C_SLAVE_PORT,
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO,
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.send_buf_depth = I2C_SLAVE_TX_BUF_LEN,
|
||||
.receive_buf_depth = I2C_SLAVE_RX_BUF_LEN,
|
||||
.slave_addr = I2C_SLAVE_ADDR,
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
|
||||
.intr_priority = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = true,
|
||||
}
|
||||
};
|
||||
|
||||
esp_err_t ret = i2c_new_slave_device(&i2c_slave_config, &i2c_slave_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "create I2C slave device failed: %s", esp_err_to_name(ret));
|
||||
vSemaphoreDelete(key_queue.mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_request = i2c_slave_request_callback,
|
||||
.on_receive = i2c_slave_receive_callback,
|
||||
};
|
||||
|
||||
ret = i2c_slave_register_event_callbacks(i2c_slave_handle, &cbs, NULL);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "register I2C slave event callback failed: %s", esp_err_to_name(ret));
|
||||
i2c_del_slave_device(i2c_slave_handle);
|
||||
i2c_slave_handle = NULL;
|
||||
vSemaphoreDelete(key_queue.mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "I2C slave initialized (address: 0x%02X, SCL: GPIO%d, SDA: GPIO%d)", I2C_SLAVE_ADDR, I2C_SLAVE_SCL_IO, I2C_SLAVE_SDA_IO);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bsp_i2c_deinit(void)
|
||||
{
|
||||
if (i2c_slave_handle == NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ret = i2c_del_slave_device(i2c_slave_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "delete I2C slave device failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
i2c_slave_handle = NULL;
|
||||
|
||||
if (key_queue.mutex) {
|
||||
vSemaphoreDelete(key_queue.mutex);
|
||||
key_queue.mutex = NULL;
|
||||
}
|
||||
|
||||
key_queue.head = 0;
|
||||
key_queue.tail = 0;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void bsp_i2c_add_key(uint8_t key)
|
||||
{
|
||||
if (key == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key_queue_push(key)) {
|
||||
ESP_LOGD(TAG, "key added to I2C queue: 0x%02X ('%c')", key, (key >= 32 && key <= 126) ? key : '?');
|
||||
} else {
|
||||
ESP_LOGD(TAG, "key queue is full, discard key: 0x%02X", key);
|
||||
}
|
||||
}
|
||||
|
||||
void bsp_i2c_add_key_index(uint8_t button_index)
|
||||
{
|
||||
if (key_queue_push(button_index)) {
|
||||
ESP_LOGD(TAG, "key index added to I2C queue: %d (row=%d, col=%d)",
|
||||
button_index, button_index / 11, button_index % 11);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "key queue is full, discard key index: %d", button_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief I2C 从机初始化
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_i2c_init(void);
|
||||
|
||||
/**
|
||||
* @brief I2C 从机反初始化
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_i2c_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 添加按键数据到 I2C 发送队列(ASCII 码模式)
|
||||
* @param key 按键 ASCII 码
|
||||
*/
|
||||
void bsp_i2c_add_key(uint8_t key);
|
||||
|
||||
/**
|
||||
* @brief 添加按键 index 到 I2C 发送队列(产测模式)
|
||||
* @param button_index 按键索引
|
||||
*/
|
||||
void bsp_i2c_add_key_index(uint8_t button_index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
|
||||
*******************************************************************************/
|
||||
#include "bsp_keyboard.h"
|
||||
#include "esp_log.h"
|
||||
#include "iot_button.h"
|
||||
#include "button_matrix.h"
|
||||
|
||||
|
||||
static const char *TAG = "bsp/keyboard";
|
||||
|
||||
|
||||
// 键盘配置,实际 (ROW1, COM10), (ROW3, COM10) 未接按键
|
||||
static const int32_t row_gpios[] = {0, 1, 2, 3}; // ROW0-ROW3
|
||||
static const int32_t col_gpios[] = {4, 5, 6, 7, 8, 29, 10, 11, 22, 23, 24}; // COM0-COM10
|
||||
// 按键句柄数组 (4x11 = 44个按键)
|
||||
static button_handle_t button_handles[44] = {0};
|
||||
// 按键事件回调
|
||||
static keyboard_event_cb_t event_cb = NULL;
|
||||
|
||||
|
||||
static void button_event_cb(void *arg, void *data)
|
||||
{
|
||||
button_event_t event = iot_button_get_event(arg);
|
||||
int button_index = (int)data;
|
||||
|
||||
button_event_data_t event_data = {
|
||||
.button_index = button_index,
|
||||
.event = event
|
||||
};
|
||||
|
||||
if (event_cb != NULL) {
|
||||
event_cb(&event_data);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t bsp_keyboard_init(void)
|
||||
{
|
||||
const button_config_t btn_cfg = {
|
||||
.long_press_time = 400,
|
||||
.short_press_time = 200,
|
||||
};
|
||||
const button_matrix_config_t matrix_cfg = {
|
||||
.row_gpios = (int32_t*)row_gpios,
|
||||
.col_gpios = (int32_t*)col_gpios,
|
||||
.row_gpio_num = 4,
|
||||
.col_gpio_num = 11,
|
||||
};
|
||||
size_t btn_num = 44;
|
||||
|
||||
esp_err_t ret = iot_button_new_matrix_device(&btn_cfg, &matrix_cfg, button_handles, &btn_num);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Create matrix button device failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bsp_keyboard_add_event_cb(keyboard_event_cb_t cb, button_event_t event_type)
|
||||
{
|
||||
if (cb == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (event_cb == NULL) {
|
||||
event_cb = cb;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 44; i++) {
|
||||
if (button_handles[i] != NULL) {
|
||||
iot_button_register_cb(button_handles[i], event_type, NULL, button_event_cb, (void*)i);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bsp_keyboard_add_event_cb_by_index(int button_index, keyboard_event_cb_t cb, button_event_t event_type)
|
||||
{
|
||||
if (button_index < 0 || button_index >= 44) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (cb == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (event_cb == NULL) {
|
||||
event_cb = cb;
|
||||
}
|
||||
|
||||
if (button_handles[button_index] == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return iot_button_register_cb(button_handles[button_index], event_type, NULL, button_event_cb, (void*)button_index);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "iot_button.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int button_index;
|
||||
button_event_t event;
|
||||
} button_event_data_t;
|
||||
|
||||
typedef void (*keyboard_event_cb_t)(button_event_data_t *event_data);
|
||||
|
||||
|
||||
/**
|
||||
* @brief 初始化键盘
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
* @note 初始化键盘
|
||||
*/
|
||||
esp_err_t bsp_keyboard_init(void);
|
||||
|
||||
/**
|
||||
* @brief 注册按键事件回调
|
||||
* @param cb 回调函数
|
||||
* @param event_type 事件类型 (BUTTON_PRESS_DOWN, BUTTON_PRESS_UP, BUTTON_SINGLE_CLICK 等)
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
* @note 为所有按键注册事件回调
|
||||
*/
|
||||
esp_err_t bsp_keyboard_add_event_cb(keyboard_event_cb_t cb, button_event_t event_type);
|
||||
|
||||
/**
|
||||
* @brief 指定按键注册事件回调
|
||||
* @param button_index 按键索引 (0-43)
|
||||
* @param cb 回调函数
|
||||
* @param event_type 事件类型 (BUTTON_PRESS_DOWN, BUTTON_PRESS_UP, BUTTON_SINGLE_CLICK 等)
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
* @note 为指定按键注册事件回调
|
||||
*/
|
||||
esp_err_t bsp_keyboard_add_event_cb_by_index(int button_index, keyboard_event_cb_t cb, button_event_t event_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*******************************************************************************
|
||||
|
||||
*******************************************************************************/
|
||||
#include "bsp_rgb_led.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
static const char *TAG = "bsp/rgb_led";
|
||||
|
||||
// RGB LED 配置
|
||||
#define RGB_R_PIN GPIO_NUM_27
|
||||
#define RGB_G_PIN GPIO_NUM_28
|
||||
#define RGB_B_PIN GPIO_NUM_9
|
||||
#define LEDC_TIMER LEDC_TIMER_0
|
||||
#define LEDC_MODE LEDC_LOW_SPEED_MODE
|
||||
#define LEDC_CHANNEL_R LEDC_CHANNEL_0
|
||||
#define LEDC_CHANNEL_G LEDC_CHANNEL_1
|
||||
#define LEDC_CHANNEL_B LEDC_CHANNEL_2
|
||||
#define LEDC_DUTY_RES LEDC_TIMER_8_BIT
|
||||
#define LEDC_FREQUENCY 5000
|
||||
|
||||
// 当前 LED 颜色值(用于单独设置通道)
|
||||
static uint8_t current_r = 0;
|
||||
static uint8_t current_g = 0;
|
||||
static uint8_t current_b = 0;
|
||||
|
||||
|
||||
esp_err_t bsp_rgb_led_init(void)
|
||||
{
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.speed_mode = LEDC_MODE,
|
||||
.timer_num = LEDC_TIMER,
|
||||
.duty_resolution = LEDC_DUTY_RES,
|
||||
.freq_hz = LEDC_FREQUENCY,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
};
|
||||
esp_err_t ret = ledc_timer_config(&ledc_timer);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "LEDC timer config failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ledc_channel_config_t ledc_channel_r = {
|
||||
.speed_mode = LEDC_MODE,
|
||||
.channel = LEDC_CHANNEL_R,
|
||||
.timer_sel = LEDC_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = RGB_R_PIN,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
};
|
||||
ret = ledc_channel_config(&ledc_channel_r);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Red channel config failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ledc_channel_config_t ledc_channel_g = {
|
||||
.speed_mode = LEDC_MODE,
|
||||
.channel = LEDC_CHANNEL_G,
|
||||
.timer_sel = LEDC_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = RGB_G_PIN,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
};
|
||||
ret = ledc_channel_config(&ledc_channel_g);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Green channel config failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ledc_channel_config_t ledc_channel_b = {
|
||||
.speed_mode = LEDC_MODE,
|
||||
.channel = LEDC_CHANNEL_B,
|
||||
.timer_sel = LEDC_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = RGB_B_PIN,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
};
|
||||
ret = ledc_channel_config(&ledc_channel_b);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Blue channel config failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "RGB LED initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void bsp_rgb_led_set_color(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
current_r = r;
|
||||
current_g = g;
|
||||
current_b = b;
|
||||
|
||||
// 共阳极 LED,需要反转 PWM 值
|
||||
uint32_t duty_r = 256 - r;
|
||||
uint32_t duty_g = 256 - g;
|
||||
uint32_t duty_b = 256 - b;
|
||||
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_R, duty_r);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_R);
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_G, duty_g);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_G);
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_B, duty_b);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_B);
|
||||
}
|
||||
|
||||
void bsp_rgb_led_set_red(uint8_t r)
|
||||
{
|
||||
current_r = r;
|
||||
uint32_t duty_r = 256 - r;
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_R, duty_r);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_R);
|
||||
}
|
||||
|
||||
void bsp_rgb_led_set_green(uint8_t g)
|
||||
{
|
||||
current_g = g;
|
||||
uint32_t duty_g = 256 - g;
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_G, duty_g);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_G);
|
||||
}
|
||||
|
||||
void bsp_rgb_led_set_blue(uint8_t b)
|
||||
{
|
||||
current_b = b;
|
||||
uint32_t duty_b = 256 - b;
|
||||
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_B, duty_b);
|
||||
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_B);
|
||||
}
|
||||
|
||||
void bsp_rgb_led_get_color(uint8_t *r, uint8_t *g, uint8_t *b)
|
||||
{
|
||||
if (r) *r = current_r;
|
||||
if (g) *g = current_g;
|
||||
if (b) *b = current_b;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化 RGB LED
|
||||
* @return ESP_OK 成功, 其他失败
|
||||
*/
|
||||
esp_err_t bsp_rgb_led_init(void);
|
||||
|
||||
/**
|
||||
* @brief 设置 RGB LED 颜色
|
||||
* @param r 红色亮度值 (0-255)
|
||||
* @param g 绿色亮度值 (0-255)
|
||||
* @param b 蓝色亮度值 (0-255)
|
||||
*/
|
||||
void bsp_rgb_led_set_color(uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
/**
|
||||
* @brief 单独设置 RGB LED 的红色通道
|
||||
* @param r 红色亮度值 (0-255)
|
||||
*/
|
||||
void bsp_rgb_led_set_red(uint8_t r);
|
||||
|
||||
/**
|
||||
* @brief 单独设置 RGB LED 的绿色通道
|
||||
* @param g 绿色亮度值 (0-255)
|
||||
*/
|
||||
void bsp_rgb_led_set_green(uint8_t g);
|
||||
|
||||
/**
|
||||
* @brief 单独设置 RGB LED 的蓝色通道
|
||||
* @param b 蓝色亮度值 (0-255)
|
||||
*/
|
||||
void bsp_rgb_led_set_blue(uint8_t b);
|
||||
|
||||
/**
|
||||
* @brief 获取当前 RGB LED 的颜色值
|
||||
* @param r 红色通道输出指针
|
||||
* @param g 绿色通道输出指针
|
||||
* @param b 蓝色通道输出指针
|
||||
*/
|
||||
void bsp_rgb_led_get_color(uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
|
||||
#include "bsp_uart.h"
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "bsp/uart";
|
||||
|
||||
static bool s_uart_initialized = false;
|
||||
static uart_cmd_callback_t s_cmd_callback = NULL;
|
||||
static TaskHandle_t s_recv_task_handle = NULL;
|
||||
|
||||
// UART 接收任务
|
||||
static void uart_recv_task(void *arg)
|
||||
{
|
||||
uint8_t *data = (uint8_t *)malloc(BSP_UART_BUF_SIZE);
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate UART buffer");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int len = uart_read_bytes(BSP_UART_NUM, data, BSP_UART_BUF_SIZE - 1, pdMS_TO_TICKS(100));
|
||||
if (len > 0) {
|
||||
data[len] = '\0';
|
||||
ESP_LOGD(TAG, "UART RX: %s", data);
|
||||
|
||||
if (s_cmd_callback) {
|
||||
s_cmd_callback((const char *)data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(data);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t bsp_uart_init(void)
|
||||
{
|
||||
if (s_uart_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = BSP_UART_BAUD_RATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t ret = uart_driver_install(BSP_UART_NUM, BSP_UART_BUF_SIZE * 2, 0, 0, NULL, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_driver_install failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uart_param_config(BSP_UART_NUM, &uart_config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_param_config failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uart_set_pin(BSP_UART_NUM, BSP_UART_TX_PIN, BSP_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_set_pin failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_uart_initialized = true;
|
||||
ESP_LOGD(TAG, "UART initialized (TX:%d, RX:%d, Baud:%d)", BSP_UART_TX_PIN, BSP_UART_RX_PIN, BSP_UART_BAUD_RATE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bsp_uart_deinit(void)
|
||||
{
|
||||
if (!s_uart_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// 停止并删除接收任务
|
||||
if (s_recv_task_handle) {
|
||||
vTaskDelete(s_recv_task_handle);
|
||||
s_recv_task_handle = NULL;
|
||||
}
|
||||
|
||||
// 删除 UART 驱动
|
||||
esp_err_t ret = uart_driver_delete(BSP_UART_NUM);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "uart_driver_delete failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s_uart_initialized = false;
|
||||
ESP_LOGD(TAG, "UART 已反初始化");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int bsp_uart_send(const uint8_t *data, int len)
|
||||
{
|
||||
if (!s_uart_initialized || data == NULL || len <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return uart_write_bytes(BSP_UART_NUM, data, len);
|
||||
}
|
||||
|
||||
int bsp_uart_send_str(const char *str)
|
||||
{
|
||||
if (!s_uart_initialized || str == NULL) {
|
||||
return -1;
|
||||
}
|
||||
return uart_write_bytes(BSP_UART_NUM, str, strlen(str));
|
||||
}
|
||||
|
||||
int bsp_uart_printf(const char *fmt, ...)
|
||||
{
|
||||
if (!s_uart_initialized || fmt == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[BSP_UART_BUF_SIZE];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (len > 0) {
|
||||
return uart_write_bytes(BSP_UART_NUM, buf, len);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void bsp_uart_set_cmd_callback(uart_cmd_callback_t cb)
|
||||
{
|
||||
s_cmd_callback = cb;
|
||||
}
|
||||
|
||||
esp_err_t bsp_uart_start_recv_task(void)
|
||||
{
|
||||
if (!s_uart_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (s_recv_task_handle != NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
BaseType_t ret = xTaskCreate(uart_recv_task, "uart_recv", 8192, NULL, 10, &s_recv_task_handle);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create UART recv task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef _BSP_UART_H_
|
||||
#define _BSP_UART_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// UART 配置
|
||||
#define BSP_UART_NUM UART_NUM_1
|
||||
#define BSP_UART_TX_PIN 25 // GPIO 25 (与 I2C SCL 共用)
|
||||
#define BSP_UART_RX_PIN 26 // GPIO 26 (与 I2C SDA 共用)
|
||||
#define BSP_UART_BAUD_RATE 115200
|
||||
#define BSP_UART_BUF_SIZE 256
|
||||
|
||||
// 命令回调函数类型
|
||||
typedef void (*uart_cmd_callback_t)(const char *cmd, int len);
|
||||
|
||||
/**
|
||||
* @brief 初始化 UART
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t bsp_uart_init(void);
|
||||
|
||||
/**
|
||||
* @brief 反初始化 UART
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t bsp_uart_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief 发送数据
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
* @return 发送的字节数
|
||||
*/
|
||||
int bsp_uart_send(const uint8_t *data, int len);
|
||||
|
||||
/**
|
||||
* @brief 发送字符串
|
||||
* @param str 字符串
|
||||
* @return 发送的字节数
|
||||
*/
|
||||
int bsp_uart_send_str(const char *str);
|
||||
|
||||
/**
|
||||
* @brief 格式化发送
|
||||
* @param fmt 格式字符串
|
||||
* @return 发送的字节数
|
||||
*/
|
||||
int bsp_uart_printf(const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief 注册命令回调
|
||||
* @param cb 回调函数
|
||||
*/
|
||||
void bsp_uart_set_cmd_callback(uart_cmd_callback_t cb);
|
||||
|
||||
/**
|
||||
* @brief 启动UART接收任务
|
||||
* @return ESP_OK 成功
|
||||
*/
|
||||
esp_err_t bsp_uart_start_recv_task(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _BSP_UART_H_ */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user