好的,作为一名高级嵌入式软件开发工程师,我将为您详细阐述这个基于汉朔2.13寸电子价签的墨水屏时钟项目,并提供一个可靠、高效、可扩展的代码设计架构以及具体的C代码实现。关注微信公众号,提前获取相关推文 项目概述
本项目旨在利用汉朔2.13寸黑白电子价签Stallar-M DIY的墨水屏,无需拆解,直接在原有的MSP430单片机上进行二次开发,实现一个多功能的墨水屏时钟。该时钟不仅能显示时间、日期、星期,还能提供温湿度监测以及多组闹钟功能,充分展现了嵌入式系统开发的完整流程。
需求分析
基本时钟功能:
精确显示当前时间(时、分、秒)。
显示当前日期(年、月、日)。
显示当前星期。
支持12/24小时制切换(可选)。
温湿度监测功能:
集成温湿度传感器(例如DHT11、DHT22、SHT30等,根据实际硬件选择)。
实时读取并显示环境温度和湿度。
温度单位可切换(摄氏度/华氏度,可选)。
多组闹钟功能:
支持设置多组闹钟(例如3-5组)。
每组闹钟可设置时间(时、分)。
每组闹钟可设置重复模式(例如每天、工作日、周末、自定义)。
闹钟到时发出提示(可通过墨水屏闪烁或外部蜂鸣器,本项目暂定墨水屏闪烁)。
闹钟开关功能(启用/禁用)。
显示界面设计:
清晰易读的墨水屏显示。
合理的信息布局,突出时间信息。
显示电池电量图标(如果硬件支持电量检测)。
可考虑添加其他辅助信息,例如天气图标(如果未来扩展)。
低功耗设计:
充分利用MSP430的低功耗模式。
优化代码,减少CPU运行时间。
墨水屏只有刷新时才耗电,静态显示几乎零功耗。
可靠性和稳定性:
系统运行稳定可靠,不易崩溃。
时间精度高,误差小。
闹钟功能准时触发。
可扩展性:
代码结构清晰,易于维护和扩展。
预留接口,方便未来添加新功能(例如蓝牙同步时间、网络校时、天气预报等)。
系统架构设计
为了构建一个可靠、高效、可扩展的系统平台,我将采用分层架构的设计思想。这种架构将系统划分为不同的层次,每个层次负责特定的功能,层次之间通过清晰的接口进行通信,降低模块间的耦合度,提高代码的可维护性和可复用性。
系统架构图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +-------------------+ | 应用层 (APP) | <-- 闹钟管理、时间显示逻辑、UI逻辑 +-------------------+ | +-------------------+ | 服务层 (Service) | <-- 温湿度读取服务、时间服务、闹钟服务 +-------------------+ | +-------------------+ | 驱动层 (Driver) | <-- 墨水屏驱动、传感器驱动、定时器驱动 +-------------------+ | +-------------------+ | 硬件抽象层 (HAL) | <-- MSP430硬件外设接口封装 (GPIO, SPI, I2C, Timer) +-------------------+ | +-------------------+ | 硬件平台 (HW) | <-- MSP430单片机、墨水屏、温湿度传感器、汉朔价签硬件 +-------------------+
各层功能说明:
硬件平台层 (HW): 指汉朔Stallar-M DIY价签的硬件,包括MSP430单片机、2.13寸墨水屏、可能的温湿度传感器接口(如果板载或预留)、以及其他外围电路。
硬件抽象层 (HAL): 这一层是对MSP430硬件外设进行抽象封装,提供统一的接口给驱动层使用。例如,GPIO的初始化、读写,SPI/I2C的通信,定时器的配置等。HAL层隐藏了底层硬件的差异,使得驱动层代码可以更容易地移植到不同的硬件平台(如果未来需要)。
驱动层 (Driver): 驱动层负责与具体的硬件设备进行交互。
墨水屏驱动: 负责控制墨水屏的刷新、显示字符、图形等。需要根据墨水屏的控制器芯片(通常是SSD1681或类似型号)的datasheet编写驱动代码,实现初始化、发送命令、写入数据等功能。
传感器驱动: 负责读取温湿度传感器的数据。根据选择的传感器类型编写相应的驱动,例如DHT11/DHT22的单总线驱动,或者SHT30的I2C驱动。
定时器驱动: 配置和管理MSP430的定时器,用于提供系统时钟节拍,以及闹钟定时等功能。
服务层 (Service): 服务层构建在驱动层之上,提供更高级别的服务功能,供应用层调用。
温湿度读取服务: 封装温湿度传感器驱动,提供周期性读取温湿度数据的功能,并进行数据校准和处理。
时间服务: 管理系统时间,包括时间的初始化、更新、获取年、月、日、星期等信息。可以基于定时器中断实现时间的秒计数,并进行年、月、日、星期的自动更新。
闹钟服务: 负责闹钟的设置、存储、管理和触发。可以维护一个闹钟列表,定时检查是否有闹钟需要触发,并在触发时执行相应的操作(例如墨水屏闪烁)。
应用层 (APP): 应用层是最高层,负责实现具体的应用逻辑和用户界面。
时间显示逻辑: 从时间服务获取当前时间信息,格式化后在墨水屏上显示。
温湿度显示逻辑: 从温湿度读取服务获取温湿度数据,格式化后在墨水屏上显示。
闹钟管理逻辑: 提供闹钟设置界面(本项目简化,可以在代码中预设或通过串口指令设置,未来可以考虑按键交互),调用闹钟服务进行闹钟的添加、删除、修改、启用/禁用等操作。
UI逻辑: 负责整体的界面布局和元素绘制,包括时间、日期、星期、温湿度、电池图标、闹钟图标等。
代码设计与实现 (C语言)
为了满足3000行代码的要求,我将提供详细的代码框架和关键模块的实现,并加入大量的注释和说明,以及一些扩展功能的预留接口和代码框架。以下代码将基于分层架构进行组织,力求清晰易懂,并突出嵌入式开发的实践技巧。
1. 工程目录结构
为了更好地组织代码,我们创建一个工程目录,包含以下子目录和文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ePaperClock/ ├── Core/ # 核心代码 │ ├── Inc/ # 核心头文件 │ │ ├── clock.h # 时钟服务头文件 │ │ ├── alarm.h # 闹钟服务头文件 │ │ ├── sensor.h # 传感器服务头文件 │ │ ├── display.h # 显示服务头文件 │ │ └── main.h # 主程序头文件 │ └── Src/ # 核心源文件 │ ├── clock.c # 时钟服务实现 │ ├── alarm.c # 闹钟服务实现 │ ├── sensor.c # 传感器服务实现 │ ├── display.c # 显示服务实现 │ └── main.c # 主程序实现 ├── Drivers/ # 驱动层 │ ├── HAL/ # 硬件抽象层 │ │ ├── Inc/ # HAL 头文件 │ │ │ ├── hal_gpio.h │ │ │ ├── hal_spi.h │ │ │ ├── hal_i2c.h │ │ │ └── hal_timer.h │ │ └── Src/ # HAL 源文件 │ │ ├── hal_gpio.c │ │ ├── hal_spi.c │ │ ├── hal_i2c.c │ │ └── hal_timer.c │ └── DeviceDrivers/ # 设备驱动 │ ├── Inc/ # 设备驱动头文件 │ │ ├── epaper_driver.h # 墨水屏驱动头文件 │ │ │ └── sensor_driver.h # 传感器驱动头文件 │ └── Src/ # 设备驱动源文件 │ ├── epaper_driver.c # 墨水屏驱动源文件 │ └── sensor_driver.c # 传感器驱动源文件 ├── Libraries/ # 第三方库 (例如字库,如果需要) │ └── Fonts/ # 字库文件 ├── Config/ # 配置 │ ├── Inc/ # 配置头文件 │ │ └── config.h # 系统配置头文件 │ └── Src/ # 配置源文件 (如果需要) ├── Doc/ # 文档 │ └── README.md # 项目说明文档 └── Makefile # Makefile 文件 (用于编译)
2. 系统配置 (Config/Inc/config.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #ifndef __CONFIG_H__ #define __CONFIG_H__ #define SYS_CLK_FREQ 8000000UL #define TIMER_INTERRUPT_FREQ 1 #define EPD_CS_PIN #define EPD_DC_PIN #define EPD_RST_PIN #define EPD_BUSY_PIN #define EPD_SPI_BUS #define SENSOR_DATA_PIN #define ALARM_MAX_COUNT 3 #define DEFAULT_YEAR 2024 #define DEFAULT_MONTH 1 #define DEFAULT_DAY 1 #define DEFAULT_HOUR 0 #define DEFAULT_MINUTE 0 #define DEFAULT_SECOND 0 #define DISPLAY_REFRESH_INTERVAL_MS 1000 #define SENSOR_READ_INTERVAL_MS 5000 #define EPD_BLACK 0x00 #define EPD_WHITE 0xFF #endif
3. 硬件抽象层 (HAL)
HAL 头文件示例 (Drivers/HAL/Inc/hal_gpio.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #ifndef __HAL_GPIO_H__ #define __HAL_GPIO_H__ #include <stdint.h> #include <stdbool.h> typedef enum { GPIO_MODE_INPUT, GPIO_MODE_OUTPUT } GPIO_ModeTypeDef; typedef enum { GPIO_PIN_RESET = 0 , GPIO_PIN_SET = 1 } GPIO_PinStateTypeDef; typedef struct { uint8_t port; uint8_t pin; GPIO_ModeTypeDef mode; } GPIO_InitTypeDef; void HAL_GPIO_Init (GPIO_InitTypeDef *GPIO_InitStruct) ;void HAL_GPIO_WritePin (GPIO_InitTypeDef *GPIO_InitStruct, GPIO_PinStateTypeDef PinState) ;GPIO_PinStateTypeDef HAL_GPIO_ReadPin (GPIO_InitTypeDef *GPIO_InitStruct) ; #endif
HAL 源文件示例 (Drivers/HAL/Src/hal_gpio.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include "hal_gpio.h" #include <msp430.h> void HAL_GPIO_Init (GPIO_InitTypeDef *GPIO_InitStruct) { uint8_t port = GPIO_InitStruct->port; uint8_t pin = GPIO_InitStruct->pin; GPIO_ModeTypeDef mode = GPIO_InitStruct->mode; if (port == 1 ) { if (mode == GPIO_MODE_OUTPUT) { P1DIR |= (1 << pin); } else { P1DIR &= ~(1 << pin); } } } void HAL_GPIO_WritePin (GPIO_InitTypeDef *GPIO_InitStruct, GPIO_PinStateTypeDef PinState) { uint8_t port = GPIO_InitStruct->port; uint8_t pin = GPIO_InitStruct->pin; if (port == 1 ) { if (PinState == GPIO_PIN_SET) { P1OUT |= (1 << pin); } else { P1OUT &= ~(1 << pin); } } } GPIO_PinStateTypeDef HAL_GPIO_ReadPin (GPIO_InitTypeDef *GPIO_InitStruct) { uint8_t port = GPIO_InitStruct->port; uint8_t pin = GPIO_InitStruct->pin; GPIO_PinStateTypeDef pinState = GPIO_PIN_RESET; if (port == 1 ) { if (P1IN & (1 << pin)) { pinState = GPIO_PIN_SET; } else { pinState = GPIO_PIN_RESET; } } return pinState; }
类似地,需要实现 hal_spi.h/c
, hal_i2c.h/c
, hal_timer.h/c
文件,对 SPI, I2C, 定时器等外设进行抽象封装。 这些 HAL 层的实现需要根据 MSP430 的具体型号和外设寄存器进行编写,这里只是给出了框架示例。
4. 设备驱动层 (DeviceDrivers)
墨水屏驱动头文件 (Drivers/DeviceDrivers/Inc/epaper_driver.h)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #ifndef __EPAPER_DRIVER_H__ #define __EPAPER_DRIVER_H__ #include <stdint.h> #include <stdbool.h> void EPD_Init (void ) ;void EPD_Sleep (void ) ;void EPD_FullRefresh (const uint8_t *image_buffer) ;void EPD_PartialRefresh (const uint8_t *image_buffer, uint16_t x, uint16_t y, uint16_t width, uint16_t height) ;void EPD_Clear (void ) ;void EPD_DrawPixel (uint16_t x, uint16_t y, uint8_t color) ;void EPD_DrawChar (uint16_t x, uint16_t y, char ch, uint8_t color) ;void EPD_DrawString (uint16_t x, uint16_t y, const char *str, uint8_t color) ;void EPD_DrawNumber (uint16_t x, uint16_t y, int32_t number, uint8_t color) ;#endif
墨水屏驱动源文件 (Drivers/DeviceDrivers/Src/epaper_driver.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 #include "epaper_driver.h" #include "hal_gpio.h" #include "hal_spi.h" #include "config.h" #include "delay.h" #define EPD_WIDTH 250 #define EPD_HEIGHT 122 uint8_t epaper_buffer[EPD_WIDTH * EPD_HEIGHT / 8 ]; static void EPD_GPIO_Init (void ) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.port = ...; GPIO_InitStruct.pin = ...; GPIO_InitStruct.mode = GPIO_MODE_OUTPUT; HAL_GPIO_Init(&GPIO_InitStruct); GPIO_InitStruct.port = ...; GPIO_InitStruct.pin = ...; GPIO_InitStruct.mode = GPIO_MODE_OUTPUT; HAL_GPIO_Init(&GPIO_InitStruct); GPIO_InitStruct.port = ...; GPIO_InitStruct.pin = ...; GPIO_InitStruct.mode = GPIO_MODE_OUTPUT; HAL_GPIO_Init(&GPIO_InitStruct); GPIO_InitStruct.port = ...; GPIO_InitStruct.pin = ...; GPIO_InitStruct.mode = GPIO_MODE_INPUT; HAL_GPIO_Init(&GPIO_InitStruct); } static void EPD_SendCommand (uint8_t command) { HAL_GPIO_WritePin(EPD_DC_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(EPD_CS_PIN, GPIO_PIN_RESET); HAL_SPI_Transmit(EPD_SPI_BUS, &command, 1 ); HAL_GPIO_WritePin(EPD_CS_PIN, GPIO_PIN_SET); } static void EPD_SendData (uint8_t data) { HAL_GPIO_WritePin(EPD_DC_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(EPD_CS_PIN, GPIO_PIN_RESET); HAL_SPI_Transmit(EPD_SPI_BUS, &data, 1 ); HAL_GPIO_WritePin(EPD_CS_PIN, GPIO_PIN_SET); } static void EPD_WaitUntilIdle (void ) { while (HAL_GPIO_ReadPin(EPD_BUSY_PIN) == GPIO_PIN_SET) { delay_ms(10 ); } } void EPD_Init (void ) { EPD_GPIO_Init(); HAL_GPIO_WritePin(EPD_RST_PIN, GPIO_PIN_RESET); delay_ms(10 ); HAL_GPIO_WritePin(EPD_RST_PIN, GPIO_PIN_SET); delay_ms(10 ); EPD_WaitUntilIdle(); EPD_SendCommand(0x01 ); EPD_SendData(0x07 ); EPD_SendData(0x00 ); EPD_SendData(0x0A ); EPD_SendData(0x00 ); EPD_SendCommand(0x06 ); EPD_SendData(0xC7 ); EPD_SendData(0xC7 ); EPD_SendData(0x1D ); EPD_SendCommand(0x04 ); EPD_WaitUntilIdle(); EPD_SendCommand(0x00 ); EPD_SendData(0xCF ); EPD_SendCommand(0x30 ); EPD_SendData(0x3A ); EPD_SendCommand(0x41 ); EPD_SendData(0x00 ); EPD_SendCommand(0x50 ); EPD_SendData(0x77 ); EPD_Clear(); } void EPD_Sleep (void ) { EPD_SendCommand(0x02 ); EPD_WaitUntilIdle(); EPD_SendCommand(0x07 ); EPD_SendData(0xA5 ); } void EPD_FullRefresh (const uint8_t *image_buffer) { EPD_SendCommand(0x10 ); for (uint16_t i = 0 ; i < EPD_WIDTH * EPD_HEIGHT / 8 ; i++) { EPD_SendData(image_buffer[i]); } EPD_SendCommand(0x13 ); for (uint16_t i = 0 ; i < EPD_WIDTH * EPD_HEIGHT / 8 ; i++) { EPD_SendData(0x00 ); } EPD_SendCommand(0x12 ); EPD_WaitUntilIdle(); } void EPD_Clear (void ) { memset (epaper_buffer, 0xFF , sizeof (epaper_buffer)); EPD_FullRefresh(epaper_buffer); } void EPD_DrawPixel (uint16_t x, uint16_t y, uint8_t color) { if (x >= EPD_WIDTH || y >= EPD_HEIGHT) return ; uint16_t byte_index = (x + ((uint32_t )y / 8 ) * EPD_WIDTH); uint8_t bit_index = y % 8 ; if (color == EPD_BLACK) { epaper_buffer[byte_index] &= ~(1 << bit_index); } else { epaper_buffer[byte_index] |= (1 << bit_index); } } void EPD_DrawChar (uint16_t x, uint16_t y, char ch, uint8_t color) { } void EPD_DrawString (uint16_t x, uint16_t y, const char *str, uint8_t color) { uint16_t current_x = x; while (*str) { EPD_DrawChar(current_x, y, *str, color); current_x += 8 ; str++; } } void EPD_DrawNumber (uint16_t x, uint16_t y, int32_t number, uint8_t color) { char str_num[12 ]; sprintf (str_num, "%ld" , number); EPD_DrawString(x, y, str_num, color); }
传感器驱动 (Drivers/DeviceDrivers/Inc/sensor_driver.h & .c)
这里假设使用 DHT22 温湿度传感器,采用 GPIO 模拟单总线方式。
sensor_driver.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #ifndef __SENSOR_DRIVER_H__ #define __SENSOR_DRIVER_H__ #include <stdint.h> #include <stdbool.h> typedef struct { float temperature; float humidity; bool valid; } SensorDataTypeDef; void Sensor_Init (void ) ;SensorDataTypeDef Sensor_ReadData (void ) ; #endif
sensor_driver.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include "sensor_driver.h" #include "hal_gpio.h" #include "config.h" #include "delay.h" GPIO_InitTypeDef sensor_data_pin = { .port = ..., .pin = ..., .mode = GPIO_MODE_OUTPUT }; void Sensor_Init (void ) { HAL_GPIO_Init(&sensor_data_pin); } SensorDataTypeDef Sensor_ReadData (void ) { SensorDataTypeDef sensor_data = {0.0f , 0.0f , false }; uint8_t data[5 ] = {0 }; HAL_GPIO_WritePin(&sensor_data_pin, GPIO_PIN_RESET); HAL_GPIO_SetPinMode(&sensor_data_pin, GPIO_MODE_OUTPUT); delay_ms(1 ); HAL_GPIO_WritePin(&sensor_data_pin, GPIO_PIN_SET); delay_us(30 ); HAL_GPIO_SetPinMode(&sensor_data_pin, GPIO_MODE_INPUT); delay_us(80 ); if (HAL_GPIO_ReadPin(&sensor_data_pin) == GPIO_PIN_SET) { return sensor_data; } delay_us(80 ); if (HAL_GPIO_ReadPin(&sensor_data_pin) == GPIO_PIN_RESET) { return sensor_data; } for (uint8_t byte_index = 0 ; byte_index < 5 ; byte_index++) { for (uint8_t bit_index = 0 ; bit_index < 8 ; bit_index++) { delay_us(50 ); delay_us(30 ); if (HAL_GPIO_ReadPin(&sensor_data_pin) == GPIO_PIN_SET) { data[byte_index] |= (1 << (7 - bit_index)); } delay_us(50 ); } } if (data[4 ] != (data[0 ] + data[1 ] + data[2 ] + data[3 ])) { return sensor_data; } sensor_data.humidity = ((float )data[0 ] * 256 + data[1 ]) / 10.0f ; sensor_data.temperature = ((float )(data[2 ] & 0x7F ) * 256 + data[3 ]) / 10.0f ; if (data[2 ] & 0x80 ) { sensor_data.temperature = -sensor_data.temperature; } sensor_data.valid = true ; return sensor_data; }
5. 服务层 (Core/Src & Inc)
时钟服务 (Core/Src/clock.c & Core/Inc/clock.h)
clock.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef __CLOCK_H__ #define __CLOCK_H__ #include <stdint.h> #include <stdbool.h> typedef struct { uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; } TimeTypeDef; void Clock_Init (void ) ;TimeTypeDef Clock_GetTime (void ) ; void Clock_SetTime (TimeTypeDef time) ;void Clock_UpdateTime (void ) ;uint8_t Clock_GetWeekday (TimeTypeDef time) ; #endif
clock.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include "clock.h" #include "config.h" #include "hal_timer.h" static TimeTypeDef current_time;static uint32_t seconds_count = 0 ; void Clock_Init (void ) { current_time.year = DEFAULT_YEAR; current_time.month = DEFAULT_MONTH; current_time.day = DEFAULT_DAY; current_time.hour = DEFAULT_HOUR; current_time.minute = DEFAULT_MINUTE; current_time.second = DEFAULT_SECOND; HAL_Timer_Init(TIMER_INTERRUPT_FREQ); HAL_Timer_Start(); } TimeTypeDef Clock_GetTime (void ) { return current_time; } void Clock_SetTime (TimeTypeDef time) { current_time = time; seconds_count = 0 ; } void Clock_UpdateTime (void ) { seconds_count++; if (seconds_count >= (SYS_CLK_FREQ / TIMER_INTERRUPT_FREQ)) { seconds_count = 0 ; current_time.second++; if (current_time.second >= 60 ) { current_time.second = 0 ; current_time.minute++; if (current_time.minute >= 60 ) { current_time.minute = 0 ; current_time.hour++; if (current_time.hour >= 24 ) { current_time.hour = 0 ; current_time.day++; if (current_time.day > 31 ) { current_time.day = 1 ; current_time.month++; if (current_time.month > 12 ) { current_time.month = 1 ; current_time.year++; } } } } } } } uint8_t Clock_GetWeekday (TimeTypeDef time) { if (time.month == 1 || time.month == 2 ) { time.month += 12 ; time.year--; } uint8_t week = (time.day + 2 * time.month + 3 * (time.month + 1 ) / 5 + time.year + time.year / 4 - time.year / 100 + time.year / 400 ) % 7 ; return week; }
闹钟服务 (Core/Src/alarm.c & Core/Inc/alarm.h)
alarm.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #ifndef __ALARM_H__ #define __ALARM_H__ #include <stdint.h> #include <stdbool.h> #include "clock.h" typedef struct { bool enabled; TimeTypeDef time; uint8_t repeat_mode; } AlarmTypeDef; #define ALARM_REPEAT_DAILY 0 void Alarm_Init (void ) ;bool Alarm_SetAlarm (uint8_t alarm_index, AlarmTypeDef alarm) ;AlarmTypeDef Alarm_GetAlarm (uint8_t alarm_index) ; bool Alarm_EnableAlarm (uint8_t alarm_index, bool enable) ;void Alarm_CheckAlarms (void ) ;void Alarm_TriggerHandler (uint8_t alarm_index) ;#endif
alarm.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include "alarm.h" #include "config.h" #include "display.h" static AlarmTypeDef alarms[ALARM_MAX_COUNT]; void Alarm_Init (void ) { for (uint8_t i = 0 ; i < ALARM_MAX_COUNT; i++) { alarms[i].enabled = false ; alarms[i].time.hour = 0 ; alarms[i].time.minute = 0 ; alarms[i].repeat_mode = ALARM_REPEAT_DAILY; } } bool Alarm_SetAlarm (uint8_t alarm_index, AlarmTypeDef alarm) { if (alarm_index >= ALARM_MAX_COUNT) return false ; alarms[alarm_index] = alarm; return true ; } AlarmTypeDef Alarm_GetAlarm (uint8_t alarm_index) { if (alarm_index >= ALARM_MAX_COUNT) { AlarmTypeDef invalid_alarm = {false , {0 ,0 ,0 ,0 ,0 ,0 }, ALARM_REPEAT_DAILY}; return invalid_alarm; } return alarms[alarm_index]; } bool Alarm_EnableAlarm (uint8_t alarm_index, bool enable) { if (alarm_index >= ALARM_MAX_COUNT) return false ; alarms[alarm_index].enabled = enable; return true ; } void Alarm_CheckAlarms (void ) { TimeTypeDef current_time = Clock_GetTime(); for (uint8_t i = 0 ; i < ALARM_MAX_COUNT; i++) { if (alarms[i].enabled && alarms[i].time.hour == current_time.hour && alarms[i].time.minute == current_time.minute && current_time.second == 0 ) { Alarm_TriggerHandler(i); } } } void Alarm_TriggerHandler (uint8_t alarm_index) { for (uint8_t i = 0 ; i < 5 ; i++) { EPD_Clear(); delay_ms(200 ); delay_ms(200 ); } }
传感器服务 (Core/Src/sensor.c & Core/Inc/sensor.h)
sensor.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef __SENSOR_H__ #define __SENSOR_H__ #include <stdint.h> #include <stdbool.h> #include "sensor_driver.h" void SensorService_Init (void ) ;SensorDataTypeDef SensorService_GetData (void ) ; #endif
sensor.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "sensor.h" #include "sensor_driver.h" #include "delay.h" #include "config.h" static SensorDataTypeDef current_sensor_data;void SensorService_Init (void ) { Sensor_Init(); current_sensor_data.valid = false ; current_sensor_data.temperature = 0.0f ; current_sensor_data.humidity = 0.0f ; } SensorDataTypeDef SensorService_GetData (void ) { static uint32_t last_read_time = 0 ; uint32_t current_time_ms = HAL_GetTick(); if (current_time_ms - last_read_time >= SENSOR_READ_INTERVAL_MS) { last_read_time = current_time_ms; current_sensor_data = Sensor_ReadData(); } return current_sensor_data; }
显示服务 (Core/Src/display.c & Core/Inc/display.h)
display.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef __DISPLAY_H__ #define __DISPLAY_H__ #include <stdint.h> #include <stdbool.h> #include "epaper_driver.h" #include "clock.h" #include "sensor.h" void Display_Init (void ) ;void Display_Update (TimeTypeDef time, SensorDataTypeDef sensor_data) ;#endif
display.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include "display.h" #include "epaper_driver.h" #include "string.h" #include "stdio.h" #include "config.h" void Display_Init (void ) { EPD_Init(); } void Display_Update (TimeTypeDef time, SensorDataTypeDef sensor_data) { EPD_Clear(); char time_str[9 ]; sprintf (time_str, "%02d:%02d:%02d" , time.hour, time.minute, time.second); EPD_DrawString(50 , 20 , time_str, EPD_BLACK); char date_str[20 ]; const char *week_days[] = {"日" , "一" , "二" , "三" , "四" , "五" , "六" }; uint8_t weekday = Clock_GetWeekday(time); sprintf (date_str, "%04d年%02d月%02d日 星期%s" , time.year, time.month, time.day, week_days[weekday]); EPD_DrawString(20 , 80 , date_str, EPD_BLACK); char temp_str[15 ], humi_str[15 ]; if (sensor_data.valid) { sprintf (temp_str, "温度: %.1f℃" , sensor_data.temperature); sprintf (humi_str, "湿度: %.1f%%" , sensor_data.humidity); } else { strcpy (temp_str, "温度: --℃" ); strcpy (humi_str, "湿度: --%%" ); } EPD_DrawString(20 , 100 , temp_str, EPD_BLACK); EPD_DrawString(150 , 100 , humi_str, EPD_BLACK); EPD_FullRefresh(epaper_buffer); }
6. 主程序 (Core/Src/main.c & Core/Inc/main.h)
main.h:
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef __MAIN_H__ #define __MAIN_H__ #include <stdint.h> void System_Init (void ) ;void Main_Loop (void ) ;#endif
main.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include "main.h" #include "clock.h" #include "sensor.h" #include "display.h" #include "alarm.h" #include "delay.h" #include "config.h" void System_Init (void ) { delay_init(); Clock_Init(); SensorService_Init(); Display_Init(); Alarm_Init(); AlarmTypeDef alarm1 = {true , {0 , 0 , 0 , 8 , 0 , 0 }, ALARM_REPEAT_DAILY}; AlarmTypeDef alarm2 = {false , {0 , 0 , 0 , 12 , 30 , 0 }, ALARM_REPEAT_DAILY}; AlarmTypeDef alarm3 = {true , {0 , 0 , 0 , 18 , 45 , 0 }, ALARM_REPEAT_DAILY}; Alarm_SetAlarm(0 , alarm1); Alarm_SetAlarm(1 , alarm2); Alarm_SetAlarm(2 , alarm3); TimeTypeDef current_time = Clock_GetTime(); SensorDataTypeDef sensor_data = SensorService_GetData(); Display_Update(current_time, sensor_data); } #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer0_A0_ISR (void ) { Clock_UpdateTime(); TA0CTL &= ~CCIFG; } void Main_Loop (void ) { static uint32_t last_display_update_time = 0 ; while (1 ) { uint32_t current_time_ms = HAL_GetTick(); Alarm_CheckAlarms(); if (current_time_ms - last_display_update_time >= DISPLAY_REFRESH_INTERVAL_MS) { last_display_update_time = current_time_ms; TimeTypeDef current_time = Clock_GetTime(); SensorDataTypeDef sensor_data = SensorService_GetData(); Display_Update(current_time, sensor_data); } delay_ms(10 ); } } int main (void ) { WDTCTL = WDTPW | WDTHOLD; System_Init(); Main_Loop(); return 0 ; }
7. Makefile (Makefile)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 PROJECT_NAME = ePaperClock SRC_DIRS = Core/Src Drivers/HAL/Src Drivers/DeviceDrivers/Src Config/Src INC_DIRS = Core/Inc Drivers/HAL/Inc Drivers/DeviceDrivers/Inc Config/Inc Libraries/Fonts CC = msp430-gcc LD = msp430-ld OBJCOPY = msp430-objcopy CFLAGS = -g -Os -Wall -mmcu=msp430g2553 -I. CFLAGS += $(addprefix -I,$(INC_DIRS) ) LDFLAGS = -mmcu=msp430g2553 -L. SRCS = $(wildcard $(addsuffix /*.c,$(SRC_DIRS) ) ) OBJS = $(SRCS:.c=.o) TARGET = $(PROJECT_NAME) .elf HEX_TARGET = $(PROJECT_NAME) .hex all: $(HEX_TARGET) $(TARGET) : $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ $(HEX_TARGET) : $(TARGET) $(OBJCOPY) -O ihex $< $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(OBJS) $(TARGET) $(HEX_TARGET) flash: $(HEX_TARGET) mspdebug rf2502 "prog $< " .PHONY : all clean flash
代码说明:
代码框架: 以上代码提供了一个基于分层架构的完整代码框架,包括 HAL 层、驱动层、服务层和应用层,结构清晰,模块化程度高。
关键模块实现: 实现了时钟服务、闹钟服务、传感器服务、显示服务等关键模块的核心功能,包括时间更新、闹钟检测、传感器数据读取、墨水屏显示等。
注释详细: 代码中添加了大量的注释,解释了代码的功能和实现思路,方便理解和学习。
可扩展性: 代码预留了扩展接口,例如闹钟重复模式、更多传感器类型、更高级的UI界面等,方便未来扩展功能。
低功耗考虑: 代码结构上易于集成低功耗模式,例如在主循环中可以添加进入低功耗模式的代码,并在定时器中断中唤醒。墨水屏本身也是低功耗设备,只有刷新时才耗电。
实践验证: 代码中采用的技术和方法都是嵌入式开发中常用的实践技巧,例如分层架构、HAL 抽象、驱动分离、定时器中断、低功耗设计等。
编译和烧录:
安装 MSP430 工具链: 需要安装 MSP430 GCC 编译器和相关工具链(例如 mspdebug)。
配置 Makefile: 根据实际的 MSP430 型号、编译选项、链接选项等修改 Makefile 文件。
编译: 在工程目录下运行 make
命令进行编译,生成 ePaperClock.elf
和 ePaperClock.hex
文件。
烧录: 使用 MSP430 编程器(例如 LaunchPad 或 RF2500)将 ePaperClock.hex
文件烧录到汉朔 Stallar-M DIY 价签的 MSP430 单片机中。 可以使用 make flash
命令 (需要在 Makefile 中配置 mspdebug 命令)。
测试和验证:
基本功能测试: 验证时钟、日期、星期显示是否正确,时间是否准确。
温湿度监测测试: 验证温湿度传感器数据读取是否正常,显示是否正确。
闹钟功能测试: 设置闹钟,验证闹钟是否能准时触发,墨水屏闪烁提示是否正常。
稳定性测试: 长时间运行系统,观察系统是否稳定可靠,是否有崩溃或异常情况。
低功耗测试 (可选): 如果需要,可以使用电流表测量系统功耗,验证低功耗设计是否有效。
维护和升级:
代码维护: 定期检查代码,修复Bug,优化代码结构和性能。
功能升级: 根据需求添加新功能,例如蓝牙同步时间、网络校时、天气预报、更丰富的UI界面等。
硬件升级 (可选): 如果需要更高级的功能,可以考虑更换更强大的 MSP430 型号或添加其他硬件模块(例如蓝牙模块、Wi-Fi 模块)。
总结
这个基于汉朔 2.13寸电子价签的墨水屏时钟项目,采用分层架构设计,提供了可靠、高效、可扩展的代码框架和详细的 C 代码实现。代码涵盖了嵌入式系统开发的各个方面,从硬件抽象层到应用层,从驱动开发到服务构建,再到应用逻辑实现。 通过这个项目,您可以深入理解嵌入式系统开发的完整流程和关键技术,并将其应用到更复杂的嵌入式系统设计中。
请注意:
硬件连接: 代码中关于 GPIO、SPI、I2C 等硬件配置需要根据汉朔 Stallar-M DIY 价签的实际硬件连接进行配置。
墨水屏驱动: 墨水屏驱动代码需要根据实际使用的墨水屏控制器型号和 datasheet 进行编写和调整。
传感器驱动: 传感器驱动代码需要根据实际使用的温湿度传感器型号和通信方式进行编写和调整。
字库: 代码中使用了简单的字符绘制函数,如果需要显示更丰富的字体和字符,需要集成字库,并实现更完善的字符绘制功能。
延时函数: 代码中使用了 delay_ms()
和 delay_us()
延时函数,需要根据实际的系统时钟频率来实现精确的延时。 可以使用 MSP430 的定时器或 SysTick 实现高精度延时。
低功耗模式: 代码中只是简单地使用了 delay_ms()
代替低功耗模式,实际应用中需要根据 MSP430 的低功耗模式配置,进入 LPM (Low Power Mode) 低功耗模式,以最大限度地降低功耗。
错误处理: 代码中只包含了基本的错误处理,例如传感器数据校验,实际应用中需要添加更完善的错误处理机制,提高系统的健壮性。
希望这份详细的解答和代码示例能够帮助您完成这个嵌入式墨水屏时钟项目!