这节主要讲下spi-lcd-st7789单片机程序如何移植到linux。不会详细描述linux spi驱动框架的细节,想要详细了解Linux spi驱动的内容,可以参考韦东山老师的视频。
单片机程序参考正点原子的代码,如有侵权请告知。
spi-lcd-st7789 单片机驱动程序
spi-lcd-st7789 单片机驱动程序使用的正点原子的代码,主要涉及spi控制器的初始化,lcd的初始化,lcd命令字及数据接口。
此外单片机驱动程序还包括了显示字符,图片,颜色填充,画矩形和圆形的代码。主要涉及到的的接口如下:
void LCD_Init(void); //初始化
void LCD_DisplayOn(void); //开显示
void LCD_DisplayOff(void); //关显示
void LCD_Write_HalfWord(const u16 da); //写半个字节数据到LCD
void LCD_Address_Set(u16 x1, u16 y1, u16 x2, u16 y2); //设置数据显示区域
void LCD_Clear(u16 color); //清屏
void LCD_Fill(u16 x_start, u16 y_start, u16 x_end, u16 y_end, u16 color); //填充单色
void LCD_Fill2(u16 x_start, u16 y_start, u16 x_end, u16 y_end, u16 color); //填充单色
void LCD_Draw_Point(u16 x, u16 y); //画点
void LCD_Draw_ColorPoint(u16 x, u16 y,u16 color); //画带颜色点
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2); //画线
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2); //画矩形
void LCD_Draw_Circle(u16 x0, u16 y0, u8 r); //画圆
void LCD_ShowChar(u16 x, u16 y, char chr, u8 size); //显示一个字符
void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size); //显示一个数字
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode); //显示数字
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,char *p); //显示字符串
void LCD_Show_Image(u16 x, u16 y, u16 width, u16 height, const u8 *p); //显示图片
void Display_ALIENTEK_LOGO(u16 x,u16 y);
void LCD_Test(void);
void LCD_SPI_Send(u8 *data, u32 size); //spi数据发送接口
static void LCD_Write_Cmd(u8 cmd); //st7789 命令发送接口
static void LCD_Write_Data(u8 data); //st7789 数据发送接口
各个接口具体内容如下:
#define SET_LCD_ST7789_CS_HIGH (gpio_direction_output(g_spi_lcd_st7789_device.m_cs_gpio_nr, 1))
#define SET_LCD_ST7789_CS_LOW (gpio_direction_output(g_spi_lcd_st7789_device.m_cs_gpio_nr, 0))
#define SET_LCD_ST7789_RS_HIGH (gpio_direction_output(g_spi_lcd_st7789_device.m_reset_gpio_nr, 1))
#define SET_LCD_ST7789_RS_LOW (gpio_direction_output(g_spi_lcd_st7789_device.m_reset_gpio_nr, 0))
#define SET_LCD_ST7789_PWR_HIGH (gpio_direction_output(g_spi_lcd_st7789_device.m_pwr_gpio_nr, 1))
#define SET_LCD_ST7789_PWR_LOW (gpio_direction_output(g_spi_lcd_st7789_device.m_pwr_gpio_nr, 0))
#define SET_LCD_ST7789_DC_HIGH (gpio_direction_output(g_spi_lcd_st7789_device.m_dc_gpio_nr, 1))
#define SET_LCD_ST7789_DC_LOW (gpio_direction_output(g_spi_lcd_st7789_device.m_dc_gpio_nr, 0))
/**
* @brief LCD控制接口初始化
*
* @param void
*
* @return void
*/
static void LCD_Gpio_Init(void)
{
//替换成linux接口
/*
LCD_CS = 0;
LCD_PWR = 0;
LCD_RST = 0;
mdelay(120);
LCD_RST = 1;
*/
SET_LCD_ST7789_CS_LOW;
SET_LCD_ST7789_PWR_LOW;
SET_LCD_ST7789_RS_LOW;
msleep(120);
SET_LCD_ST7789_RS_HIGH;
}
/**
* @brief LCD底层SPI发送数据函数
*
* @param data 数据的起始地址
* @param size 发送数据大小
*
* @return void
*/
static void LCD_SPI_Send(u8 *data, u32 size)
{
if (!use_dma_flag) {
u32 i;
u32 delta;
delta = size/0xFFFF;
for(i = 0; i<=delta; i++)
{
if( i==delta ) /* 发送最后一帧数据 */ {
//SPI2_WriteData(&data[i*0xFFFF], size%0xFFFF); /SPI2_WriteData/替换linux接口
spi_lcd_st7789_send_data(&data[i*0xFFFF], size%0xFFFF);
} else {/* 超长数据一次发送0xFFFF字节数据 */
//SPI2_WriteData(&data[i*0xFFFF], 0xFFFF); //SPI2_WriteData替换linux接口
spi_lcd_st7789_send_data(&data[i*0xFFFF], 0xFFFF);
}
}
} else {
//printk(KERN_WARNING "%s:%d[dma t1]\n", __FUNCTION__, __LINE__);
if (-1 == dma_split_size) {
spi_lcd_st7789_send_data(data, size);
} else {
if (dma_split_size % 32 != 0) {
printk(KERN_WARNING "dma_split_size shuold be 32x, use in normal mode\n");
}
u32 i;
u32 delta;
delta = size/dma_split_size;
for(i = 0; i<=delta; i++)
{
if( i==delta ) /* 发送最后一帧数据 */ {
spi_lcd_st7789_send_data(&data[i*dma_split_size], size%dma_split_size);
} else {/* 超长数据一次发送0xFFFF字节数据 */
spi_lcd_st7789_send_data(&data[i*dma_split_size], dma_split_size);
}
}
}
}
}
/**
* @brief 写命令到LCD
*
* @param cmd 需要发送的命令
*
* @return void
*/
static void LCD_Write_Cmd(u8 cmd)
{
//替换linux接口
//LCD_WR = 0;
SET_LCD_ST7789_DC_LOW;
LCD_SPI_Send(&cmd, 1);
}
/**
* @brief 写数据到LCD
*
* @param cmd 需要发送的数据
*
* @return void
*/
static void LCD_Write_Data(u8 data)
{
LCD_WR = 1; //Linux //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(&data, 1);
}
/**
* @brief 写半个字的数据到LCD
*
* @param cmd 需要发送的数据
*
* @return void
*/
void LCD_Write_HalfWord(const u16 da)
{
u8 data[2] = {0};
data[0] = da >> 8;
data[1] = da;
LCD_WR = 1; //Linux //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(data, 2);
}
/**
* 设置数据写入LCD缓存区域
*
* @param x1,y1 起点坐标
* @param x2,y2 终点坐标
*
* @return void
*/
void LCD_Address_Set(u16 x1, u16 y1, u16 x2, u16 y2)
{
LCD_Write_Cmd(0x2a);
LCD_Write_Data(x1 >> 8);
LCD_Write_Data(x1);
LCD_Write_Data(x2 >> 8);
LCD_Write_Data(x2);
LCD_Write_Cmd(0x2b);
LCD_Write_Data(y1 >> 8);
LCD_Write_Data(y1);
LCD_Write_Data(y2 >> 8);
LCD_Write_Data(y2);
LCD_Write_Cmd(0x2C);
}
/**
* 打开LCD显示
*
* @param void
*
* @return void
*/
void LCD_DisplayOn(void)
{
//LCD_PWR = 1; //Linux
SET_LCD_ST7789_PWR_HIGH;
}
/**
* 关闭LCD显示
*
* @param void
*
* @return void
*/
void LCD_DisplayOff(void)
{
//LCD_PWR = 0; //Linux
SET_LCD_ST7789_PWR_LOW;
}
/**
* 以一种颜色清空LCD屏
*
* @param color 清屏颜色
*
* @return void
*/
void LCD_Clear(u16 color)
{
u16 i, j;
u8 data[2] = {0};
data[0] = color >> 8;
data[1] = color;
LCD_Address_Set(0, 0, LCD_Width - 1, LCD_Height - 1);
for(j = 0; j < lcd_st7789_buf_size / 2; j++)
{
lcd_st7789_lcd_buf[j * 2] = data[0];
lcd_st7789_lcd_buf[j * 2 + 1] = data[1];
}
//LCD_WR = 1; //Linux
SET_LCD_ST7789_DC_HIGH;
for(i = 0; i < (lcd_st7789_fb_total_buf_size / lcd_st7789_buf_size); i++)
{
LCD_SPI_Send(lcd_st7789_lcd_buf, lcd_st7789_buf_size);
}
}
/**
* 用一个颜色填充整个区域
*
* @param x_start,y_start 起点坐标
* @param x_end,y_end 终点坐标
* @param color 填充颜色
*
* @return void
*/
void LCD_Fill(u16 x_start, u16 y_start, u16 x_end, u16 y_end, u16 color)
{
u16 i = 0;
u32 size = 0, size_remain = 0;
size = (x_end - x_start + 1) * (y_end - y_start + 1) * 2;
if(size > lcd_st7789_buf_size)
{
size_remain = size - lcd_st7789_buf_size;
size = lcd_st7789_buf_size;
}
LCD_Address_Set(x_start, y_start, x_end, y_end);
while(1)
{
for(i = 0; i < size / 2; i++)
{
lcd_st7789_lcd_buf[2 * i] = color >> 8;
lcd_st7789_lcd_buf[2 * i + 1] = color;
}
//LCD_WR = 1; //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(lcd_st7789_lcd_buf, size);
if(size_remain == 0)
break;
if(size_remain > lcd_st7789_buf_size)
{
size_remain = size_remain - lcd_st7789_buf_size;
}
else
{
size = size_remain;
size_remain = 0;
}
}
}
/**
* 用一个颜色填充整个区域
*
* @param x_start,y_start 起点坐标
* @param x_end,y_end 终点坐标
* @param color 填充颜色
*
* @return void
*/
void LCD_Fill2(u16 x_start, u16 y_start, u16 x_end, u16 y_end, u16 color)
{
u16 i = 0, j = 0;
u32 size = 0, size_remain = 0;
LCD_Address_Set(x_start, y_start, x_end, y_end);
while(1)
{
for(i = 0; i < size / 2; i++)
{
lcd_st7789_lcd_buf[2 * i] = color >> 8;
lcd_st7789_lcd_buf[2 * i + 1] = color;
}
//LCD_WR = 1; //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(lcd_st7789_lcd_buf, size);
if(size_remain == 0)
break;
if(size_remain > lcd_st7789_buf_size)
{
size_remain = size_remain - lcd_st7789_buf_size;
}
else
{
size = size_remain;
size_remain = 0;
}
}
}
/**
* 画点函数
*
* @param x,y 画点坐标
*
* @return void
*/
void LCD_Draw_Point(u16 x, u16 y)
{
LCD_Address_Set(x, y, x, y);
LCD_Write_HalfWord(POINT_COLOR);
}
/**
* 画点带颜色函数
*
* @param x,y 画点坐标
*
* @return void
*/
void LCD_Draw_ColorPoint(u16 x, u16 y,u16 color)
{
LCD_Address_Set(x, y, x, y);
LCD_Write_HalfWord(color);
}
/**
* @brief 画线函数(直线、斜线)
*
* @param x1,y1 起点坐标
* @param x2,y2 终点坐标
*
* @return void
*/
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2)
{
u16 t;
int xerr = 0, yerr = 0, delta_x, delta_y, distance;
int incx, incy, row, col;
u32 i = 0;
if(y1 == y2)
{
/*快速画水平线*/
LCD_Address_Set(x1, y1, x2, y2);
for(i = 0; i < x2 - x1; i++)
{
lcd_st7789_lcd_buf[2 * i] = POINT_COLOR >> 8;
lcd_st7789_lcd_buf[2 * i + 1] = POINT_COLOR;
}
//LCD_WR = 1; //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(lcd_st7789_lcd_buf, (x2 - x1) * 2);
return;
}
delta_x = x2 - x1;
delta_y = y2 - y1;
row = x1;
col = y1;
if(delta_x > 0)incx = 1;
else if(delta_x == 0)incx = 0;
else
{
incx = -1;
delta_x = -delta_x;
}
if(delta_y > 0)incy = 1;
else if(delta_y == 0)incy = 0;
else
{
incy = -1;
delta_y = -delta_y;
}
if(delta_x > delta_y)distance = delta_x;
else distance = delta_y;
for(t = 0; t <= distance + 1; t++)
{
LCD_Draw_Point(row, col);
xerr += delta_x ;
yerr += delta_y ;
if(xerr > distance)
{
xerr -= distance;
row += incx;
}
if(yerr > distance)
{
yerr -= distance;
col += incy;
}
}
}
/**
* @brief 画一个矩形
*
* @param x1,y1 起点坐标
* @param x2,y2 终点坐标
*
* @return void
*/
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)
{
LCD_DrawLine(x1, y1, x2, y1);
LCD_DrawLine(x1, y1, x1, y2);
LCD_DrawLine(x1, y2, x2, y2);
LCD_DrawLine(x2, y1, x2, y2);
}
/**
* @brief 画一个圆
*
* @param x0,y0 圆心坐标
* @param r 圆半径
*
* @return void
*/
void LCD_Draw_Circle(u16 x0, u16 y0, u8 r)
{
int a, b;
int di;
a = 0;
b = r;
di = 3 - (r << 1);
while(a <= b)
{
LCD_Draw_Point(x0 - b, y0 - a);
LCD_Draw_Point(x0 + b, y0 - a);
LCD_Draw_Point(x0 - a, y0 + b);
LCD_Draw_Point(x0 - b, y0 - a);
LCD_Draw_Point(x0 - a, y0 - b);
LCD_Draw_Point(x0 + b, y0 + a);
LCD_Draw_Point(x0 + a, y0 - b);
LCD_Draw_Point(x0 + a, y0 + b);
LCD_Draw_Point(x0 - b, y0 + a);
a++;
if(di < 0)di += 4 * a + 6;
else
{
di += 10 + 4 * (a - b);
b--;
}
LCD_Draw_Point(x0 + a, y0 + b);
}
}
/**
* @brief 显示一个ASCII码字符
*
* @param x,y 显示起始坐标
* @param chr 需要显示的字符
* @param size 字体大小(支持16/24/32号字体)
*
* @return void
*/
void LCD_ShowChar(u16 x, u16 y, char chr, u8 size)
{
u8 temp, t1, t;
u8 csize; //得到字体一个字符对应点阵集所占的字节数
u16 colortemp;
u8 sta;
chr = chr - ' '; //得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
if((x > (LCD_Width - size / 2)) || (y > (LCD_Height - size))) return;
LCD_Address_Set(x, y, x + size / 2 - 1, y + size - 1);//(x,y,x+8-1,y+16-1)
if((size == 16) || (size == 32) ) //16和32号字体
{
csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);
for(t = 0; t < csize; t++)
{
if(size == 16)temp = asc2_1608[chr][t]; //调用1608字体
else if(size == 32)temp = asc2_3216[chr][t]; //调用3216字体
else return; //没有的字库
for(t1 = 0; t1 < 8; t1++)
{
if(temp & 0x80) colortemp = POINT_COLOR;
else colortemp = BACK_COLOR;
LCD_Write_HalfWord(colortemp);
temp <<= 1;
}
}
}
else if (size == 12) //12号字体
{
csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);
for(t = 0; t < csize; t++)
{
temp = asc2_1206[chr][t];
for(t1 = 0; t1 < 6; t1++)
{
if(temp & 0x80) colortemp = POINT_COLOR;
else colortemp = BACK_COLOR;
LCD_Write_HalfWord(colortemp);
temp <<= 1;
}
}
}
else if(size == 24) //24号字体
{
csize = (size * 16) / 8;
for(t = 0; t < csize; t++)
{
temp = asc2_2412[chr][t];
if(t % 2 == 0)sta = 8;
else sta = 4;
for(t1 = 0; t1 < sta; t1++)
{
if(temp & 0x80) colortemp = POINT_COLOR;
else colortemp = BACK_COLOR;
LCD_Write_HalfWord(colortemp);
temp <<= 1;
}
}
}
}
/**
* @brief m^n函数
*
* @param m,n 输入参数
*
* @return m^n次方
*/
static u32 LCD_Pow(u8 m, u8 n)
{
u32 result = 1;
while(n--)result *= m;
return result;
}
/**
* @brief 显示数字,高位为0不显示
*
* @param x,y 起点坐标
* @param num 需要显示的数字,数字范围(0~4294967295)
* @param len 需要显示的位数
* @param size 字体大小
*
* @return void
*/
void LCD_ShowNum(u16 x, u16 y, u32 num, u8 len, u8 size)
{
u8 t, temp;
u8 enshow = 0;
for(t = 0; t < len; t++)
{
temp = (num / LCD_Pow(10, len - t - 1)) % 10;
if(enshow == 0 && t < (len - 1))
{
if(temp == 0)
{
LCD_ShowChar(x + (size / 2)*t, y, ' ', size);
continue;
}
else enshow = 1;
}
LCD_ShowChar(x + (size / 2)*t, y, temp + '0', size);
}
}
/**
* @brief 显示数字,高位为0,可以控制显示为0还是不显示
*
* @param x,y 起点坐标
* @param num 需要显示的数字,数字范围(0~999999999)
* @param len 需要显示的位数
* @param size 字体大小
* @param mode 1:高位显示0 0:高位不显示
*
* @return void
*/
void LCD_ShowxNum(u16 x, u16 y, u32 num, u8 len, u8 size, u8 mode)
{
u8 t, temp;
u8 enshow = 0;
for(t = 0; t < len; t++)
{
temp = (num / LCD_Pow(10, len - t - 1)) % 10;
if(enshow == 0 && t < (len - 1))
{
if(temp == 0)
{
if(mode)LCD_ShowChar(x + (size / 2)*t, y, '0', size);
else
LCD_ShowChar(x + (size / 2)*t, y, ' ', size);
continue;
}
else enshow = 1;
}
LCD_ShowChar(x + (size / 2)*t, y, temp + '0', size);
}
}
/**
* @brief 显示字符串
*
* @param x,y 起点坐标
* @param width 字符显示区域宽度
* @param height 字符显示区域高度
* @param size 字体大小
* @param p 字符串起始地址
*
* @return void
*/
void LCD_ShowString(u16 x, u16 y, u16 width, u16 height, u8 size, char *p)
{
u8 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);
x += size / 2;
p++;
}
}
/**
* @brief 显示图片
*
* @remark Image2Lcd取模方式: C语言数据/水平扫描/16位真彩色(RGB565)/高位在前 其他的不要选
*
* @param x,y 起点坐标
* @param width 图片宽度
* @param height 图片高度
* @param p 图片缓存数据起始地址
*
* @return void
*/
void LCD_Show_Image(u16 x, u16 y, u16 width, u16 height, const u8 *p)
{
if(x + width > LCD_Width || y + height > LCD_Height)
{
return;
}
LCD_Address_Set(x, y, x + width - 1, y + height - 1);
//LCD_WR = 1; //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send((u8 *)p, width * height * 2);
}
/**
* @brief LCD初始化
*
* @param x,y 显示坐标
*
* @return void
*/
void Display_ALIENTEK_LOGO(u16 x, u16 y)
{
LCD_Show_Image(x, y, 240, 82, ALIENTEK_LOGO);
}
/**
* @brief LCD 测试 会依次显示几种颜色 然后显示一副图片,最后清屏
*
* @param Node
*
* @return void
*/
void LCD_Test(void)
{
u16 color_list[14] = {
[0] = WHITE,
[1] = BLUE,
[2] = BRED,
[3] = GRED,
[4] = GBLUE,
[5] = RED,
[6] = MAGENTA,
[7] = GREEN,
[8] = CYAN,
[9] = YELLOW,
[10] = BROWN,
[11] = BRRED,
[12] = GRAY,
[13] = BLACK,
};
u32 i = 0;
for (i=0; i<14; i++) {
MY_PRINT("Test loop %d start", i);
//LCD_Address_Set(0, 0, LCD_Width - 1, LCD_Height - 1);
//LCD_Clear(color_list[i]);
LCD_Fill(0, 0, 119, 119, color_list[i]);
LCD_Fill(119, 119, 239, 239, color_list[i]);
LCD_Fill(120, 0, 239, 119, color_list[13-i]);
LCD_Fill(0, 120, 120, 239, color_list[13-i]);
LCD_DrawRectangle(0, 0, 119, 119);
LCD_DrawRectangle(120, 0, 239, 119);
LCD_Draw_Circle(59, 59, 55);
LCD_Draw_Circle(179, 59, 55);
POINT_COLOR = color_list[13-i];
LCD_ShowString(0, 121, 200, 80, FONT_SIZE_24, "HelloWorld");
LCD_ShowString(0, 160, 200, 80, FONT_SIZE_32, "HelloWorld");
MY_PRINT("Test loop %d end", i);
mdelay(1000);
LCD_Show_Image(0, 0, 240, 240, gImage_test_pic_240_240);
mdelay(1000);
}
MY_PRINT("Test done!!!");
}
/**
* @brief LCD初始化
*
* @param void
*
* @return void
*/
void LCD_Init(void)
{
LCD_Gpio_Init(); //硬件接口初始化
mdelay(120);
/* Sleep Out */
LCD_Write_Cmd(0x11);
/* wait for power stability */
mdelay(120);
/* Memory Data Access Control */
LCD_Write_Cmd(0x36);
LCD_Write_Data(0x00);
/* RGB 5-6-5-bit */
LCD_Write_Cmd(0x3A);
LCD_Write_Data(0x65);
/* Porch Setting */
LCD_Write_Cmd(0xB2);
LCD_Write_Data(0x0C);
LCD_Write_Data(0x0C);
LCD_Write_Data(0x00);
LCD_Write_Data(0x33);
LCD_Write_Data(0x33);
/* Gate Control */
LCD_Write_Cmd(0xB7);
LCD_Write_Data(0x72);
/* VCOM Setting */
LCD_Write_Cmd(0xBB);
LCD_Write_Data(0x3D); //Vcom=1.625V
/* LCM Control */
LCD_Write_Cmd(0xC0);
LCD_Write_Data(0x2C);
/* VDV and VRH Command Enable */
LCD_Write_Cmd(0xC2);
LCD_Write_Data(0x01);
/* VRH Set */
LCD_Write_Cmd(0xC3);
LCD_Write_Data(0x19);
/* VDV Set */
LCD_Write_Cmd(0xC4);
LCD_Write_Data(0x20);
/* Frame Rate Control in Normal Mode */
LCD_Write_Cmd(0xC6);
LCD_Write_Data(0x0F); //60MHZ
/* Power Control 1 */
LCD_Write_Cmd(0xD0);
LCD_Write_Data(0xA4);
LCD_Write_Data(0xA1);
/* Positive Voltage Gamma Control */
LCD_Write_Cmd(0xE0);
LCD_Write_Data(0xD0);
LCD_Write_Data(0x04);
LCD_Write_Data(0x0D);
LCD_Write_Data(0x11);
LCD_Write_Data(0x13);
LCD_Write_Data(0x2B);
LCD_Write_Data(0x3F);
LCD_Write_Data(0x54);
LCD_Write_Data(0x4C);
LCD_Write_Data(0x18);
LCD_Write_Data(0x0D);
LCD_Write_Data(0x0B);
LCD_Write_Data(0x1F);
LCD_Write_Data(0x23);
/* Negative Voltage Gamma Control */
LCD_Write_Cmd(0xE1);
LCD_Write_Data(0xD0);
LCD_Write_Data(0x04);
LCD_Write_Data(0x0C);
LCD_Write_Data(0x11);
LCD_Write_Data(0x13);
LCD_Write_Data(0x2C);
LCD_Write_Data(0x3F);
LCD_Write_Data(0x44);
LCD_Write_Data(0x51);
LCD_Write_Data(0x2F);
LCD_Write_Data(0x1F);
LCD_Write_Data(0x1F);
LCD_Write_Data(0x20);
LCD_Write_Data(0x23);
/* Display Inversion On */
LCD_Write_Cmd(0x21);
LCD_Write_Cmd(0x29);
/*打开显示*/
//LCD_PWR = 1;
SET_LCD_ST7789_PWR_HIGH;
//LCD_Fill(0,0,239, 239, BLACK);
}
看到上面这么多的代码,大家可能有疑惑,这么多接口该怎么移植?移植工作是否有很大的工作量?
这里可以明确的告诉大家,移植工作相当的简单,只要改动几个接口就可以完成驱动底层的移植。
移植接口1: LCD_Gpio_Init(),注释掉的是原来的接口,后面添加的是我自己修改的:
用到的几个gpio设置的宏定义在上面代码片段中定义了,就是利用用linux 驱动gpio相关的接口设置gpio的电平。
static void LCD_Gpio_Init(void)
{
//替换成linux接口
/*
LCD_CS = 0;
LCD_PWR = 0;
LCD_RST = 0;
mdelay(120);
LCD_RST = 1;
*/
SET_LCD_ST7789_CS_LOW;
SET_LCD_ST7789_PWR_LOW;
SET_LCD_ST7789_RS_LOW;
msleep(120);
SET_LCD_ST7789_RS_HIGH;
}
移植接口2: LCD_SPI_Send(u8 *data, u32 size) 利用linux spi 驱动框架函数直接替换原来的函数就可以了:
SPI2_WriteData 为arm CSMIS 接口的单片发送spi数据接口
spi_lcd_st7789_send_data 基于linux spi驱动框架的spi数据发送接口。底层细节imx6ul bsp 和linux spi 驱动框架都已经做好,我们只要调用spi发送函数就可以了。
数据发送接口就是简单的替换就行了,为了考虑imx6ul的dma数据传输,增加了部分dma控制的逻辑。更详细的dma部分放到后面章节讲解。
uint8_t spi_lcd_st7789_send_data(uint8_t *buf, uint32_t buf_size)
{
uint8_t *send_buf = buf;
uint32_t send_buf_size = buf_size;
struct spi_transfer t = {
.tx_buf = send_buf,
.bits_per_word = 8,
.len = send_buf_size,
};
return spi_sync_transfer(g_spi_lcd_st7789_device.m_spi_device, &t, 1);
}
/**
* @brief LCD底层SPI发送数据函数
*
* @param data 数据的起始地址
* @param size 发送数据大小
*
* @return void
*/
static void LCD_SPI_Send(u8 *data, u32 size)
{
if (!use_dma_flag) {
u32 i;
u32 delta;
delta = size/0xFFFF;
for(i = 0; i<=delta; i++)
{
if( i==delta ) /* 发送最后一帧数据 */ {
//SPI2_WriteData(&data[i*0xFFFF], size%0xFFFF); /SPI2_WriteData/替换linux接口
spi_lcd_st7789_send_data(&data[i*0xFFFF], size%0xFFFF);
} else {/* 超长数据一次发送0xFFFF字节数据 */
//SPI2_WriteData(&data[i*0xFFFF], 0xFFFF); //SPI2_WriteData替换linux接口
spi_lcd_st7789_send_data(&data[i*0xFFFF], 0xFFFF);
}
}
} else {
//printk(KERN_WARNING "%s:%d[dma t1]\n", __FUNCTION__, __LINE__);
if (-1 == dma_split_size) {
spi_lcd_st7789_send_data(data, size);
} else {
if (dma_split_size % 32 != 0) {
printk(KERN_WARNING "dma_split_size shuold be 32x, use in normal mode\n");
}
u32 i;
u32 delta;
delta = size/dma_split_size;
for(i = 0; i<=delta; i++)
{
if( i==delta ) /* 发送最后一帧数据 */ {
spi_lcd_st7789_send_data(&data[i*dma_split_size], size%dma_split_size);
} else {/* 超长数据一次发送0xFFFF字节数据 */
spi_lcd_st7789_send_data(&data[i*dma_split_size], dma_split_size);
}
}
}
}
}
移植接口3:gpio 控制接口的替换
有些单片机的接口涉及到了gpio的操作,我们主要把这些gpio操作换成Linux gpio驱动框架的接口就可以了,主要涉及到gpio操作如下:
/**
* @brief 写命令到LCD
*
* @param cmd 需要发送的命令
*
* @return void
*/
static void LCD_Write_Cmd(u8 cmd)
{
//替换linux接口
//LCD_WR = 0;
SET_LCD_ST7789_DC_LOW;
LCD_SPI_Send(&cmd, 1);
}
/**
* @brief 写数据到LCD
*
* @param cmd 需要发送的数据
*
* @return void
*/
static void LCD_Write_Data(u8 data)
{
LCD_WR = 1; //Linux //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(&data, 1);
}
/**
* @brief 写半个字的数据到LCD
*
* @param cmd 需要发送的数据
*
* @return void
*/
void LCD_Write_HalfWord(const u16 da)
{
u8 data[2] = {0};
data[0] = da >> 8;
data[1] = da;
LCD_WR = 1; //Linux //Linux
SET_LCD_ST7789_DC_HIGH;
LCD_SPI_Send(data, 2);
}
到这里接口移植部分其实已经完成了。但是要想让这部分移植好的代码可以imx6ul控制实际的lcd硬件,还有下面的工作要做:
- lcd使用imx6ul spi控制器的硬件功能配置
- lcd使用imx6ul gpio控制引脚的硬件功能配置
- 将lcd使用的spi控制器及gpio信息注册关联到spi驱动框架及gpio 子系统
spi控制器及gpio 硬件功能的配置,涉及到pinctrl子系统及devicetree相关的内容,这部分内容不是这篇博客的重点,这里仅仅将lcd硬件涉及到的dts节点简单说明下:
spi1硬配置及使用到的gpio硬件配置dts如下:
pinctrl 节点:
//Add by Wuliang 2020.12.12
pinctrl_ecspi1: ecspi1 {
fsl,pins = <
MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI 0x000010B0
MX6UL_PAD_CSI_DATA07__ECSPI1_MISO 0x000010B0 //not used in st7789
MX6UL_PAD_CSI_DATA04__ECSPI1_SCLK 0x000010B0 //
MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x000010B0 //SPI_LCD_ST7789_RESET
MX6UL_PAD_CSI_DATA02__GPIO4_IO23 0x000010B0 //SPI_LCD_ST7789_WR(DC)
MX6UL_PAD_CSI_DATA03__GPIO4_IO24 0x000010B0 //SPI_LCD_ST7789_PWR
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B0 //SPI_LCD_ST7789_CS
>;
spi1 下lcd节点:
#if SPI_LCD_ST7789
&ecspi1 {
fsl,spi-num-chipselects = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi1>;
cs-gpio = <&gpio4 24 GPIO_ACTIVE_HIGH>; /* GPIO4_24 */
status = "okay";
spi_lcd_st7789@0 {
compatible = "spi_lcd_st7789";
spi-max-frequency = <15000000>; //max 66ns 15000000Hz
reg = <0>;
//st7789-cs-gpio = <&gpio4 24 GPIO_ACTIVE_HIGH>; /* GPIO4_24 */
st7789-rs-gpio = <&gpio4 21 GPIO_ACTIVE_HIGH>; /* GPIO4_21 */
st7789-dc-gpio = <&gpio4 23 GPIO_ACTIVE_HIGH>; /* GPIO4_23 */
st7789-pwr-gpio = <&gpio4 20 GPIO_ACTIVE_HIGH>; /* GPIO4_20 */
spi-cpha = <1>;
spi-cpol = <1>;
};
};
#endif //SPI_LCD_ST7789
配置pinctrl节点有个地方要注意,SPI1控制引脚有多组引脚复用,spi1的控制器要同时使用同一组,否则可能出现默认奇妙的错误:
配置spi1的引脚都要使用MX6UL_PAD_CSI_DATA*开头的引脚配置,不能部分使用MX6UL_PAD_CSI_DATA* 部分使用MX6UL_PAD_LCD_DATA*。
比如spi1 mosi引脚有如下2个,
MX6UL_PAD_CSI_DATA06__ECSPI1_MOSI
MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI
第一次实验时spi1的引脚除了MOSI使用了MX6UL_PAD_LCD_DATA22__ECSPI1_MOSI,其他都使用了MX6UL_PAD_CSI_DATA*,导致spi1一直不能发送数据。这个问题搞了几天才找到。
lcd 信息注册到spi子系统的内容可以参考后面公开的代码。为了方便测试,驱动模块增加了一个测试接口,只要在插入模块的时候设置标记,如果没有错误的话,lcd会循环显示多种不同的颜色。
插入模块时执行测的命令如下:
insmod lcd_st7789.ko test_lcd_flag=1
一切ok的画会有如下测试画面出现: