diff --git a/CMakeLists.txt b/CMakeLists.txt index 61c41a0..18abeae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(${CMAKE_PROJECT_NAME} Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c Core/Bsp/BSP_Device/spi_st7735s/fonts.c Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.c + Core/Bsp/MultiButton-master/Multi_Button.c ) # Add STM32CubeMX generated sources add_subdirectory(cmake/stm32cubemx) @@ -68,6 +69,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE Core/Bsp/BSP_Device/device_ctrl Core/Bsp/BSP_Device/spi_st7735s Core/Bsp/BSP_Device/bsp_mp3 + Core/Bsp/MultiButton-master ) # Add project symbols (macros) diff --git a/Development_Docs/MP3/0001.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0001.mp3 similarity index 100% rename from Development_Docs/MP3/0001.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0001.mp3 diff --git a/Development_Docs/MP3/0002.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0002.mp3 similarity index 100% rename from Development_Docs/MP3/0002.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0002.mp3 diff --git a/Development_Docs/MP3/0003.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0003.mp3 similarity index 100% rename from Development_Docs/MP3/0003.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0003.mp3 diff --git a/Development_Docs/MP3/0004.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0004.mp3 similarity index 100% rename from Development_Docs/MP3/0004.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0004.mp3 diff --git a/Development_Docs/MP3/0005.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0005.mp3 similarity index 100% rename from Development_Docs/MP3/0005.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0005.mp3 diff --git a/Development_Docs/MP3/0006.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0006.mp3 similarity index 100% rename from Development_Docs/MP3/0006.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0006.mp3 diff --git a/Development_Docs/MP3/0007.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0007.mp3 similarity index 100% rename from Development_Docs/MP3/0007.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0007.mp3 diff --git a/Development_Docs/MP3/0008.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0008.mp3 similarity index 100% rename from Development_Docs/MP3/0008.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0008.mp3 diff --git a/Development_Docs/MP3/0009.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0009.mp3 similarity index 100% rename from Development_Docs/MP3/0009.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0009.mp3 diff --git a/Development_Docs/MP3/0010.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0010.mp3 similarity index 100% rename from Development_Docs/MP3/0010.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0010.mp3 diff --git a/Development_Docs/MP3/0011.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0011.mp3 similarity index 100% rename from Development_Docs/MP3/0011.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0011.mp3 diff --git a/Development_Docs/MP3/0012.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0012.mp3 similarity index 100% rename from Development_Docs/MP3/0012.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0012.mp3 diff --git a/Development_Docs/MP3/0013.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0013.mp3 similarity index 100% rename from Development_Docs/MP3/0013.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0013.mp3 diff --git a/Development_Docs/MP3/0014.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0014.mp3 similarity index 100% rename from Development_Docs/MP3/0014.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0014.mp3 diff --git a/Development_Docs/MP3/0015.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0015.mp3 similarity index 100% rename from Development_Docs/MP3/0015.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0015.mp3 diff --git a/Development_Docs/MP3/0016.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0016.mp3 similarity index 100% rename from Development_Docs/MP3/0016.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0016.mp3 diff --git a/Development_Docs/MP3/0017.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0017.mp3 similarity index 100% rename from Development_Docs/MP3/0017.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0017.mp3 diff --git a/Development_Docs/MP3/0018.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0018.mp3 similarity index 100% rename from Development_Docs/MP3/0018.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0018.mp3 diff --git a/Development_Docs/MP3/0019.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0019.mp3 similarity index 100% rename from Development_Docs/MP3/0019.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0019.mp3 diff --git a/Development_Docs/MP3/0020.mp3 b/Core/Bsp/BSP_Device/bsp_mp3/MP3/0020.mp3 similarity index 100% rename from Development_Docs/MP3/0020.mp3 rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/0020.mp3 diff --git a/Development_Docs/MP3/LIST/mp3_integration_summary.md b/Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_integration_summary.md similarity index 100% rename from Development_Docs/MP3/LIST/mp3_integration_summary.md rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_integration_summary.md diff --git a/Development_Docs/MP3/LIST/mp3_usage_example.py b/Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_usage_example.py similarity index 100% rename from Development_Docs/MP3/LIST/mp3_usage_example.py rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/mp3_usage_example.py diff --git a/Development_Docs/MP3/LIST/rename_mp3_files.py b/Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/rename_mp3_files.py similarity index 100% rename from Development_Docs/MP3/LIST/rename_mp3_files.py rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/rename_mp3_files.py diff --git a/Development_Docs/MP3/LIST/verify_mp3_checksum.py b/Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/verify_mp3_checksum.py similarity index 100% rename from Development_Docs/MP3/LIST/verify_mp3_checksum.py rename to Core/Bsp/BSP_Device/bsp_mp3/MP3/LIST/verify_mp3_checksum.py diff --git a/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.h b/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.h index d68e9e1..eed6a68 100644 --- a/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.h +++ b/Core/Bsp/BSP_Device/bsp_mp3/mp3_driver.h @@ -7,7 +7,7 @@ extern "C" { #include "stm32f1xx_hal.h" #include - +#include "mp3_play_index.h" /* MP3命令定义 */ #define MP3_HEADER 0x7E // 帧头 #define MP3_VERSION 0xFF // 版本号 diff --git a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c index 931ad45..bacb3e9 100644 --- a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c +++ b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.c @@ -6,117 +6,185 @@ #define DELAY 0x80 // based on Adafruit ST7735 library for Arduino -static const uint8_t - init_cmds1[] = { // Init for 7735R, part 1 (red or green tab) - 15, // 15 commands in list: - ST7735_SWRESET, DELAY, // 1: Software reset, 0 args, w/delay - 150, // 150 ms delay - ST7735_SLPOUT, DELAY, // 2: Out of sleep mode, 0 args, w/delay - 255, // 500 ms delay - ST7735_FRMCTR1, 3, // 3: Frame rate ctrl - normal mode, 3 args: - 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) - ST7735_FRMCTR2, 3, // 4: Frame rate control - idle mode, 3 args: - 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) - ST7735_FRMCTR3, 6, // 5: Frame rate ctrl - partial mode, 6 args: - 0x01, 0x2C, 0x2D, // Dot inversion mode - 0x01, 0x2C, 0x2D, // Line inversion mode - ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg, no delay: - 0x07, // No inversion - ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay: +static const uint8_t init_cmds1[] = + { // Init for 7735R, part 1 (red or green tab) + 15, // 15 commands in list: + ST7735_SWRESET, + DELAY, // 1: Software reset, 0 args, w/delay + 150, // 150 ms delay + ST7735_SLPOUT, + DELAY, // 2: Out of sleep mode, 0 args, w/delay + 255, // 500 ms delay + ST7735_FRMCTR1, + 3, // 3: Frame rate ctrl - normal mode, 3 args: + 0x01, + 0x2C, + 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) + ST7735_FRMCTR2, + 3, // 4: Frame rate control - idle mode, 3 args: + 0x01, + 0x2C, + 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) + ST7735_FRMCTR3, + 6, // 5: Frame rate ctrl - partial mode, 6 args: + 0x01, + 0x2C, + 0x2D, // Dot inversion mode + 0x01, + 0x2C, + 0x2D, // Line inversion mode + ST7735_INVCTR, + 1, // 6: Display inversion ctrl, 1 arg, no delay: + 0x07, // No inversion + ST7735_PWCTR1, + 3, // 7: Power control, 3 args, no delay: 0xA2, - 0x02, // -4.6V - 0x84, // AUTO mode - ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay: - 0xC5, // VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD - ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay: - 0x0A, // Opamp current small - 0x00, // Boost frequency - ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay: - 0x8A, // BCLK/2, Opamp current small & Medium low + 0x02, // -4.6V + 0x84, // AUTO mode + ST7735_PWCTR2, + 1, // 8: Power control, 1 arg, no delay: + 0xC5, // VGH25 = 2.4C VGSEL = -10 VGH = 3 * AVDD + ST7735_PWCTR3, + 2, // 9: Power control, 2 args, no delay: + 0x0A, // Opamp current small + 0x00, // Boost frequency + ST7735_PWCTR4, + 2, // 10: Power control, 2 args, no delay: + 0x8A, // BCLK/2, Opamp current small & Medium low 0x2A, - ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay: - 0x8A, 0xEE, - ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay: + ST7735_PWCTR5, + 2, // 11: Power control, 2 args, no delay: + 0x8A, + 0xEE, + ST7735_VMCTR1, + 1, // 12: Power control, 1 arg, no delay: 0x0E, - ST7735_INVOFF, 0, // 13: Don't invert display, no args, no delay - ST7735_MADCTL, 1, // 14: Memory access control (directions), 1 arg: - ST7735_ROTATION, // row addr/col addr, bottom to top refresh - ST7735_COLMOD, 1, // 15: set color mode, 1 arg, no delay: - 0x05}, // 16-bit color + ST7735_INVOFF, + 0, // 13: Don't invert display, no args, no delay + ST7735_MADCTL, + 1, // 14: Memory access control (directions), 1 arg: + ST7735_ROTATION, // row addr/col addr, bottom to top refresh + ST7735_COLMOD, + 1, // 15: set color mode, 1 arg, no delay: + 0x05}, // 16-bit color #if (defined(ST7735_IS_128X128) || defined(ST7735_IS_160X128)) - init_cmds2[] = { // Init for 7735R, part 2 (1.44" display) - 2, // 2 commands in list: - ST7735_CASET, 4, // 1: Column addr set, 4 args, no delay: - 0x00, 0x00, // XSTART = 0 - 0x00, 0x7F, // XEND = 127 - ST7735_RASET, 4, // 2: Row addr set, 4 args, no delay: - 0x00, 0x00, // XSTART = 0 - 0x00, 0x7F}, // XEND = 127 -#endif // ST7735_IS_128X128 + init_cmds2[] = + { // Init for 7735R, part 2 (1.44" display) + 2, // 2 commands in list: + ST7735_CASET, + 4, // 1: Column addr set, 4 args, no delay: + 0x00, + 0x00, // XSTART = 0 + 0x00, + 0x7F, // XEND = 127 + ST7735_RASET, + 4, // 2: Row addr set, 4 args, no delay: + 0x00, + 0x00, // XSTART = 0 + 0x00, + 0x7F}, // XEND = 127 +#endif // ST7735_IS_128X128 #ifdef ST7735_IS_160X80 - init_cmds2[] = { // Init for 7735S, part 2 (160x80 display) - 3, // 3 commands in list: - ST7735_CASET, 4, // 1: Column addr set, 4 args, no delay: - 0x00, 0x00, // XSTART = 0 - 0x00, 0x4F, // XEND = 79 - ST7735_RASET, 4, // 2: Row addr set, 4 args, no delay: - 0x00, 0x00, // XSTART = 0 - 0x00, 0x9F, // XEND = 159 - ST7735_INVON, 0}, // 3: Invert colors + init_cmds2[] = + { // Init for 7735S, part 2 (160x80 display) + 3, // 3 commands in list: + ST7735_CASET, + 4, // 1: Column addr set, 4 args, no delay: + 0x00, + 0x00, // XSTART = 0 + 0x00, + 0x4F, // XEND = 79 + ST7735_RASET, + 4, // 2: Row addr set, 4 args, no delay: + 0x00, + 0x00, // XSTART = 0 + 0x00, + 0x9F, // XEND = 159 + ST7735_INVON, + 0}, // 3: Invert colors #endif - init_cmds3[] = { // Init for 7735R, part 3 (red or green tab) - 4, // 4 commands in list: - ST7735_GMCTRP1, 16, // 1: Gamma Adjustments (pos. polarity), 16 args, no delay: - 0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, ST7735_GMCTRN1, 16, // 2: Gamma Adjustments (neg. polarity), 16 args, no delay: - 0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, ST7735_NORON, DELAY, // 3: Normal display on, no args, w/delay - 10, // 10 ms delay - ST7735_DISPON, DELAY, // 4: Main screen turn on, no args w/delay - 100}; // 100 ms delay + init_cmds3[] = { // Init for 7735R, part 3 (red or green tab) + 4, // 4 commands in list: + ST7735_GMCTRP1, + 16, // 1: Gamma Adjustments (pos. polarity), 16 args, no delay: + 0x02, + 0x1c, + 0x07, + 0x12, + 0x37, + 0x32, + 0x29, + 0x2d, + 0x29, + 0x25, + 0x2B, + 0x39, + 0x00, + 0x01, + 0x03, + 0x10, + ST7735_GMCTRN1, + 16, // 2: Gamma Adjustments (neg. polarity), 16 args, no delay: + 0x03, + 0x1d, + 0x07, + 0x06, + 0x2E, + 0x2C, + 0x29, + 0x2D, + 0x2E, + 0x2E, + 0x37, + 0x3F, + 0x00, + 0x00, + 0x02, + 0x10, + ST7735_NORON, + DELAY, // 3: Normal display on, no args, w/delay + 10, // 10 ms delay + ST7735_DISPON, + DELAY, // 4: Main screen turn on, no args w/delay + 100}; // 100 ms delay -static void ST7735_Select() -{ +static void ST7735_Select() { HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET); } -void ST7735_Unselect() -{ +void ST7735_Unselect() { HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET); } -static void ST7735_Reset() -{ +static void ST7735_Reset() { HAL_GPIO_WritePin(ST7735_RES_GPIO_Port, ST7735_RES_Pin, GPIO_PIN_RESET); HAL_Delay(5); HAL_GPIO_WritePin(ST7735_RES_GPIO_Port, ST7735_RES_Pin, GPIO_PIN_SET); } -static void ST7735_WriteCommand(uint8_t cmd) -{ +static void ST7735_WriteCommand(uint8_t cmd) { HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(&ST7735_SPI_PORT, &cmd, sizeof(cmd), HAL_MAX_DELAY); -// HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, &cmd, sizeof(cmd)); + // HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, &cmd, sizeof(cmd)); } -static void ST7735_WriteData(uint8_t *buff, size_t buff_size) -{ +static void ST7735_WriteData(uint8_t *buff, size_t buff_size) { HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET); - HAL_SPI_Transmit(&ST7735_SPI_PORT, buff, buff_size, HAL_MAX_DELAY); - // HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, buff, buff_size); + HAL_SPI_Transmit(&ST7735_SPI_PORT, buff, buff_size, HAL_MAX_DELAY); + // HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, buff, buff_size); } -static void ST7735_ExecuteCommandList(const uint8_t *addr) -{ +static void ST7735_ExecuteCommandList(const uint8_t *addr) { uint8_t numCommands, numArgs; uint16_t ms; numCommands = *addr++; - while (numCommands--) - { + while (numCommands--) { uint8_t cmd = *addr++; ST7735_WriteCommand(cmd); @@ -124,14 +192,12 @@ static void ST7735_ExecuteCommandList(const uint8_t *addr) // If high bit set, delay follows args ms = numArgs & DELAY; numArgs &= ~DELAY; - if (numArgs) - { + if (numArgs) { ST7735_WriteData((uint8_t *)addr, numArgs); addr += numArgs; } - if (ms) - { + if (ms) { ms = *addr++; if (ms == 255) ms = 500; @@ -151,8 +217,8 @@ static void ST7735_ExecuteCommandList(const uint8_t *addr) * @param x1 显示窗口右下角的X坐标 * @param y1 显示窗口右下角的Y坐标 */ -static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) -{ +static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, + uint8_t y1) { // column address set ST7735_WriteCommand(ST7735_CASET); // 设置列地址范围,考虑到显示区域的起始偏移 @@ -178,8 +244,7 @@ static void ST7735_SetAddressWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t * 以及初始化完成后填充屏幕为黄色。这些操作确保了显示器能够正确配置并进入 * 可显示状态。 */ -void ST7735_Init() -{ +void ST7735_Init() { // 选择ST7735设备,准备进行初始化 ST7735_Select(); @@ -216,8 +281,7 @@ void ST7735_Init() * @param y 纵坐标,表示像素点在显示屏上的垂直位置。 * @param color 像素点的颜色值,采用RGB565格式。 */ -void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) -{ +void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { // 检查坐标是否超出显示屏边界 if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT)) return; @@ -232,13 +296,13 @@ void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) uint8_t data[] = {color >> 8, color & 0xFF}; // 发送颜色数据到显示屏 - //ST7735_WriteData(data, sizeof(data)); + // ST7735_WriteData(data, sizeof(data)); HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET); HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data)); -// HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY); + // HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY); // 取消选择显示屏,结束操作 ST7735_Unselect(); @@ -254,8 +318,8 @@ void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) * @param color 字符的颜色 * @param bgcolor 背景颜色 */ -static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uint16_t color, uint16_t bgcolor) -{ +static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, + uint16_t color, uint16_t bgcolor) { // 定义循环变量和临时变量 uint32_t i, b, j; @@ -263,25 +327,24 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uin ST7735_SetAddressWindow(x, y, x + font->width - 1, y + font->height - 1); // 遍历字符的每一行 - for (i = 0; i < font->height; i++) - { + for (i = 0; i < font->height; i++) { // 获取当前行的字形数据 b = font->data[(ch - 32) * font->height + i]; // 遍历当前行的每一列 - for (j = 0; j < font->width; j++) - { + for (j = 0; j < font->width; j++) { // 检查当前列是否为前景色 - if ((b << j) & 0x8000) - { + if ((b << j) & 0x8000) { // 是前景色,则发送颜色数据到显示屏 uint8_t data[] = {color >> 8, color & 0xFF}; - ST7735_WriteData(data, sizeof(data)); - } - else - { + // ST7735_WriteData(data, sizeof(data)); + HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET); + HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data)); + } else { // 不是前景色,则发送背景色数据到显示屏 uint8_t data[] = {bgcolor >> 8, bgcolor & 0xFF}; - ST7735_WriteData(data, sizeof(data)); + // ST7735_WriteData(data, sizeof(data)); + HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET); + HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data)); } } } @@ -290,8 +353,8 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef *font, uin /* Simpler (and probably slower) implementation: -static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color) { - uint32_t i, b, j; +static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, +uint16_t color) { uint32_t i, b, j; for(i = 0; i < font->height; i++) { b = font->data[(ch - 32) * font->height + i]; @@ -304,23 +367,19 @@ static void ST7735_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, uint } */ -void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font, uint16_t color, uint16_t bgcolor) -{ +void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font, + uint16_t color, uint16_t bgcolor) { ST7735_Select(); - while (*str) - { - if (x + font->width >= ST7735_WIDTH) - { + while (*str) { + if (x + font->width >= ST7735_WIDTH) { x = 0; y += font->height; - if (y + font->height >= ST7735_HEIGHT) - { + if (y + font->height >= ST7735_HEIGHT) { break; } - if (*str == ' ') - { + if (*str == ' ') { // skip spaces in the beginning of the new line str++; continue; @@ -348,8 +407,8 @@ void ST7735_WriteString(uint16_t x, uint16_t y, const char *str, FontDef *font, * @param h 矩形的高度 * @param color 用于填充矩形的颜色 */ -void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) -{ +void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, + uint16_t color) { // clipping if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT)) return; @@ -368,13 +427,12 @@ void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16 // Set the data/command pin to data mode HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET); // Send the color data for each pixel in the rectangle - for (y = h; y > 0; y--) - { - for (x = w; x > 0; x--) - { + for (y = h; y > 0; y--) { + for (x = w; x > 0; x--) { - // HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), HAL_MAX_DELAY); - HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data)); + // HAL_SPI_Transmit(&ST7735_SPI_PORT, data, sizeof(data), + // HAL_MAX_DELAY); + HAL_SPI_Transmit_DMA(&ST7735_SPI_PORT, data, sizeof(data)); } } @@ -384,8 +442,8 @@ void ST7735_FillRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16 // Fill a rectangle area on the ST7735 LCD screen with a specified color // This function is optimized for speed, suitable for filling large areas -void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) -{ +void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h, + uint16_t color) { // clipping if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT)) return; @@ -417,10 +475,11 @@ void ST7735_FillRectangleFast(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ui * * @param color 要填充的颜色值。 */ -void ST7735_FillScreen(uint16_t color) -{ - // 使用FillRectangle函数填充整个屏幕,起始坐标为(0, 0),覆盖整个屏幕宽度和高度。 - ST7735_FillRectangle(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color); +void ST7735_FillScreen(uint16_t color) { + // 使用FillRectangle函数填充整个屏幕,起始坐标为(0, + // 0),覆盖整个屏幕宽度和高度。 + // ST7735_FillRectangle(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color); + ST7735_FillRectangleFast(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color); } /** @@ -429,12 +488,12 @@ void ST7735_FillScreen(uint16_t color) * 使用指定的颜色填充整个屏幕。该函数通过调用ST7735_FillRectangleFast函数, * 设置填充区域为整个屏幕的宽度和高度来实现快速填充屏幕的效果。 * - * @param color 填充屏幕所使用的颜色值。这是一个16位的颜色值,适用于ST7735显示屏。 + * @param color + * 填充屏幕所使用的颜色值。这是一个16位的颜色值,适用于ST7735显示屏。 */ -void ST7735_FillScreenFast(uint16_t color) -{ - // 调用快速填充矩形函数,参数为(起始X坐标, 起始Y坐标, 屏幕宽度, 屏幕高度, 颜色) - // 用于填充整个屏幕为指定的颜色 +void ST7735_FillScreenFast(uint16_t color) { + // 调用快速填充矩形函数,参数为(起始X坐标, 起始Y坐标, 屏幕宽度, 屏幕高度, + // 颜色) 用于填充整个屏幕为指定的颜色 ST7735_FillRectangleFast(0, 0, ST7735_WIDTH, ST7735_HEIGHT, color); } @@ -451,8 +510,8 @@ void ST7735_FillScreenFast(uint16_t color) * @param h 图像的高度 * @param data 图像数据指针,图像数据是16位的RGB565格式 */ -void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data) -{ +void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, + const uint16_t *data) { // 检查图像的起始坐标是否超出液晶屏的边界 if ((x >= ST7735_WIDTH) || (y >= ST7735_HEIGHT)) return; @@ -481,10 +540,10 @@ void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint /** * ST7735_InvertColors函数用于控制ST7735显示屏的颜色反转功能。 * - * @param invert 一个布尔值,决定是否反转显示屏的颜色。如果invert为true,颜色将反转;如果为false,颜色将保持正常。 + * @param invert + * 一个布尔值,决定是否反转显示屏的颜色。如果invert为true,颜色将反转;如果为false,颜色将保持正常。 */ -void ST7735_InvertColors(bool invert) -{ +void ST7735_InvertColors(bool invert) { // 选择ST7735显示屏以进行通信。 ST7735_Select(); @@ -503,8 +562,7 @@ void ST7735_InvertColors(bool invert) * * @param gamma 伽马曲线定义,指定要设置的伽马曲线 */ -void ST7735_SetGamma(GammaDef gamma) -{ +void ST7735_SetGamma(GammaDef gamma) { // 选择ST7735显示器以进行通信 ST7735_Select(); diff --git a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h index 0d67402..215c20d 100644 --- a/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h +++ b/Core/Bsp/BSP_Device/spi_st7735s/spi_st7735s.h @@ -274,6 +274,11 @@ extern "C" // 解释:在使用ST7735显示屏之前,必须调用此函数进行初始化设置 void ST7735_Init(void); + // 启用/禁用DMA传输模式 + // 参数:enable - true启用DMA模式,false禁用DMA模式 + // 解释:初始化完成后调用此函数启用DMA模式可大幅提升刷屏速度 + void ST7735_EnableDMA(bool enable); + // 在指定位置绘制一个像素点 // 参数:x - 像素点的X坐标;y - 像素点的Y坐标;color - 像素点的颜色 // 解释:此函数用于在显示屏上的特定位置绘制一个单一颜色的像素点 diff --git a/Core/Bsp/BSP_WF_24/dx_wf_24.c b/Core/Bsp/BSP_WF_24/dx_wf_24.c index 8504586..05289a0 100644 --- a/Core/Bsp/BSP_WF_24/dx_wf_24.c +++ b/Core/Bsp/BSP_WF_24/dx_wf_24.c @@ -2,6 +2,7 @@ #include "cmsis_os.h" #include "cmsis_os2.h" #include "elog.h" +#include "mp3_driver.h" // 添加MP3模块头文件 #include #include #include @@ -201,16 +202,19 @@ uint8_t WIFI_Connect_WiFi(const char *ssid, const char *password, elog_i(TAG, "等待WiFi基础连接响应..."); if (!WIFI_WaitEvent("OK", "ERROR", 3000)) { elog_e(TAG, "WiFi基础连接失败"); + MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败,播放提示音 return 0; } elog_i(TAG, "等待WiFi详细连接结果..."); if (!WIFI_WaitEvent("+CWJAP:1", "+CWJAP:0", timeout_ms)) { elog_e(TAG, "WiFi详细连接失败"); + MP3_Play(WIFI_CONNECT_FAIL); // WiFi连接失败,播放提示音 return 0; } elog_i(TAG, "WiFi连接成功"); + MP3_Play(WIFI_CONNECT_OK); // WiFi连接成功,播放提示音 return 1; } diff --git a/Core/Bsp/MultiButton-master/multi_button.c b/Core/Bsp/MultiButton-master/multi_button.c new file mode 100644 index 0000000..b43c6d0 --- /dev/null +++ b/Core/Bsp/MultiButton-master/multi_button.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2016 Zibin Zheng + * All rights reserved + */ + +#include "multi_button.h" + +// Macro for callback execution with null check +#define EVENT_CB(ev) do { if(handle->cb[ev]) handle->cb[ev](handle); } while(0) + +// Button handle list head +static Button* head_handle = NULL; + +// Forward declarations +static void button_handler(Button* handle); +static inline uint8_t button_read_level(Button* handle); + +/** + * @brief Initialize the button struct handle + * @param handle: the button handle struct + * @param pin_level: read the HAL GPIO of the connected button level + * @param active_level: pressed GPIO level + * @param button_id: the button id + * @retval None + */ +void button_init(Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id) +{ + if (!handle || !pin_level) return; // parameter validation + + memset(handle, 0, sizeof(Button)); + handle->event = (uint8_t)BTN_NONE_PRESS; + handle->hal_button_level = pin_level; + handle->button_level = !active_level; // initialize to opposite of active level + handle->active_level = active_level; + handle->button_id = button_id; + handle->state = BTN_STATE_IDLE; +} + +/** + * @brief Attach the button event callback function + * @param handle: the button handle struct + * @param event: trigger event type + * @param cb: callback function + * @retval None + */ +void button_attach(Button* handle, ButtonEvent event, BtnCallback cb) +{ + if (!handle || event >= BTN_EVENT_COUNT) return; // parameter validation + handle->cb[event] = cb; +} + +/** + * @brief Detach the button event callback function + * @param handle: the button handle struct + * @param event: trigger event type + * @retval None + */ +void button_detach(Button* handle, ButtonEvent event) +{ + if (!handle || event >= BTN_EVENT_COUNT) return; // parameter validation + handle->cb[event] = NULL; +} + +/** + * @brief Get the button event that happened + * @param handle: the button handle struct + * @retval button event + */ +ButtonEvent button_get_event(Button* handle) +{ + if (!handle) return BTN_NONE_PRESS; + return (ButtonEvent)(handle->event); +} + +/** + * @brief Get the repeat count of button presses + * @param handle: the button handle struct + * @retval repeat count + */ +uint8_t button_get_repeat_count(Button* handle) +{ + if (!handle) return 0; + return handle->repeat; +} + +/** + * @brief Reset button state to idle + * @param handle: the button handle struct + * @retval None + */ +void button_reset(Button* handle) +{ + if (!handle) return; + handle->state = BTN_STATE_IDLE; + handle->ticks = 0; + handle->repeat = 0; + handle->event = (uint8_t)BTN_NONE_PRESS; + handle->debounce_cnt = 0; +} + +/** + * @brief Check if button is currently pressed + * @param handle: the button handle struct + * @retval 1: pressed, 0: not pressed, -1: error + */ +int button_is_pressed(Button* handle) +{ + if (!handle) return -1; + return (handle->button_level == handle->active_level) ? 1 : 0; +} + +/** + * @brief Read button level with inline optimization + * @param handle: the button handle struct + * @retval button level + */ +static inline uint8_t button_read_level(Button* handle) +{ + return handle->hal_button_level(handle->button_id); +} + +/** + * @brief Button driver core function, driver state machine + * @param handle: the button handle struct + * @retval None + */ +static void button_handler(Button* handle) +{ + uint8_t read_gpio_level = button_read_level(handle); + + // Increment ticks counter when not in idle state + if (handle->state > BTN_STATE_IDLE) { + handle->ticks++; + } + + /*------------Button debounce handling---------------*/ + if (read_gpio_level != handle->button_level) { + // Continue reading same new level for debounce + if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) { + handle->button_level = read_gpio_level; + handle->debounce_cnt = 0; + } + } else { + // Level not changed, reset counter + handle->debounce_cnt = 0; + } + + /*-----------------State machine-------------------*/ + switch (handle->state) { + case BTN_STATE_IDLE: + if (handle->button_level == handle->active_level) { + // Button press detected + handle->event = (uint8_t)BTN_PRESS_DOWN; + EVENT_CB(BTN_PRESS_DOWN); + handle->ticks = 0; + handle->repeat = 1; + handle->state = BTN_STATE_PRESS; + } else { + handle->event = (uint8_t)BTN_NONE_PRESS; + } + break; + + case BTN_STATE_PRESS: + if (handle->button_level != handle->active_level) { + // Button released + handle->event = (uint8_t)BTN_PRESS_UP; + EVENT_CB(BTN_PRESS_UP); + handle->ticks = 0; + handle->state = BTN_STATE_RELEASE; + } else if (handle->ticks > LONG_TICKS) { + // Long press detected + handle->event = (uint8_t)BTN_LONG_PRESS_START; + EVENT_CB(BTN_LONG_PRESS_START); + handle->state = BTN_STATE_LONG_HOLD; + } + break; + + case BTN_STATE_RELEASE: + if (handle->button_level == handle->active_level) { + // Button pressed again + handle->event = (uint8_t)BTN_PRESS_DOWN; + EVENT_CB(BTN_PRESS_DOWN); + if (handle->repeat < PRESS_REPEAT_MAX_NUM) { + handle->repeat++; + } + EVENT_CB(BTN_PRESS_REPEAT); + handle->ticks = 0; + handle->state = BTN_STATE_REPEAT; + } else if (handle->ticks > SHORT_TICKS) { + // Timeout reached, determine click type + if (handle->repeat == 1) { + handle->event = (uint8_t)BTN_SINGLE_CLICK; + EVENT_CB(BTN_SINGLE_CLICK); + } else if (handle->repeat == 2) { + handle->event = (uint8_t)BTN_DOUBLE_CLICK; + EVENT_CB(BTN_DOUBLE_CLICK); + } + handle->state = BTN_STATE_IDLE; + } + break; + + case BTN_STATE_REPEAT: + if (handle->button_level != handle->active_level) { + // Button released + handle->event = (uint8_t)BTN_PRESS_UP; + EVENT_CB(BTN_PRESS_UP); + if (handle->ticks < SHORT_TICKS) { + handle->ticks = 0; + handle->state = BTN_STATE_RELEASE; // Continue waiting for more presses + } else { + handle->state = BTN_STATE_IDLE; // End of sequence + } + } else if (handle->ticks > SHORT_TICKS) { + // Held down too long, treat as normal press + handle->state = BTN_STATE_PRESS; + } + break; + + case BTN_STATE_LONG_HOLD: + if (handle->button_level == handle->active_level) { + // Continue holding + handle->event = (uint8_t)BTN_LONG_PRESS_HOLD; + EVENT_CB(BTN_LONG_PRESS_HOLD); + } else { + // Released from long press + handle->event = (uint8_t)BTN_PRESS_UP; + EVENT_CB(BTN_PRESS_UP); + handle->state = BTN_STATE_IDLE; + } + break; + + default: + // Invalid state, reset to idle + handle->state = BTN_STATE_IDLE; + break; + } +} + +/** + * @brief Start the button work, add the handle into work list + * @param handle: target handle struct + * @retval 0: succeed, -1: already exist, -2: invalid parameter + */ +int button_start(Button* handle) +{ + if (!handle) return -2; // invalid parameter + + Button* target = head_handle; + while (target) { + if (target == handle) return -1; // already exist + target = target->next; + } + + handle->next = head_handle; + head_handle = handle; + return 0; +} + +/** + * @brief Stop the button work, remove the handle from work list + * @param handle: target handle struct + * @retval None + */ +void button_stop(Button* handle) +{ + if (!handle) return; // parameter validation + + Button** curr; + for (curr = &head_handle; *curr; ) { + Button* entry = *curr; + if (entry == handle) { + *curr = entry->next; + entry->next = NULL; // clear next pointer + return; + } else { + curr = &entry->next; + } + } +} + +/** + * @brief Background ticks, timer repeat invoking interval 5ms + * @param None + * @retval None + */ +void button_ticks(void) +{ + Button* target; + for (target = head_handle; target; target = target->next) { + button_handler(target); + } +} diff --git a/Core/Bsp/MultiButton-master/multi_button.h b/Core/Bsp/MultiButton-master/multi_button.h new file mode 100644 index 0000000..09db0d6 --- /dev/null +++ b/Core/Bsp/MultiButton-master/multi_button.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016 Zibin Zheng + * All rights reserved + */ + +#ifndef _MULTI_BUTTON_H_ +#define _MULTI_BUTTON_H_ + +#include +#include + +// Configuration constants - can be modified according to your needs +#define TICKS_INTERVAL 5 // ms - timer interrupt interval +#define DEBOUNCE_TICKS 3 // MAX 7 (0 ~ 7) - debounce filter depth +#define SHORT_TICKS (300 / TICKS_INTERVAL) // short press threshold +#define LONG_TICKS (1000 / TICKS_INTERVAL) // long press threshold +#define PRESS_REPEAT_MAX_NUM 15 // maximum repeat counter value + +// Forward declaration +typedef struct _Button Button; + +// Button callback function type +typedef void (*BtnCallback)(Button* btn_handle); + +// Button event types +typedef enum { + BTN_PRESS_DOWN = 0, // button pressed down + BTN_PRESS_UP, // button released + BTN_PRESS_REPEAT, // repeated press detected + BTN_SINGLE_CLICK, // single click completed + BTN_DOUBLE_CLICK, // double click completed + BTN_LONG_PRESS_START, // long press started + BTN_LONG_PRESS_HOLD, // long press holding + BTN_EVENT_COUNT, // total number of events + BTN_NONE_PRESS // no event +} ButtonEvent; + +// Button state machine states +typedef enum { + BTN_STATE_IDLE = 0, // idle state + BTN_STATE_PRESS, // pressed state + BTN_STATE_RELEASE, // released state waiting for timeout + BTN_STATE_REPEAT, // repeat press state + BTN_STATE_LONG_HOLD // long press hold state +} ButtonState; + +// Button structure +struct _Button { + uint16_t ticks; // tick counter + uint8_t repeat : 4; // repeat counter (0-15) + uint8_t event : 4; // current event (0-15) + uint8_t state : 3; // state machine state (0-7) + uint8_t debounce_cnt : 3; // debounce counter (0-7) + uint8_t active_level : 1; // active GPIO level (0 or 1) + uint8_t button_level : 1; // current button level + uint8_t button_id; // button identifier + uint8_t (*hal_button_level)(uint8_t button_id); // HAL function to read GPIO + BtnCallback cb[BTN_EVENT_COUNT]; // callback function array + Button* next; // next button in linked list +}; + +#ifdef __cplusplus +extern "C" { +#endif + +// Public API functions +void button_init(Button* handle, uint8_t(*pin_level)(uint8_t), uint8_t active_level, uint8_t button_id); +void button_attach(Button* handle, ButtonEvent event, BtnCallback cb); +void button_detach(Button* handle, ButtonEvent event); +ButtonEvent button_get_event(Button* handle); +int button_start(Button* handle); +void button_stop(Button* handle); +void button_ticks(void); + +// Utility functions +uint8_t button_get_repeat_count(Button* handle); +void button_reset(Button* handle); +int button_is_pressed(Button* handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Core/Inc/main.h b/Core/Inc/main.h index 54ef4e9..70a8453 100644 --- a/Core/Inc/main.h +++ b/Core/Inc/main.h @@ -63,8 +63,8 @@ void Error_Handler(void); #define KEY2_GPIO_Port GPIOC #define KEY3_Pin GPIO_PIN_2 #define KEY3_GPIO_Port GPIOC -#define KEY3C3_Pin GPIO_PIN_3 -#define KEY3C3_GPIO_Port GPIOC +#define KEY4_Pin GPIO_PIN_3 +#define KEY4_GPIO_Port GPIOC #define HX711_DOUT_Pin GPIO_PIN_2 #define HX711_DOUT_GPIO_Port GPIOA #define HX711_SCK_Pin GPIO_PIN_3 diff --git a/Core/Src/freertos.c b/Core/Src/freertos.c index 2308e14..d39664e 100644 --- a/Core/Src/freertos.c +++ b/Core/Src/freertos.c @@ -19,10 +19,9 @@ /* Includes ------------------------------------------------------------------*/ #include "FreeRTOS.h" -#include "cmsis_os.h" -#include "main.h" #include "task.h" - +#include "main.h" +#include "cmsis_os.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ @@ -30,10 +29,10 @@ #include "dx_wf_24.h" #include "elog.h" #include "mp3_driver.h" +#include "mp3_play_index.h" +#include "multi_button.h" #include "spi_st7735s.h" #include "stdio.h" -#include "mp3_play_index.h" - /* USER CODE END Includes */ @@ -45,7 +44,11 @@ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define TAG "Main" - +/******************** 按键功能定义 ********************/ +#define KEY1_MODE_SWITCH // 按键1:模式切换(自动/手动) +#define KEY2_MANUAL_FEED // 按键2:手动喂食(仅手动模式有效) +#define KEY3_DISPLAY_NEXT // 按键3:切换显示界面 +#define KEY4_TIME_SET // 按键4:长按设置时间 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ @@ -56,45 +59,82 @@ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN Variables */ +// LCD页面枚举定义 +typedef enum { + LCD_PAGE_TIME = 0, // 时间显示页面 + LCD_PAGE_TEMP_HUMI, // 温湿度页面 + LCD_PAGE_FOOD_WEIGHT, // 食物重量页面 + LCD_PAGE_WATER_LEVEL, // 水位页面 + LCD_PAGE_SYSTEM_STATUS // 系统状态页面 +} LCD_Page_t; + +// 传感器数据结构体 +typedef struct { + float temperature; // 温度值 + float humidity; // 湿度值 + float food_weight; // 食物重量 + uint8_t water_level; // 水位状态 (0=无水, 1=有水) + uint8_t system_mode; // 系统模式 (0=手动, 1=自动) +} Sensor_Data_t; + +// 全局变量 +static LCD_Page_t current_page = LCD_PAGE_TIME; // 当前显示页面 +static Sensor_Data_t sensor_data = {0}; // 传感器数据 + /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; const osThreadAttr_t defaultTask_attributes = { - .name = "defaultTask", - .stack_size = 256 * 4, - .priority = (osPriority_t)osPriorityHigh1, + .name = "defaultTask", + .stack_size = 256 * 4, + .priority = (osPriority_t) osPriorityHigh1, }; /* Definitions for wifi_mqtt */ osThreadId_t wifi_mqttHandle; const osThreadAttr_t wifi_mqtt_attributes = { - .name = "wifi_mqtt", - .stack_size = 3000 * 4, - .priority = (osPriority_t)osPriorityHigh, + .name = "wifi_mqtt", + .stack_size = 3000 * 4, + .priority = (osPriority_t) osPriorityHigh, }; /* Definitions for LCD_SHOW_Task */ osThreadId_t LCD_SHOW_TaskHandle; const osThreadAttr_t LCD_SHOW_Task_attributes = { - .name = "LCD_SHOW_Task", - .stack_size = 1024 * 4, - .priority = (osPriority_t)osPriorityHigh, + .name = "LCD_SHOW_Task", + .stack_size = 1024 * 4, + .priority = (osPriority_t) osPriorityHigh, +}; +/* Definitions for button */ +osThreadId_t buttonHandle; +const osThreadAttr_t button_attributes = { + .name = "button", + .stack_size = 512 * 4, + .priority = (osPriority_t) osPriorityRealtime2, }; /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ - +void LCD_NextPage(void); +void LCD_PrevPage(void); +void LCD_SetPage(LCD_Page_t page); +LCD_Page_t LCD_GetCurrentPage(void); +void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water, + uint8_t mode); +Sensor_Data_t *LCD_GetSensorData(void); +void user_button_init(void); /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void *argument); extern void wifi_task_mqtt(void *argument); void LCD_Task(void *argument); +void button_task(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /** - * @brief FreeRTOS initialization - * @param None - * @retval None - */ + * @brief FreeRTOS initialization + * @param None + * @retval None + */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ ST7735_Init(); // 初始化ST7735显示屏 @@ -118,8 +158,7 @@ void MX_FREERTOS_Init(void) { /* Create the thread(s) */ /* creation of defaultTask */ - defaultTaskHandle = - osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); + defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* creation of wifi_mqtt */ wifi_mqttHandle = osThreadNew(wifi_task_mqtt, NULL, &wifi_mqtt_attributes); @@ -127,6 +166,9 @@ void MX_FREERTOS_Init(void) { /* creation of LCD_SHOW_Task */ LCD_SHOW_TaskHandle = osThreadNew(LCD_Task, NULL, &LCD_SHOW_Task_attributes); + /* creation of button */ + buttonHandle = osThreadNew(button_task, NULL, &button_attributes); + /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ /* USER CODE END RTOS_THREADS */ @@ -134,6 +176,7 @@ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */ + } /* USER CODE BEGIN Header_StartDefaultTask */ @@ -143,7 +186,8 @@ void MX_FREERTOS_Init(void) { * @retval None */ /* USER CODE END Header_StartDefaultTask */ -void StartDefaultTask(void *argument) { +void StartDefaultTask(void *argument) +{ /* USER CODE BEGIN StartDefaultTask */ // 1. 打开运行灯 Device_Control(DEVICE_LED_RUN, 1); @@ -159,7 +203,7 @@ void StartDefaultTask(void *argument) { } elog_i("MP3", "模块初始化完成"); - MP3_Play(SYS_POWER_ON); + MP3_Play(SYS_POWER_ON); // 播放系统启动音 /* Infinite loop */ for (;;) { @@ -179,36 +223,117 @@ void StartDefaultTask(void *argument) { * @retval None */ /* USER CODE END Header_LCD_Task */ -void LCD_Task(void *argument) { +void LCD_Task(void *argument) +{ /* USER CODE BEGIN LCD_Task */ - char time_str[16]; + char display_str[32]; SNTP_Time_t now; - uint16_t text_color = ST7735_BLACK; - uint16_t bg_color = ST7735_WHITE; + uint16_t text_color = ST7735_WHITE; + uint16_t bg_color = ST7735_BLACK; + + // 显示区域参数 - 适配160x80横屏 + // 宽度160px,高度80px,使用较小字体确保内容完整显示 + + // 初始化传感器数据 - 全部设置为NC状态 + sensor_data.temperature = 0.0f; // NC - 未检测到温度 + sensor_data.humidity = 0.0f; // NC - 未检测到湿度 + sensor_data.food_weight = 0.0f; // NC - 未检测到重量 + sensor_data.water_level = 0; // NC - 未检测到水 + sensor_data.system_mode = 1; // 自动模式 - // 显示时间的区域大小,假设字体16x24 - const uint16_t x = 0; - const uint16_t y = 0; - const uint16_t w = 6 * 16; // 6个字符,每个字符16px宽 - const uint16_t h = 24; // 高度与字体匹配 /* Infinite loop */ for (;;) { - if (WIFI_Is_Time_Valid()) { - WIFI_Get_SNTP_Time(); - now = WIFI_Get_Current_Time(); - if (now.valid) { - snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", now.hour, - now.minute, now.second); + // 根据当前页面显示不同内容 + switch (current_page) { + + case LCD_PAGE_TIME: + // 时间显示页面 + ST7735_FillScreen(bg_color); + ST7735_WriteString(2, 2, "Time", &Font_7x10, text_color, bg_color); + + if (WIFI_Is_Time_Valid()) { + WIFI_Get_SNTP_Time(); + now = WIFI_Get_Current_Time(); + if (now.valid) { + snprintf(display_str, sizeof(display_str), "%02d:%02d:%02d", now.hour, + now.minute, now.second); + } else { + snprintf(display_str, sizeof(display_str), "--:--:--"); + } + ST7735_WriteString(10, 20, display_str, &Font_16x26, text_color, bg_color); } else { - snprintf(time_str, sizeof(time_str), "--:--:--"); + ST7735_WriteString(10, 20, "--:--:--", &Font_16x26, text_color, bg_color); + } + break; + + case LCD_PAGE_TEMP_HUMI: + // 温湿度页面 + ST7735_FillScreen(bg_color); + ST7735_WriteString(2, 2, "Temp & Humi", &Font_7x10, text_color, bg_color); + + // 显示温度 + snprintf(display_str, sizeof(display_str), "T: %.1f C", sensor_data.temperature); + ST7735_WriteString(5, 20, display_str, &Font_11x18, text_color, bg_color); + + // 显示湿度 + snprintf(display_str, sizeof(display_str), "H: %.1f %%", sensor_data.humidity); + ST7735_WriteString(5, 45, display_str, &Font_11x18, text_color, bg_color); + break; + + case LCD_PAGE_FOOD_WEIGHT: + // 食物重量页面 + ST7735_FillScreen(bg_color); + ST7735_WriteString(2, 2, "Food Weight", &Font_7x10, text_color, bg_color); + + // 显示食物重量 + snprintf(display_str, sizeof(display_str), "%.1f g", sensor_data.food_weight); + ST7735_WriteString(25, 20, display_str, &Font_16x26, text_color, bg_color); + + // 显示状态 + if (sensor_data.food_weight < 50.0f) { + ST7735_WriteString(20, 55, "Low", &Font_11x18, ST7735_RED, bg_color); + } else if (sensor_data.food_weight < 100.0f) { + ST7735_WriteString(15, 55, "Medium", &Font_11x18, ST7735_YELLOW, bg_color); + } else { + ST7735_WriteString(20, 55, "Good", &Font_11x18, ST7735_GREEN, bg_color); + } + break; + + case LCD_PAGE_WATER_LEVEL: + // 水位页面 + ST7735_FillScreen(bg_color); + ST7735_WriteString(2, 2, "Water Level", &Font_7x10, text_color, bg_color); + + // 根据水位传感器状态显示 + if (sensor_data.water_level == 0) { + ST7735_WriteString(20, 20, "NO WATER", &Font_16x26, ST7735_RED, bg_color); + ST7735_WriteString(35, 55, "Add water", &Font_11x18, ST7735_YELLOW, bg_color); + } else { + ST7735_WriteString(50, 25, "OK", &Font_16x26, ST7735_GREEN, bg_color); + ST7735_WriteString(35, 55, "Water OK", &Font_11x18, text_color, bg_color); + } + break; + + case LCD_PAGE_SYSTEM_STATUS: + // 系统状态页面 + ST7735_FillScreen(bg_color); + ST7735_WriteString(2, 2, "System Status", &Font_7x10, text_color, bg_color); + + // 显示WiFi状态 + if (WIFI_Is_Time_Valid()) { + ST7735_WriteString(5, 20, "WiFi: OK", &Font_11x18, ST7735_GREEN, bg_color); + } else { + ST7735_WriteString(5, 20, "WiFi: OFF", &Font_11x18, ST7735_RED, bg_color); } - // 先清空显示区域 - ST7735_FillRectangleFast(x, y, w, h, bg_color); - - // 写入时间 - ST7735_WriteString(x, y, time_str, &Font_16x26, text_color, bg_color); + // 显示系统模式 + if (sensor_data.system_mode) { + ST7735_WriteString(5, 45, "Mode: AUTO", &Font_11x18, text_color, bg_color); + } else { + ST7735_WriteString(5, 45, "Mode: MANUAL", &Font_11x18, text_color, bg_color); + } + break; } osDelay(1000); @@ -216,7 +341,173 @@ void LCD_Task(void *argument) { /* USER CODE END LCD_Task */ } +/* USER CODE BEGIN Header_button_task */ +/** +* @brief Function implementing the button thread. +* @param argument: Not used +* @retval None +*/ +/* USER CODE END Header_button_task */ +void button_task(void *argument) +{ + /* USER CODE BEGIN button_task */ + user_button_init(); + + /* Infinite loop */ + for(;;) + { + button_ticks(); + osDelay(5); + } + /* USER CODE END button_task */ +} + /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ +// 按键库实现部分// + +/* USER CODE BEGIN KEY Prototypes */ + +static Button KEY1; // 按键1 +static Button KEY2; // 按键2 +static Button KEY3; // 按键3 +static Button KEY4; // 按键4 + +uint8_t read_button_gpio(uint8_t button_id) { + switch (button_id) { + case 1: + return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin); + break; + case 2: + return HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin); + break; + case 3: + return HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin); + break; + case 4: + return HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin); + break; + default: + return 0; + } +} + +void key1_single_click_handler(Button *btn) { elog_i("KEY", "按键1单击"); } + +void key2_single_click_handler(Button *btn) { elog_i("KEY", "按键2单击"); } + +void key3_single_click_handler(Button *btn) { + elog_i("KEY", "按键3单击"); + LCD_NextPage(); +} + +void key4_single_click_handler(Button *btn) { elog_i("KEY", "按键4单击"); } + +void user_button_init(void) { + // 初始化按键 (active_level: 0=低电平有效, 1=高电平有效) + button_init(&KEY1, read_button_gpio, 0, 1); + button_init(&KEY2, read_button_gpio, 0, 2); + button_init(&KEY3, read_button_gpio, 0, 3); + button_init(&KEY4, read_button_gpio, 0, 4); + + elog_i("BUTTON", "按键初始化完成"); + + // 设置按键回调函数 + button_attach(&KEY1, BTN_SINGLE_CLICK, key1_single_click_handler); + button_attach(&KEY2, BTN_SINGLE_CLICK, key2_single_click_handler); + button_attach(&KEY3, BTN_SINGLE_CLICK, key3_single_click_handler); + button_attach(&KEY4, BTN_SINGLE_CLICK, key4_single_click_handler); + + elog_i("BUTTON", "按键回调函数设置完成"); + + // 启动按键任务 + button_start(&KEY1); + button_start(&KEY2); + button_start(&KEY3); + button_start(&KEY4); + + elog_i("BUTTON", "按键任务已启动"); +} +/* USER CODE END KEY Prototypes */ + +/* USER CODE BEGIN LCD_Page_Functions */ +/** + * @brief 切换到下一个显示页面 + * @retval None + */ +void LCD_NextPage(void) { + current_page = (current_page + 1) % 5; // 循环切换5个页面 + elog_i("LCD", "切换到页面 %d", current_page + 1); + + // 播放页面切换提示音 + MP3_Play(PARAM_SAVE_OK); // 使用参数保存成功的音效作为页面切换提示 +} + +/** + * @brief 切换到上一个显示页面 + * @retval None + */ +void LCD_PrevPage(void) { + if (current_page == 0) { + current_page = 4; + } else { + current_page--; + } + elog_i("LCD", "切换到页面 %d", current_page + 1); + + // 播放页面切换提示音 + MP3_Play(PARAM_SAVE_OK); +} + +/** + * @brief 设置当前显示页面 + * @param page: 目标页面索引 (0-4) + * @retval None + */ +void LCD_SetPage(LCD_Page_t page) { + if (page < 5) { + current_page = page; + elog_i("LCD", "设置页面为 %d", page + 1); + MP3_Play(PARAM_SAVE_OK); + } +} + +/** + * @brief 获取当前页面索引 + * @retval 当前页面索引 + */ +LCD_Page_t LCD_GetCurrentPage(void) { return current_page; } + +/** + * @brief 更新传感器数据 + * @param temp: 温度值 + * @param humi: 湿度值 + * @param weight: 食物重量 + * @param water: 水位状态 (0=无水, 1=有水) + * @param mode: 系统模式 + * @retval None + */ +void LCD_UpdateSensorData(float temp, float humi, float weight, uint8_t water, + uint8_t mode) { + sensor_data.temperature = temp; + sensor_data.humidity = humi; + sensor_data.food_weight = weight; + sensor_data.water_level = water; + sensor_data.system_mode = mode; + + elog_i("LCD", "传感器数据更新: T=%.1fC H=%.1f%% W=%.1fg Water=%s Mode=%s", + temp, humi, weight, water ? "DETECTED" : "NONE", + mode ? "AUTO" : "MANUAL"); +} + +/** + * @brief 获取传感器数据指针 + * @retval 传感器数据结构体指针 + */ +Sensor_Data_t *LCD_GetSensorData(void) { return &sensor_data; } + +/* USER CODE END LCD_Page_Functions */ + /* USER CODE END Application */ + diff --git a/Core/Src/gpio.c b/Core/Src/gpio.c index 39b5d6d..48973ef 100644 --- a/Core/Src/gpio.c +++ b/Core/Src/gpio.c @@ -60,10 +60,10 @@ void MX_GPIO_Init(void) /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, LED2_Pin|LED1_Pin, GPIO_PIN_SET); - /*Configure GPIO pins : KEY1_Pin KEY2_Pin KEY3_Pin KEY3C3_Pin */ - GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin|KEY3C3_Pin; + /*Configure GPIO pins : KEY1_Pin KEY2_Pin KEY3_Pin KEY4_Pin */ + GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin|KEY3_Pin|KEY4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /*Configure GPIO pin : HX711_DOUT_Pin */ diff --git a/Core/Src/spi.c b/Core/Src/spi.c index 8e6497c..485632e 100644 --- a/Core/Src/spi.c +++ b/Core/Src/spi.c @@ -45,7 +45,7 @@ void MX_SPI1_Init(void) hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; - hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; + hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; diff --git a/README.md b/README.md index ec8dc5e..17aa84a 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,43 @@ - `WIFI_Get_Current_Time()`:获取存储的当前时间(返回结构体副本) - `WIFI_Is_Time_Valid()`:检查时间是否有效 -### 使用示例: \ No newline at end of file +## MP3 音频播放功能 + +本工程集成了 MP3 音频播放功能,用于提供各种系统状态和事件的语音提示。相关的音频索引定义在 `Core/Bsp/BSP_Device/bsp_mp3/mp3_play_index.h` 文件中。 + +### 音频提示分类: + +#### 系统状态与初始化 +- `SYS_POWER_ON` (1): 系统上电完成,智能宠物喂食系统启动完成,进入待机模式 +- `WIFI_CONNECT_OK` (2): WiFi连接成功,云平台数据同步已开启 +- `WIFI_CONNECT_FAIL` (3): WiFi连接失败,请检查网络配置 + +#### 喂食模块 +- `FEED_AUTO_START` (4): 自动喂食启动,步进电机开始出粮 +- `FEED_IN_PROGRESS` (5): 正在出粮,称重模块实时监测中 +- `FEED_COMPLETE` (6): 喂食完成,当前食物重量已达设定值 +- `FEED_MANUAL_TRIGGER` (7): 手动喂食指令已接收,开始出粮 +- `FOOD_LOW_ALARM` (8): 食物余量不足警告,请及时添加 + +#### 喂水模块 +- `WATER_REFILL_START` (9): 水位低于阈值,水泵启动,开始自动补水 +- `WATER_REFILL_DONE` (10): 补水完成,水位已达设定上限 +- `WATER_LOW_ALARM` (11): 水位过低警告,请检查水源或水泵 +- `WATER_PIR_REFILL` (12): 检测到宠物靠近且水位偏低,启动自动补水 + +#### 模式切换 +- `MODE_AUTO` (13): 已切换至自动运行模式 +- `MODE_MANUAL` (14): 已切换至手动控制模式 + +#### 参数设置 +- `PARAM_SAVE_OK` (15): 参数设置已保存,系统配置已更新 +- `PARAM_TIME_SET` (16): 自动喂食时间已更新 +- `PARAM_WEIGHT_SET` (17): 喂食重量阈值已更新 + +#### 远程控制与异常 +- `REMOTE_CMD_RECEIVED` (18): 接收到微信小程序远程控制指令 +- `DATA_UPLOAD_FAIL` (19): 数据上传失败,请检查网络连接 +- `SYS_ERROR_ALARM` (20): 系统检测到异常,请检查硬件模块 + +### 使用说明: +系统通过调用相应的音频索引值来播放对应的提示音,为用户提供直观的听觉反馈,增强用户体验。 \ No newline at end of file diff --git a/hahha.ioc b/hahha.ioc index b227ae1..273edbd 100644 --- a/hahha.ioc +++ b/hahha.ioc @@ -35,7 +35,7 @@ Dma.USART1_TX.0.Priority=DMA_PRIORITY_VERY_HIGH Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority FREERTOS.FootprintOK=true FREERTOS.IPParameters=Tasks01,FootprintOK,configTOTAL_HEAP_SIZE -FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL +FREERTOS.Tasks01=defaultTask,41,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;wifi_mqtt,40,3000,wifi_task_mqtt,As external,NULL,Dynamic,NULL,NULL;LCD_SHOW_Task,40,1024,LCD_Task,Default,NULL,Dynamic,NULL,NULL;button,50,512,button_task,Default,NULL,Dynamic,NULL,NULL FREERTOS.configTOTAL_HEAP_SIZE=30000 File.Version=6 GPIO.groupedBy=Group By Peripherals @@ -177,12 +177,14 @@ PB6.Mode=I2C PB6.Signal=I2C1_SCL PB7.Mode=I2C PB7.Signal=I2C1_SDA -PC0.GPIOParameters=GPIO_Label +PC0.GPIOParameters=GPIO_PuPd,GPIO_Label PC0.GPIO_Label=KEY1 +PC0.GPIO_PuPd=GPIO_PULLUP PC0.Locked=true PC0.Signal=GPIO_Input -PC1.GPIOParameters=GPIO_Label +PC1.GPIOParameters=GPIO_PuPd,GPIO_Label PC1.GPIO_Label=KEY2 +PC1.GPIO_PuPd=GPIO_PULLUP PC1.Locked=true PC1.Signal=GPIO_Input PC12.Mode=Asynchronous @@ -191,12 +193,14 @@ PC14-OSC32_IN.Mode=LSE-External-Oscillator PC14-OSC32_IN.Signal=RCC_OSC32_IN PC15-OSC32_OUT.Mode=LSE-External-Oscillator PC15-OSC32_OUT.Signal=RCC_OSC32_OUT -PC2.GPIOParameters=GPIO_Label +PC2.GPIOParameters=GPIO_PuPd,GPIO_Label PC2.GPIO_Label=KEY3 +PC2.GPIO_PuPd=GPIO_PULLUP PC2.Locked=true PC2.Signal=GPIO_Input -PC3.GPIOParameters=GPIO_Label -PC3.GPIO_Label=KEY3 +PC3.GPIOParameters=GPIO_PuPd,GPIO_Label +PC3.GPIO_Label=KEY4 +PC3.GPIO_PuPd=GPIO_PULLUP PC3.Locked=true PC3.Signal=GPIO_Input PC4.GPIOParameters=GPIO_Label @@ -291,8 +295,8 @@ RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK RCC.TimSysFreq_Value=72000000 RCC.USBFreq_Value=72000000 RCC.VCOOutput2Freq_Value=8000000 -SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 -SPI1.CalculateBaudRate=9.0 MBits/s +SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_4 +SPI1.CalculateBaudRate=18.0 MBits/s SPI1.Direction=SPI_DIRECTION_2LINES SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI1.Mode=SPI_MODE_MASTER