BA4RDK

欢迎来到我的博客

博客园 首页 新随笔 联系 订阅 管理

该项目使用LVGL对STM32单片机进行UI开发

作者:@QSX
邮箱:also0103@163.com

------设计方案------

显示屏: 240*320 LCD触摸屏 ILI9341显示驱动 XPT2046触摸驱动
MCU : STM32F405RGT6
RTC : DS3231
外设 : 无源蜂鸣器,LED
FLASH:片内Flash

UI界面参考手机端番茄钟APP设计

------基础功能------

  • 实时时间,掉电走时
  • 自动保存数据
  • 创建专注任务
  • 日期倒数日
  • 统计总览 (日均,周均,年均)
  • 设置菜单
  • 亮度调节

------电路设计原理图------

SCH_Schematic1_1-P1_2026-08-07

------相关驱动代码------

  • 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;
}
posted on 2026-08-05 02:37  BA4RDK  阅读(1)  评论(0)    收藏  举报