2.4寸SPI串口ILI9341芯片彩色LCD驱动
该LCD分辨率为240 * 320. 与单片机连接需要接以下引脚
- GND: 地线
- 3.3V: 3.3V供电
- BL:背光信号,可以使用PWM调节背光亮度
- SCK: SPI时钟信号
- RST: LCD Reset引脚
- CS: SPI 片选引脚,如果只接一个SPI设备,该引脚可以直接接地
- A0:命令/数据选择引脚
- SDI: SPI数据输入引脚,MOSI
- SDO: SPI数据输出引脚,MISO,如果不读取液晶屏的数据,这个引脚可以不接
我们采用STM32F103RB单片机驱动该屏幕,连接好GND和3.3v后,其他引脚接线如下:
-
将BL引脚接到TIM2_CH2引脚 PA1,在STM32CubeMX中设置TIM2 Channel2为PWM Generation。根据我们希望的亮度值,设置Prescalar与ARR(详情见此文:xxxxx)
-
在STM32CubeMX中将SPI2设置为Full Duplex Master(如果不打算读取液晶屏的数据,设成Transmit Only Master也是可以的。)。将PB13脚接到LCD SCK脚,PB15脚接到LCD SDI脚。
-
将PB0接到LCD RST脚, PB1接到LCD A0脚,PB2接到LCD CS脚。并在STM32CubeMX中将这三个引脚的模式都设为Output Push Pull (推挽输出)。输出level High,最大输出速度 High。并将三个引脚分别命名为LCD_RST, LCD_A0, LCD_CS
完成以上设置于接线之后,我们就可以生成代码并实现相关驱动的代码了,部分关键代码如下所示:
#define LCD_ORIENTATION_LANDSCAPE 0x28
#define LCD_ORIENTATION_PORTRAIT 0x48
#define LCD_BASE_WIDTH 240
#define LCD_BASE_HEIGHT 320#define LCD_ORIENTATION LCD_ORIENTATION_PORTRAIT//默认是竖屏,所以宽度240,高度320,当横屏时,需要交换设置
#define LCD_WIDTH (LCD_ORIENTATION == LCD_ORIENTATION_PORTRAIT ? LCD_BASE_WIDTH : LCD_BASE_HEIGHT)
#define LCD_HEIGHT (LCD_ORIENTATION == LCD_ORIENTATION_PORTRAIT ? LCD_BASE_HEIGHT : LCD_BASE_WIDTH)#define LCD_MODE_DATA() HAL_GPIO_WritePin(LCD_A0_GPIO_Port, LCD_A0_Pin, GPIO_PIN_SET)
#define LCD_MODE_CMD() HAL_GPIO_WritePin(LCD_A0_GPIO_Port, LCD_A0_Pin, GPIO_PIN_RESET)
#define LCD_CHIP_SELECT() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET)
#define LCD_CHIP_UNSELECT() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET)// 通过拉低LCD_RST引脚实现LCD硬复位
void LCD_Hard_Reset() {HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);HAL_Delay(20);HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET);HAL_Delay(120);}// 发送一个字节的命令
void LCD_Send_Cmd(uint8_t cmd) {LCD_CHIP_SELECT();LCD_MODE_CMD();HAL_SPI_Transmit(&SPI_HANDLE, &cmd, 1, 1000);LCD_CHIP_UNSELECT();
}// 发送len个字节的数据
void LCD_Send_Data(uint8_t *data, size_t len) {LCD_CHIP_SELECT();LCD_MODE_DATA();HAL_SPI_Transmit(&SPI_HANDLE, data, len, 1000);LCD_CHIP_UNSELECT();
}// 设置屏幕方向,横屏传入0x28,竖屏传入0x48
void LCD_Set_Oritention(uint8_t ori) {LCD_Send_Cmd(0x36);LCD_Send_Data(&ori, 1);
}// 设置操作区的大小
void LCD_Set_Window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {LCD_Send_Cmd(0x2a);LCD_Send_Data((uint8_t[]){x0 >> 8, x0 & 0xff, x1 >> 8, x1 & 0xff}, 4);LCD_Send_Cmd(0x2b);LCD_Send_Data((uint8_t[]){y0 >> 8, y0 & 0xff, y1 >> 8, y1 & 0xff}, 4);
}// 用color颜色填充整个屏幕,这里不需要每个点指定位置,只要发送足够的数据,芯片会自动根据从上到下,从左到右的顺序绘制操作区的每个点
void LCD_Clear(uint16_t color) {LCD_Set_Window(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);LCD_Send_Cmd(0x2c);uint8_t color_data[2];color_data[0] = color >> 8; // 高字节color_data[1] = color & 0xFF; // 低字节for (int i = 0; i < LCD_WIDTH * LCD_HEIGHT; i++) {LCD_Send_Data(color_data, 2);}
}// LCD初始化
void LCD_Init() {LCD_Send_Cmd(0x3A);LCD_Send_Data((uint8_t[]){0x55}, 1); // 设置成RGB565格式,默认为RGB666LCD_Send_Cmd(0xB1); // 设置帧率LCD_Send_Data((uint8_t[]){0x00, 0x15}, 2); // 默认为0x1B 70Hz, 会有肉眼可见的闪烁,可以设高一点,这里设成了0x15, 90HzLCD_Set_Oritention(LCD_ORIENTATION); // 根据需要设置横屏或竖屏LCD_Clear(DEFAULT_BACKGROUND_COLOR); //清屏// 退出睡眠模式LCD_Send_Cmd(0x11);HAL_Delay(10); // 手册要求至少delay 10ms// 开启显示LCD_Send_Cmd(0x29);}
完成以上方法之后,在main.c中,调用
LCD_Reset();
LCD_Init();
我们就可以在屏幕上看到按照DEFAULT_BACKGROUND_COLOR颜色点亮的屏幕了。
其他显示文字、图像、或者线条的方法,就是通过发送0x2c命令填充像素。如:
// 画一个点,现将操作区设置为x, y坐标点,然后发送0x2c命令,跟上一个2字节的颜色数据,就能在屏幕的x,y坐标处显示出一个点
void LCD_Draw_Pixel(uint16_t x, uint16_t y, uint16_t color) {LCD_Set_Window(x, y, x, y);LCD_Send_Cmd(0x2c);uint8_t color_data[] = {color >> 8, color & 0xFF};LCD_Send_Data(color_data, 2);
}// 画一条线,从(x0, y0)到(x1, y1)两点间画一条直线。利用了之前画点的函数
void LCD_Draw_Line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) {uint16_t start_x, start_y, end_x, end_y;if (x0 < x1) {start_x = x0;end_x = x1;} else {start_x = x1;end_x = x0;}if (y0 < y1) {start_y = y0;end_y = y1;} else {start_y = y1;end_y = y0;}for (uint16_t i = start_x; i <= end_x; i++) {for (uint16_t j = start_y; j <= end_y; j++) {LCD_Draw_Pixel(i, j, color);}}}
完整项目工程在这里:https://github.com/sqlxx/stm32-workshop/ (lcd_driver分支)
