用联盛德HLK-W806和ST7567 LCD自制一个简易天气站:从驱动到UI显示的完整项目

发布时间:2026/6/8 2:22:47

用联盛德HLK-W806和ST7567 LCD自制一个简易天气站:从驱动到UI显示的完整项目 基于联盛德HLK-W806与ST7567 LCD的智能天气站开发实战在嵌入式开发领域将底层硬件驱动与实用功能结合一直是创客们热衷的挑战。联盛德HLK-W806作为一款高性价比的物联网开发板搭配ST7567 LCD显示屏能够构建出功能完善且成本可控的智能设备。本文将带您从零开始开发一个具备温度、湿度监测功能的简易天气站涵盖硬件连接、驱动移植、UI设计到性能优化的全流程。1. 项目规划与硬件准备开发一个完整的嵌入式项目合理的规划与硬件选型是成功的第一步。我们需要明确功能需求并选择合适的组件。核心硬件清单主控板联盛德HLK-W806开发板主频240MHz的C-SKY CK804处理器内置288KB SRAM与1MB Flash丰富的外设接口(SPI/I2C/UART等)显示模块ST7567 LCD屏幕(128x64分辨率)单色点阵显示支持4线SPI接口内置显存管理环境传感器DHT22温湿度模块温度测量范围-40~80℃ (±0.5℃)湿度测量范围0~100% RH (±2%)单总线数字输出硬件连接示意图ST7567引脚W806 GPIO功能说明CSBPB14片选信号RESETPB10硬件复位AOPB11数据/命令选择SCLKPB15SPI时钟SDAPB17SPI数据输入VDD3.3V电源正极GNDGND电源地提示ST7567的背光LED_A引脚建议通过1-5K限流电阻连接至W806的PB16以便程序控制亮度。2. ST7567驱动开发与图形库封装要让LCD正常工作首先需要建立稳定的通信基础。联盛德WM-SDK提供了完善的SPI外设驱动我们可以基于此实现ST7567的底层控制。2.1 SPI通信初始化// 硬件SPI初始化配置 void SPI_Init(void) { SPI_InitTypeDef spi_init_struct; spi_init_struct.SPI_Mode SPI_MODE_MASTER; spi_init_struct.SPI_FrameSize SPI_FRAMESIZE_8BIT; spi_init_struct.SPI_CPOL SPI_CPOL_LOW; spi_init_struct.SPI_CPHA SPI_CPHA_1EDGE; spi_init_struct.SPI_NSS SPI_NSS_SOFT; spi_init_struct.SPI_Endian SPI_ENDIAN_MSB; spi_init_struct.SPI_Clock SPI_CLOCK_DIV16; SPI_Init(SPI0, spi_init_struct); SPI_Enable(SPI0); }2.2 基本绘图功能实现ST7567采用分页式显存管理每页对应屏幕上的8行像素。我们需要封装核心的像素操作函数// 在指定位置绘制像素点 void LCD_DrawPixel(uint8_t x, uint8_t y, uint8_t color) { if(x LCD_WIDTH || y LCD_HEIGHT) return; uint8_t page y / 8; uint8_t bit_mask 1 (y % 8); if(color) { frame_buffer[page][x] | bit_mask; } else { frame_buffer[page][x] ~bit_mask; } } // 全屏刷新函数 void LCD_Update(void) { for(uint8_t page0; page8; page) { LCD_SetPage(page); LCD_SetColumn(0); for(uint8_t col0; col128; col) { LCD_WriteData(frame_buffer[page][col]); } } }基于像素操作我们可以进一步构建高级图形功能// 绘制直线(Bresenham算法) void LCD_DrawLine(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { int dx abs(x1-x0), sx x0x1 ? 1 : -1; int dy abs(y1-y0), sy y0y1 ? 1 : -1; int err (dxdy ? dx : -dy)/2; while(1){ LCD_DrawPixel(x0, y0, 1); if(x0x1 y0y1) break; int e2 err; if(e2 -dx) { err - dy; x0 sx; } if(e2 dy) { err dx; y0 sy; } } } // 绘制圆形(中点圆算法) void LCD_DrawCircle(uint8_t x0, uint8_t y0, uint8_t r) { int x r, y 0; int err 0; while(x y) { LCD_DrawPixel(x0 x, y0 y, 1); LCD_DrawPixel(x0 y, y0 x, 1); LCD_DrawPixel(x0 - y, y0 x, 1); LCD_DrawPixel(x0 - x, y0 y, 1); LCD_DrawPixel(x0 - x, y0 - y, 1); LCD_DrawPixel(x0 - y, y0 - x, 1); LCD_DrawPixel(x0 y, y0 - x, 1); LCD_DrawPixel(x0 x, y0 - y, 1); if(err 0) { y 1; err 2*y 1; } if(err 0) { x - 1; err - 2*x 1; } } }3. 用户界面设计与实现良好的用户界面是提升产品体验的关键。针对天气站的需求我们需要设计简洁直观的信息展示方式。3.1 界面布局规划采用分区域显示策略------------------------------- | 天气图标区 | | | ------------------------------- | 温度 | 湿度 | 时间 | | 23°C | 45% | 14:25 | ------------------------------- | 预测趋势图 | -------------------------------3.2 字体与图标处理在资源受限的MCU上需要精心设计字体资源。我们可以使用位图字体工具生成定制字体// 6x8像素ASCII字体示例 const uint8_t Font_6x8[] { // 字符0 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 字符1 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 其他字符定义... }; // 显示字符串函数 void LCD_DrawString(uint8_t x, uint8_t y, char *str, const uint8_t *font) { while(*str) { LCD_DrawChar(x, y, *str, font); x 6; // 字符宽度 str; } }天气图标可以采用预先设计的8x8或16x16像素位图// 晴天图标(16x16) const uint8_t icon_sunny[] { 0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x00, 0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x00, // 其余数据... };3.3 动态数据更新策略为避免频繁全屏刷新导致的闪烁采用差异更新机制// 温度显示区域更新函数 void UpdateTempDisplay(float temp) { static float last_temp 0; if(temp ! last_temp) { char buf[16]; sprintf(buf, %2.1f°C, temp); // 清除原内容 LCD_FillRect(0, 40, 40, 16, 0); // 绘制新内容 LCD_DrawString(0, 40, buf, Font_6x8); last_temp temp; } }4. 传感器数据采集与处理环境数据的准确采集是天气站的核心功能。DHT22传感器通过单总线协议通信需要精确的时序控制。4.1 DHT22驱动实现// DHT22数据读取函数 int8_t DHT22_Read(float *temp, float *humi) { uint8_t data[5] {0}; uint8_t i,j; // 主机启动信号 GPIO_ResetBits(DHT22_GPIO, DHT22_PIN); Delay_ms(1); GPIO_SetBits(DHT22_GPIO, DHT22_PIN); Delay_us(30); // 等待从机响应 if(!GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN)) { uint32_t timeout 10000; while(!GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN) timeout--); timeout 10000; while(GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN) timeout--); // 读取40位数据 for(i0; i5; i) { for(j0; j8; j) { timeout 10000; while(!GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN) timeout--); Delay_us(30); if(GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN)) { data[i] | (1 (7-j)); timeout 10000; while(GPIO_ReadInputDataBit(DHT22_GPIO, DHT22_PIN) timeout--); } } } // 校验和验证 if(data[4] (data[0]data[1]data[2]data[3])) { *humi (data[0]8 | data[1]) * 0.1; *temp ((data[2]0x7F)8 | data[3]) * 0.1; if(data[2]0x80) *temp * -1; return 0; } } return -1; }4.2 数据滤波处理传感器数据可能存在噪声采用滑动平均滤波提升稳定性#define FILTER_SIZE 5 typedef struct { float buffer[FILTER_SIZE]; uint8_t index; float sum; } Filter; void Filter_Init(Filter *f) { memset(f, 0, sizeof(Filter)); } float Filter_Add(Filter *f, float value) { f-sum - f-buffer[f-index]; f-buffer[f-index] value; f-sum value; f-index (f-index 1) % FILTER_SIZE; return f-sum / FILTER_SIZE; }5. 系统优化与性能提升在资源受限的嵌入式系统中性能优化尤为重要。ST7567 LCD的刷新率较低需要特别处理。5.1 显示刷新优化关键优化策略局部刷新仅更新变化的内容区域双缓冲机制在内存中完成绘制后再整体更新刷新频率控制限制最高刷新率避免闪烁// 带局部刷新标志的显示系统 typedef struct { uint8_t buffer[8][128]; uint8_t dirty[8]; // 每页的脏标记 } Display; void Display_Init(Display *disp) { memset(disp, 0, sizeof(Display)); } void Display_SetPixel(Display *disp, uint8_t x, uint8_t y, uint8_t color) { uint8_t page y / 8; uint8_t bit y % 8; if(color) { disp-buffer[page][x] | (1 bit); } else { disp-buffer[page][x] ~(1 bit); } disp-dirty[page] 1; } void Display_Update(Display *disp) { static uint32_t last_update 0; uint32_t now HAL_GetTick(); // 限制刷新率(100ms间隔) if(now - last_update 100) return; for(uint8_t page0; page8; page) { if(disp-dirty[page]) { LCD_SetPage(page); LCD_SetColumn(0); for(uint8_t col0; col128; col) { LCD_WriteData(disp-buffer[page][col]); } disp-dirty[page] 0; } } last_update now; }5.2 电源管理为延长电池供电时的使用时间实现低功耗模式void Enter_LowPowerMode(void) { // 关闭外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI0, DISABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_GPIOB, DISABLE); // 配置唤醒源(如RTC或外部中断) EXTI_InitTypeDef exti_init; exti_init.EXTI_Line EXTI_Line0; exti_init.EXTI_Mode EXTI_Mode_Interrupt; exti_init.EXTI_Trigger EXTI_Trigger_Rising; exti_init.EXTI_LineCmd ENABLE; EXTI_Init(exti_init); // 进入停止模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新初始化系统 SystemInit(); GPIO_Init(); SPI_Init(); }6. 项目进阶与扩展基础功能实现后可以考虑以下扩展方向提升产品价值6.1 无线数据传输通过W806内置的WiFi功能实现数据上传至云平台// WiFi连接示例 void WiFi_Connect(void) { tls_wifi_set_oneshot_flag(1); tls_wifi_connect((uint8_t*)SSID, strlen(SSID), (uint8_t*)password, strlen(password), 0); // 等待连接成功 while(tls_wifi_get_state() ! WIFI_STATE_CONNECTED) { Delay_ms(100); } } // HTTP数据上报 void Upload_Data(float temp, float humi) { char url[256]; sprintf(url, http://api.weather.com/data?temp%.1fhumi%.1f, temp, humi); struct tls_http_request *req tls_http_request_create(url); tls_http_request_set_method(req, HTTP_METHOD_GET); tls_http_request_perform(req); tls_http_request_destroy(req); }6.2 多级菜单系统实现交互式菜单提升用户体验typedef struct { char *title; void (*draw)(void); void (*handle)(uint8_t key); MenuItem *children; uint8_t child_count; } MenuItem; MenuItem main_menu[] { {实时数据, Draw_CurrentData, Handle_CurrentData, NULL, 0}, {历史记录, Draw_History, Handle_History, history_submenu, 2}, {系统设置, Draw_Settings, Handle_Settings, settings_submenu, 3} }; void Menu_Draw(MenuItem *menu, uint8_t count, uint8_t selected) { LCD_Clear(); for(uint8_t i0; icount; i) { if(i selected) { LCD_DrawString(10, i*16, , Font_6x8); } LCD_DrawString(20, i*16, menu[i].title, Font_6x8); } LCD_Update(); }6.3 数据记录功能利用W806的内部Flash实现简易数据存储#define LOG_SIZE 24*7 // 存储一周数据(每小时一条) typedef struct { float temp; float humi; uint32_t timestamp; } LogEntry; void Flash_WriteLog(uint32_t addr, LogEntry *log) { FLASH_Unlock(); FLASH_ErasePage(addr); uint32_t *src (uint32_t*)log; for(uint8_t i0; isizeof(LogEntry)/4; i) { FLASH_ProgramWord(addr i*4, src[i]); } FLASH_Lock(); } void Flash_ReadLog(uint32_t addr, LogEntry *log) { uint32_t *dst (uint32_t*)log; for(uint8_t i0; isizeof(LogEntry)/4; i) { dst[i] *(uint32_t*)(addr i*4); } }7. 项目调试与问题排查开发过程中常见问题及解决方案ST7567显示异常排查表现象可能原因解决方案无任何显示电源未接通检查VDD和GND连接全屏乱码SPI时序错误调整SPI时钟分频显示上下颠倒COM方向设置错误修改ST7567_COM_DIRECTION显示左右镜像SEG方向设置错误调整ST7567_SEG_DIRECTION对比度异常EV参数设置不当重新校准ST7567_SET_EV值部分区域不显示显存偏移错误检查ST7567_X_OFFSET设置DHT22传感器常见问题读取超时检查接线是否正确确保上拉电阻(4.7KΩ)已连接校验和错误可能是信号干扰缩短传感器与MCU距离数据不稳定增加软件滤波避免频繁读取(间隔≥2秒)WiFi连接调试技巧# 在开发主机上使用ping测试网络连通性 ping 192.168.1.1 # 查看W806分配的IP地址 tls_wifi_get_ip_addr() # 使用Wireshark抓包分析HTTP通信8. 项目部署与维护完成开发后需要考虑产品的实际部署方案8.1 外壳设计与安装3D打印设计要点预留足够的通风孔确保传感器准确性考虑LCD可视角度设计倾斜面为WiFi天线留出无遮挡区域8.2 OTA远程升级实现固件无线更新功能// OTA升级流程 void OTA_Update(void) { WiFi_Connect(); // 下载新固件 struct tls_http_request *req tls_http_request_create(OTA_URL); tls_http_request_set_method(req, HTTP_METHOD_GET); uint8_t *data tls_http_request_perform(req); // 校验固件 if(Check_Firmware(data)) { // 写入Flash FLASH_Unlock(); FLASH_ErasePage(APP_ADDR); for(int i0; iFW_SIZE; i4) { FLASH_ProgramWord(APP_ADDRi, *(uint32_t*)(datai)); } FLASH_Lock(); // 重启应用 NVIC_SystemReset(); } tls_http_request_destroy(req); }8.3 长期运行稳定性保障系统看门狗配置void Watchdog_Init(void) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_256); // 约1.6秒超时 IWDG_SetReload(0xFFF); IWDG_Enable(); } void Watchdog_Feed(void) { IWDG_ReloadCounter(); }异常复位记录void Log_ResetReason(void) { uint8_t reason RCC_GetFlagStatus(RCC_FLAG_PORRST) ? 1 : RCC_GetFlagStatus(RCC_FLAG_PINRST) ? 2 : RCC_GetFlagStatus(RCC_FLAG_SWRST) ? 3 : 0; Flash_Write(RESET_LOG_ADDR, reason, 1); }通过本项目的完整实现开发者不仅能够掌握HLK-W806与ST7567的基础开发技能还能学习到嵌入式系统设计的全流程方法论。从硬件驱动到用户界面从数据采集到无线通信每个环节都蕴含着嵌入式开发的精髓。

相关新闻