×
单片机 > 单片机程序设计 > 详情

STM32HAL库使用SPI驱动1.44寸TFTLCD

发布时间:2020-06-09 发布时间:
|

  关于STM32F4单片机,使用HAL库自带的SPI,驱动TFTLCD屏幕的资料网上好像不太多,正好最近我做了这项工作,把成果分享给大家。我的代码实现了这些功能:任意坐标画点,指定首尾坐标画线,画方框,指定区域显示彩图,显示16* 16或者12* 12的汉字、ASCII码,并附带ASCII码表与少量的汉字字库。


硬件设计

  屏幕选择:使用了一款低成本十六位彩屏,只要十块钱。链接

  厂家看到文章请联系我打广告费,哈哈。

  虽然用这个屏幕的可能不多,但我了解到,只要其控制芯片是ST7735S,那么程序就应该差不多。不同的地方在于,厂家的封装与玻璃不太一样,玻璃有个伽马值不同,会导致颜色看上去不太一样。


  屏幕的引脚信息

  我的原理图设计:使用了STM32F405RG芯片的SPI1,屏幕没有MISO。



cubeMX中SPI的配置大致如下:

  其实SPI的速度我选的是21MBITS/s,可能再快一点也行,没有测试。

  其它引脚比较散,都是当做IO来用,CubeMX中的配置过程就不说了,汇总如下


名称 引脚 功能

LCD_RST PC5 屏幕复位

LCD_CD PB0 0数据1指令

SPI_MOSI PB5 数据线

SPI_CLK PB3 时钟线

LCD_CS PB1 片选,低电平有效

LCD_LED PB2 背光,高电平有效

发送数据与指令的基本函数

  在引脚初始化以后,我定义了几个位带操作,方便操作引脚


#define LCD_RST   PCout(5)

#define LCD_CD    PBout(0)

#define LCD_CS    PBout(1)

#define LCD_LED   PBout(2)


  不论是发送数据还是引脚,我都采用了HAL库提供的现成的SPI发送函数:


  很多人在使用STM32的SPI时都用模拟SPI,说STM32的硬件SPI有问题,我暂时没有发现硬件SPI的问题。不过模拟SPI很容易讲清楚原理,按位发送数据,一般写法是这样的:


      for(i=0;i<8;i++)

      {

          if(dat&0x80)

      {

      SDA=1;

  如果你没有使用HAL库,可以把HAL_SPI_Transmit替换掉。

  发送数据与指令的区别就在于LCD_CD引脚的电平状态,两个函数如下:


/**

  * @brief 向LCD屏幕写一个字节的命令

  * @param 命令内容,具体命令可以参照手册

  * @retval None

  */

static void LCD_WriteCommand(uint8_t temp)

{

LCD_CD = 0;

LCD_CS = 0;

HAL_SPI_Transmit(&hspi1,&temp, 1, 0xffff);

LCD_CS = 1;

}

/**

  * @brief 向LCD屏幕写一个字节的数据

  * @param 数据

  * @retval None

  */

static void LCD_WriteData(uint8_t temp)

{

LCD_CD = 1;

LCD_CS = 0;

HAL_SPI_Transmit(&hspi1,&temp, 1, 0xffff);

LCD_CS = 1;

}


  可以看出来,除了LCD_CD引脚用于切换命令,也需要操作LCD_CS来选中屏幕。个人认为操作过多操作引脚会影响效率,而发送数据的函数应用的十分频繁,特别是对于我们选用的十六位屏幕,每个像素都需要十六位的数据,所以,我们经常用到的功能是发送个十六位的数据。代码可以这么写,调用两次发送8位数据的函数:


static void LCD_WD_U16(u16 temp)

{

    LCD_WriteData(temp>>8);

    LCD_WriteData(temp);

}


  由于要操作两次IO,所以我稍微做了一点优化:


/**

  * @brief 向LCD屏幕写两个字节的数据

  * @param 16位的数据

* @note  此函数可以直接调用LCD_WriteData两次,但是IO的操作是多余的

  *        由于每个图片的数据都是16位的,所以此函数很常用,因此稍作优化,减少操作IO

  * @retval None

  */

static void LCD_WD_U16(u16 temp)

{

u8 tempBuf[2];

tempBuf[0] = temp>>8;

tempBuf[1] = temp;

LCD_CD = 1;

LCD_CS = 0;

HAL_SPI_Transmit(&hspi1,tempBuf, 2, 0xffff);

LCD_CS = 1;

}


  同理写了一个函数,用于发送数组。彩图数组动辄都是上万位的,并且是连续发送数据,所以也不需要操作多次IO。


/**

  * @brief 向LCD屏幕写一个数组的长度

  * @param 数组地址与长度

* @note  此函数可以直接调用LCD_WriteData若干次,但是IO的操作是多余的

  *        由于每个图片的数据都是16位的很长的数组,所以此函数很常用,因此稍作优化,减少操作IO,一个图片的数组值操作一次IO

  * @retval None

  */

static void LCD_WD_buf(uint8_t *pData, uint16_t Size)

{

LCD_CD = 1;

LCD_CS = 0;

HAL_SPI_Transmit(&hspi1,pData, Size, 0xffff);

LCD_CS = 1;

}


初始化与定位

  初始化代码太长,就不放了。其实初始化代码是厂家提供的,只不过原来是51程序,我移植了下。

  屏幕的显示需要坐标系,定位操作其实就是发个特定的命令,表示设置x/y轴,在发送特定的数据,表示具体位置。操作思路在《ST7735S手册》中都有体现,例如设置列地址:

  我们找到了设置列地址的命令,再把自己需要的坐标计算出来,假如全屏显示:


/**

  * @brief 设置显示区域为全屏

  * @param None

  * @retval None

  */

static void Full_Screen(void)

{

LCD_WriteCommand(0x2A);     //设置列地址

LCD_WriteData(0x00);

LCD_WriteData(0x02);

LCD_WriteData(0x00);

LCD_WriteData(0x81);


LCD_WriteCommand(0x2B);     //设置行地址

LCD_WriteData(0x00);

LCD_WriteData(0x03);

LCD_WriteData(0x00);

LCD_WriteData(0x82);


LCD_WriteCommand(0x2C);   //写内存

}


  设置某个点的坐标:


/**

  * @brief 设置某个点的坐标

  * @param 点的横纵坐标

* @note  坐标的起点为(2,3)

  * @retval None

  */

static void LCD_SetXY(u16 x,u16 y)

{

LCD_WriteCommand(0x2A);     //设置横轴

LCD_WD_U16(x+2);

LCD_WriteCommand(0x2B);     //设置纵轴

LCD_WD_U16(y+3);

LCD_WriteCommand(0x2C);   //写内存

}


  设置某个区域的坐标:


/**

  * @brief 设置某个显示区域的坐标

  * @param 区域左上角的坐标与右下角的坐标

* @note  坐标的起点为(2,3)

  * @retval None

  */

static void LCD_SetArea(u16 x0, u16 y0,u16 x1, u16 y1)

{

LCD_WriteCommand(0x2A);     //设置横轴

LCD_WD_U16(x0+2);

LCD_WD_U16(x1+2);

LCD_WriteCommand(0x2B);     //设置纵轴

LCD_WD_U16(y0+3);

LCD_WD_U16(y1+3);

LCD_WriteCommand(0x2C);   //写内存

}


颜色的确定

  所谓十六位真彩色,意思就是每个像素的颜色由十六位决定。我们在初始化函数中设置的是这样分配的:

  红色5位,绿色6位,蓝色5位

  很容易想到白色的RGB值就是0xffff,黑色是0x0000。其它还有几个颜色的定义如下:


#define RED    0xf800

#define GREEN  0x07e0

#define BLUE   0x001f

#define YELLOW 0xffe0

#define WHITE  0xffff

#define BLACK  0x0000

#define PURPLE 0xf81f


  一定要注意,高位在前。有很多取色工具可以帮我们算出某个颜色的RGB值。


画点、线、框

  前边已经写了确定点坐标的方法,画点就十分简单了:


/**

  * @brief 画一个点

  * @param 点的横纵坐标,点的颜色

  * @retval None

  */

void LCD_DrawPoint(u16 x,u16 y,u16 color)

{

LCD_SetXY(x,y);

LCD_WD_U16(color);

}


  画线函数理论上来讲就是调用多次画点的函数。如果是横平竖直的线,那十分简单了。如果是斜线呢?那就需要考虑斜率了。由于像素是离散的,所以线上的点,我们只处理所谓的整数部分,代码比较复杂,主要是因为整型变量处理四舍五入的小数部分稍微有点吃力。


/**

  * @brief 画一条线

  * @param 线的起点与终点的横纵坐标,颜色

* @note  可以画斜线

  * @retval None

  */

void LCD_DrawLine(u16 x0, u16 y0,u16 x1, u16 y1,u16 Color)   

{

int dx,             // x轴上的距离

    dy,             // y轴上的距离

    dx2,            // 计算坐标的临时变量

    dy2, 

    x_inc,          // inc表示点的“生长方向” x_inc>1代表从左向右

    y_inc,          // inc表示点的“生长方向” x_inc>1代表从上向下(左上角是坐标原点)

    error,          // 由于坐标点只有整数,是离散的不是连续的,需要变量用于四舍五入的计算

    index;         

LCD_SetXY(x0,y0);

dx = x1-x0;//计算x距离

dy = y1-y0;//计算y距离


if (dx>=0)

{

x_inc = 1;

}

else

{

x_inc = -1;

dx    = -dx;  

if (dy>=0)

{

y_inc = 1;

else

{

y_inc = -1;

dy    = -dy; 


dx2 = dx << 1; //相当于乘以2,如此一来,四舍五入的误差就变成了不到1舍,大于1入

dy2 = dy << 1;


if (dx > dy)//x距离大于y距离,那么对于每个x轴上只有一个点,每个y轴可能只有半个点

{

error = dy2 - dx; 

for (index=0; index <= dx; index++)//要画的点数不会超过x距离

{

LCD_DrawPoint(x0,y0,Color);

if (error >= 0) //如果error>0 说明真实的y的误差>0.5了,实际上应该+1了

{

error-=dx2;

y0+=y_inc;//增加y坐标值

error+=dy2;

x0+=x_inc;//x坐标值每次画点后都递增1

}

else

{

error = dx2 - dy; 

for (index=0; index <= dy; index++)

{

LCD_DrawPoint(x0,y0,Color);

if (error >= 0)

{

error-=dy2;

x0+=x_inc;

error+=dx2;

y0+=y_inc;

}


  由于界面中,我们常常需要划一个方框,或者称之为“按钮”,所以我又封装了一个函数:


/**

  * @brief 画一个方框,或者称之为按钮

* @pa


关键字:STM32  HAL库  SPI驱动  TFTLCD 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
8051单片机的函数发生器的设计