关于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)
- 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="toke


最低0.47元/天 解锁文章
5095

被折叠的 条评论
为什么被折叠?



