• 21.1 stm32使用LTDC驱动LCD--配置说明


    本文讲解如何配置LTDC驱动LCD的参数配置,以及CubeMx参数配置说明
    本文使用的是淘宝买的一块带电容触摸的液晶显示屏5寸TFT液晶显示屏高清800*480免驱40P通用RGBIPS全视角彩屏GT911
    说实话,价格还是相对挺便宜的,值得入手,哈哈哈
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这款屏幕采用的是RGB888格式
    这里面也就是常用的引脚:
    R0-R7、G0-G7、B0-B7
    DCLK–时钟线
    HSYNC\VSYNC–同步线
    DE–数据使能

    DISP是显示使能,控制屏幕的哈
    背光是通过控制LED+\LED-的电流实现的
    在这里插入图片描述
    在这里插入图片描述
    由此可以找到,LED需要流过20mA电流,下面是它的推荐电路
    在这里插入图片描述
    接着我就绘制了我的PCB,如下所示:我的是电容触摸屏,所以电阻触摸引脚位置悬空的
    在这里插入图片描述
    在这里插入图片描述
    接下来,我们就来CubeMx配置LTDC驱动它吧

    CubeMx配置LTDC

    硬件相关参数设置

    在这里插入图片描述
    注意:这里的HSYNC、VSYNC、DE的有效极性需要和实际相反
    在这里插入图片描述
    由此图可以看出HSYNC、VSYNC、DE的有效极性都是高,clk的下降沿采样
    在这里插入图片描述
    上面我的LTDC配置取得都是典型值
    Pulse Width对应的是HSW和VSW

    引脚需要全部高速
    在这里插入图片描述
    然后就还有LTDC输出给LCD的时钟信号,由上面的数据手册给出的典型值配置25MHz,LTDC会由LTDC_PCLK引脚输出给LCD
    在这里插入图片描述
    在这里插入图片描述
    到此,LTDC硬件相关的参数配置完毕

    LTDC图像层配置

    在这里插入图片描述
    开启全局中断,并且优先级可以设置低点
    在这里插入图片描述
    DMA2D在代码里重新配置过得,可以按此设置
    在这里插入图片描述
    关于FMC的SDRAM存储属性设置可以参考如下:
    在这里插入图片描述
    在这里插入图片描述

    调试

    如何判定硬件问题:
    在函数void MX_LTDC_Init(void)中的HAL_LTDC_Init()后如下处理:
    在这里插入图片描述
    如果LCD能显示红色说明硬件正常,否则有问题
    lcd_base_backlight_set是开启屏幕背光

    完整工程下载:(旧版本,不建议使用,LCD显示刷新没处理后续说的cache更新)

    链接:https://pan.baidu.com/s/1g_VezTfR_-fgqSpPFlvtqQ
    提取码:qqio

    补充:

    该屏幕GT911驱动:

    需要注意的是INT引脚禁止上下拉,应配置为浮空输入
    触摸屏手指触摸时会触摸70ms左右,这段时间其实坐标是同一点,INT引脚持续输出方波信号10ms间隔
    所以应采集判定坐标避免重复点如下函数:

    /****************************************************
    @function:触摸芯片扫描
    @param:(x,y)--点
    @return:-1--无触摸,0--触摸
    @note:只读取一个点,建议10ms执行一次
    注意:此函数未处理重复触摸点,尤其是在两次点击相同像素点的情况下,但是概率不大
    ****************************************************/
    int GT911_Scan(uint16_t *x,uint16_t *y)
    {
        static uint16_t xlast = 0,ylast = 0;
        
        if(!gt911.Enable)return -1;
        if(!GT911_PenInt())return -1;//没有触摸
        GT911_TouchPointRead(x,y);
        if((*x == xlast) && (*y == ylast))return -1;//移除重复点
        xlast = *x;
        ylast = *y;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    完整驱动代码:
    GT911_driver.c
    GT911_driver.h
    i2c_driver.c//模拟IIC
    i2c_driver.h
    delay_driver.c//定时器微秒精确延时
    delay_driver.h

    GT911_driver.h

    #ifndef _GT911_driver_H_
    #define _GT911_driver_H_
    #ifdef __cplusplus
    extern "C" {
    #endif
    #include "stdint.h"
    
    uint16_t GT911_ScreenWidthGet(void);
    uint16_t GT911_ScreenHeigthGet(void);
    int GT911_Init(void);
    int GT911_Scan(uint16_t *x,uint16_t *y);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    GT911_driver.c

    /**********************************************************************
    *file:自己编写的GT911触摸检测
    *author:残梦
    *versions:V1.0
    *date:2023.11.8
    *note:
    注意GT911的INT引脚会在触摸时出现10ms的方波信号,手指单次触摸维持在70ms左右,
    持续触摸会一直到释放,默认高电平,触摸低电平,引脚应浮空严禁上下拉
    **********************************************************************/
    #include "GT911_driver.h"
    #include "i2c_driver.h"
    #include "delay_driver.h"
    #include "gpio.h"
    #include "stdio.h"
    
    /* 定义触笔中断INT的GPIO端口 */
    #define TP_INT_GPIO_CLK_ENABLE()	__HAL_RCC_GPIOD_CLK_ENABLE()
    #define TP_INT_GPIO_PORT              GPIOD
    #define TP_INT_PIN                    GPIO_PIN_4
    #define TP_RST_GPIO_CLK_ENABLE()	__HAL_RCC_GPIOD_CLK_ENABLE()
    #define TP_RST_GPIO_PORT              GPIOD
    #define TP_RST_PIN                    GPIO_PIN_5
    
    //#define GT911_I2C_ADDR1		 0xBA
    #define GT911_READ_XY_REG    0x814E /* 坐标寄存器 */ 
    #define GT911_CLEARBUF_REG   0x814E /* 清除坐标寄存器 */ 
    #define GT911_CONFIG_REG     0x8047 /* 配置参数寄存器 */ 
    #define GT911_COMMAND_REG    0x8040 /* 实时命令 */ 
    #define GT911_PRODUCT_ID_REG 0x8140 /* 芯片ID */ 
    #define GT911_VENDOR_ID_REG  0x814A /* 当前模组选项信息 */ 
    #define GT911_CONFIG_VERSION_REG   0x8047 /* 配置文件版本号 */ 
    #define GT911_CONFIG_CHECKSUM_REG  0x80FF /* 配置文件校验码 */ 
    #define GT911_FIRMWARE_VERSION_REG 0x8144 /* 固件版本号 */ 
    
    typedef struct
    {
        //芯片参数
    	uint8_t	i2cAddress;//i2c地址
    	uint16_t width;//屏幕宽度
    	uint16_t height;//屏幕高度
    
        //用户变量
    	uint8_t Enable;//使能状态,初始化成功则为1,失败为0
    }GT911_StructDef;
    
    static GT911_StructDef gt911 = \
    {
        .Enable = 0,
    };//触摸屏结构体变量
    static void GT911_WriteReg(uint16_t _usRegAddr, uint8_t *_pRegBuf, uint8_t _ucLen);
    static void GT911_ReadReg(uint16_t _usRegAddr, uint8_t *_pRegBuf, uint8_t _ucLen);
    static void GT911_ReadMaxXY(uint16_t *_X, uint16_t *_Y);
    static void GT911_InitPin(void);
    static uint32_t GT911_ReadID(void);
    static int GT911_DetectID(void);
    static uint8_t GT911_PenInt(void);
    static void GT911_TouchPointRead(uint16_t *x,uint16_t *y);
    
    /****************************************************
    @function:写1个或连续的多个寄存器
    @param: _usRegAddr : 寄存器地址
            _pRegBuf : 寄存器数据缓冲区
            _ucLen : 数据长度
    @return:void
    @note:
    ****************************************************/
    static void GT911_WriteReg(uint16_t _usRegAddr, uint8_t *_pRegBuf, uint8_t _ucLen)
    {
    	uint8_t i;
    
        i2c_Start();					/* 总线开始信号 */
    
        i2c_SendByte(gt911.i2cAddress);	/* 发送设备地址+写信号 */
    	i2c_WaitAck();
    
        i2c_SendByte(_usRegAddr >> 8);	/* 地址高8位 */
    	i2c_WaitAck();
    
        i2c_SendByte(_usRegAddr);		/* 地址低8位 */
    	i2c_WaitAck();
    
    	for (i = 0; i < _ucLen; i++)
    	{
    	    i2c_SendByte(_pRegBuf[i]);		/* 寄存器数据 */
    		i2c_WaitAck();
    	}
    
        i2c_Stop();                   			/* 总线停止信号 */
    }
    
    /****************************************************
    @function:读1个或连续的多个寄存器
    @param: _usRegAddr : 寄存器地址
            _pRegBuf : 寄存器数据缓冲区
            _ucLen : 数据长度
    @return:void
    @note:
    ****************************************************/
    static void GT911_ReadReg(uint16_t _usRegAddr, uint8_t *_pRegBuf, uint8_t _ucLen)
    {
    	uint8_t i;
    	
    	{
    		i2c_Start();					/* 总线开始信号 */
    
    		i2c_SendByte(gt911.i2cAddress);	/* 发送设备地址+写信号 */
    		i2c_WaitAck();
    
    		i2c_SendByte(_usRegAddr >> 8);	/* 地址高8位 */
    		i2c_WaitAck();
    
    		i2c_SendByte(_usRegAddr);		/* 地址低8位 */
    		i2c_WaitAck();
    
    		i2c_Start();
    		i2c_SendByte(gt911.i2cAddress + 0x01);	/* 发送设备地址+读信号 */
    
    		i2c_WaitAck();
    	}
    	
    	for (i = 0; i < 30; i++);
    
    	for (i = 0; i < _ucLen - 1; i++)
    	{
    	    _pRegBuf[i] = i2c_ReadByte();	/* 读寄存器数据 */
    		i2c_Ack();
    	}
    
    	/* 最后一个数据 */
    	 _pRegBuf[i] = i2c_ReadByte();		/* 读寄存器数据 */
    	i2c_NAck();
    
        i2c_Stop();							/* 总线停止信号 */
    }
    
    /****************************************************
    @function:获得GT911触摸板的分辨率,X、Y最大值+1.
    @param: *_X : 水平分辨率
    		*_Y : 垂直分辨率
    @return:void
    @note:
    ****************************************************/
    static void GT911_ReadMaxXY(uint16_t *_X, uint16_t *_Y)
    {
    	uint8_t buf[4];
    	
    	GT911_ReadReg(0x8048, buf, 4);
    	
    	*_X = buf[0] + buf[1] * 256;
    	*_Y = buf[2] + buf[3] * 256;
    }
    
    /****************************************************
    @function:配置触摸芯片初始化引脚
    @param:void
    @return:void
    @note:
    ****************************************************/
    static void GT911_InitPin(void)
    {
    	GPIO_InitTypeDef gpio_init;
    
    	/* 第1步:打开GPIO时钟 */
    	TP_INT_GPIO_CLK_ENABLE();
        TP_RST_GPIO_CLK_ENABLE();
    	
    	/* 第2步:配置所有的按键GPIO为浮动输入模式 */
    	gpio_init.Mode = GPIO_MODE_INPUT;   		/* 设置输入 */
    	gpio_init.Pull = GPIO_NOPULL;                 /* 浮空:严禁上下拉 */
    	
    	gpio_init.Speed = GPIO_SPEED_FREQ_LOW;  /* GPIO速度等级 */
    	gpio_init.Pin = TP_INT_PIN;
    	HAL_GPIO_Init(TP_INT_GPIO_PORT, &gpio_init);
    
    	gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
    	gpio_init.Pull = GPIO_NOPULL;
    	gpio_init.Speed = GPIO_SPEED_FREQ_LOW;  /* GPIO速度等级 */
    	gpio_init.Pin = TP_RST_PIN;
    	HAL_GPIO_Init(TP_RST_GPIO_PORT, &gpio_init);
        HAL_GPIO_WritePin(TP_RST_GPIO_PORT,TP_RST_PIN,GPIO_PIN_SET);//使能触摸芯片
    }
    
    /****************************************************
    @function:获得GT911的芯片ID
    @param:void
    @return:void
    @note:16位版本
    ****************************************************/
    static uint32_t GT911_ReadID(void)
    {
    	uint8_t buf[4]; 
    
    	GT911_ReadReg(GT911_PRODUCT_ID_REG, buf, 4); 
    
    	return ((uint32_t)buf[3] << 24) + ((uint32_t)buf[2] << 16) + ((uint32_t)buf[1] <<8) + buf[0]; 
    }
    
    /****************************************************
    @function:设置GT911芯片屏幕的宽度
    @param:void
    @return:void
    @note:
    ****************************************************/
    uint16_t GT911_ScreenWidthGet(void)
    {
    	return gt911.width;
    }
    
    /****************************************************
    @function:设置GT911芯片屏幕的高度
    @param:void
    @return:void
    @note:
    ****************************************************/
    uint16_t GT911_ScreenHeigthGet(void)
    {
    	return gt911.height;
    }
    
    /****************************************************
    @function:触摸芯片识别
    @param:void
    @return:-1--识别失败,0--成功
    @note:
    ****************************************************/
    static int GT911_DetectID(void)
    {
    	uint8_t i = 0,address = 0;
    	uint32_t id = 0;
    	uint16_t MaxX = 0, MaxY = 0;
    
    	HAL_Delay(50);//50ms,等待GT811复位就绪,才能探测GT811芯片 ID
    	printf("开始识别触摸屏型号...\r\n");
    	address = 0x28;
    	for(i=0;i < 5;i++)//GT811电容触摸板和GT911的I2C地址相同,一般就 0x28 、 0xBA 两种,通过读取触摸IC的芯片ID来识别
    	{
    		address = (address == 0x28)?0xBA:0x28;
    		if(i2c_CheckDevice(address) == 0)
    		{
    			delay_us(500);
    			gt911.i2cAddress = address;
    			id = GT911_ReadID();
    			if(id == 0x00313139)
    			{
    				GT911_ReadMaxXY(&MaxX, &MaxY);//读取屏幕宽度、高度
    				gt911.width = MaxX;
    				gt911.height = MaxY;
    				if (MaxX == 480 && MaxY == 272){printf("检测到4.3寸电容触摸屏GT911(0x28) 480x272\r\n");}				
    				else{printf("检测到7.0寸电容触摸屏GT911(0x28) 800x480\r\n");}
    				return 0;
    			}
    			else
    			{
    				printf("检测到7.0寸电容触摸屏GT811(0x28) 800x480\r\n");
    				return -1;
    			}
    		}
    		HAL_Delay(10);
    	}
    
    	printf("未识别出显示模块\r\n");
    	return -1;
    }
    
    /****************************************************
    @function:配置触摸芯片初始化
    @param:void
    @return:-1--识别失败,0--成功
    @note:调用此函数初始化触摸芯片需初始化IIC引脚i2c_Init()
    ****************************************************/
    int GT911_Init(void)
    {
    	gt911.Enable = 0;
    	i2c_Init();//主函数中已经初始化
    	GT911_InitPin();
    	if(GT911_DetectID() < 0)return -1;
    	gt911.Enable = 1;
    	return 0;
    }
    
    /****************************************************
    @function:判断触摸按下
    @param:void
    @return:0表示无触笔按下,1表示有触笔按下
    @note:
    ****************************************************/
    static uint8_t GT911_PenInt(void)
    {
        //if(HAL_GPIO_ReadPin(TP_INT_GPIO_PORT,TP_INT_PIN) == GPIO_PIN_RESET)return 1;
    	if ((TP_INT_GPIO_PORT->IDR & TP_INT_PIN) == 0)return 1;
    	return 0;
    }
    
    /****************************************************
    @function:读取触摸点
    @param:x,y--点
    @return:-1--参数错误,0--成功
    @note:读取GT911触摸数据;读取全部8个点的数据,需要 720us左右
    ****************************************************/
    static void GT911_TouchPointRead(uint16_t *x,uint16_t *y)
    {
    	uint8_t clear_flag = 0;
    	uint8_t buf[48];//一个点8个字节,5点*8=40
    	GT911_ReadReg(GT911_READ_XY_REG, buf, 8);
    	GT911_WriteReg(GT911_READ_XY_REG, &clear_flag,	1);//读完坐标后必须写0清除
    
        //TouchpointFlag = buf[0];
        //Touchkeystate = buf[1];
        *x = ((uint16_t)buf[3] << 8) + buf[2];
        *y = ((uint16_t)buf[5] << 8) + buf[4];
        //p = ((uint16_t)buf[7] << 8) + buf[6];
    }
    
    /****************************************************
    @function:触摸芯片扫描
    @param:(x,y)--点
    @return:-1--无触摸,0--触摸
    @note:只读取一个点,建议10ms执行一次
    注意:此函数未处理重复触摸点,尤其是在两次点击相同像素点的情况下,但是概率不大
    ****************************************************/
    int GT911_Scan(uint16_t *x,uint16_t *y)
    {
        static uint16_t xlast = 0,ylast = 0;
        
        if(!gt911.Enable)return -1;
        if(!GT911_PenInt())return -1;//没有触摸
        GT911_TouchPointRead(x,y);
        if((*x == xlast) && (*y == ylast))return -1;//移除重复点
        xlast = *x;
        ylast = *y;
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333

    i2c_driver.h

    #ifndef _i2c_driver_H_
    #define _i2c_driver_H_
    #ifdef __cplusplus
    extern "C" {
    #endif
    #include "stdint.h"
    
    #define I2C_WR	0		/* 写控制bit */
    #define I2C_RD	1		/* 读控制bit */
    
    void i2c_Init(void);
    void i2c_Start(void);
    void i2c_Stop(void);
    void i2c_SendByte(uint8_t _ucByte);
    uint8_t i2c_ReadByte(void);
    uint8_t i2c_WaitAck(void);
    void i2c_Ack(void);
    void i2c_NAck(void);
    uint8_t i2c_CheckDevice(uint8_t _Address);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    i2c_driver.c

    /**********************************************************************
    *file:GPIO引脚模拟IIC文件
    *author:残梦
    *versions:V1.1
    *date:2023.07.30
    *note:
    **********************************************************************/
    #include "i2c_driver.h"
    #include "delay_driver.h"
    #include "gpio.h"
    
    //宏定义
    /* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
    #define I2C_SCL_GPIO	GPIOB			/* 连接到SCL时钟线的GPIO */
    #define I2C_SDA_GPIO	GPIOB			/* 连接到SDA数据线的GPIO */
    
    #define I2C_SCL_PIN		GPIO_PIN_6			/* 连接到SCL时钟线的GPIO */
    #define I2C_SDA_PIN		GPIO_PIN_7			/* 连接到SDA数据线的GPIO */
    
    #define ALL_I2C_GPIO_CLK_ENABLE()	__HAL_RCC_GPIOB_CLK_ENABLE()
    
    
    /* 定义读写SCL和SDA的宏 */
    #define I2C_SCL_1()  I2C_SCL_GPIO->BSRR = I2C_SCL_PIN				    /* SCL = 1 */
    #define I2C_SCL_0()  I2C_SCL_GPIO->BSRR = ((uint32_t)I2C_SCL_PIN << 16U)/* SCL = 0 */
    
    #define I2C_SDA_1()  I2C_SDA_GPIO->BSRR = I2C_SDA_PIN				    /* SDA = 1 */
    #define I2C_SDA_0()  I2C_SDA_GPIO->BSRR = ((uint32_t)I2C_SDA_PIN << 16U)/* SDA = 0 */
    
    #define I2C_SDA_READ()  ((I2C_SDA_GPIO->IDR & I2C_SDA_PIN) != 0)	/* 读SDA口线状态 */
    #define I2C_SCL_READ()  ((I2C_SCL_GPIO->IDR & I2C_SCL_PIN) != 0)	/* 读SCL口线状态 */
    
    static void i2c_Delay(void);
    
    /****************************************************
    @function:配置I2C总线的GPIO,采用模拟IO的方式实现
    @param:void
    @return:void
    @note:
    ****************************************************/
    void i2c_Init(void)
    {
    	GPIO_InitTypeDef gpio_init;
    
    	/* 第1步:打开GPIO时钟 */
    	ALL_I2C_GPIO_CLK_ENABLE();
    	
    	gpio_init.Mode = GPIO_MODE_OUTPUT_OD;	/* 设置开漏输出 */
    	gpio_init.Pull = GPIO_NOPULL;			/* 上下拉电阻不使能 */
    	gpio_init.Speed = GPIO_SPEED_FREQ_LOW;	// GPIO_SPEED_FREQ_HIGH;  /* GPIO速度等级 */
    	
    	gpio_init.Pin = I2C_SCL_PIN;	
    	HAL_GPIO_Init(I2C_SCL_GPIO, &gpio_init);	
    	
    	gpio_init.Pin = I2C_SDA_PIN;	
    	HAL_GPIO_Init(I2C_SDA_GPIO, &gpio_init);	
    
    	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
    	i2c_Stop();
    }
    
    /****************************************************
    @function:I2C总线位延迟,最快400KHz
    @param:void
    @return:void
    @note:
    ****************************************************/
    static void i2c_Delay(void)
    {
    	delay_us(2);
    }
    
    /****************************************************
    @function:发起I2C总线启动信号
    @param:void
    @return:void
    @note:
    ****************************************************/
    void i2c_Start(void)
    {
    	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
    	I2C_SDA_1();
    	I2C_SCL_1();
    	i2c_Delay();
    	I2C_SDA_0();
    	i2c_Delay();
    	
    	I2C_SCL_0();
    	i2c_Delay();
    }
    
    /****************************************************
    @function:发起I2C总线停止信号
    @param:void
    @return:void
    @note:
    ****************************************************/
    void i2c_Stop(void)
    {
    	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
    	I2C_SDA_0();
    	i2c_Delay();
    	I2C_SCL_1();
    	i2c_Delay();
    	I2C_SDA_1();
    	i2c_Delay();
    }
    
    /****************************************************
    @function:向I2C总线设备发送8bit数据
    @param:_ucByte : 等待发送的字节
    @return:void
    @note:
    ****************************************************/
    void i2c_SendByte(uint8_t _ucByte)
    {
    	uint8_t i;
    
    	/* 先发送字节的高位bit7 */
    	for (i = 0; i < 8; i++)
    	{
    		if (_ucByte & 0x80)
    		{
    			I2C_SDA_1();
    		}
    		else
    		{
    			I2C_SDA_0();
    		}
    		i2c_Delay();
    		I2C_SCL_1();
    		i2c_Delay();
    		I2C_SCL_0();
    		I2C_SCL_0();	/* 2019-03-14 针对GT811电容触摸,添加一行,相当于延迟几十ns */
    		if (i == 7)
    		{
    			 I2C_SDA_1(); // 释放总线
    		}
    		_ucByte <<= 1;	/* 左移一个bit */	
    	}
    }
    
    /****************************************************
    @function:从I2C总线设备读取8bit数据
    @param:void
    @return:读到的数据
    @note:
    ****************************************************/
    uint8_t i2c_ReadByte(void)
    {
    	uint8_t i;
    	uint8_t value;
    
    	/* 读到第1个bit为数据的bit7 */
    	value = 0;
    	for (i = 0; i < 8; i++)
    	{
    		value <<= 1;
    		I2C_SCL_1();
    		i2c_Delay();
    		if (I2C_SDA_READ())
    		{
    			value++;
    		}
    		I2C_SCL_0();
    		i2c_Delay();
    	}
    	return value;
    }
    
    /****************************************************
    @function:产生一个时钟,并读取器件的ACK应答信号
    @param:void
    @return:返回0表示正确应答,1表示无器件响应
    @note:
    ****************************************************/
    uint8_t i2c_WaitAck(void)
    {
    	uint8_t re;
    
    	I2C_SDA_1();	/* CPU释放SDA总线 */
    	i2c_Delay();
    	I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
    	i2c_Delay();
    	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
    	{
    		re = 1;
    	}
    	else
    	{
    		re = 0;
    	}
    	I2C_SCL_0();
    	i2c_Delay();
    	return re;
    }
    
    /****************************************************
    @function:产生一个ACK信号
    @param:void
    @return:void
    @note:
    ****************************************************/
    void i2c_Ack(void)
    {
    	I2C_SDA_0();	/* CPU驱动SDA = 0 */
    	i2c_Delay();
    	I2C_SCL_1();	/* CPU产生1个时钟 */
    	i2c_Delay();
    	I2C_SCL_0();
    	i2c_Delay();
    	I2C_SDA_1();	/* CPU释放SDA总线 */
    	
    	i2c_Delay();
    }
    
    /****************************************************
    @function:产生1个NACK信号
    @param:void
    @return:void
    @note:
    ****************************************************/
    void i2c_NAck(void)
    {
    	I2C_SDA_1();	/* CPU驱动SDA = 1 */
    	i2c_Delay();
    	I2C_SCL_1();	/* CPU产生1个时钟 */
    	i2c_Delay();
    	I2C_SCL_0();
    	i2c_Delay();
    }
    
    /****************************************************
    @function:检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
    @param:_Address:设备的I2C总线地址
    @return:返回值 0 表示正确, 返回1表示未探测到
    @note:
    ****************************************************/
    uint8_t i2c_CheckDevice(uint8_t _Address)
    {
    	uint8_t ucAck;
    
    	if (I2C_SDA_READ() && I2C_SCL_READ())
    	{
    		i2c_Start();		/* 发送启动信号 */
    
    		/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
    		i2c_SendByte(_Address | I2C_WR);
    		ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */
    
    		i2c_Stop();			/* 发送停止信号 */
    
    		return ucAck;
    	}
    	return 1;	/* I2C总线异常 */
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258

    delay_driver.h

    #ifndef _delay_driver_H_
    #define _delay_driver_H_
    #ifdef __cplusplus
    extern "C" {
    #endif
    #include "stdlib.h"
    
    void delay_Init(void);
    void delay_us(unsigned int us);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    delay_driver.h

    /**********************************************************************
    *file:微秒级精确延时
    *author:残梦
    *versions:V1.0
    *date:2023.10.17
    *note:基础时基是0.1us
    1、修改dDelayTIM和dDelayTIM_Handle
    配置定时器参数参考delay_Init()
    **********************************************************************/
    #include "delay_driver.h"
    #include "tim.h"
    
    #define dDelayTIM TIM24
    #define dDelayTIM_Handle htim24
    #define dTIM_Bit (32) //定时器位数;32位定时器时0xFFFFFFFF,16位定时器0xFFFF
    #define dTIM_Period_MAX ((uint32_t )((dTIM_Bit == 16)?0xFFFF:0xFFFFFFFF))
    //TIM_HandleTypeDef htim24;//CubeMx配置了,就不重复定义
    
    
    /******************************
    @function:初始化延时
    @param:void
    @return:void
    @remark:CubeMx配置了,就不重复配置
    ******************************/
    void delay_Init(void)
    {
        TIM_ClockConfigTypeDef sClockSourceConfig = {0};
        TIM_MasterConfigTypeDef sMasterConfig = {0};
    
        dDelayTIM_Handle.Instance = dDelayTIM;
        dDelayTIM_Handle.Init.Prescaler = 275-1;
        dDelayTIM_Handle.Init.CounterMode = TIM_COUNTERMODE_UP;
        dDelayTIM_Handle.Init.Period = dTIM_Period_MAX;
        dDelayTIM_Handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        dDelayTIM_Handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
        if (HAL_TIM_Base_Init(&dDelayTIM_Handle) != HAL_OK)
        {
            Error_Handler();
        }
        sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
        if (HAL_TIM_ConfigClockSource(&dDelayTIM_Handle, &sClockSourceConfig) != HAL_OK)
        {
            Error_Handler();
        }
        sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
        sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
        if (HAL_TIMEx_MasterConfigSynchronization(&dDelayTIM_Handle, &sMasterConfig) != HAL_OK)
        {
            Error_Handler();
        }
    }
    
    /******************************
    @function:us延时
    @param:us--待延时的时间
    @return:void
    @remark:
    ******************************/
    void delay_us(uint32_t us)
    {
      if(!us){return;}
      us = (us > (dTIM_Period_MAX))?dTIM_Period_MAX:us;
      //us *= 1;//基础是1us
      dDelayTIM_Handle.Instance->CNT = 0;
      HAL_TIM_Base_Start(&dDelayTIM_Handle);
      while(dDelayTIM_Handle.Instance->CNT < us);
      HAL_TIM_Base_Stop(&dDelayTIM_Handle);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    该屏幕LCD驱动说明:

    在使用DMA2D刷新SDRAM数据时应根据自己内存区属性是否添加CACHE更新

    SCB_CleanInvalidateDCache();//注意,此处需要让Cache内容更新
    
    • 1

    如H7开启MPU和Cache后使用0x24000000做数显,配置为透写或回写,不添加此句更新cache就会出现随机花屏
    填充函数和复制颜色块函数

    /****************************************************
    @function:通过DMA2D对于指定区域进行颜色填充(固定颜色)
    @param:
        LayerIndex    图层
        pDst          颜色数据目的地址
        xSize         要复制区域的X轴大小,即每行像素数
        ySize         要复制区域的Y轴大小,即行数
        OffLine       前景层图像的行偏移
        ColorIndex    要填充的颜色值
    @return:void
    @note:
    ****************************************************/
    static void lcd_base_DMA2D_FillBuffer(uint32_t LayerIndex, void * pDst, uint32_t xSize, uint32_t ySize, uint32_t OffLine, uint32_t ColorIndex) 
    {
    	uint32_t PixelFormat;
    
    	PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
    
    	/* 颜色填充 */
    	DMA2D->CR      = 0x00030000UL | (1 << 9);        
    	DMA2D->OCOLR   = ColorIndex;                     
    
    	/* 设置填充的颜色目的地址 */
    	DMA2D->OMAR    = (uint32_t)pDst;                      
    
    	/* 目的行偏移地址 */
    	DMA2D->OOR     = OffLine;                        
    
    	/* 设置颜色格式 */
    	DMA2D->OPFCCR  = PixelFormat;                    
    
    	/* 设置填充大小 */
    	DMA2D->NLR     = (uint32_t)(xSize << 16) | (uint16_t)ySize;
    
          SCB_CleanInvalidateDCache();//注意,此处需要让Cache内容更新
    	DMA2D->CR     |= DMA2D_CR_START; 
    
    	/* 等待DMA2D传输完成 */
    	while (DMA2D->CR & DMA2D_CR_START) 
    	{
    	}
    }
    
    /**********************************************************************************************************
    *	函 数 名: _DMA2D_Copy
    *	功能说明: 通过DMA2D从前景层复制指定区域的颜色数据到目标区域
    *	形    参: pSrc          颜色数据源地址
    *             pDst          颜色数据目的地址
    *             xSize         目的区域的X轴大小,即每行像素数
    *             ySize         目的区域的Y轴大小,即行数
    *             OffLineSrc    前景层图像的行偏移
    *             OffLineDst    输出的行偏移
    *             PixelFormat   目标区颜色格式
    *	返 回 值: 无
    **********************************************************************************************************/
    static void lcd_base_DMA2D_Copy(void * pSrc, void * pDst, uint32_t xSize, uint32_t ySize, uint32_t OffLineSrc, uint32_t OffLineDst, uint32_t PixelFormat) 
    {
    
    	/* DMA2D采用存储器到存储器模式, 这种模式是前景层作为DMA2D输入 */  
    	DMA2D->CR      = 0x00000000UL | (1 << 9);
    	DMA2D->FGMAR   = (uint32_t)pSrc;
    	DMA2D->OMAR    = (uint32_t)pDst;
    	DMA2D->FGOR    = OffLineSrc;
    	DMA2D->OOR     = OffLineDst;
    	
    	/* 前景层和输出区域都采用的RGB565颜色格式 */
    	DMA2D->FGPFCCR = LTDC_PIXEL_FORMAT_RGB565;
    	DMA2D->OPFCCR  = LTDC_PIXEL_FORMAT_RGB565;
    	
    	DMA2D->NLR     = (uint32_t)(xSize << 16) | (uint16_t)ySize;
          
          SCB_CleanInvalidateDCache();//注意,此处需要让Cache内容更新
    	/* 启动传输 */
    	DMA2D->CR   |= DMA2D_CR_START;   
    
    	/* 等待DMA2D传输完成 */
    	while (DMA2D->CR & DMA2D_CR_START) {} 
    }
    
    /****************************************************
    @function:通过DMA2D对于指定区域进行颜色复制(图像复制)
    @param:
        LayerIndex    图层
        pDst          颜色数据目的地址
        xSize         要复制区域的X轴大小,即每行像素数
        ySize         要复制区域的Y轴大小,即行数
        OffLine       前景层图像的行偏移
        ColorIndex    要填充的颜色值
    @return:void
    @note:只支持横屏
    ****************************************************/
    void lcd_base_DMA2D_CopyBuffer(uint16_t x,uint16_t y,uint32_t xSize, uint32_t ySize,void * color)
    {
        if((color == NULL) || (xSize == 0) || (ySize == 0))return;
    
    	lcd_base_DMA2D_Copy((uint32_t *)color,                                        /* 位图地址 */
    			    (uint32_t *)(s_CurrentFrameBuffer + g_LcdWidth*y*2 + x*2),       /* 显示起始地址(328, 20) */  
    			    xSize,                                                          /* 位图长 */
    			    ySize,                                                          /* 位图高 */
    			    0,                                                            /* 位图行偏移 */
    			    g_LcdWidth-xSize,                                               /* 目标区行偏移 */
    			    LTDC_PIXEL_FORMAT_RGB565);                                    /* 目标区颜色格式 */
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    完整工程:(最新)

    链接:https://pan.baidu.com/s/1I64wD4Ft7PBI0cIogGp45A
    提取码:qpxx

  • 相关阅读:
    Reflect 对象的创建目的
    胡哥面试视频手录
    Qt之QChar类的数据转换
    malloc 是怎么回事
    Android 转场动画源码剖析
    《非暴力沟通》就是分享式沟通
    C# 关键字与基本数据类型
    【Monorepo实战】pnpm+turbo+vitepress构建公共组件库文档系统
    快速掌握正则表达式
    2023年9月8日
  • 原文地址:https://blog.csdn.net/qq_36561846/article/details/133888554