基于STM32的TFT屏表盘时钟设计(ILI9341驱动)
一、系统概述
以STM32F103C8T6(Cortex-M3,72MHz)为主控,通过SPI接口驱动ILI9341 TFT屏(320×240分辨率,16位色),实现模拟表盘时钟功能。核心包括:
- 表盘绘制:圆形表盘、12小时刻度、60分钟刻度、数字标识;
- 指针动态显示:时针、分针、秒针根据实时时间转动(精度±1秒);
- 时间管理:基于STM32内部RTC实时时钟(32.768kHz晶振),支持掉电走时;
- 交互功能:按键设置时间(时/分/秒)。
应用场景:嵌入式仪表、智能家居控制面板、工业HMI。
二、硬件设计
2.1 核心组件选型
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| 主控 | STM32F103C8T6(64KB Flash,20KB RAM) | TFT屏驱动、RTC控制、指针计算、按键处理 |
| TFT屏 | 2.4寸ILI9341(320×240,SPI接口) | 显示表盘、指针、时间数字 |
| RTC时钟 | 内部RTC+32.768kHz晶振 | 提供实时时间(时/分/秒/年/月/日) |
| 按键 | 3个轻触按键(设置/加/减) | 时间设置(时/分/秒调整) |
| 电源 | 5V/1A DC(或3.7V锂电池+升压模块) | 为系统供电(TFT屏需3.3V,通过LDO转换) |
2.2 硬件连接
| 模块 | STM32引脚(F103C8T6) | 说明 |
|---|---|---|
| ILI9341 | SCK→PA5(SPI1_SCK) | SPI时钟线 |
| MOSI→PA7(SPI1_MOSI) | SPI数据线(主机输出) | |
| CS→PA4(SPI1_NSS) | 片选(低有效) | |
| DC→PA3(GPIO输出) | 数据/命令选择(1=数据,0=命令) | |
| RST→PA2(GPIO输出) | 复位(低电平复位) | |
| RTC | OSC32_IN→PC14,OSC32_OUT→PC15 | 32.768kHz晶振接口 |
| 按键 | SET→PB0,ADD→PB1,SUB→PB2 | 时间设置按键(上拉输入) |
三、软件设计(STM32 HAL库)
3.1 开发环境
- IDE:STM32CubeIDE 1.13.0+
- 库:STM32Cube_FW_F1_V1.8.0(HAL库)
- TFT屏驱动:ILI9341 SPI驱动(含画点、画线、显示字符函数)
3.2 核心原理
-
表盘绘制:以TFT屏中心为原点(160, 120),绘制半径100px的圆,标注12个主刻度(小时)和60个分刻度,显示数字1~12。
-
指针角度计算:
- 秒针:θs=6°/s×秒数(360°/60=6°/s)
- 分针:θm=6°/min×分数+0.1°/s×秒数(6°/min=0.1°/s)
- 时针:θh=30°/h×时数+0.5°/min×分数(30°/h=0.5°/min)
-
指针绘制:根据角度计算指针端点坐标(以中心为原点,用三角函数计算x/y偏移),用画线函数绘制(秒针红色,分针蓝色,时针黑色)。
3.3 核心代码实现
3.3.1 TFT屏驱动(ILI9341 SPI,关键函数)
#include "ili9341.h"
#include "spi.h"
#include "gpio.h"
// 写命令
void ILI9341_WriteCmd(uint8_t cmd) {
HAL_GPIO_WritePin(ILI9341_DC_GPIO_Port, ILI9341_DC_Pin, GPIO_PIN_RESET); // DC=0(命令)
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
}
// 写数据
void ILI9341_WriteData(uint8_t data) {
HAL_GPIO_WritePin(ILI9341_DC_GPIO_Port, ILI9341_DC_Pin, GPIO_PIN_SET); // DC=1(数据)
HAL_SPI_Transmit(&hspi1, &data, 1, 100);
}
// 初始化ILI9341
void ILI9341_Init(void) {
// 复位序列
HAL_GPIO_WritePin(ILI9341_RST_GPIO_Port, ILI9341_RST_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(ILI9341_RST_GPIO_Port, ILI9341_RST_Pin, GPIO_PIN_SET);
HAL_Delay(100);
// 配置显示参数(省略部分命令,完整初始化需参考ILI9341手册)
ILI9341_WriteCmd(0xCF); // 电源控制B
ILI9341_WriteData(0x00);
ILI9341_WriteData(0xC1);
ILI9341_WriteData(0x30);
// ... 其他初始化命令(如0xED、0xE8、0xCB等)
ILI9341_WriteCmd(0x29); // 开启显示
}
// 画点(x,y: 坐标,color: 颜色)
void ILI9341_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
ILI9341_SetWindow(x, y, x, y); // 设置窗口为单点
ILI9341_WriteData(color >> 8); // 高8位
ILI9341_WriteData(color & 0xFF); // 低8位
}
// 画线(x1,y1: 起点,x2,y2: 终点,color: 颜色)
void ILI9341_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
// Bresenham算法(省略实现,可调用LCD库函数)
}
3.3.2 RTC实时时钟配置
#include "rtc.h"
// RTC初始化(32.768kHz晶振,1Hz中断)
void RTC_Init(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
// 启用LSE振荡器(32.768kHz)
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置RTC时钟源为LSE
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
// 初始化RTC
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 0x7F; // 异步分频:128(32.768kHz/128=256Hz)
hrtc.Init.SynchPrediv = 0xFF; // 同步分频:256(256Hz/256=1Hz)
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
HAL_RTC_Init(&hrtc);
// 设置初始时间(2024-01-01 00:00:00)
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0;
sDate.Year = 24; sDate.Month = 1; sDate.Date = 1;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
3.3.3 表盘绘制与指针更新
#include "clock.h"
#include "ili9341.h"
#include "rtc.h"
// 表盘参数
#define CENTER_X 160 // 表盘中心X坐标
#define CENTER_Y 120 // 表盘中心Y坐标
#define RADIUS 100 // 表盘半径(px)
// 绘制静态表盘(圆、刻度、数字)
void Draw_Dial(void) {
// 1. 清屏(黑色背景)
ILI9341_FillScreen(BLACK);
// 2. 绘制表盘外圆(白色)
ILI9341_DrawCircle(CENTER_X, CENTER_Y, RADIUS, WHITE);
// 3. 绘制12个主刻度(小时)和60个分刻度
for (int i=0; i<60; i++) {
float angle = i * 6.0f; // 6°/分(360°/60)
float rad = angle * 3.14159f / 180.0f; // 弧度
uint16_t x1 = CENTER_X + (RADIUS-10) * cos(rad);
uint16_t y1 = CENTER_Y + (RADIUS-10) * sin(rad);
uint16_t x2 = CENTER_X + RADIUS * cos(rad);
uint16_t y2 = CENTER_Y + RADIUS * sin(rad);
if (i % 5 == 0) { // 主刻度(小时)
ILI9341_DrawLine(x1, y1, x2, y2, WHITE); // 长刻度
// 显示小时数字(1~12)
uint8_t hour = i/5 == 0 ? 12 : i/5;
x1 = CENTER_X + (RADIUS-25) * cos(rad) - 5;
y1 = CENTER_Y + (RADIUS-25) * sin(rad) - 5;
ILI9341_ShowChar(x1, y1, hour+'0', WHITE, BLACK, 16);
} else { // 分刻度
ILI9341_DrawLine(x1, y1, x2, y2, GRAY); // 短刻度
}
}
}
// 更新指针(根据RTC时间计算角度,绘制时针/分针/秒针)
void Update_Hands(void) {
RTC_TimeTypeDef sTime;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); // 读取当前时间
uint8_t hour = sTime.Hours;
uint8_t min = sTime.Minutes;
uint8_t sec = sTime.Seconds;
// 1. 计算指针角度(弧度)
float sec_angle = sec * 6.0f * 3.14159f / 180.0f; // 秒针:6°/s
float min_angle = (min * 6.0f + sec * 0.1f) * 3.14159f / 180.0f; // 分针:6°/min+0.1°/s
float hour_angle = (hour * 30.0f + min * 0.5f) * 3.14159f / 180.0f; // 时针:30°/h+0.5°/min
// 2. 计算指针端点坐标(长度:秒针80px,分针60px,时针40px)
uint16_t sec_x = CENTER_X + 80 * sin(sec_angle);
uint16_t sec_y = CENTER_Y - 80 * cos(sec_angle);
uint16_t min_x = CENTER_X + 60 * sin(min_angle);
uint16_t min_y = CENTER_Y - 60 * cos(min_angle);
uint16_t hour_x = CENTER_X + 40 * sin(hour_angle);
uint16_t hour_y = CENTER_Y - 40 * cos(hour_angle);
// 3. 绘制指针(先擦除旧指针,再画新指针,避免闪烁)
static uint16_t last_sec_x, last_sec_y, last_min_x, last_min_y, last_hour_x, last_hour_y;
ILI9341_DrawLine(CENTER_X, CENTER_Y, last_sec_x, last_sec_y, BLACK); // 擦除旧秒针
ILI9341_DrawLine(CENTER_X, CENTER_Y, last_min_x, last_min_y, BLACK); // 擦除旧分针
ILI9341_DrawLine(CENTER_X, CENTER_Y, last_hour_x, last_hour_y, BLACK); // 擦除旧时针
// 画新指针(秒针红色,分针蓝色,时针白色)
ILI9341_DrawLine(CENTER_X, CENTER_Y, sec_x, sec_y, RED);
ILI9341_DrawLine(CENTER_X, CENTER_Y, min_x, min_y, BLUE);
ILI9341_DrawLine(CENTER_X, CENTER_Y, hour_x, hour_y, WHITE);
// 保存当前坐标(下次擦除用)
last_sec_x = sec_x; last_sec_y = sec_y;
last_min_x = min_x; last_min_y = min_y;
last_hour_x = hour_x; last_hour_y = hour_y;
}
3.3.4 主函数(表盘绘制+时间更新)
int main(void) {
HAL_Init();
SystemClock_Config(); // 72MHz时钟配置
MX_GPIO_Init(); // GPIO初始化(TFT屏、按键)
MX_SPI1_Init(); // SPI1初始化(TFT屏)
MX_RTC_Init(); // RTC初始化
ILI9341_Init(); // TFT屏初始化
Draw_Dial(); // 绘制静态表盘
while (1) {
Update_Hands(); // 更新指针
HAL_Delay(1000); // 1秒刷新一次
}
}
参考代码 STM32-TFT屏显示表盘时钟 www.youwenfan.com/contentcnt/182344.html
四、测试与验证
-
硬件连接:按2.2节连接TFT屏、RTC晶振、按键,确保SPI通信正常。
-
功能测试:
- 上电后TFT屏显示表盘,指针随RTC时间准确转动(秒针1秒/格,分针1分/格);
- 按下“设置”键进入调时模式,通过“加/减”键调整时/分/秒,观察指针是否更新。
-
精度测试:连续运行24小时,记录时间误差(应<±5秒)。
六、总结
基于STM32和ILI9341 TFT屏实现了模拟表盘时钟,核心是表盘绘制、指针角度计算与RTC时间管理。通过局部刷新优化显示效果,可扩展日期显示、闹钟功能或触摸交互(添加XPT2046触摸芯片)。