该项目使用LVGL对STM32单片机进行UI开发
作者:@QSX
邮箱:also0103@163.com
------设计方案------
显示屏: 240*320 LCD触摸屏 ILI9341显示驱动 XPT2046触摸驱动
MCU : STM32F405RGT6
RTC : DS3231
外设 : 无源蜂鸣器,LED
FLASH:片内Flash
UI界面参考手机端番茄钟APP设计
------基础功能------
- 实时时间,掉电走时
- 自动保存数据
- 创建专注任务
- 日期倒数日
- 统计总览 (日均,周均,年均)
- 设置菜单
- 亮度调节
------电路设计原理图------

------相关驱动代码------
- ILI9341显示驱动
点击查看代码
#include "lcd_ili9341.h"
/*------------------- 引脚宏 -------------------*/
#define LCD_CS_PORT GPIOB
#define LCD_CS_PIN GPIO_PIN_9
#define LCD_WR_PORT GPIOB
#define LCD_WR_PIN GPIO_PIN_5
#define LCD_RS_PORT GPIOB
#define LCD_RS_PIN GPIO_PIN_8
#define LCD_RD_PORT GPIOA
#define LCD_RD_PIN GPIO_PIN_8
#define LCD_DATA_PORT GPIOC
/* 16位数据整体输出(只影响低16位) */
#define LCD_DATA_OUT(d) (LCD_DATA_PORT->BSRR = (((uint32_t)(~(d) & 0xFFFF)) << 16) | ((d) & 0xFFFF))
/* 控制线操作 */
#define CS_LOW() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
#define CS_HIGH() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET)
#define WR_LOW() HAL_GPIO_WritePin(LCD_WR_PORT, LCD_WR_PIN, GPIO_PIN_RESET)
#define WR_HIGH() HAL_GPIO_WritePin(LCD_WR_PORT, LCD_WR_PIN, GPIO_PIN_SET)
#define RS_LOW() HAL_GPIO_WritePin(LCD_RS_PORT, LCD_RS_PIN, GPIO_PIN_RESET)
#define RS_HIGH() HAL_GPIO_WritePin(LCD_RS_PORT, LCD_RS_PIN, GPIO_PIN_SET)
#define RD_LOW() HAL_GPIO_WritePin(LCD_RD_PORT, LCD_RD_PIN, GPIO_PIN_RESET)
#define RD_HIGH() HAL_GPIO_WritePin(LCD_RD_PORT, LCD_RD_PIN, GPIO_PIN_SET)
/*------------------- 内部函数 -------------------*/
static void app_LCD_WriteCmd(uint16_t cmd) {
CS_LOW();
RS_LOW();
LCD_DATA_OUT(cmd);
WR_LOW();
__NOP(); __NOP();
WR_HIGH();
CS_HIGH();
}
static void app_LCD_WriteData(uint16_t data) {
CS_LOW();
RS_HIGH();
LCD_DATA_OUT(data);
WR_LOW();
__NOP(); __NOP();
WR_HIGH();
CS_HIGH();
}
static void app_LCD_WriteReg(uint16_t cmd, uint16_t data) {
app_LCD_WriteCmd(cmd);
app_LCD_WriteData(data);
}
static void app_LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
app_LCD_WriteCmd(0x2A); // 列地址
app_LCD_WriteData(x1 >> 8);
app_LCD_WriteData(x1 & 0xFF);
app_LCD_WriteData(x2 >> 8);
app_LCD_WriteData(x2 & 0xFF);
app_LCD_WriteCmd(0x2B); // 页地址
app_LCD_WriteData(y1 >> 8);
app_LCD_WriteData(y1 & 0xFF);
app_LCD_WriteData(y2 >> 8);
app_LCD_WriteData(y2 & 0xFF);
app_LCD_WriteCmd(0x2C); // 内存写
}
/*------------------- 公开 API -------------------*/
void app_LCD_Init(void) {
/* GPIO 时钟使能 */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 数据口 PC0~PC15
GPIO_InitStruct.Pin = 0xFFFF;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 若报错可改为 GPIO_SPEED_HIGH
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// 控制引脚 CS, WR, RS
GPIO_InitStruct.Pin = LCD_CS_PIN | LCD_WR_PIN | LCD_RS_PIN;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// RD引脚
GPIO_InitStruct.Pin = LCD_RD_PIN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 初始电平
CS_HIGH();
WR_HIGH();
RS_LOW();
RD_HIGH();
HAL_Delay(20); // 等待硬件复位完成
/* ILI9341 初始化序列 */
app_LCD_WriteReg(0x01, 0x0000); // 软复位
HAL_Delay(150);
app_LCD_WriteReg(0xCF, 0x00);
app_LCD_WriteReg(0xED, 0x64);
app_LCD_WriteReg(0xE8, 0x85);
app_LCD_WriteReg(0xCB, 0x39);
app_LCD_WriteReg(0xF7, 0x20);
app_LCD_WriteReg(0xEA, 0x00);
app_LCD_WriteReg(0xC0, 0x23);
app_LCD_WriteReg(0xC1, 0x10);
app_LCD_WriteReg(0xC5, 0x3E);
app_LCD_WriteReg(0xC7, 0x86);
app_LCD_WriteReg(0x36, 0x48); // 旋转方向
app_LCD_WriteReg(0x3A, 0x55); // 16位色
app_LCD_WriteReg(0xB1, 0x00);
app_LCD_WriteReg(0xB6, 0x0A);
app_LCD_WriteReg(0xF2, 0x00);
app_LCD_WriteReg(0x26, 0x01);
app_LCD_WriteReg(0xE0, 0x0F); // Gamma+
app_LCD_WriteReg(0xE1, 0x00); // Gamma-
app_LCD_WriteReg(0x11, 0x0000); // 退出睡眠
HAL_Delay(120);
app_LCD_WriteReg(0x29, 0x0000); // 显示开启
HAL_Delay(20);
}
void app_LCD_Clear(uint16_t color) {
app_LCD_SetWindow(0, 0, 239, 319);
for (uint32_t i = 0; i < 240UL * 320; i++) {
app_LCD_WriteData(color);
}
}
void app_LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) {
app_LCD_SetWindow(x, y, x, y);
app_LCD_WriteData(color);
}
void app_LCD_Fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
app_LCD_SetWindow(x1, y1, x2, y2);
uint32_t pixelCount = (uint32_t)(x2 - x1 + 1) * (uint32_t)(y2 - y1 + 1);
for (uint32_t i = 0; i < pixelCount; i++) {
app_LCD_WriteData(color);
}
}
void app_LCD_SetRotation(uint8_t rotation) {
uint16_t val;
switch (rotation) {
case 0: val = 0x48; break;
case 1: val = 0x28; break;
case 2: val = 0x98; break;
case 3: val = 0x78; break;
default: return;
}
app_LCD_WriteReg(0x36, val);
}
/**
* @brief 设置 LCD 的写入窗口(供 LVGL 调用)
* @param x1, y1 : 窗口起始坐标
* @param x2, y2 : 窗口结束坐标
*/
void lcd_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
app_LCD_SetWindow(x1, y1, x2, y2);
}
/**
* @brief 批量写入 RGB565 颜色数据(供 LVGL 调用)
* @param data : 颜色数据数组指针
* @param len : 数据长度(像素个数)
*/
void lcd_write_ram(uint16_t *data, uint32_t len)
{
CS_LOW();
RS_HIGH();
while (len--) {
uint16_t color = *data++;
LCD_DATA_OUT(color);
WR_LOW();
__NOP(); __NOP();
WR_HIGH();
}
CS_HIGH();
}
- XPT2046触摸驱动
点击查看代码
#include "xpt2046.h"
/**
* @brief SPI读写单个字节函数,同步收发
* @param tx_data 要发送给XPT2046的1字节命令/数据
* @retval uint8_t 从XPT2046同步读取到的1字节返回数据
* @note 使用硬件SPI1,全双工收发,阻塞等待直到收发完成
*/
static uint8_t SPI_RW(uint8_t tx_data)
{
uint8_t rx_data = 0;
// HAL库SPI全双工收发:发送tx_data同时接收1字节到rx_data,永不超时
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, HAL_MAX_DELAY);
return rx_data;
}
/**
* @brief 读取XPT2046指定通道的12位ADC转换原始值
* @param cmd XPT2046控制命令(CMD_X读取X轴/CMD_Y读取Y轴)
* @retval uint16_t 12位ADC采样值(范围0~4095)
* @note XPT2046输出格式:16bit数据,高4bit无效,低12bit为ADC结果,需右移4位
*/
uint16_t XPT2046_ReadADC(uint8_t cmd)
{
uint16_t v; // 存放拼接后的16位原始采样数据
uint8_t buf[2]; // 缓存读取的两个字节高8位、低8位
// 拉低片选TCS,选中XPT2046芯片,开始SPI通信
HAL_GPIO_WritePin(TCS_GPIO_Port, TCS_Pin, GPIO_PIN_RESET);
// 第一步:发送通道选择命令,芯片开始ADC转换
SPI_RW(cmd);
// 第二步:发送0x00空字节,读取转换结果高8位
buf[0] = SPI_RW(0x00);
// 第三步:再发0x00空字节,读取转换结果低8位
buf[1] = SPI_RW(0x00);
// 拉高片选,释放XPT2046,结束本次通信
HAL_GPIO_WritePin(TCS_GPIO_Port, TCS_Pin, GPIO_PIN_SET);
// 拼接高低字节:高8位左移8位 + 低8位,得到完整16位数据
v = (uint16_t)buf[0] << 8;
v |= buf[1];
// XPT2046数据格式:bit15~bit12无效,bit11~bit0为12位ADC,右移4位舍弃无效高位
return v >> 4;
}
/**
* @brief 获取触摸屏原始AD采样坐标(未校准,电阻屏原始值)
* @param x 输出参数:X通道原始ADC值
* @param y 输出参数:Y通道原始ADC值
* @note XPT2046特性:首次读取仅唤醒内部参考电压,数值不稳定,需要预读一次丢弃无效数据
*/
void XPT2046_GetXY(uint16_t *x, uint16_t *y)
{
// 第一次读取X轴:dummy空读,仅唤醒芯片内部基准电压,结果丢弃不用
XPT2046_ReadADC(CMD_X);
// 第一次读取Y轴:预转换,消除上电/休眠带来的采样误差
XPT2046_ReadADC(CMD_Y);
// 正式读取稳定后的X、Y原始ADC数值
*x = XPT2046_ReadADC(CMD_X);
*y = XPT2046_ReadADC(CMD_Y);
}
/**
* @brief 触摸屏原始ADC坐标转换为LCD屏幕像素坐标(带校准、边界限幅)
* @param sx 输出:屏幕X像素坐标(0 ~ LCD_WIDTH-1)
* @param sy 输出:屏幕Y像素坐标(0 ~ LCD_HEIGHT-1)
* @note 1. 电阻屏原始XY和屏幕XY坐标轴是翻转互换关系,代码做了映射交换
* 2. 使用四点校准参数CAL_X_MIN/MAX、CAL_Y_MIN/MAX做线性比例换算
* 3. 增加边界保护,防止触摸超出校准范围出现负坐标或超屏幕像素
*/
void XPT2046_GetScreenXY(uint16_t *sx, uint16_t *sy)
{
uint16_t rx, ry; // rx=X通道原始ADC,ry=Y通道原始ADC
int32_t x, y; // 有符号整形,防止减法计算出现负数溢出
// 读取触摸屏未校准原始AD值
XPT2046_GetXY(&rx, &ry);
// 原始Y轴AD值 → 屏幕X像素线性映射
// 公式:屏幕坐标 = (原始值 - 校准最小值) * 屏幕最大像素 / (校准最大值-最小值)
x = ((int32_t)ry - CAL_Y_MIN) * (LCD_WIDTH - 1) / (CAL_Y_MAX - CAL_Y_MIN);
// 原始X轴AD值 → 屏幕Y像素线性映射(电阻屏XY轴互换)
y = ((int32_t)rx - CAL_X_MIN) * (LCD_HEIGHT - 1) / (CAL_X_MAX - CAL_X_MIN);
// 边界限幅:防止触摸偏移导致坐标小于0
if (x < 0) x = 0;
if (y < 0) y = 0;
// 边界限幅:防止坐标超过屏幕最大像素
if (x >= LCD_WIDTH) x = LCD_WIDTH - 1;
if (y >= LCD_HEIGHT) y = LCD_HEIGHT - 1;
// 转换为无符号16位像素坐标输出
*sx = (uint16_t)x;
*sy = (uint16_t)y;
}
浙公网安备 33010602011771号