基于STM32的TFT屏表盘时钟设计(ILI9341驱动)

基于STM32的TFT屏表盘时钟设计(ILI9341驱动)

一、系统概述

以STM32F103C8T6(Cortex-M3,72MHz)为主控,通过SPI接口驱动ILI9341 TFT屏(320×240分辨率,16位色),实现模拟表盘时钟功能。核心包括:

应用场景:嵌入式仪表、智能家居控制面板、工业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 开发环境

3.2 核心原理

  1. 表盘绘制:以TFT屏中心为原点(160, 120),绘制半径100px的圆,标注12个主刻度(小时)和60个分刻度,显示数字1~12。

  2. 指针角度计算

    • 秒针:θ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)
  3. 指针绘制:根据角度计算指针端点坐标(以中心为原点,用三角函数计算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

四、测试与验证

  1. 硬件连接:按2.2节连接TFT屏、RTC晶振、按键,确保SPI通信正常。

  2. 功能测试

    • 上电后TFT屏显示表盘,指针随RTC时间准确转动(秒针1秒/格,分针1分/格);
    • 按下“设置”键进入调时模式,通过“加/减”键调整时/分/秒,观察指针是否更新。
  3. 精度测试:连续运行24小时,记录时间误差(应<±5秒)。

六、总结

基于STM32和ILI9341 TFT屏实现了模拟表盘时钟,核心是表盘绘制、指针角度计算与RTC时间管理。通过局部刷新优化显示效果,可扩展日期显示、闹钟功能或触摸交互(添加XPT2046触摸芯片)。

专注于matlab/simulink,电子电路,编程