Add source code.

This commit is contained in:
luoweiyuan
2026-04-03 15:05:23 +08:00
parent 65a0115881
commit 444b5bdcbd
26 changed files with 12468 additions and 38 deletions
+25 -38
View File
@@ -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).
+7
View File
@@ -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)
+35
View File
@@ -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
+29
View File
@@ -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
+72
View File
@@ -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
+404
View File
@@ -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;
}
+127
View File
@@ -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
+133
View File
@@ -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_ */
+154
View File
@@ -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;
}
+50
View File
@@ -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_ */
+255
View File
@@ -0,0 +1,255 @@
/*******************************************************************************
* M5Stack Unit CardKB2 I2C 从机 BSP
*
* I2C 从机配置:
* - 地址:0x5F
* - SCLGPIO 25
* - SDAGPIO 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);
}
}
+38
View File
@@ -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
+97
View File
@@ -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);
}
+47
View File
@@ -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
+142
View File
@@ -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;
}
+53
View File
@@ -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
+164
View File
@@ -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;
}
+73
View File
@@ -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