diff --git a/main/02_physical/boards/board_sweekar.h b/main/02_physical/boards/board_sweekar.h new file mode 100644 index 0000000..7c60516 --- /dev/null +++ b/main/02_physical/boards/board_sweekar.h @@ -0,0 +1,25 @@ +#ifndef BOARD_SELECTION_H +#define BOARD_SELECTION_H + +#include "sdkconfig.h" + +// 根据menuconfig的配置选择不同的board版本 +#ifdef CONFIG_BOARD_VERSION_V1_1_11 + #include "02_physical/boards/board_v1_1_11/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_12) + #include "02_physical/boards/board_v1_1_12/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_13) + #include "02_physical/boards/board_v1_1_13/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_14) + #include "02_physical/boards/board_v1_1_14/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_16) + #include "02_physical/boards/board_v1_1_16/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_17) + #include "02_physical/boards/board_v1_1_17/board_sweekar.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_18) + #include "02_physical/boards/board_v1_1_18/board_sweekar.h" +#else + #include "02_physical/boards/board_v1_1_11/board_sweekar.h" +#endif + +#endif // BOARD_SELECTION_H \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_11/board_sweekar.cc b/main/02_physical/boards/board_v1_1_11/board_sweekar.cc new file mode 100644 index 0000000..9915adf --- /dev/null +++ b/main/02_physical/boards/board_v1_1_11/board_sweekar.cc @@ -0,0 +1,261 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +} + +#include "board_sweekar.h" +#include "config.h" +#include "drv8428.h" +#include "aw8646.h" + +#define TAG "board_sweekar" + +BoardSweekar::BoardSweekar() +{ +#ifdef CONFIG_STEPPER_DRIVER_AW8646 + stepper_driver_ = new Aw8646Driver(io_expansion_); +#else + stepper_driver_ = new Drv8428Driver(io_expansion_); +#endif +} + +BoardSweekar::~BoardSweekar() +{ + if (stepper_driver_) { + delete stepper_driver_; + stepper_driver_ = nullptr; + } +} + +/** + * @file board.h + * @brief 板级外设初始化类 + */ +/** + * @brief 步进电机初始化函数 + * + * 配置LEDC定时器和通道用于PWM输出,设置步进电机的工作模式、使能状态、细分模式和方向, + * 并初始化PWM占空比为50%。初始化过程中若出现错误,会通过ESP_LOGE输出错误信息及错误码。 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 初始化驱动对象并初始化 + if (stepper_driver_) { + err = stepper_driver_->Init(); + if (err != ESP_OK) { + ESP_LOGE(TAG_BOARD, "步进电机驱动初始化失败, 错误码: 0x%x", err); + } + } else { + ESP_LOGE(TAG_BOARD, "步进电机驱动创建失败"); + } +} + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = 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, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 为线性马达设置总线句柄和设备地址(使用7位地址) + linear_motor_.bus_handle_ = bus_handle_; + linear_motor_.device_addr_ = LINEAR_MOTOR_I2C_SLAVE_ADDR << 1; + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + + + + diff --git a/main/02_physical/boards/board_v1_1_11/board_sweekar.h b/main/02_physical/boards/board_v1_1_11/board_sweekar.h new file mode 100644 index 0000000..d644169 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_11/board_sweekar.h @@ -0,0 +1,46 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "stepper_driver.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" + +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + +public: + BoardSweekar(); + ~BoardSweekar(); + + IOExpansionBoard io_expansion_; // IO扩展板对象 + LinearMotor linear_motor_; // 线性马达对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + StepperDriver* stepper_driver_ = nullptr; // 步进电机驱动对象 + + StepperDriver* GetStepperDriver() const { return stepper_driver_; } + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_11/config.h b/main/02_physical/boards/board_v1_1_11/config.h new file mode 100644 index 0000000..36fd4b7 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_11/config.h @@ -0,0 +1,138 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/timer.h" +#include "esp_intr_alloc.h" +#include "soc/timer_group_struct.h" // 包含TIMERG0、TIMERG1的结构体定义 +#include "soc/timer_group_reg.h" // 包含定时器相关寄存器宏定义 +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机引脚设置 */ +#define LEDC_OUTPUT_IO GPIO_NUM_42 // 步进电机LEDC引脚输入 + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_40 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_NC // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART0 +#define UART_TX_PIN GPIO_NUM_NC // GPIO43 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO44 作为 RX +#define BAUD_RATE 9600 // 波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x5B // AW9523BTQR器件地址 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* 原生触摸引脚配置 */ +// 触摸按键数量配置 +// #define TOUCH_BUTTON_NUM 4 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 +// #define TOUCH_PAD_CHANNEL_4 TOUCH_PAD_NUM14 // GPIO14 + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_8 + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_8 // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 + +#endif // _BOARD_CONFIG_H_ + + + + + + + + + + + + + + + + diff --git a/main/02_physical/boards/board_v1_1_11/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_11/io_expansion_board.cc new file mode 100644 index 0000000..3c3e978 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_11/io_expansion_board.cc @@ -0,0 +1,732 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // // 唤醒挂起的I2C任务 + // if (instance->xI2CReadTaskHandle_) { + // if (xTaskResumeFromISR(instance->xI2CReadTaskHandle_) == pdTRUE) { + // xHigherPriorityTaskWoken = pdTRUE; + // } + // } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_NEGEDGE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + // 配置P0_0-P0_7的端口为输入还是输出:0-输出;1-输入 + // err = IoexpandWriteReg(CONFIG_PORT0, 0x04); // 0000 0100 + err = IoexpandWriteReg(CONFIG_PORT0, 0x24); // 0010 0100 + // err = IoexpandWriteReg(CONFIG_PORT0, 0x2C); // 0010 1100 0-2,0-3,0-5都是输入 + + // D 1101 + // 9 1001 + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + // 配置P1_0-P1_7的端口输出:0-输出;1-输入 + // err = IoexpandWriteReg(CONFIG_PORT1, 0x84); + err = IoexpandWriteReg(CONFIG_PORT1, 0x80); // 1000 0000 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功"); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + err = IoexpandWriteReg(OUTPUT_PORT0, 0xFD); // 1111 1101 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x7E); // 0111 1110 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + err = IoexpandWriteReg(INT_PORT1, 0x7F); // 0111 1111 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功"); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT, uint8_t m0_pin, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + /* code */ + printf("设置步进电机为全步模式\n"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + /* code */ + printf("设置步进电机为1/8步模式\n"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << bl_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << bl_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << reset_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << reset_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << interrupt_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << interrupt_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_CS引脚电平(低电平为选中状态,高电平为不选中状态) + */ +esp_err_t IOExpansionBoard::SetLcdCsMode(bool mode, uint8_t OUTPUT_PORT, uint8_t cs_pin) +{ + uint8_t current_val; + if(mode){ + // 进入选中状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << cs_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入不选中状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << cs_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 + instance->IoexpandSetMode(); + // 设置LCD屏幕背光引脚(初始为关闭) + err = instance->SetLcdBlMode(LCD_BL_HIGH, LCD_BL_PORT, LCD_BL_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕背光引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕背光引脚成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + uint8_t current_val; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + // 读取I2C数据 + instance->IoexpandReadReg(INPUT_PORT1,¤t_val); + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "读取数据: 0x%0X", current_val); + if(current_val & (1 << 7)){ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚(P1_7)\n"); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + } + + // vTaskSuspend(nullptr); // 挂起任务等待下次唤醒 + } +} + +// bool IOExpansionBoard::AllocateStaticTaskMemory() +// { +// printf("IO拓展芯片创建任务前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务前 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// // 分配任务栈(PSRAM) +// const size_t TASK_STACK_SIZE = 4096; +// pI2CReadTaskStackBuffer = (StackType_t*)heap_caps_malloc( +// TASK_STACK_SIZE * sizeof(StackType_t), +// MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT +// ); + +// if (!pI2CReadTaskStackBuffer) { +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task stack from PSRAM"); +// return false; +// } + +// // 分配任务控制块(内部RAM) +// pxI2CReadTaskBuffer = (StaticTask_t*)pvPortMalloc(sizeof(StaticTask_t)); +// if (!pxI2CReadTaskBuffer) { +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task TCB from internal RAM"); +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// return false; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory allocated successfully"); +// printf("IO拓展芯片创建任务后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务后 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// return true; +// } + +// void IOExpansionBoard::FreeStaticTaskMemory() +// { +// if (pI2CReadTaskStackBuffer != nullptr) { +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// } + +// if (pxI2CReadTaskBuffer != nullptr) { +// vPortFree(pxI2CReadTaskBuffer); +// pxI2CReadTaskBuffer = nullptr; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory freed"); +// } + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_11/io_expansion_board.h b/main/02_physical/boards/board_v1_1_11/io_expansion_board.h new file mode 100644 index 0000000..baa836d --- /dev/null +++ b/main/02_physical/boards/board_v1_1_11/io_expansion_board.h @@ -0,0 +1,284 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_PORT CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +/* 设置LCD的BL引脚 */ +#define LCD_BL_PIN 6 //LCD背光引脚 +#define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +#define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置LCD的CS引脚 */ +#define LCD_CS_PIN 2 //LCD片选引脚 +#define LCD_CS_PORT OUTPUT_PORT1 //LCD片选引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_CS_HIGH true //LCD片选引脚状态为高电平 +#define LCD_CS_LOW false //LCD片选引脚状态为低电平 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT, uint8_t m0_pin, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置LCD的CS引脚状态 + * @param CS状态(低电平为选中状态,高电平为不选中状态) + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdCsMode(bool mode, uint8_t OUTPUT_PORT, uint8_t cs_pin); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_12/board_sweekar.cc b/main/02_physical/boards/board_v1_1_12/board_sweekar.cc new file mode 100644 index 0000000..2244670 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_12/board_sweekar.cc @@ -0,0 +1,257 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +} + +#include "board_sweekar.h" +#include "config.h" +#define TAG "board_sweekar" +/** + * @file board.h + * @brief 板级外设初始化类 + */ +/** + * @brief 步进电机初始化函数 + * + * 配置LEDC定时器和通道用于PWM输出,设置步进电机的工作模式、使能状态、细分模式和方向, + * 并初始化PWM占空比为50%。初始化过程中若出现错误,会通过ESP_LOGE输出错误信息及错误码。 + */ +void BoardSweekar::StepperMotorInit(void) +{ + // 配置LEDC用于PWM输出 + ledc_timer_config_t ledc_timer = { + .speed_mode = LEDC_MODE, + .duty_resolution = LEDC_DUTY_RES, + .timer_num = LEDC_TIMER, + .freq_hz = LEDC_FREQ_HZ, + .clk_cfg = LEDC_USE_APB_CLK, + }; + esp_err_t err = ledc_timer_config(&ledc_timer); + if (err != ESP_OK){ + ESP_LOGE(TAG_BOARD, "步进电机LEDC定时器配置失败,错误码: 0x%x", err); + } + + // 配置LEDC通道 + ledc_channel_config_t ledc_channel = { + .gpio_num = LEDC_OUTPUT_IO, + .speed_mode = LEDC_MODE, + .channel = LEDC_CHANNEL, + .timer_sel = LEDC_TIMER, + .duty = 0, // 初始占空比0 + .hpoint = 0 + }; + err = ledc_channel_config(&ledc_channel); + if (err != ESP_OK){ + ESP_LOGE(TAG_BOARD, "步进电机LEDC通道配置失败,错误码: 0x%x", err); + } + + // 检查I2C总线是否已初始化 + if (bus_handle_ == nullptr) { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 设置步进电机工作模式,使能电机,设置步进电机细分模式,设置步进电机方向 + err = io_expansion_.SetMotorSleepMode(STEPPER_MOTOR_WORK, STEPPER_MOTOR_SLEEP_PORT, STEPPER_MOTOR_WORK); + if (err != ESP_OK){ + ESP_LOGE(TAG_BOARD, "步进电机初始化失败,错误码: 0x%x", err); + } + err = io_expansion_.SetMotorEnableMode(STEPPER_MOTOR_ENABLE_DISABLE, STEPPER_MOTOR_ENABLE_PORT, STEPPER_MOTOR_ENABLE_PIN); + if (err != ESP_OK){ + ESP_LOGE(TAG_BOARD, "步进电机初始化失败,错误码: 0x%x", err); + } + io_expansion_.SetStepperMicrostepMode(4, STEPPER_MOTOR_MADE_PORT, STEPPER_MOTOR_M0_PIN, STEPPER_MOTOR_M1_PIN); + err = io_expansion_.StepperMotorSetDir(STEPPER_MOTOR_LEFT, STEPPER_MOTOR_DIR_PORT, STEPPER_MOTOR_DIR_PIN); + if (err != ESP_OK){ + ESP_LOGE(TAG_BOARD, "步进电机初始化失败,错误码: 0x%x", err); + } + + // 初始PWM占空比设为0% + ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, 0); + ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); +} + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_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, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 为线性马达设置总线句柄和设备地址(使用7位地址) + linear_motor_.bus_handle_ = bus_handle_; + linear_motor_.device_addr_ = LINEAR_MOTOR_I2C_SLAVE_ADDR << 1; + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + + + + diff --git a/main/02_physical/boards/board_v1_1_12/board_sweekar.h b/main/02_physical/boards/board_v1_1_12/board_sweekar.h new file mode 100644 index 0000000..72492a9 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_12/board_sweekar.h @@ -0,0 +1,38 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" + +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + +public: + IOExpansionBoard io_expansion_; // IO扩展板对象 + LinearMotor linear_motor_; // 线性马达对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_12/config.h b/main/02_physical/boards/board_v1_1_12/config.h new file mode 100644 index 0000000..1f2b6ba --- /dev/null +++ b/main/02_physical/boards/board_v1_1_12/config.h @@ -0,0 +1,139 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/timer.h" +#include "esp_intr_alloc.h" +#include "soc/timer_group_struct.h" // 包含TIMERG0、TIMERG1的结构体定义 +#include "soc/timer_group_reg.h" // 包含定时器相关寄存器宏定义 +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机引脚设置 */ +#define LEDC_OUTPUT_IO GPIO_NUM_42 // 步进电机LEDC引脚输入 + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_NC // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO43 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO44 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x5B // AW9523BTQR器件地址 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* 原生触摸引脚配置 */ +// 触摸按键数量配置 +// #define TOUCH_BUTTON_NUM 4 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 +// #define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 +#define TOUCH_PAD_CHANNEL_4 TOUCH_PAD_NUM14 // GPIO14 + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +#endif // _BOARD_CONFIG_H_ + + + + + + + + + + + + + + + + diff --git a/main/02_physical/boards/board_v1_1_12/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_12/io_expansion_board.cc new file mode 100644 index 0000000..1c8a964 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_12/io_expansion_board.cc @@ -0,0 +1,724 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // // 唤醒挂起的I2C任务 + // if (instance->xI2CReadTaskHandle_) { + // if (xTaskResumeFromISR(instance->xI2CReadTaskHandle_) == pdTRUE) { + // xHigherPriorityTaskWoken = pdTRUE; + // } + // } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_NEGEDGE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + // 配置P0_0-P0_7的端口为输入还是输出:0-输出;1-输入 + // err = IoexpandWriteReg(CONFIG_PORT0, 0x04); // 0000 0100 + err = IoexpandWriteReg(CONFIG_PORT0, 0x24); // 0010 0100 + // err = IoexpandWriteReg(CONFIG_PORT0, 0x2C); // 0010 1100 0-2,0-3,0-5都是输入 + + // D 1101 + // 9 1001 + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + // 配置P1_0-P1_7的端口输出:0-输出;1-输入 + // err = IoexpandWriteReg(CONFIG_PORT1, 0x84); + err = IoexpandWriteReg(CONFIG_PORT1, 0x80); // 1000 0000 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功"); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + err = IoexpandWriteReg(OUTPUT_PORT0, 0xFD); // 1111 1101 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x7E); // 0111 1110 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + err = IoexpandWriteReg(INT_PORT1, 0x7F); // 0111 1111 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功"); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT, uint8_t m0_pin, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + /* code */ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m0_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << m1_pin); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + if(IoexpandWriteReg(OUTPUT_PORT, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << bl_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << bl_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << reset_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << reset_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << interrupt_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << interrupt_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_CS引脚电平(低电平为选中状态,高电平为不选中状态) + */ +esp_err_t IOExpansionBoard::SetLcdCsMode(bool mode, uint8_t OUTPUT_PORT, uint8_t cs_pin) +{ + uint8_t current_val; + if(mode){ + // 进入选中状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val &= ~(1 << cs_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入不选中状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + ESP_LOGW(TAG_I2C_IO_EXPAND_TASK, "0x%x", current_val); + current_val |= (1 << cs_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 + instance->IoexpandSetMode(); + + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + instance->IoexpandWriteReg(OUTPUT_PORT0, 0xff); + uint8_t current_val; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + // 读取I2C数据 + instance->IoexpandReadReg(INPUT_PORT1,¤t_val); + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "读取数据: 0x%0X", current_val); + if(current_val & (1 << 7)){ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚(P1_7)\n"); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + } + + // vTaskSuspend(nullptr); // 挂起任务等待下次唤醒 + } +} + +// bool IOExpansionBoard::AllocateStaticTaskMemory() +// { +// printf("IO拓展芯片创建任务前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务前 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// // 分配任务栈(PSRAM) +// const size_t TASK_STACK_SIZE = 4096; +// pI2CReadTaskStackBuffer = (StackType_t*)heap_caps_malloc( +// TASK_STACK_SIZE * sizeof(StackType_t), +// MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT +// ); + +// if (!pI2CReadTaskStackBuffer) { +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task stack from PSRAM"); +// return false; +// } + +// // 分配任务控制块(内部RAM) +// pxI2CReadTaskBuffer = (StaticTask_t*)pvPortMalloc(sizeof(StaticTask_t)); +// if (!pxI2CReadTaskBuffer) { +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task TCB from internal RAM"); +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// return false; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory allocated successfully"); +// printf("IO拓展芯片创建任务后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务后 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// return true; +// } + +// void IOExpansionBoard::FreeStaticTaskMemory() +// { +// if (pI2CReadTaskStackBuffer != nullptr) { +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// } + +// if (pxI2CReadTaskBuffer != nullptr) { +// vPortFree(pxI2CReadTaskBuffer); +// pxI2CReadTaskBuffer = nullptr; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory freed"); +// } + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_12/io_expansion_board.h b/main/02_physical/boards/board_v1_1_12/io_expansion_board.h new file mode 100644 index 0000000..0a3f47b --- /dev/null +++ b/main/02_physical/boards/board_v1_1_12/io_expansion_board.h @@ -0,0 +1,284 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_PORT CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +/* 设置LCD的BL引脚 */ +#define LCD_BL_PIN 6 //LCD背光引脚 +#define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +#define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置LCD的CS引脚 */ +#define LCD_CS_PIN 2 //LCD片选引脚 +#define LCD_CS_PORT OUTPUT_PORT1 //LCD片选引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_CS_HIGH true //LCD片选引脚状态为高电平 +#define LCD_CS_LOW false //LCD片选引脚状态为低电平 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void setRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT, uint8_t m0_pin, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置LCD的CS引脚状态 + * @param CS状态(低电平为选中状态,高电平为不选中状态) + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdCsMode(bool mode, uint8_t OUTPUT_PORT, uint8_t cs_pin); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_13/board_sweekar.cc b/main/02_physical/boards/board_v1_1_13/board_sweekar.cc new file mode 100644 index 0000000..88a1db3 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_13/board_sweekar.cc @@ -0,0 +1,311 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "driver/gptimer.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +} + +#include "board_sweekar.h" +#include "config.h" +#define TAG "board_sweekar" + +/*========================== 步进电机相关驱动 ==========================*/ + +/** + * @brief 步进电机初始化函数 + * 配置步进电机所需的GPIO引脚模式,通过IO扩展板设置步进驱动芯片的工作模式 + * 在 stepper_motor.cc 中完成 GPTimer + GPIO 翻转 STEP 脉冲的初始化 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO(不再用 LEDC) + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低,保证上电不乱跳 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 通过 IO 扩展板配置步进驱动芯片:睡眠/使能/细分/方向 + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_WORK, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_WORK + ); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机睡眠模式配置失败, 错误码: 0x%x", err); + } + + err = io_expansion_.SetMotorEnableMode( + STEPPER_MOTOR_ENABLE_DISABLE, + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机使能配置失败, 错误码: 0x%x", err); + } + + // 细分设置:这里仍然用原来的 1/8 细分 + io_expansion_.SetStepperMicrostepMode( + 4, + STEPPER_MOTOR_M0_MADE_PORT, + STEPPER_MOTOR_M0_PIN, + STEPPER_MOTOR_M1_MADE_PORT, + STEPPER_MOTOR_M1_PIN + ); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机细分模式配置失败, 错误码: 0x%x", err); + } + + // 默认方向先设一个(比如“上升”方向) + err = io_expansion_.StepperMotorSetDir( + STEPPER_MOTOR_LEFT, + STEPPER_MOTOR_DIR_PORT, + STEPPER_MOTOR_DIR_PIN + ); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机方向配置失败, 错误码: 0x%x", err); + } +} + +/*========================== 毫米波雷达相关驱动 ==========================*/ + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_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, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/*========================== I2C总线及挂载的从设备相关驱动 ==========================*/ + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 为线性马达设置总线句柄和设备地址(使用7位地址) + linear_motor_.bus_handle_ = bus_handle_; + linear_motor_.device_addr_ = LINEAR_MOTOR_I2C_SLAVE_ADDR << 1; + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/*========================== VBAT to VBAT_5V相关驱动 ==========================*/ + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + +/*========================== 底座控制串口相关驱动 ==========================*/ + +/** + * @brief 底座控制命令串口初始化函数 + */ +void BoardSweekar::BaseBoardControlUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = BASE_BOARD_CTL_UART_BAUDRATE, + .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, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(BASE_BOARD_CTL_UART_PORT, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(BASE_BOARD_CTL_UART_PORT, BASE_BOARD_CTL_UART_TX_PIN, BASE_BOARD_CTL_UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动 + ESP_ERROR_CHECK(uart_driver_install(BASE_BOARD_CTL_UART_PORT, + BASE_BOARD_CTL_UART_BUF_SIZE, + BASE_BOARD_CTL_UART_BUF_SIZE, + 0, NULL, 0)); + + base_board_control_.SetUartPort(BASE_BOARD_CTL_UART_PORT); +} + + + diff --git a/main/02_physical/boards/board_v1_1_13/board_sweekar.h b/main/02_physical/boards/board_v1_1_13/board_sweekar.h new file mode 100644 index 0000000..ba6e3c4 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_13/board_sweekar.h @@ -0,0 +1,45 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" +#include "base_board_control.h" + +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + +public: + IOExpansionBoard io_expansion_; // IO扩展板对象 + LinearMotor linear_motor_; // 线性马达对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + BaseBoardControl base_board_control_; // 底座控制对象 + + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 初始化底座控制串口 + void BaseBoardControlUartInit(void); + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_13/config.h b/main/02_physical/boards/board_v1_1_13/config.h new file mode 100644 index 0000000..525c6d3 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_13/config.h @@ -0,0 +1,145 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/timer.h" +#include "esp_intr_alloc.h" +#include "soc/timer_group_struct.h" // 包含TIMERG0、TIMERG1的结构体定义 +#include "soc/timer_group_reg.h" // 包含定时器相关寄存器宏定义 +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机配置 */ +#define STEPPER_STEP_IO GPIO_NUM_42 // 步进电机脉冲输入引脚 +#define STEPPER_TIMER_RES_HZ 1000000 // 步进电机定时器分辨率 1 MHz(1 us 步进) + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_NC // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO40 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO39 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x58 // AW9523BTQR器件地址,v1_1_13的addr1和addr0是0 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* 原生触摸引脚配置 */ +// 触摸按键数量配置 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +/* 底座控制命令串口配置 */ +#define BASE_BOARD_CTL_UART_PORT UART_NUM_0 +#define BASE_BOARD_CTL_UART_TX_PIN GPIO_NUM_43 +#define BASE_BOARD_CTL_UART_RX_PIN GPIO_NUM_44 +#define BASE_BOARD_CTL_UART_BAUDRATE 115200 +#define BASE_BOARD_CTL_UART_BUF_SIZE 256 + +#endif // _BOARD_CONFIG_H_ + + + + + + + + + + + + + + + + diff --git a/main/02_physical/boards/board_v1_1_13/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_13/io_expansion_board.cc new file mode 100644 index 0000000..dc31200 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_13/io_expansion_board.cc @@ -0,0 +1,1053 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "photo_interrupter.h" +#include "adc_battery_monitor.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + last_port0_state_(0), + last_port1_state_(0), + radar_last_state_(false), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr), + is_initialized_(false), + interrupt_gpio_(GPIO_NUM_NC), + reset_gpio_(GPIO_NUM_NC) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + + // 调试:记录中断触发 + // ESP_EARLY_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发!"); // IRAM函数中不能使用普通日志 + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_ANYEDGE; // 改为双沿触发,检测上升沿和下降沿 + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + // io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + // io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 计算Port0的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort0() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT0){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT0){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT0){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT0){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT0){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 计算Port1的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort1() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT1){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT1){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT1){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT1){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT1){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +/** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort0(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT0, data); +} + +/** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort1(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT1, data); +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + + err = IoexpandWriteReg(CONFIG_PORT0, ByteCalculatePort0()); + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort0()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功, 0x%02X", ByteCalculatePort0()); + } + + err = IoexpandWriteReg(CONFIG_PORT1, ByteCalculatePort1()); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort1()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功, 0x%02X", ByteCalculatePort1()); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + err = IoexpandWriteReg(OUTPUT_PORT0, 0x40); // 0100 0000 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x04); // 0000 0100 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + + // AW9523B中断使能配置:0为使能中断,1为禁止中断 + // 使能P0_5(充电检测)和P1_3(充满检测)的中断 + uint8_t int_enable_port0 = 0x00; // 0x00 = 使能所有P0引脚中断 + uint8_t int_enable_port1 = 0x00; // 0x00 = 使能所有P1引脚中断 + + err = IoexpandWriteReg(INT_PORT0, int_enable_port0); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能成功: 0x%02X (0=使能)", int_enable_port0); + } + + err = IoexpandWriteReg(INT_PORT1, int_enable_port1); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功: 0x%02X (0=使能)", int_enable_port1); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << enable_pin); + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << enable_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << nsleep_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << nsleep_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << reset_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << reset_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val |= (1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val &= ~(1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 PA_CTRL引脚电平 + */ +esp_err_t IOExpansionBoard::SetPaCtrlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t pa_ctrl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入高电平状态 + ESP_LOGI("io_ex:", "read reg"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pa_ctrl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入低电平状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pa_ctrl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + + +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + + // 执行硬件复位 - 这是解决中断引脚持续拉低的关键 + err = instance->ResetIoExpand(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位成功"); + + // 检查复位后的中断引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "复位后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + } + + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 - 复位后必须重新配置寄存器! + err = instance->IoexpandSetMode(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置成功"); + } + // 设置LCD屏幕背光引脚(初始为关闭) + err = instance->SetLcdBlMode(LCD_BL_HIGH, LCD_BL_PORT, LCD_BL_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕背光引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕背光引脚成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + // 设置LCD屏幕复位引脚为低电平 + err = instance->SetLcdResetMode(LCD_RST_LOW, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + + uint8_t current_val_port0_ = 0,current_val_port1_ = 0,current_temp_ = 0; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板中断触发"); + + // 检查中断触发时的引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发时引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + // AW9523B中断处理说明(根据数据手册): + // 1. 当输入引脚状态变化时,AW9523B会拉低INT引脚(连接到ESP32的GPIO10) + // 2. 通过读取输入端口寄存器(00H, 01H)来清除对应端口的中断 + // 3. 无独立的中断状态寄存器,需要通过输入电平变化来判断中断源 + // 4. 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + + // 一次性读取两个端口的输入寄存器,避免重复读取和错误的OR操作 + instance->IoexpandReadReg(INPUT_PORT0, ¤t_val_port0_); + instance->IoexpandReadReg(INPUT_PORT1, ¤t_val_port1_); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "读取端口状态 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + + // 检查读取寄存器后中断引脚是否被清除(应该恢复高电平) + interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "读取寄存器后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + //判断是否有中断来自雷达引脚 + if((RADAR_INTERRUPT_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((RADAR_INTERRUPT_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚1\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到雷达引脚中断\n"); + // } + + //判断是否有中断来自初始光学传感器引脚 + if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到初始光学传感器引脚中断\n"); + // } + + //判断是否有中断来自结束光学传感器引脚 + if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + + //CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 + //FULL未充满为高电平 充满电为低电平 + //判断是否有中断来自电池充电引脚 + //根据配置:BATTERY_CHRG_INPUT = INPUT_PORT0, BATTERY_FULL_INPUT = INPUT_PORT1 + + // AW9523B没有独立的中断状态寄存器,需要通过比较前后状态变化来判断中断源 + // 由于读取输入端口会清除中断,我们需要先保存当前状态,然后与上次状态比较 + + //获取当前电池状态 + bool current_charging_state = !(current_val_port0_ & (1 << BATTERY_CHRG_PIN)); // CHRG低电平表示充电中 + bool current_full_state = !(current_val_port1_ & (1 << BATTERY_FULL_PIN)); // FULL低电平表示已充满 + + // 详细的调试信息 + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - CHRG_PIN: %d (bit %d), FULL_PIN: %d (bit %d)", + (current_val_port0_ & (1 << BATTERY_CHRG_PIN)) ? 1 : 0, BATTERY_CHRG_PIN, + (current_val_port1_ & (1 << BATTERY_FULL_PIN)) ? 1 : 0, BATTERY_FULL_PIN); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 充电状态: %s, 充满状态: %s", + current_charging_state ? "充电中" : "未充电", + current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 上次充电状态: %s, 上次充满状态: %s", + instance->battery_charging_last_state_ ? "充电中" : "未充电", + instance->battery_full_last_state_ ? "已充满" : "未充满"); + + //检查充电状态是否发生变化 + if (current_charging_state != instance->battery_charging_last_state_) { + instance->battery_charging_last_state_ = current_charging_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充电状态变化: %s", current_charging_state ? "充电中" : "未充电"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充电状态变化详情 - 从 %s 到 %s", + instance->battery_charging_last_state_ ? "未充电" : "充电中", + current_charging_state ? "充电中" : "未充电"); + + if (current_charging_state) { + // 进入充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到开始充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerChrgCallback(); + } + if (instance->battery_charging_callback_) { + instance->battery_charging_callback_(); + } + } else { + // 退出充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到停止充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerNotChrgCallback(); + } + if (instance->battery_not_charging_callback_) { + instance->battery_not_charging_callback_(); + } + } + } + + //检查充满状态是否发生变化 + if (current_full_state != instance->battery_full_last_state_) { + instance->battery_full_last_state_ = current_full_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充满状态变化: %s", current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充满状态变化详情 - 从 %s 到 %s", + instance->battery_full_last_state_ ? "未充满" : "已充满", + current_full_state ? "已充满" : "未充满"); + + if (current_full_state) { + // 进入充满状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到电池已充满"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerFullCallback(); + } + if (instance->battery_full_callback_) { + instance->battery_full_callback_(); + } + } + + } + + // 根据AW9523B数据手册:读取输入端口寄存器清除对应端口的中断 + // 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "中断处理完成,已读取输入端口寄存器清除中断标志"); + } +} + +// bool IOExpansionBoard::AllocateStaticTaskMemory() +// { +// printf("IO拓展芯片创建任务前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务前 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// // 分配任务栈(PSRAM) +// const size_t TASK_STACK_SIZE = 4096; +// pI2CReadTaskStackBuffer = (StackType_t*)heap_caps_malloc( +// TASK_STACK_SIZE * sizeof(StackType_t), +// MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT +// ); + +// if (!pI2CReadTaskStackBuffer) { +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task stack from PSRAM"); +// return false; +// } + +// // 分配任务控制块(内部RAM) +// pxI2CReadTaskBuffer = (StaticTask_t*)pvPortMalloc(sizeof(StaticTask_t)); +// if (!pxI2CReadTaskBuffer) +// ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to allocate task TCB from internal RAM"); +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// return false; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory allocated successfully"); +// printf("IO拓展芯片创建任务后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); +// printf("IO拓展芯片创建任务后 Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +// return true; +// } + +// void IOExpansionBoard::FreeStaticTaskMemory() +// { +// if (pI2CReadTaskStackBuffer != nullptr) { +// heap_caps_free(pI2CReadTaskStackBuffer); +// pI2CReadTaskStackBuffer = nullptr; +// } + +// if (pxI2CReadTaskBuffer != nullptr) { +// vPortFree(pxI2CReadTaskBuffer); +// pxI2CReadTaskBuffer = nullptr; +// } + +// ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static task memory freed"); +// } + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_13/io_expansion_board.h b/main/02_physical/boards/board_v1_1_13/io_expansion_board.h new file mode 100644 index 0000000..3951893 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_13/io_expansion_board.h @@ -0,0 +1,419 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; +class PhotoInterrupter; +class AdcBatteryMonitor; + +// 回调函数类型定义 +typedef std::function RadarCallback; +typedef std::function PhotoInterrupterStartCallback; +typedef std::function PhotoInterrupterEndCallback; +typedef std::function BatteryChargingCallback; +typedef std::function BatteryFullCallback; +typedef std::function BatteryNotChargingCallback; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 +#define STEPPER_MOTOR_DIR_CONFIG CONFIG_PORT0 //步进电机方向引脚配置在P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M0_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M0_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_5 + +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_M1_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M1_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_4 + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_CONFIG CONFIG_PORT1 //步进电机睡眠引脚配置在P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_CONFIG CONFIG_PORT0 //电机使能引脚配置在P0_7 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_CONFIG CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口 +#define RADAR_INTERRUPT_INPUT INPUT_PORT1 //毫米波雷达中断引脚输入状态检测在INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +// 光学传感器引脚定义 +#define START_PHOTO_INTERRUPTER_PIN 2 // 光学传感器引脚(P0_2) +#define START_PHOTO_INTERRUPTER_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define START_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define START_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define START_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +#define END_PHOTO_INTERRUPTER_PIN 3 // 光学传感器引脚(P0_3) +#define END_PHOTO_INTERRUPTER_PORT_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define END_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define END_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define END_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +/* 设置LCD的BL引脚 */ +#define LCD_BL_PIN 6 //LCD背光引脚对应IO扩展板引脚P0_6 +#define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define LCD_BL_CONFIG CONFIG_PORT0 //LCD背光引脚配置在P0_6 +#define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +#define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚对应IO扩展板引脚P1_1 +#define LCD_RST_CONFIG CONFIG_PORT1 //LCD复位引脚配置在P1_1 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置PA控制引脚 */ +#define PA_CTRL_PIN 2 //PA控制引脚对应IO扩展板引脚P1_2 +#define PA_CTRL_CONFIG CONFIG_PORT1 //PA控制引脚配置在P1_2 +#define PA_CTRL_PORT OUTPUT_PORT1 //PA控制引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define PA_CTRL_HIGH true //PA控制引脚状态为高电平 +#define PA_CTRL_LOW false //PA控制引脚状态为低电平 + +// 电池充电检测引脚定义 +#define BATTERY_FULL_INPUT INPUT_PORT1 // 电池充满检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_FULL_PIN 3 // 电池充满检测引脚对应IO扩展板引脚P1_3 +#define BATTERY_FULL_CONFIG CONFIG_PORT1 // 电池充满检测引脚配置在P1_3 + +#define BATTERY_CHRG_INPUT INPUT_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_CHRG_PIN 5 // 充电检测引脚(P0_5) +#define BATTERY_CHRG_CONFIG CONFIG_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +#define IO_EXPAND_RSTN GPIO_NUM_45 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 设置限位开关指针 + * @param photo_interrupter 限位开关对象指针 + */ + void setPhotoInterrupter(PhotoInterrupter* photo_interrupter) { photo_interrupter_ = photo_interrupter; }; + + /** + * @brief 设置电池监控器指针 + * @param battery_monitor 电池监控器对象指针 + */ + void setBatteryMonitor(AdcBatteryMonitor* battery_monitor) { battery_monitor_ = battery_monitor; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + /** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort0(uint8_t *data); + + /** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort1(uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置PA控制引脚状态 + * @param PA_CTRL状态(低电平为选中状态,高电平为不选中状态) + * @return esp_err_t 操作结果 + */ + esp_err_t SetPaCtrlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t pa_ctrl_pin); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + /** + * @brief 计算Port0的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort0(); + + /** + * @brief 计算Port1的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort1(); + + /** + * @brief 设置雷达中断回调函数 + * @param callback 雷达中断回调函数 + */ + void SetRadarCallback(RadarCallback callback) { radar_callback_ = callback; } + + /** + * @brief 设置光学传感器开始中断回调函数 + * @param callback 光学传感器开始中断回调函数 + */ + void SetPhotoInterrupterStartCallback(PhotoInterrupterStartCallback callback) { photo_start_callback_ = callback; } + + /** + * @brief 设置光学传感器结束中断回调函数 + * @param callback 光学传感器结束中断回调函数 + */ + void SetPhotoInterrupterEndCallback(PhotoInterrupterEndCallback callback) { photo_end_callback_ = callback; } + + /** + * @brief 设置电池充电中断回调函数 + * @param callback 电池充电中断回调函数 + */ + void SetBatteryChargingCallback(BatteryChargingCallback callback) { battery_charging_callback_ = callback; } + + /** + * @brief 设置电池充满中断回调函数 + * @param callback 电池充满中断回调函数 + */ + void SetBatteryFullCallback(BatteryFullCallback callback) { battery_full_callback_ = callback; } + + /** + * @brief 设置电池非充电中断回调函数 + * @param callback 电池非充电中断回调函数 + */ + void SetBatteryNotChargingCallback(BatteryNotChargingCallback callback) { battery_not_charging_callback_ = callback; } + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + PhotoInterrupter* photo_interrupter_ = nullptr; // 限位开关指针,用于触发回调 + AdcBatteryMonitor* battery_monitor_ = nullptr; // 电池监控器指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // 成员变量用于状态跟踪 + uint8_t last_port0_state_ = 0; // 上次检测到的Port0状态 + uint8_t last_port1_state_ = 0; // 上次检测到的Port1状态 + bool radar_last_state_ = false; // 雷达引脚上一次的状态 + bool battery_charging_last_state_ = false; // 电池充电状态上一次的状态 + bool battery_full_last_state_ = false; // 电池充满状态上一次的状态 + + // 回调函数成员变量 + RadarCallback radar_callback_ = nullptr; + PhotoInterrupterStartCallback photo_start_callback_ = nullptr; + PhotoInterrupterEndCallback photo_end_callback_ = nullptr; + BatteryChargingCallback battery_charging_callback_ = nullptr; + BatteryFullCallback battery_full_callback_ = nullptr; + BatteryNotChargingCallback battery_not_charging_callback_ = nullptr; + + // PhotoInterrupter* photo_interrupter_ = nullptr; // 光学传感器指针,用于触发回调 + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_14/board_sweekar.cc b/main/02_physical/boards/board_v1_1_14/board_sweekar.cc new file mode 100644 index 0000000..1237ebe --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/board_sweekar.cc @@ -0,0 +1,702 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "driver/gptimer.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +} + +#include "board_sweekar.h" +#include "config.h" +#include "board.h" // 添加board.h以获取audio_board_sdcard_init函数声明 +#include "drv8428.h" +#include "aw8646.h" + +#define TAG "board_sweekar" + +BoardSweekar::BoardSweekar() + : base_board_hal_uart_(BASE_BOARD_CTL_UART_PORT) +{ +#ifdef CONFIG_STEPPER_DRIVER_AW8646 + stepper_driver_ = new Aw8646Driver(io_expansion_); +#else + stepper_driver_ = new Drv8428Driver(io_expansion_); +#endif +} + +BoardSweekar::~BoardSweekar() +{ + if (stepper_driver_) { + delete stepper_driver_; + stepper_driver_ = nullptr; + } +} + + +/*========================== 步进电机相关驱动 ==========================*/ + +/** + * @brief 步进电机初始化函数 + * 配置步进电机所需的GPIO引脚模式,通过IO扩展板设置步进驱动芯片的工作模式 + * 在 stepper_motor.cc 中完成 GPTimer + GPIO 翻转 STEP 脉冲的初始化 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO(不再用 LEDC) + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低,保证上电不乱跳 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 初始化驱动对象并初始化 + if (stepper_driver_) { + err = stepper_driver_->Init(); + if (err != ESP_OK) { + ESP_LOGE(TAG_BOARD, "步进电机驱动初始化失败, 错误码: 0x%x", err); + } + } else { + ESP_LOGE(TAG_BOARD, "步进电机驱动创建失败"); + } +} + +/** + * @brief LCD IO引脚初始化函数(消除与步进电机的耦合) + * + * 配置LCD所需的IO扩展板寄存器,确保LCD复位引脚正常工作 + * 此函数独立于步进电机初始化,专门处理LCD相关的IO配置 + */ +void BoardSweekar::LcdIoInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) 检查I2C总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 2) 执行硬件复位,确保IO扩展板处于已知状态 + err = io_expansion_.ResetIoExpand(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板硬件复位失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板硬件复位成功"); + } + + // 3) 配置IO扩展板模式(端口方向等) + err = io_expansion_.IoexpandSetMode(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板模式配置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板模式配置成功"); + } + + // 4) 初始化LCD复位引脚:设置为高电平 + err = io_expansion_.SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "LCD复位引脚高电平设置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "LCD复位引脚高电平设置成功"); + } + + io_expansion_.SetLedEnMode(true); +} + +/*========================== 毫米波雷达相关驱动 ==========================*/ + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 122, + .source_clk = UART_SCLK_DEFAULT, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/*========================== I2C总线及挂载的从设备相关驱动 ==========================*/ + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + .clk_flags = 0, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 线性马达的初始化已移至 InitializeAllHardware 中统一调用 Init 方法 + // linear_motor_ 的参数传递将在那里完成,不再此处直接赋值,提高内聚性 + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/*========================== VBAT to VBAT_5V相关驱动 ==========================*/ + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + if (VBAT_5V_PIN == GPIO_NUM_NC) { + return; + } + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (VBAT_5V_PIN != GPIO_NUM_NC) ? (1ULL << VBAT_5V_PIN) : 0, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + +/*========================== 底座控制串口相关驱动 ==========================*/ + +/** + * @brief 底座控制命令串口初始化函数 + */ +void BoardSweekar::BaseBoardControlUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = BASE_BOARD_CTL_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 122, + .source_clk = UART_SCLK_DEFAULT, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(BASE_BOARD_CTL_UART_PORT, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(BASE_BOARD_CTL_UART_PORT, BASE_BOARD_CTL_UART_TX_PIN, BASE_BOARD_CTL_UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动 + ESP_ERROR_CHECK(uart_driver_install(BASE_BOARD_CTL_UART_PORT, + BASE_BOARD_CTL_UART_BUF_SIZE, + BASE_BOARD_CTL_UART_BUF_SIZE, + 0, NULL, 0)); + + base_board_control_.Init(&base_board_hal_uart_); +} + +/*========================== SD卡相关驱动 ==========================*/ + +/** + * @brief SD卡初始化函数 + * + * 初始化SD卡外设,使用1线模式进行通信。该函数负责初始化外设管理器 + * 并配置SD卡,为后续的文件系统操作做准备。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示失败原因 + */ +esp_err_t BoardSweekar::InitializeSdcard(void) +{ + static const char *SDCARD_TAG = "SDCARD_INIT"; + esp_err_t ret; + + ESP_LOGI(SDCARD_TAG, "Initializing SD card"); + + if (is_sdcard_initialized_) { + ESP_LOGW(SDCARD_TAG, "SD card already initialized"); + return ESP_OK; + } + + // 初始化外设管理 + esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG(); + periph_cfg.extern_stack = true; // 将任务栈分配到 PSRAM 以节省 Internal SRAM + periph_set_ = esp_periph_set_init(&periph_cfg); + if (!periph_set_) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize peripheral set"); + return ESP_FAIL; + } + + // 初始化SD卡外设 + ret = audio_board_sdcard_init(periph_set_, SD_MODE_1_LINE); + if (ret != ESP_OK) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + return ret; + } + + is_sdcard_initialized_ = true; + ESP_LOGI(SDCARD_TAG, "SD card initialized successfully"); + return ESP_OK; +} + +/** + * @brief 统一的硬件初始化接口实现 + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备可能依赖) + * 2. IO扩展板(可能影响其他设备) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. 其他外设 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardware(void) +{ + static const char *HW_INIT_TAG = "HW_INIT"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting unified hardware initialization..."); + + // 1. 初始化I2C总线(最基础) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "SD card initialized"); + } + + // 4. 初始化LCD IO引脚 + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "LCD IO pins initialized"); + + // 5. 初始化触摸传感器 + touch_sensor_.SetI2cHandle(bus_handle_); + touch_sensor_.Begin(); + ESP_LOGI(HW_INIT_TAG, "Touch sensor initialized"); + + // 6. 初始化其他必要外设 + // BaseBoardControlUartInit(); // 底座控制串口(demo5需要) + // ESP_LOGI(HW_INIT_TAG, "Base board control UART initialized"); + + // 初始化底座控制 + // base_board_control_.Init(); + // ESP_LOGI(HW_INIT_TAG, "Base board control initialized"); + + // 可选外设(根据具体应用需求) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + StepperMotorInit(); + + + + ESP_LOGI(HW_INIT_TAG, "Unified hardware initialization completed successfully"); + return ESP_OK; +} + +/** + * @brief 进入低功耗模式 + * + * 根据指定的功耗模式,执行相应的功耗优化策略: + * - IDLE模式:关闭非必要外设,保持快速响应 + * - LIGHT_SLEEP模式:进入轻度睡眠,CPU暂停,外设断电 + * - DEEP_SLEEP模式:深度睡眠,仅RTC运行,功耗最低 + */ +esp_err_t BoardSweekar::EnterLowPowerMode(power_mode_t mode) { + static const char *POWER_TAG = "POWER_MGMT"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "Entering power mode: %d", mode); + + if (mode == current_power_mode_) { + ESP_LOGW(POWER_TAG, "Already in power mode %d", mode); + return ESP_OK; + } + + // 保存当前模式,以便恢复 + power_mode_t previous_mode = current_power_mode_; + + switch (mode) { + case POWER_MODE_NORMAL: + // 正常模式,不需要特殊处理 + break; + + case POWER_MODE_IDLE: + // 空闲模式:关闭非必要外设 + ESP_LOGI(POWER_TAG, "Idle mode: disabling non-essential peripherals"); + + // 关闭SD卡(如果正在使用,需要先卸载) + if (is_sdcard_initialized_) { + // 注意:这里只是示例,实际应用中需要确保没有文件操作 + ESP_LOGW(POWER_TAG, "SD card still active in idle mode"); + } + + // 关闭显示背光 + // 关闭非必要GPIO的上拉/下拉 + // 降低I2C时钟频率 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠 + ESP_LOGI(POWER_TAG, "Light sleep mode: preparing for sleep"); + + // 设置唤醒源(需要根据具体需求配置) + // esp_sleep_enable_timer_wakeup(1000000); // 1秒后唤醒 + // esp_sleep_enable_ext0_wakeup(GPIO_NUM_XX, 0); // 外部中断唤醒 + + // 进入轻度睡眠 + // ret = esp_light_sleep_start(); + if (ret != ESP_OK) { + ESP_LOGE(POWER_TAG, "Failed to enter light sleep: %s", esp_err_to_name(ret)); + current_power_mode_ = previous_mode; + return ret; + } + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠 + ESP_LOGI(POWER_TAG, "Deep sleep mode: system will restart after wake-up"); + + // 保存必要的RTC数据 + // rtc_gpio_isolate(GPIO_NUM_XX); // 隔离GPIO + + // 设置深度睡眠唤醒源 + // esp_sleep_enable_timer_wakeup(60000000); // 60秒后唤醒 + + // 进入深度睡眠 + // esp_deep_sleep_start(); + // 注意:深度睡眠后系统会重启,不会返回 + break; + + default: + ESP_LOGE(POWER_TAG, "Invalid power mode: %d", mode); + return ESP_ERR_INVALID_ARG; + } + + current_power_mode_ = mode; + ESP_LOGI(POWER_TAG, "Successfully entered power mode: %d", mode); + return ret; +} + +/** + * @brief 退出低功耗模式 + * + * 从低功耗模式恢复到正常运行模式,重新初始化必要的硬件资源。 + */ +esp_err_t BoardSweekar::ExitLowPowerMode(void) { + static const char *POWER_TAG = "POWER_MGMT"; + + ESP_LOGI(POWER_TAG, "Exiting low power mode"); + + if (current_power_mode_ == POWER_MODE_NORMAL) { + ESP_LOGW(POWER_TAG, "Already in normal mode"); + return ESP_OK; + } + + // 根据之前的功耗模式执行相应的恢复操作 + switch (current_power_mode_) { + case POWER_MODE_IDLE: + // 恢复空闲模式 + ESP_LOGI(POWER_TAG, "Restoring from idle mode"); + + // 重新初始化必要的外设 + // 恢复I2C时钟频率 + // 重新开启显示背光 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠唤醒后会自动继续执行 + ESP_LOGI(POWER_TAG, "Woken up from light sleep"); + + // 重新配置时钟(如果需要) + // 重新初始化外设 + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠后系统会重启,不会执行到这里 + ESP_LOGW(POWER_TAG, "Deep sleep recovery should not reach here"); + break; + + default: + break; + } + + // 恢复到正常模式 + current_power_mode_ = POWER_MODE_NORMAL; + ESP_LOGI(POWER_TAG, "Successfully exited low power mode"); + + return ESP_OK; +} + +/** + * @brief 设置硬件模块电源状态 + * + * 提供精细化的电源管理,可以单独控制各个硬件模块的电源状态。 + */ +esp_err_t BoardSweekar::SetModulePower(hardware_module_t module, bool enable) { + static const char *POWER_TAG = "POWER_MODULE"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "%s module %d", enable ? "Enabling" : "Disabling", module); + + // 更新电源状态位图 + if (enable) { + power_state_flags_ |= (1 << module); + } else { + power_state_flags_ &= ~(1 << module); + } + + switch (module) { + case HW_MODULE_I2C: + if (!enable && bus_handle_) { + // 注意:I2C总线通常不建议完全关闭,可能影响多个设备 + ESP_LOGW(POWER_TAG, "I2C bus power control not recommended"); + } + break; + + case HW_MODULE_SDCARD: + if (enable && !is_sdcard_initialized_) { + ret = InitializeSdcard(); + } else if (!enable && is_sdcard_initialized_) { + // 卸载SD卡文件系统 + // 注意:需要确保没有打开的文件 + ESP_LOGW(POWER_TAG, "SD card power down requires file system cleanup"); + } + break; + + case HW_MODULE_LCD: + // LCD背光控制 + // LCD电源引脚控制(如果有) + break; + + case HW_MODULE_AUDIO: + // 音频编解码器电源控制 + // 需要与AudioPlayer协调 + break; + + case HW_MODULE_MOTOR: + // 步进电机驱动器电源控制 + // 电机睡眠模式 + break; + + case HW_MODULE_GYROSCOPE: + // 陀螺仪电源控制 + // 设置陀螺仪为低功耗模式 + break; + + case HW_MODULE_TOUCH: + // 触摸传感器电源控制 + // 设置触摸传感器为睡眠模式 + break; + + case HW_MODULE_RADAR: + // 毫米波雷达电源控制 + // 设置雷达为低功耗模式 + break; + + case HW_MODULE_IO_EXPAND: + // IO扩展板电源控制 + // 设置扩展板为低功耗模式 + break; + + default: + ESP_LOGE(POWER_TAG, "Unknown hardware module: %d", module); + return ESP_ERR_INVALID_ARG; + } + + if (ret == ESP_OK) { + ESP_LOGI(POWER_TAG, "Module %d power %s successfully", module, enable ? "enabled" : "disabled"); + } else { + ESP_LOGE(POWER_TAG, "Failed to %s module %d: %s", enable ? "enable" : "disable", module, esp_err_to_name(ret)); + } + + return ret; +} + + + diff --git a/main/02_physical/boards/board_v1_1_14/board_sweekar.h b/main/02_physical/boards/board_v1_1_14/board_sweekar.h new file mode 100644 index 0000000..fa1db66 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/board_sweekar.h @@ -0,0 +1,171 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "stepper_driver.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" +#include "base_board_control.h" +#include "base_board_hal_uart.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" + +/** + * @brief 功耗模式枚举 + */ +typedef enum { + POWER_MODE_NORMAL, // 正常运行模式,所有硬件可用 + POWER_MODE_IDLE, // 空闲模式,关闭非必要外设 + POWER_MODE_LIGHT_SLEEP, // 轻度睡眠,快速唤醒 + POWER_MODE_DEEP_SLEEP, // 深度睡眠,仅RTC运行 +} power_mode_t; + +/** + * @brief 硬件模块枚举,用于精细化电源管理 + */ +typedef enum { + HW_MODULE_I2C, // I2C总线 + HW_MODULE_SDCARD, // SD卡 + HW_MODULE_LCD, // LCD显示 + HW_MODULE_AUDIO, // 音频编解码器 + HW_MODULE_MOTOR, // 步进电机 + HW_MODULE_GYROSCOPE, // 陀螺仪 + HW_MODULE_TOUCH, // 触摸传感器 + HW_MODULE_RADAR, // 毫米波雷达 + HW_MODULE_IO_EXPAND, // IO扩展板 +} hardware_module_t; + +/** + * @brief 硬件板级管理器 + * + * 采用高内聚低耦合设计原则,统一管理所有硬件资源。 + * 提供统一的硬件初始化接口和资源访问,避免各模块重复初始化。 + */ +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + esp_periph_set_handle_t periph_set_ = nullptr; // 外设管理句柄 + bool is_sdcard_initialized_ = false; // SD卡初始化状态 + power_mode_t current_power_mode_ = POWER_MODE_NORMAL; // 当前功耗模式 + uint32_t power_state_flags_ = 0xFFFFFFFF; // 各模块电源状态位图(1为开启) + +public: + BoardSweekar(); + ~BoardSweekar(); + + IOExpansionBoard io_expansion_; // IO扩展板对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + BaseBoardHalUart base_board_hal_uart_; // 底座控制HAL对象 + BaseBoardControl base_board_control_; // 底座控制对象 + StepperDriver* stepper_driver_ = nullptr; // 步进电机驱动对象 + + StepperDriver* GetStepperDriver() const { return stepper_driver_; } + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化LCD IO引脚(消除与步进电机的耦合) + void LcdIoInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 初始化底座控制串口 + void BaseBoardControlUartInit(void); + + // 初始化SD卡(推荐使用统一的硬件初始化) + esp_err_t InitializeSdcard(void); + + // 获取SD卡初始化状态 + bool IsSdcardInitialized() const { return is_sdcard_initialized_; } + + // 获取外设管理句柄(供需要访问SD卡的模块使用) + esp_periph_set_handle_t GetPeriphSet() const { return periph_set_; } + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } + + /** + * @brief 统一的硬件初始化接口 + * + * 按照依赖关系顺序初始化所有硬件资源,确保各模块可以安全使用。 + * 推荐在主程序启动时调用此函数,而不是分别调用各个初始化函数。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardware(void); + + /** + * @brief 完整的统一硬件初始化接口(专为复杂应用优化) + * + * 包含所有必要外设的完整初始化,适用于demo5等复杂应用场景。 + * 这个函数会初始化:I2C总线、IO扩展板、SD卡、LCD IO、底座控制等。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardwareComplete(void); + + /** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置,优化启动流程。 + * + * @param app_type 应用类型(demo5、音频播放器、显示应用等) + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeHardwareForApplication(int app_type); + + /** + * @brief 功耗管理接口 - 进入低功耗模式 + * + * 根据指定的功耗模式,关闭非必要硬件、设置GPIO为低功耗状态、 + * 调整时钟频率等。支持多种功耗模式以适应不同应用场景。 + * + * @param mode 功耗模式 + * @return esp_err_t 操作结果 + */ + esp_err_t EnterLowPowerMode(power_mode_t mode); + + /** + * @brief 功耗管理接口 - 退出低功耗模式 + * + * 从低功耗模式恢复,重新初始化必要的硬件资源。 + * + * @return esp_err_t 操作结果 + */ + esp_err_t ExitLowPowerMode(void); + + /** + * @brief 获取当前功耗模式 + * + * @return power_mode_t 当前功耗模式 + */ + power_mode_t GetPowerMode(void) const { return current_power_mode_; } + + /** + * @brief 设置硬件模块电源状态 + * + * 单独控制某个硬件模块的电源状态,用于精细化的功耗管理。 + * + * @param module 硬件模块 + * @param enable true为开启,false为关闭 + * @return esp_err_t 操作结果 + */ + esp_err_t SetModulePower(hardware_module_t module, bool enable); +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_14/board_sweekar_complete_init.cc b/main/02_physical/boards/board_v1_1_14/board_sweekar_complete_init.cc new file mode 100644 index 0000000..37a454a --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/board_sweekar_complete_init.cc @@ -0,0 +1,186 @@ +/** + * @file board_sweekar_complete_init.cc + * @brief 完整的统一硬件初始化实现 + * + * 这个文件提供了完整的统一硬件初始化函数,包含所有必要的硬件初始化步骤。 + * 专为demo5等复杂应用场景设计。 + */ + +/** + * @brief 完整的统一硬件初始化接口(专为demo5优化) + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备依赖) + * 2. IO扩展板(控制其他外设) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. LCD IO引脚(显示相关) + * 5. 底座控制串口(通信) + * 6. 底座控制模块(业务逻辑) + * 7. 其他外设(根据应用需求) + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardwareComplete(void) +{ + static const char *HW_INIT_TAG = "HW_INIT_COMPLETE"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting complete hardware initialization..."); + + // 1. 初始化I2C总线(最基础,其他设备依赖) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "✓ I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "✓ I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "⚠ SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "✓ SD card initialized"); + } + + // 4. 初始化LCD IO引脚(显示相关) + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "✓ LCD IO pins initialized"); + + // 5. 初始化底座控制串口(demo5需要) + BaseBoardControlUartInit(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control UART initialized"); + + // 6. 初始化底座控制模块 + base_board_control_.Init(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control initialized"); + + // 7. 设置IO扩展板的PA控制模式(demo5需要) + io_expansion_.SetPaCtrlMode(true); + ESP_LOGI(HW_INIT_TAG, "✓ IO expansion PA control mode set"); + + // 8. 可选外设(根据具体应用需求启用) + // ESP_LOGI(HW_INIT_TAG, "Initializing optional peripherals..."); + // + // // 毫米波雷达(如需要) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Millimeter wave radar initialized"); + // + // // VBAT升压(如需要) + // VbatToVbat5vInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ VBAT to 5V booster initialized"); + // + // // 步进电机(如需要) + // StepperMotorInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Stepper motor initialized"); + + ESP_LOGI(HW_INIT_TAG, "=== Complete hardware initialization finished successfully ==="); + return ESP_OK; +} + +/** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置 + */ +esp_err_t BoardSweekar::InitializeHardwareForApplication(application_type_t app_type) { + static const char *APP_INIT_TAG = "APP_HW_INIT"; + + ESP_LOGI(APP_INIT_TAG, "Initializing hardware for application type: %d", app_type); + + // 基础硬件初始化(所有应用都需要) + esp_err_t ret = InitializeAllHardwareComplete(); + if (ret != ESP_OK) { + ESP_LOGE(APP_INIT_TAG, "Basic hardware initialization failed"); + return ret; + } + + switch (app_type) { + case APP_TYPE_DEMO5: // 宠物游戏demo + ESP_LOGI(APP_INIT_TAG, "Configuring for Demo5 (Pet Game)..."); + + // Demo5需要的额外初始化 + // 触摸传感器、陀螺仪、RGB LED等会在应用层初始化 + + ESP_LOGI(APP_INIT_TAG, "✓ Demo5 hardware configuration complete"); + break; + + case APP_TYPE_AUDIO_PLAYER: // 音频播放器 + ESP_LOGI(APP_INIT_TAG, "Configuring for Audio Player..."); + + // 音频应用需要确保音频编解码器正确初始化 + // 这部分通常在AudioPlayer类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Audio player hardware configuration complete"); + break; + + case APP_TYPE_DISPLAY_ONLY: // 纯显示应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Display Only..."); + + // 显示应用需要确保LCD相关硬件正确配置 + // 这部分通常在Display类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Display-only hardware configuration complete"); + break; + + case APP_TYPE_MINIMAL: // 最小化应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Minimal Application..."); + + // 最小化配置,只保留最基本的功能 + // 关闭非必要外设以节省功耗 + + ESP_LOGI(APP_INIT_TAG, "✓ Minimal hardware configuration complete"); + break; + + default: + ESP_LOGW(APP_INIT_TAG, "Unknown application type, using default configuration"); + break; + } + + return ESP_OK; +} + +/** + * @brief 硬件初始化状态报告 + * + * 提供详细的硬件初始化状态报告,便于调试和问题排查 + */ +void BoardSweekar::ReportHardwareStatus(void) { + static const char *STATUS_TAG = "HW_STATUS"; + + ESP_LOGI(STATUS_TAG, "=== Hardware Initialization Status Report ==="); + + // I2C总线状态 + ESP_LOGI(STATUS_TAG, "I2C Bus: %s", (bus_handle_ != nullptr) ? "✓ Initialized" : "✗ Not initialized"); + + // SD卡状态 + ESP_LOGI(STATUS_TAG, "SD Card: %s", (is_sdcard_initialized_) ? "✓ Initialized" : "✗ Not initialized"); + if (is_sdcard_initialized_) { + ESP_LOGI(STATUS_TAG, " Peripheral Set: %s", (periph_set_ != nullptr) ? "✓ Available" : "✗ Not available"); + } + + // IO扩展板状态 + ESP_LOGI(STATUS_TAG, "IO Expansion Board: %s", (io_expansion_.bus_handle_ != nullptr) ? "✓ Configured" : "✗ Not configured"); + + // 底座控制状态 + ESP_LOGI(STATUS_TAG, "Base Board Control: %s", (base_board_control_.GetUartPort() != 0) ? "✓ Initialized" : "✗ Not initialized"); + + // 功耗管理状态 + ESP_LOGI(STATUS_TAG, "Power Management: %s", (current_power_mode_ == POWER_MODE_NORMAL) ? "✓ Normal mode" : "⚠ Low power mode"); + ESP_LOGI(STATUS_TAG, " Current Mode: %d", current_power_mode_); + ESP_LOGI(STATUS_TAG, " Module Power States: 0x%08X", power_state_flags_); + + // 内存状态 + ESP_LOGI(STATUS_TAG, "Memory Status:"); + ESP_LOGI(STATUS_TAG, " Internal RAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI(STATUS_TAG, " PSRAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + ESP_LOGI(STATUS_TAG, "=== End of Hardware Status Report ==="); +} \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_14/config.h b/main/02_physical/boards/board_v1_1_14/config.h new file mode 100644 index 0000000..0eed794 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/config.h @@ -0,0 +1,132 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/timer.h" +#include "esp_intr_alloc.h" +#include "soc/timer_group_struct.h" // 包含TIMERG0、TIMERG1的结构体定义 +#include "soc/timer_group_reg.h" // 包含定时器相关寄存器宏定义 +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机配置 */ +#define STEPPER_STEP_IO GPIO_NUM_42 // 步进电机脉冲输入引脚 +#define STEPPER_TIMER_RES_HZ 1000000 // 步进电机定时器分辨率 1 MHz(1 us 步进) + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_45 // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO40 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO39 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x58 // AW9523BTQR器件地址,v1_1_13的addr1和addr0是0 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* 原生触摸引脚配置 */ +// 触摸按键数量配置 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +/* 底座控制命令串口配置 */ +#define BASE_BOARD_CTL_UART_PORT UART_NUM_0 +#define BASE_BOARD_CTL_UART_TX_PIN GPIO_NUM_43 +#define BASE_BOARD_CTL_UART_RX_PIN GPIO_NUM_44 +#define BASE_BOARD_CTL_UART_BAUDRATE 115200 +#define BASE_BOARD_CTL_UART_BUF_SIZE 256 + +/* 背光控制引脚配置 */ +#define BL_PIN GPIO_NUM_45 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/02_physical/boards/board_v1_1_14/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_14/io_expansion_board.cc new file mode 100644 index 0000000..a4a345c --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/io_expansion_board.cc @@ -0,0 +1,1023 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "photo_interrupter.h" +#include "adc_battery_monitor.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + last_port0_state_(0), + last_port1_state_(0), + radar_last_state_(false), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr), + is_initialized_(false), + interrupt_gpio_(GPIO_NUM_NC), + reset_gpio_(GPIO_NUM_NC) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + + // 调试:记录中断触发 + // ESP_EARLY_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发!"); // IRAM函数中不能使用普通日志 + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_ANYEDGE; // 改为双沿触发,检测上升沿和下降沿 + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + // io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + // io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 计算Port0的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort0() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT0){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT0){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT0){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT0){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT0){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 计算Port1的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort1() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT1){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT1){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT1){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT1){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT1){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +/** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort0(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT0, data); +} + +/** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort1(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT1, data); +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + + err = IoexpandWriteReg(CONFIG_PORT0, ByteCalculatePort0()); + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort0()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功, 0x%02X", ByteCalculatePort0()); + } + + err = IoexpandWriteReg(CONFIG_PORT1, ByteCalculatePort1()); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort1()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功, 0x%02X", ByteCalculatePort1()); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + err = IoexpandWriteReg(OUTPUT_PORT0, 0x40); // 0100 0000 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x04); // 0000 0100 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + + // AW9523B中断使能配置:0为使能中断,1为禁止中断 + // 使能P0_5(充电检测)和P1_3(充满检测)的中断 + uint8_t int_enable_port0 = 0x00; // 0x00 = 使能所有P0引脚中断 + uint8_t int_enable_port1 = 0x00; // 0x00 = 使能所有P1引脚中断 + + err = IoexpandWriteReg(INT_PORT0, int_enable_port0); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能成功: 0x%02X (0=使能)", int_enable_port0); + } + + err = IoexpandWriteReg(INT_PORT1, int_enable_port1); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功: 0x%02X (0=使能)", int_enable_port1); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << enable_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入睡眠状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +esp_err_t IOExpansionBoard::SetLedEnMode(bool mode) +{ + return ESP_OK; +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入复位模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val |= (1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val &= ~(1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val |= (1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val &= ~(1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 PA_CTRL引脚电平 + */ +esp_err_t IOExpansionBoard::SetPaCtrlMode(bool mode) +{ + uint8_t current_val; + if(mode){ + // 进入高电平状态 + ESP_LOGI("io_ex:", "read reg"); + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val |= (1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } + else{ + // 进入低电平状态 + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val &= ~(1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } +} + + + +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + + // 执行硬件复位 - 这是解决中断引脚持续拉低的关键 + err = instance->ResetIoExpand(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位成功"); + + // 检查复位后的中断引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "复位后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + } + + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 - 复位后必须重新配置寄存器! + err = instance->IoexpandSetMode(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + // 设置LCD屏幕复位引脚为低电平 + err = instance->SetLcdResetMode(LCD_RST_LOW, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + + uint8_t current_val_port0_ = 0,current_val_port1_ = 0,current_temp_ = 0; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板中断触发"); + + // 检查中断触发时的引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发时引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + // AW9523B中断处理说明(根据数据手册): + // 1. 当输入引脚状态变化时,AW9523B会拉低INT引脚(连接到ESP32的GPIO10) + // 2. 通过读取输入端口寄存器(00H, 01H)来清除对应端口的中断 + // 3. 无独立的中断状态寄存器,需要通过输入电平变化来判断中断源 + // 4. 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + + // 一次性读取两个端口的输入寄存器,避免重复读取和错误的OR操作 + instance->IoexpandReadReg(INPUT_PORT0, ¤t_val_port0_); + instance->IoexpandReadReg(INPUT_PORT1, ¤t_val_port1_); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "读取端口状态 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + + // 检查读取寄存器后中断引脚是否被清除(应该恢复高电平) + interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "读取寄存器后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + //判断是否有中断来自雷达引脚 + if((RADAR_INTERRUPT_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((RADAR_INTERRUPT_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚1\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到雷达引脚中断\n"); + // } + + //判断是否有中断来自初始光学传感器引脚 + if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到初始光学传感器引脚中断\n"); + // } + + //判断是否有中断来自结束光学传感器引脚 + if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + + //CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 + //FULL未充满为高电平 充满电为低电平 + //判断是否有中断来自电池充电引脚 + //根据配置:BATTERY_CHRG_INPUT = INPUT_PORT0, BATTERY_FULL_INPUT = INPUT_PORT1 + + // AW9523B没有独立的中断状态寄存器,需要通过比较前后状态变化来判断中断源 + // 由于读取输入端口会清除中断,我们需要先保存当前状态,然后与上次状态比较 + + //获取当前电池状态 + bool current_charging_state = !(current_val_port0_ & (1 << BATTERY_CHRG_PIN)); // CHRG低电平表示充电中 + bool current_full_state = !(current_val_port1_ & (1 << BATTERY_FULL_PIN)); // FULL低电平表示已充满 + + // 详细的调试信息 + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - CHRG_PIN: %d (bit %d), FULL_PIN: %d (bit %d)", + (current_val_port0_ & (1 << BATTERY_CHRG_PIN)) ? 1 : 0, BATTERY_CHRG_PIN, + (current_val_port1_ & (1 << BATTERY_FULL_PIN)) ? 1 : 0, BATTERY_FULL_PIN); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 充电状态: %s, 充满状态: %s", + current_charging_state ? "充电中" : "未充电", + current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 上次充电状态: %s, 上次充满状态: %s", + instance->battery_charging_last_state_ ? "充电中" : "未充电", + instance->battery_full_last_state_ ? "已充满" : "未充满"); + + //检查充电状态是否发生变化 + if (current_charging_state != instance->battery_charging_last_state_) { + instance->battery_charging_last_state_ = current_charging_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充电状态变化: %s", current_charging_state ? "充电中" : "未充电"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充电状态变化详情 - 从 %s 到 %s", + instance->battery_charging_last_state_ ? "未充电" : "充电中", + current_charging_state ? "充电中" : "未充电"); + + if (current_charging_state) { + // 进入充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到开始充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerChrgCallback(); + } + if (instance->battery_charging_callback_) { + instance->battery_charging_callback_(); + } + } else { + // 退出充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到停止充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerNotChrgCallback(); + } + if (instance->battery_not_charging_callback_) { + instance->battery_not_charging_callback_(); + } + } + } + + //检查充满状态是否发生变化 + if (current_full_state != instance->battery_full_last_state_) { + instance->battery_full_last_state_ = current_full_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充满状态变化: %s", current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充满状态变化详情 - 从 %s 到 %s", + instance->battery_full_last_state_ ? "未充满" : "已充满", + current_full_state ? "已充满" : "未充满"); + + if (current_full_state) { + // 进入充满状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到电池已充满"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerFullCallback(); + } + if (instance->battery_full_callback_) { + instance->battery_full_callback_(); + } + } + + } + + // 根据AW9523B数据手册:读取输入端口寄存器清除对应端口的中断 + // 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "中断处理完成,已读取输入端口寄存器清除中断标志"); + } +} + + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_14/io_expansion_board.h b/main/02_physical/boards/board_v1_1_14/io_expansion_board.h new file mode 100644 index 0000000..f2c0c3c --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/io_expansion_board.h @@ -0,0 +1,421 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; +class PhotoInterrupter; +class AdcBatteryMonitor; + +// 回调函数类型定义 +typedef std::function RadarCallback; +typedef std::function PhotoInterrupterStartCallback; +typedef std::function PhotoInterrupterEndCallback; +typedef std::function BatteryChargingCallback; +typedef std::function BatteryFullCallback; +typedef std::function BatteryNotChargingCallback; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 +#define STEPPER_MOTOR_DIR_CONFIG CONFIG_PORT0 //步进电机方向引脚配置在P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M0_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M0_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_5 + +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_M1_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M1_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_4 + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_CONFIG CONFIG_PORT1 //步进电机睡眠引脚配置在P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_CONFIG CONFIG_PORT0 //电机使能引脚配置在P0_7 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_CONFIG CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口 +#define RADAR_INTERRUPT_INPUT INPUT_PORT1 //毫米波雷达中断引脚输入状态检测在INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +// 光学传感器引脚定义 +#define START_PHOTO_INTERRUPTER_PIN 2 // 光学传感器引脚(P0_2) +#define START_PHOTO_INTERRUPTER_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define START_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define START_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define START_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +#define END_PHOTO_INTERRUPTER_PIN 3 // 光学传感器引脚(P0_3) +#define END_PHOTO_INTERRUPTER_PORT_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define END_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define END_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define END_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +// /* 设置LCD的BL引脚 */ +// #define LCD_BL_PIN 6 //LCD背光引脚对应IO扩展板引脚P0_6 +// #define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +// #define LCD_BL_CONFIG CONFIG_PORT0 //LCD背光引脚配置在P0_6 +// #define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +// #define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚对应IO扩展板引脚P1_1 +#define LCD_RST_CONFIG CONFIG_PORT1 //LCD复位引脚配置在P1_1 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置PA控制引脚 */ +#define PA_CTRL_PIN 2 //PA控制引脚对应IO扩展板引脚P1_2 +#define PA_CTRL_CONFIG CONFIG_PORT1 //PA控制引脚配置在P1_2 +#define PA_CTRL_PORT OUTPUT_PORT1 //PA控制引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define PA_CTRL_HIGH true //PA控制引脚状态为高电平 +#define PA_CTRL_LOW false //PA控制引脚状态为低电平 + +// 电池充电检测引脚定义 +#define BATTERY_FULL_INPUT INPUT_PORT1 // 电池充满检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_FULL_PIN 3 // 电池充满检测引脚对应IO扩展板引脚P1_3 +#define BATTERY_FULL_CONFIG CONFIG_PORT1 // 电池充满检测引脚配置在P1_3 + +#define BATTERY_CHRG_INPUT INPUT_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_CHRG_PIN 5 // 充电检测引脚(P0_5) +#define BATTERY_CHRG_CONFIG CONFIG_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +// #define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 设置限位开关指针 + * @param photo_interrupter 限位开关对象指针 + */ + void setPhotoInterrupter(PhotoInterrupter* photo_interrupter) { photo_interrupter_ = photo_interrupter; }; + + /** + * @brief 设置电池监控器指针 + * @param battery_monitor 电池监控器对象指针 + */ + void setBatteryMonitor(AdcBatteryMonitor* battery_monitor) { battery_monitor_ = battery_monitor; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + /** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort0(uint8_t *data); + + /** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort1(uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + esp_err_t SetLedEnMode(bool mode); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置PA控制引脚状态 + * @param mode PA_CTRL状态(true为高电平,false为低电平) + * @return esp_err_t 操作结果 + */ + esp_err_t SetPaCtrlMode(bool mode); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + /** + * @brief 计算Port0的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort0(); + + /** + * @brief 计算Port1的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort1(); + + /** + * @brief 设置雷达中断回调函数 + * @param callback 雷达中断回调函数 + */ + void SetRadarCallback(RadarCallback callback) { radar_callback_ = callback; } + + /** + * @brief 设置光学传感器开始中断回调函数 + * @param callback 光学传感器开始中断回调函数 + */ + void SetPhotoInterrupterStartCallback(PhotoInterrupterStartCallback callback) { photo_start_callback_ = callback; } + + /** + * @brief 设置光学传感器结束中断回调函数 + * @param callback 光学传感器结束中断回调函数 + */ + void SetPhotoInterrupterEndCallback(PhotoInterrupterEndCallback callback) { photo_end_callback_ = callback; } + + /** + * @brief 设置电池充电中断回调函数 + * @param callback 电池充电中断回调函数 + */ + void SetBatteryChargingCallback(BatteryChargingCallback callback) { battery_charging_callback_ = callback; } + + /** + * @brief 设置电池充满中断回调函数 + * @param callback 电池充满中断回调函数 + */ + void SetBatteryFullCallback(BatteryFullCallback callback) { battery_full_callback_ = callback; } + + /** + * @brief 设置电池非充电中断回调函数 + * @param callback 电池非充电中断回调函数 + */ + void SetBatteryNotChargingCallback(BatteryNotChargingCallback callback) { battery_not_charging_callback_ = callback; } + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + PhotoInterrupter* photo_interrupter_ = nullptr; // 限位开关指针,用于触发回调 + AdcBatteryMonitor* battery_monitor_ = nullptr; // 电池监控器指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // 成员变量用于状态跟踪 + uint8_t last_port0_state_ = 0; // 上次检测到的Port0状态 + uint8_t last_port1_state_ = 0; // 上次检测到的Port1状态 + bool radar_last_state_ = false; // 雷达引脚上一次的状态 + bool battery_charging_last_state_ = false; // 电池充电状态上一次的状态 + bool battery_full_last_state_ = false; // 电池充满状态上一次的状态 + + // 回调函数成员变量 + RadarCallback radar_callback_ = nullptr; + PhotoInterrupterStartCallback photo_start_callback_ = nullptr; + PhotoInterrupterEndCallback photo_end_callback_ = nullptr; + BatteryChargingCallback battery_charging_callback_ = nullptr; + BatteryFullCallback battery_full_callback_ = nullptr; + BatteryNotChargingCallback battery_not_charging_callback_ = nullptr; + + // PhotoInterrupter* photo_interrupter_ = nullptr; // 光学传感器指针,用于触发回调 + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif diff --git a/main/02_physical/boards/board_v1_1_14/power_management_config.h b/main/02_physical/boards/board_v1_1_14/power_management_config.h new file mode 100644 index 0000000..da609fb --- /dev/null +++ b/main/02_physical/boards/board_v1_1_14/power_management_config.h @@ -0,0 +1,178 @@ +/** + * @file power_management_config.h + * @brief 功耗管理配置和策略定义 + * @author Embedded Software Expert + * @date 2025年1月 + * + * 这个文件定义了功耗管理的配置参数、策略和优化建议, + * 为系统提供灵活的功耗管理方案。 + */ + +#ifndef POWER_MANAGEMENT_CONFIG_H +#define POWER_MANAGEMENT_CONFIG_H + +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp32/rom/rtc.h" + +/** + * @brief 功耗管理配置结构体 + */ +typedef struct { + // 睡眠模式配置 + uint64_t light_sleep_duration_us; // 轻度睡眠持续时间(微秒) + uint64_t deep_sleep_duration_us; // 深度睡眠持续时间(微秒) + + // 唤醒源配置 + bool enable_timer_wakeup; // 启用定时器唤醒 + bool enable_gpio_wakeup; // 启用GPIO唤醒 + bool enable_touch_wakeup; // 启用触摸唤醒 + gpio_num_t wakeup_gpio_num; // 唤醒GPIO编号 + int wakeup_level; // 唤醒电平(0或1) + + // 功耗优化配置 + bool disable_bt_in_sleep; // 睡眠时关闭蓝牙 + bool disable_wifi_in_sleep; // 睡眠时关闭WiFi + bool reduce_cpu_frequency; // 降低CPU频率 + uint32_t cpu_frequency_hz; // CPU频率(Hz) + + // 外设功耗配置 + bool power_down_sdcard; // 睡眠时关闭SD卡电源 + bool power_down_display; // 睡眠时关闭显示电源 + bool power_down_audio; // 睡眠时关闭音频电源 + bool power_down_motor; // 睡眠时关闭电机电源 + + // RTC内存配置 + bool preserve_rtc_memory; // 保留RTC内存 + size_t rtc_memory_size; // RTC内存大小 +} power_management_config_t; + +/** + * @brief 默认功耗管理配置 + */ +#define DEFAULT_POWER_CONFIG { \ + .light_sleep_duration_us = 1000000, /* 1秒 */ \ + .deep_sleep_duration_us = 30000000, /* 30秒 */ \ + .enable_timer_wakeup = true, /* 启用定时器唤醒 */ \ + .enable_gpio_wakeup = false, /* 不启用GPIO唤醒 */ \ + .enable_touch_wakeup = false, /* 不启用触摸唤醒 */ \ + .wakeup_gpio_num = GPIO_NUM_NC, /* 无GPIO唤醒 */ \ + .wakeup_level = 0, /* 低电平唤醒 */ \ + .disable_bt_in_sleep = true, /* 睡眠时关闭蓝牙 */ \ + .disable_wifi_in_sleep = true, /* 睡眠时关闭WiFi */ \ + .reduce_cpu_frequency = true, /* 降低CPU频率 */ \ + .cpu_frequency_hz = 80000000, /* 80MHz */ \ + .power_down_sdcard = true, /* 睡眠时关闭SD卡 */ \ + .power_down_display = true, /* 睡眠时关闭显示 */ \ + .power_down_audio = true, /* 睡眠时关闭音频 */ \ + .power_down_motor = true, /* 睡眠时关闭电机 */ \ + .preserve_rtc_memory = true, /* 保留RTC内存 */ \ + .rtc_memory_size = 4096, /* 4KB RTC内存 */ \ +} + +/** + * @brief 各功耗模式的详细配置 + */ +static const power_management_config_t idle_mode_config = { + .light_sleep_duration_us = 500000, // 500ms轻度睡眠 + .deep_sleep_duration_us = 0, // 不使用深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, // 按键唤醒 + .wakeup_level = 0, + .disable_bt_in_sleep = false, // 保持蓝牙连接 + .disable_wifi_in_sleep = false, // 保持WiFi连接 + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, // 40MHz + .power_down_sdcard = false, // 保持SD卡(可能有后台任务) + .power_down_display = true, // 关闭显示背光 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 2048, +}; + +static const power_management_config_t light_sleep_config = { + .light_sleep_duration_us = 0, // 直接进入轻度睡眠 + .deep_sleep_duration_us = 0, + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 4096, +}; + +static const power_management_config_t deep_sleep_config = { + .light_sleep_duration_us = 0, + .deep_sleep_duration_us = 60000000, // 60秒深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 10000000, // 10MHz + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 8192, +}; + +/** + * @brief 功耗统计和监控结构体 + */ +typedef struct { + uint64_t total_sleep_time_us; // 总睡眠时间 + uint64_t total_wake_time_us; // 总唤醒时间 + uint32_t sleep_count; // 睡眠次数 + uint32_t wake_count; // 唤醒次数 + uint32_t average_sleep_duration_us; // 平均睡眠时长 + uint32_t average_wake_duration_us; // 平均唤醒时长 + float estimated_power_saving_percent; // 估计的功耗节省百分比 +} power_statistics_t; + +/** + * @brief 功耗优化建议 + * + * 基于硬件特性和使用模式,提供功耗优化建议 + */ +typedef enum { + OPTIMIZATION_NONE = 0, + OPTIMIZATION_REDUCE_CPU_FREQ, // 降低CPU频率 + OPTIMIZATION_DISABLE_UNUSED_PERIPHERALS, // 关闭未使用的外设 + OPTIMIZATION_USE_LIGHT_SLEEP, // 使用轻度睡眠 + OPTIMIZATION_USE_DEEP_SLEEP, // 使用深度睡眠 + OPTIMIZATION_OPTIMIZE_GPIO_STATES, // 优化GPIO状态 + OPTIMIZATION_USE_RTC_MEMORY, // 使用RTC内存 + OPTIMIZATION_BATCH_OPERATIONS, // 批量操作 +} power_optimization_t; + +/** + * @brief 功耗管理事件回调 + */ +typedef void (*power_event_callback_t)(power_mode_t old_mode, power_mode_t new_mode, void* user_data); + +/** + * @brief 功耗管理事件 + */ +typedef enum { + POWER_EVENT_MODE_CHANGE, // 功耗模式改变 + POWER_EVENT_SLEEP_START, // 睡眠开始 + POWER_EVENT_SLEEP_END, // 睡眠结束 + POWER_EVENT_WAKEUP, // 系统唤醒 + POWER_EVENT_OPTIMIZATION_TRIGGER, // 功耗优化触发 +} power_event_t; + +#endif // POWER_MANAGEMENT_CONFIG_H \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_16/board_sweekar.cc b/main/02_physical/boards/board_v1_1_16/board_sweekar.cc new file mode 100644 index 0000000..6229388 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/board_sweekar.cc @@ -0,0 +1,738 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "driver/gptimer.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +} + +#include "board_sweekar.h" +#include "config.h" +#include "board.h" // 添加board.h以获取audio_board_sdcard_init函数声明 +#include "drv8428.h" +#include "aw8646.h" + +#define TAG "board_sweekar" + +BoardSweekar::BoardSweekar() +{ +#ifdef CONFIG_STEPPER_DRIVER_AW8646 + stepper_driver_ = new Aw8646Driver(io_expansion_); +#else + stepper_driver_ = new Drv8428Driver(io_expansion_); +#endif + + // Initialize Shutdown Control Pin (GPIO_NUM_13) and pull it HIGH + // This is required to keep the main power on. +#ifdef SHUTDOWN_CTRL_PIN + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << SHUTDOWN_CTRL_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + gpio_set_level(SHUTDOWN_CTRL_PIN, 1); + ESP_LOGI(TAG, "Initialized SHUTDOWN_CTRL_PIN (%d) to HIGH", SHUTDOWN_CTRL_PIN); +#endif +} + +BoardSweekar::~BoardSweekar() +{ + if (stepper_driver_) { + delete stepper_driver_; + stepper_driver_ = nullptr; + } +} + +/*========================== 步进电机相关驱动 ==========================*/ + +/** + * @brief 步进电机初始化函数 + * 配置步进电机所需的GPIO引脚模式,通过IO扩展板设置步进驱动芯片的工作模式 + * 在 stepper_motor.cc 中完成 GPTimer + GPIO 翻转 STEP 脉冲的初始化 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO(不再用 LEDC) + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低,保证上电不乱跳 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 初始化驱动对象并初始化 + if (stepper_driver_) { + err = stepper_driver_->Init(); + if (err != ESP_OK) { + ESP_LOGE(TAG_BOARD, "步进电机驱动初始化失败, 错误码: 0x%x", err); + } + } else { + ESP_LOGE(TAG_BOARD, "步进电机驱动创建失败"); + } +} + +/** + * @brief LCD IO引脚初始化函数(消除与步进电机的耦合) + * + * 配置LCD所需的IO扩展板寄存器,确保LCD复位引脚正常工作 + * 此函数独立于步进电机初始化,专门处理LCD相关的IO配置 + */ +void BoardSweekar::LcdIoInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) 检查I2C总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 2) 执行硬件复位,确保IO扩展板处于已知状态 + err = io_expansion_.ResetIoExpand(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板硬件复位失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板硬件复位成功"); + } + + // 3) 配置IO扩展板模式(端口方向等) + err = io_expansion_.IoexpandSetMode(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板模式配置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板模式配置成功"); + } + + // 4) 初始化LCD复位引脚:设置为高电平 + err = io_expansion_.SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "LCD复位引脚高电平设置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "LCD复位引脚高电平设置成功"); + } +} + +/*========================== 毫米波雷达相关驱动 ==========================*/ + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 执行系统关机(拉高 SHUTDOWN_CTRL_PIN 切断电源) + */ +void BoardSweekar::SystemShutdown(void) +{ +#ifdef SHUTDOWN_CTRL_PIN + ESP_LOGI(TAG, "Executing System Shutdown... Pulling SHUTDOWN_CTRL_PIN (%d) HIGH", SHUTDOWN_CTRL_PIN); + gpio_set_level(SHUTDOWN_CTRL_PIN, 1); + vTaskDelay(pdMS_TO_TICKS(1000)); +#else + ESP_LOGW(TAG, "SHUTDOWN_CTRL_PIN is not defined. Cannot execute hardware shutdown."); +#endif +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/*========================== I2C总线及挂载的从设备相关驱动 ==========================*/ + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 线性马达的初始化已移至 InitializeAllHardware 中统一调用 Init 方法 + // linear_motor_ 的参数传递将在那里完成,不再此处直接赋值,提高内聚性 + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/*========================== VBAT to VBAT_5V相关驱动 ==========================*/ + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + +/*========================== 底座控制串口相关驱动 ==========================*/ + +/** + * @brief 底座控制命令串口初始化函数 + */ +// 根据配置选择通信方式的HAL头文件 +#ifdef CONFIG_BASE_BOARD_COMM_I2C +#include "base_board_hal_i2c.h" +#else +#include "base_board_hal_uart.h" +#endif + +void BoardSweekar::BaseBoardControlInit(void) +{ +#ifdef CONFIG_BASE_BOARD_COMM_I2C + // I2C Mode + if (bus_handle_ == nullptr) { + ESP_LOGE(TAG, "I2C bus not initialized before BaseBoardControlInit"); + return; + } + + // 创建 I2C HAL 实例 + BaseBoardHalI2c *hal = new BaseBoardHalI2c(bus_handle_); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl I2C initialized"); +#else + // UART Mode + uart_config_t uart_config = { + .baud_rate = BASE_BOARD_CTL_UART_BAUDRATE, + .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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(BASE_BOARD_CTL_UART_PORT, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(BASE_BOARD_CTL_UART_PORT, BASE_BOARD_CTL_UART_TX_PIN, BASE_BOARD_CTL_UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动 + ESP_ERROR_CHECK(uart_driver_install(BASE_BOARD_CTL_UART_PORT, + BASE_BOARD_CTL_UART_BUF_SIZE, + BASE_BOARD_CTL_UART_BUF_SIZE, + 0, NULL, 0)); + + BaseBoardHalUart *hal = new BaseBoardHalUart(BASE_BOARD_CTL_UART_PORT); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl UART initialized"); +#endif +} + +/*========================== SD卡相关驱动 ==========================*/ + +/** + * @brief SD卡初始化函数 + * + * 初始化SD卡外设,使用1线模式进行通信。该函数负责初始化外设管理器 + * 并配置SD卡,为后续的文件系统操作做准备。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示失败原因 + */ +esp_err_t BoardSweekar::InitializeSdcard(void) +{ + static const char *SDCARD_TAG = "SDCARD_INIT"; + esp_err_t ret; + + ESP_LOGI(SDCARD_TAG, "Initializing SD card"); + + if (is_sdcard_initialized_) { + ESP_LOGW(SDCARD_TAG, "SD card already initialized"); + return ESP_OK; + } + + // 初始化外设管理 + esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG(); + periph_cfg.extern_stack = true; // 将任务栈分配到 PSRAM 以节省 Internal SRAM + periph_set_ = esp_periph_set_init(&periph_cfg); + if (!periph_set_) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize peripheral set"); + return ESP_FAIL; + } + + // 初始化SD卡外设 + ret = audio_board_sdcard_init(periph_set_, SD_MODE_1_LINE); + if (ret != ESP_OK) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + return ret; + } + + is_sdcard_initialized_ = true; + ESP_LOGI(SDCARD_TAG, "SD card initialized successfully"); + return ESP_OK; +} + +/** + * @brief 统一的硬件初始化接口实现 + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备可能依赖) + * 2. IO扩展板(可能影响其他设备) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. 其他外设 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardware(void) +{ + static const char *HW_INIT_TAG = "HW_INIT"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting unified hardware initialization..."); + + // 1. 初始化I2C总线(最基础) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "SD card initialized"); + } + + // 4. 初始化LCD IO引脚 + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "LCD IO pins initialized"); + + // 5. 初始化其他必要外设 + // BaseBoardControlUartInit(); // 底座控制串口(demo5需要) + // ESP_LOGI(HW_INIT_TAG, "Base board control UART initialized"); + + // 初始化底座控制 + // base_board_control_.Init(); + // ESP_LOGI(HW_INIT_TAG, "Base board control initialized"); + + // 可选外设(根据具体应用需求) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + StepperMotorInit(); + + + + ESP_LOGI(HW_INIT_TAG, "Unified hardware initialization completed successfully"); + return ESP_OK; +} + +/** + * @brief 进入低功耗模式 + * + * 根据指定的功耗模式,执行相应的功耗优化策略: + * - IDLE模式:关闭非必要外设,保持快速响应 + * - LIGHT_SLEEP模式:进入轻度睡眠,CPU暂停,外设断电 + * - DEEP_SLEEP模式:深度睡眠,仅RTC运行,功耗最低 + */ +esp_err_t BoardSweekar::EnterLowPowerMode(power_mode_t mode) { + static const char *POWER_TAG = "POWER_MGMT"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "Entering power mode: %d", mode); + + if (mode == current_power_mode_) { + ESP_LOGW(POWER_TAG, "Already in power mode %d", mode); + return ESP_OK; + } + + // 保存当前模式,以便恢复 + power_mode_t previous_mode = current_power_mode_; + + switch (mode) { + case POWER_MODE_NORMAL: + // 正常模式,不需要特殊处理 + break; + + case POWER_MODE_IDLE: + // 空闲模式:关闭非必要外设 + ESP_LOGI(POWER_TAG, "Idle mode: disabling non-essential peripherals"); + + // 关闭SD卡(如果正在使用,需要先卸载) + if (is_sdcard_initialized_) { + // 注意:这里只是示例,实际应用中需要确保没有文件操作 + ESP_LOGW(POWER_TAG, "SD card still active in idle mode"); + } + + // 关闭显示背光 + // 关闭非必要GPIO的上拉/下拉 + // 降低I2C时钟频率 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠 + ESP_LOGI(POWER_TAG, "Light sleep mode: preparing for sleep"); + + // 设置唤醒源(需要根据具体需求配置) + // esp_sleep_enable_timer_wakeup(1000000); // 1秒后唤醒 + // esp_sleep_enable_ext0_wakeup(GPIO_NUM_XX, 0); // 外部中断唤醒 + + // 进入轻度睡眠 + // ret = esp_light_sleep_start(); + if (ret != ESP_OK) { + ESP_LOGE(POWER_TAG, "Failed to enter light sleep: %s", esp_err_to_name(ret)); + current_power_mode_ = previous_mode; + return ret; + } + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠 + ESP_LOGI(POWER_TAG, "Deep sleep mode: system will restart after wake-up"); + + // 保存必要的RTC数据 + // rtc_gpio_isolate(GPIO_NUM_XX); // 隔离GPIO + + // 设置深度睡眠唤醒源 + // esp_sleep_enable_timer_wakeup(60000000); // 60秒后唤醒 + + // 进入深度睡眠 + // esp_deep_sleep_start(); + // 注意:深度睡眠后系统会重启,不会返回 + break; + + default: + ESP_LOGE(POWER_TAG, "Invalid power mode: %d", mode); + return ESP_ERR_INVALID_ARG; + } + + current_power_mode_ = mode; + ESP_LOGI(POWER_TAG, "Successfully entered power mode: %d", mode); + return ret; +} + +/** + * @brief 退出低功耗模式 + * + * 从低功耗模式恢复到正常运行模式,重新初始化必要的硬件资源。 + */ +esp_err_t BoardSweekar::ExitLowPowerMode(void) { + static const char *POWER_TAG = "POWER_MGMT"; + + ESP_LOGI(POWER_TAG, "Exiting low power mode"); + + if (current_power_mode_ == POWER_MODE_NORMAL) { + ESP_LOGW(POWER_TAG, "Already in normal mode"); + return ESP_OK; + } + + // 根据之前的功耗模式执行相应的恢复操作 + switch (current_power_mode_) { + case POWER_MODE_IDLE: + // 恢复空闲模式 + ESP_LOGI(POWER_TAG, "Restoring from idle mode"); + + // 重新初始化必要的外设 + // 恢复I2C时钟频率 + // 重新开启显示背光 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠唤醒后会自动继续执行 + ESP_LOGI(POWER_TAG, "Woken up from light sleep"); + + // 重新配置时钟(如果需要) + // 重新初始化外设 + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠后系统会重启,不会执行到这里 + ESP_LOGW(POWER_TAG, "Deep sleep recovery should not reach here"); + break; + + default: + break; + } + + // 恢复到正常模式 + current_power_mode_ = POWER_MODE_NORMAL; + ESP_LOGI(POWER_TAG, "Successfully exited low power mode"); + + return ESP_OK; +} + +/** + * @brief 设置硬件模块电源状态 + * + * 提供精细化的电源管理,可以单独控制各个硬件模块的电源状态。 + */ +esp_err_t BoardSweekar::SetModulePower(hardware_module_t module, bool enable) { + static const char *POWER_TAG = "POWER_MODULE"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "%s module %d", enable ? "Enabling" : "Disabling", module); + + // 更新电源状态位图 + if (enable) { + power_state_flags_ |= (1 << module); + } else { + power_state_flags_ &= ~(1 << module); + } + + switch (module) { + case HW_MODULE_I2C: + if (!enable && bus_handle_) { + // 注意:I2C总线通常不建议完全关闭,可能影响多个设备 + ESP_LOGW(POWER_TAG, "I2C bus power control not recommended"); + } + break; + + case HW_MODULE_SDCARD: + if (enable && !is_sdcard_initialized_) { + ret = InitializeSdcard(); + } else if (!enable && is_sdcard_initialized_) { + // 卸载SD卡文件系统 + // 注意:需要确保没有打开的文件 + ESP_LOGW(POWER_TAG, "SD card power down requires file system cleanup"); + } + break; + + case HW_MODULE_LCD: + // LCD背光控制 + // LCD电源引脚控制(如果有) + break; + + case HW_MODULE_AUDIO: + // 音频编解码器电源控制 + // 需要与AudioPlayer协调 + break; + + case HW_MODULE_MOTOR: + // 步进电机驱动器电源控制 + // 电机睡眠模式 + break; + + case HW_MODULE_GYROSCOPE: + // 陀螺仪电源控制 + // 设置陀螺仪为低功耗模式 + break; + + case HW_MODULE_TOUCH: + // 触摸传感器电源控制 + // 设置触摸传感器为睡眠模式 + break; + + case HW_MODULE_RADAR: + // 毫米波雷达电源控制 + // 设置雷达为低功耗模式 + break; + + case HW_MODULE_IO_EXPAND: + // IO扩展板电源控制 + // 设置扩展板为低功耗模式 + break; + + default: + ESP_LOGE(POWER_TAG, "Unknown hardware module: %d", module); + return ESP_ERR_INVALID_ARG; + } + + if (ret == ESP_OK) { + ESP_LOGI(POWER_TAG, "Module %d power %s successfully", module, enable ? "enabled" : "disabled"); + } else { + ESP_LOGE(POWER_TAG, "Failed to %s module %d: %s", enable ? "enable" : "disable", module, esp_err_to_name(ret)); + } + + return ret; +} + + + diff --git a/main/02_physical/boards/board_v1_1_16/board_sweekar.h b/main/02_physical/boards/board_v1_1_16/board_sweekar.h new file mode 100644 index 0000000..dc8463f --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/board_sweekar.h @@ -0,0 +1,174 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +// #include "stepper_motor.h" // ! +#include "stepper_driver.h" +#include "i2c_bus.h" +#include "gyroscope.h" +// #include "touch_sensor.h" // ! +#include "base_board_control.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" + +/** + * @brief 功耗模式枚举 + */ +typedef enum { + POWER_MODE_NORMAL, // 正常运行模式,所有硬件可用 + POWER_MODE_IDLE, // 空闲模式,关闭非必要外设 + POWER_MODE_LIGHT_SLEEP, // 轻度睡眠,快速唤醒 + POWER_MODE_DEEP_SLEEP, // 深度睡眠,仅RTC运行 +} power_mode_t; + +/** + * @brief 硬件模块枚举,用于精细化电源管理 + */ +typedef enum { + HW_MODULE_I2C, // I2C总线 + HW_MODULE_SDCARD, // SD卡 + HW_MODULE_LCD, // LCD显示 + HW_MODULE_AUDIO, // 音频编解码器 + HW_MODULE_MOTOR, // 步进电机 + HW_MODULE_GYROSCOPE, // 陀螺仪 + HW_MODULE_TOUCH, // 触摸传感器 + HW_MODULE_RADAR, // 毫米波雷达 + HW_MODULE_IO_EXPAND, // IO扩展板 +} hardware_module_t; + +/** + * @brief 硬件板级管理器 + * + * 采用高内聚低耦合设计原则,统一管理所有硬件资源。 + * 提供统一的硬件初始化接口和资源访问,避免各模块重复初始化。 + */ +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + esp_periph_set_handle_t periph_set_ = nullptr; // 外设管理句柄 + bool is_sdcard_initialized_ = false; // SD卡初始化状态 + power_mode_t current_power_mode_ = POWER_MODE_NORMAL; // 当前功耗模式 + uint32_t power_state_flags_ = 0xFFFFFFFF; // 各模块电源状态位图(1为开启) + +public: + BoardSweekar(); + ~BoardSweekar(); + + IOExpansionBoard io_expansion_; // IO扩展板对象 + Gyroscope gyroscope_; // 陀螺仪对象 + // TouchSensor touch_sensor_; // 触摸传感器对象 //! + BaseBoardControl base_board_control_; // 底座控制对象 + StepperDriver* stepper_driver_ = nullptr; // 步进电机驱动对象 + + StepperDriver* GetStepperDriver() const { return stepper_driver_; } + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化LCD IO引脚(消除与步进电机的耦合) + void LcdIoInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 初始化底座控制串口 + void BaseBoardControlInit(void); + + // 初始化SD卡(推荐使用统一的硬件初始化) + esp_err_t InitializeSdcard(void); + + // 获取SD卡初始化状态 + bool IsSdcardInitialized() const { return is_sdcard_initialized_; } + + // 获取外设管理句柄(供需要访问SD卡的模块使用) + esp_periph_set_handle_t GetPeriphSet() const { return periph_set_; } + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } + + /** + * @brief 统一的硬件初始化接口 + * + * 按照依赖关系顺序初始化所有硬件资源,确保各模块可以安全使用。 + * 推荐在主程序启动时调用此函数,而不是分别调用各个初始化函数。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardware(void); + + /** + * @brief 完整的统一硬件初始化接口(专为复杂应用优化) + * + * 包含所有必要外设的完整初始化,适用于demo5等复杂应用场景。 + * 这个函数会初始化:I2C总线、IO扩展板、SD卡、LCD IO、底座控制等。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardwareComplete(void); + + /** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置,优化启动流程。 + * + * @param app_type 应用类型(demo5、音频播放器、显示应用等) + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeHardwareForApplication(int app_type); + + /** + * @brief 功耗管理接口 - 进入低功耗模式 + * + * 根据指定的功耗模式,关闭非必要硬件、设置GPIO为低功耗状态、 + * 调整时钟频率等。支持多种功耗模式以适应不同应用场景。 + * + * @param mode 功耗模式 + * @return esp_err_t 操作结果 + */ + esp_err_t EnterLowPowerMode(power_mode_t mode); + + /** + * @brief 功耗管理接口 - 退出低功耗模式 + * + * 从低功耗模式恢复,重新初始化必要的硬件资源。 + * + * @return esp_err_t 操作结果 + */ + esp_err_t ExitLowPowerMode(void); + + /** + * @brief 获取当前功耗模式 + * + * @return power_mode_t 当前功耗模式 + */ + power_mode_t GetPowerMode(void) const { return current_power_mode_; } + + /** + * @brief 设置硬件模块电源状态 + * + * 单独控制某个硬件模块的电源状态,用于精细化的功耗管理。 + * + * @param module 硬件模块 + * @param enable true为开启,false为关闭 + * @return esp_err_t 操作结果 + */ + esp_err_t SetModulePower(hardware_module_t module, bool enable); + + /** + * @brief 执行系统关机(拉高 SHUTDOWN_CTRL_PIN) + */ + void SystemShutdown(void); +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_16/board_sweekar_complete_init.cc b/main/02_physical/boards/board_v1_1_16/board_sweekar_complete_init.cc new file mode 100644 index 0000000..37a454a --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/board_sweekar_complete_init.cc @@ -0,0 +1,186 @@ +/** + * @file board_sweekar_complete_init.cc + * @brief 完整的统一硬件初始化实现 + * + * 这个文件提供了完整的统一硬件初始化函数,包含所有必要的硬件初始化步骤。 + * 专为demo5等复杂应用场景设计。 + */ + +/** + * @brief 完整的统一硬件初始化接口(专为demo5优化) + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备依赖) + * 2. IO扩展板(控制其他外设) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. LCD IO引脚(显示相关) + * 5. 底座控制串口(通信) + * 6. 底座控制模块(业务逻辑) + * 7. 其他外设(根据应用需求) + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardwareComplete(void) +{ + static const char *HW_INIT_TAG = "HW_INIT_COMPLETE"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting complete hardware initialization..."); + + // 1. 初始化I2C总线(最基础,其他设备依赖) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "✓ I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "✓ I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "⚠ SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "✓ SD card initialized"); + } + + // 4. 初始化LCD IO引脚(显示相关) + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "✓ LCD IO pins initialized"); + + // 5. 初始化底座控制串口(demo5需要) + BaseBoardControlUartInit(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control UART initialized"); + + // 6. 初始化底座控制模块 + base_board_control_.Init(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control initialized"); + + // 7. 设置IO扩展板的PA控制模式(demo5需要) + io_expansion_.SetPaCtrlMode(true); + ESP_LOGI(HW_INIT_TAG, "✓ IO expansion PA control mode set"); + + // 8. 可选外设(根据具体应用需求启用) + // ESP_LOGI(HW_INIT_TAG, "Initializing optional peripherals..."); + // + // // 毫米波雷达(如需要) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Millimeter wave radar initialized"); + // + // // VBAT升压(如需要) + // VbatToVbat5vInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ VBAT to 5V booster initialized"); + // + // // 步进电机(如需要) + // StepperMotorInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Stepper motor initialized"); + + ESP_LOGI(HW_INIT_TAG, "=== Complete hardware initialization finished successfully ==="); + return ESP_OK; +} + +/** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置 + */ +esp_err_t BoardSweekar::InitializeHardwareForApplication(application_type_t app_type) { + static const char *APP_INIT_TAG = "APP_HW_INIT"; + + ESP_LOGI(APP_INIT_TAG, "Initializing hardware for application type: %d", app_type); + + // 基础硬件初始化(所有应用都需要) + esp_err_t ret = InitializeAllHardwareComplete(); + if (ret != ESP_OK) { + ESP_LOGE(APP_INIT_TAG, "Basic hardware initialization failed"); + return ret; + } + + switch (app_type) { + case APP_TYPE_DEMO5: // 宠物游戏demo + ESP_LOGI(APP_INIT_TAG, "Configuring for Demo5 (Pet Game)..."); + + // Demo5需要的额外初始化 + // 触摸传感器、陀螺仪、RGB LED等会在应用层初始化 + + ESP_LOGI(APP_INIT_TAG, "✓ Demo5 hardware configuration complete"); + break; + + case APP_TYPE_AUDIO_PLAYER: // 音频播放器 + ESP_LOGI(APP_INIT_TAG, "Configuring for Audio Player..."); + + // 音频应用需要确保音频编解码器正确初始化 + // 这部分通常在AudioPlayer类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Audio player hardware configuration complete"); + break; + + case APP_TYPE_DISPLAY_ONLY: // 纯显示应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Display Only..."); + + // 显示应用需要确保LCD相关硬件正确配置 + // 这部分通常在Display类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Display-only hardware configuration complete"); + break; + + case APP_TYPE_MINIMAL: // 最小化应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Minimal Application..."); + + // 最小化配置,只保留最基本的功能 + // 关闭非必要外设以节省功耗 + + ESP_LOGI(APP_INIT_TAG, "✓ Minimal hardware configuration complete"); + break; + + default: + ESP_LOGW(APP_INIT_TAG, "Unknown application type, using default configuration"); + break; + } + + return ESP_OK; +} + +/** + * @brief 硬件初始化状态报告 + * + * 提供详细的硬件初始化状态报告,便于调试和问题排查 + */ +void BoardSweekar::ReportHardwareStatus(void) { + static const char *STATUS_TAG = "HW_STATUS"; + + ESP_LOGI(STATUS_TAG, "=== Hardware Initialization Status Report ==="); + + // I2C总线状态 + ESP_LOGI(STATUS_TAG, "I2C Bus: %s", (bus_handle_ != nullptr) ? "✓ Initialized" : "✗ Not initialized"); + + // SD卡状态 + ESP_LOGI(STATUS_TAG, "SD Card: %s", (is_sdcard_initialized_) ? "✓ Initialized" : "✗ Not initialized"); + if (is_sdcard_initialized_) { + ESP_LOGI(STATUS_TAG, " Peripheral Set: %s", (periph_set_ != nullptr) ? "✓ Available" : "✗ Not available"); + } + + // IO扩展板状态 + ESP_LOGI(STATUS_TAG, "IO Expansion Board: %s", (io_expansion_.bus_handle_ != nullptr) ? "✓ Configured" : "✗ Not configured"); + + // 底座控制状态 + ESP_LOGI(STATUS_TAG, "Base Board Control: %s", (base_board_control_.GetUartPort() != 0) ? "✓ Initialized" : "✗ Not initialized"); + + // 功耗管理状态 + ESP_LOGI(STATUS_TAG, "Power Management: %s", (current_power_mode_ == POWER_MODE_NORMAL) ? "✓ Normal mode" : "⚠ Low power mode"); + ESP_LOGI(STATUS_TAG, " Current Mode: %d", current_power_mode_); + ESP_LOGI(STATUS_TAG, " Module Power States: 0x%08X", power_state_flags_); + + // 内存状态 + ESP_LOGI(STATUS_TAG, "Memory Status:"); + ESP_LOGI(STATUS_TAG, " Internal RAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI(STATUS_TAG, " PSRAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + ESP_LOGI(STATUS_TAG, "=== End of Hardware Status Report ==="); +} \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_16/config.h b/main/02_physical/boards/board_v1_1_16/config.h new file mode 100644 index 0000000..e5a3387 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/config.h @@ -0,0 +1,138 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/timer.h" +#include "esp_intr_alloc.h" +#include "soc/timer_group_struct.h" // 包含TIMERG0、TIMERG1的结构体定义 +#include "soc/timer_group_reg.h" // 包含定时器相关寄存器宏定义 +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +// #include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机配置 */ +#define STEPPER_STEP_IO GPIO_NUM_42 // 步进电机脉冲输入引脚 +#define STEPPER_TIMER_RES_HZ 1000000 // 步进电机定时器分辨率 1 MHz(1 us 步进) + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_45 // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 1 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO40 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO39 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x58 // AW9523BTQR器件地址,v1_1_13的addr1和addr0是0 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* AW触摸芯片配置 */ +#define AW_TOUCH_IRQ_PIN GPIO_NUM_11 // AW触摸芯片中断引脚 +#define NFC_RST_PIN GPIO_NUM_12 // NFC复位引脚 +#define SHUTDOWN_CTRL_PIN GPIO_NUM_13 // 关机控制引脚 + +/* 原生触摸引脚配置 (v1.1.16已改为上述功能,保留定义需谨慎) */ +// 触摸按键数量配置 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 - 注意:v1.1.16如果不使用ESP32原生触摸,这些定义可能不再适用 +// 但为了兼容编译,可以保留或注释 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 (Now AW_IRQ) +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 (Now NFC_RST) +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 (Now SHUTDOWN) + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +/* 底座控制命令串口配置 */ +#define BASE_BOARD_CTL_UART_PORT UART_NUM_0 +#define BASE_BOARD_CTL_UART_TX_PIN GPIO_NUM_43 +#define BASE_BOARD_CTL_UART_RX_PIN GPIO_NUM_44 +#define BASE_BOARD_CTL_UART_BAUDRATE 115200 +#define BASE_BOARD_CTL_UART_BUF_SIZE 256 + +/* 背光控制引脚配置 */ +#define BL_PIN GPIO_NUM_45 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/02_physical/boards/board_v1_1_16/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_16/io_expansion_board.cc new file mode 100644 index 0000000..9e976d2 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/io_expansion_board.cc @@ -0,0 +1,1034 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "photo_interrupter.h" +#include "adc_battery_monitor.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + last_port0_state_(0), + last_port1_state_(0), + radar_last_state_(false), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr), + is_initialized_(false), + interrupt_gpio_(GPIO_NUM_NC), + reset_gpio_(GPIO_NUM_NC) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + + // 调试:记录中断触发 + // ESP_EARLY_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发!"); // IRAM函数中不能使用普通日志 + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_ANYEDGE; // 改为双沿触发,检测上升沿和下降沿 + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + // io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + // io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 计算Port0的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort0() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT0){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT0){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT0){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT0){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT0){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 计算Port1的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort1() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT1){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT1){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT1){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT1){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT1){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +/** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort0(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT0, data); +} + +/** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort1(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT1, data); +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + + err = IoexpandWriteReg(CONFIG_PORT0, ByteCalculatePort0()); + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort0()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功, 0x%02X", ByteCalculatePort0()); + } + + err = IoexpandWriteReg(CONFIG_PORT1, ByteCalculatePort1()); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort1()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功, 0x%02X", ByteCalculatePort1()); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + err = IoexpandWriteReg(OUTPUT_PORT0, 0x40); // 0100 0000 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x04); // 0000 0100 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + + // AW9523B中断使能配置:0为使能中断,1为禁止中断 + // 使能P0_5(充电检测)和P1_3(充满检测)的中断 + uint8_t int_enable_port0 = 0x00; // 0x00 = 使能所有P0引脚中断 + uint8_t int_enable_port1 = 0x00; // 0x00 = 使能所有P1引脚中断 + + err = IoexpandWriteReg(INT_PORT0, int_enable_port0); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能成功: 0x%02X (0=使能)", int_enable_port0); + } + + err = IoexpandWriteReg(INT_PORT1, int_enable_port1); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功: 0x%02X (0=使能)", int_enable_port1); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << enable_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入睡眠状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入复位模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val |= (1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val &= ~(1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LED_EN 引脚状态(v1.1.16:高电平有效,低电平无效) + */ +esp_err_t IOExpansionBoard::SetLedEnMode(bool enable) +{ + uint8_t current_val; + esp_err_t err = IoexpandReadReg(LED_EN_PORT, ¤t_val); + if (err != ESP_OK) return err; + if (enable) { + current_val |= (1 << LED_EN_PIN); // 高电平:开启 + } else { + current_val &= ~(1 << LED_EN_PIN); // 低电平:关闭 + } + return IoexpandWriteReg(LED_EN_PORT, current_val); +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val |= (1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val &= ~(1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 PA_CTRL引脚电平 + */ +esp_err_t IOExpansionBoard::SetPaCtrlMode(bool mode) +{ + uint8_t current_val; + if(mode){ + // 进入高电平状态 + ESP_LOGI("io_ex:", "read reg"); + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val |= (1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } + else{ + // 进入低电平状态 + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val &= ~(1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } +} + + + +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + + // 执行硬件复位 - 这是解决中断引脚持续拉低的关键 + err = instance->ResetIoExpand(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位成功"); + + // 检查复位后的中断引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "复位后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + } + + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 - 复位后必须重新配置寄存器! + err = instance->IoexpandSetMode(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + // 设置LCD屏幕复位引脚为低电平 + err = instance->SetLcdResetMode(LCD_RST_LOW, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + + uint8_t current_val_port0_ = 0,current_val_port1_ = 0,current_temp_ = 0; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板中断触发"); + + // 检查中断触发时的引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发时引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + // AW9523B中断处理说明(根据数据手册): + // 1. 当输入引脚状态变化时,AW9523B会拉低INT引脚(连接到ESP32的GPIO10) + // 2. 通过读取输入端口寄存器(00H, 01H)来清除对应端口的中断 + // 3. 无独立的中断状态寄存器,需要通过输入电平变化来判断中断源 + // 4. 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + + // 一次性读取两个端口的输入寄存器,避免重复读取和错误的OR操作 + instance->IoexpandReadReg(INPUT_PORT0, ¤t_val_port0_); + instance->IoexpandReadReg(INPUT_PORT1, ¤t_val_port1_); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "读取端口状态 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + + // 检查读取寄存器后中断引脚是否被清除(应该恢复高电平) + interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "读取寄存器后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + //判断是否有中断来自雷达引脚 + if((RADAR_INTERRUPT_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((RADAR_INTERRUPT_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚1\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到雷达引脚中断\n"); + // } + + //判断是否有中断来自初始光学传感器引脚 + if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到初始光学传感器引脚中断\n"); + // } + + //判断是否有中断来自结束光学传感器引脚 + if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + + //CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 + //FULL未充满为高电平 充满电为低电平 + //判断是否有中断来自电池充电引脚 + //根据配置:BATTERY_CHRG_INPUT = INPUT_PORT0, BATTERY_FULL_INPUT = INPUT_PORT1 + + // AW9523B没有独立的中断状态寄存器,需要通过比较前后状态变化来判断中断源 + // 由于读取输入端口会清除中断,我们需要先保存当前状态,然后与上次状态比较 + + //获取当前电池状态 + bool current_charging_state = !(current_val_port0_ & (1 << BATTERY_CHRG_PIN)); // CHRG低电平表示充电中 + bool current_full_state = !(current_val_port1_ & (1 << BATTERY_FULL_PIN)); // FULL低电平表示已充满 + + // 详细的调试信息 + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - CHRG_PIN: %d (bit %d), FULL_PIN: %d (bit %d)", + (current_val_port0_ & (1 << BATTERY_CHRG_PIN)) ? 1 : 0, BATTERY_CHRG_PIN, + (current_val_port1_ & (1 << BATTERY_FULL_PIN)) ? 1 : 0, BATTERY_FULL_PIN); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 充电状态: %s, 充满状态: %s", + current_charging_state ? "充电中" : "未充电", + current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 上次充电状态: %s, 上次充满状态: %s", + instance->battery_charging_last_state_ ? "充电中" : "未充电", + instance->battery_full_last_state_ ? "已充满" : "未充满"); + + //检查充电状态是否发生变化 + if (current_charging_state != instance->battery_charging_last_state_) { + instance->battery_charging_last_state_ = current_charging_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充电状态变化: %s", current_charging_state ? "充电中" : "未充电"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充电状态变化详情 - 从 %s 到 %s", + instance->battery_charging_last_state_ ? "未充电" : "充电中", + current_charging_state ? "充电中" : "未充电"); + + if (current_charging_state) { + // 进入充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到开始充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerChrgCallback(); + } + if (instance->battery_charging_callback_) { + instance->battery_charging_callback_(); + } + } else { + // 退出充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到停止充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerNotChrgCallback(); + } + if (instance->battery_not_charging_callback_) { + instance->battery_not_charging_callback_(); + } + } + } + + //检查充满状态是否发生变化 + if (current_full_state != instance->battery_full_last_state_) { + instance->battery_full_last_state_ = current_full_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充满状态变化: %s", current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充满状态变化详情 - 从 %s 到 %s", + instance->battery_full_last_state_ ? "未充满" : "已充满", + current_full_state ? "已充满" : "未充满"); + + if (current_full_state) { + // 进入充满状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到电池已充满"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerFullCallback(); + } + if (instance->battery_full_callback_) { + instance->battery_full_callback_(); + } + } + + } + + // 根据AW9523B数据手册:读取输入端口寄存器清除对应端口的中断 + // 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "中断处理完成,已读取输入端口寄存器清除中断标志"); + } +} + + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_16/io_expansion_board.h b/main/02_physical/boards/board_v1_1_16/io_expansion_board.h new file mode 100644 index 0000000..0157e39 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/io_expansion_board.h @@ -0,0 +1,432 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; +class PhotoInterrupter; +class AdcBatteryMonitor; + +// 回调函数类型定义 +typedef std::function RadarCallback; +typedef std::function PhotoInterrupterStartCallback; +typedef std::function PhotoInterrupterEndCallback; +typedef std::function BatteryChargingCallback; +typedef std::function BatteryFullCallback; +typedef std::function BatteryNotChargingCallback; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 +#define STEPPER_MOTOR_DIR_CONFIG CONFIG_PORT0 //步进电机方向引脚配置在P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M0_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M0_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_5 + +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_M1_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M1_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_4 + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_CONFIG CONFIG_PORT1 //步进电机睡眠引脚配置在P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_CONFIG CONFIG_PORT0 //电机使能引脚配置在P0_7 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_CONFIG CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口 +#define RADAR_INTERRUPT_INPUT INPUT_PORT1 //毫米波雷达中断引脚输入状态检测在INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +// 光学传感器引脚定义 +#define START_PHOTO_INTERRUPTER_PIN 2 // 光学传感器引脚(P0_2) +#define START_PHOTO_INTERRUPTER_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define START_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define START_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define START_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +#define END_PHOTO_INTERRUPTER_PIN 3 // 光学传感器引脚(P0_3) +#define END_PHOTO_INTERRUPTER_PORT_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define END_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define END_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define END_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +/***********************************LED_EN 电源控制(WS2812/背光等)*************************************/ +// v1.1.16: LED_EN 高电平有效,低电平无效;端口为 IO 扩展 P0_1 +#define LED_EN_PIN 1 +#define LED_EN_PORT OUTPUT_PORT0 +#define LED_EN_CONFIG CONFIG_PORT0 + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +// /* 设置LCD的BL引脚 */ +// #define LCD_BL_PIN 6 //LCD背光引脚对应IO扩展板引脚P0_6 +// #define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +// #define LCD_BL_CONFIG CONFIG_PORT0 //LCD背光引脚配置在P0_6 +// #define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +// #define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚对应IO扩展板引脚P1_1 +#define LCD_RST_CONFIG CONFIG_PORT1 //LCD复位引脚配置在P1_1 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置PA控制引脚 */ +#define PA_CTRL_PIN 2 //PA控制引脚对应IO扩展板引脚P1_2 +#define PA_CTRL_CONFIG CONFIG_PORT1 //PA控制引脚配置在P1_2 +#define PA_CTRL_PORT OUTPUT_PORT1 //PA控制引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define PA_CTRL_HIGH true //PA控制引脚状态为高电平 +#define PA_CTRL_LOW false //PA控制引脚状态为低电平 + +// 电池充电检测引脚定义 +#define BATTERY_FULL_INPUT INPUT_PORT1 // 电池充满检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_FULL_PIN 3 // 电池充满检测引脚对应IO扩展板引脚P1_3 +#define BATTERY_FULL_CONFIG CONFIG_PORT1 // 电池充满检测引脚配置在P1_3 + +#define BATTERY_CHRG_INPUT INPUT_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_CHRG_PIN 5 // 充电检测引脚(P0_5) +#define BATTERY_CHRG_CONFIG CONFIG_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +// #define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 设置限位开关指针 + * @param photo_interrupter 限位开关对象指针 + */ + void setPhotoInterrupter(PhotoInterrupter* photo_interrupter) { photo_interrupter_ = photo_interrupter; }; + + /** + * @brief 设置电池监控器指针 + * @param battery_monitor 电池监控器对象指针 + */ + void setBatteryMonitor(AdcBatteryMonitor* battery_monitor) { battery_monitor_ = battery_monitor; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + /** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort0(uint8_t *data); + + /** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort1(uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置 LED_EN 引脚状态(v1.1.16:高电平有效,低电平无效) + * @param enable true: 开启(置高),false: 关闭(置低) + * @return esp_err_t 操作结果 + */ + esp_err_t SetLedEnMode(bool enable); + + /** + * @brief 设置PA控制引脚状态 + * @param mode PA_CTRL状态(true为高电平,false为低电平) + * @return esp_err_t 操作结果 + */ + esp_err_t SetPaCtrlMode(bool mode); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + /** + * @brief 计算Port0的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort0(); + + /** + * @brief 计算Port1的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort1(); + + /** + * @brief 设置雷达中断回调函数 + * @param callback 雷达中断回调函数 + */ + void SetRadarCallback(RadarCallback callback) { radar_callback_ = callback; } + + /** + * @brief 设置光学传感器开始中断回调函数 + * @param callback 光学传感器开始中断回调函数 + */ + void SetPhotoInterrupterStartCallback(PhotoInterrupterStartCallback callback) { photo_start_callback_ = callback; } + + /** + * @brief 设置光学传感器结束中断回调函数 + * @param callback 光学传感器结束中断回调函数 + */ + void SetPhotoInterrupterEndCallback(PhotoInterrupterEndCallback callback) { photo_end_callback_ = callback; } + + /** + * @brief 设置电池充电中断回调函数 + * @param callback 电池充电中断回调函数 + */ + void SetBatteryChargingCallback(BatteryChargingCallback callback) { battery_charging_callback_ = callback; } + + /** + * @brief 设置电池充满中断回调函数 + * @param callback 电池充满中断回调函数 + */ + void SetBatteryFullCallback(BatteryFullCallback callback) { battery_full_callback_ = callback; } + + /** + * @brief 设置电池非充电中断回调函数 + * @param callback 电池非充电中断回调函数 + */ + void SetBatteryNotChargingCallback(BatteryNotChargingCallback callback) { battery_not_charging_callback_ = callback; } + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + PhotoInterrupter* photo_interrupter_ = nullptr; // 限位开关指针,用于触发回调 + AdcBatteryMonitor* battery_monitor_ = nullptr; // 电池监控器指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // 成员变量用于状态跟踪 + uint8_t last_port0_state_ = 0; // 上次检测到的Port0状态 + uint8_t last_port1_state_ = 0; // 上次检测到的Port1状态 + bool radar_last_state_ = false; // 雷达引脚上一次的状态 + bool battery_charging_last_state_ = false; // 电池充电状态上一次的状态 + bool battery_full_last_state_ = false; // 电池充满状态上一次的状态 + + // 回调函数成员变量 + RadarCallback radar_callback_ = nullptr; + PhotoInterrupterStartCallback photo_start_callback_ = nullptr; + PhotoInterrupterEndCallback photo_end_callback_ = nullptr; + BatteryChargingCallback battery_charging_callback_ = nullptr; + BatteryFullCallback battery_full_callback_ = nullptr; + BatteryNotChargingCallback battery_not_charging_callback_ = nullptr; + + // PhotoInterrupter* photo_interrupter_ = nullptr; // 光学传感器指针,用于触发回调 + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif diff --git a/main/02_physical/boards/board_v1_1_16/power_management_config.h b/main/02_physical/boards/board_v1_1_16/power_management_config.h new file mode 100644 index 0000000..da609fb --- /dev/null +++ b/main/02_physical/boards/board_v1_1_16/power_management_config.h @@ -0,0 +1,178 @@ +/** + * @file power_management_config.h + * @brief 功耗管理配置和策略定义 + * @author Embedded Software Expert + * @date 2025年1月 + * + * 这个文件定义了功耗管理的配置参数、策略和优化建议, + * 为系统提供灵活的功耗管理方案。 + */ + +#ifndef POWER_MANAGEMENT_CONFIG_H +#define POWER_MANAGEMENT_CONFIG_H + +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp32/rom/rtc.h" + +/** + * @brief 功耗管理配置结构体 + */ +typedef struct { + // 睡眠模式配置 + uint64_t light_sleep_duration_us; // 轻度睡眠持续时间(微秒) + uint64_t deep_sleep_duration_us; // 深度睡眠持续时间(微秒) + + // 唤醒源配置 + bool enable_timer_wakeup; // 启用定时器唤醒 + bool enable_gpio_wakeup; // 启用GPIO唤醒 + bool enable_touch_wakeup; // 启用触摸唤醒 + gpio_num_t wakeup_gpio_num; // 唤醒GPIO编号 + int wakeup_level; // 唤醒电平(0或1) + + // 功耗优化配置 + bool disable_bt_in_sleep; // 睡眠时关闭蓝牙 + bool disable_wifi_in_sleep; // 睡眠时关闭WiFi + bool reduce_cpu_frequency; // 降低CPU频率 + uint32_t cpu_frequency_hz; // CPU频率(Hz) + + // 外设功耗配置 + bool power_down_sdcard; // 睡眠时关闭SD卡电源 + bool power_down_display; // 睡眠时关闭显示电源 + bool power_down_audio; // 睡眠时关闭音频电源 + bool power_down_motor; // 睡眠时关闭电机电源 + + // RTC内存配置 + bool preserve_rtc_memory; // 保留RTC内存 + size_t rtc_memory_size; // RTC内存大小 +} power_management_config_t; + +/** + * @brief 默认功耗管理配置 + */ +#define DEFAULT_POWER_CONFIG { \ + .light_sleep_duration_us = 1000000, /* 1秒 */ \ + .deep_sleep_duration_us = 30000000, /* 30秒 */ \ + .enable_timer_wakeup = true, /* 启用定时器唤醒 */ \ + .enable_gpio_wakeup = false, /* 不启用GPIO唤醒 */ \ + .enable_touch_wakeup = false, /* 不启用触摸唤醒 */ \ + .wakeup_gpio_num = GPIO_NUM_NC, /* 无GPIO唤醒 */ \ + .wakeup_level = 0, /* 低电平唤醒 */ \ + .disable_bt_in_sleep = true, /* 睡眠时关闭蓝牙 */ \ + .disable_wifi_in_sleep = true, /* 睡眠时关闭WiFi */ \ + .reduce_cpu_frequency = true, /* 降低CPU频率 */ \ + .cpu_frequency_hz = 80000000, /* 80MHz */ \ + .power_down_sdcard = true, /* 睡眠时关闭SD卡 */ \ + .power_down_display = true, /* 睡眠时关闭显示 */ \ + .power_down_audio = true, /* 睡眠时关闭音频 */ \ + .power_down_motor = true, /* 睡眠时关闭电机 */ \ + .preserve_rtc_memory = true, /* 保留RTC内存 */ \ + .rtc_memory_size = 4096, /* 4KB RTC内存 */ \ +} + +/** + * @brief 各功耗模式的详细配置 + */ +static const power_management_config_t idle_mode_config = { + .light_sleep_duration_us = 500000, // 500ms轻度睡眠 + .deep_sleep_duration_us = 0, // 不使用深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, // 按键唤醒 + .wakeup_level = 0, + .disable_bt_in_sleep = false, // 保持蓝牙连接 + .disable_wifi_in_sleep = false, // 保持WiFi连接 + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, // 40MHz + .power_down_sdcard = false, // 保持SD卡(可能有后台任务) + .power_down_display = true, // 关闭显示背光 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 2048, +}; + +static const power_management_config_t light_sleep_config = { + .light_sleep_duration_us = 0, // 直接进入轻度睡眠 + .deep_sleep_duration_us = 0, + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 4096, +}; + +static const power_management_config_t deep_sleep_config = { + .light_sleep_duration_us = 0, + .deep_sleep_duration_us = 60000000, // 60秒深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 10000000, // 10MHz + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 8192, +}; + +/** + * @brief 功耗统计和监控结构体 + */ +typedef struct { + uint64_t total_sleep_time_us; // 总睡眠时间 + uint64_t total_wake_time_us; // 总唤醒时间 + uint32_t sleep_count; // 睡眠次数 + uint32_t wake_count; // 唤醒次数 + uint32_t average_sleep_duration_us; // 平均睡眠时长 + uint32_t average_wake_duration_us; // 平均唤醒时长 + float estimated_power_saving_percent; // 估计的功耗节省百分比 +} power_statistics_t; + +/** + * @brief 功耗优化建议 + * + * 基于硬件特性和使用模式,提供功耗优化建议 + */ +typedef enum { + OPTIMIZATION_NONE = 0, + OPTIMIZATION_REDUCE_CPU_FREQ, // 降低CPU频率 + OPTIMIZATION_DISABLE_UNUSED_PERIPHERALS, // 关闭未使用的外设 + OPTIMIZATION_USE_LIGHT_SLEEP, // 使用轻度睡眠 + OPTIMIZATION_USE_DEEP_SLEEP, // 使用深度睡眠 + OPTIMIZATION_OPTIMIZE_GPIO_STATES, // 优化GPIO状态 + OPTIMIZATION_USE_RTC_MEMORY, // 使用RTC内存 + OPTIMIZATION_BATCH_OPERATIONS, // 批量操作 +} power_optimization_t; + +/** + * @brief 功耗管理事件回调 + */ +typedef void (*power_event_callback_t)(power_mode_t old_mode, power_mode_t new_mode, void* user_data); + +/** + * @brief 功耗管理事件 + */ +typedef enum { + POWER_EVENT_MODE_CHANGE, // 功耗模式改变 + POWER_EVENT_SLEEP_START, // 睡眠开始 + POWER_EVENT_SLEEP_END, // 睡眠结束 + POWER_EVENT_WAKEUP, // 系统唤醒 + POWER_EVENT_OPTIMIZATION_TRIGGER, // 功耗优化触发 +} power_event_t; + +#endif // POWER_MANAGEMENT_CONFIG_H \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_17/board_sweekar.cc b/main/02_physical/boards/board_v1_1_17/board_sweekar.cc new file mode 100644 index 0000000..59cd1dd --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/board_sweekar.cc @@ -0,0 +1,780 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "driver/gptimer.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +} + +#include "board_sweekar.h" +#include "config.h" +#include "board.h" // 添加board.h以获取audio_board_sdcard_init函数声明 +#include "drv8428.h" +#include "aw8646.h" +#include "esp_vfs_fat.h" +#include "audio_mem.h" +#include "esp_timer.h" +#include "base_board_hal_i2c.h" + +#define TAG "board_sweekar" + +static BoardSweekar* s_board_instance = nullptr; + +BoardSweekar* BoardSweekar::GetInstance() +{ + if (s_board_instance == nullptr) { + static BoardSweekar global_board; + s_board_instance = &global_board; + } + return s_board_instance; +} + +BoardSweekar::BoardSweekar() +{ +#ifdef CONFIG_STEPPER_DRIVER_AW8646 + stepper_driver_ = new Aw8646Driver(io_expansion_); +#else + stepper_driver_ = new Drv8428Driver(io_expansion_); +#endif + + // Initialize Shutdown Control Pin (GPIO_NUM_13) and pull it HIGH + // This is required to keep the main power on. +#ifdef SHUTDOWN_CTRL_PIN + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << SHUTDOWN_CTRL_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + // 拉低开机 + gpio_set_level(SHUTDOWN_CTRL_PIN, 0); + ESP_LOGI(TAG, "Initialized SHUTDOWN_CTRL_PIN (%d) to LOW (Power ON)", SHUTDOWN_CTRL_PIN); +#endif +} + +BoardSweekar::~BoardSweekar() +{ + if (stepper_driver_) { + delete stepper_driver_; + stepper_driver_ = nullptr; + } +} + +void BoardSweekar::SetAudioAmplifier(bool enable) { + // 隐藏具体的硬件实现细节(比如当前版本是通过 IO 扩展板控制的) + io_expansion_.SetPaCtrlMode(enable); + ESP_LOGI(TAG, "Audio Amplifier %s", enable ? "ENABLED" : "DISABLED"); +} + +/*========================== 步进电机相关驱动 ==========================*/ + +/** + * @brief 步进电机初始化函数 + * 配置步进电机所需的GPIO引脚模式,通过IO扩展板设置步进驱动芯片的工作模式 + * 在 stepper_motor.cc 中完成 GPTimer + GPIO 翻转 STEP 脉冲的初始化 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO(不再用 LEDC) + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低,保证上电不乱跳 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 初始化驱动对象并初始化 + if (stepper_driver_) { + err = stepper_driver_->Init(); + if (err != ESP_OK) { + ESP_LOGE(TAG_BOARD, "步进电机驱动初始化失败, 错误码: 0x%x", err); + } + } else { + ESP_LOGE(TAG_BOARD, "步进电机驱动创建失败"); + } +} + +/** + * @brief LCD IO引脚初始化函数(消除与步进电机的耦合) + * + * 配置LCD所需的IO扩展板寄存器,确保LCD复位引脚正常工作 + * 此函数独立于步进电机初始化,专门处理LCD相关的IO配置 + */ +void BoardSweekar::LcdIoInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) 检查I2C总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 2) 执行硬件复位,确保IO扩展板处于已知状态 + err = io_expansion_.ResetIoExpand(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板硬件复位失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板硬件复位成功"); + } + + // 3) 配置IO扩展板模式(端口方向等) + err = io_expansion_.IoexpandSetMode(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板模式配置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板模式配置成功"); + } + + // 4) 初始化LCD复位引脚:设置为高电平 + err = io_expansion_.SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "LCD复位引脚高电平设置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "LCD复位引脚高电平设置成功"); + } + + // 5) 初始化LED_EN电源为关闭状态 (PMOS高电平关) + // 只有在需要亮灯时,由ActuatorActor主动开启,避免平静状态下常亮 + err = io_expansion_.SetLedEnMode(false); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "初始化LED_EN电源(关闭)失败, 错误码: 0x%x", err); + } + else + { + ESP_LOGI(TAG_BOARD, "初始化LED_EN电源(关闭)成功"); + } +} + +/** + * @brief 执行系统关机 + * 内部拉高 SHUTDOWN_CTRL_PIN 引脚切断电源 + */ +void BoardSweekar::SystemShutdown(void) +{ +#ifdef SHUTDOWN_CTRL_PIN + ESP_LOGI(TAG, "Executing System Shutdown... Pulling SHUTDOWN_CTRL_PIN (%d) HIGH", SHUTDOWN_CTRL_PIN); + // 用户指定:拉高可以完成关机动作 + gpio_set_level(SHUTDOWN_CTRL_PIN, 1); + + // 给一点时间让电源完全断开 + vTaskDelay(pdMS_TO_TICKS(1000)); +#else + ESP_LOGW(TAG, "SHUTDOWN_CTRL_PIN is not defined. Cannot execute hardware shutdown."); +#endif +} + +/*========================== 毫米波雷达相关驱动 ==========================*/ + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/*========================== I2C总线及挂载的从设备相关驱动 ==========================*/ + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 线性马达的初始化已移至 InitializeAllHardware 中统一调用 Init 方法 + // linear_motor_ 的参数传递将在那里完成,不再此处直接赋值,提高内聚性 + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/*========================== VBAT to VBAT_5V相关驱动 ==========================*/ + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + +/*========================== 底座控制串口相关驱动 ==========================*/ + +/** + * @brief 底座控制命令串口初始化函数 + */ +// 根据配置选择通信方式的HAL头文件 +#ifdef CONFIG_BASE_BOARD_COMM_I2C +#include "base_board_hal_i2c.h" +#else +#include "base_board_hal_uart.h" +#endif + +void BoardSweekar::BaseBoardControlInit(void) +{ +#ifdef CONFIG_BASE_BOARD_COMM_I2C + // I2C Mode + if (bus_handle_ == nullptr) { + ESP_LOGE(TAG, "I2C bus not initialized before BaseBoardControlInit"); + return; + } + + // 创建 I2C HAL 实例 + BaseBoardHalI2c *hal = new BaseBoardHalI2c(bus_handle_); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl I2C initialized"); +#else + // UART Mode + uart_config_t uart_config = { + .baud_rate = BASE_BOARD_CTL_UART_BAUDRATE, + .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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(BASE_BOARD_CTL_UART_PORT, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(BASE_BOARD_CTL_UART_PORT, BASE_BOARD_CTL_UART_TX_PIN, BASE_BOARD_CTL_UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动 + ESP_ERROR_CHECK(uart_driver_install(BASE_BOARD_CTL_UART_PORT, + BASE_BOARD_CTL_UART_BUF_SIZE, + BASE_BOARD_CTL_UART_BUF_SIZE, + 0, NULL, 0)); + + BaseBoardHalUart *hal = new BaseBoardHalUart(BASE_BOARD_CTL_UART_PORT); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl UART initialized"); +#endif +} + +/*========================== SD卡相关驱动 ==========================*/ + +/** + * @brief SD卡初始化函数 + * + * 初始化SD卡外设,使用1线模式进行通信。该函数负责初始化外设管理器 + * 并配置SD卡,为后续的文件系统操作做准备。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示失败原因 + */ +esp_err_t BoardSweekar::InitializeSdcard(void) +{ + static const char *SDCARD_TAG = "SDCARD_INIT"; + esp_err_t ret; + + ESP_LOGI(SDCARD_TAG, "Initializing SD card"); + + if (is_sdcard_initialized_) { + ESP_LOGW(SDCARD_TAG, "SD card already initialized"); + return ESP_OK; + } + + // 初始化外设管理 + esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG(); + periph_cfg.extern_stack = true; // 将任务栈分配到 PSRAM 以节省 Internal SRAM + periph_set_ = esp_periph_set_init(&periph_cfg); + if (!periph_set_) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize peripheral set"); + return ESP_FAIL; + } + + // 初始化SD卡外设 + ret = audio_board_sdcard_init(periph_set_, SD_MODE_1_LINE); + if (ret != ESP_OK) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + return ret; + } + + is_sdcard_initialized_ = true; + ESP_LOGI(SDCARD_TAG, "SD card initialized successfully"); + return ESP_OK; +} + +/** + * @brief 统一的硬件初始化接口实现 + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备可能依赖) + * 2. IO扩展板(可能影响其他设备) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. 其他外设 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardware(void) +{ + static const char *HW_INIT_TAG = "HW_INIT"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting unified hardware initialization..."); + + // 1. 初始化I2C总线(最基础) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "SD card initialized"); + } + + // 4. 初始化LCD IO引脚 + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "LCD IO pins initialized"); + + // 5. 初始化底座控制 + if (bus_handle_ != nullptr) { + BaseBoardHalI2c *hal_i2c = new BaseBoardHalI2c(bus_handle_); + base_board_control_.Init(hal_i2c); + ESP_LOGI(HW_INIT_TAG, "Base board control initialized with I2C handle"); + } else { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize base board control: I2C bus is null"); + } + + // 6. 可选外设(根据具体应用需求) + // BaseBoardControlUartInit(); // 底座控制串口(demo5需要) + // ESP_LOGI(HW_INIT_TAG, "Base board control UART initialized"); + + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + StepperMotorInit(); + + + + ESP_LOGI(HW_INIT_TAG, "Unified hardware initialization completed successfully"); + return ESP_OK; +} + +/** + * @brief 进入低功耗模式 + * + * 根据指定的功耗模式,执行相应的功耗优化策略: + * - IDLE模式:关闭非必要外设,保持快速响应 + * - LIGHT_SLEEP模式:进入轻度睡眠,CPU暂停,外设断电 + * - DEEP_SLEEP模式:深度睡眠,仅RTC运行,功耗最低 + */ +esp_err_t BoardSweekar::EnterLowPowerMode(power_mode_t mode) { + static const char *POWER_TAG = "POWER_MGMT"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "Entering power mode: %d", mode); + + if (mode == current_power_mode_) { + ESP_LOGW(POWER_TAG, "Already in power mode %d", mode); + return ESP_OK; + } + + // 保存当前模式,以便恢复 + power_mode_t previous_mode = current_power_mode_; + + switch (mode) { + case POWER_MODE_NORMAL: + // 正常模式,不需要特殊处理 + break; + + case POWER_MODE_IDLE: + // 空闲模式:关闭非必要外设 + ESP_LOGI(POWER_TAG, "Idle mode: disabling non-essential peripherals"); + + // 关闭SD卡(如果正在使用,需要先卸载) + if (is_sdcard_initialized_) { + // 注意:这里只是示例,实际应用中需要确保没有文件操作 + ESP_LOGW(POWER_TAG, "SD card still active in idle mode"); + } + + // 关闭显示背光 + // 关闭非必要GPIO的上拉/下拉 + // 降低I2C时钟频率 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠 + ESP_LOGI(POWER_TAG, "Light sleep mode: preparing for sleep"); + + // 设置唤醒源(需要根据具体需求配置) + // esp_sleep_enable_timer_wakeup(1000000); // 1秒后唤醒 + // esp_sleep_enable_ext0_wakeup(GPIO_NUM_XX, 0); // 外部中断唤醒 + + // 进入轻度睡眠 + // ret = esp_light_sleep_start(); + if (ret != ESP_OK) { + ESP_LOGE(POWER_TAG, "Failed to enter light sleep: %s", esp_err_to_name(ret)); + current_power_mode_ = previous_mode; + return ret; + } + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠 + ESP_LOGI(POWER_TAG, "Deep sleep mode: system will restart after wake-up"); + + // 保存必要的RTC数据 + // rtc_gpio_isolate(GPIO_NUM_XX); // 隔离GPIO + + // 设置深度睡眠唤醒源 + // esp_sleep_enable_timer_wakeup(60000000); // 60秒后唤醒 + + // 进入深度睡眠 + // esp_deep_sleep_start(); + // 注意:深度睡眠后系统会重启,不会返回 + break; + + default: + ESP_LOGE(POWER_TAG, "Invalid power mode: %d", mode); + return ESP_ERR_INVALID_ARG; + } + + current_power_mode_ = mode; + ESP_LOGI(POWER_TAG, "Successfully entered power mode: %d", mode); + return ret; +} + +/** + * @brief 退出低功耗模式 + * + * 从低功耗模式恢复到正常运行模式,重新初始化必要的硬件资源。 + */ +esp_err_t BoardSweekar::ExitLowPowerMode(void) { + static const char *POWER_TAG = "POWER_MGMT"; + + ESP_LOGI(POWER_TAG, "Exiting low power mode"); + + if (current_power_mode_ == POWER_MODE_NORMAL) { + ESP_LOGW(POWER_TAG, "Already in normal mode"); + return ESP_OK; + } + + // 根据之前的功耗模式执行相应的恢复操作 + switch (current_power_mode_) { + case POWER_MODE_IDLE: + // 恢复空闲模式 + ESP_LOGI(POWER_TAG, "Restoring from idle mode"); + + // 重新初始化必要的外设 + // 恢复I2C时钟频率 + // 重新开启显示背光 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠唤醒后会自动继续执行 + ESP_LOGI(POWER_TAG, "Woken up from light sleep"); + + // 重新配置时钟(如果需要) + // 重新初始化外设 + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠后系统会重启,不会执行到这里 + ESP_LOGW(POWER_TAG, "Deep sleep recovery should not reach here"); + break; + + default: + break; + } + + // 恢复到正常模式 + current_power_mode_ = POWER_MODE_NORMAL; + ESP_LOGI(POWER_TAG, "Successfully exited low power mode"); + + return ESP_OK; +} + +/** + * @brief 设置硬件模块电源状态 + * + * 提供精细化的电源管理,可以单独控制各个硬件模块的电源状态。 + */ +esp_err_t BoardSweekar::SetModulePower(hardware_module_t module, bool enable) { + static const char *POWER_TAG = "POWER_MODULE"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "%s module %d", enable ? "Enabling" : "Disabling", module); + + // 更新电源状态位图 + if (enable) { + power_state_flags_ |= (1 << module); + } else { + power_state_flags_ &= ~(1 << module); + } + + switch (module) { + case HW_MODULE_I2C: + if (!enable && bus_handle_) { + // 注意:I2C总线通常不建议完全关闭,可能影响多个设备 + ESP_LOGW(POWER_TAG, "I2C bus power control not recommended"); + } + break; + + case HW_MODULE_SDCARD: + if (enable && !is_sdcard_initialized_) { + ret = InitializeSdcard(); + } else if (!enable && is_sdcard_initialized_) { + // 卸载SD卡文件系统 + // 注意:需要确保没有打开的文件 + ESP_LOGW(POWER_TAG, "SD card power down requires file system cleanup"); + } + break; + + case HW_MODULE_LCD: + // LCD背光控制 + // LCD电源引脚控制(如果有) + break; + + case HW_MODULE_AUDIO: + // 音频编解码器电源控制 + // 需要与AudioPlayer协调 + break; + + case HW_MODULE_MOTOR: + // 步进电机驱动器电源控制 + // 电机睡眠模式 + break; + + case HW_MODULE_GYROSCOPE: + // 陀螺仪电源控制 + // 设置陀螺仪为低功耗模式 + break; + + case HW_MODULE_TOUCH: + // 触摸传感器电源控制 + // 设置触摸传感器为睡眠模式 + break; + + case HW_MODULE_RADAR: + // 毫米波雷达电源控制 + // 设置雷达为低功耗模式 + break; + + case HW_MODULE_IO_EXPAND: + // IO扩展板电源控制 + // 设置扩展板为低功耗模式 + break; + + default: + ESP_LOGE(POWER_TAG, "Unknown hardware module: %d", module); + return ESP_ERR_INVALID_ARG; + } + + if (ret == ESP_OK) { + ESP_LOGI(POWER_TAG, "Module %d power %s successfully", module, enable ? "enabled" : "disabled"); + } else { + ESP_LOGE(POWER_TAG, "Failed to %s module %d: %s", enable ? "enable" : "disable", module, esp_err_to_name(ret)); + } + + return ret; +} + + + diff --git a/main/02_physical/boards/board_v1_1_17/board_sweekar.h b/main/02_physical/boards/board_v1_1_17/board_sweekar.h new file mode 100644 index 0000000..55665ac --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/board_sweekar.h @@ -0,0 +1,181 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "stepper_driver.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" +#include "base_board_control.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" + +/** + * @brief 功耗模式枚举 + */ +typedef enum { + POWER_MODE_NORMAL, // 正常运行模式,所有硬件可用 + POWER_MODE_IDLE, // 空闲模式,关闭非必要外设 + POWER_MODE_LIGHT_SLEEP, // 轻度睡眠,快速唤醒 + POWER_MODE_DEEP_SLEEP, // 深度睡眠,仅RTC运行 +} power_mode_t; + +/** + * @brief 硬件模块枚举,用于精细化电源管理 + */ +typedef enum { + HW_MODULE_I2C, // I2C总线 + HW_MODULE_SDCARD, // SD卡 + HW_MODULE_LCD, // LCD显示 + HW_MODULE_AUDIO, // 音频编解码器 + HW_MODULE_MOTOR, // 步进电机 + HW_MODULE_GYROSCOPE, // 陀螺仪 + HW_MODULE_TOUCH, // 触摸传感器 + HW_MODULE_RADAR, // 毫米波雷达 + HW_MODULE_IO_EXPAND, // IO扩展板 +} hardware_module_t; + +/** + * @brief 硬件板级管理器 + * + * 采用高内聚低耦合设计原则,统一管理所有硬件资源。 + * 提供统一的硬件初始化接口和资源访问,避免各模块重复初始化。 + */ +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + esp_periph_set_handle_t periph_set_ = nullptr; // 外设管理句柄 + bool is_sdcard_initialized_ = false; // SD卡初始化状态 + power_mode_t current_power_mode_ = POWER_MODE_NORMAL; // 当前功耗模式 + uint32_t power_state_flags_ = 0xFFFFFFFF; // 各模块电源状态位图(1为开启) + +public: + BoardSweekar(); + ~BoardSweekar(); + + static BoardSweekar* GetInstance(); + + // --- 语义化的高层 API:音频与功放控制 --- + // 开启或关闭外放功放 (PA),供上层 AudioActor/AudioPlayer 调用,屏蔽底层引脚实现 + void SetAudioAmplifier(bool enable); + + IOExpansionBoard io_expansion_; // IO扩展板对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + BaseBoardControl base_board_control_; // 底座控制对象 + StepperDriver* stepper_driver_ = nullptr; // 步进电机驱动对象 + + StepperDriver* GetStepperDriver() const { return stepper_driver_; } + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化LCD IO引脚(消除与步进电机的耦合) + void LcdIoInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + /** + * @brief 执行系统关机 + * 内部拉高/拉低 SHUTDOWN_CTRL_PIN 引脚切断电源 + */ + void SystemShutdown(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 初始化底座控制串口 + void BaseBoardControlInit(void); + + // 初始化SD卡(推荐使用统一的硬件初始化) + esp_err_t InitializeSdcard(void); + + // 获取SD卡初始化状态 + bool IsSdcardInitialized() const { return is_sdcard_initialized_; } + + // 获取外设管理句柄(供需要访问SD卡的模块使用) + esp_periph_set_handle_t GetPeriphSet() const { return periph_set_; } + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } + + /** + * @brief 统一的硬件初始化接口 + * + * 按照依赖关系顺序初始化所有硬件资源,确保各模块可以安全使用。 + * 推荐在主程序启动时调用此函数,而不是分别调用各个初始化函数。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardware(void); + + /** + * @brief 完整的统一硬件初始化接口(专为复杂应用优化) + * + * 包含所有必要外设的完整初始化,适用于demo5等复杂应用场景。 + * 这个函数会初始化:I2C总线、IO扩展板、SD卡、LCD IO、底座控制等。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardwareComplete(void); + + /** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置,优化启动流程。 + * + * @param app_type 应用类型(demo5、音频播放器、显示应用等) + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeHardwareForApplication(int app_type); + + /** + * @brief 功耗管理接口 - 进入低功耗模式 + * + * 根据指定的功耗模式,关闭非必要硬件、设置GPIO为低功耗状态、 + * 调整时钟频率等。支持多种功耗模式以适应不同应用场景。 + * + * @param mode 功耗模式 + * @return esp_err_t 操作结果 + */ + esp_err_t EnterLowPowerMode(power_mode_t mode); + + /** + * @brief 功耗管理接口 - 退出低功耗模式 + * + * 从低功耗模式恢复,重新初始化必要的硬件资源。 + * + * @return esp_err_t 操作结果 + */ + esp_err_t ExitLowPowerMode(void); + + /** + * @brief 获取当前功耗模式 + * + * @return power_mode_t 当前功耗模式 + */ + power_mode_t GetPowerMode(void) const { return current_power_mode_; } + + /** + * @brief 设置硬件模块电源状态 + * + * 单独控制某个硬件模块的电源状态,用于精细化的功耗管理。 + * + * @param module 硬件模块 + * @param enable true为开启,false为关闭 + * @return esp_err_t 操作结果 + */ + esp_err_t SetModulePower(hardware_module_t module, bool enable); +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_17/board_sweekar_complete_init.cc b/main/02_physical/boards/board_v1_1_17/board_sweekar_complete_init.cc new file mode 100644 index 0000000..37a454a --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/board_sweekar_complete_init.cc @@ -0,0 +1,186 @@ +/** + * @file board_sweekar_complete_init.cc + * @brief 完整的统一硬件初始化实现 + * + * 这个文件提供了完整的统一硬件初始化函数,包含所有必要的硬件初始化步骤。 + * 专为demo5等复杂应用场景设计。 + */ + +/** + * @brief 完整的统一硬件初始化接口(专为demo5优化) + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备依赖) + * 2. IO扩展板(控制其他外设) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. LCD IO引脚(显示相关) + * 5. 底座控制串口(通信) + * 6. 底座控制模块(业务逻辑) + * 7. 其他外设(根据应用需求) + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardwareComplete(void) +{ + static const char *HW_INIT_TAG = "HW_INIT_COMPLETE"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting complete hardware initialization..."); + + // 1. 初始化I2C总线(最基础,其他设备依赖) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "✓ I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "✓ I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "⚠ SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "✓ SD card initialized"); + } + + // 4. 初始化LCD IO引脚(显示相关) + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "✓ LCD IO pins initialized"); + + // 5. 初始化底座控制串口(demo5需要) + BaseBoardControlUartInit(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control UART initialized"); + + // 6. 初始化底座控制模块 + base_board_control_.Init(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control initialized"); + + // 7. 设置IO扩展板的PA控制模式(demo5需要) + io_expansion_.SetPaCtrlMode(true); + ESP_LOGI(HW_INIT_TAG, "✓ IO expansion PA control mode set"); + + // 8. 可选外设(根据具体应用需求启用) + // ESP_LOGI(HW_INIT_TAG, "Initializing optional peripherals..."); + // + // // 毫米波雷达(如需要) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Millimeter wave radar initialized"); + // + // // VBAT升压(如需要) + // VbatToVbat5vInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ VBAT to 5V booster initialized"); + // + // // 步进电机(如需要) + // StepperMotorInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Stepper motor initialized"); + + ESP_LOGI(HW_INIT_TAG, "=== Complete hardware initialization finished successfully ==="); + return ESP_OK; +} + +/** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置 + */ +esp_err_t BoardSweekar::InitializeHardwareForApplication(application_type_t app_type) { + static const char *APP_INIT_TAG = "APP_HW_INIT"; + + ESP_LOGI(APP_INIT_TAG, "Initializing hardware for application type: %d", app_type); + + // 基础硬件初始化(所有应用都需要) + esp_err_t ret = InitializeAllHardwareComplete(); + if (ret != ESP_OK) { + ESP_LOGE(APP_INIT_TAG, "Basic hardware initialization failed"); + return ret; + } + + switch (app_type) { + case APP_TYPE_DEMO5: // 宠物游戏demo + ESP_LOGI(APP_INIT_TAG, "Configuring for Demo5 (Pet Game)..."); + + // Demo5需要的额外初始化 + // 触摸传感器、陀螺仪、RGB LED等会在应用层初始化 + + ESP_LOGI(APP_INIT_TAG, "✓ Demo5 hardware configuration complete"); + break; + + case APP_TYPE_AUDIO_PLAYER: // 音频播放器 + ESP_LOGI(APP_INIT_TAG, "Configuring for Audio Player..."); + + // 音频应用需要确保音频编解码器正确初始化 + // 这部分通常在AudioPlayer类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Audio player hardware configuration complete"); + break; + + case APP_TYPE_DISPLAY_ONLY: // 纯显示应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Display Only..."); + + // 显示应用需要确保LCD相关硬件正确配置 + // 这部分通常在Display类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Display-only hardware configuration complete"); + break; + + case APP_TYPE_MINIMAL: // 最小化应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Minimal Application..."); + + // 最小化配置,只保留最基本的功能 + // 关闭非必要外设以节省功耗 + + ESP_LOGI(APP_INIT_TAG, "✓ Minimal hardware configuration complete"); + break; + + default: + ESP_LOGW(APP_INIT_TAG, "Unknown application type, using default configuration"); + break; + } + + return ESP_OK; +} + +/** + * @brief 硬件初始化状态报告 + * + * 提供详细的硬件初始化状态报告,便于调试和问题排查 + */ +void BoardSweekar::ReportHardwareStatus(void) { + static const char *STATUS_TAG = "HW_STATUS"; + + ESP_LOGI(STATUS_TAG, "=== Hardware Initialization Status Report ==="); + + // I2C总线状态 + ESP_LOGI(STATUS_TAG, "I2C Bus: %s", (bus_handle_ != nullptr) ? "✓ Initialized" : "✗ Not initialized"); + + // SD卡状态 + ESP_LOGI(STATUS_TAG, "SD Card: %s", (is_sdcard_initialized_) ? "✓ Initialized" : "✗ Not initialized"); + if (is_sdcard_initialized_) { + ESP_LOGI(STATUS_TAG, " Peripheral Set: %s", (periph_set_ != nullptr) ? "✓ Available" : "✗ Not available"); + } + + // IO扩展板状态 + ESP_LOGI(STATUS_TAG, "IO Expansion Board: %s", (io_expansion_.bus_handle_ != nullptr) ? "✓ Configured" : "✗ Not configured"); + + // 底座控制状态 + ESP_LOGI(STATUS_TAG, "Base Board Control: %s", (base_board_control_.GetUartPort() != 0) ? "✓ Initialized" : "✗ Not initialized"); + + // 功耗管理状态 + ESP_LOGI(STATUS_TAG, "Power Management: %s", (current_power_mode_ == POWER_MODE_NORMAL) ? "✓ Normal mode" : "⚠ Low power mode"); + ESP_LOGI(STATUS_TAG, " Current Mode: %d", current_power_mode_); + ESP_LOGI(STATUS_TAG, " Module Power States: 0x%08X", power_state_flags_); + + // 内存状态 + ESP_LOGI(STATUS_TAG, "Memory Status:"); + ESP_LOGI(STATUS_TAG, " Internal RAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI(STATUS_TAG, " PSRAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + ESP_LOGI(STATUS_TAG, "=== End of Hardware Status Report ==="); +} \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_17/config.h b/main/02_physical/boards/board_v1_1_17/config.h new file mode 100644 index 0000000..7af2bd4 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/config.h @@ -0,0 +1,136 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/gptimer.h" +#include "esp_intr_alloc.h" +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机配置 */ +#define STEPPER_STEP_IO GPIO_NUM_42 // 步进电机脉冲输入引脚 +#define STEPPER_TIMER_RES_HZ 1000000 // 步进电机定时器分辨率 1 MHz(1 us 步进) + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_45 // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO40 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO39 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x58 // AW9523BTQR器件地址,v1_1_13的addr1和addr0是0 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* AW触摸芯片配置 */ +#define AW_TOUCH_IRQ_PIN GPIO_NUM_11 // AW触摸芯片中断引脚 +#define NFC_RST_PIN GPIO_NUM_12 // NFC复位引脚 +#define SHUTDOWN_CTRL_PIN GPIO_NUM_13 // 关机控制引脚 + +/* 原生触摸引脚配置 (v1.1.17已改为上述功能,保留定义需谨慎) */ +// 触摸按键数量配置 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 - 注意:v1.1.17如果不使用ESP32原生触摸,这些定义可能不再适用 +// 但为了兼容编译,可以保留或注释 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 (Now AW_IRQ) +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 (Now NFC_RST) +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 (Now SHUTDOWN) + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +/* 底座控制命令串口配置 */ +#define BASE_BOARD_CTL_UART_PORT UART_NUM_0 +#define BASE_BOARD_CTL_UART_TX_PIN GPIO_NUM_43 +#define BASE_BOARD_CTL_UART_RX_PIN GPIO_NUM_44 +#define BASE_BOARD_CTL_UART_BAUDRATE 115200 +#define BASE_BOARD_CTL_UART_BUF_SIZE 256 + +/* 背光控制引脚配置 */ +#define BL_PIN GPIO_NUM_45 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/02_physical/boards/board_v1_1_17/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_17/io_expansion_board.cc new file mode 100644 index 0000000..bfe1679 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/io_expansion_board.cc @@ -0,0 +1,1040 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "photo_interrupter.h" +#include "adc_battery_monitor.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + last_port0_state_(0), + last_port1_state_(0), + radar_last_state_(false), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr), + is_initialized_(false), + interrupt_gpio_(GPIO_NUM_NC), + reset_gpio_(GPIO_NUM_NC) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + + // 调试:记录中断触发 + // ESP_EARLY_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发!"); // IRAM函数中不能使用普通日志 + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_ANYEDGE; // 改为双沿触发,检测上升沿和下降沿 + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + // io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + // io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 计算Port0的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort0() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT0){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT0){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT0){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT0){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT0){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 计算Port1的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort1() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT1){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT1){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT1){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT1){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT1){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +/** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort0(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT0, data); +} + +/** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort1(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT1, data); +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + + err = IoexpandWriteReg(CONFIG_PORT0, ByteCalculatePort0()); + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort0()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功, 0x%02X", ByteCalculatePort0()); + } + + err = IoexpandWriteReg(CONFIG_PORT1, ByteCalculatePort1()); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort1()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功, 0x%02X", ByteCalculatePort1()); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + // P0_6(旧的LCD_BL, 0x40), P0_1(led_en, PMOS高侧驱动, 高电平关, 0x02) + err = IoexpandWriteReg(OUTPUT_PORT0, 0x42); // 0100 0010 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x04); // 0000 0100 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + + // AW9523B中断使能配置:0为使能中断,1为禁止中断 + // 使能P0_5(充电检测)和P1_3(充满检测)的中断 + uint8_t int_enable_port0 = 0x00; // 0x00 = 使能所有P0引脚中断 + uint8_t int_enable_port1 = 0x00; // 0x00 = 使能所有P1引脚中断 + + err = IoexpandWriteReg(INT_PORT0, int_enable_port0); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能成功: 0x%02X (0=使能)", int_enable_port0); + } + + err = IoexpandWriteReg(INT_PORT1, int_enable_port1); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功: 0x%02X (0=使能)", int_enable_port1); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << enable_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入睡眠状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LED_EN 引脚电平(用于控制LCD背光/LED灯电源) + * @param mode true:开启(低电平), false:关闭(高电平) + */ +esp_err_t IOExpansionBoard::SetLedEnMode(bool mode) +{ + uint8_t current_val; + esp_err_t err = IoexpandReadReg(LED_EN_PORT, ¤t_val); + if (err != ESP_OK) return err; + + if(mode){ + // 开启 (PMOS低电平有效) + current_val &= ~(1 << LED_EN_PIN); + } + else{ + // 关闭 (PMOS高电平无效) + current_val |= (1 << LED_EN_PIN); + } + return IoexpandWriteReg(LED_EN_PORT, current_val); +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入复位模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val |= (1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val &= ~(1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val |= (1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val &= ~(1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 PA_CTRL引脚电平 + */ +esp_err_t IOExpansionBoard::SetPaCtrlMode(bool mode) +{ + uint8_t current_val; + if(mode){ + // 进入高电平状态 + ESP_LOGI("io_ex:", "read reg"); + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val |= (1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } + else{ + // 进入低电平状态 + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val &= ~(1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } +} + + + +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + + // 执行硬件复位 - 这是解决中断引脚持续拉低的关键 + err = instance->ResetIoExpand(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位成功"); + + // 检查复位后的中断引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "复位后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + } + + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 - 复位后必须重新配置寄存器! + err = instance->IoexpandSetMode(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + // 设置LCD屏幕复位引脚为低电平 + err = instance->SetLcdResetMode(LCD_RST_LOW, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + + uint8_t current_val_port0_ = 0,current_val_port1_ = 0,current_temp_ = 0; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板中断触发"); + + // 检查中断触发时的引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发时引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + // AW9523B中断处理说明(根据数据手册): + // 1. 当输入引脚状态变化时,AW9523B会拉低INT引脚(连接到ESP32的GPIO10) + // 2. 通过读取输入端口寄存器(00H, 01H)来清除对应端口的中断 + // 3. 无独立的中断状态寄存器,需要通过输入电平变化来判断中断源 + // 4. 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + + // 一次性读取两个端口的输入寄存器,避免重复读取和错误的OR操作 + instance->IoexpandReadReg(INPUT_PORT0, ¤t_val_port0_); + instance->IoexpandReadReg(INPUT_PORT1, ¤t_val_port1_); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "读取端口状态 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + + // 检查读取寄存器后中断引脚是否被清除(应该恢复高电平) + interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "读取寄存器后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + //判断是否有中断来自雷达引脚 + if((RADAR_INTERRUPT_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((RADAR_INTERRUPT_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚1\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到雷达引脚中断\n"); + // } + + //判断是否有中断来自初始光学传感器引脚 + if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到初始光学传感器引脚中断\n"); + // } + + //判断是否有中断来自结束光学传感器引脚 + if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + + //CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 + //FULL未充满为高电平 充满电为低电平 + //判断是否有中断来自电池充电引脚 + //根据配置:BATTERY_CHRG_INPUT = INPUT_PORT0, BATTERY_FULL_INPUT = INPUT_PORT1 + + // AW9523B没有独立的中断状态寄存器,需要通过比较前后状态变化来判断中断源 + // 由于读取输入端口会清除中断,我们需要先保存当前状态,然后与上次状态比较 + + //获取当前电池状态 + bool current_charging_state = !(current_val_port0_ & (1 << BATTERY_CHRG_PIN)); // CHRG低电平表示充电中 + bool current_full_state = !(current_val_port1_ & (1 << BATTERY_FULL_PIN)); // FULL低电平表示已充满 + + // 详细的调试信息 + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - CHRG_PIN: %d (bit %d), FULL_PIN: %d (bit %d)", + (current_val_port0_ & (1 << BATTERY_CHRG_PIN)) ? 1 : 0, BATTERY_CHRG_PIN, + (current_val_port1_ & (1 << BATTERY_FULL_PIN)) ? 1 : 0, BATTERY_FULL_PIN); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 充电状态: %s, 充满状态: %s", + current_charging_state ? "充电中" : "未充电", + current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 上次充电状态: %s, 上次充满状态: %s", + instance->battery_charging_last_state_ ? "充电中" : "未充电", + instance->battery_full_last_state_ ? "已充满" : "未充满"); + + //检查充电状态是否发生变化 + if (current_charging_state != instance->battery_charging_last_state_) { + instance->battery_charging_last_state_ = current_charging_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充电状态变化: %s", current_charging_state ? "充电中" : "未充电"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充电状态变化详情 - 从 %s 到 %s", + instance->battery_charging_last_state_ ? "未充电" : "充电中", + current_charging_state ? "充电中" : "未充电"); + + if (current_charging_state) { + // 进入充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到开始充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerChrgCallback(); + } + if (instance->battery_charging_callback_) { + instance->battery_charging_callback_(); + } + } else { + // 退出充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到停止充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerNotChrgCallback(); + } + if (instance->battery_not_charging_callback_) { + instance->battery_not_charging_callback_(); + } + } + } + + //检查充满状态是否发生变化 + if (current_full_state != instance->battery_full_last_state_) { + instance->battery_full_last_state_ = current_full_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充满状态变化: %s", current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充满状态变化详情 - 从 %s 到 %s", + instance->battery_full_last_state_ ? "未充满" : "已充满", + current_full_state ? "已充满" : "未充满"); + + if (current_full_state) { + // 进入充满状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到电池已充满"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerFullCallback(); + } + if (instance->battery_full_callback_) { + instance->battery_full_callback_(); + } + } + + } + + // 根据AW9523B数据手册:读取输入端口寄存器清除对应端口的中断 + // 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "中断处理完成,已读取输入端口寄存器清除中断标志"); + } +} + + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_17/io_expansion_board.h b/main/02_physical/boards/board_v1_1_17/io_expansion_board.h new file mode 100644 index 0000000..2697e39 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/io_expansion_board.h @@ -0,0 +1,432 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; +class PhotoInterrupter; +class AdcBatteryMonitor; + +// 回调函数类型定义 +typedef std::function RadarCallback; +typedef std::function PhotoInterrupterStartCallback; +typedef std::function PhotoInterrupterEndCallback; +typedef std::function BatteryChargingCallback; +typedef std::function BatteryFullCallback; +typedef std::function BatteryNotChargingCallback; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 +#define STEPPER_MOTOR_DIR_CONFIG CONFIG_PORT0 //步进电机方向引脚配置在P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M0_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M0_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_5 + +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_M1_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M1_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_4 + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_CONFIG CONFIG_PORT1 //步进电机睡眠引脚配置在P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_CONFIG CONFIG_PORT0 //电机使能引脚配置在P0_7 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_CONFIG CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口 +#define RADAR_INTERRUPT_INPUT INPUT_PORT1 //毫米波雷达中断引脚输入状态检测在INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +// 光学传感器引脚定义 +#define START_PHOTO_INTERRUPTER_PIN 2 // 光学传感器引脚(P0_2) +#define START_PHOTO_INTERRUPTER_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define START_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define START_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define START_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +#define END_PHOTO_INTERRUPTER_PIN 3 // 光学传感器引脚(P0_3) +#define END_PHOTO_INTERRUPTER_PORT_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define END_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define END_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define END_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +// /* 设置LCD的BL引脚 */ +// #define LCD_BL_PIN 6 //LCD背光引脚对应IO扩展板引脚P0_6 +// #define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +// #define LCD_BL_CONFIG CONFIG_PORT0 //LCD背光引脚配置在P0_6 +// #define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +// #define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LED_EN (LCD背光/LED控制)引脚, PMOS高侧驱动 */ +#define LED_EN_PIN 1 //LED_EN对应IO扩展板引脚P0_1 +#define LED_EN_PORT OUTPUT_PORT0 //挂载在输出端口OUTPUT_PORT0 +#define LED_EN_CONFIG CONFIG_PORT0 //配置在P0_1 +#define LED_EN_OFF true //高电平关(PMOS) +#define LED_EN_ON false //低电平开(PMOS) + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚对应IO扩展板引脚P1_1 +#define LCD_RST_CONFIG CONFIG_PORT1 //LCD复位引脚配置在P1_1 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置PA控制引脚 */ +#define PA_CTRL_PIN 2 //PA控制引脚对应IO扩展板引脚P1_2 +#define PA_CTRL_CONFIG CONFIG_PORT1 //PA控制引脚配置在P1_2 +#define PA_CTRL_PORT OUTPUT_PORT1 //PA控制引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define PA_CTRL_HIGH true //PA控制引脚状态为高电平 +#define PA_CTRL_LOW false //PA控制引脚状态为低电平 + +// 电池充电检测引脚定义 +#define BATTERY_FULL_INPUT INPUT_PORT1 // 电池充满检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_FULL_PIN 3 // 电池充满检测引脚对应IO扩展板引脚P1_3 +#define BATTERY_FULL_CONFIG CONFIG_PORT1 // 电池充满检测引脚配置在P1_3 + +#define BATTERY_CHRG_INPUT INPUT_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_CHRG_PIN 5 // 充电检测引脚(P0_5) +#define BATTERY_CHRG_CONFIG CONFIG_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +// #define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 设置限位开关指针 + * @param photo_interrupter 限位开关对象指针 + */ + void setPhotoInterrupter(PhotoInterrupter* photo_interrupter) { photo_interrupter_ = photo_interrupter; }; + + /** + * @brief 设置电池监控器指针 + * @param battery_monitor 电池监控器对象指针 + */ + void setBatteryMonitor(AdcBatteryMonitor* battery_monitor) { battery_monitor_ = battery_monitor; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + /** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort0(uint8_t *data); + + /** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort1(uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置 LED_EN 引脚电平(用于控制LCD背光/LED灯电源) + * @param mode true:开启(低电平), false:关闭(高电平) + */ + esp_err_t SetLedEnMode(bool mode); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置PA控制引脚状态 + * @param mode PA_CTRL状态(true为高电平,false为低电平) + * @return esp_err_t 操作结果 + */ + esp_err_t SetPaCtrlMode(bool mode); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + /** + * @brief 计算Port0的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort0(); + + /** + * @brief 计算Port1的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort1(); + + /** + * @brief 设置雷达中断回调函数 + * @param callback 雷达中断回调函数 + */ + void SetRadarCallback(RadarCallback callback) { radar_callback_ = callback; } + + /** + * @brief 设置光学传感器开始中断回调函数 + * @param callback 光学传感器开始中断回调函数 + */ + void SetPhotoInterrupterStartCallback(PhotoInterrupterStartCallback callback) { photo_start_callback_ = callback; } + + /** + * @brief 设置光学传感器结束中断回调函数 + * @param callback 光学传感器结束中断回调函数 + */ + void SetPhotoInterrupterEndCallback(PhotoInterrupterEndCallback callback) { photo_end_callback_ = callback; } + + /** + * @brief 设置电池充电中断回调函数 + * @param callback 电池充电中断回调函数 + */ + void SetBatteryChargingCallback(BatteryChargingCallback callback) { battery_charging_callback_ = callback; } + + /** + * @brief 设置电池充满中断回调函数 + * @param callback 电池充满中断回调函数 + */ + void SetBatteryFullCallback(BatteryFullCallback callback) { battery_full_callback_ = callback; } + + /** + * @brief 设置电池非充电中断回调函数 + * @param callback 电池非充电中断回调函数 + */ + void SetBatteryNotChargingCallback(BatteryNotChargingCallback callback) { battery_not_charging_callback_ = callback; } + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + PhotoInterrupter* photo_interrupter_ = nullptr; // 限位开关指针,用于触发回调 + AdcBatteryMonitor* battery_monitor_ = nullptr; // 电池监控器指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // 成员变量用于状态跟踪 + uint8_t last_port0_state_ = 0; // 上次检测到的Port0状态 + uint8_t last_port1_state_ = 0; // 上次检测到的Port1状态 + bool radar_last_state_ = false; // 雷达引脚上一次的状态 + bool battery_charging_last_state_ = false; // 电池充电状态上一次的状态 + bool battery_full_last_state_ = false; // 电池充满状态上一次的状态 + + // 回调函数成员变量 + RadarCallback radar_callback_ = nullptr; + PhotoInterrupterStartCallback photo_start_callback_ = nullptr; + PhotoInterrupterEndCallback photo_end_callback_ = nullptr; + BatteryChargingCallback battery_charging_callback_ = nullptr; + BatteryFullCallback battery_full_callback_ = nullptr; + BatteryNotChargingCallback battery_not_charging_callback_ = nullptr; + + // PhotoInterrupter* photo_interrupter_ = nullptr; // 光学传感器指针,用于触发回调 + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_17/power_management_config.h b/main/02_physical/boards/board_v1_1_17/power_management_config.h new file mode 100644 index 0000000..da609fb --- /dev/null +++ b/main/02_physical/boards/board_v1_1_17/power_management_config.h @@ -0,0 +1,178 @@ +/** + * @file power_management_config.h + * @brief 功耗管理配置和策略定义 + * @author Embedded Software Expert + * @date 2025年1月 + * + * 这个文件定义了功耗管理的配置参数、策略和优化建议, + * 为系统提供灵活的功耗管理方案。 + */ + +#ifndef POWER_MANAGEMENT_CONFIG_H +#define POWER_MANAGEMENT_CONFIG_H + +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp32/rom/rtc.h" + +/** + * @brief 功耗管理配置结构体 + */ +typedef struct { + // 睡眠模式配置 + uint64_t light_sleep_duration_us; // 轻度睡眠持续时间(微秒) + uint64_t deep_sleep_duration_us; // 深度睡眠持续时间(微秒) + + // 唤醒源配置 + bool enable_timer_wakeup; // 启用定时器唤醒 + bool enable_gpio_wakeup; // 启用GPIO唤醒 + bool enable_touch_wakeup; // 启用触摸唤醒 + gpio_num_t wakeup_gpio_num; // 唤醒GPIO编号 + int wakeup_level; // 唤醒电平(0或1) + + // 功耗优化配置 + bool disable_bt_in_sleep; // 睡眠时关闭蓝牙 + bool disable_wifi_in_sleep; // 睡眠时关闭WiFi + bool reduce_cpu_frequency; // 降低CPU频率 + uint32_t cpu_frequency_hz; // CPU频率(Hz) + + // 外设功耗配置 + bool power_down_sdcard; // 睡眠时关闭SD卡电源 + bool power_down_display; // 睡眠时关闭显示电源 + bool power_down_audio; // 睡眠时关闭音频电源 + bool power_down_motor; // 睡眠时关闭电机电源 + + // RTC内存配置 + bool preserve_rtc_memory; // 保留RTC内存 + size_t rtc_memory_size; // RTC内存大小 +} power_management_config_t; + +/** + * @brief 默认功耗管理配置 + */ +#define DEFAULT_POWER_CONFIG { \ + .light_sleep_duration_us = 1000000, /* 1秒 */ \ + .deep_sleep_duration_us = 30000000, /* 30秒 */ \ + .enable_timer_wakeup = true, /* 启用定时器唤醒 */ \ + .enable_gpio_wakeup = false, /* 不启用GPIO唤醒 */ \ + .enable_touch_wakeup = false, /* 不启用触摸唤醒 */ \ + .wakeup_gpio_num = GPIO_NUM_NC, /* 无GPIO唤醒 */ \ + .wakeup_level = 0, /* 低电平唤醒 */ \ + .disable_bt_in_sleep = true, /* 睡眠时关闭蓝牙 */ \ + .disable_wifi_in_sleep = true, /* 睡眠时关闭WiFi */ \ + .reduce_cpu_frequency = true, /* 降低CPU频率 */ \ + .cpu_frequency_hz = 80000000, /* 80MHz */ \ + .power_down_sdcard = true, /* 睡眠时关闭SD卡 */ \ + .power_down_display = true, /* 睡眠时关闭显示 */ \ + .power_down_audio = true, /* 睡眠时关闭音频 */ \ + .power_down_motor = true, /* 睡眠时关闭电机 */ \ + .preserve_rtc_memory = true, /* 保留RTC内存 */ \ + .rtc_memory_size = 4096, /* 4KB RTC内存 */ \ +} + +/** + * @brief 各功耗模式的详细配置 + */ +static const power_management_config_t idle_mode_config = { + .light_sleep_duration_us = 500000, // 500ms轻度睡眠 + .deep_sleep_duration_us = 0, // 不使用深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, // 按键唤醒 + .wakeup_level = 0, + .disable_bt_in_sleep = false, // 保持蓝牙连接 + .disable_wifi_in_sleep = false, // 保持WiFi连接 + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, // 40MHz + .power_down_sdcard = false, // 保持SD卡(可能有后台任务) + .power_down_display = true, // 关闭显示背光 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 2048, +}; + +static const power_management_config_t light_sleep_config = { + .light_sleep_duration_us = 0, // 直接进入轻度睡眠 + .deep_sleep_duration_us = 0, + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 4096, +}; + +static const power_management_config_t deep_sleep_config = { + .light_sleep_duration_us = 0, + .deep_sleep_duration_us = 60000000, // 60秒深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 10000000, // 10MHz + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 8192, +}; + +/** + * @brief 功耗统计和监控结构体 + */ +typedef struct { + uint64_t total_sleep_time_us; // 总睡眠时间 + uint64_t total_wake_time_us; // 总唤醒时间 + uint32_t sleep_count; // 睡眠次数 + uint32_t wake_count; // 唤醒次数 + uint32_t average_sleep_duration_us; // 平均睡眠时长 + uint32_t average_wake_duration_us; // 平均唤醒时长 + float estimated_power_saving_percent; // 估计的功耗节省百分比 +} power_statistics_t; + +/** + * @brief 功耗优化建议 + * + * 基于硬件特性和使用模式,提供功耗优化建议 + */ +typedef enum { + OPTIMIZATION_NONE = 0, + OPTIMIZATION_REDUCE_CPU_FREQ, // 降低CPU频率 + OPTIMIZATION_DISABLE_UNUSED_PERIPHERALS, // 关闭未使用的外设 + OPTIMIZATION_USE_LIGHT_SLEEP, // 使用轻度睡眠 + OPTIMIZATION_USE_DEEP_SLEEP, // 使用深度睡眠 + OPTIMIZATION_OPTIMIZE_GPIO_STATES, // 优化GPIO状态 + OPTIMIZATION_USE_RTC_MEMORY, // 使用RTC内存 + OPTIMIZATION_BATCH_OPERATIONS, // 批量操作 +} power_optimization_t; + +/** + * @brief 功耗管理事件回调 + */ +typedef void (*power_event_callback_t)(power_mode_t old_mode, power_mode_t new_mode, void* user_data); + +/** + * @brief 功耗管理事件 + */ +typedef enum { + POWER_EVENT_MODE_CHANGE, // 功耗模式改变 + POWER_EVENT_SLEEP_START, // 睡眠开始 + POWER_EVENT_SLEEP_END, // 睡眠结束 + POWER_EVENT_WAKEUP, // 系统唤醒 + POWER_EVENT_OPTIMIZATION_TRIGGER, // 功耗优化触发 +} power_event_t; + +#endif // POWER_MANAGEMENT_CONFIG_H \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_18/board_sweekar.cc b/main/02_physical/boards/board_v1_1_18/board_sweekar.cc new file mode 100644 index 0000000..8369f11 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/board_sweekar.cc @@ -0,0 +1,778 @@ +/** + * @file board.cc + * @brief 板级外设初始化类,包含步进电机、毫米波雷达、I2C总线等外设的初始化函数。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +extern "C" { +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/uart.h" +#include "driver/ledc.h" +#include "driver/gptimer.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/gpio_types.h" +#include "hal/uart_types.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2c_bus.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +} + +#include "board_sweekar.h" +#include "config.h" +#include "board.h" // 添加board.h以获取audio_board_sdcard_init函数声明 +#include "drv8428.h" +#include "aw8646.h" +#include "esp_vfs_fat.h" +#include "audio_mem.h" +#include "esp_timer.h" +#include "base_board_hal_i2c.h" + +#define TAG "board_sweekar" + +static BoardSweekar* s_board_instance = nullptr; + +BoardSweekar* BoardSweekar::GetInstance() +{ + if (s_board_instance == nullptr) { + static BoardSweekar global_board; + s_board_instance = &global_board; + } + return s_board_instance; +} + +BoardSweekar::BoardSweekar() +{ +#ifdef CONFIG_STEPPER_DRIVER_AW8646 + stepper_driver_ = new Aw8646Driver(io_expansion_); +#else + stepper_driver_ = new Drv8428Driver(io_expansion_); +#endif +} + +BoardSweekar::~BoardSweekar() +{ + if (stepper_driver_) { + delete stepper_driver_; + stepper_driver_ = nullptr; + } +} + +void BoardSweekar::SetAudioAmplifier(bool enable) { + // AUD_CTRL 是 codec 侧控制脚,不参与 PA 开关;PA 仍由 IO 扩展板控制。 + io_expansion_.SetPaCtrlMode(enable); + ESP_LOGI(TAG, "Audio Amplifier (via IO Expansion) %s", enable ? "ENABLED" : "DISABLED"); +} + +/*========================== 步进电机相关驱动 ==========================*/ + +/** + * @brief 步进电机初始化函数 + * 配置步进电机所需的GPIO引脚模式,通过IO扩展板设置步进驱动芯片的工作模式 + * 在 stepper_motor.cc 中完成 GPTimer + GPIO 翻转 STEP 脉冲的初始化 + */ +void BoardSweekar::StepperMotorInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) STEP 引脚改为普通 GPIO(不再用 LEDC) + gpio_config_t io_cfg; + io_cfg.pin_bit_mask = 1ULL << STEPPER_STEP_IO; + io_cfg.mode = GPIO_MODE_OUTPUT; + io_cfg.pull_up_en = GPIO_PULLUP_DISABLE; + io_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_cfg.intr_type = GPIO_INTR_DISABLE; + err = gpio_config(&io_cfg); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "步进电机 STEP GPIO 配置失败, 错误码: 0x%x", err); + } + + // 初始电平拉低,保证上电不乱跳 + gpio_set_level(STEPPER_STEP_IO, 0); + + // 2) 检查 I2C 总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 3) 初始化驱动对象并初始化 + if (stepper_driver_) { + err = stepper_driver_->Init(); + if (err != ESP_OK) { + ESP_LOGE(TAG_BOARD, "步进电机驱动初始化失败, 错误码: 0x%x", err); + } + } else { + ESP_LOGE(TAG_BOARD, "步进电机驱动创建失败"); + } +} + +/** + * @brief LCD IO引脚初始化函数(消除与步进电机的耦合) + * + * 配置LCD所需的IO扩展板寄存器,确保LCD复位引脚正常工作 + * 此函数独立于步进电机初始化,专门处理LCD相关的IO配置 + */ +void BoardSweekar::LcdIoInit(void) +{ + esp_err_t err = ESP_OK; + + // 1) 检查I2C总线是否已初始化 + if (bus_handle_ == nullptr) + { + ESP_LOGE(TAG_BOARD, "I2C总线未初始化,请先调用I2CMasterInit()"); + return; + } + + // 2) 执行硬件复位,确保IO扩展板处于已知状态 + err = io_expansion_.ResetIoExpand(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板硬件复位失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板硬件复位成功"); + } + + // 3) 配置IO扩展板模式(端口方向等) + err = io_expansion_.IoexpandSetMode(); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "IO扩展板模式配置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "IO扩展板模式配置成功"); + } + + // 4) 初始化LCD复位引脚:设置为高电平 + err = io_expansion_.SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "LCD复位引脚高电平设置失败, 错误码: 0x%x", err); + return; + } + else + { + ESP_LOGI(TAG_BOARD, "LCD复位引脚高电平设置成功"); + } + + // 5) 初始化LED_EN电源为关闭状态 (PMOS高电平关) + // 只有在需要亮灯时,由ActuatorActor主动开启,避免平静状态下常亮 + err = io_expansion_.SetLedEnMode(false); + if (err != ESP_OK) + { + ESP_LOGE(TAG_BOARD, "初始化LED_EN电源(关闭)失败, 错误码: 0x%x", err); + } + else + { + ESP_LOGI(TAG_BOARD, "初始化LED_EN电源(关闭)成功"); + } +} + +/** + * @brief 执行系统关机 + * 内部拉高 SHUTDOWN_CTRL_PIN 引脚切断电源 + */ +void BoardSweekar::SystemShutdown(void) +{ +#ifdef SHUTDOWN_CTRL_PIN + ESP_LOGI(TAG, "Executing System Shutdown... Pulling SHUTDOWN_CTRL_PIN (%d) HIGH", SHUTDOWN_CTRL_PIN); + // 用户指定:拉高可以完成关机动作 + gpio_set_level(SHUTDOWN_CTRL_PIN, 1); + + // 给一点时间让电源完全断开 + vTaskDelay(pdMS_TO_TICKS(1000)); +#else + ESP_LOGW(TAG, "SHUTDOWN_CTRL_PIN is not defined. Cannot execute hardware shutdown."); +#endif +} + +/*========================== 毫米波雷达相关驱动 ==========================*/ + +/** + * @brief 毫米波雷达串口初始化函数 + * + * 配置毫米波雷达所使用的串口参数(波特率、数据位、校验位、停止位等),设置串口引脚, + * 并安装串口驱动。采用较大的RX/TX FIFO深度以处理突发数据,禁用驱动自带事件队列以避免 + * 与自定义中断冲突。 + */ +void BoardSweekar::MillimeterWaveRadarUartInit(void) +{ + uart_config_t uart_config = { + .baud_rate = RADAR_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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(RADAR_UART_NUM, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(RADAR_UART_NUM, UART_TX_PIN, UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动(禁用驱动自带事件队列,避免与自定义中断冲突) + // 增加 RX/TX FIFO 深度以处理突发数据 + ESP_ERROR_CHECK(uart_driver_install(RADAR_UART_NUM, RADAR_BUF_SIZE * 4, RADAR_BUF_SIZE * 4, + 0, NULL, 0)); +} + +/** + * @brief 毫米波雷达中断引脚初始化函数 + * + * 配置毫米波雷达中断引脚为输入模式,禁用上拉电阻,使能下拉电阻,初始禁用中断功能 + * (如需使用中断需单独进行配置)。 + */ +void BoardSweekar::MillimeterWaveRadarGpioInit(void) +{ + // GPIO配置结构体 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << MILLIMETER_WAVE_RADAR_PIN), // 选择GPIO引脚 + .mode = GPIO_MODE_INPUT, // 设置为输入模式 + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, // 使能下拉 + .intr_type = GPIO_INTR_DISABLE // 禁用中断(如需中断需单独配置) + }; + + // 配置GPIO + gpio_config(&io_conf); +} + +/*========================== I2C总线及挂载的从设备相关驱动 ==========================*/ + +/** + * @brief I2C总线及从机设备初始化函数 + * + * 首先配置并创建I2C主机总线,然后依次添加IO扩展板和线性马达作为I2C从机设备到总线上。 + * 若总线创建或设备添加失败,会输出错误信息并进行相应的资源清理(删除已创建的总线)。 + * 初始化成功后,会通过ESP_LOGI输出IO扩展板和线性马达的从机地址信息。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示相应的失败原因 + */ +esp_err_t BoardSweekar::I2CMasterInit(void) +{ + static const char *I2C_INIT = "I2C_INIT"; + + // 初始化IO扩展板复位引脚 + gpio_config_t reset_io_conf = { + .pin_bit_mask = (1ULL << IO_EXPAND_RSTN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉 + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&reset_io_conf); + + // 执行复位时序:先拉低100ms,再拉高并等待100ms + gpio_set_level(IO_EXPAND_RSTN, 0); // 拉低复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms + gpio_set_level(IO_EXPAND_RSTN, 1); // 释放复位 + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + + ESP_LOGI(I2C_INIT, "IO扩展板复位完成"); + + // 创建统一的I2C总线(使用ESP-ADF的i2c_bus) + i2c_config_t i2c_cfg = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C0_MASTER_SDA_IO, + .scl_io_num = I2C0_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = 100000, + }, + }; + + // 创建I2C总线(ESP-ADF的audio_board_init会复用这个总线) + bus_handle_ = i2c_bus_create(I2C0_MASTER_PORT, &i2c_cfg); + if (bus_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create I2C bus"); + return ESP_FAIL; + } + + vTaskDelay(pdMS_TO_TICKS(100)); // 等待设备启动 + // ESP-ADF的i2c_bus不需要单独添加设备,直接通过总线句柄和设备地址进行通信 + + // 验证线性马达设备是否存在 + esp_err_t err = i2c_bus_probe_addr(bus_handle_, LINEAR_MOTOR_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "线性马达设备未找到(地址:0x%02X),错误码: 0x%x", LINEAR_MOTOR_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "线性马达设备检测成功(地址:0x%02X)", LINEAR_MOTOR_I2C_SLAVE_ADDR); + } + // 验证IO扩展板设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, I2C_IO_EXPAND_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "IO扩展板设备未找到(地址:0x%02X),错误码: 0x%x", I2C_IO_EXPAND_SLAVE_ADDR, err); + // 不返回错误,继续初始化其他设备 + } else { + ESP_LOGI(I2C_INIT, "IO扩展板设备检测成功(地址:0x%02X)", I2C_IO_EXPAND_SLAVE_ADDR); + } + // 验证陀螺仪设备是否存在 + err = i2c_bus_probe_addr(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1); + if (err != ESP_OK) { + ESP_LOGE(I2C_INIT, "陀螺仪设备未找到(地址:0x%02X),错误码: 0x%x", GYROSCOPE_I2C_SLAVE_ADDR, err); + // 不返回错误,继续初始化 + } else { + ESP_LOGI(I2C_INIT, "陀螺仪设备检测成功(地址:0x%02X)", GYROSCOPE_I2C_SLAVE_ADDR); + } + + // 为IO扩展板设置总线句柄和设备地址(使用7位地址) + io_expansion_.bus_handle_ = bus_handle_; + io_expansion_.device_addr_ = I2C_IO_EXPAND_SLAVE_ADDR << 1; + + // 线性马达的初始化已移至 InitializeAllHardware 中统一调用 Init 方法 + // linear_motor_ 的参数传递将在那里完成,不再此处直接赋值,提高内聚性 + + ESP_LOGI(I2C_INIT, "I2C总线初始化完成"); + return ESP_OK; +} + +/*========================== VBAT to VBAT_5V相关驱动 ==========================*/ + +/** + * @brief 开启VBAT to VBAT_5V + * + * 创建开启升压的电源管理单元 + * + */ +void BoardSweekar::VbatToVbat5vInit(void) +{ + // 配置VBAT_5V引脚为输出模式 + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << VBAT_5V_PIN), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&io_conf); + // 使能VBAT_5V + gpio_set_level(VBAT_5V_PIN, 1); +} + +/*========================== 底座控制串口相关驱动 ==========================*/ + +/** + * @brief 底座控制命令串口初始化函数 + */ +// 根据配置选择通信方式的HAL头文件 +#ifdef CONFIG_BASE_BOARD_COMM_I2C +#include "base_board_hal_i2c.h" +#else +#include "base_board_hal_uart.h" +#endif + +void BoardSweekar::BaseBoardControlInit(void) +{ +#ifdef CONFIG_BASE_BOARD_COMM_I2C + // I2C Mode + if (bus_handle_ == nullptr) { + ESP_LOGE(TAG, "I2C bus not initialized before BaseBoardControlInit"); + return; + } + + // 创建 I2C HAL 实例 + BaseBoardHalI2c *hal = new BaseBoardHalI2c(bus_handle_); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl I2C initialized"); +#else + // UART Mode + uart_config_t uart_config = { + .baud_rate = BASE_BOARD_CTL_UART_BAUDRATE, + .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, + .flags = { + .allow_pd = 0, + .backup_before_sleep = 0, + }, + }; + + // 1. 设置串口参数 + ESP_ERROR_CHECK(uart_param_config(BASE_BOARD_CTL_UART_PORT, &uart_config)); + + // 2. 设置 UART 引脚 + ESP_ERROR_CHECK(uart_set_pin(BASE_BOARD_CTL_UART_PORT, BASE_BOARD_CTL_UART_TX_PIN, BASE_BOARD_CTL_UART_RX_PIN, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + // 3. 安装 UART 驱动 + ESP_ERROR_CHECK(uart_driver_install(BASE_BOARD_CTL_UART_PORT, + BASE_BOARD_CTL_UART_BUF_SIZE, + BASE_BOARD_CTL_UART_BUF_SIZE, + 0, NULL, 0)); + + BaseBoardHalUart *hal = new BaseBoardHalUart(BASE_BOARD_CTL_UART_PORT); + base_board_control_.Init(hal); + ESP_LOGI(TAG, "BaseBoardControl UART initialized"); +#endif +} + +/*========================== SD卡相关驱动 ==========================*/ + +/** + * @brief SD卡初始化函数 + * + * 初始化SD卡外设,使用1线模式进行通信。该函数负责初始化外设管理器 + * 并配置SD卡,为后续的文件系统操作做准备。 + * + * @return esp_err_t 初始化结果,ESP_OK表示成功,其他错误码表示失败原因 + */ +esp_err_t BoardSweekar::InitializeSdcard(void) +{ + static const char *SDCARD_TAG = "SDCARD_INIT"; + esp_err_t ret; + + ESP_LOGI(SDCARD_TAG, "Initializing SD card"); + + if (is_sdcard_initialized_) { + ESP_LOGW(SDCARD_TAG, "SD card already initialized"); + return ESP_OK; + } + + // 初始化外设管理 + esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG(); + periph_cfg.extern_stack = true; // 将任务栈分配到 PSRAM 以节省 Internal SRAM + periph_set_ = esp_periph_set_init(&periph_cfg); + if (!periph_set_) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize peripheral set"); + return ESP_FAIL; + } + + // 初始化SD卡外设 + ret = audio_board_sdcard_init(periph_set_, SD_MODE_1_LINE); + if (ret != ESP_OK) { + ESP_LOGE(SDCARD_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + return ret; + } + + is_sdcard_initialized_ = true; + ESP_LOGI(SDCARD_TAG, "SD card initialized successfully"); + return ESP_OK; +} + +/** + * @brief 统一的硬件初始化接口实现 + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备可能依赖) + * 2. IO扩展板(可能影响其他设备) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. 其他外设 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardware(void) +{ + static const char *HW_INIT_TAG = "HW_INIT"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting unified hardware initialization..."); + + // 1. 初始化I2C总线(最基础) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "SD card initialized"); + } + + // 4. 初始化LCD IO引脚 + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "LCD IO pins initialized"); + + // 5. 初始化底座控制 + if (bus_handle_ != nullptr) { + BaseBoardHalI2c *hal_i2c = new BaseBoardHalI2c(bus_handle_); + base_board_control_.Init(hal_i2c); + ESP_LOGI(HW_INIT_TAG, "Base board control initialized with I2C handle"); + } else { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize base board control: I2C bus is null"); + } + + // 6. AUD_CTRL 为 codec 控制脚,板级初始化后保持拉高,不参与 PA 开关。 +#ifdef AUD_CTRL_PIN + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << AUD_CTRL_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + gpio_set_level(AUD_CTRL_PIN, 1); + ESP_LOGI(HW_INIT_TAG, "AUD_CTRL pin initialized to HIGH"); +#endif + + // 7. 可选外设(根据具体应用需求) + // BaseBoardControlUartInit(); // 底座控制串口(demo5需要) + // ESP_LOGI(HW_INIT_TAG, "Base board control UART initialized"); + + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + StepperMotorInit(); + + + + ESP_LOGI(HW_INIT_TAG, "Unified hardware initialization completed successfully"); + return ESP_OK; +} + +/** + * @brief 进入低功耗模式 + * + * 根据指定的功耗模式,执行相应的功耗优化策略: + * - IDLE模式:关闭非必要外设,保持快速响应 + * - LIGHT_SLEEP模式:进入轻度睡眠,CPU暂停,外设断电 + * - DEEP_SLEEP模式:深度睡眠,仅RTC运行,功耗最低 + */ +esp_err_t BoardSweekar::EnterLowPowerMode(power_mode_t mode) { + static const char *POWER_TAG = "POWER_MGMT"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "Entering power mode: %d", mode); + + if (mode == current_power_mode_) { + ESP_LOGW(POWER_TAG, "Already in power mode %d", mode); + return ESP_OK; + } + + // 保存当前模式,以便恢复 + power_mode_t previous_mode = current_power_mode_; + + switch (mode) { + case POWER_MODE_NORMAL: + // 正常模式,不需要特殊处理 + break; + + case POWER_MODE_IDLE: + // 空闲模式:关闭非必要外设 + ESP_LOGI(POWER_TAG, "Idle mode: disabling non-essential peripherals"); + + // 关闭SD卡(如果正在使用,需要先卸载) + if (is_sdcard_initialized_) { + // 注意:这里只是示例,实际应用中需要确保没有文件操作 + ESP_LOGW(POWER_TAG, "SD card still active in idle mode"); + } + + // 关闭显示背光 + // 关闭非必要GPIO的上拉/下拉 + // 降低I2C时钟频率 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠 + ESP_LOGI(POWER_TAG, "Light sleep mode: preparing for sleep"); + + // 设置唤醒源(需要根据具体需求配置) + // esp_sleep_enable_timer_wakeup(1000000); // 1秒后唤醒 + // esp_sleep_enable_ext0_wakeup(GPIO_NUM_XX, 0); // 外部中断唤醒 + + // 进入轻度睡眠 + // ret = esp_light_sleep_start(); + if (ret != ESP_OK) { + ESP_LOGE(POWER_TAG, "Failed to enter light sleep: %s", esp_err_to_name(ret)); + current_power_mode_ = previous_mode; + return ret; + } + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠 + ESP_LOGI(POWER_TAG, "Deep sleep mode: system will restart after wake-up"); + + // 保存必要的RTC数据 + // rtc_gpio_isolate(GPIO_NUM_XX); // 隔离GPIO + + // 设置深度睡眠唤醒源 + // esp_sleep_enable_timer_wakeup(60000000); // 60秒后唤醒 + + // 进入深度睡眠 + // esp_deep_sleep_start(); + // 注意:深度睡眠后系统会重启,不会返回 + break; + + default: + ESP_LOGE(POWER_TAG, "Invalid power mode: %d", mode); + return ESP_ERR_INVALID_ARG; + } + + current_power_mode_ = mode; + ESP_LOGI(POWER_TAG, "Successfully entered power mode: %d", mode); + return ret; +} + +/** + * @brief 退出低功耗模式 + * + * 从低功耗模式恢复到正常运行模式,重新初始化必要的硬件资源。 + */ +esp_err_t BoardSweekar::ExitLowPowerMode(void) { + static const char *POWER_TAG = "POWER_MGMT"; + + ESP_LOGI(POWER_TAG, "Exiting low power mode"); + + if (current_power_mode_ == POWER_MODE_NORMAL) { + ESP_LOGW(POWER_TAG, "Already in normal mode"); + return ESP_OK; + } + + // 根据之前的功耗模式执行相应的恢复操作 + switch (current_power_mode_) { + case POWER_MODE_IDLE: + // 恢复空闲模式 + ESP_LOGI(POWER_TAG, "Restoring from idle mode"); + + // 重新初始化必要的外设 + // 恢复I2C时钟频率 + // 重新开启显示背光 + break; + + case POWER_MODE_LIGHT_SLEEP: + // 轻度睡眠唤醒后会自动继续执行 + ESP_LOGI(POWER_TAG, "Woken up from light sleep"); + + // 重新配置时钟(如果需要) + // 重新初始化外设 + break; + + case POWER_MODE_DEEP_SLEEP: + // 深度睡眠后系统会重启,不会执行到这里 + ESP_LOGW(POWER_TAG, "Deep sleep recovery should not reach here"); + break; + + default: + break; + } + + // 恢复到正常模式 + current_power_mode_ = POWER_MODE_NORMAL; + ESP_LOGI(POWER_TAG, "Successfully exited low power mode"); + + return ESP_OK; +} + +/** + * @brief 设置硬件模块电源状态 + * + * 提供精细化的电源管理,可以单独控制各个硬件模块的电源状态。 + */ +esp_err_t BoardSweekar::SetModulePower(hardware_module_t module, bool enable) { + static const char *POWER_TAG = "POWER_MODULE"; + esp_err_t ret = ESP_OK; + + ESP_LOGI(POWER_TAG, "%s module %d", enable ? "Enabling" : "Disabling", module); + + // 更新电源状态位图 + if (enable) { + power_state_flags_ |= (1 << module); + } else { + power_state_flags_ &= ~(1 << module); + } + + switch (module) { + case HW_MODULE_I2C: + if (!enable && bus_handle_) { + // 注意:I2C总线通常不建议完全关闭,可能影响多个设备 + ESP_LOGW(POWER_TAG, "I2C bus power control not recommended"); + } + break; + + case HW_MODULE_SDCARD: + if (enable && !is_sdcard_initialized_) { + ret = InitializeSdcard(); + } else if (!enable && is_sdcard_initialized_) { + // 卸载SD卡文件系统 + // 注意:需要确保没有打开的文件 + ESP_LOGW(POWER_TAG, "SD card power down requires file system cleanup"); + } + break; + + case HW_MODULE_LCD: + // LCD背光控制 + // LCD电源引脚控制(如果有) + break; + + case HW_MODULE_AUDIO: + // 音频编解码器电源控制 + // 需要与AudioPlayer协调 + break; + + case HW_MODULE_MOTOR: + // 步进电机驱动器电源控制 + // 电机睡眠模式 + break; + + case HW_MODULE_GYROSCOPE: + // 陀螺仪电源控制 + // 设置陀螺仪为低功耗模式 + break; + + case HW_MODULE_TOUCH: + // 触摸传感器电源控制 + // 设置触摸传感器为睡眠模式 + break; + + case HW_MODULE_RADAR: + // 毫米波雷达电源控制 + // 设置雷达为低功耗模式 + break; + + case HW_MODULE_IO_EXPAND: + // IO扩展板电源控制 + // 设置扩展板为低功耗模式 + break; + + default: + ESP_LOGE(POWER_TAG, "Unknown hardware module: %d", module); + return ESP_ERR_INVALID_ARG; + } + + if (ret == ESP_OK) { + ESP_LOGI(POWER_TAG, "Module %d power %s successfully", module, enable ? "enabled" : "disabled"); + } else { + ESP_LOGE(POWER_TAG, "Failed to %s module %d: %s", enable ? "enable" : "disable", module, esp_err_to_name(ret)); + } + + return ret; +} + + + diff --git a/main/02_physical/boards/board_v1_1_18/board_sweekar.h b/main/02_physical/boards/board_v1_1_18/board_sweekar.h new file mode 100644 index 0000000..55665ac --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/board_sweekar.h @@ -0,0 +1,181 @@ +#ifndef __BOARD_H__ +#define __BOARD_H__ +#include "config.h" +#include "io_expansion_board.h" +#include "stepper_motor.h" +#include "stepper_driver.h" +#include "i2c_bus.h" +#include "gyroscope.h" +#include "touch_sensor.h" +#include "base_board_control.h" +#include "esp_peripherals.h" +#include "periph_sdcard.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" + +/** + * @brief 功耗模式枚举 + */ +typedef enum { + POWER_MODE_NORMAL, // 正常运行模式,所有硬件可用 + POWER_MODE_IDLE, // 空闲模式,关闭非必要外设 + POWER_MODE_LIGHT_SLEEP, // 轻度睡眠,快速唤醒 + POWER_MODE_DEEP_SLEEP, // 深度睡眠,仅RTC运行 +} power_mode_t; + +/** + * @brief 硬件模块枚举,用于精细化电源管理 + */ +typedef enum { + HW_MODULE_I2C, // I2C总线 + HW_MODULE_SDCARD, // SD卡 + HW_MODULE_LCD, // LCD显示 + HW_MODULE_AUDIO, // 音频编解码器 + HW_MODULE_MOTOR, // 步进电机 + HW_MODULE_GYROSCOPE, // 陀螺仪 + HW_MODULE_TOUCH, // 触摸传感器 + HW_MODULE_RADAR, // 毫米波雷达 + HW_MODULE_IO_EXPAND, // IO扩展板 +} hardware_module_t; + +/** + * @brief 硬件板级管理器 + * + * 采用高内聚低耦合设计原则,统一管理所有硬件资源。 + * 提供统一的硬件初始化接口和资源访问,避免各模块重复初始化。 + */ +class BoardSweekar +{ +private: + i2c_bus_handle_t bus_handle_ = nullptr; // 使用ESP-ADF的I2C总线句柄 + esp_periph_set_handle_t periph_set_ = nullptr; // 外设管理句柄 + bool is_sdcard_initialized_ = false; // SD卡初始化状态 + power_mode_t current_power_mode_ = POWER_MODE_NORMAL; // 当前功耗模式 + uint32_t power_state_flags_ = 0xFFFFFFFF; // 各模块电源状态位图(1为开启) + +public: + BoardSweekar(); + ~BoardSweekar(); + + static BoardSweekar* GetInstance(); + + // --- 语义化的高层 API:音频与功放控制 --- + // 开启或关闭外放功放 (PA),供上层 AudioActor/AudioPlayer 调用,屏蔽底层引脚实现 + void SetAudioAmplifier(bool enable); + + IOExpansionBoard io_expansion_; // IO扩展板对象 + Gyroscope gyroscope_; // 陀螺仪对象 + TouchSensor touch_sensor_; // 触摸传感器对象 + BaseBoardControl base_board_control_; // 底座控制对象 + StepperDriver* stepper_driver_ = nullptr; // 步进电机驱动对象 + + StepperDriver* GetStepperDriver() const { return stepper_driver_; } + + // 初始化步进电机 + void StepperMotorInit(void); + + // 初始化LCD IO引脚(消除与步进电机的耦合) + void LcdIoInit(void); + + // 初始化串口 + void MillimeterWaveRadarUartInit(void); + + //初始化毫米波雷达的中断引脚 + void MillimeterWaveRadarGpioInit(void); + + /** + * @brief 执行系统关机 + * 内部拉高/拉低 SHUTDOWN_CTRL_PIN 引脚切断电源 + */ + void SystemShutdown(void); + + // 初始化I2C总线 + esp_err_t I2CMasterInit(void); + + // 初始化VBAT toVBAT_5V引脚 + void VbatToVbat5vInit(void); + + // 初始化底座控制串口 + void BaseBoardControlInit(void); + + // 初始化SD卡(推荐使用统一的硬件初始化) + esp_err_t InitializeSdcard(void); + + // 获取SD卡初始化状态 + bool IsSdcardInitialized() const { return is_sdcard_initialized_; } + + // 获取外设管理句柄(供需要访问SD卡的模块使用) + esp_periph_set_handle_t GetPeriphSet() const { return periph_set_; } + + // 获取I2C总线句柄 + i2c_bus_handle_t GetBusHandle() const { return bus_handle_; } + + /** + * @brief 统一的硬件初始化接口 + * + * 按照依赖关系顺序初始化所有硬件资源,确保各模块可以安全使用。 + * 推荐在主程序启动时调用此函数,而不是分别调用各个初始化函数。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardware(void); + + /** + * @brief 完整的统一硬件初始化接口(专为复杂应用优化) + * + * 包含所有必要外设的完整初始化,适用于demo5等复杂应用场景。 + * 这个函数会初始化:I2C总线、IO扩展板、SD卡、LCD IO、底座控制等。 + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeAllHardwareComplete(void); + + /** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置,优化启动流程。 + * + * @param app_type 应用类型(demo5、音频播放器、显示应用等) + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ + esp_err_t InitializeHardwareForApplication(int app_type); + + /** + * @brief 功耗管理接口 - 进入低功耗模式 + * + * 根据指定的功耗模式,关闭非必要硬件、设置GPIO为低功耗状态、 + * 调整时钟频率等。支持多种功耗模式以适应不同应用场景。 + * + * @param mode 功耗模式 + * @return esp_err_t 操作结果 + */ + esp_err_t EnterLowPowerMode(power_mode_t mode); + + /** + * @brief 功耗管理接口 - 退出低功耗模式 + * + * 从低功耗模式恢复,重新初始化必要的硬件资源。 + * + * @return esp_err_t 操作结果 + */ + esp_err_t ExitLowPowerMode(void); + + /** + * @brief 获取当前功耗模式 + * + * @return power_mode_t 当前功耗模式 + */ + power_mode_t GetPowerMode(void) const { return current_power_mode_; } + + /** + * @brief 设置硬件模块电源状态 + * + * 单独控制某个硬件模块的电源状态,用于精细化的功耗管理。 + * + * @param module 硬件模块 + * @param enable true为开启,false为关闭 + * @return esp_err_t 操作结果 + */ + esp_err_t SetModulePower(hardware_module_t module, bool enable); +}; +#endif diff --git a/main/02_physical/boards/board_v1_1_18/board_sweekar_complete_init.cc b/main/02_physical/boards/board_v1_1_18/board_sweekar_complete_init.cc new file mode 100644 index 0000000..52e9456 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/board_sweekar_complete_init.cc @@ -0,0 +1,200 @@ +/** + * @file board_sweekar_complete_init.cc + * @brief 完整的统一硬件初始化实现 + * + * 这个文件提供了完整的统一硬件初始化函数,包含所有必要的硬件初始化步骤。 + * 专为demo5等复杂应用场景设计。 + */ + +/** + * @brief 完整的统一硬件初始化接口(专为demo5优化) + * + * 按照依赖关系顺序初始化所有硬件资源: + * 1. I2C总线(最基础,其他设备依赖) + * 2. IO扩展板(控制其他外设) + * 3. SD卡(文件系统,供显示和音频使用) + * 4. LCD IO引脚(显示相关) + * 5. 底座控制串口(通信) + * 6. 底座控制模块(业务逻辑) + * 7. 其他外设(根据应用需求) + * + * @return esp_err_t 初始化结果,ESP_OK表示全部成功 + */ +esp_err_t BoardSweekar::InitializeAllHardwareComplete(void) +{ + static const char *HW_INIT_TAG = "HW_INIT_COMPLETE"; + esp_err_t ret; + + ESP_LOGI(HW_INIT_TAG, "Starting complete hardware initialization..."); + + // 1. 初始化I2C总线(最基础,其他设备依赖) + ret = I2CMasterInit(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize I2C master: %s", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(HW_INIT_TAG, "✓ I2C master initialized"); + + // 2. 创建I2C读任务(IO扩展板需要) + io_expansion_.CreateI2cReadTask(); + ESP_LOGI(HW_INIT_TAG, "✓ I2C read task created"); + + // 3. 初始化SD卡(文件系统,供显示和音频使用) + ret = InitializeSdcard(); + if (ret != ESP_OK) { + ESP_LOGE(HW_INIT_TAG, "Failed to initialize SD card: %s", esp_err_to_name(ret)); + // SD卡失败不是致命错误,继续初始化其他硬件 + ESP_LOGW(HW_INIT_TAG, "⚠ SD card initialization failed, but continuing with other hardware"); + } else { + ESP_LOGI(HW_INIT_TAG, "✓ SD card initialized"); + } + + // 4. 初始化LCD IO引脚(显示相关) + LcdIoInit(); + ESP_LOGI(HW_INIT_TAG, "✓ LCD IO pins initialized"); + + // 5. 初始化底座控制串口(demo5需要) + BaseBoardControlUartInit(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control UART initialized"); + + // 6. 初始化底座控制模块 + base_board_control_.Init(); + ESP_LOGI(HW_INIT_TAG, "✓ Base board control initialized"); + + // 7. 设置IO扩展板的PA控制模式(demo5需要) + io_expansion_.SetPaCtrlMode(true); + ESP_LOGI(HW_INIT_TAG, "✓ IO expansion PA control mode set"); + + // 8. 初始化 AUD_CTRL 引脚(如果定义了的话) +#ifdef AUD_CTRL_PIN + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << AUD_CTRL_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + // 拉高开机 + gpio_set_level(AUD_CTRL_PIN, 1); + ESP_LOGI(HW_INIT_TAG, "✓ AUD_CTRL pin initialized to HIGH"); +#endif + + // 9. 可选外设(根据具体应用需求启用) + // ESP_LOGI(HW_INIT_TAG, "Initializing optional peripherals..."); + // + // // 毫米波雷达(如需要) + // MillimeterWaveRadarUartInit(); + // MillimeterWaveRadarGpioInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Millimeter wave radar initialized"); + // + // // VBAT升压(如需要) + // VbatToVbat5vInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ VBAT to 5V booster initialized"); + // + // // 步进电机(如需要) + // StepperMotorInit(); + // ESP_LOGI(HW_INIT_TAG, "✓ Stepper motor initialized"); + + ESP_LOGI(HW_INIT_TAG, "=== Complete hardware initialization finished successfully ==="); + return ESP_OK; +} + +/** + * @brief 针对特定应用的硬件初始化配置 + * + * 为不同类型的应用提供专门的初始化配置 + */ +esp_err_t BoardSweekar::InitializeHardwareForApplication(application_type_t app_type) { + static const char *APP_INIT_TAG = "APP_HW_INIT"; + + ESP_LOGI(APP_INIT_TAG, "Initializing hardware for application type: %d", app_type); + + // 基础硬件初始化(所有应用都需要) + esp_err_t ret = InitializeAllHardwareComplete(); + if (ret != ESP_OK) { + ESP_LOGE(APP_INIT_TAG, "Basic hardware initialization failed"); + return ret; + } + + switch (app_type) { + case APP_TYPE_DEMO5: // 宠物游戏demo + ESP_LOGI(APP_INIT_TAG, "Configuring for Demo5 (Pet Game)..."); + + // Demo5需要的额外初始化 + // 触摸传感器、陀螺仪、RGB LED等会在应用层初始化 + + ESP_LOGI(APP_INIT_TAG, "✓ Demo5 hardware configuration complete"); + break; + + case APP_TYPE_AUDIO_PLAYER: // 音频播放器 + ESP_LOGI(APP_INIT_TAG, "Configuring for Audio Player..."); + + // 音频应用需要确保音频编解码器正确初始化 + // 这部分通常在AudioPlayer类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Audio player hardware configuration complete"); + break; + + case APP_TYPE_DISPLAY_ONLY: // 纯显示应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Display Only..."); + + // 显示应用需要确保LCD相关硬件正确配置 + // 这部分通常在Display类中处理 + + ESP_LOGI(APP_INIT_TAG, "✓ Display-only hardware configuration complete"); + break; + + case APP_TYPE_MINIMAL: // 最小化应用 + ESP_LOGI(APP_INIT_TAG, "Configuring for Minimal Application..."); + + // 最小化配置,只保留最基本的功能 + // 关闭非必要外设以节省功耗 + + ESP_LOGI(APP_INIT_TAG, "✓ Minimal hardware configuration complete"); + break; + + default: + ESP_LOGW(APP_INIT_TAG, "Unknown application type, using default configuration"); + break; + } + + return ESP_OK; +} + +/** + * @brief 硬件初始化状态报告 + * + * 提供详细的硬件初始化状态报告,便于调试和问题排查 + */ +void BoardSweekar::ReportHardwareStatus(void) { + static const char *STATUS_TAG = "HW_STATUS"; + + ESP_LOGI(STATUS_TAG, "=== Hardware Initialization Status Report ==="); + + // I2C总线状态 + ESP_LOGI(STATUS_TAG, "I2C Bus: %s", (bus_handle_ != nullptr) ? "✓ Initialized" : "✗ Not initialized"); + + // SD卡状态 + ESP_LOGI(STATUS_TAG, "SD Card: %s", (is_sdcard_initialized_) ? "✓ Initialized" : "✗ Not initialized"); + if (is_sdcard_initialized_) { + ESP_LOGI(STATUS_TAG, " Peripheral Set: %s", (periph_set_ != nullptr) ? "✓ Available" : "✗ Not available"); + } + + // IO扩展板状态 + ESP_LOGI(STATUS_TAG, "IO Expansion Board: %s", (io_expansion_.bus_handle_ != nullptr) ? "✓ Configured" : "✗ Not configured"); + + // 底座控制状态 + ESP_LOGI(STATUS_TAG, "Base Board Control: %s", (base_board_control_.GetUartPort() != 0) ? "✓ Initialized" : "✗ Not initialized"); + + // 功耗管理状态 + ESP_LOGI(STATUS_TAG, "Power Management: %s", (current_power_mode_ == POWER_MODE_NORMAL) ? "✓ Normal mode" : "⚠ Low power mode"); + ESP_LOGI(STATUS_TAG, " Current Mode: %d", current_power_mode_); + ESP_LOGI(STATUS_TAG, " Module Power States: 0x%08X", power_state_flags_); + + // 内存状态 + ESP_LOGI(STATUS_TAG, "Memory Status:"); + ESP_LOGI(STATUS_TAG, " Internal RAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + ESP_LOGI(STATUS_TAG, " PSRAM Free: %d bytes", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + + ESP_LOGI(STATUS_TAG, "=== End of Hardware Status Report ==="); +} \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_18/config.h b/main/02_physical/boards/board_v1_1_18/config.h new file mode 100644 index 0000000..c69e4e2 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/config.h @@ -0,0 +1,136 @@ +/* 定义引脚分配 */ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +/** + * @file config.h + * @brief 定义了引脚的分配。 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/i2s.h" +#include "esp_log.h" +#include "esp_rom_sys.h" // 用于 esp_rom_delay_us +#include "esp_timer.h" +#include "esp_mac.h" +#include "driver/ledc.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/gptimer.h" +#include "esp_intr_alloc.h" +#include "driver/uart.h" +#include "esp_err.h" +#include "freertos/queue.h" +#include "soc/uart_reg.h" +#include "linear_motor.h" +#include "gyroscope.h" + +#define TAG_BOARD "SWEEKAR_ESP32" +/*I2C总线引脚定义*/ +#define I2C0_MASTER_PORT I2C_NUM_0 +#define I2C0_MASTER_SDA_IO GPIO_NUM_2 +#define I2C0_MASTER_SCL_IO GPIO_NUM_3 + +/* 步进电机配置 */ +#define STEPPER_STEP_IO GPIO_NUM_42 // 步进电机脉冲输入引脚 +#define STEPPER_TIMER_RES_HZ 1000000 // 步进电机定时器分辨率 1 MHz(1 us 步进) + +/* 定义屏幕驱动控制引脚 */ +#define ST7789_PIN_CLK GPIO_NUM_48 // SPI时钟引脚 +#define ST7789_PIN_DC GPIO_NUM_38 // 数据/命令选择引脚RSEL +#define ST7789_PIN_MOSI GPIO_NUM_47 // SPI数据输出引脚 +#define ST7789_PIN_CS GPIO_NUM_46 // SPI片选引脚 +#define ST7789_PIN_BL GPIO_NUM_45 // 背光引脚 +#define ST7789_PIN_RST GPIO_NUM_NC // 复位引脚 +#define LCD_BL_ACTIVE_LEVEL 0 // 背光有效电平:1=高电平有效(NMOS),0=低电平有效(PMOS) + +#define LCD_WIDTH 172 // 屏幕宽度(水平方向像素数) +#define LCD_HEIGHT 320 // 屏幕高度(垂直方向像素数) + +/* 毫米波雷达配置参数 */ +/* 定义毫米波雷达的中断引脚 */ +#define MILLIMETER_WAVE_RADAR_PIN GPIO_NUM_35 +// UART 配置参数 +#define RADAR_UART_NUM UART_NUM_1 // 使用 UART1 +#define UART_TX_PIN GPIO_NUM_40 // GPIO40 作为 TX(根据实际硬件连接调整) +#define UART_RX_PIN GPIO_NUM_39 // GPIO39 作为 RX +#define RADAR_BAUD_RATE 9600 // 雷达波特率 +#define RADAR_BUF_SIZE 100 // 雷达接收缓冲区大小 + +/* IO扩展硬件配置常量 */ +#define I2C_MASTER_FREQ_HZ 100000 // I2C通信频率(100kHz)(可更改至400000) +#define I2C_IO_EXPAND_SLAVE_ADDR 0x58 // AW9523BTQR器件地址,v1_1_13的addr1和addr0是0 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // 中断检测引脚 +#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志默认值 +#define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/* 定义线性马达的I2C从机配置常量 */ +#define LINEAR_MOTOR_I2C_SLAVE_ADDR 0x5A // 线性马达的I2C从机地址 + +/* 定义陀螺仪的I2C从机配置常量 */ +#define GYROSCOPE_I2C_SLAVE_ADDR 0x6A // 陀螺仪的I2C从机地址 +#define GYROSCOPE_INPUT_PIN_SEL GPIO_NUM_21 // 陀螺仪中断引脚 + +/* AW触摸芯片配置 */ +#define AW_TOUCH_IRQ_PIN GPIO_NUM_11 // AW触摸芯片中断引脚 +#define NFC_RST_PIN GPIO_NUM_12 // NFC复位引脚 +#define AUD_CTRL_PIN GPIO_NUM_13 // AUD_CTRL引脚 + +/* 原生触摸引脚配置 (v1.1.18已改为上述功能,保留定义需谨慎) */ +// 触摸按键数量配置 +#define TOUCH_BUTTON_NUM 3 +// 触摸通道定义 - 注意:v1.1.18如果不使用ESP32原生触摸,这些定义可能不再适用 +// 但为了兼容编译,可以保留或注释 +#define TOUCH_PAD_CHANNEL_1 TOUCH_PAD_NUM11 // GPIO11 (Now AW_IRQ) +#define TOUCH_PAD_CHANNEL_2 TOUCH_PAD_NUM12 // GPIO12 (Now NFC_RST) +#define TOUCH_PAD_CHANNEL_3 TOUCH_PAD_NUM13 // GPIO13 (Now AUD_CTRL) + +/* ES8311音频编解码器配置 (ESP-ADF兼容) */ +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_INPUT_REFERENCE true + +/* I2S GPIO定义 */ +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_15 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_4 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 + +/* PA引脚定义 */ +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC + +/* I2C配置 */ +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_3 +#define AUDIO_CODEC_ES8311_ADDR 0x18 +#define AUDIO_CODEC_ES7210_ADDR 0x40 + +/* WS2812 RGB LED配置 */ +#define WS2812_PIN GPIO_NUM_41 // WS2812数据引脚 +#define WS2812_NUM_LEDS 2 // WS2812 LED数量 + +/* 创建VBAT to VBAT_5V */ +#define VBAT_5V_PIN GPIO_NUM_NC // VBAT_5V引脚 + +/* 定义按键的中断引脚 */ +#define BUTTON_PIN GPIO_NUM_0 // 按键中断引脚 +#define RIGHT_BUTTON_PIN GPIO_NUM_8 // 按键中断引脚 + +/* 底座控制命令串口配置 */ +#define BASE_BOARD_CTL_UART_PORT UART_NUM_0 +#define BASE_BOARD_CTL_UART_TX_PIN GPIO_NUM_43 +#define BASE_BOARD_CTL_UART_RX_PIN GPIO_NUM_44 +#define BASE_BOARD_CTL_UART_BAUDRATE 115200 +#define BASE_BOARD_CTL_UART_BUF_SIZE 256 + +/* 背光控制引脚配置 */ +#define BL_PIN GPIO_NUM_45 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/02_physical/boards/board_v1_1_18/io_expansion_board.cc b/main/02_physical/boards/board_v1_1_18/io_expansion_board.cc new file mode 100644 index 0000000..bfe1679 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/io_expansion_board.cc @@ -0,0 +1,1040 @@ +#include "io_expansion_board.h" +#include "millimeter_wave_radar.h" +#include "photo_interrupter.h" +#include "adc_battery_monitor.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "freertos/portable.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_rom_sys.h" +/** + * @file io_expansion_board.cc + * @brief IO扩展板功能实现类,包含I2C通信、GPIO中断配置、外设控制等功能 + * @details 实现了IO扩展板的初始化(中断引脚、复位引脚)、寄存器读写、步进电机控制(细分模式、使能、方向、睡眠状态)、 + * LCD相关引脚控制(背光、复位、片选)、毫米波雷达中断引脚配置等功能,并提供I2C数据读取任务的创建与管理 + * @author zouyuhua + * @date 2025年8月7日 + * + */ + +/** + * @brief 构造函数 + */ +IOExpansionBoard::IOExpansionBoard() + : bus_handle_(nullptr), + device_addr_(0), + last_port0_state_(0), + last_port1_state_(0), + radar_last_state_(false), + i2c_task_event_group_(nullptr), + xI2CReadTaskHandle_(nullptr), + is_initialized_(false), + interrupt_gpio_(GPIO_NUM_NC), + reset_gpio_(GPIO_NUM_NC) +{ +} + +/** + * @brief 析构函数 + */ +IOExpansionBoard::~IOExpansionBoard() +{ + // 删除任务 + if (xI2CReadTaskHandle_) { + vTaskDelete(xI2CReadTaskHandle_); + } + + // if(pI2CReadTaskStackBuffer) { + // heap_caps_free(pI2CReadTaskStackBuffer); + // pI2CReadTaskStackBuffer = nullptr; + // } + + // if(pxI2CReadTaskBuffer) { + // vPortFree(pxI2CReadTaskBuffer); + // pxI2CReadTaskBuffer = nullptr; + // } + + // 删除事件组 + if (i2c_task_event_group_) { + vEventGroupDelete(i2c_task_event_group_); + } + + // 移除中断处理 + if (interrupt_gpio_ != GPIO_NUM_NC) { + gpio_isr_handler_remove(interrupt_gpio_); + } + + // 删除IO扩展板设备 + // 使用i2c_bus API时不需要手动删除设备句柄 + // bus_handle_由board_sweekar统一管理 +} + +/** + * @brief 静态中断处理函数 + */ +void IRAM_ATTR IOExpansionBoard::GpioIsrHandler(void* arg) +{ + IOExpansionBoard* instance = static_cast(arg); + if (!instance) return; + + // 调试:记录中断触发 + // ESP_EARLY_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发!"); // IRAM函数中不能使用普通日志 + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // 设置事件标志位 + if (instance->i2c_task_event_group_) { + xEventGroupSetBitsFromISR( + instance->i2c_task_event_group_, + BIT0, + &xHigherPriorityTaskWoken + ); + } + + // 触发上下文切换 + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief 初始化GPIO中断检测 + */ +esp_err_t IOExpansionBoard::InterruptGpioInit(gpio_num_t gpio_num) +{ + // 配置IO扩展板中断引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_ANYEDGE; // 改为双沿触发,检测上升沿和下降沿 + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + // io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + // io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + + + esp_err_t err = gpio_config(&io_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + // 配置中断服务 + err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断服务安装失败,错误码: 0x%x", err); + return err; + } + // 注册中断处理函数 + err = gpio_isr_handler_add(gpio_num, GpioIsrHandler, this); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断处理函数添加失败,错误码: 0x%x", err); + return err; + } + // 创建事件组 + i2c_task_event_group_ = xEventGroupCreate(); + if (!i2c_task_event_group_) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "事件组创建失败"); + return ESP_FAIL; + } + + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d中断初始化完成", gpio_num); + return ESP_OK; +} + +/** + * @brief 初始化硬件复位引脚 + * 设置为上拉,需要复位,需要输出低电平为20us的脉冲信号 + */ +esp_err_t IOExpansionBoard::ResetGpioInit(gpio_num_t gpio_num) +{ + reset_gpio_ = gpio_num; + + // 配置复位引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = 1ULL << gpio_num; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + esp_err_t err = gpio_config(&io_conf); + gpio_set_level(gpio_num, 1); // 复位引脚拉高 + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO配置失败,错误码: 0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "GPIO%d复位引脚初始化完成", gpio_num); + return err; + } +} + +/** + * @brief 计算Port0的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort0() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT0){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT0){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT0){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT0){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT0){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 计算Port1的字节值,用于配置输入还是输出模式(0为输出模式,1为输入模式) + * @return uint8_t 操作结果 + */ +uint8_t IOExpansionBoard::ByteCalculatePort1() +{ + uint8_t byte_val = 0x00; // 初始化为所有引脚为输出模式(0),然后根据配置设置为输入模式(1) + + // 输入模式配置:将对应位置位(1为输入模式) + if(RADAR_INTERRUPT_CONFIG == CONFIG_PORT1){ //雷达中断引脚 + byte_val |= (1 << RADAR_INTERRUPT_PIN); + } + if(START_PHOTO_INTERRUPTER_CONFIG == CONFIG_PORT1){ //起始光学传感器 + byte_val |= (1 << START_PHOTO_INTERRUPTER_PIN); + } + if(END_PHOTO_INTERRUPTER_PORT_CONFIG == CONFIG_PORT1){ //结束光学传感器 + byte_val |= (1 << END_PHOTO_INTERRUPTER_PIN); + } + if(BATTERY_FULL_CONFIG == CONFIG_PORT1){ //电池充满检测 + byte_val |= (1 << BATTERY_FULL_PIN); + } + if(BATTERY_CHRG_CONFIG == CONFIG_PORT1){ //电池充电检测 + byte_val |= (1 << BATTERY_CHRG_PIN); + } + + return byte_val; +} + +/** + * @brief 复位IO扩展板 + */ +esp_err_t IOExpansionBoard::ResetIoExpand() +{ + if (reset_gpio_ == GPIO_NUM_NC) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位引脚未初始化"); + return ESP_ERR_INVALID_STATE; + } + + // 复位IO扩展板 + gpio_set_level(reset_gpio_, 0); + esp_rom_delay_us(30); + gpio_set_level(reset_gpio_, 1); + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "复位失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "复位成功"); + } + return ESP_OK; +} + +/** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::IoexpandWriteReg(uint8_t reg_addr, uint8_t data) +{ + return i2c_bus_write_bytes(bus_handle_, device_addr_, ®_addr, 1, &data, 1); +} + +/** + * @brief 从寄存器读取数据 + * @param reg_addr 寄存器地址(如INPUT_PORT0、ID等) + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果(ESP_OK为成功) + */ +esp_err_t IOExpansionBoard::IoexpandReadReg(uint8_t reg_addr, uint8_t* data) +{ + if (data == nullptr){ // 检查输入参数有效性 + return ESP_ERR_INVALID_ARG; + } + + // 使用i2c_bus_read_bytes进行寄存器读取 + esp_err_t err = i2c_bus_read_bytes(bus_handle_, device_addr_, ®_addr, 1, data, 1); + if (err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "读取寄存器失败,错误码:0x%x", err); + return err; + } + + return ESP_OK; +} + +/** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort0(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT0, data); +} + +/** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ +esp_err_t IOExpansionBoard::ReadInputPort1(uint8_t* data) +{ + return IoexpandReadReg(INPUT_PORT1, data); +} + +//设置IO扩展板为输入模式或是输出模式 +/* +* 这里需要根据实际情况设置IO扩展板的模式,比如输入模式还是输出模式,以及各个引脚的配置。 +* P0_0 对应NFC_RST引脚,需要设置为输出模式,低电平有效。(待测) +* P0_1 对应USB_VBUS引脚,需要设置为输出模式,低电平有效。(待测) +* P0_2 对应BTN_RIGHT_EAR引脚,需要设置为输出模式,低电平有效。(待测) +* P0_3 暂时未使用。 +* P0_4 对应步进电机方向引脚,需要设置为输出模式,根据需求设置。(待测) +* P0_5 对应CHRG引脚(待测) +* P0_6 LCD_DC引脚,需要设置为输出模式,根据需求设置电平。(待测) +* P0_7 对应步进电机的使能引脚,需要设置为输出模式,根据需求设置。(待测) +* +* +* P1_0 对应EG800K_PWR。(待测) +* P1_1 对应LCD_RST,需要设置为输出模式,低电平有效。(待测) +* P1_2 对应LCD_CS,需要设置为输出模式,低电平有效。(待测) +* P1_3 暂时未使用。 +* P1_4 对应步进电机的细分模式M1引脚。(待测) +* P1_5 对应步进电机的细分模式M0引脚。(待测) +* P1_6 对应步进电机的睡眠引脚。(待测) +* P1_7 对应RADAR_O雷达的输出引脚。(待测) +* */ +esp_err_t IOExpansionBoard::IoexpandSetMode(void) +{ + uint8_t ctl_val; + //配置P0_0-P0_7的端口为推挽输出模式 + esp_err_t err = IoexpandReadReg(CTL, &ctl_val); + ctl_val |= (1 << 4); // 设置D[4]位为1(GPOMD=1),切换P0为推挽模式 + err = IoexpandWriteReg(CTL, ctl_val); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功"); + } + + err = IoexpandWriteReg(CONFIG_PORT0, ByteCalculatePort0()); + + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort0()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口成功, 0x%02X", ByteCalculatePort0()); + } + + err = IoexpandWriteReg(CONFIG_PORT1, ByteCalculatePort1()); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口失败,错误码:0x%x", err); + return err; + } + else{ + // 打印BytecalculatePort1()结果 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口成功, 0x%02X", ByteCalculatePort1()); + } + // 配置P0_0-P0_7的初始为高电平 + // 配置P1_0-P1_7的初始为高电平 + // P0_6(旧的LCD_BL, 0x40), P0_1(led_en, PMOS高侧驱动, 高电平关, 0x02) + err = IoexpandWriteReg(OUTPUT_PORT0, 0x42); // 0100 0010 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口初始值成功"); + } + err = IoexpandWriteReg(OUTPUT_PORT1, 0x04); // 0000 0100 + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口初始值成功"); + } + + + // AW9523B中断使能配置:0为使能中断,1为禁止中断 + // 使能P0_5(充电检测)和P1_3(充满检测)的中断 + uint8_t int_enable_port0 = 0x00; // 0x00 = 使能所有P0引脚中断 + uint8_t int_enable_port1 = 0x00; // 0x00 = 使能所有P1引脚中断 + + err = IoexpandWriteReg(INT_PORT0, int_enable_port0); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P0端口中断使能成功: 0x%02X (0=使能)", int_enable_port0); + } + + err = IoexpandWriteReg(INT_PORT1, int_enable_port1); + if(err != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能失败,错误码:0x%x", err); + return err; + } + else{ + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "配置P1端口中断使能成功: 0x%02X (0=使能)", int_enable_port1); + } + + return ESP_OK; +} + +/** + * @brief 设置步进电机的细分模式 + */ +void IOExpansionBoard::SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin) +{ + uint8_t current_val; + switch (mode) { + case 1: //全步输出模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 2: //非圆 1/2 高低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 3: //1/4 步 低高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + case 4: //1/8 步 高高电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val |= (1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val |= (1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + default: //全步输入模式 低低电平 + IoexpandReadReg(OUTPUT_PORT_m0, ¤t_val); + current_val &= ~(1 << m0_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m0, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + IoexpandReadReg(OUTPUT_PORT_m1, ¤t_val); + current_val &= ~(1 << m1_pin); + if(IoexpandWriteReg(OUTPUT_PORT_m1, current_val) != ESP_OK){ + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置步进电机细分模式失败"); + } + break; + } +} + +/** + * @brief 设置步进电机的使能 + */ +esp_err_t IOExpansionBoard::SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin) +{ + uint8_t current_val; + if(enable){ + // 进入工作状态mode + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << enable_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "!!!!!!!!!0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << enable_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} +/** + * @brief 设置步进电机运行方向 + */ +esp_err_t IOExpansionBoard::StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin) +{ + uint8_t current_val; + if(dir){ + // 方向为正转 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/** + * @brief 设置步进电机是否进入睡眠或者工作状态(高电平进入工作状态,低电平进入睡眠状态) + */ +esp_err_t IOExpansionBoard::SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin) +{ + uint8_t current_val; + if(mode){ + // 进入工作状态mode + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val |= (1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入睡眠状态 + // ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入睡眠状态"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "电机睡眠输出寄存器: 0x%x", current_val); + current_val &= ~(1 << nsleep_pin); + // ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置电机睡眠输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + + +/*********************************************IO扩展板LCD引脚配置参数***********************************************/ +/** + * @brief 设置 LCD_BL引脚电平(高电平为命令模式,低电平为数据模式) + */ +esp_err_t IOExpansionBoard::SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin) +{ + uint8_t current_val; + if(mode){ + // 进入命令模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val |= (1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入数据模式 + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + current_val &= ~(1 << bl_pin); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/** + * @brief 设置 LED_EN 引脚电平(用于控制LCD背光/LED灯电源) + * @param mode true:开启(低电平), false:关闭(高电平) + */ +esp_err_t IOExpansionBoard::SetLedEnMode(bool mode) +{ + uint8_t current_val; + esp_err_t err = IoexpandReadReg(LED_EN_PORT, ¤t_val); + if (err != ESP_OK) return err; + + if(mode){ + // 开启 (PMOS低电平有效) + current_val &= ~(1 << LED_EN_PIN); + } + else{ + // 关闭 (PMOS高电平无效) + current_val |= (1 << LED_EN_PIN); + } + return IoexpandWriteReg(LED_EN_PORT, current_val); +} + +/** + * @brief 设置 LCD_RST引脚电平(高电平为复位模式,低电平为工作模式) + */ +esp_err_t IOExpansionBoard::SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t reset_pin) +{ + uint8_t current_val; + if(mode){ + // 进入复位模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入复位模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val |= (1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } + else{ + // 进入工作模式 + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "进入工作模式"); + IoexpandReadReg(OUTPUT_PORT, ¤t_val); + //打印读出来的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "LCD复位输出寄存器: 0x%x", current_val); + current_val &= ~(1 << reset_pin); + //打印设置后的寄存器值 + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "设置LCD复位输出寄存器: 0x%x", current_val); + return IoexpandWriteReg(OUTPUT_PORT, current_val); + } +} + +/*********************************************IO扩展板的输入引脚配置以及中断引脚定义***********************************************/ +/** + * @brief 设置 毫米波雷达的中断引脚 + */ +esp_err_t IOExpansionBoard::SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin) +{ + uint8_t current_val; + if(mode){ + // 进入输入模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val |= (1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } + else{ + // 进入输出模式 + IoexpandReadReg(INPUT_PORT, ¤t_val); + current_val &= ~(1 << interrupt_pin); + return IoexpandWriteReg(INPUT_PORT, current_val); + } +} + +/** + * @brief 设置 PA_CTRL引脚电平 + */ +esp_err_t IOExpansionBoard::SetPaCtrlMode(bool mode) +{ + uint8_t current_val; + if(mode){ + // 进入高电平状态 + ESP_LOGI("io_ex:", "read reg"); + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val |= (1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } + else{ + // 进入低电平状态 + IoexpandReadReg(PA_CTRL_PORT, ¤t_val); + current_val &= ~(1 << PA_CTRL_PIN); + return IoexpandWriteReg(PA_CTRL_PORT, current_val); + } +} + + + +/** + * @brief I2C数据读取任务主体 + */ +void IOExpansionBoard::I2cReadTask(void* pvParameters) +{ + IOExpansionBoard* instance = static_cast(pvParameters); + if (!instance) { + vTaskDelete(nullptr); + return; + } + // 初始化复位引脚 + esp_err_t err = instance->ResetGpioInit(IO_EXPAND_RSTN); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO复位初始化失败"); + } + + // 执行硬件复位 - 这是解决中断引脚持续拉低的关键 + err = instance->ResetIoExpand(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板硬件复位成功"); + + // 检查复位后的中断引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "复位后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + } + + // 初始化IO扩展板子的中断引脚 + err = instance->InterruptGpioInit(GPIO_INPUT_PIN_SEL); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "GPIO中断初始化失败"); + } + // 设置IO扩展板模式 - 复位后必须重新配置寄存器! + err = instance->IoexpandSetMode(); + if (err != ESP_OK) { + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置失败"); + } else { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板模式配置成功"); + } + // 设置LCD屏幕复位引脚 + err = instance->SetLcdResetMode(LCD_RST_HIGH, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + // 设置LCD屏幕复位引脚为低电平 + err = instance->SetLcdResetMode(LCD_RST_LOW, LCD_RST_PORT, LCD_RST_PIN); + if(err != ESP_OK){ + ESP_LOGE(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚失败"); + } + else{ + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "设置LCD屏幕复位引脚成功"); + } + + uint8_t current_val_port0_ = 0,current_val_port1_ = 0,current_temp_ = 0; + for (;;) { + // 等待中断事件触发 + if (instance->i2c_task_event_group_) { + xEventGroupWaitBits(instance->i2c_task_event_group_, BIT0, pdTRUE, pdFALSE, portMAX_DELAY); + } + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "IO扩展板中断触发"); + + // 检查中断触发时的引脚状态 + int interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "中断触发时引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + // AW9523B中断处理说明(根据数据手册): + // 1. 当输入引脚状态变化时,AW9523B会拉低INT引脚(连接到ESP32的GPIO10) + // 2. 通过读取输入端口寄存器(00H, 01H)来清除对应端口的中断 + // 3. 无独立的中断状态寄存器,需要通过输入电平变化来判断中断源 + // 4. 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + + // 一次性读取两个端口的输入寄存器,避免重复读取和错误的OR操作 + instance->IoexpandReadReg(INPUT_PORT0, ¤t_val_port0_); + instance->IoexpandReadReg(INPUT_PORT1, ¤t_val_port1_); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "读取端口状态 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + + // 检查读取寄存器后中断引脚是否被清除(应该恢复高电平) + interrupt_level = gpio_get_level(GPIO_INPUT_PIN_SEL); + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "读取寄存器后中断引脚状态: %s (0=低电平, 1=高电平)", + interrupt_level ? "高电平" : "低电平"); + + //判断是否有中断来自雷达引脚 + if((RADAR_INTERRUPT_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port0_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((RADAR_INTERRUPT_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN))){ + // 检查雷达引脚状态是否发生了变化,避免重复触发 + bool current_radar_state = (current_val_port1_ & (1 << RADAR_INTERRUPT_PIN)) != 0; + if (current_radar_state != instance->radar_last_state_) { + instance->radar_last_state_ = current_radar_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自雷达引脚1\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用毫米波雷达回调函数 + if (instance->radar_) { + instance->radar_->TriggerRadarDetectCallback(); + } + // 调用雷达回调函数(新接口) + if (instance->radar_callback_) { + instance->radar_callback_(); + } + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到雷达引脚中断\n"); + // } + + //判断是否有中断来自初始光学传感器引脚 + if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((START_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << START_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << START_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自初始光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用初始光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerStartCallback(); + } + // 调用光学传感器开始回调函数(新接口) + if (instance->photo_start_callback_) { + instance->photo_start_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + // else{ + // // ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "未检测到初始光学传感器引脚中断\n"); + // } + + //判断是否有中断来自结束光学传感器引脚 + if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT0) && (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port0_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port0_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port0_state_ = current_val_port0_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + } + } + else if((END_PHOTO_INTERRUPTER_INPUT == INPUT_PORT1) && (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN))){ + bool current_photo_state = (current_val_port1_ & (1 << END_PHOTO_INTERRUPTER_PIN)) != 0; + uint8_t old_photo_state = instance->last_port1_state_ & (1 << END_PHOTO_INTERRUPTER_PIN); + if ((current_photo_state ? 1 : 0) != (old_photo_state ? 1 : 0)) { + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到中断来自结束光学传感器引脚\n"); + // 读取输入寄存器以清除中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + // 调用结束光学传感器回调函数 + if (instance->photo_interrupter_) { + instance->photo_interrupter_->TriggerEndCallback(); + } + // 调用光学传感器结束回调函数(新接口) + if (instance->photo_end_callback_) { + instance->photo_end_callback_(); + } + // 更新状态 + instance->last_port1_state_ = current_val_port1_; + } else { + // 状态未变化,仍读取寄存器以清除可能的中断标志 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + } + } + + //CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 + //FULL未充满为高电平 充满电为低电平 + //判断是否有中断来自电池充电引脚 + //根据配置:BATTERY_CHRG_INPUT = INPUT_PORT0, BATTERY_FULL_INPUT = INPUT_PORT1 + + // AW9523B没有独立的中断状态寄存器,需要通过比较前后状态变化来判断中断源 + // 由于读取输入端口会清除中断,我们需要先保存当前状态,然后与上次状态比较 + + //获取当前电池状态 + bool current_charging_state = !(current_val_port0_ & (1 << BATTERY_CHRG_PIN)); // CHRG低电平表示充电中 + bool current_full_state = !(current_val_port1_ & (1 << BATTERY_FULL_PIN)); // FULL低电平表示已充满 + + // 详细的调试信息 + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - Port0: 0x%02X, Port1: 0x%02X", current_val_port0_, current_val_port1_); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - CHRG_PIN: %d (bit %d), FULL_PIN: %d (bit %d)", + (current_val_port0_ & (1 << BATTERY_CHRG_PIN)) ? 1 : 0, BATTERY_CHRG_PIN, + (current_val_port1_ & (1 << BATTERY_FULL_PIN)) ? 1 : 0, BATTERY_FULL_PIN); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 充电状态: %s, 充满状态: %s", + current_charging_state ? "充电中" : "未充电", + current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "电池状态检测 - 上次充电状态: %s, 上次充满状态: %s", + instance->battery_charging_last_state_ ? "充电中" : "未充电", + instance->battery_full_last_state_ ? "已充满" : "未充满"); + + //检查充电状态是否发生变化 + if (current_charging_state != instance->battery_charging_last_state_) { + instance->battery_charging_last_state_ = current_charging_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充电状态变化: %s", current_charging_state ? "充电中" : "未充电"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充电状态变化详情 - 从 %s 到 %s", + instance->battery_charging_last_state_ ? "未充电" : "充电中", + current_charging_state ? "充电中" : "未充电"); + + if (current_charging_state) { + // 进入充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到开始充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerChrgCallback(); + } + if (instance->battery_charging_callback_) { + instance->battery_charging_callback_(); + } + } else { + // 退出充电状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到停止充电"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerNotChrgCallback(); + } + if (instance->battery_not_charging_callback_) { + instance->battery_not_charging_callback_(); + } + } + } + + //检查充满状态是否发生变化 + if (current_full_state != instance->battery_full_last_state_) { + instance->battery_full_last_state_ = current_full_state; + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "电池充满状态变化: %s", current_full_state ? "已充满" : "未充满"); + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "充满状态变化详情 - 从 %s 到 %s", + instance->battery_full_last_state_ ? "未充满" : "已充满", + current_full_state ? "已充满" : "未充满"); + + if (current_full_state) { + // 进入充满状态 + ESP_LOGI(TAG_GPIO_IO_EXPAND_DETECT, "检测到电池已充满"); + if (instance->battery_monitor_) { + instance->battery_monitor_->TriggerFullCallback(); + } + if (instance->battery_full_callback_) { + instance->battery_full_callback_(); + } + } + + } + + // 根据AW9523B数据手册:读取输入端口寄存器清除对应端口的中断 + // 读取INPUT_PORT0 (00H) 清除P0口中断,读取INPUT_PORT1 (01H) 清除P1口中断 + uint8_t temp_input_val; + instance->IoexpandReadReg(INPUT_PORT0, &temp_input_val); + instance->IoexpandReadReg(INPUT_PORT1, &temp_input_val); + + ESP_LOGD(TAG_GPIO_IO_EXPAND_DETECT, "中断处理完成,已读取输入端口寄存器清除中断标志"); + } +} + + +/** + * @brief 创建I2C数据读取任务 + */ +void IOExpansionBoard::CreateI2cReadTask() +{ + printf("创建I2C读取任务之前:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之前:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); + if (xI2CReadTaskHandle_) { + return; // 任务已创建 + } + + xTaskCreateWithCaps( + I2cReadTask, + "i2c_read_task", + I2C_TASK_STACK, + this, + I2C_TASK_PRIORITY, + &xI2CReadTaskHandle_, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT + ); + + if (xI2CReadTaskHandle_ == NULL) { + ESP_LOGE(TAG_I2C_IO_EXPAND_TASK, "Failed to create static I2C read task"); + } else { + ESP_LOGI(TAG_I2C_IO_EXPAND_TASK, "Static I2C read task created successfully"); + } + printf("创建I2C读取任务之后:Internal Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); + printf("创建I2C读取任务之后:PSRAM Free: %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); +} + +/** + * @brief 启动I2C任务 (整合初始化和任务创建) + */ +esp_err_t IOExpansionBoard::StartTask() +{ + // 再创建任务 + if (!xI2CReadTaskHandle_) { + CreateI2cReadTask(); + } + return ESP_OK; +} diff --git a/main/02_physical/boards/board_v1_1_18/io_expansion_board.h b/main/02_physical/boards/board_v1_1_18/io_expansion_board.h new file mode 100644 index 0000000..2697e39 --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/io_expansion_board.h @@ -0,0 +1,432 @@ +#ifndef IO_EXPANSION_BOARD_H +#define IO_EXPANSION_BOARD_H + +#include +#include +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "portmacro.h" + +// 前向声明,避免循环引用 +class MillimeterWaveRadar; +class PhotoInterrupter; +class AdcBatteryMonitor; + +// 回调函数类型定义 +typedef std::function RadarCallback; +typedef std::function PhotoInterrupterStartCallback; +typedef std::function PhotoInterrupterEndCallback; +typedef std::function BatteryChargingCallback; +typedef std::function BatteryFullCallback; +typedef std::function BatteryNotChargingCallback; + +/* 寄存器地址 */ +#define INPUT_PORT0 0x00 // 输入端口0寄存器 +#define INPUT_PORT1 0x01 // 输入端口1寄存器 0为低电平(仅支持读取) +#define OUTPUT_PORT0 0x02 // 输出端口0寄存器 +#define OUTPUT_PORT1 0x03 // 输出端口1寄存器 0为低电平 +#define CONFIG_PORT0 0x04 // 配置端口0寄存器 +#define CONFIG_PORT1 0x05 // 配置端口1寄存器 0为输出模式 +#define INT_PORT0 0x06 // 中断端口0寄存器 +#define INT_PORT1 0x07 // 中断端口1寄存器 0为使能中断(根据手册默认是使能的) +#define CTL 0x11 // 全局控制寄存器 +#define ID 0x23 // 芯片ID寄存器(0x23) + +/***********************************IO扩展板电机引脚配置参数*************************************/ +/* 设置步进电机方向 */ +#define STEPPER_MOTOR_LEFT false //步进电机的方向为左转 +#define STEPPER_MOTOR_RIGHT true //步进电机的方向为右转 +#define STEPPER_MOTOR_DIR_PORT OUTPUT_PORT0 //步进电机方向引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_DIR_PIN 4 //步进电机方向引脚对应的IO扩展板引脚P0_4 +#define STEPPER_MOTOR_DIR_CONFIG CONFIG_PORT0 //步进电机方向引脚配置在P0_4 + +/* 设置步进电机细分模式 */ +#define STEPPER_MOTOR_M0_PIN 5 //步进电机细分模式选择引脚对应IO扩展板引脚P1_5 +#define STEPPER_MOTOR_M0_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M0_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_5 + +#define STEPPER_MOTOR_M1_PIN 4 //步进电机细分模式选择引脚对应IO扩展板引脚P1_4 +#define STEPPER_MOTOR_M1_MADE_PORT OUTPUT_PORT1 //步进电机模式选择引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define STEPPER_MOTOR_M1_CONFIG CONFIG_PORT1 //步进电机细分模式选择引脚配置在P1_4 + +/* 设置步进电机睡眠引脚 */ +#define STEPPER_MOTOR_SLEEP false //步进电机进入睡眠模式 +#define STEPPER_MOTOR_WORK true //步进电机进入工作模式 +#define STEPPER_MOTOR_SLEEP_PIN 6 //步进电机睡眠引脚对应IO扩展板引脚P1_6 +#define STEPPER_MOTOR_SLEEP_CONFIG CONFIG_PORT1 //步进电机睡眠引脚配置在P1_6 +#define STEPPER_MOTOR_SLEEP_PORT OUTPUT_PORT1 //步进电机睡眠引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 + +/* 设置电机的使能引脚 */ +#define STEPPER_MOTOR_ENABLE_PIN 7 //电机使能引脚对应IO扩展板引脚P0_7 +#define STEPPER_MOTOR_ENABLE_PORT OUTPUT_PORT0 //电机使能引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +#define STEPPER_MOTOR_ENABLE_CONFIG CONFIG_PORT0 //电机使能引脚配置在P0_7 +#define STEPPER_MOTOR_ENABLE_ENABLE true //电机使能引脚状态为高电平 +#define STEPPER_MOTOR_ENABLE_DISABLE false //电机使能引脚状态为低电平 + +/* 设置毫米波雷达对应IO扩展板的中断引脚 */ +#define RADAR_INTERRUPT_PIN 7 //毫米波雷达中断引脚对应IO扩展板引脚P1_7 +#define RADAR_INTERRUPT_CONFIG CONFIG_PORT1 //毫米波雷达中断引脚挂载在IO扩展板的输入端口 +#define RADAR_INTERRUPT_INPUT INPUT_PORT1 //毫米波雷达中断引脚输入状态检测在INPUT_PORT1 +#define RADAR_INTERRUPT_ENABLE true //毫米波雷达中断引脚状态为输入状态 +#define RADAR_INTERRUPT_DISABLE false //毫米波雷达中断引脚状态为输出状态 +/* 任务配置常量 */ +#define WRITE_BUF_SIZE 2 // I2C单次写入字节数 +#define READ_BUF_SIZE 2 // I2C单次读取字节数 +#define I2C_TASK_PRIORITY 2 // 任务优先级(中等) +#define I2C_TASK_STACK 4096 // 任务栈大小(4KB) + +// 光学传感器引脚定义 +#define START_PHOTO_INTERRUPTER_PIN 2 // 光学传感器引脚(P0_2) +#define START_PHOTO_INTERRUPTER_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define START_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define START_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define START_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +#define END_PHOTO_INTERRUPTER_PIN 3 // 光学传感器引脚(P0_3) +#define END_PHOTO_INTERRUPTER_PORT_CONFIG CONFIG_PORT0 // 光学传感器引脚挂载在IO扩展板的输入端口 +#define END_PHOTO_INTERRUPTER_INPUT INPUT_PORT0 // 光学传感器引脚输入状态检测在INPUT_PORT0 +#define END_PHOTO_INTERRUPTER_HIGH true // 光学传感器引脚状态为高电平 +#define END_PHOTO_INTERRUPTER_LOW false // 光学传感器引脚状态为低电平 + +/***********************************IO扩展板LCD引脚配置参数*************************************/ +// /* 设置LCD的BL引脚 */ +// #define LCD_BL_PIN 6 //LCD背光引脚对应IO扩展板引脚P0_6 +// #define LCD_BL_PORT OUTPUT_PORT0 //LCD背光引脚挂载在IO扩展板的输出端口OUTPUT_PORT0 +// #define LCD_BL_CONFIG CONFIG_PORT0 //LCD背光引脚配置在P0_6 +// #define LCD_BL_HIGH true //LCD背光引脚状态为高电平 +// #define LCD_BL_LOW false //LCD背光引脚状态为低电平 + +/* 设置LED_EN (LCD背光/LED控制)引脚, PMOS高侧驱动 */ +#define LED_EN_PIN 1 //LED_EN对应IO扩展板引脚P0_1 +#define LED_EN_PORT OUTPUT_PORT0 //挂载在输出端口OUTPUT_PORT0 +#define LED_EN_CONFIG CONFIG_PORT0 //配置在P0_1 +#define LED_EN_OFF true //高电平关(PMOS) +#define LED_EN_ON false //低电平开(PMOS) + +/* 设置LCD的RST引脚 */ +#define LCD_RST_PIN 1 //LCD复位引脚对应IO扩展板引脚P1_1 +#define LCD_RST_CONFIG CONFIG_PORT1 //LCD复位引脚配置在P1_1 +#define LCD_RST_PORT OUTPUT_PORT1 //LCD复位引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define LCD_RST_HIGH true //LCD复位引脚状态为高电平 +#define LCD_RST_LOW false //LCD复位引脚状态为低电平 + +/* 设置PA控制引脚 */ +#define PA_CTRL_PIN 2 //PA控制引脚对应IO扩展板引脚P1_2 +#define PA_CTRL_CONFIG CONFIG_PORT1 //PA控制引脚配置在P1_2 +#define PA_CTRL_PORT OUTPUT_PORT1 //PA控制引脚挂载在IO扩展板的输出端口OUTPUT_PORT1 +#define PA_CTRL_HIGH true //PA控制引脚状态为高电平 +#define PA_CTRL_LOW false //PA控制引脚状态为低电平 + +// 电池充电检测引脚定义 +#define BATTERY_FULL_INPUT INPUT_PORT1 // 电池充满检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_FULL_PIN 3 // 电池充满检测引脚对应IO扩展板引脚P1_3 +#define BATTERY_FULL_CONFIG CONFIG_PORT1 // 电池充满检测引脚配置在P1_3 + +#define BATTERY_CHRG_INPUT INPUT_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 +#define BATTERY_CHRG_PIN 5 // 充电检测引脚(P0_5) +#define BATTERY_CHRG_CONFIG CONFIG_PORT0 // 充电检测引脚挂载在IO扩展板的输入端口 + +#define TAG_GPIO_IO_EXPAND_DETECT "GPIO_IO_EXPAND_DETECT" // GPIO中断检测日志标签 +#define TAG_I2C_IO_EXPAND_TASK "I2C_IO_EXPAND_TASK" // I2CIO扩展板任务日志标签 + +// GPIO引脚定义 +#define GPIO_INPUT_PIN_SEL GPIO_NUM_10 // IO扩展板中断检测引脚 +// #define IO_EXPAND_RSTN GPIO_NUM_14 // IO扩展板复位引脚 + +/** + * @class IOExpansionBoard + * @brief I/O扩展板驱动类,封装AW9523BTQR芯片操作 + */ +class IOExpansionBoard +{ +public: + /** + * @brief 构造函数 + */ + IOExpansionBoard(); + + /** + * @brief 设置毫米波雷达指针 + * @param radar 毫米波雷达对象指针 + */ + void SetRadar(MillimeterWaveRadar* radar) { radar_ = radar; }; + + /** + * @brief 设置限位开关指针 + * @param photo_interrupter 限位开关对象指针 + */ + void setPhotoInterrupter(PhotoInterrupter* photo_interrupter) { photo_interrupter_ = photo_interrupter; }; + + /** + * @brief 设置电池监控器指针 + * @param battery_monitor 电池监控器对象指针 + */ + void setBatteryMonitor(AdcBatteryMonitor* battery_monitor) { battery_monitor_ = battery_monitor; }; + + /** + * @brief 析构函数 + */ + ~IOExpansionBoard(); + + /** + * @brief 初始化GPIO中断检测 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetGpioInit(gpio_num_t gpio_num); + + /** + * @brief 初始化IO扩展板复位引脚 + * @param gpio_num 复位引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t ResetIoExpand(); + + /** + * @brief 初始化I2C主机通信 + * @return esp_err_t 初始化结果 + */ + // esp_err_t IoExpansionI2cInit(); + + /** + * @brief 创建I2C数据读取任务 + */ + void CreateI2cReadTask(); + + /** + * @brief 启动I2C任务(整合初始化和任务创建) + * @return esp_err_t 操作结果 + */ + esp_err_t StartTask(); + + /** + * @brief 写寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandWriteReg(uint8_t reg_addr, uint8_t data); + + /** + * @brief 读寄存器数据 + * @param reg_addr 寄存器地址 + * @param data 要读出的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandReadReg(uint8_t reg_addr, uint8_t *data); + + /** + * @brief 读取输入端口0寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort0(uint8_t *data); + + /** + * @brief 读取输入端口1寄存器数据 + * @param data 输出参数,用于存储读取到的数据 + * @return esp_err_t 操作结果 + */ + esp_err_t ReadInputPort1(uint8_t *data); + + // /** + // * @brief 设置IO扩展板GPIO中断状态 通过读取Input_Port0 和 Input_Port1 寄存器可以清除中断 + // * @param reg_addr 寄存器地址 + // * @return esp_err_t 操作结果 + // */ + // esp_err_t SetInterruptStatus(bool status); + + /** + * @brief 配置IO扩展版上的IO口为输出还是输入,并且设置初始为高电平 + * @return esp_err_t 操作结果 + */ + esp_err_t IoexpandSetMode(); + + /** + * @brief 设置步进电机的细分模式 + * @param mode 模式:1/2/4/8/16/32/64 + * @return esp_err_t 操作结果 + */ + void SetStepperMicrostepMode(uint8_t mode, uint8_t OUTPUT_PORT_m0, uint8_t m0_pin, uint8_t OUTPUT_PORT_m1, uint8_t m1_pin); + + /** + * @brief 设置步进电机的方向 + * @param dir 方向 + * @param OUTPUT_PORT 输出端口 + * @param pin 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t StepperMotorSetDir(bool dir, uint8_t OUTPUT_PORT, uint8_t pin); + + /** + * @brief 设置步进电机的睡眠模式 + * @param 是否进入睡眠模式 + * @param 输出端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorSleepMode(bool mode, uint8_t OUTPUT_PORT, uint8_t nsleep_pin); + + /** + * @brief 设置步进电机的使能引脚状态 + * @param 使能状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetMotorEnableMode(bool enable, uint8_t OUTPUT_PORT, uint8_t enable_pin); + + /** + * @brief 设置LCD的BL引脚状态 + * @param BL状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdBlMode(bool mode, uint8_t OUTPUT_PORT, uint8_t bl_pin); + + /** + * @brief 设置 LED_EN 引脚电平(用于控制LCD背光/LED灯电源) + * @param mode true:开启(低电平), false:关闭(高电平) + */ + esp_err_t SetLedEnMode(bool mode); + + /** + * @brief 设置LCD的RST引脚状态 + * @param RST状态 + * @return esp_err_t 操作结果 + */ + esp_err_t SetLcdResetMode(bool mode, uint8_t OUTPUT_PORT, uint8_t rst_pin); + + /** + * @brief 设置PA控制引脚状态 + * @param mode PA_CTRL状态(true为高电平,false为低电平) + * @return esp_err_t 操作结果 + */ + esp_err_t SetPaCtrlMode(bool mode); + + /** + * @brief 设置雷达中断引脚 + * @param 毫米波雷达的中断引脚模式 + * @param 输入端口 + * @param 引脚号 + * @return esp_err_t 操作结果 + */ + esp_err_t SetRadarInterruptPin(bool mode, uint8_t INPUT_PORT, uint8_t interrupt_pin); + + /** + * @brief 计算Port0的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort0(); + + /** + * @brief 计算Port1的字节值 + * @return uint8_t 操作结果 + */ + uint8_t ByteCalculatePort1(); + + /** + * @brief 设置雷达中断回调函数 + * @param callback 雷达中断回调函数 + */ + void SetRadarCallback(RadarCallback callback) { radar_callback_ = callback; } + + /** + * @brief 设置光学传感器开始中断回调函数 + * @param callback 光学传感器开始中断回调函数 + */ + void SetPhotoInterrupterStartCallback(PhotoInterrupterStartCallback callback) { photo_start_callback_ = callback; } + + /** + * @brief 设置光学传感器结束中断回调函数 + * @param callback 光学传感器结束中断回调函数 + */ + void SetPhotoInterrupterEndCallback(PhotoInterrupterEndCallback callback) { photo_end_callback_ = callback; } + + /** + * @brief 设置电池充电中断回调函数 + * @param callback 电池充电中断回调函数 + */ + void SetBatteryChargingCallback(BatteryChargingCallback callback) { battery_charging_callback_ = callback; } + + /** + * @brief 设置电池充满中断回调函数 + * @param callback 电池充满中断回调函数 + */ + void SetBatteryFullCallback(BatteryFullCallback callback) { battery_full_callback_ = callback; } + + /** + * @brief 设置电池非充电中断回调函数 + * @param callback 电池非充电中断回调函数 + */ + void SetBatteryNotChargingCallback(BatteryNotChargingCallback callback) { battery_not_charging_callback_ = callback; } + + // 公共成员变量 + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + uint8_t device_addr_; // I2C设备地址 + +private: + // 静态成员函数 + MillimeterWaveRadar* radar_ = nullptr; // 毫米波雷达指针,用于触发回调 + PhotoInterrupter* photo_interrupter_ = nullptr; // 限位开关指针,用于触发回调 + AdcBatteryMonitor* battery_monitor_ = nullptr; // 电池监控器指针,用于触发回调 + static void IRAM_ATTR GpioIsrHandler(void* arg); // 中断处理函数 + static void I2cReadTask(void* pvParameters); // I2C数据读取任务函数 + + // 成员变量用于状态跟踪 + uint8_t last_port0_state_ = 0; // 上次检测到的Port0状态 + uint8_t last_port1_state_ = 0; // 上次检测到的Port1状态 + bool radar_last_state_ = false; // 雷达引脚上一次的状态 + bool battery_charging_last_state_ = false; // 电池充电状态上一次的状态 + bool battery_full_last_state_ = false; // 电池充满状态上一次的状态 + + // 回调函数成员变量 + RadarCallback radar_callback_ = nullptr; + PhotoInterrupterStartCallback photo_start_callback_ = nullptr; + PhotoInterrupterEndCallback photo_end_callback_ = nullptr; + BatteryChargingCallback battery_charging_callback_ = nullptr; + BatteryFullCallback battery_full_callback_ = nullptr; + BatteryNotChargingCallback battery_not_charging_callback_ = nullptr; + + // PhotoInterrupter* photo_interrupter_ = nullptr; // 光学传感器指针,用于触发回调 + // /** + // * @brief 分配静态任务所需的内存 + // * @return bool 分配是否成功 + // */ + // bool AllocateStaticTaskMemory(); + + // /** + // * @brief 释放静态任务分配的内存 + // */ + // void FreeStaticTaskMemory(); + + // 实例成员变量 + EventGroupHandle_t i2c_task_event_group_ = nullptr; // 事件组 + TaskHandle_t xI2CReadTaskHandle_ = nullptr; // 任务句柄 + bool is_initialized_ = false; // 初始化状态标记(新增) + gpio_num_t interrupt_gpio_ = GPIO_NUM_NC; // 中断引脚 + gpio_num_t reset_gpio_ = GPIO_NUM_NC; // 复位引脚 + + // StackType_t* pI2CReadTaskStackBuffer = nullptr; // 任务栈指针(PSRAM) + // StaticTask_t* pxI2CReadTaskBuffer = nullptr; // 任务控制块指针(内部RAM) +}; + +#endif \ No newline at end of file diff --git a/main/02_physical/boards/board_v1_1_18/power_management_config.h b/main/02_physical/boards/board_v1_1_18/power_management_config.h new file mode 100644 index 0000000..da609fb --- /dev/null +++ b/main/02_physical/boards/board_v1_1_18/power_management_config.h @@ -0,0 +1,178 @@ +/** + * @file power_management_config.h + * @brief 功耗管理配置和策略定义 + * @author Embedded Software Expert + * @date 2025年1月 + * + * 这个文件定义了功耗管理的配置参数、策略和优化建议, + * 为系统提供灵活的功耗管理方案。 + */ + +#ifndef POWER_MANAGEMENT_CONFIG_H +#define POWER_MANAGEMENT_CONFIG_H + +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp32/rom/rtc.h" + +/** + * @brief 功耗管理配置结构体 + */ +typedef struct { + // 睡眠模式配置 + uint64_t light_sleep_duration_us; // 轻度睡眠持续时间(微秒) + uint64_t deep_sleep_duration_us; // 深度睡眠持续时间(微秒) + + // 唤醒源配置 + bool enable_timer_wakeup; // 启用定时器唤醒 + bool enable_gpio_wakeup; // 启用GPIO唤醒 + bool enable_touch_wakeup; // 启用触摸唤醒 + gpio_num_t wakeup_gpio_num; // 唤醒GPIO编号 + int wakeup_level; // 唤醒电平(0或1) + + // 功耗优化配置 + bool disable_bt_in_sleep; // 睡眠时关闭蓝牙 + bool disable_wifi_in_sleep; // 睡眠时关闭WiFi + bool reduce_cpu_frequency; // 降低CPU频率 + uint32_t cpu_frequency_hz; // CPU频率(Hz) + + // 外设功耗配置 + bool power_down_sdcard; // 睡眠时关闭SD卡电源 + bool power_down_display; // 睡眠时关闭显示电源 + bool power_down_audio; // 睡眠时关闭音频电源 + bool power_down_motor; // 睡眠时关闭电机电源 + + // RTC内存配置 + bool preserve_rtc_memory; // 保留RTC内存 + size_t rtc_memory_size; // RTC内存大小 +} power_management_config_t; + +/** + * @brief 默认功耗管理配置 + */ +#define DEFAULT_POWER_CONFIG { \ + .light_sleep_duration_us = 1000000, /* 1秒 */ \ + .deep_sleep_duration_us = 30000000, /* 30秒 */ \ + .enable_timer_wakeup = true, /* 启用定时器唤醒 */ \ + .enable_gpio_wakeup = false, /* 不启用GPIO唤醒 */ \ + .enable_touch_wakeup = false, /* 不启用触摸唤醒 */ \ + .wakeup_gpio_num = GPIO_NUM_NC, /* 无GPIO唤醒 */ \ + .wakeup_level = 0, /* 低电平唤醒 */ \ + .disable_bt_in_sleep = true, /* 睡眠时关闭蓝牙 */ \ + .disable_wifi_in_sleep = true, /* 睡眠时关闭WiFi */ \ + .reduce_cpu_frequency = true, /* 降低CPU频率 */ \ + .cpu_frequency_hz = 80000000, /* 80MHz */ \ + .power_down_sdcard = true, /* 睡眠时关闭SD卡 */ \ + .power_down_display = true, /* 睡眠时关闭显示 */ \ + .power_down_audio = true, /* 睡眠时关闭音频 */ \ + .power_down_motor = true, /* 睡眠时关闭电机 */ \ + .preserve_rtc_memory = true, /* 保留RTC内存 */ \ + .rtc_memory_size = 4096, /* 4KB RTC内存 */ \ +} + +/** + * @brief 各功耗模式的详细配置 + */ +static const power_management_config_t idle_mode_config = { + .light_sleep_duration_us = 500000, // 500ms轻度睡眠 + .deep_sleep_duration_us = 0, // 不使用深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, // 按键唤醒 + .wakeup_level = 0, + .disable_bt_in_sleep = false, // 保持蓝牙连接 + .disable_wifi_in_sleep = false, // 保持WiFi连接 + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, // 40MHz + .power_down_sdcard = false, // 保持SD卡(可能有后台任务) + .power_down_display = true, // 关闭显示背光 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 2048, +}; + +static const power_management_config_t light_sleep_config = { + .light_sleep_duration_us = 0, // 直接进入轻度睡眠 + .deep_sleep_duration_us = 0, + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 40000000, + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 4096, +}; + +static const power_management_config_t deep_sleep_config = { + .light_sleep_duration_us = 0, + .deep_sleep_duration_us = 60000000, // 60秒深度睡眠 + .enable_timer_wakeup = true, + .enable_gpio_wakeup = true, + .wakeup_gpio_num = GPIO_NUM_0, + .wakeup_level = 0, + .disable_bt_in_sleep = true, // 关闭蓝牙 + .disable_wifi_in_sleep = true, // 关闭WiFi + .reduce_cpu_frequency = true, + .cpu_frequency_hz = 10000000, // 10MHz + .power_down_sdcard = true, // 关闭SD卡 + .power_down_display = true, // 关闭显示 + .power_down_audio = true, // 关闭音频 + .power_down_motor = true, // 关闭电机 + .preserve_rtc_memory = true, + .rtc_memory_size = 8192, +}; + +/** + * @brief 功耗统计和监控结构体 + */ +typedef struct { + uint64_t total_sleep_time_us; // 总睡眠时间 + uint64_t total_wake_time_us; // 总唤醒时间 + uint32_t sleep_count; // 睡眠次数 + uint32_t wake_count; // 唤醒次数 + uint32_t average_sleep_duration_us; // 平均睡眠时长 + uint32_t average_wake_duration_us; // 平均唤醒时长 + float estimated_power_saving_percent; // 估计的功耗节省百分比 +} power_statistics_t; + +/** + * @brief 功耗优化建议 + * + * 基于硬件特性和使用模式,提供功耗优化建议 + */ +typedef enum { + OPTIMIZATION_NONE = 0, + OPTIMIZATION_REDUCE_CPU_FREQ, // 降低CPU频率 + OPTIMIZATION_DISABLE_UNUSED_PERIPHERALS, // 关闭未使用的外设 + OPTIMIZATION_USE_LIGHT_SLEEP, // 使用轻度睡眠 + OPTIMIZATION_USE_DEEP_SLEEP, // 使用深度睡眠 + OPTIMIZATION_OPTIMIZE_GPIO_STATES, // 优化GPIO状态 + OPTIMIZATION_USE_RTC_MEMORY, // 使用RTC内存 + OPTIMIZATION_BATCH_OPERATIONS, // 批量操作 +} power_optimization_t; + +/** + * @brief 功耗管理事件回调 + */ +typedef void (*power_event_callback_t)(power_mode_t old_mode, power_mode_t new_mode, void* user_data); + +/** + * @brief 功耗管理事件 + */ +typedef enum { + POWER_EVENT_MODE_CHANGE, // 功耗模式改变 + POWER_EVENT_SLEEP_START, // 睡眠开始 + POWER_EVENT_SLEEP_END, // 睡眠结束 + POWER_EVENT_WAKEUP, // 系统唤醒 + POWER_EVENT_OPTIMIZATION_TRIGGER, // 功耗优化触发 +} power_event_t; + +#endif // POWER_MANAGEMENT_CONFIG_H \ No newline at end of file diff --git a/main/02_physical/boards/config.h b/main/02_physical/boards/config.h new file mode 100644 index 0000000..19468d2 --- /dev/null +++ b/main/02_physical/boards/config.h @@ -0,0 +1,26 @@ +#ifndef BOARD_CONFIG_H +#define BOARD_CONFIG_H + +#include "sdkconfig.h" + +// 根据menuconfig的配置选择不同版本的config.h文件 +#ifdef CONFIG_BOARD_VERSION_V1_1_11 + #include "boards/board_v1_1_11/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_12) + #include "boards/board_v1_1_12/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_13) + #include "boards/board_v1_1_13/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_14) + #include "boards/board_v1_1_14/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_16) + #include "boards/board_v1_1_16/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_17) + #include "boards/board_v1_1_17/config.h" +#elif defined(CONFIG_BOARD_VERSION_V1_1_18) + #include "boards/board_v1_1_18/config.h" +#else + // 默认使用v1.1.11版本,避免编译错误 + #include "boards/board_v1_1_13/config.h" +#endif + +#endif // BOARD_CONFIG_H \ No newline at end of file diff --git a/main/02_physical/boards/module.cmake b/main/02_physical/boards/module.cmake new file mode 100644 index 0000000..f360aee --- /dev/null +++ b/main/02_physical/boards/module.cmake @@ -0,0 +1,35 @@ +# 根据 menuconfig 中选择的板卡配置,自动匹配 boards/board_v* 目录 +# 这样新增板卡时,只要: +# 1. 增加对应的 Kconfig 项,例如 CONFIG_BOARD_VERSION_V1_1_18 +# 2. 新增目录 02_physical/boards/board_v1_1_18/ +# 就不需要再修改 main/CMakeLists.txt + +set(BOARD_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MAIN_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") +set(SELECTED_BOARD_DIR "") + +file(GLOB BOARD_CANDIDATE_DIRS RELATIVE "${BOARD_ROOT_DIR}" "${BOARD_ROOT_DIR}/board_v*") + +foreach(board_dir ${BOARD_CANDIDATE_DIRS}) + if(IS_DIRECTORY "${BOARD_ROOT_DIR}/${board_dir}") + string(REGEX REPLACE "^board_" "" board_suffix "${board_dir}") + string(TOUPPER "${board_suffix}" board_suffix_upper) + set(board_config_var "CONFIG_BOARD_VERSION_${board_suffix_upper}") + + if(DEFINED ${board_config_var} AND ${board_config_var}) + set(SELECTED_BOARD_DIR "02_physical/boards/${board_dir}") + break() + endif() + endif() +endforeach() + +if(NOT SELECTED_BOARD_DIR) + message(FATAL_ERROR "Please select a board version in menuconfig") +endif() + +target_sources(${COMPONENT_LIB} PRIVATE "${SELECTED_BOARD_DIR}/board_sweekar.cc") +target_sources(${COMPONENT_LIB} PRIVATE "${SELECTED_BOARD_DIR}/io_expansion_board.cc") +target_include_directories(${COMPONENT_LIB} PRIVATE "${SELECTED_BOARD_DIR}") + +# 添加到 main 目录,使类似 "boards/config.h" 的包含路径保持兼容 +target_include_directories(${COMPONENT_LIB} PRIVATE "${MAIN_ROOT_DIR}") diff --git a/main/04_hal/audio_codec/audio_codec.cc b/main/04_hal/audio_codec/audio_codec.cc new file mode 100644 index 0000000..66b5e3f --- /dev/null +++ b/main/04_hal/audio_codec/audio_codec.cc @@ -0,0 +1,247 @@ +#include "audio_codec.h" +#include "esp_log.h" +#include "es7210.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +static const char *TAG = "AudioCodec"; + +// 静态成员初始化 +AudioCodec* AudioCodec::instance_ = nullptr; +SemaphoreHandle_t AudioCodec::mutex_ = nullptr; + +AudioCodec::AudioCodec() + : board_handle_(nullptr), + is_initialized_(false), + current_mode_(AUDIO_CODEC_MODE_DECODE), + current_volume_(100) +{ +} + +AudioCodec::~AudioCodec() { + Deinitialize(); +} + +AudioCodec* AudioCodec::GetInstance() { + // 双重检查锁定模式确保线程安全 + if (instance_ == nullptr) { + if (mutex_ == nullptr) { + mutex_ = xSemaphoreCreateMutex(); + } + + if (xSemaphoreTake(mutex_, portMAX_DELAY) == pdTRUE) { + if (instance_ == nullptr) { + instance_ = new AudioCodec(); + } + xSemaphoreGive(mutex_); + } + } + return instance_; +} + +esp_err_t AudioCodec::Initialize() { + if (is_initialized_) { + ESP_LOGW(TAG, "AudioCodec already initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Initializing AudioCodec"); + + esp_err_t ret = InitializeBoard(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize audio board: %s", esp_err_to_name(ret)); + return ret; + } + + // 先设置初始化标志,然后启动编解码器 + is_initialized_ = true; + + // 默认启用双向模式,同时支持播放和录音 + ret = StartCodec(AUDIO_CODEC_MODE_BOTH); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start codec in both mode: %s", esp_err_to_name(ret)); + is_initialized_ = false; // 如果启动失败,重置标志 + return ret; + } + + ESP_LOGI(TAG, "AudioCodec initialized successfully with both encode and decode enabled"); + return ESP_OK; +} + +esp_err_t AudioCodec::InitializeBoard() { + // 初始化音频板 + board_handle_ = audio_board_init(); + if (board_handle_ == nullptr) { + ESP_LOGE(TAG, "Failed to initialize audio board"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Audio board initialized successfully"); + return ESP_OK; +} + +esp_err_t AudioCodec::StartCodec(audio_codec_mode_t mode) { + // 检查音频板是否已初始化,而不是检查整体初始化状态 + if (board_handle_ == nullptr) { + ESP_LOGE(TAG, "Audio board not initialized"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = ESP_OK; + + switch (mode) { + case AUDIO_CODEC_MODE_DECODE: + ESP_LOGI(TAG, "Starting codec in decode mode"); + ret = audio_hal_ctrl_codec(board_handle_->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START); + break; + + case AUDIO_CODEC_MODE_ENCODE: + ESP_LOGI(TAG, "Starting codec in encode mode"); + ret = audio_hal_ctrl_codec(board_handle_->audio_hal, AUDIO_HAL_CODEC_MODE_ENCODE, AUDIO_HAL_CTRL_START); + if (ret == ESP_OK) { + // 设置ADC音量 + ret = audio_hal_set_volume(board_handle_->adc_hal, 100); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to set ADC volume: %s", esp_err_to_name(ret)); + } + + // 设置ES7210麦克风增益 + ret = es7210_adc_set_gain((es7210_input_mics_t)(ES7210_INPUT_MIC1 | ES7210_INPUT_MIC2 | ES7210_INPUT_MIC3), GAIN_37_5DB); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to set ES7210 gain: %s", esp_err_to_name(ret)); + } else { + ESP_LOGI(TAG, "ES7210 gain set to 37.5dB"); + } + } + break; + + case AUDIO_CODEC_MODE_BOTH: + ESP_LOGI(TAG, "Starting codec in both modes"); + ret = audio_hal_ctrl_codec(board_handle_->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START); + if (ret == ESP_OK) { + // 同时设置播放和录音参数 + audio_hal_set_volume(board_handle_->adc_hal, 100); + es7210_adc_set_gain((es7210_input_mics_t)(ES7210_INPUT_MIC1 | ES7210_INPUT_MIC2 | ES7210_INPUT_MIC3), GAIN_37_5DB); + } + break; + + default: + ESP_LOGE(TAG, "Invalid codec mode: %d", mode); + return ESP_ERR_INVALID_ARG; + } + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start codec: %s", esp_err_to_name(ret)); + return ret; + } + + current_mode_ = mode; + + // 设置初始音量(仅在播放模式下) + if (mode == AUDIO_CODEC_MODE_DECODE || mode == AUDIO_CODEC_MODE_BOTH) { + SetVolume(current_volume_); + } + + ESP_LOGI(TAG, "Codec started successfully in mode %d", mode); + return ESP_OK; +} + +esp_err_t AudioCodec::StopCodec() { + if (!is_initialized_) { + ESP_LOGW(TAG, "AudioCodec not initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Stopping codec"); + // 修复:直接使用audio_hal_codec_mode_t类型 + esp_err_t ret = audio_hal_ctrl_codec(board_handle_->audio_hal, (audio_hal_codec_mode_t)current_mode_, AUDIO_HAL_CTRL_STOP); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to stop codec: %s", esp_err_to_name(ret)); + } + + return ret; +} + +esp_err_t AudioCodec::SetVolume(uint8_t volume) { + if (!is_initialized_) { + ESP_LOGE(TAG, "AudioCodec not initialized"); + return ESP_ERR_INVALID_STATE; + } + + if (volume > 100) { + ESP_LOGW(TAG, "Volume %d exceeds maximum, setting to 100", volume); + volume = 100; + } + + // 转换为HAL音量范围 (0-63) + int hal_volume = (volume * 63) / 100; + esp_err_t ret = audio_hal_set_volume(board_handle_->audio_hal, hal_volume); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set volume: %s", esp_err_to_name(ret)); + return ret; + } + + current_volume_ = volume; + ESP_LOGI(TAG, "Volume set to %d%% (HAL volume: %d)", volume, hal_volume); + return ESP_OK; +} + +esp_err_t AudioCodec::GetVolume(uint8_t* volume) { + if (!volume) { + return ESP_ERR_INVALID_ARG; + } + + *volume = current_volume_; + return ESP_OK; +} + +esp_err_t AudioCodec::SetAdcVolume(uint8_t volume) { + if (!is_initialized_) { + ESP_LOGE(TAG, "AudioCodec not initialized"); + return ESP_ERR_INVALID_STATE; + } + + if (volume > 100) { + volume = 100; + } + + esp_err_t ret = audio_hal_set_volume(board_handle_->adc_hal, volume); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set ADC volume: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "ADC volume set to %d", volume); + return ESP_OK; +} + +audio_board_handle_t AudioCodec::GetBoardHandle() { + return board_handle_; +} + +bool AudioCodec::IsInitialized() { + return is_initialized_; +} + +esp_err_t AudioCodec::Deinitialize() { + if (!is_initialized_) { + return ESP_OK; + } + + ESP_LOGI(TAG, "Deinitializing AudioCodec"); + + StopCodec(); + Cleanup(); + + is_initialized_ = false; + ESP_LOGI(TAG, "AudioCodec deinitialized"); + return ESP_OK; +} + +void AudioCodec::Cleanup() { + if (board_handle_) { + audio_board_deinit(board_handle_); + board_handle_ = nullptr; + } +} \ No newline at end of file diff --git a/main/04_hal/audio_codec/audio_codec.h b/main/04_hal/audio_codec/audio_codec.h new file mode 100644 index 0000000..af45c86 --- /dev/null +++ b/main/04_hal/audio_codec/audio_codec.h @@ -0,0 +1,74 @@ +#ifndef AUDIO_CODEC_H +#define AUDIO_CODEC_H + +#include "esp_err.h" +#include "audio_hal.h" +#include "board.h" +#include "esp_peripherals.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// 使用audio_hal中已定义的模式类型,避免重复定义 +typedef audio_hal_codec_mode_t audio_codec_mode_t; + +// 为了代码可读性,定义别名 +#define AUDIO_CODEC_MODE_DECODE AUDIO_HAL_CODEC_MODE_DECODE +#define AUDIO_CODEC_MODE_ENCODE AUDIO_HAL_CODEC_MODE_ENCODE +#define AUDIO_CODEC_MODE_BOTH AUDIO_HAL_CODEC_MODE_BOTH + +class AudioCodec { +public: + // 获取单例实例 + static AudioCodec* GetInstance(); + + // 初始化音频编解码器(默认启用双向模式) + esp_err_t Initialize(); + + // 音量控制 + esp_err_t SetVolume(uint8_t volume); + esp_err_t GetVolume(uint8_t* volume); + + // ADC音量控制(录音) + esp_err_t SetAdcVolume(uint8_t volume); + + // 获取音频板句柄 + audio_board_handle_t GetBoardHandle(); + + // 检查是否已初始化 + bool IsInitialized(); + + // 反初始化 + esp_err_t Deinitialize(); + +private: + AudioCodec(); // 私有构造函数 + ~AudioCodec(); + + // 禁止拷贝构造和赋值 + AudioCodec(const AudioCodec&) = delete; + AudioCodec& operator=(const AudioCodec&) = delete; + + static AudioCodec* instance_; + static SemaphoreHandle_t mutex_; + + audio_board_handle_t board_handle_; + bool is_initialized_; + audio_codec_mode_t current_mode_; + uint8_t current_volume_; + + esp_err_t InitializeBoard(); + void Cleanup(); + esp_err_t StartCodec(audio_codec_mode_t mode); + esp_err_t StopCodec(); +}; + +#ifdef __cplusplus +} +#endif + +#endif // AUDIO_CODEC_H \ No newline at end of file diff --git a/main/04_hal/audio_codec/module.cmake b/main/04_hal/audio_codec/module.cmake new file mode 100644 index 0000000..a60c841 --- /dev/null +++ b/main/04_hal/audio_codec/module.cmake @@ -0,0 +1,16 @@ +# main/04_hal/audio_codec/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_HAL_AUDIO_CODEC AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] audio_codec module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "04_hal/audio_codec/audio_codec.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "04_hal/audio_codec" +) \ No newline at end of file diff --git a/main/04_hal/base_board_hal/base_board_hal.h b/main/04_hal/base_board_hal/base_board_hal.h new file mode 100644 index 0000000..e7244ed --- /dev/null +++ b/main/04_hal/base_board_hal/base_board_hal.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include "esp_err.h" + +/** + * @brief Base Board Hardware Abstraction Layer + * Abstracts the communication transport (UART vs I2C) and sensor reading. + */ +class BaseBoardHal +{ +public: + virtual ~BaseBoardHal() = default; + + /** + * @brief Initialize the hardware interface + */ + virtual esp_err_t Init() = 0; + + /** + * @brief Send raw data to the base board + * @param data Pointer to data buffer + * @param len Length of data + */ + virtual esp_err_t Send(const uint8_t *data, size_t len) = 0; + + /** + * @brief Read raw data from the base board + * @param data Pointer to data buffer + * @param len Length of data to read + */ + virtual esp_err_t Read(uint8_t *data, size_t len) = 0; + + /** + * @brief Get the current angle from Hall sensor (or via query) + * @param[out] angle Output angle in degrees (0.0 - 360.0) + * @return ESP_OK on success + */ + virtual esp_err_t GetAngle(float *angle) = 0; +}; diff --git a/main/04_hal/base_board_hal/base_board_hal_i2c.h b/main/04_hal/base_board_hal/base_board_hal_i2c.h new file mode 100644 index 0000000..6e55c30 --- /dev/null +++ b/main/04_hal/base_board_hal/base_board_hal_i2c.h @@ -0,0 +1,65 @@ +#pragma once + +#include "base_board_hal.h" +#include "i2c_bus.h" +#include "esp_log.h" + +class BaseBoardHalI2c : public BaseBoardHal +{ +public: + static constexpr uint8_t kBaseSlaveAddr = 0x22; + // TMAG5273 default address often 0x35 or defined by config + static constexpr uint8_t kHallSensorAddr = 0x35; + + BaseBoardHalI2c(i2c_bus_handle_t bus_handle) + : bus_handle_(bus_handle) {} + + virtual ~BaseBoardHalI2c() + { + // i2c_bus handles device management internally if used via i2c_bus_create + // But here we just use the bus handle directly. + } + + virtual esp_err_t Init() override + { + if (!bus_handle_) return ESP_ERR_INVALID_STATE; + + // Check if device is alive + esp_err_t err = i2c_bus_probe_addr(bus_handle_, kBaseSlaveAddr << 1); + if (err != ESP_OK) { + ESP_LOGW("BaseBoardHalI2c", "Base board not found at 0x%02X", kBaseSlaveAddr); + // Don't fail init, maybe it's sleeping + } else { + ESP_LOGI("BaseBoardHalI2c", "Base board found at 0x%02X", kBaseSlaveAddr); + } + + return ESP_OK; + } + + virtual esp_err_t Send(const uint8_t *data, size_t len) override + { + if (!bus_handle_) return ESP_ERR_INVALID_STATE; + + // i2c_bus_write_bytes expects: bus, addr, reg_addr, reg_len, data, datalen + // The data pointer in i2c_bus_write_bytes is (uint8_t *), not (const uint8_t *) + // This is a common issue in legacy C APIs. We have to cast away constness. + + return i2c_bus_write_bytes(bus_handle_, kBaseSlaveAddr << 1, NULL, 0, (uint8_t*)data, len); + } + + virtual esp_err_t Read(uint8_t *data, size_t len) override + { + if (!bus_handle_) return ESP_ERR_INVALID_STATE; + + return i2c_bus_read_bytes(bus_handle_, kBaseSlaveAddr << 1, NULL, 0, data, len); + } + + virtual esp_err_t GetAngle(float *angle) override + { + // TODO: Implement TMAG5273 read logic using i2c_bus_read_bytes + return ESP_ERR_NOT_SUPPORTED; + } + +private: + i2c_bus_handle_t bus_handle_; +}; diff --git a/main/04_hal/base_board_hal/base_board_hal_uart.h b/main/04_hal/base_board_hal/base_board_hal_uart.h new file mode 100644 index 0000000..63f715e --- /dev/null +++ b/main/04_hal/base_board_hal/base_board_hal_uart.h @@ -0,0 +1,45 @@ +#pragma once + +#include "base_board_hal.h" +#include "driver/uart.h" + +class BaseBoardHalUart : public BaseBoardHal +{ +public: + BaseBoardHalUart(uart_port_t port) : port_(port) {} + virtual ~BaseBoardHalUart() = default; + + virtual esp_err_t Init() override + { + // Assuming UART is already initialized by board.cc or similar + return ESP_OK; + } + + virtual esp_err_t Send(const uint8_t *data, size_t len) override + { + if (port_ >= UART_NUM_MAX) { + return ESP_ERR_INVALID_STATE; + } + int written = uart_write_bytes(port_, (const char *)data, len); + return (written == (int)len) ? ESP_OK : ESP_FAIL; + } + + virtual esp_err_t Read(uint8_t *data, size_t len) override + { + if (port_ >= UART_NUM_MAX) { + return ESP_ERR_INVALID_STATE; + } + int read = uart_read_bytes(port_, data, len, pdMS_TO_TICKS(100)); + return (read == (int)len) ? ESP_OK : ESP_FAIL; + } + + virtual esp_err_t GetAngle(float *angle) override + { + // TODO: Implement angle reading via UART (query command or report parsing) + // For now, return not supported or cached value if we had one + return ESP_ERR_NOT_SUPPORTED; + } + +private: + uart_port_t port_; +}; diff --git a/main/04_hal/base_board_hal/module.cmake b/main/04_hal/base_board_hal/module.cmake new file mode 100644 index 0000000..8b135ff --- /dev/null +++ b/main/04_hal/base_board_hal/module.cmake @@ -0,0 +1,13 @@ +# main/04_hal/base_board_hal/module.cmake + +# 注意:此文件夹可能没有 cpp 源文件,仅有头文件。如果有源文件,请追加到 MODULE_SRCS。 + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_HAL_BASE_BOARD_HAL AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] base_board_hal module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_INCLUDE_DIRS + "04_hal/base_board_hal" +) \ No newline at end of file diff --git a/main/04_hal/stepper_driver/aw8646.cc b/main/04_hal/stepper_driver/aw8646.cc new file mode 100644 index 0000000..cdd98ed --- /dev/null +++ b/main/04_hal/stepper_driver/aw8646.cc @@ -0,0 +1,213 @@ +#include "aw8646.h" +#include "config.h" +#include "esp_log.h" +#include "io_expansion_board.h" + +// Define AW8646 specific pins for Board v1.1.16 +// P0_0 -> DRV_T0 +// P0_6 -> DRV_T1 +// Only if NFC_RST_PIN is defined (implying it's moved to GPIO, freeing up P0_0) +#ifdef NFC_RST_PIN +#define AW8646_USE_CURRENT_CONTROL +#define AW8646_T0_PIN 0 +#define AW8646_T0_PORT OUTPUT_PORT0 +#define AW8646_T1_PIN 6 +#define AW8646_T1_PORT OUTPUT_PORT0 +#endif + +Aw8646Driver::Aw8646Driver(IOExpansionBoard &ioExp) : io_expansion_(ioExp) +{ +} + +esp_err_t Aw8646Driver::Init() +{ + esp_err_t err = ESP_OK; + + // 1. 默认处于休眠和禁用状态,防止发热! + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_SLEEP, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set sleep mode: 0x%x", err); + return err; + } + + // 2. Disable Enable Pin (High = Disable for AW8646) + err = io_expansion_.SetMotorEnableMode( + true, // High -> Disable + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set enable mode: 0x%x", err); + return err; + } + +#ifdef AW8646_USE_CURRENT_CONTROL + // 3. Initialize T0 and T1 pins for Current Control + // Default to Low (0, 0) for now. + uint8_t output0_val = 0; + err = io_expansion_.IoexpandReadReg(OUTPUT_PORT0, &output0_val); + if (err == ESP_OK) { + output0_val &= ~(1 << AW8646_T0_PIN); // Set T0 Low + output0_val &= ~(1 << AW8646_T1_PIN); // Set T1 Low + err = io_expansion_.IoexpandWriteReg(OUTPUT_PORT0, output0_val); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set T0/T1: 0x%x", err); + } + } else { + ESP_LOGE(TAG, "Failed to read port 0: 0x%x", err); + } +#endif + + // 4. Set Microsteps (Default to 1/8 step) + err = SetMicrosteps(8); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set microsteps: 0x%x", err); + return err; + } + + // 5. Set Default Direction + err = SetDirection(true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set direction: 0x%x", err); + return err; + } + + ESP_LOGI(TAG, "AW8646 Driver Initialized (EN Logic Inverted, T0/T1 Configured)"); + return ESP_OK; +} + +esp_err_t Aw8646Driver::Enable(bool enable) +{ + esp_err_t err = ESP_OK; + if (enable) + { + ESP_LOGD(TAG, "Enabling AW8646 (sleep + enable=Low)"); + + // Ensure Sleep is High (Work) + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_WORK, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + if (err != ESP_OK) return err; + + // Enable Pin: Low to Enable + err = io_expansion_.SetMotorEnableMode( + false, // Low -> Enable + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + } + else + { + ESP_LOGD(TAG, "Disabling AW8646 (disable=High + sleep)"); + + // Enable Pin: High to Disable + err = io_expansion_.SetMotorEnableMode( + true, // High -> Disable + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + if (err != ESP_OK) return err; + + // Sleep Pin: Low to Sleep + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_SLEEP, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + } + return err; +} + +esp_err_t Aw8646Driver::SetDirection(bool dir) +{ + uint8_t mode = dir ? STEPPER_MOTOR_RIGHT : STEPPER_MOTOR_LEFT; + + return io_expansion_.StepperMotorSetDir( + mode, + STEPPER_MOTOR_DIR_PORT, + STEPPER_MOTOR_DIR_PIN + ); +} + +esp_err_t Aw8646Driver::Sleep(bool sleep) +{ + uint8_t mode = sleep ? STEPPER_MOTOR_SLEEP : STEPPER_MOTOR_WORK; + return io_expansion_.SetMotorSleepMode( + mode, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); +} + +esp_err_t Aw8646Driver::SetTriLevelPin(uint8_t port_reg, uint8_t config_reg, uint8_t pin, int state) +{ + uint8_t val; + esp_err_t err = ESP_OK; + + if (state == 2) { // Hi-Z -> Input + // Set as Input + err = io_expansion_.IoexpandReadReg(config_reg, &val); + if (err != ESP_OK) return err; + val |= (1 << pin); // Set Input + err = io_expansion_.IoexpandWriteReg(config_reg, val); + } else { // 0 or 1 -> Output + // Set as Output + err = io_expansion_.IoexpandReadReg(config_reg, &val); + if (err != ESP_OK) return err; + val &= ~(1 << pin); // Set Output + err = io_expansion_.IoexpandWriteReg(config_reg, val); + if (err != ESP_OK) return err; + + // Set Level + err = io_expansion_.IoexpandReadReg(port_reg, &val); + if (err != ESP_OK) return err; + + if (state == 1) val |= (1 << pin); + else val &= ~(1 << pin); + + err = io_expansion_.IoexpandWriteReg(port_reg, val); + } + return err; +} + +esp_err_t Aw8646Driver::SetMicrosteps(int microsteps) +{ + // AW8646 Microstep Table (Rising Edge Only) + // 1 (Full): MODE1=0, MODE0=0 + // 2 (1/2): MODE1=0, MODE0=Z (Hi-Z) + // 4 (1/4): MODE1=0, MODE0=1 + // 8 (1/8): MODE1=Z, MODE0=0 + // 16 (1/16): MODE1=Z, MODE0=1 + // 32 (1/32): MODE1=1, MODE0=Z + + int m1 = 0; + int m0 = 0; + + switch(microsteps) { + case 1: m1 = 0; m0 = 0; break; + case 2: m1 = 0; m0 = 2; break; // Z + case 4: m1 = 0; m0 = 1; break; + case 8: m1 = 2; m0 = 0; break; // Z + case 16: m1 = 2; m0 = 1; break; // Z + case 32: m1 = 1; m0 = 2; break; // Z + default: + ESP_LOGW(TAG, "Unsupported microsteps: %d, defaulting to 1/8", microsteps); + m1 = 2; m0 = 0; + break; + } + + ESP_LOGI(TAG, "Setting Microsteps to 1/%d (M1=%d, M0=%d)", microsteps, m1, m0); + + esp_err_t err; + err = SetTriLevelPin(STEPPER_MOTOR_M1_MADE_PORT, STEPPER_MOTOR_M1_CONFIG, STEPPER_MOTOR_M1_PIN, m1); + if (err != ESP_OK) return err; + + err = SetTriLevelPin(STEPPER_MOTOR_M0_MADE_PORT, STEPPER_MOTOR_M0_CONFIG, STEPPER_MOTOR_M0_PIN, m0); + return err; +} diff --git a/main/04_hal/stepper_driver/aw8646.h b/main/04_hal/stepper_driver/aw8646.h new file mode 100644 index 0000000..52fd2d6 --- /dev/null +++ b/main/04_hal/stepper_driver/aw8646.h @@ -0,0 +1,27 @@ +#ifndef AW8646_DRIVER_H +#define AW8646_DRIVER_H + +#include "stepper_driver.h" +#include "io_expansion_board.h" +#include "esp_log.h" + +class Aw8646Driver : public StepperDriver +{ +public: + explicit Aw8646Driver(IOExpansionBoard &ioExp); + virtual ~Aw8646Driver() = default; + + esp_err_t Init() override; + esp_err_t Enable(bool enable) override; + esp_err_t SetDirection(bool dir) override; + esp_err_t Sleep(bool sleep) override; + esp_err_t SetMicrosteps(int microsteps) override; + +private: + esp_err_t SetTriLevelPin(uint8_t port_reg, uint8_t config_reg, uint8_t pin, int state); + + IOExpansionBoard &io_expansion_; + const char* TAG = "Aw8646Driver"; +}; + +#endif // AW8646_DRIVER_H diff --git a/main/04_hal/stepper_driver/aw8646.pdf b/main/04_hal/stepper_driver/aw8646.pdf new file mode 100644 index 0000000..c1c7464 Binary files /dev/null and b/main/04_hal/stepper_driver/aw8646.pdf differ diff --git a/main/04_hal/stepper_driver/drv8428.cc b/main/04_hal/stepper_driver/drv8428.cc new file mode 100644 index 0000000..4cfa43f --- /dev/null +++ b/main/04_hal/stepper_driver/drv8428.cc @@ -0,0 +1,147 @@ +#include "drv8428.h" +#include "config.h" + +Drv8428Driver::Drv8428Driver(IOExpansionBoard &ioExp) : io_expansion_(ioExp) +{ +} + +esp_err_t Drv8428Driver::Init() +{ + esp_err_t err = ESP_OK; + + // 1. Set Sleep Mode to WORK (wake up initially to configure?) + // Actually BoardSweekar::StepperMotorInit sets it to WORK then ENABLE_DISABLE. + // Let's follow that. + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_WORK, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set sleep mode: 0x%x", err); + return err; + } + + // 2. Disable Enable Pin + err = io_expansion_.SetMotorEnableMode( + STEPPER_MOTOR_ENABLE_DISABLE, + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set enable mode: 0x%x", err); + return err; + } + + // 3. Set Microsteps (Defaulting to 1/8 as per original code which passed 4? need to verify) + // BoardSweekar passed 4. + // "io_expansion_.SetStepperMicrostepMode(4, ...)" + // Let's assume 4 maps to 1/8 or whatever. + err = SetMicrosteps(4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set microsteps: 0x%x", err); + return err; + } + + // 4. Set Default Direction + err = SetDirection(true); // Default to "UP" or whatever true means + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set direction: 0x%x", err); + return err; + } + + ESP_LOGI(TAG, "DRV8428 Driver Initialized"); + return ESP_OK; +} + +esp_err_t Drv8428Driver::Enable(bool enable) +{ + esp_err_t err = ESP_OK; + if (enable) + { + ESP_LOGD(TAG, "Enabling stepper motor (sleep + enable)"); + + // 1. Set sleep mode to WORK + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_WORK, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + if (err != ESP_OK) return err; + + // 2. Set enable mode to ENABLE + err = io_expansion_.SetMotorEnableMode( + STEPPER_MOTOR_ENABLE_ENABLE, + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + } + else + { + ESP_LOGD(TAG, "Disabling stepper motor (disable + sleep)"); + + // 1. Disable enable pin + err = io_expansion_.SetMotorEnableMode( + STEPPER_MOTOR_ENABLE_DISABLE, + STEPPER_MOTOR_ENABLE_PORT, + STEPPER_MOTOR_ENABLE_PIN + ); + if (err != ESP_OK) return err; + + // 2. Set sleep mode to SLEEP + err = io_expansion_.SetMotorSleepMode( + STEPPER_MOTOR_SLEEP, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); + } + return err; +} + +esp_err_t Drv8428Driver::SetDirection(bool dir) +{ + // dir: true=UP, false=DOWN (based on original StepperMotor::SetDir logic) + // Original: dir ? STEPPER_MOTOR_RIGHT : STEPPER_MOTOR_LEFT + // Wait, StepperMotor::SetDir used STEPPER_MOTOR_RIGHT for true. + // STEPPER_MOTOR_RIGHT is a macro, likely 1 or 0. + + uint8_t mode = dir ? STEPPER_MOTOR_RIGHT : STEPPER_MOTOR_LEFT; + + return io_expansion_.StepperMotorSetDir( + mode, + STEPPER_MOTOR_DIR_PORT, + STEPPER_MOTOR_DIR_PIN + ); +} + +esp_err_t Drv8428Driver::Sleep(bool sleep) +{ + // This is explicitly setting sleep mode, might be redundant with Enable(false) + // but useful if we want to sleep without disabling or vice versa if supported. + // For DRV8428, Enable(false) sets Sleep to SLEEP. + + uint8_t mode = sleep ? STEPPER_MOTOR_SLEEP : STEPPER_MOTOR_WORK; + return io_expansion_.SetMotorSleepMode( + mode, + STEPPER_MOTOR_SLEEP_PORT, + STEPPER_MOTOR_SLEEP_PIN + ); +} + +esp_err_t Drv8428Driver::SetMicrosteps(int microsteps) +{ + // Original code used hardcoded ports for microsteps. + // io_expansion_.SetStepperMicrostepMode(4, STEPPER_MOTOR_M0_MADE_PORT, STEPPER_MOTOR_M0_PIN, + // STEPPER_MOTOR_M1_MADE_PORT, STEPPER_MOTOR_M1_PIN); + + // We can just pass the value 'microsteps' if it corresponds to the index expected by SetStepperMicrostepMode. + // Or we might need a mapping. + // Assuming 'microsteps' passed here is the raw value/index for now. + + io_expansion_.SetStepperMicrostepMode( + (uint8_t)microsteps, + STEPPER_MOTOR_M0_MADE_PORT, STEPPER_MOTOR_M0_PIN, + STEPPER_MOTOR_M1_MADE_PORT, STEPPER_MOTOR_M1_PIN + ); + return ESP_OK; +} diff --git a/main/04_hal/stepper_driver/drv8428.h b/main/04_hal/stepper_driver/drv8428.h new file mode 100644 index 0000000..71e6c60 --- /dev/null +++ b/main/04_hal/stepper_driver/drv8428.h @@ -0,0 +1,25 @@ +#ifndef DRV8428_DRIVER_H +#define DRV8428_DRIVER_H + +#include "stepper_driver.h" +#include "io_expansion_board.h" +#include "esp_log.h" + +class Drv8428Driver : public StepperDriver +{ +public: + explicit Drv8428Driver(IOExpansionBoard &ioExp); + virtual ~Drv8428Driver() = default; + + esp_err_t Init() override; + esp_err_t Enable(bool enable) override; + esp_err_t SetDirection(bool dir) override; + esp_err_t Sleep(bool sleep) override; + esp_err_t SetMicrosteps(int microsteps) override; + +private: + IOExpansionBoard &io_expansion_; + const char* TAG = "Drv8428Driver"; +}; + +#endif // DRV8428_DRIVER_H diff --git a/main/04_hal/stepper_driver/module.cmake b/main/04_hal/stepper_driver/module.cmake new file mode 100644 index 0000000..39a1ea0 --- /dev/null +++ b/main/04_hal/stepper_driver/module.cmake @@ -0,0 +1,17 @@ +# main/04_hal/stepper_driver/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_HAL_STEPPER_DRIVER AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] stepper_driver module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "04_hal/stepper_driver/drv8428.cc" + "04_hal/stepper_driver/aw8646.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "04_hal/stepper_driver" +) \ No newline at end of file diff --git a/main/04_hal/stepper_driver/stepper_driver.h b/main/04_hal/stepper_driver/stepper_driver.h new file mode 100644 index 0000000..c25b83e --- /dev/null +++ b/main/04_hal/stepper_driver/stepper_driver.h @@ -0,0 +1,54 @@ +#ifndef STEPPER_DRIVER_H +#define STEPPER_DRIVER_H + +#include +#include "esp_err.h" + +/** + * @brief Abstract base class for stepper motor driver chips. + * + * This interface abstracts the low-level control of the stepper motor driver, + * such as enabling/disabling, setting direction, sleep mode, and microstepping configuration. + * It does NOT handle step generation (that is handled by the StepperMotor class using GPTimer). + */ +class StepperDriver +{ +public: + virtual ~StepperDriver() = default; + + /** + * @brief Initialize the driver hardware (GPIOs, IO Expander, etc.) + * @return ESP_OK on success + */ + virtual esp_err_t Init() = 0; + + /** + * @brief Enable or disable the motor driver output. + * @param enable true to enable, false to disable + * @return ESP_OK on success + */ + virtual esp_err_t Enable(bool enable) = 0; + + /** + * @brief Set the motor direction. + * @param dir true for one direction (e.g. UP/CW), false for the other (e.g. DOWN/CCW) + * @return ESP_OK on success + */ + virtual esp_err_t SetDirection(bool dir) = 0; + + /** + * @brief Set the motor driver to sleep mode to save power. + * @param sleep true to sleep, false to wake up + * @return ESP_OK on success + */ + virtual esp_err_t Sleep(bool sleep) = 0; + + /** + * @brief Set microstepping mode. + * @param microsteps Microstepping value (e.g., 1, 2, 4, 8, 16, 32, etc.) + * @return ESP_OK on success + */ + virtual esp_err_t SetMicrosteps(int microsteps) = 0; +}; + +#endif // STEPPER_DRIVER_H diff --git a/main/05_executors/adc_battery_monitor/adc_battery_monitor.cc b/main/05_executors/adc_battery_monitor/adc_battery_monitor.cc new file mode 100644 index 0000000..de47bfd --- /dev/null +++ b/main/05_executors/adc_battery_monitor/adc_battery_monitor.cc @@ -0,0 +1,167 @@ +#include "adc_battery_monitor.h" +#include + +#define TAG "AdcBatteryMonitor" ///< 日志标签,用于ESP-IDF日志系统 + +AdcBatteryMonitor::AdcBatteryMonitor(BoardSweekar& board, adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor) + : board_(board) { + + // Initialize ADC battery estimation + adc_battery_estimation_t adc_cfg = { + .internal = { + .adc_unit = adc_unit, + .adc_bitwidth = ADC_BITWIDTH_DEFAULT, + .adc_atten = ADC_ATTEN_DB_12, + }, + .adc_channel = adc_channel, + .upper_resistor = upper_resistor, + .lower_resistor = lower_resistor + }; + adc_cfg.charging_detect_cb = [](void *user_data) -> bool { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data; + return self->IsCharging(); + }; + adc_cfg.charging_detect_user_data = this; + adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg); + + // Initialize timer + esp_timer_create_args_t timer_cfg = { + .callback = [](void *arg) { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg; + self->CheckBatteryStatus(); + }, + .arg = this, + .name = "adc_battery_monitor", + .skip_unhandled_events = false, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); +} + +// 注册ADC电池回调函数 +void AdcBatteryMonitor::AdcBatteryMonitor_Init(void) { + ESP_LOGI(TAG, "注册ADC电池回调函数"); + // 设置默认的电池状态回调函数 + OnNotCharging([]() { + ESP_LOGI(TAG, "电池未充电状态回调 - 默认处理"); + }); + + OnFull([]() { + ESP_LOGI(TAG, "电池充满电状态回调 - 默认处理"); + }); + + OnCharging([]() { + ESP_LOGI(TAG, "电池充电中状态回调 - 默认处理"); + }); +} + +AdcBatteryMonitor::~AdcBatteryMonitor() { + if (adc_battery_estimation_handle_) { + ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_)); + } + + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } +} + +bool AdcBatteryMonitor::IsCharging() { + // 通过IO扩展板读取充电检测引脚状态 + // BATTERY_CHRG_PIN连接到P0_5,对应INPUT_PORT0 + uint8_t port0_data = 0; + esp_err_t err = board_.io_expansion_.ReadInputPort0(&port0_data); + + // 如果读取失败,返回false + if (err != ESP_OK) { + return false; + } + + // 检查BATTERY_CHRG_PIN位状态 + // 当引脚为低电平时表示正在充电 + return (port0_data & (1 << BATTERY_CHRG_PIN)) == 0; +} + +bool AdcBatteryMonitor::IsDischarging() { + return !IsCharging(); +} + +uint8_t AdcBatteryMonitor::GetBatteryLevel() { + // 如果句柄无效,返回默认值 + if (adc_battery_estimation_handle_ == nullptr) { + printf("adc_battery_estimation_handle_ 为空!\n"); + return 0; + } + + float capacity = 0; + esp_err_t err = adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity); + if (err != ESP_OK) { + printf("返回值出错!\n"); + return 0; // 出错时返回默认值 + } + return (uint8_t)capacity; +} + +void AdcBatteryMonitor::OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; +} + +void AdcBatteryMonitor::CheckBatteryStatus() { + bool new_charging_status = IsCharging(); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + } +} + +// 三个电池状态回调函数的实现 +void AdcBatteryMonitor::TriggerNotChrgCallback() { + if (on_not_charging_callback_) { + on_not_charging_callback_(); + } +} + +void AdcBatteryMonitor::TriggerFullCallback() { + if (on_full_callback_) { + on_full_callback_(); + } +} + +void AdcBatteryMonitor::TriggerChrgCallback() { + if (on_charging_callback_) { + on_charging_callback_(); + } +} + +// 注册电池状态回调函数的实现 +void AdcBatteryMonitor::OnNotCharging(std::function callback) { + on_not_charging_callback_ = callback; +} + +void AdcBatteryMonitor::OnFull(std::function callback) { + on_full_callback_ = callback; +} + +void AdcBatteryMonitor::OnCharging(std::function callback) { + on_charging_callback_ = callback; +} + +//CHRG引脚充电前为高电平 充电中为低电平 充满电为高电平 +//FULL未充满为高电平 充满电为低电平 +bool AdcBatteryMonitor::IsFull() { + // 通过IO扩展板读取电池充满检测引脚状态 + // BATTERY_FULL_PIN连接到P1_3,对应INPUT_PORT1 + uint8_t port1_data = 0; + esp_err_t err = board_.io_expansion_.ReadInputPort1(&port1_data); + + // 如果读取失败,返回false + if (err != ESP_OK) { + return false; + } + + // 检查BATTERY_FULL_PIN位状态 + // 当引脚为低电平时表示电池已充满 + return (port1_data & (1 << BATTERY_FULL_PIN)) == 0; +} \ No newline at end of file diff --git a/main/05_executors/adc_battery_monitor/adc_battery_monitor.h b/main/05_executors/adc_battery_monitor/adc_battery_monitor.h new file mode 100644 index 0000000..bbd441e --- /dev/null +++ b/main/05_executors/adc_battery_monitor/adc_battery_monitor.h @@ -0,0 +1,47 @@ +#ifndef ADC_BATTERY_MONITOR_H +#define ADC_BATTERY_MONITOR_H + +#include +#include +#include +#include +#include "02_physical/boards/board_sweekar.h" + +class AdcBatteryMonitor { +public: + AdcBatteryMonitor(BoardSweekar& board, adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor); + ~AdcBatteryMonitor(); + + bool IsCharging(); + bool IsDischarging(); + bool IsFull(); // 电池充满检测函数 + uint8_t GetBatteryLevel(); + + // 注册电池状态回调函数 + void AdcBatteryMonitor_Init(void); + // 三个电池状态回调函数 + void TriggerNotChrgCallback(); // 触发未充电回调 + void TriggerFullCallback(); // 触发充满电回调 + void TriggerChrgCallback(); // 触发充电中回调 + + // 注册电池状态回调函数 + void OnNotCharging(std::function callback); // 注册未充电回调 + void OnFull(std::function callback); // 注册充满电回调 + void OnCharging(std::function callback); // 注册充电中回调 + + void OnChargingStatusChanged(std::function callback); + +private: + BoardSweekar& board_; // 添加对BoardSweekar的引用 + adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr; + esp_timer_handle_t timer_handle_ = nullptr; + bool is_charging_ = false; + std::function on_charging_status_changed_; + std::function on_not_charging_callback_; // 未充电回调函数 + std::function on_full_callback_; // 充满电回调函数 + std::function on_charging_callback_; // 充电中回调函数 + + void CheckBatteryStatus(); +}; + +#endif // ADC_BATTERY_MONITOR_H diff --git a/main/05_executors/adc_battery_monitor/module.cmake b/main/05_executors/adc_battery_monitor/module.cmake new file mode 100644 index 0000000..9ca36f8 --- /dev/null +++ b/main/05_executors/adc_battery_monitor/module.cmake @@ -0,0 +1,16 @@ +# main/05_executors/adc_battery_monitor/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_EXEC_ADC_BATTERY_MONITOR AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] adc_battery_monitor module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "05_executors/adc_battery_monitor/adc_battery_monitor.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "05_executors/adc_battery_monitor" +) diff --git a/main/05_executors/base_board_control/base_board_control.cc b/main/05_executors/base_board_control/base_board_control.cc new file mode 100644 index 0000000..172388d --- /dev/null +++ b/main/05_executors/base_board_control/base_board_control.cc @@ -0,0 +1,257 @@ +#include "base_board_control.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +static const char *TAG = "BaseBoardCtrl"; + +esp_err_t BaseBoardControl::Init(BaseBoardHal *hal) +{ + if (hal == nullptr) + { + ESP_LOGE(TAG, "Init: hal is null"); + return ESP_ERR_INVALID_ARG; + } + hal_ = hal; + return hal_->Init(); +} + +esp_err_t BaseBoardControl::GetAngle(float *angle) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + return hal_->GetAngle(angle); +} + +esp_err_t BaseBoardControl::SendSetAngleAbs(uint16_t angle_deg, + uint8_t speed_percent, + uint8_t flags) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + + // 角度限制在 0~359 之间 + if (angle_deg > 359) + { + angle_deg = static_cast(angle_deg % 360); + } + + // 速度百分比限制在 0~100 + if (speed_percent > 100) + { + speed_percent = 100; + } + + uint8_t payload[4]; + + payload[0] = static_cast(angle_deg & 0xFF); // LSB + payload[1] = static_cast((angle_deg >> 8) & 0xFF); // MSB + payload[2] = speed_percent; + payload[3] = flags; // 预留/扩展位 + + return SendFrame(0x01, payload, sizeof(payload)); +} + +esp_err_t BaseBoardControl::SendSetAngleRel(int16_t delta_deg, + uint8_t speed_percent, + uint8_t flags) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + + if (speed_percent > 100) + { + speed_percent = 100; + } + + uint8_t payload[4]; + + // int16_t 直接按补码低字节在前的顺序发 + uint16_t delta_u = static_cast(delta_deg); + + payload[0] = static_cast(delta_u & 0xFF); // LSB + payload[1] = static_cast((delta_u >> 8) & 0xFF); // MSB + payload[2] = speed_percent; + payload[3] = flags; // 预留/扩展位 + + return SendFrame(0x02, payload, sizeof(payload)); +} + +esp_err_t BaseBoardControl::SendSetMotorMove(MotorMode mode, uint8_t speed_percent, uint16_t duration_ms) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + + if (speed_percent > 100) { + speed_percent = 100; + } + + uint8_t payload[4]; + + payload[0] = mode; + payload[1] = speed_percent; + payload[2] = static_cast(duration_ms & 0xFF); + payload[3] = static_cast((duration_ms >> 8) & 0xFF); + + return SendFrame(0x03, payload, sizeof(payload)); +} + +esp_err_t BaseBoardControl::SendSetLedRgb(uint8_t mode, + uint8_t target_mask, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t speed) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + + if (speed > 100) + { + speed = 100; + } + + uint8_t payload[7]; + + payload[0] = mode; // 灯效模式 + payload[1] = target_mask; // 位掩码,指示哪些 LED 生效 + payload[2] = r; + payload[3] = g; + payload[4] = b; + payload[5] = speed; // 呼吸/渐变速度 + payload[6] = 0; // Reserved,写死为 0 + + return SendFrame(0x10, payload, sizeof(payload)); +} + +esp_err_t BaseBoardControl::SendFrame(uint8_t cmd, + const uint8_t *payload, + uint8_t payload_len) +{ + if (hal_ == nullptr) + { + return ESP_ERR_INVALID_STATE; + } + + // LEN = CMD(1) + payload_len + uint8_t len = static_cast(1u + payload_len); + + // 总长度 = HEAD1 + HEAD2 + LEN + (CMD + payload_len) + CRC = 3 + len + 1 + size_t frame_len = static_cast(3u + len + 1u); + if (frame_len > kMaxFrameLen) + { + ESP_LOGE(TAG, "SendFrame: frame too long, len=%u", static_cast(frame_len)); + return ESP_ERR_INVALID_SIZE; + } + + uint8_t frame[kMaxFrameLen]; + + // 帧头 + frame[0] = 0xAA; + frame[1] = 0x55; + + // LEN 字段:从 CMD 开始计数 + frame[2] = len; + + // CMD + frame[3] = cmd; + + // payload + for (uint8_t i = 0; i < payload_len; ++i) + { + frame[4u + i] = payload[i]; + } + + uint8_t crc = CRC8(&frame[2], len + 1); // 从 LEN 开始算起 + + // CRC 放在数据之后 + frame[3u + len] = crc; + + const int kMaxRetries = 5; + const int kRetryDelayMs = 50; + + for (int i = 0; i < kMaxRetries; i++) + { + esp_err_t err = hal_->Send(frame, frame_len); + if (err == ESP_OK) + { + // 等待从机处理并准备好 ACK + // 增加延时以确保从机有足够时间写入 I2C 缓冲 + vTaskDelay(pdMS_TO_TICKS(20)); + + uint8_t ack_buf[kAckFrameLen] = {0}; + err = hal_->Read(ack_buf, kAckFrameLen); + if (err == ESP_OK) + { + // 校验 ACK + if (ack_buf[0] == 0xAA && ack_buf[1] == 0x55 && + ack_buf[2] == 0x01 && ack_buf[3] == 0xFF) + { + uint8_t crc = CRC8(&ack_buf[2], 2); + if (crc == ack_buf[4]) + { + return ESP_OK; // 握手成功 + } + else + { + ESP_LOGW(TAG, "ACK CRC Fail: Recv=0x%02X Calc=0x%02X", ack_buf[4], crc); + } + } + else + { + ESP_LOGW(TAG, "Invalid ACK: %02X %02X %02X %02X", + ack_buf[0], ack_buf[1], ack_buf[2], ack_buf[3]); + } + } + else + { + ESP_LOGW(TAG, "Read ACK Failed: %s", esp_err_to_name(err)); + } + } + else + { + ESP_LOGW(TAG, "Send Failed: %s", esp_err_to_name(err)); + } + + // 重试前等待 + vTaskDelay(pdMS_TO_TICKS(kRetryDelayMs)); + } + + ESP_LOGE(TAG, "SendFrame Failed after %d retries", kMaxRetries); + return ESP_FAIL; +} + +uint8_t BaseBoardControl::CRC8(const uint8_t *data, size_t len) +{ + uint8_t crc = 0x00; + + for (size_t i = 0; i < len; ++i) + { + crc ^= data[i]; + + for (int b = 0; b < 8; ++b) + { + if (crc & 0x80) + { + crc = static_cast((crc << 1) ^ 0x07); + } + else + { + crc = static_cast(crc << 1); + } + } + } + + return crc; +} diff --git a/main/05_executors/base_board_control/base_board_control.h b/main/05_executors/base_board_control/base_board_control.h new file mode 100644 index 0000000..5d2e98f --- /dev/null +++ b/main/05_executors/base_board_control/base_board_control.h @@ -0,0 +1,63 @@ +// base_board_control.h +#pragma once + +#include +#include "esp_err.h" +#include "base_board_hal.h" + +class BaseBoardControl +{ +public: + /** 帧最大长度,按你的协议冗余留一点空间 */ + static constexpr size_t kMaxFrameLen = 16; + /** ACK 帧长度: HEAD(2) + LEN(1) + CMD(1) + CRC(1) = 5 */ + static constexpr size_t kAckFrameLen = 5; + + BaseBoardControl() = default; + ~BaseBoardControl() = default; + + esp_err_t Init(BaseBoardHal *hal); + + /** 发送绝对角度命令 CMD=0x01 */ + esp_err_t SendSetAngleAbs(uint16_t angle_deg, + uint8_t speed_percent, + uint8_t flags = 0); + + /** 发送相对角度命令 CMD=0x02 */ + esp_err_t SendSetAngleRel(int16_t delta_deg, + uint8_t speed_percent, + uint8_t flags = 0); + + /** 电机工作模式枚举 */ + enum MotorMode + { + MotorOff = 0, + MotorForward = 1, + MotorBackward = 2 + }; + + /** 发送电机开环控制命令 CMD=0x03 */ + esp_err_t SendSetMotorMove(MotorMode mode, uint8_t speed_percent, uint16_t duration_ms); + + /** 发送 RGB 命令 CMD=0x10 */ + esp_err_t SendSetLedRgb(uint8_t mode, + uint8_t target_mask, + uint8_t r, + uint8_t g, + uint8_t b, + uint8_t speed); + + /** 获取当前角度 */ + esp_err_t GetAngle(float *angle); + +private: + BaseBoardHal *hal_ = nullptr; + + /** 通用打包 + 发送接口:传 CMD 和 payload,内部加头、LEN、CRC */ + esp_err_t SendFrame(uint8_t cmd, + const uint8_t *payload, + uint8_t payload_len); + + /** CRC8 工具函数 */ + static uint8_t CRC8(const uint8_t *data, size_t len); +}; diff --git a/main/05_executors/base_board_control/module.cmake b/main/05_executors/base_board_control/module.cmake new file mode 100644 index 0000000..dfa063d --- /dev/null +++ b/main/05_executors/base_board_control/module.cmake @@ -0,0 +1,16 @@ +# main/05_executors/base_board_control/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_EXEC_BASE_BOARD_CONTROL AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] base_board_control module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "05_executors/base_board_control/base_board_control.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "05_executors/base_board_control" +) diff --git a/main/05_executors/gyroscope/gyroscope.cc b/main/05_executors/gyroscope/gyroscope.cc new file mode 100644 index 0000000..af1b475 --- /dev/null +++ b/main/05_executors/gyroscope/gyroscope.cc @@ -0,0 +1,625 @@ +#include "gyroscope.h" +#include "freertos/idf_additions.h" + +Gyroscope::Gyroscope() +{ + bus_handle_ = nullptr; + on_shake_callback_ = nullptr; // 初始化回调函数 + on_pickup_callback_ = nullptr; // 初始化拿起回调函数 + on_putdown_callback_ = nullptr; // 初始化放置回调函数 + on_tilt_callback_ = nullptr; // 初始化倾斜回调函数 + timer_handle_ = nullptr; // 初始化定时器句柄 + pGyroscopeTaskStackBuffer_ = nullptr; + pxGyroscopeTaskBuffer_ = nullptr; + + // 初始化滤波相关变量 + last_filtered_x_ = 0; + last_filtered_y_ = 0; + last_filtered_z_ = 0; + is_first_sample_ = true; +} + +Gyroscope::~Gyroscope() +{ + // 删除定时器 + DeleteTimer(); + + if (pGyroscopeTaskStackBuffer_) { + free(pGyroscopeTaskStackBuffer_); + pGyroscopeTaskStackBuffer_ = nullptr; + } + if (pxGyroscopeTaskBuffer_) { + vPortFree(pxGyroscopeTaskBuffer_); + pxGyroscopeTaskBuffer_ = nullptr; + } +} + +/** + * @brief 创建500ms周期定时器 + * @return esp_err_t 创建结果 + */ +esp_err_t Gyroscope::CreateTimer() +{ + if (timer_handle_ != nullptr) { + ESP_LOGW(TAG_GYROSCOPE, "定时器已存在"); + return ESP_OK; + } + + // 配置定时器参数 + esp_timer_create_args_t timer_args = {}; + timer_args.callback = &TimerCallback; // 静态回调函数 + timer_args.arg = this; // 传入实例指针 + timer_args.name = "gyro_timer"; // 定时器名称 + timer_args.dispatch_method = ESP_TIMER_TASK; // 使用任务调度 + timer_args.skip_unhandled_events = false; // 不跳过未处理事件 + + // 创建定时器 + esp_err_t err = esp_timer_create(&timer_args, &timer_handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG_GYROSCOPE, "创建定时器失败: 0x%x", err); + return err; + } + + // 启动定时器,500ms周期重复 + err = esp_timer_start_periodic(timer_handle_, 50000); // 50000微秒 = 50ms 20hz + if (err != ESP_OK) { + ESP_LOGE(TAG_GYROSCOPE, "启动定时器失败: 0x%x", err); + esp_timer_delete(timer_handle_); + timer_handle_ = nullptr; + return err; + } + + ESP_LOGI(TAG_GYROSCOPE, "50ms周期定时器创建并启动成功"); + return ESP_OK; +} + +/** + * @brief 停止并删除定时器 + */ +void Gyroscope::DeleteTimer() +{ + if (timer_handle_ == nullptr) { + return; + } + + // 停止定时器 + esp_err_t err = esp_timer_stop(timer_handle_); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG_GYROSCOPE, "停止定时器失败: 0x%x", err); + } + + // 删除定时器 + err = esp_timer_delete(timer_handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG_GYROSCOPE, "删除定时器失败: 0x%x", err); + } + + timer_handle_ = nullptr; + ESP_LOGI(TAG_GYROSCOPE, "定时器已删除"); +} + +/** + * @brief 静态定时器回调函数 + * @param arg 传入的Gyroscope实例指针 + */ +void Gyroscope::TimerCallback(void* arg) +{ + Gyroscope* gyroscope = static_cast(arg); + if (gyroscope) { + gyroscope->TimerInterruptHandler(); + } +} + +/** + * @brief 定时器中断处理函数 + * 每50ms调用一次,可以在这里执行周期性任务 + */ +void Gyroscope::TimerInterruptHandler() +{ + // 在这里添加定时器中断处理逻辑 + // 例如:读取传感器数据、检测动作、发送状态等 + AccelerationData accel_data_ = {0}; + + esp_err_t err = GetAcceleration(&accel_data_); + if (err == ESP_OK) { + // 根据实际安装方式重新定义姿态判断 + // 平放状态:Y轴接近±1g(因为Y轴垂直于桌面),X和Z轴接近0 + bool is_flat = (fabs(accel_data_.y) > 0.85 && fabs(accel_data_.y) < 1.15) && + (fabs(accel_data_.x) < 0.15 && fabs(accel_data_.z) < 0.15); + + // 手持状态:Y轴接近0,X和Z轴中至少有一个有明显值 + bool is_held = (fabs(accel_data_.y) < 0.7) && + (fabs(accel_data_.x) > 0.15 || fabs(accel_data_.z) > 0.15); + + // 计算合成加速度(总加速度大小) + float total_accel = sqrt(accel_data_.x * accel_data_.x + + accel_data_.y * accel_data_.y + + accel_data_.z * accel_data_.z); + + // 计算与上次合成加速度的差值(加速度变化量) + float total_accel_diff = fabs(total_accel - total_accel_last_); + + // 计算Y轴加速度变化 + float y_diff = accel_data_.y - accel_data_y_last_; + + // 检测是否处于平放稳定状态 + if(is_flat) { + stable_count_++; + if(stable_count_ >= 8) { // 连续8次检测到平放状态(400ms),减少误判 + stable_count_ = 8; // 防止溢出 + if(!is_stable_) { + ESP_LOGI(TAG_GYROSCOPE, "定时器: 检测到稳定平放状态 (x:%.2f, y:%.2f, z:%.2f, total:%.2f)", + accel_data_.x, accel_data_.y, accel_data_.z, total_accel); + is_stable_ = true; + is_lifted_ = false; // 重置拿起状态 + putdown_count_ = 0; // 重置放置计数器 + lift_count_ = 0; // 重置拿起计数器 + + // 调用放置回调 + if (on_putdown_callback_) { + on_putdown_callback_(); + } + } + } + } + else { + stable_count_ = 0; // 不在范围内则重置稳定计数器 + + // 如果之前是稳定状态,检测是否有拿起动作 + if(is_stable_) { + // 检测拿起动作的多种特征: + // 1. Y轴从接近1g变为接近0g(位置变化) + // 2. 合成加速度有显著变化(表示有加速度) + bool y_change_indicates_lift = fabs(y_diff) > 0.25; + bool total_accel_change_indicates_lift = total_accel_diff > 0.3; // 检测加速度突变 + + if(y_change_indicates_lift || total_accel_change_indicates_lift) { + lift_count_++; + if(lift_count_ >= 1) { // 降低拿起检测阈值,提高灵敏度 + ESP_LOGI(TAG_GYROSCOPE, "定时器: 检测到拿起动作 (x:%.2f, y:%.2f, z:%.2f, total:%.2f, y_diff:%.2f, accel_diff:%.2f)", + accel_data_.x, accel_data_.y, accel_data_.z, total_accel, y_diff, total_accel_diff); + is_stable_ = false; // 重置状态 + is_lifted_ = true; // 设置拿起状态 + lift_count_ = 0; + stable_count_ = 0; + + // 调用拿起回调 + if (on_pickup_callback_) { + on_pickup_callback_(); + } + } + } else { + lift_count_ = 0; // 变化不符合预期,重置拿起计数器 + } + } + // 如果之前是被拿起状态,检测是否有放置动作 + else if(is_lifted_) { + // 检测从手持到平放的变化 - Y轴从接近0g变为接近1g + bool y_change_indicates_putdown = (y_diff > 0.25 && y_diff < 0.80); + + if(y_change_indicates_putdown) { + putdown_count_++; + if(putdown_count_ >= 3) { // 连续3次检测到缓慢变化 + ESP_LOGI(TAG_GYROSCOPE, "定时器: 检测到缓慢放置动作 (x:%.2f, y:%.2f, z:%.2f, total:%.2f, y_diff:%.2f)", + accel_data_.x, accel_data_.y, accel_data_.z, total_accel, y_diff); + is_lifted_ = false; // 重置拿起状态 + putdown_count_ = 0; + + // 调用放置回调 + if (on_putdown_callback_) { + on_putdown_callback_(); + } + } + } else { + putdown_count_ = 0; // 变化不符合预期,重置放置计数器 + } + } + + // 检测倾斜动作 - 仅当手持时才触发倾斜 + if (is_held && (fabs(accel_data_.x) > 0.3f || fabs(accel_data_.z) > 0.3f)) { + // 检测到倾斜,调用倾斜回调 + if (on_tilt_callback_) { + on_tilt_callback_(); + } + } + } + // 保存当前值作为下一次的上一次值 + accel_data_y_last_ = accel_data_.y; + total_accel_last_ = total_accel; + } +} +/** + * @brief 注册摇晃检测回调函数 + * + * @param callback 当检测到摇晃时调用的回调函数 + */ +void Gyroscope::OnShake(std::function callback) +{ + on_shake_callback_ = callback; +} + +/** + * @brief 注册拿起检测回调函数 + * + * @param callback 当检测到设备被拿起时调用的回调函数 + */ +void Gyroscope::OnPickUp(std::function callback) +{ + on_pickup_callback_ = callback; +} + +/** + * @brief 注册放置检测回调函数 + * + * @param callback 当检测到设备被放置时调用的回调函数 + */ +void Gyroscope::OnPutDown(std::function callback) +{ + on_putdown_callback_ = callback; +} + +/** + * @brief 注册倾斜检测回调函数 + * + * @param callback 当检测到设备倾斜时调用的回调函数 + */ +void Gyroscope::OnTilt(std::function callback) +{ + on_tilt_callback_ = callback; +} + +/** + * @brief 静态中断处理函数 + * + * @param arg 传入的Gyroscope实例指针 + */ +void IRAM_ATTR Gyroscope::GpioIsrHandler(void* arg) +{ + Gyroscope* gyroscope = static_cast(arg); + if (gyroscope) { + gyroscope->interrupt_flag_ = true; + } +} + +/** + * @brief 向陀螺仪寄存器写入数据 + * + * @param reg 寄存器地址 + * @param data 要写入的数据 + * @return esp_err_t ESP_OK表示成功 + */ +esp_err_t Gyroscope::WriteRegister(uint8_t reg, uint8_t data) +{ + if (bus_handle_ == nullptr) { + ESP_LOGE(TAG_GYROSCOPE, "I2C总线未初始化"); + return ESP_FAIL; + } + + return i2c_bus_write_bytes(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1, ®, 1, &data, 1); +} + +/** + * @brief 从陀螺仪寄存器读取数据 + * + * @param reg 寄存器地址 + * @param data 读取到的数据指针 + * @return esp_err_t ESP_OK表示成功 + */ +esp_err_t Gyroscope::ReadRegister(uint8_t reg, uint8_t* data) +{ + if (bus_handle_ == nullptr || data == nullptr) { + ESP_LOGE(TAG_GYROSCOPE, "参数错误或I2C总线未初始化"); + return ESP_FAIL; + } + + return i2c_bus_read_bytes(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1, ®, 1, data, 1); +} + +/** + * @brief 批量读取陀螺仪寄存器数据 + * + * @param reg 起始寄存器地址 + * @param data 读取到的数据指针 + * @param length 要读取的字节数 + * @return esp_err_t ESP_OK表示成功 + */ +esp_err_t Gyroscope::ReadRegisters(uint8_t reg, uint8_t* data, size_t length) +{ + if (bus_handle_ == nullptr || data == nullptr) { + ESP_LOGE(TAG_GYROSCOPE, "参数错误或I2C总线未初始化"); + return ESP_FAIL; + } + + return i2c_bus_read_bytes(bus_handle_, GYROSCOPE_I2C_SLAVE_ADDR << 1, ®, 1, data, length); +} + +/** + * @brief 初始化陀螺仪中断GPIO + * + * @param gpio_num 要初始化的GPIO引脚号 + * @return esp_err_t ESP_OK表示成功 + */ +esp_err_t Gyroscope::InterruptGpioInit(gpio_num_t gpio_num) +{ + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_POSEDGE; // 上升沿触发 + io_conf.pin_bit_mask = (1ULL<x = raw_x * ACCEL_SENSITIVITY_2G / 1000.0f; + data->y = raw_y * ACCEL_SENSITIVITY_2G / 1000.0f; + data->z = raw_z * ACCEL_SENSITIVITY_2G / 1000.0f; + + return ESP_OK; +} + +/** + * @brief 陀螺仪读取任务 + * Y轴立起 Z轴躺下 + * 该任务会周期性读取陀螺仪数据并处理中断 + */ +void IRAM_ATTR Gyroscope::GyroReadTask(void* pvParameters) +{ + Gyroscope* gyroscope = static_cast(pvParameters); + uint32_t interrupt_count = 0; + while (1) { + if (gyroscope->interrupt_flag_) { + interrupt_count++; + gyroscope->ClearInterruptFlags(); + gyroscope->interrupt_flag_ = false; + + // 调用用户注册的回调函数 + if (gyroscope->on_shake_callback_) { + gyroscope->on_shake_callback_(); + } + + ESP_LOGI(TAG_GYROSCOPE, "累计中断次数: %lu", interrupt_count); + if(interrupt_count >= 2){ + interrupt_count = 0; + ESP_LOGI(TAG_GYROSCOPE, "检测到摇晃>>>>>>>>>>"); + } + } + vTaskDelay(pdMS_TO_TICKS(50)); // 50ms检查一次,更快的响应 + } +} diff --git a/main/05_executors/gyroscope/gyroscope.h b/main/05_executors/gyroscope/gyroscope.h new file mode 100644 index 0000000..36e67f8 --- /dev/null +++ b/main/05_executors/gyroscope/gyroscope.h @@ -0,0 +1,214 @@ +#ifndef GYROSCOPE_H +#define GYROSCOPE_H + +#include +#include +#include +#include +#include "i2c_bus.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "config.h" +#include "io_expansion_board.h" +#include +#include "esp_timer.h" + +// 加速度数据结构体 +typedef struct { + float x; // X轴加速度 (g) + float y; // Y轴加速度 (g) + float z; // Z轴加速度 (g) +} AccelerationData; + +// 一阶滤波系数 +#define WEIGHTFACTOR 0.93f + +/* 陀螺仪寄存器定义 */ +#define CTRL1_XL 0x10 // 加速度计控制寄存器1 +#define CTRL2_G 0x11 // 陀螺仪控制寄存器2 +#define CTRL3_C 0x12 // 控制寄存器3 +#define CTRL4_C 0x13 // 控制寄存器4 +#define CTRL5_C 0x14 // 控制寄存器5 +#define CTRL6_C 0x15 // 控制寄存器6 +#define CTRL7_G 0x16 // 陀螺仪控制寄存器7 +#define CTRL8_XL 0x17 // 加速度计控制寄存器8 +#define CTRL9_XL 0x18 // 加速度计控制寄存器9 +#define CTRL10_C 0x19 // 控制寄存器10 +#define INT1_CTRL 0x0D // 中断1控制寄存器 +#define WAKE_UP_DUR 0x5B // 唤醒持续时间寄存器 +#define WAKE_UP_THS 0x5C // 唤醒阈值寄存器 +#define TAP_CFG 0x58 // 敲击配置寄存器 +#define TAP_THS_6D 0x59 // 6D方向阈值寄存器 +#define INT_DUR2 0x5A // 中断持续时间寄存器 +#define MD1_CFG 0x5E // 中断1配置寄存器 +#define MD2_CFG 0x5F // 中断2配置寄存器 +#define FUNC_SRC1 0x53 // 功能源寄存器1 +#define WAKE_UP_SRC 0x1B // 唤醒源寄存器 + +#define ACCEL_X_L 0x28 // OUTX_L_XL +#define ACCEL_X_H 0x29 // OUTX_H_XL +#define ACCEL_Y_L 0x2A // OUTY_L_XL +#define ACCEL_Y_H 0x2B // OUTY_H_XL +#define ACCEL_Z_L 0x2C // OUTZ_L_XL +#define ACCEL_Z_H 0x2D // OUTZ_H_XL + +#define ACCEL_SENSITIVITY_2G 0.061f // 2g 灵敏度 (g/LSB) + +#define I2C_MASTER_TIMEOUT_MS 1000 // I2C主设备超时时间(毫秒) + +/** + * @class Gyroscope + * @brief 陀螺仪驱动类,封装芯片操作 + */ +#define TAG_GYROSCOPE "Gyroscope" +class Gyroscope +{ +public: + /** + * @brief 构造函数 + */ + Gyroscope(); + + /** + * @brief 析构函数 + */ + ~Gyroscope(); + + /** + * @brief 初始化中断引脚 + * @param gpio_num 中断引脚编号 + * @return esp_err_t 初始化结果 + */ + esp_err_t InterruptGpioInit(gpio_num_t gpio_num); + + /** + * @brief 创建陀螺仪读取任务 + */ + void CreateGyroReadTask(); + + /** + * @brief 初始化陀螺仪 + * @return esp_err_t 初始化结果 + */ + esp_err_t Init(i2c_bus_handle_t bus_handle); + + /** + * @brief 清除中断标志 + * @return esp_err_t 清除结果 + */ + esp_err_t ClearInterruptFlags(); + + + /** + * @brief 获取当前加速度数据 + * @param data 指向AccelerationData结构体的指针,用于存储读取到的加速度数据 + * @return esp_err_t 获取结果 + */ + esp_err_t GetAcceleration(AccelerationData* data); + + /** + * @brief 创建500ms周期定时器 + * @return esp_err_t 创建结果 + */ + esp_err_t CreateTimer(); + + /** + * @brief 停止并删除定时器 + */ + void DeleteTimer(); + + /** + * @brief 注册摇晃检测回调函数 + * @param callback 当检测到摇晃时调用的回调函数 + */ + void OnShake(std::function callback); + + /** + * @brief 注册拿起检测回调函数 + * @param callback 当检测到设备被拿起时调用的回调函数 + */ + void OnPickUp(std::function callback); + + /** + * @brief 注册放置检测回调函数 + * @param callback 当检测到设备被放置时调用的回调函数 + */ + void OnPutDown(std::function callback); + + /** + * @brief 注册倾斜检测回调函数 + * @param callback 当检测到设备倾斜时调用的回调函数 + */ + void OnTilt(std::function callback); + + i2c_bus_handle_t bus_handle_ = nullptr; // I2C总线句柄 + volatile uint8_t interrupt_count_ = 0; // 中断计数 + volatile bool interrupt_flag_ = false; // 中断标志位)(判断是否需要处理) + +private: + static void GpioIsrHandler(void* arg); // 中断处理函数 + static void GyroReadTask(void* pvParameters); + static void TimerCallback(void* arg); // 定时器回调函数 + + void TimerInterruptHandler(); // 定时器中断处理函数 + + esp_err_t WriteRegister(uint8_t reg, uint8_t data); + esp_err_t ReadRegister(uint8_t reg, uint8_t* data); + esp_err_t ReadRegisters(uint8_t reg, uint8_t* data, size_t length); + + /** + * @brief 读取原始加速度数据 + * @param x 指向X轴加速度存储的指针 + * @param y 指向Y轴加速度存储的指针 + * @param z 指向Z轴加速度存储的指针 + * @return esp_err_t 读取结果 + */ + esp_err_t ReadRawAcceleration(int16_t* x, int16_t* y, int16_t* z); + + std::function on_shake_callback_; // 摇晃回调函数 + std::function on_pickup_callback_; // 拿起回调函数 + std::function on_putdown_callback_; // 放置回调函数 + std::function on_tilt_callback_; // 倾斜回调函数 + + esp_timer_handle_t timer_handle_ = nullptr; // 定时器句柄 + + /** + * @brief 分配静态任务所需的内存 + * @return bool 分配是否成功 + */ + bool AllocateStaticTaskMemory(); + + /** + * @brief 释放静态任务分配的内存 + */ + void FreeStaticTaskMemory(); + + TaskHandle_t xGyroscopeTaskHandle_ = nullptr; + StackType_t* pGyroscopeTaskStackBuffer_ = nullptr; // 任务栈指针(PSRAM) + StaticTask_t* pxGyroscopeTaskBuffer_ = nullptr; // 任务控制块指针(内部RAM) + + // 滤波相关成员变量 + int16_t last_filtered_x_ = 0; // 上一次滤波后的X轴值 + int16_t last_filtered_y_ = 0; // 上一次滤波后的Y轴值 + int16_t last_filtered_z_ = 0; // 上一次滤波后的Z轴值 + bool is_first_sample_ = true; // 是否为第一次采样标志 + + // 状态检测相关成员变量 + float accel_data_y_last_ = 0.0f; // 保存上一次Y轴数据 + float total_accel_last_ = 1.0f; // 保存上一次合成加速度值,初始值为1g(平放时的典型值) + uint8_t stable_count_ = 0; // 稳定状态计数器 + uint8_t lift_count_ = 0; // 拿起动作计数器 + uint8_t putdown_count_ = 0; // 放置动作计数器 + bool is_stable_ = false; // 是否处于稳定平放状态 + bool is_lifted_ = false; // 是否处于被拿起状态 +}; + +#endif // GYROSCOPE_H diff --git a/main/05_executors/gyroscope/module.cmake b/main/05_executors/gyroscope/module.cmake new file mode 100644 index 0000000..ca0f8aa --- /dev/null +++ b/main/05_executors/gyroscope/module.cmake @@ -0,0 +1,16 @@ +# main/05_executors/gyroscope/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_EXEC_GYROSCOPE AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] gyroscope module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "05_executors/gyroscope/gyroscope.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "05_executors/gyroscope" +) \ No newline at end of file diff --git a/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.cc b/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.cc new file mode 100644 index 0000000..689f4bc --- /dev/null +++ b/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.cc @@ -0,0 +1,202 @@ +#include "millimeter_wave_radar.h" +#include +#include +#include +#include + +static const char *TAG = "MillimeterWaveRadar"; + +bool MillimeterWaveRadar::Init() { + uart_config_t uart_config = { + .baud_rate = MillimeterWaveRadar::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_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(UART_NUM, TX_PIN, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + ESP_ERROR_CHECK(uart_driver_install(UART_NUM, 256, 0, 0, NULL, 0)); + + // 清空缓冲区 + uart_flush(UART_NUM); + return true; +} + +uint8_t MillimeterWaveRadar::CalculateChecksum(uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4) { + return d1 ^ d2 ^ d3 ^ d4; +} + +// 通过查看最高位是0还是1来判断是否是写命令。但是查询感应模式时最高位不是1(5A 11 00 00 00 11 FE),输入的是0x11,所以仅对此条不适用。 +bool MillimeterWaveRadar::IsWriteCommand(uint8_t cmd) { + if (cmd == 0x11) { + return false; + } + return (cmd & 0x80) == 0x00; // 写命令高位为0 +} + +bool MillimeterWaveRadar::SendFrame(const uint8_t *frame, size_t len) { + int written = uart_write_bytes(UART_NUM, frame, len); + uart_wait_tx_done(UART_NUM, pdMS_TO_TICKS(10)); + return written == (int)len; +} + +MillimeterWaveRadar::Result MillimeterWaveRadar::ReceiveFrame(uint8_t *buffer, size_t maxSize, size_t &receivedLen) { + TickType_t start = xTaskGetTickCount(); + size_t total = 0; + while ((xTaskGetTickCount() - start) < pdMS_TO_TICKS(TIMEOUT_MS)) { + size_t available; + uart_get_buffered_data_len(UART_NUM, &available); + if (available >= 7) { + int read = uart_read_bytes(UART_NUM, buffer, 7, pdMS_TO_TICKS(10)); + if (read == 7) { + receivedLen = 7; + // 校验帧头尾 + if (buffer[0] != 0x5A || buffer[6] != 0xFE) { + ESP_LOGW(TAG, "Invalid frame header/tail"); + return Result::CHECKSUM_ERROR; + } + // 校验 checksum + uint8_t expected = CalculateChecksum(buffer[1], buffer[2], buffer[3], buffer[4]); + if (expected != buffer[5]) { + ESP_LOGW(TAG, "Checksum mismatch"); + return Result::CHECKSUM_ERROR; + } + return Result::OK; + } + } + vTaskDelay(pdMS_TO_TICKS(1)); + } + return Result::TIMEOUT; +} + +MillimeterWaveRadar::Result MillimeterWaveRadar::SetParameter(uint8_t paramId, uint32_t value) { + if (!IsWriteCommand(paramId)) { + return Result::INVALID_PARAM; + } + + uint8_t frame[7]; + frame[0] = 0x5A; + frame[1] = paramId; + + switch (paramId) { + case SET_RADAR_DISTANCE_THRESHOLD: + if (value < 100 || value > 65000) return Result::INVALID_PARAM; + frame[2] = 0x00; + frame[3] = (value >> 8) & 0xFF; + frame[4] = value & 0xFF; + ESP_LOGI(TAG, "Setting distance threshold to %d", static_cast(value)); + break; + case SET_RADAR_DELAY_TIME: + if (value > 0xFFFFFF) return Result::INVALID_PARAM; + frame[2] = (value >> 16) & 0xFF; + frame[3] = (value >> 8) & 0xFF; + frame[4] = value & 0xFF; + ESP_LOGI(TAG, "Setting delay time to %d", static_cast(value)); + break; + case SET_RADAR_LOCKOUT_TIME: + if (value > 0xFFFFFF) return Result::INVALID_PARAM; + frame[2] = (value >> 16) & 0xFF; + frame[3] = (value >> 8) & 0xFF; + frame[4] = value & 0xFF; + ESP_LOGI(TAG, "Setting lockout time to %d", static_cast(value)); + break; + case SET_RADAR_LIGHT_SENSOR_ENABLE: + case SET_RADAR_OUTPUT_LEVEL: + case SET_RADAR_POWER_MODE: + case SET_RADAR_TRIGGER_MODE: + case SET_RADAR_TX_POWER_LEVEL: + case SET_RADAR_PWM_ENABLE: + case SET_RADAR_PULSE_WIDTH: + case SET_RADAR_SENSING_MODE: + case SET_RADAR_SENSING_RESULT: + if (value > 0xFF) return Result::INVALID_PARAM; + frame[2] = 0x00; + frame[3] = 0x00; + frame[4] = value & 0xFF; + break; + case SET_RADAR_LIGHT_THRESHOLD: + if (value > 0xFF) return Result::INVALID_PARAM; + frame[2] = 0x00; + frame[3] = 0x00; + frame[4] = value & 0xFF; + break; + case SET_RADAR_PWM_DUTY: + if (value > 3500) return Result::INVALID_PARAM; + frame[2] = 0x00; + frame[3] = (value >> 8) & 0xFF; + frame[4] = value & 0xFF; + break; + default: + return Result::INVALID_PARAM; + } + + frame[5] = CalculateChecksum(frame[1], frame[2], frame[3], frame[4]); + frame[6] = 0xFE; + + if (!SendFrame(frame, 7)) { + return Result::UART_ERROR; + } + + uint8_t reply[7]; + size_t len; + Result res = ReceiveFrame(reply, sizeof(reply), len); + ESP_LOGW(TAG, "Received raw reply frame: %02X %02X %02X %02X %02X %02X %02X", + reply[0], reply[1], reply[2], reply[3], reply[4], reply[5], reply[6]); + if (res != Result::OK) return res; + + // 发送保存指令 + uint8_t saveFrame[7] = {0x5A, 0x20, 0x00, 0x00, 0x01, 0x21, 0xFE}; + SendFrame(saveFrame, 7); + // 等待保存回复(协议注释1:约100ms) + vTaskDelay(pdMS_TO_TICKS(110)); // 稍微多等一点 + uart_flush(UART_NUM); // 清除可能的残留 + + return Result::OK; +} + +void MillimeterWaveRadar::OnRadarDetect(RadarDetectCallback callback) { + ESP_LOGI(TAG, "Registering radar detection callback"); + radar_detect_callback_ = callback; +} + +void MillimeterWaveRadar::TriggerRadarDetectCallback() { + ESP_LOGI(TAG, "Triggering radar detection callback"); + if (radar_detect_callback_) { + radar_detect_callback_(); + } +} + + + +MillimeterWaveRadar::Result MillimeterWaveRadar::QueryParameter(uint8_t paramId, uint32_t &outputValue) { + if (IsWriteCommand(paramId)) { + ESP_LOGE(TAG, "Attempted to query with a write command ID: 0x%02X", paramId); + return Result::INVALID_PARAM; + } + + uint8_t frame[7] = {0x5A, paramId, 0x00, 0x00, 0x00, paramId, 0xFE}; + ESP_LOGI(TAG, "Sending query frame for param 0x%02X: %02X %02X %02X %02X %02X %02X %02X", + paramId, frame[0], frame[1], frame[2], frame[3], frame[4], frame[5], frame[6]); + + if (!SendFrame(frame, 7)) { + ESP_LOGE(TAG, "UART send failed"); + return Result::UART_ERROR; + } + + uint8_t reply[7]; + size_t len; + Result res = ReceiveFrame(reply, sizeof(reply), len); + if (res != Result::OK) { + ESP_LOGW(TAG, "Failed to receive reply for param 0x%02X, error code: %d", paramId, static_cast(res)); + return res; + } + + // 打印原始回复帧(新增功能) + ESP_LOGW(TAG, "Received raw reply frame: %02X %02X %02X %02X %02X %02X %02X", + reply[0], reply[1], reply[2], reply[3], reply[4], reply[5], reply[6]); + + return Result::OK; +} \ No newline at end of file diff --git a/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.h b/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.h new file mode 100644 index 0000000..7fd47e5 --- /dev/null +++ b/main/05_executors/millimeter_wave_radar/millimeter_wave_radar.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +// 参数名称宏定义(对应协议中的命令) +#define SET_RADAR_DISTANCE_THRESHOLD 0x01 // 设置感应距离门限值。Data3、Data4 为感应距离门限值,取值范围为 100 ~ 65000【值越大,感应距离越近】 +#define SET_RADAR_DELAY_TIME 0x02 // 设置输出延时时间。取值范围为 0X00~ 0XFFFFFF +#define SET_RADAR_LIGHT_SENSOR_ENABLE 0x03 // 开关光感。Data4 为 0x01 时开启光感,为 0x00 时关闭光感。 +#define SET_RADAR_LOCKOUT_TIME 0x04 // 设置封锁时间。取值范围为 0X00~ 0XFFFFFF +#define SET_RADAR_OUTPUT_LEVEL 0x05 // 设置有效电平。Data4 为 0x01 时高电平有效,为 0x00 时低电平有效 +#define SET_RADAR_POWER_MODE 0x06 // 设置功耗模式。Data4 为 0x00 时超低功耗模式(50/60uA),为 0x01 时常规功耗模式(13mA 档位) +#define SET_RADAR_SENSING_RESULT 0x07 // 设置触发状态。Data4 为 0x00 时触发状态为未触发感应,为 0x01 时触发状态为已触发感应。 +#define SET_RADAR_TRIGGER_MODE 0x0A // 设置触发模式。Data4 为 0x00 时触发模式为连续触发,为 0x01 时触发模式为单次触发。 +#define SET_RADAR_TX_POWER_LEVEL 0x0B // 设置发射功率档位。Data4 取值范围为 0~ 7,总共 8 档发射功率可调,值越大,功率越小,=7 时最小,相当于关闭发射功率。发射功率档位值越小,实际发射功率越大,距离越远,抗干扰性越好。 +#define SET_RADAR_LIGHT_THRESHOLD 0x0C // 设置光感门限值。取值范围为 0X00~ 0XFFFFFF。 +#define SET_RADAR_PWM_ENABLE 0x0D // 开关 PWM 功能 +#define SET_RADAR_PWM_DUTY 0x0E // 设置 PWM 占空比 +#define SET_RADAR_PULSE_WIDTH 0x0F // 设置供电脉冲宽度 +#define SET_RADAR_SENSING_MODE 0x10 // 选择感应模式。Data4 为 0x00 时为移动感应模式,为 0x01 时为手扫感应模式。 + +// 参数名称宏定义(对应协议中的命令) +#define QUERY_RADAR_DISTANCE_THRESHOLD 0x81 // 查询感应距离门限值 +#define QUERY_RADAR_DELAY_TIME 0x82 // 查询输出延时时间 +#define QUERY_RADAR_LIGHT_SENSOR_ENABLE 0x83 // 查询开关光感 +#define QUERY_RADAR_LOCKOUT_TIME 0x84 // 查询封锁时间 +#define QUERY_RADAR_OUTPUT_LEVEL 0x85 // 查询有效电平 +#define QUERY_RADAR_POWER_MODE 0x86 // 查询功耗模式 +#define QUERY_RADAR_SENSING_RESULT 0x87 // 查询触发状态 +#define QUERY_RADAR_TRIGGER_MODE 0x8A // 查询触发模式 +#define QUERY_RADAR_TX_POWER_LEVEL 0x8B // 查询发射功率档位 +#define QUERY_RADAR_LIGHT_THRESHOLD 0x8C // 查询光感门限值 +#define QUERY_RADAR_PWM_ENABLE 0x8D // 查询开关 PWM 功能 +#define QUERY_RADAR_PWM_DUTY 0x8E // 查询 PWM 占空比 +#define QUERY_RADAR_PULSE_WIDTH 0x8F // 查询供电脉冲宽度 +#define QUERY_RADAR_SENSING_MODE 0x11 // 查询感应模式 + +class MillimeterWaveRadar { +public: + // 雷达检测回调函数类型定义 + typedef std::function RadarDetectCallback; + enum class Result { + OK, + TIMEOUT, + CHECKSUM_ERROR, + INVALID_PARAM, + UART_ERROR + }; + + // 初始化 UART(使用 UART1, TX=GPIO39, RX=GPIO40) + bool Init(); + + // 设置参数(自动保存) + Result SetParameter(uint8_t paramId, uint32_t value); + + // 查询参数(返回值通过 outputValue 返回) + Result QueryParameter(uint8_t paramId, uint32_t &outputValue); + + Result QueryRawResponse(uint8_t paramId); + + // 注册雷达检测回调函数 + void OnRadarDetect(RadarDetectCallback callback); + + // 触发雷达检测回调函数(供IO扩展板调用) + void TriggerRadarDetectCallback(); + +private: + static constexpr uart_port_t UART_NUM = UART_NUM_1; + static constexpr int TX_PIN = 39; + static constexpr int RX_PIN = 40; + static constexpr uint32_t BAUD_RATE = 9600; + static constexpr uint32_t TIMEOUT_MS = 100; + + // 雷达检测回调函数 + RadarDetectCallback radar_detect_callback_ = nullptr; + + bool SendFrame(const uint8_t *frame, size_t len); + Result ReceiveFrame(uint8_t *buffer, size_t maxSize, size_t &receivedLen); + uint8_t CalculateChecksum(uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4); + bool IsWriteCommand(uint8_t cmd); +}; \ No newline at end of file diff --git a/main/05_executors/millimeter_wave_radar/module.cmake b/main/05_executors/millimeter_wave_radar/module.cmake new file mode 100644 index 0000000..6007d55 --- /dev/null +++ b/main/05_executors/millimeter_wave_radar/module.cmake @@ -0,0 +1,16 @@ +# main/05_executors/millimeter_wave_radar/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_EXEC_MILLIMETER_WAVE_RADAR AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] millimeter_wave_radar module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "05_executors/millimeter_wave_radar/millimeter_wave_radar.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "05_executors/millimeter_wave_radar" +) \ No newline at end of file diff --git a/main/05_executors/photo_interrupter/module.cmake b/main/05_executors/photo_interrupter/module.cmake new file mode 100644 index 0000000..7cc56bf --- /dev/null +++ b/main/05_executors/photo_interrupter/module.cmake @@ -0,0 +1,16 @@ +# main/05_executors/photo_interrupter/module.cmake + + +# 判断 menuconfig 中的开关 +if(NOT CONFIG_ENABLE_EXEC_PHOTO_INTERRUPTER AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] photo_interrupter module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "05_executors/photo_interrupter/photo_interrupter.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "05_executors/photo_interrupter" +) diff --git a/main/05_executors/photo_interrupter/photo_interrupter.cc b/main/05_executors/photo_interrupter/photo_interrupter.cc new file mode 100644 index 0000000..2a0eff3 --- /dev/null +++ b/main/05_executors/photo_interrupter/photo_interrupter.cc @@ -0,0 +1,129 @@ +/** + * @file photo_interrupter.cc + * @brief 光电限位开关类实现文件 + * + * 该类实现了光电限位开关的完整功能,包括初始化、中断处理、 + * 回调函数管理等。支持起始位置和结束位置两个限位开关的检测。 + */ + +#include "photo_interrupter.h" +#include + +#define TAG "PhotoInterrupter" ///< 日志标签,用于ESP-IDF日志系统 + +/** + * @brief 构造函数 + * + * 初始化PhotoInterrupter对象,设置默认状态 + * 目前构造函数为空,所有初始化操作在Init()方法中完成 + */ +PhotoInterrupter::PhotoInterrupter() +{ + +} + +/** + * @brief 析构函数 + * + * 清理资源,释放分配的内存 + * 目前析构函数为空,没有需要特殊清理的资源 + */ +PhotoInterrupter::~PhotoInterrupter() +{ + +} + +/** + * @brief 初始化限位开关 + * + * 执行限位开关的初始化操作,包括: + * 1. 输出初始化日志 + * 2. 设置默认的起始限位开关回调函数 + * 3. 设置默认的结束限位开关回调函数 + * + * 默认回调函数仅输出触发日志,用户可以通过OnStartTrigger()和OnEndTrigger() + * 方法覆盖这些默认回调函数 + */ +void PhotoInterrupter::Init() +{ + ESP_LOGI(TAG, "限位开关初始化"); + + // 设置默认回调函数 - 起始限位开关触发时输出日志 + OnStartTrigger([]() { + ESP_LOGI(TAG, "起始限位开关触发 - 默认回调"); + }); + + // 设置默认回调函数 - 结束限位开关触发时输出日志 + OnEndTrigger([]() { + ESP_LOGI(TAG, "结束限位开关触发 - 默认回调"); + }); +} + +/** + * @brief 处理限位开关中断 + * + * 当限位开关被触发时调用的中断处理函数 + * 目前仅输出中断触发日志,可以根据需要扩展更多处理逻辑 + */ +void PhotoInterrupter::Interrupt() +{ + ESP_LOGI(TAG, "限位开关中断触发"); +} + +/** + * @brief 设置起始限位开关触发回调函数 + * @param callback 回调函数对象,当起始限位开关被触发时执行 + * + * 允许用户自定义起始限位开关被触发时的处理逻辑。 + * 该函数会保存传入的回调函数,并输出注册日志。 + */ +void PhotoInterrupter::OnStartTrigger(std::function callback) +{ + on_start_trigger_ = callback; + ESP_LOGI(TAG, "注册起始限位开关回调函数"); +} + +/** + * @brief 设置结束限位开关触发回调函数 + * @param callback 回调函数对象,当结束限位开关被触发时执行 + * + * 允许用户自定义结束限位开关被触发时的处理逻辑。 + * 该函数会保存传入的回调函数,并输出注册日志。 + */ +void PhotoInterrupter::OnEndTrigger(std::function callback) +{ + on_end_trigger_ = callback; + ESP_LOGI(TAG, "注册结束限位开关回调函数"); +} + +/** + * @brief 触发起始限位开关回调 + * + * 当检测到起始限位开关被触发时,执行注册的回调函数。 + * 如果回调函数已设置(不为nullptr),则执行回调并输出触发日志。 + * + * 该函数通常由IO扩展板在检测到起始限位开关中断时调用。 + */ +void PhotoInterrupter::TriggerStartCallback() +{ + if (on_start_trigger_) { + ESP_LOGI(TAG, "触发起始限位开关回调"); + on_start_trigger_(); + } +} + +/** + * @brief 触发结束限位开关回调 + * + * 当检测到结束限位开关被触发时,执行注册的回调函数。 + * 如果回调函数已设置(不为nullptr),则执行回调并输出触发日志。 + * + * 该函数通常由IO扩展板在检测到结束限位开关中断时调用。 + */ +void PhotoInterrupter::TriggerEndCallback() +{ + if (on_end_trigger_) { + ESP_LOGI(TAG, "触发结束限位开关回调"); + on_end_trigger_(); + } +} \ No newline at end of file diff --git a/main/05_executors/photo_interrupter/photo_interrupter.h b/main/05_executors/photo_interrupter/photo_interrupter.h new file mode 100644 index 0000000..032f5ac --- /dev/null +++ b/main/05_executors/photo_interrupter/photo_interrupter.h @@ -0,0 +1,87 @@ +/** + * @file photo_interrupter.h + * @brief 光电限位开关类头文件 + * + * 该类用于处理光电限位开关的触发事件,支持起始位置和结束位置的检测。 + * 通过回调函数机制,当限位开关被触发时执行相应的处理逻辑。 + */ + +#ifndef PHOTO_INTERRUPTER_H +#define PHOTO_INTERRUPTER_H + +#include "io_expansion_board.h" +#include + +/** + * @class PhotoInterrupter + * @brief 光电限位开关类 + * + * 提供限位开关的初始化、中断处理和回调函数管理功能。 + * 支持起始位置和结束位置两个限位开关的检测。 + */ +class PhotoInterrupter { +public: + /** + * @brief 构造函数 + * + * 初始化PhotoInterrupter对象,设置默认状态 + */ + PhotoInterrupter(); + + /** + * @brief 析构函数 + * + * 清理资源,释放分配的内存 + */ + ~PhotoInterrupter(); + + /** + * @brief 初始化限位开关 + * + * 执行限位开关的初始化操作,包括设置默认回调函数 + */ + void Init(); + + /** + * @brief 处理限位开关中断 + * + * 当限位开关被触发时调用的中断处理函数 + */ + void Interrupt(); + + /** + * @brief 设置起始限位开关触发回调函数 + * @param callback 回调函数对象,当起始限位开关被触发时执行 + * + * 允许用户自定义起始限位开关被触发时的处理逻辑 + */ + void OnStartTrigger(std::function callback); + + /** + * @brief 设置结束限位开关触发回调函数 + * @param callback 回调函数对象,当结束限位开关被触发时执行 + * + * 允许用户自定义结束限位开关被触发时的处理逻辑 + */ + void OnEndTrigger(std::function callback); + + /** + * @brief 触发起始限位开关回调 + * + * 当检测到起始限位开关被触发时,执行注册的回调函数 + */ + void TriggerStartCallback(); + + /** + * @brief 触发结束限位开关回调 + * + * 当检测到结束限位开关被触发时,执行注册的回调函数 + */ + void TriggerEndCallback(); + +private: + std::function on_start_trigger_; ///< 起始限位开关回调函数 + std::function on_end_trigger_; ///< 结束限位开关回调函数 +}; + +#endif // PHOTO_INTERRUPTER_H \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..2b92ae0 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,150 @@ +# 获取当前选择的 demo 配置 +if(CONFIG_SWEEKAR_DEMO_1) + set(ACTIVE_DEMO "sweekar_demo1") +elseif(CONFIG_SWEEKAR_DEMO_2) + set(ACTIVE_DEMO "sweekar_demo2") +elseif(CONFIG_SWEEKAR_DEMO_3) + set(ACTIVE_DEMO "sweekar_demo3") +elseif(CONFIG_SWEEKAR_DEMO_4) + set(ACTIVE_DEMO "sweekar_demo4") +elseif(CONFIG_SWEEKAR_DEMO_5) + set(ACTIVE_DEMO "sweekar_demo5") +elseif(CONFIG_SWEEKAR_DEMO_6) + set(ACTIVE_DEMO "sweekar_demo6") +elseif(CONFIG_SWEEKAR_DEMO_7) + set(ACTIVE_DEMO "sweekar_demo7") +endif() + +# 初始化全局变量,用于收集各模块的信息 +set(MODULE_SRCS) +set(MODULE_INCLUDE_DIRS) +set(MODULE_REQUIRES) +set(MODULE_PRIV_REQUIRES) +set(MODULE_EMBED_TXTFILES) + +if(NOT DEFINED CONFIG_SWEEKAR_DEMO_SELECTION) + set(EARLY_EXPANSION TRUE) + message(STATUS "Early expansion pass: loading all module requirements") +else() + set(EARLY_EXPANSION FALSE) +endif() + +# =================================================================== +# 执行 Kconfig 自动组装脚本 (在配置阶段自动生成 Kconfig.modules) +# =================================================================== +execute_process( + COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/generate_kconfig_modules.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + RESULT_VARIABLE generate_kconfig_result +) +if(NOT generate_kconfig_result EQUAL 0) + message(FATAL_ERROR "Failed to generate Kconfig.modules") +endif() + +# ==================== 模块注册区 ==================== +# 定义需要扫描的架构层级目录 +set(SUBSYSTEM_DIRS + "04_hal" + "05_executors" + "05_service" + "06_behaviors" + "tests" +) + +# 扫描并加载所有启用的子模块(依赖各个模块中的 module.cmake) +foreach(dir ${SUBSYSTEM_DIRS}) + file(GLOB MODULE_FILES "${CMAKE_CURRENT_LIST_DIR}/${dir}/*/module.cmake") + foreach(mod_file ${MODULE_FILES}) + include(${mod_file}) + endforeach() +endforeach() + +# sweekar_demos 模块 (单独加载) +# include(${CMAKE_CURRENT_LIST_DIR}/sweekar_demos/module.cmake) + +# =================================================================== + +if(MODULE_REQUIRES) + list(REMOVE_DUPLICATES MODULE_REQUIRES) +endif() +if(MODULE_PRIV_REQUIRES) + list(REMOVE_DUPLICATES MODULE_PRIV_REQUIRES) +endif() + +message(STATUS "==== DEBUG ====") +message(STATUS "MODULE_REQUIRES: ${MODULE_REQUIRES}") +message(STATUS "MODULE_SRCS: ${MODULE_SRCS}") +message(STATUS "===============") + +idf_component_register( + SRCS + "main.cc" + # "tests/test.cc" + # "tests/korvo_port/korvo_port_runner.cc" + # "tests/korvo_port/ai_agent.c" + # "tests/korvo_port/audio_proc.c" + # "tests/korvo_port/rtc_proc.c" + # "../managed_components/espressif__zlib/zlib/contrib/minizip/unzip.c" + # "../managed_components/espressif__zlib/zlib/contrib/minizip/ioapi.c" + ${MODULE_SRCS} + INCLUDE_DIRS + "." + # "../managed_components/espressif__zlib/zlib/contrib/minizip" + # "05_executors/touch_sensor" + # "05_executors/aw_touch_sensor" + ${MODULE_INCLUDE_DIRS} + + REQUIRES + # lvgl + # esp_timer + # driver + # esp_driver_i2c + # esp_driver_i2s + # esp_event + # nvs_flash + # spi_flash + # esp_driver_gpio + # spiffs + # vfs + # esp_http_server + # log + # fatfs + # bt + # mbedtls + # audio_pipeline + # audio_hal + # audio_board + # esp_peripherals + # audio_stream + # audio_sal + # esp-adf-libs + # espressif__zlib + ${MODULE_REQUIRES} + PRIV_REQUIRES + # esp-qcloud + ${MODULE_PRIV_REQUIRES} + EMBED_TXTFILES + ${MODULE_EMBED_TXTFILES} +) +# 添加 C++ 标准库支持 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# 自动创建SPIFFS分区镜像 +# 这会在构建时自动生成audio.bin并在烧录时一并烧录 +# spiffs_create_partition_image(audio ../spiffs FLASH_IN_PROJECT) + +# 添加编译选项来忽略字符串常量转换警告 +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-write-strings) +target_compile_options(${COMPONENT_LIB} PRIVATE -DUSE_FILE32API) + + +set(LANG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/assets/lang_config.h") + +# 根据 boards 目录内部模块自动匹配当前选择的板卡 +include(${CMAKE_CURRENT_LIST_DIR}/02_physical/boards/module.cmake) + +if (CONFIG_VOLC_RTC_MODE) + set(COMPONENT_SRCS ${COMPONENT_SRCS} RtcBotUtils.c) +endif() +target_compile_definitions(${COMPONENT_LIB} PRIVATE IOAPI_NO_64) diff --git a/main/Kconfig.modules b/main/Kconfig.modules new file mode 100644 index 0000000..b1822dd --- /dev/null +++ b/main/Kconfig.modules @@ -0,0 +1,52 @@ +menu "Sweekar Subsystems Configuration" + + menu "04 Hal Modules" + config ENABLE_HAL_AUDIO_CODEC + bool "Enable Audio Codec" + default y + help + Enable the audio_codec module in 04_hal. + config ENABLE_HAL_BASE_BOARD_HAL + bool "Enable Base Board Hal" + default y + help + Enable the base_board_hal module in 04_hal. + config ENABLE_HAL_STEPPER_DRIVER + bool "Enable Stepper Driver" + default y + help + Enable the stepper_driver module in 04_hal. + endmenu + + menu "05 Executors Modules" + config ENABLE_EXEC_ADC_BATTERY_MONITOR + bool "Enable Adc Battery Monitor" + default y + help + Enable the adc_battery_monitor module in 05_executors. + config ENABLE_EXEC_BASE_BOARD_CONTROL + bool "Enable Base Board Control" + default y + help + Enable the base_board_control module in 05_executors. + config ENABLE_EXEC_GYROSCOPE + bool "Enable Gyroscope" + default y + help + Enable the gyroscope module in 05_executors. + config ENABLE_EXEC_MILLIMETER_WAVE_RADAR + bool "Enable Millimeter Wave Radar" + default y + help + Enable the millimeter_wave_radar module in 05_executors. + config ENABLE_EXEC_PHOTO_INTERRUPTER + bool "Enable Photo Interrupter" + default y + help + Enable the photo_interrupter module in 05_executors. + endmenu + + menu "tests Modules" + endmenu + +endmenu diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild new file mode 100644 index 0000000..e21b00a --- /dev/null +++ b/main/Kconfig.projbuild @@ -0,0 +1,544 @@ +menu "Sweekar Application Configuration" + + # ============================================================== + # 模块化配置自动扫描引入点 + # 这里会自动加载 main/Kconfig.modules 文件 + # ============================================================== + rsource "Kconfig.modules" + + choice BOARD_VERSION_SELECTION + prompt "Select Board Version" + default BOARD_VERSION_V1_1_11 + help + Select the hardware board version to use. + + config BOARD_VERSION_V1_1_11 + bool "Board v1.1.11" + help + Use board version v1.1.11 + + config BOARD_VERSION_V1_1_12 + bool "Board v1.1.12" + help + Use board version v1.1.12 + + config BOARD_VERSION_V1_1_13 + bool "Board v1.1.13" + help + Use board version v1.1.13 + + config BOARD_VERSION_V1_1_14 + bool "Board v1.1.14" + help + Use board version v1.1.14 + + config BOARD_VERSION_V1_1_16 + bool "Board v1.1.16" + help + Use board version v1.1.16 + + config BOARD_VERSION_V1_1_17 + bool "Board v1.1.17" + help + Use board version v1.1.17 + + config BOARD_VERSION_V1_1_18 + bool "Board v1.1.18" + help + Use board version v1.1.18 + + endchoice + + choice STEPPER_DRIVER_IMPL + prompt "Stepper Driver Implementation" + default STEPPER_DRIVER_DRV8428 + help + Select the stepper motor driver chip. + + config STEPPER_DRIVER_DRV8428 + bool "TI DRV8428" + help + Use TI DRV8428 stepper motor driver. + + config STEPPER_DRIVER_AW8646 + bool "Awinic AW8646" + help + Use Awinic AW8646 stepper motor driver. + + endchoice + + choice LINEAR_MOTOR_TYPE + prompt "Linear Motor Type" + default LINEAR_MOTOR_LD0832 + help + Select the linear motor type. + + config LINEAR_MOTOR_LD0832 + bool "LD0832 (1.8V, 235Hz, 21 ohm)" + help + Use LD0832 linear motor. + + config LINEAR_MOTOR_LD0625 + bool "LD0625 (0.7V, 260Hz, 8 ohm)" + help + Use LD0625 linear motor. + + endchoice + + choice AUDIO_DECODER_SUPPORT + prompt "Audio Decoder" + default AUDIO_SUPPORT_OPUS_DECODER + + config AUDIO_SUPPORT_G711A_DECODER + bool "AUDIUO-G711-DECODER" + + config AUDIO_SUPPORT_OPUS_DECODER + bool "AUDIO-OPUS-DECODER" + + config AUDIO_SUPPORT_AAC_DECODER + bool "AUDIO-AAC-DECODER" + + endchoice + + choice AUDIO_CONVERSATION_MODE_SUPPORT + prompt "Conversion Mode" + default CONTINUOUS_CONVERSATION_MODE + + config LANGUAGE_WAKEUP_MODE + select USE_WAKENET + select SR_WN_WN9_HILEXIN + bool "LANGUAGE-WAKEUP-MODE" + + config CONTINUOUS_CONVERSATION_MODE + bool "CONTINUOUS-CONVERSATION-MODE" + + endchoice + + choice RTC_MODE_CHOICE + prompt "rtc mode choice" + default COZE_REQUEST + help + Select rtc request mode + + config COZE_REQUEST + bool "enable coze request" + + config RTC_TOKEN_REQUEST + bool "enable rtc token request" + endchoice + + choice RTC_BACKEND_PROVIDER + prompt "RTC backend provider" + default RTC_BACKEND_VOLC + help + Select the RTC SDK backend used by the AI RTC service. + + config RTC_BACKEND_VOLC + bool "VolcEngine RTC (current default)" + + config RTC_BACKEND_AGORA + bool "Agora RTC (migration target)" + endchoice + + menu "Agora RTC Configuration" + depends on RTC_BACKEND_AGORA + + config AGORA_SDK_ENABLED + bool "Agora SDK integrated" + default n + help + Enable this option after adding Agora RTC SDK headers/libs. + + config AGORA_APP_ID + string "Agora App ID" + default "" + + config AGORA_TOKEN + string "Agora RTC Token" + default "" + + config AGORA_CHANNEL + string "Agora Channel Name" + default "default" + + config AGORA_UID + int "Agora Local UID" + default 0 + endmenu + + choice TOUCH_SENSOR_IMPL + prompt "Touch Sensor Implementation" + default TOUCH_SENSOR_IMPL_ESP + + config TOUCH_SENSOR_IMPL_ESP + bool "ESP32 Touch Pad" + help + Use ESP32 internal touch sensor (via touch_pad). + + config TOUCH_SENSOR_IMPL_AW + bool "Awinic Touch Sensor" + help + Use Awinic AW933xx touch sensor. + + endchoice + + choice BASE_BOARD_COMM_INTERFACE + prompt "Base Board Communication Interface" + default BASE_BOARD_COMM_UART + help + Select the communication interface for the base board. + + config BASE_BOARD_COMM_UART + bool "UART (Legacy)" + help + Use UART for base board communication. + + config BASE_BOARD_COMM_I2C + bool "I2C (v1.1.16+)" + help + Use I2C for base board communication. + endchoice + + + config COZE_URL + string "COZE-URL" + default "http://115.190.92.216:8899/startvoicechat" + depends on COZE_REQUEST + + config COZE_AUTHORIZATION + string "COZE-AUTHORIZATION" + default "af78e3067582ac8c04ba20174410bd1" + depends on COZE_REQUEST + + config COZE_BOTID + string "COZE-BOTID" + default "ep-20241104163951-sdmjc" + depends on COZE_REQUEST + + + config RTC_TOKEN + depends on RTC_TOKEN_REQUEST + config APPID + string "appid" + depends on RTC_TOKEN_REQUEST + + config ROOMID + string "roomid" + depends on RTC_TOKEN_REQUEST + + config UID + string "userid" + depends on RTC_TOKEN_REQUEST + + config TOKEN + string "token" + depends on RTC_TOKEN_REQUEST + + config ENABLE_RECORDER_DEBUG + bool "enable recorder debug save to sdcard" + default "n" + help + Save the data processed by AEC to the SD card. The saved file name is "rec.pcm", and by default, 30 seconds' worth of data will be saved. + + config ENABLE_OTA_UPDATE + bool "Enable OTA Update" + default y + help + Enable or disable OTA + + choice OTA_UPDATE_STRATEGY + prompt "OTA Update Strategy" + default OTA_MODE_NOTIFY + depends on ENABLE_OTA_UPDATE + help + Select the OTA update strategy + + config OTA_MODE_AUTO + bool "Auto Update (Immediate)" + help + Automatically download and install updates immediately + + config OTA_MODE_NOTIFY + bool "Notify Update (Wait for Confirmation)" + help + Wait for user confirmation before starting the OTA update process. + endchoice + + endmenu + +menu "QCloud COS Download Configuration" + + config QCLOUD_COS_QUEUE_SIZE + int "COS download queue size" + default 20 + range 1 100 + help + Number of pending COS download messages that can be buffered. + + config QCLOUD_COS_WORKER_COUNT + int "COS worker thread count" + default 1 + range 1 4 + help + Number of worker threads for COS download processing. + + config QCLOUD_COS_WORKER_STACK_SIZE + int "COS worker stack size (bytes)" + default 16384 + range 4096 65536 + help + Stack size in bytes for each COS worker thread. + + config QCLOUD_COS_HTTP_BUFFER_SIZE + int "COS HTTP download buffer size (bytes)" + default 32768 + range 4096 131072 + help + Buffer size used by HTTP read and esp_http_client internal buffer. + + config QCLOUD_COS_ZIP_UNZIP_BUFFER_SIZE + int "COS ZIP unzip read buffer size (bytes)" + default 32768 + range 4096 131072 + help + Buffer size used when reading compressed data from ZIP entries. + + config QCLOUD_COS_SD_WRITE_BUFFER_SIZE + int "COS SD write buffer size (bytes)" + default 32768 + range 4096 131072 + help + stdio write buffer size for HTTP download and ZIP extraction output. + +endmenu + + + menu "WiFi Configuration" + config ENABLE_HARDCODED_WIFI + bool "Enable Hardcoded WiFi Credentials" + default n + help + If enabled, the device will first attempt to connect to the specified WiFi network. + If disabled or connection fails, it will fall back to SmartConfig/QCloud provisioning. + + config HARDCODED_WIFI_SSID + string "WiFi SSID" + depends on ENABLE_HARDCODED_WIFI + default "" + help + The SSID of the WiFi network to connect to. + Leave empty to fallback to SmartConfig/QCloud provisioning. + + config HARDCODED_WIFI_PASSWORD + string "WiFi Password" + depends on ENABLE_HARDCODED_WIFI + default "" + help + The password of the WiFi network to connect to. + endmenu + + # Sweekar Demo Selection + menu "Sweekar Demo Selection" + + choice SWEEKAR_DEMO_SELECTION + prompt "Select Active Demo" + default SWEEKAR_DEMO_3 + help + Select which demo to build and run. Only one demo can be active at a time. + + config SWEEKAR_DEMO_2 + bool "Demo 2" + + config SWEEKAR_DEMO_3 + bool "Demo 3 - Basic Pet Game" + + config SWEEKAR_DEMO_5 + bool "Demo 5" + + config SWEEKAR_DEMO_6 + bool "Demo 6 - Layered Architecture" + + config SWEEKAR_DEMO_7 + bool "Demo 7 - Microkernel Architecture" + + endchoice + + config ACTIVE_DEMO_NAME + string + default "sweekar_demo2" if SWEEKAR_DEMO_2 + default "sweekar_demo3" if SWEEKAR_DEMO_3 + default "sweekar_demo5" if SWEEKAR_DEMO_5 + default "sweekar_demo6" if SWEEKAR_DEMO_6 + default "sweekar_demo7" if SWEEKAR_DEMO_7 + + config SWEEKAR_DEMO6_HATCHING5_TVC + bool "Use TVC version for Hatching 5" + depends on SWEEKAR_DEMO_6 + default n + help + If enabled, plays EGG_EMORAISING_HATCHING5_TVC instead of EGG_EMORAISING_HATCHING5_NORMAL during the final hatching stage. + + endmenu + + menu "ESP QCloud Example Configuration" + menu "Light development board selection " + choice LIGHTBULB_DEMO_DRIVER_SELECT + bool "Demo select" + default LIGHTBULB_DEMO_DRIVER_SELECT_WS2812 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32H2 + default LIGHTBULB_DEMO_DRIVER_SELECT_PWM + help + Select the driver type you want to demonstrate + + config LIGHTBULB_DEMO_DRIVER_SELECT_SM2135E + bool "Demo SM2135E" + config LIGHTBULB_DEMO_DRIVER_SELECT_WS2812 + bool "Demo WS2812" + config LIGHTBULB_DEMO_DRIVER_SELECT_PWM + bool "Demo PWM" + endchoice + + config WS2812_LED_GPIO + int "WS2812 LED GPIO" + default 8 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32H2 + default 18 if IDF_TARGET_ESP32S2 + default 48 if IDF_TARGET_ESP32S3 + default 4 if IDF_TARGET_ESP32C2 + default 18 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_WS2812 + help + Set the WS2812 RGB LED GPIO. + + config WS2812_LED_NUM + int "WS2812 LED NUM" + default 1 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_WS2812 + help + Set the WS2812 LED NUM. + + config PWM_FREQ_HZ + int "PWM Frequency (hz)" + default 4000 + range 2000 5000 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_PWM + help + Set the PWM Frequency (hz). + + config PWM_RED_GPIO + int "PWM Red GPIO" + default 10 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 + default 15 if IDF_TARGET_ESP32S3 + default 0 if IDF_TARGET_ESP32C2 + default 23 if IDF_TARGET_ESP32H2 + default 25 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_PWM + help + Set the PWM Red LED GPIO. + + config PWM_GREEN_GPIO + int "PWM Green GPIO" + default 6 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 + default 16 if IDF_TARGET_ESP32S3 + default 1 if IDF_TARGET_ESP32C2 + default 24 if IDF_TARGET_ESP32H2 + default 26 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_PWM + help + Set the PWM Green LED GPIO. + + config PWM_BLUE_GPIO + int "PWM Blue GPIO" + default 7 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 + default 17 if IDF_TARGET_ESP32S3 + default 8 if IDF_TARGET_ESP32C2 + default 25 if IDF_TARGET_ESP32H2 + default 27 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_PWM + help + Set the PWM Blue LED GPIO. + + config SM2135E_IIC_CLK_GPIO + int "SM2135E IIC CLK GPIO" + default 4 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_SM2135E + help + Set the SM2135E CLK GPIO. + + config SM2135E_IIC_SDA_GPIO + int "SM2135E IIC SDA GPIO" + default 5 + depends on LIGHTBULB_DEMO_DRIVER_SELECT_SM2135E + help + Set the SM2135E SDA GPIO. + + + + + + + + endmenu + menu "Light provisioning network selection" + config LIGHT_PROVISIONING_SMARTCONFIG + bool "Enable SmartConfig's smart network configuration method" + default n + help + Enable SmartConfig's smart network configuration method + + config LIGHT_PROVISIONING_SOFTAPCONFIG + bool "Enable SoftAP's network configuration method" + default y + help + "Enable SoftAP's network configuration method" + + config LIGHT_PROVISIONING_BLECONFIG + depends on QCLOUD_ENABLE_BLECONFIG + bool "Enable BLE's network configuration method" + default y + help + "Enable BLE's network configuration method" + + config LIGHT_PROVISIONING_SOFTAPCONFIG_SSID + depends on LIGHT_PROVISIONING_SOFTAPCONFIG + string "SoftAP ssid" + default "tcloud_XXX" + help + SSID (network name) for the phone to connect to. + + config LIGHT_PROVISIONING_BLECONFIG_NAME + depends on LIGHT_PROVISIONING_BLECONFIG + string "BLE name" + default "Qcloud" + help + BLE for the phone to connect to. + endmenu + + config LIGHT_REBOOT_UNBROKEN_COUNT_RESET + int "More than this number of continuous uninterrupted restarts triggers a reset of the device" + range 3 15 + default 5 + help + Devices without buttons trigger a device reset through continuous restart. + + config LIGHT_DEBUG + bool "The device will be in debug mode" + default n + help + Debug mode device performance is only used during development. + + + + choice QCLOUD_WIFI_CONFIG_METHOD + prompt "WiFi Provisioning Method" + default QCLOUD_WIFI_SMARTCONFIG + help + Select how the device should connect to WiFi. + + config QCLOUD_WIFI_PROVISIONING + bool "WeChat Mini-Program (connectWiFi)" + + config QCLOUD_WIFI_SMARTCONFIG + bool "ESP-Touch / SmartConfig (connectWiFi_smartconfig)" + endchoice + + endmenu + diff --git a/main/MODULE_DEVELOPMENT_GUIDE.md b/main/MODULE_DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..3293538 --- /dev/null +++ b/main/MODULE_DEVELOPMENT_GUIDE.md @@ -0,0 +1,124 @@ +# Sweekar-ESP32 模块化开发与构建系统指南 + +为了支持多人无冲突协作,并保持系统代码的高内聚、低耦合,Sweekar-ESP32 的 CMake 构建系统采用 **“透明化脚手架 (Transparent Scaffolding) + Kconfig 自动组装”** 架构。 + +在这套架构下,开发者在添加新的底层硬件驱动、业务服务或测试代码时,**完全不需要修改任何顶层文件(包括 `main/CMakeLists.txt` 和 `main/Kconfig.projbuild`)**,构建系统会自动帮你生成必要的模板文件,而后续的修改则完全交由你掌控,没有任何“黑魔法”。 + +以下是添加新模块的标准流程和机制说明。 + +--- + +## 1. 确定模块归属的目录分类 +系统顶层会自动扫描以下目录下的子文件夹: +- `04_hal/` : 硬件抽象层代码(如 Codec 驱动、马达驱动等) +- `05_executors/` : 执行器组件(如 AudioPlayer, Display, 传感器读取等) +- `05_service/` : 后台服务组件(如 AI RTC、QCloud、WiFi 连接等) +- `06_behaviors/` : 行为逻辑组件 +- `tests/` : 各种测试模块(如麦克风测试、扬声器测试等) +- `sweekar_demos/` : 各种组装好的 Demo 入口 + +*假设我们要添加一个专门测试麦克风的模块,名为 `test_mic`,它显然应该归属于 `tests/` 目录。* + +## 2. 新建模块与文件 (自动化部分) +在对应的分类下新建文件夹,并将你的源码和头文件放进去: +```bash +mkdir -p main/tests/test_mic +touch main/tests/test_mic/test_mic.cc +touch main/tests/test_mic/test_mic.h +``` + +**【核心自动化机制】** +此时,你**不需要**手动去写 CMake 文件。你只需要在终端执行一次: +```bash +idf.py reconfigure +``` +(或者 `idf.py build` 如果这是你第一次编译该工程)。 + +构建系统中的 Python 脚本 (`generate_kconfig_modules.py`) 会在配置阶段自动扫描目录。当它发现 `tests/test_mic` 目录下有源码但**没有** `module.cmake` 时,它会**自动为你生成一个 `module.cmake` 脚手架文件**。 + +自动生成的文件内容类似于: +```cmake +# main/tests/test_mic/module.cmake +# 自动生成的模块脚手架,可直接手工修改。 +# 如果后续添加了新文件,可以手工在此处添加,或者删除本文件让脚本重新生成。 + +if(NOT CONFIG_ENABLE_TEST_TEST_MIC AND NOT EARLY_EXPANSION) + message(STATUS " [Skip] test_mic module disabled by menuconfig.") + return() +endif() + +list(APPEND MODULE_SRCS + "tests/test_mic/test_mic.cc" +) + +list(APPEND MODULE_INCLUDE_DIRS + "tests/test_mic" +) + +# 需要公开依赖时,在这里手工补充。例如: +# list(APPEND MODULE_REQUIRES +# esp_timer +# driver +# ) +``` + +## 3. 后续维护与修改 (手工部分) + +**“透明化”原则**:一旦 `module.cmake` 被生成,Python 脚本就**再也不会覆盖或修改它**。它现在完全属于你。 + +### 3.1 当你新增/删除源文件时 +你有两种选择: +1. **手动修改(推荐)**:直接打开 `module.cmake`,在 `list(APPEND MODULE_SRCS ...)` 中手动添加或删除文件路径。所见即所得,非常直观。 +2. **重新生成(偷懒大法)**:如果你一口气加了十几个文件,不想手写路径,你可以**直接删除**这个 `module.cmake` 文件,然后运行 `idf.py reconfigure`,脚本会重新帮你扫描并生成一份包含所有最新文件的脚手架。 + +### 3.2 当你需要依赖其他库时 +直接在 `module.cmake` 底部,取消注释并填入你需要的 ESP-IDF 组件名(如 `esp_adc`, `audio_pipeline` 等): +```cmake +list(APPEND MODULE_REQUIRES + audio_pipeline + driver +) +``` + +## 4. 编写模块内部的 `Kconfig` (可选) +系统会自动为你的模块生成一个默认的配置开关(例如 `CONFIG_ENABLE_TEST_TEST_MIC`)。 +- `tests/` 目录下的默认开关为关闭(`default n`)。 +- 其他目录默认开启(`default y`)。 + +如果你需要为你的模块定义更复杂的配置选项(比如定义默认开启、依赖其他模块、或者包含子菜单),你可以在模块目录(`main/tests/test_mic/`)下建立一个名为 `Kconfig` 的文件: + +```kconfig +# main/tests/test_mic/Kconfig +config ENABLE_TEST_MIC + bool "Enable Microphone Test Module" + default n + help + Enable this to compile the microphone testing routines. +``` +> 系统如果扫描到模块自带 `Kconfig`,就会使用你写的,而不再自动生成默认开关。 + +## 5. 编译与配置 +1. 运行 `idf.py menuconfig`。 +2. 导航到 `Sweekar Subsystems Configuration` -> 对应的目录(如 `Tests Modules`)。 +3. 找到你刚刚添加的 `Enable test_mic`(或你自己定义的开关名字),按空格键勾选 `[*]`。 +4. 保存退出后,运行 `idf.py build` 即可! + +--- + +## ⚠️ 常见问题与注意事项 + +### Q: 为什么我新建了一个文件夹或者删除了 `module.cmake`,运行 `idf.py build` 没有反应? +这是 CMake 的缓存机制导致的。如果你只是增删了目录或文件,但没有修改任何现有的 `CMakeLists.txt` 内容,CMake 会跳过配置阶段直接去编译,导致 Python 扫描脚本没有运行。 +**解决办法**:遇到这种情况,请明确告诉 IDF 重新配置: +```bash +idf.py reconfigure +``` +然后再运行 `idf.py build`。 + +### Q: 为什么要加 `NOT EARLY_EXPANSION`? +ESP-IDF 的构建系统分为两步: +1. **依赖解析阶段 (Early Expansion)**:此时 CMake 快速扫描所有组件收集 `REQUIRES`,但此时 `menuconfig` 的配置尚未加载,所有的 `CONFIG_xxx` 都是空值! +2. **实际构建阶段 (Build Pass)**:此时配置加载完毕,开始真实编译。 + +如果在 `module.cmake` 顶部仅仅写 `if(NOT CONFIG_ENABLE_XXX) return()`,会导致在依赖解析阶段该模块就被错误地跳过了,从而丢失其依赖声明,引发“找不到头文件”的编译灾难。 +加入 `AND NOT EARLY_EXPANSION` 后,能保证所有启用的组件依赖都被正确收集到构建树中,实现完美解耦。 diff --git a/main/generate_kconfig_modules.py b/main/generate_kconfig_modules.py new file mode 100644 index 0000000..59cfa21 --- /dev/null +++ b/main/generate_kconfig_modules.py @@ -0,0 +1,247 @@ +import os +import re + +# The directories to scan for Kconfig files +SUBSYSTEM_DIRS = ["04_hal", "05_executors", "05_service", "06_behaviors", "tests"] +TEMPLATE_SCAN_DIRS = SUBSYSTEM_DIRS + ["sweekar_demos"] +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +OUTPUT_FILE = os.path.join(BASE_DIR, "Kconfig.modules") + +SOURCE_EXTENSIONS = (".c", ".cc", ".cpp") +HEADER_EXTENSIONS = (".h", ".hpp") +SKIP_DIR_NAMES = {"build", "__pycache__"} + + +def iter_child_dirs(parent_dir): + if not os.path.exists(parent_dir): + return [] + return sorted( + [ + name + for name in os.listdir(parent_dir) + if os.path.isdir(os.path.join(parent_dir, name)) and not name.startswith(".") + ] + ) + + +def compute_default_config_name(subsystem_dir, module_name): + if subsystem_dir == "tests": + prefix = "TEST" + else: + prefix = subsystem_dir.split("_")[1].upper() + if prefix == "EXECUTORS": + prefix = "EXEC" + elif prefix == "BEHAVIORS": + prefix = "BEHAV" + elif prefix == "SERVICE": + prefix = "SVC" + return f"CONFIG_ENABLE_{prefix}_{module_name.upper()}" + + +def contains_module_files(module_dir): + for _, _, files in os.walk(module_dir): + for filename in files: + if filename.endswith(SOURCE_EXTENSIONS) or filename.endswith(HEADER_EXTENSIONS): + return True + return False + + +def collect_existing_module_cmake_files(): + module_files = [] + for root, _, files in os.walk(BASE_DIR): + if "module.cmake" in files: + module_files.append(os.path.join(root, "module.cmake")) + return sorted(module_files) + + +def collect_module_files(module_dir): + srcs = [] + include_dirs = {"."} + + for root, dirs, files in os.walk(module_dir): + dirs[:] = [ + d for d in dirs + if d not in SKIP_DIR_NAMES and not d.startswith(".") + ] + + rel_root = os.path.relpath(root, module_dir).replace(os.sep, "/") + rel_root = "." if rel_root == "." else rel_root + + has_header = False + for filename in files: + if filename.endswith(SOURCE_EXTENSIONS): + rel_path = os.path.normpath(os.path.join(rel_root, filename)).replace(os.sep, "/") + if rel_path.startswith("./"): + rel_path = rel_path[2:] + srcs.append(rel_path) + elif filename.endswith(HEADER_EXTENSIONS): + has_header = True + + if has_header: + include_dirs.add(rel_root) + + srcs = sorted(set(srcs)) + include_dirs = sorted(set(include_dirs)) + return srcs, include_dirs + + +def is_already_aggregated(rel_module_dir, existing_module_files): + rel_module_prefix = f'"{rel_module_dir}/' + for module_file in existing_module_files: + try: + with open(module_file, "r", encoding="utf-8") as f: + content = f.read() + except OSError: + continue + + if rel_module_prefix in content: + return True + return False + + +def render_module_template(subsystem_dir, module_name, module_dir): + srcs, include_dirs = collect_module_files(module_dir) + rel_module_dir = os.path.relpath(module_dir, BASE_DIR).replace(os.sep, "/") + + lines = [ + f"# main/{subsystem_dir}/{module_name}/module.cmake", + "# 自动生成的模块脚手架,可直接手工修改。", + "# 如果后续添加了新文件,可以手工在此处添加,或者删除本文件让脚本重新生成。", + "", + ] + + if subsystem_dir != "sweekar_demos": + config_name = compute_default_config_name(subsystem_dir, module_name) + lines.extend( + [ + f"if(NOT {config_name} AND NOT EARLY_EXPANSION)", + f' message(STATUS " [Skip] {module_name} module disabled by menuconfig.")', + " return()", + "endif()", + "", + ] + ) + + lines.append("list(APPEND MODULE_SRCS") + for src in srcs: + lines.append(f' "{rel_module_dir}/{src}"') + lines.append(")") + lines.append("") + + lines.append("list(APPEND MODULE_INCLUDE_DIRS") + for inc in include_dirs: + if inc == ".": + lines.append(f' "{rel_module_dir}"') + else: + lines.append(f' "{rel_module_dir}/{inc}"') + lines.append(")") + lines.append("") + + lines.extend( + [ + "# 需要公开依赖时,在这里手工补充。例如:", + "# list(APPEND MODULE_REQUIRES", + "# esp_timer", + "# driver", + "# )", + "", + "# 需要私有依赖时,在这里手工补充。例如:", + "# list(APPEND MODULE_PRIV_REQUIRES", + "# nvs_flash", + "# )", + "", + ] + ) + return "\n".join(lines) + + +def ensure_module_templates(): + created_files = [] + existing_module_files = collect_existing_module_cmake_files() + + for subsystem_dir in TEMPLATE_SCAN_DIRS: + dir_path = os.path.join(BASE_DIR, subsystem_dir) + if not os.path.exists(dir_path): + continue + + for module_name in iter_child_dirs(dir_path): + if subsystem_dir == "sweekar_demos" and not module_name.startswith("sweekar_demo"): + continue + + module_dir = os.path.join(dir_path, module_name) + module_cmake = os.path.join(module_dir, "module.cmake") + if os.path.exists(module_cmake): + continue + + if not contains_module_files(module_dir): + continue + + rel_module_dir = os.path.relpath(module_dir, BASE_DIR).replace(os.sep, "/") + if subsystem_dir != "sweekar_demos" and is_already_aggregated(rel_module_dir, existing_module_files): + continue + + template_content = render_module_template(subsystem_dir, module_name, module_dir) + with open(module_cmake, "w", encoding="utf-8") as f: + f.write(template_content) + created_files.append(module_cmake) + + return created_files + +def generate_kconfig_modules(): + created_templates = ensure_module_templates() + kconfig_content = 'menu "Sweekar Subsystems Configuration"\n\n' + + for d in SUBSYSTEM_DIRS: + dir_path = os.path.join(BASE_DIR, d) + if not os.path.exists(dir_path): + continue + + # Create a pretty menu name from the directory name + # e.g., "05_executors" -> "05 Executors Modules" + parts = d.split('_') + menu_name = parts[0] + " " + " ".join([p.capitalize() for p in parts[1:]]) + " Modules" + + kconfig_content += f' menu "{menu_name}"\n' + + # Sort subdirectories to ensure consistent ordering + subdirs = iter_child_dirs(dir_path) + + for module_name in subdirs: + module_dir = os.path.join(dir_path, module_name) + kconfig_file = os.path.join(module_dir, "Kconfig") + + if os.path.exists(kconfig_file): + # If the module has its own Kconfig, just source it + # Make sure the source path is relative to the project root or uses proper env vars + # Since Kconfig.projbuild is processed relative to project root usually + rel_path = os.path.join("main", d, module_name, "Kconfig") + kconfig_content += f' source "{rel_path}"\n' + else: + # If no Kconfig exists, generate a default toggle for it + config_name = compute_default_config_name(d, module_name) + pretty_name = " ".join([word.capitalize() for word in module_name.split('_')]) + + # Default to 'n' for tests, 'y' for everything else + default_val = 'n' if d == 'tests' else 'y' + + kconfig_content += f''' config {config_name.replace("CONFIG_", "")} + bool "Enable {pretty_name}" + default {default_val} + help + Enable the {module_name} module in {d}. +''' + kconfig_content += ' endmenu\n\n' + + kconfig_content += 'endmenu\n' + + with open(OUTPUT_FILE, 'w') as f: + f.write(kconfig_content) + + print(f"Generated {OUTPUT_FILE} successfully.") + if created_templates: + print("Generated module.cmake templates:") + for path in created_templates: + print(f" - {path}") + +if __name__ == "__main__": + generate_kconfig_modules() \ No newline at end of file diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..8768341 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,19 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=5.0.0' + + # lvgl/lvgl: ^8.* + # 添加button组件依赖 + # espressif/button: ~4.1.3 + + # 78/esp-wifi-connect: ~2.4.3 + + espressif/adc_battery_estimation: ^0.2.0 + + # lightbulb_driver: ^0.0.9 + # espressif/touch_slider_sensor: ^0.2.0 + + # espressif/touch_button_sensor: ^0.2.0 + diff --git a/main/main.cc b/main/main.cc new file mode 100644 index 0000000..c9d988f --- /dev/null +++ b/main/main.cc @@ -0,0 +1,5 @@ + + +extern "C" void app_main() { + +} \ No newline at end of file