首先很感谢正点原子的教程,对于本人这两年来学习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寸电容屏的照片:
本人也觉得原子的为了兼容性导致代码量太大,得不偿失
我也一直想说这个问题,一直没有机会,没有想到有和我一样感想的人
虽然我不是菜鸟,但也不是老手,当年学习32的时候也是用原子的程序,尤其看lcd的时候,那庞大的代码量确实把我吓了一跳,我看不懂。
无奈,我只有一点点的注释,一点点删除,一次次的下载验证,,,直到lcd不能驱动了,Ctrl+Z在回头删除别的,,直到删除的代码再也不能删除,,再一句句读;
当读懂了,,复杂的我也就看懂了,也会用了,
我也尝试看过教程,但是教程我发现还不如看数据手册来的快,教程几乎每一章都有这一句:“下面的代码比较简单,我就多不说了”,,,其实下面的代码对于初学者:根本不简单。
其实原子不必要给lcd只提供一个代码,,有最简的,,有考虑兼容性的,,还有各种方法的,,,,还可以额外的一个文件夹提供其他的lcd的初始化代码的;
不仅仅是说lcd,,每一个程序都应该如此
原子的分类信息做的不好,不仅仅是代码,,得不偿失,,,,网站也是,有用的东西搜索不到,好不容易找到了,可能是做完项目很久了
说这些只是为了好的东西更好
当然,如果买了某块确定的屏幕之后,lcd控制器的id就固定了,你要是嫌弃if else多,代码量大,就把其它所有的用不着的id的代码注释掉。
我就是这样,省了10k的代码量,初始化速度也变快了一些。
注释不嫌多(只要是正确的),不管你是给别人看还是给自己看(一段时间后自己也有可能忘记自己以前写代码的思路了),我觉得蛮好的。
一周热门 更多>