spi-lcd-st7789-驱动开发-单片机程序移植-(2)

这节主要讲下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硬件,还有下面的工作要做:

  1. lcd使用imx6ul spi控制器的硬件功能配置
  2. lcd使用imx6ul gpio控制引脚的硬件功能配置
  3. 将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的画会有如下测试画面出现:

 

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
在STM32单片机上使用硬件SPI驱动4线SPI-LCD的方法如下: 1. 首先,确保你已经连接好了STM32单片机和LCD屏幕,并且正确配置了SPI引脚。 2. 在代码中包含SPI库和LCD库的头文件。例如,对于ST7735驱动的LCD屏幕,你可以包含"ST7735.h"头文件。 3. 初始化SPI和LCD屏幕。在初始化之前,你需要设置背景色和画笔色。这些颜色将用于绘制图形和文本。 4. 使用SPI发送命令和数据给LCD屏幕。你可以使用SPI的发送函数来发送命令和数据。具体的命令和数据可以参考LCD屏幕的数据手册。 5. 在LCD屏幕上绘制图形和文本。你可以使用LCD库提供的函数来绘制图形和文本。例如,你可以使用画线函数来绘制线条,使用填充矩形函数来绘制矩形,使用显示字符函数来显示文本等等。 6. 最后,关闭SPI和LCD屏幕。在程序结束之前,记得关闭SPI和LCD屏幕以释放资源。 下面是一个示例代码,演示了如何使用硬件SPI驱动4线SPI-LCD: ```c #include "ST7735.h" #include "usart.h" u16 BACK_COLOR, POINT_COLOR; //背景色,画笔色 void WriteCommand_7735(u8 cmd) { // 使用SPI发送命令给LCD屏幕 // ... } void WriteData_7735(u8 data) { // 使用SPI发送数据给LCD屏幕 // ... } void InitLCD() { // 初始化SPI和LCD屏幕 // ... } void DrawGraphics() { // 在LCD屏幕上绘制图形和文本 // ... } int main() { // 设置背景色和画笔色 BACK_COLOR = WHITE; POINT_COLOR = BLACK; // 初始化SPI和LCD屏幕 InitLCD(); // 在LCD屏幕上绘制图形和文本 DrawGraphics(); // 关闭SPI和LCD屏幕 // ... return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值