由于在学习过程中,难免会有错漏,直接指出即可
使用STM32CubeMX+Keil5控制TFTLCD显示,使用的是STM32F103ZET6
读原理图:
TFTLCD原理图:
使用分辨率为320*480,驱动IC为NT35310,16位并口驱动的TFTLCD显示器,原理图如下(标注是鼠标写的,确实字有点丑,大致看一下就行),按照我的理解,如果我只想要显示屏亮,也不需要进行手写操作,电阻触摸屏控制器暂时可以不控制
但是可惜了这个是独立出来的原理图,还得在主图里找相同的引脚,但是好消息是,引脚除了名字不太一样其他都差不太多,除了比较烦还是比较好找的
LCD原理图:
主图里的LCD对外接口,由于T开头的接的基本上都是电阻触摸屏控制器的,所以暂时不管,以后研究怎么写的时候再说,主要看FSMC开头的,也就是在独立出来的图上LCD开头的
STM32F103ZET6对应引脚:
然后对着名字在STM32F103ZET6上面找对应的引脚
LCD_CS:LCD片选信号——PG12
RS:命令/数据标志(0:命令,1:数据)——PG0
WR:LCD写信号——PD5
RD:LCD读信号——PD4
RST:硬复位LCD信号
DB1-17:16位双向数据线(额,这个编号很奇怪,既不是从0开始也没有9,我暂时没发现为啥(摊手),但是旁边那个连接的编号又是0到15,只是名字问题也没有啥影响)——PD14,PD15,PD0,PD1,PE7-PE15,PD8-PD10
BL:背光控制信号——PB0
STM32CubeMX:
找到了所需要的对应引脚之后,就可以打开STM32CubeMX
时钟设置:
SYS设置:
再次强调,只要往板子上烧录就不要选No Debug,不然有复位按键还好,没有的话你就要自己拉线出来强制复位了,这些我之前写过
引脚设置:
之前的引脚设置:
两个LED和三个按键KEY的设置在上一篇具体说明过,LED和KEY的原理图啥的上一篇也有,由于不知道需不需要用,秉持着放着也没事的,有用顺手用上的态度,所以就接着上一篇的继续加
USART串口设置:
开一个串口——PA9,PA10,用于打印那个LCD的ID,设置为异步然后开个中断,按道理来说不开,然后把相关代码全删了也行,不过就算了,反正以后也得用重定向和串口设置,先用着再说
LCD背光引脚设置:
定义LCD背光引脚,根据原理图来说是PB0引脚
FSMC设置:
设置FSMC,根据原理图可得选择Bank1,NE4,存储类型是LCD,RS脚为A10
要设置数据,需要查找一下所使用LCD屏幕的数据,我使用的是NT35310(3.5寸的,不是4.3寸的NT35510,当然也没什么区别),为什么又是全英文的手册(来自英语废物的无语),我裂开了,我找啊找,看的我脑子疼,应该是这个
额,然后按道理来说要算数,我用的是STM32F103ZET6,是72MHz,所以一个周期是13.89ns,但是我研究了一下好像填啥数没啥影响,默认的好像就行,所以查资料暂时没用,先放着吧,暂时就这么设置吧
应该是设置完了,然后,选择编辑器然后导出,打开代码,这个过程就不仔细说明
Keil5:
首先这一次不是在主函数中写入全部所需要的代码,是需要一个LCD屏幕的基础的驱动程序,由正点原子lcd驱动修改而来,可以自行从正点原子网站上下载后作修改,包含了font.h(字库)、lcd.c和lcd.h,由于本人水平有限,可能会有错误,直接指出即可
导入生成工程外代码:
先不管代码如何,无论怎么样,添加工程外的代码,都需要导入,就算这次不需要,以后也需要,所以先进行说明:
在STM32CubeMX生成代码时,生成了文件夹,需要把工程外你想使用的代码放入工程目录的文件夹内,然后在Keil5工程中添加,双击Application/User,选择想加入工程的文件导入,这边需要选择的就是font.h、lcd.c和lcd.h(截图意会一下就好)
然后,打开C/C++选项卡,加入头文件路径,先点击Include Paths那一行最右边的省略号
新建路径后添加文件夹
这样子自己写的也能被调用啦!
重定向printf:
然后,一个很重要的问题,想要用printf,现在是用不了的!printf的输出终端是串口,所以需要对printf函数重定向一下,也就是在usart.c里重写,注意,头文件需要添加stdio.h
需要注意的事情,自己添加到代码最好写在类似于下面的注释之间,以免在STM32CubeMX重新导出代码时,代码被覆盖导致的代码丢失
- /* USER CODE BEGIN 0 */
- //添加代码在类似的注释之间,以免重新导出代码时被覆盖,导致代码丢失
- /* USER CODE END 0 */
复制代码
添加头文件:
usart.c添加头文件
- /* USER CODE BEGIN 0 */
- #include "stdio.h"
- /* USER CODE END 0 */
复制代码
添加printf重定向:
usart.c内对printf的重定向
- /* USER CODE BEGIN 1 */
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
- return ch;
- }
- /* USER CODE END 1 */
复制代码
代码更改:
修改过的代码在自行学习时添加过额外的标注,可能有错误,为了便于自己学习理解,更换了部分代码顺序(对于没有影响的代码更改,不进行额外说明),下面对修改了的代码进行说明
全局更改:
即这三个文件中所有与下列有关的全部修改,可以直接使用keil5中的替换全部修改,后续的单个文件修改说明将不再进行关于下列修改的说明
keil5中替换的方式如下:
打开find:
打开replace:被替换的填到第一行,所需要的填到第二行
例如:想要把u8更改为uint8_t,即第一行填u8,第二行填uint8_t,单个更改点击replace,单文件中全部更改点击replace all
需要全部更改的内容:
lcd.c和lcd.h中
1、u8/u16/u32全部更改为uint8_t/uint16_t/uint32_t
2、vu16全部更改为__IO uint16_t
关于uint8_t之类的定义,都在stdint.h里,其实可以直接调出来看一下,下面放一小段
- /* exact-width signed integer types */
- typedef signed char int8_t;
- typedef signed short int int16_t;
- typedef signed int int32_t;
- typedef signed __INT64 int64_t;
-
- /* exact-width unsigned integer types */
- typedef unsigned char uint8_t;
- typedef unsigned short int uint16_t;
- typedef unsigned int uint32_t;
- typedef unsigned __INT64 uint64_t;
复制代码
lcd.c中
1、delay_us全部更改为opt_delay,后面括号内时间不需修改
2、delay_ms全部更改为HAL_Delay,后面括号内时间不需修改
font.h:
没有进行修改,这是字库,是ASCII字符集,包含12*12(asc2_1206[95][12])、16*16(asc2_1608[95][16])、24*24(asc2_2412[95][36])、32*32(asc2_3216[95][128])四个字符集
lcd.h:
删除LCD背光的端口宏定义,即下图中的49行代码
lcd.c:
1、删除掉#include "delay.h",下图第5行,不需要delay,ms级延迟即使用HAL库中时延即可,us级别的延时使用void opt_delay(uint8_t i)替代,void opt_delay(uint8_t i)定义在第二幅图和代码段,相关修改在全局修改中提及
- //当mdk -O1时间优化时需要设置
- //延时i
- void opt_delay(uint8_t i)
- {
- while (i--);
- }
复制代码
2、删除void HAL_SRAM_MspInit(SRAM_HandleTypeDef *hsram)全部内容,即下图第590行到第618行内容,该内容在STM32CubeMX定义好后,导出代码时会导出到usart.c和fsmc.c中,不需要再定义,如不删除会出现重复定义的报错
3、删除void LCD_Init(void)中的引脚定义及其相关代码,即下图第625行到第663行,delay前,还是因为在STM32CubeMX里已经定义过了,和前一个删除的理由相同,引脚的定义在usart.c和fsmc.c中,不需要重复定义了
4、更改LCD_LED = 1;为HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);即将第一幅图的2087行更换为第二幅图的1927行,因为lcd.h删除了相应背光引脚的宏定义,改变点亮背光的代码,背光引脚名称在STM32CubeMX中修改,更改后代码如图后代码段所示
- HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET); //点亮背光
复制代码
5、添加代码else if (size == 32)temp = asc2_3216[num][t]; //调用3216字体,添加位置如第一幅图的第2263和第2264行之间,我一直用不了32号字体,找了各种原因,最后才发现问题在这,更改后如第二幅图所示
- else if (size == 32)temp = asc2_3216[num][t]; //调用3216字体
复制代码
全局修改不再说明
代码说明:
下面对我觉得比较重要的部分进行代码的说明
font.h:
只有ASCII字符集,没有中文,可以显示的字符如下,总共有4个字符集:
- //常用ASCII表
- //偏移量32
- //ASCII字符集: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
- //PC2LCD2002取模方式设置:阴码+逐列式+顺向+C51格式
复制代码
lcd.h:
定义了LCD的参数、地址结构体、各种函数名,宏定义了扫描方向、颜色、分辨率等
LCD的地址结构体是分成了REG和RAM的
在lcd.c中按道理来说是这么读写数据的
定义了扫描的方向,可以直接在lcd.c中调用
- //扫描方向定义
- #define L2R_U2D 0 //从左到右,从上到下
- #define L2R_D2U 1 //从左到右,从下到上
- #define R2L_U2D 2 //从右到左,从上到下
- #define R2L_D2U 3 //从右到左,从下到上
-
- #define U2D_L2R 4 //从上到下,从左到右
- #define U2D_R2L 5 //从上到下,从右到左
- #define D2U_L2R 6 //从下到上,从左到右
- #define D2U_R2L 7 //从下到上,从右到左
-
- #define DFT_SCAN_DIR L2R_U2D //默认的扫描方向
复制代码
定义了颜色,里面有的颜色lcd.c中也可以直接用颜色名调用
- //画笔颜色
- #define WHITE 0xFFFF
- #define BLACK 0x0000
- #define BLUE 0x001F
- #define BRED 0XF81F
- #define GRED 0XFFE0
- #define GBLUE 0X07FF
- #define RED 0xF800
- #define MAGENTA 0xF81F
- #define GREEN 0x07E0
- #define CYAN 0x7FFF
- #define YELLOW 0xFFE0
- #define BROWN 0XBC40 //棕色
- #define BRRED 0XFC07 //棕红色
- #define GRAY 0X8430 //灰色
- //GUI颜色
-
- #define DARKBLUE 0X01CF //深蓝色
- #define LIGHTBLUE 0X7D7C //浅蓝色
- #define GRAYBLUE 0X5458 //灰蓝色
- //以上三色为PANEL的颜色
-
- #define LIGHTGREEN 0X841F //浅绿色
- #define LGRAY 0XC618 //浅灰色(PANNEL),窗体背景色
-
- #define LGRAYBLUE 0XA651 //浅灰蓝色(中间层颜色)
- #define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色)
复制代码
lcd.c:
讲一下我认为比较重要的,理解一下代码
LCD初始化,这个是必要的,我的理解是,和HAL_Init();//初始化HAL库差不多,然后在这个函数的最后,就是我们在lcd.c第四点更改的打开背光,也就是直接写高背光引脚电平,和开关led灯操作类似,如何操作led灯在之前写过,引脚名字是在STM32CubeMX里修改的,不改的话直接使用原引脚名也行
这是“默认”设置,竖屏、开背光、清屏为白色,其实这边只要正常的修改,即可以更改成lcd.h中定义过的,就能获得你自己的默认设置,比如可以清屏清成一些定义里有的其他颜色,虽然好像没有什么用
- LCD_Display_Dir(0); //默认为竖屏
- HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET); //点亮背光
- LCD_Clear(WHITE);
复制代码
这边使用到的清屏LCD_Clear(WHITE);的定义就在后几行
- //清屏函数
- //color:要清屏的填充色
- void LCD_Clear(uint16_t color)
- {
- uint32_t index = 0;
- uint32_t totalpoint = lcddev.width;
- totalpoint *= lcddev.height; //得到总点数
- LCD_SetCursor(0x00, 0x0000); //设置光标位置
- LCD_WriteRAM_Prepare(); //开始写入GRAM
- for (index = 0; index < totalpoint; index++)
- {
- LCD->LCD_RAM = color;
- }
- }
复制代码
清屏中的LCD_SetCursor(0x00, 0x0000);在好多函数里面都被调用,看一下设置光标位置的定义,这边只暂时我的这个显示屏5310的相关代码,其他可在文件中查看
- //清屏函数
- //color:要清屏的填充色
- void LCD_Clear(uint16_t color)
- {
- uint32_t index = 0;
- uint32_t totalpoint = lcddev.width;
- totalpoint *= lcddev.height; //得到总点数
- LCD_SetCursor(0x00, 0x0000); //设置光标位置
- LCD_WriteRAM_Prepare(); //开始写入GRAM
- for (index = 0; index < totalpoint; index++)
- {
- LCD->LCD_RAM = color;
- }
- }
复制代码
上面设置光标位置中调用的LCD_WR_REG和LCD_WR_DATA是用写入值的,写入的读出值的函数有几个,如下:
- //设置光标位置
- //Xpos:横坐标
- //Ypos:纵坐标
- void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos)
- {
- //省略1963/5510型号相关代码
- else //9341/5310/7789等设置坐标
- {
- LCD_WR_REG(lcddev.setxcmd);
- LCD_WR_DATA(Xpos >> 8);
- LCD_WR_DATA(Xpos & 0XFF);
- LCD_WR_REG(lcddev.setycmd);
- LCD_WR_DATA(Ypos >> 8);
- LCD_WR_DATA(Ypos & 0XFF);
- }
- }
复制代码
简单绘制函数,画点、快速画点、画线、画圆、画矩形,不进行详细说明
- //画点 x,y:坐标 POINT_COLOR:此点的颜色
- void LCD_DrawPoint(uint16_t x, uint16_t y)
- //快速画点 x,y:坐标 color:颜色
- void LCD_Fast_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
- //画线 x1,y1:起点坐标 x2,y2:终点坐标
- void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
- //画矩形 (x1,y1),(x2,y2):矩形的对角坐标
- void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
- //画圆 (x,y):中心点 r:半径
- void LCD_DrawCircle(uint16_t x0, uint16_t y0, uint8_t r)
复制代码
显示字符,显示字符、数字、字符串
- //显示一个字符 x,y:起始坐标 num:要显示的字符:" "--->"~"
- //size:字体大小 12/16/24/32 mode:叠加方式(1)还是非叠加方式(0)
- void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t num, uint8_t size, uint8_t mode)
- //显示数字,高位为0,则不显示 x,y :起点坐标 len :数字的位数
- //size:字体大小 color:颜色 num:数值(0~4294967295);
- void LCD_ShowNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint8_t size)
- //显示数字,高位为0,还是显示 x,y:起点坐标 num:数值(0~999999999); len:长度(即要显示的位数)
- //size:字体大小 mode:[7]:0,不填充;1,填充0.[6:1]:保留.[0]:0,非叠加显示;1,叠加显示.
- void LCD_ShowxNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint8_t size, uint8_t mode)
- //显示字符串 x,y:起点坐标 width,height:区域大小
- //size:字体大小 *p:字符串起始地址
- void LCD_ShowString(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, uint8_t *p)
复制代码
着重看一下显示字符串的,代码如下:
- //显示字符串
- //x,y:起点坐标
- //width,height:区域大小
- //size:字体大小
- //*p:字符串起始地址
- void LCD_ShowString(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, uint8_t *p)
- {
- uint8_t x0 = x;
- width += x;
- height += y;
- while ((*p <= '~') && (*p >= ' ')) //判断是不是非法字符!
- {
- if (x >= width)
- {
- x = x0;
- y += size;
- }
- if (y >= height)break; //退出
- LCD_ShowChar(x, y, *p, size, 0);
- x += size / 2;
- p++;
- }
- }
复制代码
发现显示字符串本质上还是调用了显示字符函数LCD_ShowChar(x, y, *p, size, 0);所以还是要看一下显示字符函数,显示字符中调用了font.h中的字库,加一个字库可以加一个else if
- //在指定位置显示一个字符
- //x,y:起始坐标
- //num:要显示的字符:" "--->"~"
- //size:字体大小 12/16/24/32
- //mode:叠加方式(1)还是非叠加方式(0)
- void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t num, uint8_t size, uint8_t mode)
- {
- uint8_t temp, t1, t;
- uint16_t y0 = y;
- uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); //得到字体一个字符对应点阵集所占的字节数
- num = num - ' '; //得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
- for (t = 0; t < csize; t++)
- {
- if (size == 12)temp = asc2_1206[num][t]; //调用1206字体
- else if (size == 16)temp = asc2_1608[num][t]; //调用1608字体
- else if (size == 24)temp = asc2_2412[num][t]; //调用2412字体
- else if (size == 32)temp = asc2_3216[num][t]; //调用3216字体
- else return; //没有的字库
- for (t1 = 0; t1 < 8; t1++)
- {
- if (temp & 0x80)LCD_Fast_DrawPoint(x, y, POINT_COLOR);
- else if (mode == 0)LCD_Fast_DrawPoint(x, y, BACK_COLOR);
- temp <<= 1;
- y++;
- if (y >= lcddev.height)return; //超区域了
- if ((y - y0) == size)
- {
- y = y0;
- x++;
- if (x >= lcddev.width)return; //超区域了
- break;
- }
- }
- }
- }
复制代码
实现结果:
主函数编写:
添加的代码说明完了,那么我们就可以写主函数了,打开main.c,还是重复一下注意事项,写入代码要写到可以写入的地方,以免STM32CubeMX再次导出时覆盖掉没有写入到可写入区域的代码
因为加入了lcd驱动的代码,所以得加个头文件#include "lcd.h"
- /* USER CODE BEGIN Includes */
- #include "lcd.h"
- /* USER CODE END Includes */
复制代码
写入操作代码,注意一下,还要到主函数已经有的各种初始化之后,即在各个_Init();之后,写的是显示字符的代码,写入的代码写在/* USER CODE BEGIN 2 */和/* USER CODE END 2 */之间,为了说明每个字号都能用,每一行字用了不同字号
- /* USER CODE BEGIN 2 */
- uint8_t lcd_id[12]; //存放LCD ID字符串
- LCD_Init(); //初始化LCD
- //LCD_Clear(WHITE); //清屏,可更改颜色,初始默认为白色
- //POINT_COLOR = BLACK; //更改画笔颜色,初始默认为黑色
- sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id); //将LCD ID写入lcd_id数组
- LCD_ShowString(30,40,200,12,12,lcd_id);
- LCD_ShowString(30,60,200,16,16,"Light the lcd");
- LCD_ShowString(30,85,200,24,24,"so exciting");
- LCD_ShowString(30,120,200,32,32,"2022/8/17");
- /* USER CODE END 2 */
复制代码
虽然没什么作用,但是可以亮一下led灯证明板子还能用,绝对不是因为我无聊,我就是没事喜欢亮一下灯,毕竟我干啥都是从点灯开始的,还是要写到可以写的位置
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- HAL_Delay(1000);
- HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
- HAL_Delay(1000);
- HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
- }
- /* USER CODE END 3 */
复制代码
结果实现:
运行导入板子,最后的表现如下:
————————————————
版权声明:试图摸大鱼
|