首先很感谢正点原子的教程,对于本人这两年来学习STM32提供了大量帮助。
本人手里有块STM32F429 Discovery板子,因为某种原因需要使用8080接口的LCD,而该块开发板自带的LCD接口采用LTDC驱动,所以就在原子哥这里买了一块4.3寸电容触摸屏,淘宝连接:http://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-959568043.14.CzhJLZ&id=39486536522
很快就收到了LCD模块,并搭建好了硬件环境,开始着手软件驱动。
要写驱动自然少不了下载原子哥的官方教程及源码,但是当我打开正点原子官方的LCD示例代码时还是吃了一惊。虽然一年前就看过正点原子的MiniSTM32的驱动代码,但是此次下载的F407 LCD代码还是让我有不少需要吐槽的地方,主要有以下几点:
1. 代码实在太长;
2. 太多的判断lcd id导致代码可读性差;
3. 注释布局杂乱,且有些注释多余。
对于以上3点总结成一句,我认为就是可阅读性太差。一般来说,能去买开发板学习的人,肯定是对STM32不太了解的人,或者说是初学者。而面对初学者就应该提供最简单的案例及可读性强的代码,但是我想对于一个不太熟悉LCD驱动的初学者来说,直接看正点原子的LCD驱动着实有些吃力。首先巨大的篇幅就让某些人失去了信心,其次过多的if条件判断有给初学者思维上造成了许多干扰。其实我也明白正点原子之所以这样做是为了能用一份代码兼容所有版本的LCD,增强软件的可移植性,也便于出货管理。但是随着LCD型号的不断增多,lcd.c文件的代码也越来越多,单单一个LCD_Init()函数就占据了大量的篇幅。
针对于以上问题,本人提供了一个方案,即能增强代码的可读性,也能增强代码的可移植性。如下:
lcd.c文件
[mw_shl_code=c,true]typedef struct _LCD_DRV{
uint32_t (*checkid)(void);
void (*init)(void);
void (*setpixel)(uint16_t color, uint16_t x, uint16_t y);
void (*fillrect)(uint16_t color, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void (*bitmap)(uint16_t color[], uint16_t x0, uint16_t y0, uint16_t width, uint16_t height);
void (*clear)(uint16_t color);
int (*ioctrl)(uint32_t cmd, uint32_t param);
uint16_t (*getpixel)(uint16_t x, uint16_t y);
}lcd_drv_t;
const lcd_drv_t* lcd_module[] = {
&nt35510_module,
&st7789v_module,
/*any other lcd module*/
};
const lcd_drv_t *lcd_drv = 0;
void LCD_GPIO_Cfg(void)
{
/*由于FMC的GPIO已由CubeMx自动配置,所以这里只设置RESET引脚*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void LCD_FMC_Cfg(void)
{
/*
由于STM32 CubeMx已自动生成相关FMC配置的代码,
所以这里就无需额外设置
*/
}
void LCD_Reset(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
delay_ms(100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
delay_ms(100);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
delay_ms(100);
}
uint8_t LCD_CheckID(void)
{
uint32_t id;
int i;
for(i = 0; i < (sizeof(lcd_module) / sizeof(lcd_module[0])); i++)
{
id = lcd_module
->checkid();
if(0 != id)
{
printf("Found ID: %x
", id);
lcd_drv = lcd_module;
return 1;
}
}
return 0;
}
void LCD_Init(void)
{
LCD_GPIO_Cfg();
LCD_FMC_Cfg();
LCD_Reset();
if(LCD_CheckID())
{
lcd_drv->init();
}
}
void LCD_DrawPixel(uint16_t color, uint16_t x, uint16_t y)
{
if(lcd_drv)
lcd_drv->setpixel(color, x, y);
}
void LCD_FillRect(uint16_t color, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
if(lcd_drv)
lcd_drv->fillrect(color, x0, y0, x1, y1);
}
void LCD_DrawBitmap(uint16_t color[], uint16_t x0, uint16_t y0, uint16_t width, uint16_t height)
{
if(lcd_drv)
lcd_drv->bitmap(color, x0, y0, width, height);
}
void LCD_Clear(uint16_t color)
{
if(lcd_drv)
lcd_drv->clear(color);
}
[/mw_shl_code]
具体LCD驱动模块文件nt35510.c
[mw_shl_code=c,true]void NT35510_WriteReg(uint16_t LCD_Reg, uint16_t LCD_RegValue)
{
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}
uint16_t NT35510_ReadReg(uint16_t LCD_Reg)
{
LCD_WR_REG(LCD_Reg); //写入要读的寄存器序号
delay_us(5);
return LCD_RD_DATA(); //返回读到的值
}
//开始写GRAM
void NT35510_WriteRAM_Prepare(void)
{
LCD_WR_REG(0X2C00);
}
void NT35510_DisplayOn(void)
{
LCD_WR_REG(0X2900); //开启显示
}
void NT35510_DisplayOff(void)
{
LCD_WR_REG(0X2800); //关闭显示
}
void NT35510_SetWindow(uint16_t x0,uint16_t y0,uint16_t x1,uint16_t y1)
{
NT35510_WriteReg(0x2A00, x0 >> 8);
NT35510_WriteReg(0x2A01, x0 & 0XFF);
NT35510_WriteReg(0x2A02, x1 >> 8);
NT35510_WriteReg(0x2A03, x1 & 0XFF);
NT35510_WriteReg(0x2B00, y0 >> 8);
NT35510_WriteReg(0x2B01, y0 & 0XFF);
NT35510_WriteReg(0x2B02, y1 >> 8);
NT35510_WriteReg(0x2B03, y1 & 0XFF);
}
void NT35510_DrawPixel(uint16_t color, uint16_t x, uint16_t y)
{
NT35510_SetWindow(x, y, x, y); //设置光标位置
NT35510_WriteRAM_Prepare(); //开始写入GRAM
LCD_WR_DATA(color);
}
//清屏函数
void NT35510_Clear(uint16_t color)
{
uint32_t i;
NT35510_SetWindow(0, 0, Width - 1, Height - 1);
NT35510_WriteRAM_Prepare();
for(i = 0; i < Width * Height; i++)
{
LCD_WR_DATA(color);
}
}
uint32_t NT35510_CheckID(void)
{
uint8_t id1, id2, id3;
id1 = NT35510_ReadReg(0XDA00); //should be 0x00
id2 = NT35510_ReadReg(0XDB00); //should be 0x80
id3 = NT35510_ReadReg(0XDC00); //should be 0x00
printf(" Read ID:%x, %x, %x
", id1, id2, id3);
if((id1 == 0x00) && (id2 == 0x80) && (id3 == 0x00))
return 0x35510;
else
return 0;
}
void NT35510_Init(void)
{
delay_ms(50);
NT35510_WriteReg(0x0000,0x0001);
delay_ms(50);
NT35510_WriteReg(0xF000,0x55);
NT35510_WriteReg(0xF001,0xAA);
NT35510_WriteReg(0xF002,0x52);
NT35510_WriteReg(0xF003,0x08);
NT35510_WriteReg(0xF004,0x01);
......
}
void NT35510_FillRect(uint16_t color, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
uint32_t i;
uint16_t width, height;
width = x1-x0+1;
height = y1-y0+1;
NT35510_SetWindow(x0, y0, x1, y1);
NT35510_WriteRAM_Prepare();
for(i = 0; i < width * height; i++)
{
LCD_WR_DATA(color);
}
}
//位图显示函数,位图中每个像素大小为16bit,高字节在前低字节在后
void NT35510_DrawBitmap(uint16_t color[], uint16_t x0, uint16_t y0, uint16_t width, uint16_t height)
{
uint32_t i;
uint16_t x1, y1;
x1 = x0 + width - 1;
y1 = y0 + height - 1;
NT35510_SetWindow(x0, y0, x1, y1);
NT35510_WriteRAM_Prepare();
for(i = 0; i < width * height; i++)
{
LCD_WR_DATA(color);
}
}
int NT35510_IOCtrl(uint32_t cmd, uint32_t param)
{
switch(cmd)
{
case LCD_CMD_SLEEP_IN:
break;
case LCD_CMD_SLEEP_OUT:
break;
case LCD_CMD_SET_DIR:
break;
default:
break;
}
return 0;
}
const lcd_drv_t nt35510_module = {
NT35510_CheckID,
NT35510_Init,
NT35510_DrawPixel,
NT35510_FillRect,
NT35510_DrawBitmap,
NT35510_Clear,
NT35510_IOCtrl
};
[/mw_shl_code]
这种将LCD驱动接口与具体LCM模块隔离的方法,既能便于日后的维护,也方便用户阅读代码。如果日后需要扩展LCD的功能,可以直接添加到LCD_IOCtrl()中,整个代码架构清晰,结构简单,更易于移植。至于如何自动判断LCD ID的功能,请参阅LCD_CheckID()函数。上层统一调用lcd.c中的函数而不用关心具体是那个模块。当然你还可以对该代码进行进一步的优化。
为了快速搭建软件工程,本人使用STM32CubeMX工具生成代码模板,并编写了上述两个文件。要注意的是,如果你是使用的正点原子的模板或是自己搭建的模板,则需要实现LCD_GPIO_Cfg()及LCD_FMC_Cfg()两个函数,由于STM32CubeMX在配置GPIO及FMC时,已经自动生成了相应的代码,所以这里为空。另外,我的硬件上时将LCD_RS接在了FMC_A0上的,所以LCD_DATA的地址为0x6C000002,请针对于自己的硬件自行修改。工程源码请见附件。
当然以上也只是我个人的意见,可能作为开发板厂商,你们需要考虑的更多。但是我还是认为,你们的客户群主要还是初学者,对于初学者来说,最简单的就是最好的(当然我的方案也不是最简单的)。如果正点原子日后生意蒸蒸日上,支持的LCD越来越多,岂不是lcd.c文件就要变成几万行的代码了。
下面上传两张STM32F429 Discovery板子与正点原子4.3寸电容屏的照片:
这是教程 不是项目 兼容再好 学习者看着累有什么用呢 ,
说的不错,代码就应该有树的概念层的概念这样移植才不吃神,不费时间。混沌在一起实在不好
非常赞同,这句话“没有HAL库那种至上而下风格,树的概念没有”,这点官方代码和微雪代码写的比较好
一周热门 更多>