STM32HAL库使用SPI驱动1.44寸TFTLCD

发布时间 2023-07-21 08:55:23作者: SymPny

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

硬件设计

  屏幕选择:使用了一款低成本十六位彩屏,只要十块钱。链接
在这里插入图片描述
  厂家看到文章请联系我打广告费,哈哈。
  虽然用这个屏幕的可能不多,但我了解到,只要其控制芯片是ST7735S,那么程序就应该差不多。不同的地方在于,厂家的封装与玻璃不太一样,玻璃有个伽马值不同,会导致颜色看上去不太一样。

  屏幕的引脚信息
在这里插入图片描述
  我的原理图设计:使用了STM32F405RG芯片的SPI1,屏幕没有MISO。
在这里插入图片描述
在这里插入图片描述

cubeMX中SPI的配置大致如下:
在这里插入图片描述在这里插入图片描述
  其实SPI的速度我选的是21MBITS/s,可能再快一点也行,没有测试。
  其它引脚比较散,都是当做IO来用,CubeMX中的配置过程就不说了,汇总如下

名称引脚功能
LCD_RSTPC5屏幕复位
LCD_CDPB00数据1指令
SPI_MOSIPB5数据线
SPI_CLKPB3时钟线
LCD_CSPB1片选,低电平有效
LCD_LEDPB2背光,高电平有效

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

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

#define LCD_RST   PCout(5)
#define LCD_CD    PBout(0)
#define LCD_CS    PBout(1)
#define LCD_LED   PBout(2)
  • 1
  • 2
  • 3
  • 4

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

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  • 1

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

      for(i=0;i<8;i++)
      {
          if(dat&0x80)
      {
      SDA=1;
  • 1
  • 2
  • 3
  • 4
  • 5

  如果你没有使用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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

static void LCD_WD_U16(u16 temp)
{
    LCD_WriteData(temp>>8);
    LCD_WriteData(temp);
}
  • 1
  • 2
  • 3
  • 4
  • 5

  由于要操作两次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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  同理写了一个函数,用于发送数组。彩图数组动辄都是上万位的,并且是连续发送数据,所以也不需要操作多次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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

初始化与定位

  初始化代码太长,就不放了。其实初始化代码是厂家提供的,只不过原来是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);
<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2B</span><span class="token punctuation">)</span><span class="token punctuation">;</span>	    <span class="token comment">//设置行地址</span>
<span class="token function">LCD_WriteData</span><span class="token punctuation">(</span><span class="token number">0x00</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">LCD_WriteData</span><span class="token punctuation">(</span><span class="token number">0x03</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">LCD_WriteData</span><span class="token punctuation">(</span><span class="token number">0x00</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">LCD_WriteData</span><span class="token punctuation">(</span><span class="token number">0x82</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2C</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">//写内存</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

  设置某个点的坐标:

/**
  * @brief 设置某个点的坐标
  * @param 点的横纵坐标
	* @note  坐标的起点为(2,3)
  * @retval None
  */	
static void LCD_SetXY(u16 x,u16 y)
{
	LCD_WriteCommand(0x2A);	    //设置横轴
	LCD_WD_U16(x+2);
<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2B</span><span class="token punctuation">)</span><span class="token punctuation">;</span>	    <span class="token comment">//设置纵轴</span>
<span class="token function">LCD_WD_U16</span><span class="token punctuation">(</span>y<span class="token operator">+</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2C</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">//写内存</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  设置某个区域的坐标:

/**
  * @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);
<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2B</span><span class="token punctuation">)</span><span class="token punctuation">;</span>	    <span class="token comment">//设置纵轴</span>
<span class="token function">LCD_WD_U16</span><span class="token punctuation">(</span>y0<span class="token operator">+</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">LCD_WD_U16</span><span class="token punctuation">(</span>y1<span class="token operator">+</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">LCD_WriteCommand</span><span class="token punctuation">(</span><span class="token number">0x2C</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">//写内存</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

颜色的确定

  所谓十六位真彩色,意思就是每个像素的颜色由十六位决定。我们在初始化函数中设置的是这样分配的:
在这里插入图片描述
  红色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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

画点、线、框

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

/**
  * @brief 画一个点
  * @param 点的横纵坐标,点的颜色
  * @retval None
  */	
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
	LCD_SetXY(x,y);
	LCD_WD_U16(color);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

/**
  * @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距离
<span class="token keyword">if</span> <span class="token punctuation">(</span>dx<span class="token operator">&gt;=</span><span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
	x_inc <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">else</span>
<span class="token punctuation">{<!-- --></span>
	x_inc <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
	dx    <span class="token operator">=</span> <span class="token operator">-</span>dx<span class="token punctuation">;</span>  
<span class="token punctuation">}</span> 

<span class="token keyword">if</span> <span class="token punctuation">(</span>dy<span class="token operator">&gt;=</span><span class="token number">0</span><span class="token punctuation">)</span>
<span class="token punctuation">{<!-- --></span>
	y_inc <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> 
<span class="token keyword">else</span>
<span class="token punctuation">{<!-- --></span>
	y_inc <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
	dy    <span class="token operator">=</span> <span class="token operator">-</span>dy<span class="token punctuation">;</span> 
<span class="token punctuation">}</span> 

dx2 <span class="token operator">=</span> dx <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">//相当于乘以2,如此一来,四舍五入的误差就变成了不到1舍,大于1入</span>
dy2 <span class="token operator">=</span> dy <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">;</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span>dx <span class="token operator">&gt;</span> dy<span class="token punctuation">)</span><span class="token comment">//x距离大于y距离,那么对于每个x轴上只有一个点,每个y轴可能只有半个点</span>
<span class="token punctuation">{<!-- --></span>		
	error <span class="token operator">=</span> dy2 <span class="token operator">-</span> dx<span class="token punctuation">;</span> 
	<span class="token keyword">for</span> <span class="token punctuation">(</span>index<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> index <span class="token operator">&lt;=</span> dx<span class="token punctuation">;</span> index<span class="token operator">++</span><span class="token punctuation">)</span><span class="token comment">//要画的点数不会超过x距离</span>
	<span class="token punctuation">{<!-- --></span>
		<span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x0<span class="token punctuation">,</span>y0<span class="token punctuation">,</span>Color<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token operator">&gt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">//如果error&gt;0 说明真实的y的误差&gt;0.5了,实际上应该+1了</span>
		<span class="token punctuation">{<!-- --></span>
			error<span class="token operator">-</span><span class="token operator">=</span>dx2<span class="token punctuation">;</span>
			y0<span class="token operator">+</span><span class="token operator">=</span>y_inc<span class="token punctuation">;</span><span class="token comment">//增加y坐标值</span>
		<span class="token punctuation">}</span> 
		error<span class="token operator">+</span><span class="token operator">=</span>dy2<span class="token punctuation">;</span>
		x0<span class="token operator">+</span><span class="token operator">=</span>x_inc<span class="token punctuation">;</span><span class="token comment">//x坐标值每次画点后都递增1</span>
	<span class="token punctuation">}</span>
<span class="token punctuation">}</span> 
<span class="token keyword">else</span>
<span class="token punctuation">{<!-- --></span>
	error <span class="token operator">=</span> dx2 <span class="token operator">-</span> dy<span class="token punctuation">;</span> 
	<span class="token keyword">for</span> <span class="token punctuation">(</span>index<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> index <span class="token operator">&lt;=</span> dy<span class="token punctuation">;</span> index<span class="token operator">++</span><span class="token punctuation">)</span>
	<span class="token punctuation">{<!-- --></span>
		<span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x0<span class="token punctuation">,</span>y0<span class="token punctuation">,</span>Color<span class="token punctuation">)</span><span class="token punctuation">;</span>
		<span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token operator">&gt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
		<span class="token punctuation">{<!-- --></span>
			error<span class="token operator">-</span><span class="token operator">=</span>dy2<span class="token punctuation">;</span>
			x0<span class="token operator">+</span><span class="token operator">=</span>x_inc<span class="token punctuation">;</span>
		<span class="token punctuation">}</span> 
		error<span class="token operator">+</span><span class="token operator">=</span>dx2<span class="token punctuation">;</span>
		y0<span class="token operator">+</span><span class="token operator">=</span>y_inc<span class="token punctuation">;</span>
	<span class="token punctuation">}</span> 
<span class="token punctuation">}</span> 

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

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

/**
  * @brief 画一个方框,或者称之为按钮
  * @param 方框左上角和右下角的点的坐标,颜色
	* @note  右边和下边的线自带加粗效果  如需花在屏幕最边缘无加粗效果
  * @retval None
  */	
void LCD_DrawBTN(u16 x1,u16 y1,u16 x2,u16 y2,u16 Color)
{
	LCD_DrawLine(x1,  y1,  x2,y1, Color); //H
	LCD_DrawLine(x1,  y1,  x1,y2, Color); //V
<span class="token function">LCD_DrawLine</span><span class="token punctuation">(</span>x1<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">,</span>y2<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span>x2<span class="token punctuation">,</span>y2<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> Color<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//H 加粗 多画一条线</span>
<span class="token function">LCD_DrawLine</span><span class="token punctuation">(</span>x1<span class="token punctuation">,</span>  y2<span class="token punctuation">,</span>  x2<span class="token punctuation">,</span>y2<span class="token punctuation">,</span> Color<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//H</span>
<span class="token function">LCD_DrawLine</span><span class="token punctuation">(</span>x2<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span>y1<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">,</span>x2<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span>y2<span class="token punctuation">,</span> Color<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//V</span>

LCD_DrawLine(x2 ,y1 ,x2,y2, Color); //V
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

显示纯色背景与图片

  我们已经做到了全屏显示,那么纯色背景的显示就很简单了:

/**
  * @brief 全屏显示纯色图片,可用作清屏
  * @param 颜色
  * @retval None
  */	
void LCD_BG_Color(u16 color)
{
	int i=16384;//128*128
 	Full_Screen();
	while(i-->0)//不可使用无符号数据类型
	LCD_WD_U16(color);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  一共有16384个像素,那就调用16384次画点的程序。只不过这里我稍作了一点优化,不用再管点的坐标了,因为已经设置了显示区域是全屏。
  我专门写了一个函数,显示各个纯色图片,可以看出有没有那个像素点颜色显示的不全:

/**
  * @brief LCD屏幕测试,依次显示纯色照片,可以看出屏幕的每一个像素点是否正常
  * @param 每种颜色持续的时间,单位ms 
  * @retval None
  */
void LCD_Test(u16 delay_Time)
{
	LCD_BG_Color(BLACK);
	HAL_Delay(delay_Time);
	LCD_BG_Color(RED);
	HAL_Delay(delay_Time);
	LCD_BG_Color(GREEN);
	HAL_Delay(delay_Time);
	LCD_BG_Color(BLUE);
	HAL_Delay(delay_Time);
	LCD_BG_Color(WHITE);
	HAL_Delay(delay_Time);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  显示全屏幕的背景图片跟纯色背景思路类似,把数据的来源改为数组就好:

/**
  * @brief 在LCD屏幕中显示一个全屏的背景图片
  * @param 无
	* @note  只适用于128*128的屏幕,为了效率把长度直接计算了出来
  *        由于图片数组要放在ROM,所以用了const,因此要做强制类型转化(u8 *)
	*        如果是双工,则收发需要同一个数组,数组不能放在ROM
  * @retval None
  */	
void LCD_BG_Image(void)
{
	uint32_t i=32768; //Z144_HEIGHT*Z144_WIDTH*2;
 	Full_Screen();
	LCD_WD_buf((u8 *)LCD_MOTOR,i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  接下来就是指定区域显示图片了,函数也很简单:

/**
  * @brief 按照指定的坐标显示图片
  * @param 图片左上角的坐标与右下角的坐标,图片地址
	* @note  图片大小与坐标必须对应
  *        由于图片数组要放在ROM,所以用了const,因此要做强制类型转化(u8 *)
	*        坐标范围[0,127]
  * @retval None
  */	
void LCD_Show_Image(u16 x0, u16 y0,u16 x1,u16 y1,const unsigned char *p)
{
	int i = (x1-x0)*(y1-y0)*2;
	LCD_SetArea(x0,y0,x1,y1);
	LCD_WD_buf((u8 *)p,i);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  那么图片数组从哪里来?
  取模得到。我去网上随便找了一张彩图,然后把尺寸编辑为100* 100像素。取模软件如下设置:
在这里插入图片描述
  然后可以得到图片转化为的数组。

显示文字与字符

  中文字符都采用GBK(或者说GB2312)编码,英文字符采用ASCII码。显示文字其实与显示图片的原理是一样一样的,思路在我另一篇博客中介绍过。
  先说汉字的取模设置:
在这里插入图片描述
  我使用了结构体储存汉字,结构体的每个元素都包含文字(或者称之为索引)和它对应的编码。因此得到的数组要稍加修改,增加双引号与汉字,例如北京:

//修改前
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x04,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x42,0x1C,0x42,0xE4,0x42,0x44,0x3E,0x04,0x00,/*"北",4*/
0x02,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,
0x10,0x10,0x1F,0xF0,0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x45,0x04,0x02,0x00,/*"京",5*/
//修改后
"北",0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x04,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,
0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x42,0x1C,0x42,0xE4,0x42,0x44,0x3E,0x04,0x00,/*"北",4*/
"京",0x02,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,
0x10,0x10,0x1F,0xF0,0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x45,0x04,0x02,0x00,/*"京",5*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  显示的字符要先判断是中文的还是英文的。英文字符的值小于128,显示函数如下:

/**
  * @brief 输出16*16的汉字或8*16的字符,函数可以自动识别是中文字符还是ASCII
  * @param 第一个字符的坐标,汉字颜色,背景颜色,需要显示的字符串。背景颜色为0表示不画背景
  * @retval None
  */
void LCD_DrawFont_GBK16(u16 x, u16 y, u16 fc, u16 bc, u8 *s)
{
	unsigned char i,j;
	unsigned short k,x0;
	x0=x;
	while(*s) 
	{	
		if((*s) < 128)        //如果是ASCII码,那么编号小于128
		{
			k=*s;
			if (k==13)        //回车
			{
				x=x0;//记录本行第一个字符的位置
				y+=16;
			}
			else 
			{
				if (k>32) k-=32; else k=0;      //ASCII前32个都不是字符,除了回车,对显示没有影响,所以字库不储存
			  for(i=0;i<16;i++)
				{
					for(j=0;j<8;j++) 
						{
							if(ASC16[k*16+i]&(0x80>>j))	//如有数据,画字体  从高位到低位取出字符
							{
								LCD_DrawPoint(x+j,y+i,fc);
							}
							else //如果没有数据,画背景  如果背景颜色为0  那么不画背景
							{
								if ((bc!=0)&&(fc!=bc)) LCD_DrawPoint(x+j,y+i,bc);
							}
						}
					}
				x+=8;//字符横坐标8个像素,x+8代表下一个字符的坐标
			}
			s++;//画完此字符与背景以后,取出下一个字符
		}
	<span class="token keyword">else</span> <span class="token comment">//GBK编码  说明是中文字符</span>
	<span class="token punctuation">{<!-- --></span>
		<span class="token keyword">for</span> <span class="token punctuation">(</span>k<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>k<span class="token operator">&lt;</span>HZ16_num<span class="token punctuation">;</span>k<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token comment">//遍历中文字库</span>
		<span class="token punctuation">{<!-- --></span>
		  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>HZ16<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">.</span>Index<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">==</span><span class="token operator">*</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">&amp;&amp;</span><span class="token punctuation">(</span>HZ16<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">.</span>Index<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">==</span><span class="token operator">*</span><span class="token punctuation">(</span>s<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token comment">//可以找到索引,说明字库中有此汉字。一个汉字占两个字节                      </span>
		  <span class="token punctuation">{<!-- --></span>                        
			  <span class="token keyword">for</span><span class="token punctuation">(</span>i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator">&lt;</span><span class="token number">16</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span>
			    <span class="token punctuation">{<!-- --></span>
						<span class="token keyword">for</span><span class="token punctuation">(</span>j<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>j<span class="token operator">&lt;</span><span class="token number">8</span><span class="token punctuation">;</span>j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token comment">//先画左半边</span>
						<span class="token punctuation">{<!-- --></span>
					    <span class="token keyword">if</span><span class="token punctuation">(</span>HZ16<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">.</span>Msk<span class="token punctuation">[</span>i<span class="token operator">*</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token operator">&amp;</span><span class="token punctuation">(</span><span class="token number">0x80</span><span class="token operator">&gt;&gt;</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span>	<span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x<span class="token operator">+</span>j<span class="token punctuation">,</span>y<span class="token operator">+</span>i<span class="token punctuation">,</span>fc<span class="token punctuation">)</span><span class="token punctuation">;</span>
							<span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span>
								<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bc<span class="token operator">!=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">&amp;&amp;</span><span class="token punctuation">(</span>fc<span class="token operator">!=</span>bc<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x<span class="token operator">+</span>j<span class="token punctuation">,</span>y<span class="token operator">+</span>i<span class="token punctuation">,</span>bc<span class="token punctuation">)</span><span class="token punctuation">;</span>
							<span class="token punctuation">}</span>
						<span class="token punctuation">}</span>
						<span class="token keyword">for</span><span class="token punctuation">(</span>j<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>j<span class="token operator">&lt;</span><span class="token number">8</span><span class="token punctuation">;</span>j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token comment">//再画右半边</span>
						<span class="token punctuation">{<!-- --></span>
					    <span class="token keyword">if</span><span class="token punctuation">(</span>HZ16<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">.</span>Msk<span class="token punctuation">[</span>i<span class="token operator">*</span><span class="token number">2</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">&amp;</span><span class="token punctuation">(</span><span class="token number">0x80</span><span class="token operator">&gt;&gt;</span>j<span class="token punctuation">)</span><span class="token punctuation">)</span>	<span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x<span class="token operator">+</span>j<span class="token operator">+</span><span class="token number">8</span><span class="token punctuation">,</span>y<span class="token operator">+</span>i<span class="token punctuation">,</span>fc<span class="token punctuation">)</span><span class="token punctuation">;</span>
							<span class="token keyword">else</span> 
							<span class="token punctuation">{<!-- --></span>
								<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>bc<span class="token operator">!=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">&amp;&amp;</span><span class="token punctuation">(</span>fc<span class="token operator">!=</span>bc<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">LCD_DrawPoint</span><span class="token punctuation">(</span>x<span class="token operator">+</span>j<span class="token operator">+</span><span class="token number">8</span><span class="token punctuation">,</span>y<span class="token operator">+</span>i<span class="token punctuation">,</span>bc<span class="token punctuation">)</span><span class="token punctuation">;</span>
							<span class="token punctuation">}</span>
						<span class="token punctuation">}</span>
			    <span class="token punctuation">}</span>
				<span class="token punctuation">}</span>
		  <span class="token punctuation">}</span>
		s<span class="token operator">+</span><span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">;</span>x<span class="token operator">+</span><span class="token operator">=</span><span class="token number">16</span><span class="token punctuation">;</span>
	<span class="token punctuation">}</span> 
<span class="token punctuation">}</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

  由于屏幕只有128* 128,一个汉字占用16,那么一行只能显示8个汉字,太少。16的字太大了,所以我又写了显示12像素字符的函数,思路一样,代码就不放了。
  需要注意的是,汉字是需要字库的。ASCII码不多,全部取模也就是100多个(有效的还不到100个),但是汉字可就多了,比如说GBK编码共收录了21886个汉字和图片,那么按照我们设计的结构体来储存(汉字占2个字节,编码占32个字节,其实还可以优化,不用储存汉字),共需要34* 21886=744124byte = 744KB。理论上来讲,STM32F405RG的flash空间是1024KB,能放下这个字库,只不过程序的编译与下载就会变得很缓慢,十分不利于调试,几乎没人这么干。
在这里插入图片描述
  因此,目前,对于汉字,我们还是用到哪个汉字,对哪个汉字取模。
  还需要注意一个问题,工程内文件的编码格式要一致,不能这个文件用UTF-8,那个是GB2312,因为我们的函数查找汉字要依赖于比较编码值,同一个汉字不同编码格式的编码值不一样。

显示效果

  我在主函数中调用以上辛辛苦苦编写的函数,省略无关的代码以后,如下:

  /* USER CODE BEGIN 2 */
	ST7735S_CPT144_Initial();
	LCD_Test(500);		
	LCD_DrawBTN(0,98,126,126,YELLOW);	
	LCD_Show_Image(0,0,99,99,gImage_git100);
	LCD_DrawLine(100,0,127,100,BLUE);
	LCD_DrawLine(127,0,100,100,PURPLE);
	LCD_DrawFont_GBK16(0,99,BLACK,WHITE,"屏幕驱动");
	LCD_DrawFont_GBK16(64,99,BLACK,WHITE,"geekYatao");
	LCD_DrawFont_GBK12(0,115,RED,GREEN,"智联有道");
	LCD_DrawFont_GBK12(48,115,RED,GREEN,"STM32F4+HAL");
	//LCD_BG_Image();

/* USER CODE END 2 */

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  刷屏速度尚可,但还是勉强能看出来刷屏幕的过程。最终显示效果不错。
在这里插入图片描述

还可以优化

  调用SPI刷屏是要占用总线的,可以使用DMA+SPI,在定时器内刷屏。如果达到每40ms刷屏一次,那么就能达到25帧,看上去就不会太卡顿。
  使用DMA发送大量数据有优势,因此可以用大数组把每一个点的信息都储存下来。为了方便发送,这个数组类型可以设置为unsigned char,共128* 128* 2=32768个元素。
  另外关于字库,既然单片机的flash放的下,那么无需外接硬件就能实现全字库的功能:把编译过的二进制字库,放到单片机flash中靠后的指定地址(避免被程序覆盖),然后需要字库的时候,让程序来读取指定的地址就好。
本文用到的部分代码在这里代码