SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。

和IIC一样,是单片机开发中最常用的通信方式之一。对比IIC,其特点表现在:

1.通信速率快,以STM32F103C8T6为例,其SPI通信速率可达18Mbps,即2.25MB/s,而IIC高速模式也才3.4Mbps;

2.全双工通信,SPI有两根数据线,一根主出从入,一根主入从出,在同一时刻既可以发送数据,也可以接受数据,而IIC是半双工,发送、接受数据不能同时进行;

主要应用场景有SD卡数据读写、TFT彩屏控制等。

本文将详细讲解利用STM32的硬件SPI接口来驱动IPS彩屏显示图片(显示文字就不详细介绍了,与OLED显示文字类似)。

先来看看SPI物理接口定义:

MISO– Master Input Slave Output主设备数据输入,从设备数据输出;

MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;

SCLK – Serial Clock,时钟信号,由主设备产生;

CS – Chip Select,从设备使能信号,由主设备控制,每个从设备对应主设备的一个CS接口,若要与某个从设备进行通讯,则通过CS使能对应的从设备。在STM32中为SPI_NSS。

在我关于IIC软件模拟的文章中详细解析了IIC的软件模拟实现方法,同样的,SPI也能用软件模拟实现,只要按照其时序控制GPIO电平高低。这里暂不使用软件模拟实现,直接配置STM32内部的硬件SPI,再调用库函数。以STM32F103C8T6为例,该型号共有两个SPI。

这里我使用的屏幕是1.3寸的IPS彩屏,分辨率240*240,最高支持16位真彩(RGB565),驱动芯片ST7789。

16位真彩即每个像素点有65535(2^16)种颜色。

三原色原理中说道:大多数的颜色可以通过红、绿、蓝三色按照不同的比例合成产生。同样,绝大多数单色光也可以分解成红、绿、蓝三种色光。

IPS屏幕上的每个像素点都由一组红绿蓝遮光片组成,白色的背光透过遮光片就呈现出红绿蓝三种颜色。改变对应遮光片的角度就能改变该颜色的强度,不同强度的红绿蓝三色混合在一起就组成了屏幕上显示的65535种颜色。

在这16位中,前面5位表示红色的强度,中间6位表示绿色的强度,后面5位表示蓝色的强度。也就是红绿蓝分别占5、6、5位,这就是RGB565得名原因。所以,红绿蓝三色的强度分别有32、64、32级。

一个像素点需要占用16个bit(即2Byte)的数据,整个屏幕就需要240x240x2=115200Byte(即115.2KB)数据。

-------针脚定义:

该屏幕有7个针脚,接口定义及与单片机连接方式如下:

GND:供电电源地

VCC:  供电电源正(3-5V)

SCL:SPI时钟线。接单片机SPI_SCK

SDA:SPI数据线。对该屏幕来说。只有数据输出而不需要输出,即主出从入,所以接单片机MOSI。单片机的MISO悬空不用。

RES:屏幕复位。单片机与屏幕进行通讯前,需将屏幕复位一次,即将该引脚置低短暂时间后再置高(习惯100ms)。

DC:数据/命令选择端口。与我之前介绍的OLED屏幕不同,OLED是通过向不同的寄存器里写数据来区分是命令还是显示SRAM数据,而该屏幕是通过该引脚电平状态来区分当前收到的数据是命令还是显示数据。DC端口为低时,写入的是命令;为高时,写入的是数据。

BLK:屏幕背光。与像素点自发光的OLED屏幕不同,IPS屏幕是需要有背光才能显示图像的。该屏幕默认是开启背光的,当BLK端口被置低,背光关闭。这里可以用一个GPIO来控制,或者直接悬空,或者接高电平。

具体与STM32的连接方式如下:

因为这里只有TFT屏幕一个从站,所以SPI_NSS不接。

------------------------------------------

接下来,一边看程序,一边讲解

1.头文件及GPIO置复位定义

将用到的外设头文件包含到.c文件中:GPIO、RCC、SPI

定义PA1、PA2、PA3的高低电平。这与51单片机不一样,51单片机可以使用sbit将变量映射到GPIO口,但是STM32没有sbit。

#include<stm32f10x.h>
#include<stm32f10x_gpio.h>
#include<stm32f10x_rcc.h>
#include<stm32f10x_spi.h>#define SPI_DC_H() GPIO_SetBits(GPIOA,GPIO_Pin_2)            //数据/命令选择
#define SPI_DC_L() GPIO_ResetBits(GPIOA,GPIO_Pin_2)         #define LCD_RES_H() GPIO_SetBits(GPIOA,GPIO_Pin_3)              //LCD复位(PA3)
#define LCD_RES_L() GPIO_ResetBits(GPIOA,GPIO_Pin_3)#define LCD_BLK_H() GPIO_SetBits(GPIOA,GPIO_Pin_1)              //LCD背光(PA1)
#define LCD_BLK_L() GPIO_ResetBits(GPIOA,GPIO_Pin_1)

2.将图片转成十六进制数据

要在OLED显示字符,是通过让屏幕像素点按照字模点亮或者灭来实现。显示图片也是同理,对于彩屏,只是每个像素点不止0或者1两种状态,它有65535种状态,只要让每个像素点按照我想要的方式处于各自的方式即可得到一张图片。我们可以把它放在一个数组里,里面的元素就是每个像素点的状态值。然后通过SPI按顺序将数组的元素传输至TFT屏幕的显示缓存寄存器,就能在屏幕显示想要的图片了。

这里需要用到的小工具是Img2Lcd。这是一个可以将图片转成数组的工具。可在网上下载该工具。

现在来演示如何将下面这张图片转成C程序数组。由于我这里使用的240x240的正方形屏幕,所以为了能填满屏屏幕,特意将该图片裁切成了正方形。

需要注意,Img2Lcd只能转换jpg格式的图片,如果是其他格式的图片,需要先转换成jpg格式。

然后在小工具中打开该文件

打开之后还需要设置相关参数。输出灰度选择16位真彩色。

最大宽度与与高度设置需要注意,我这里使用的STM32F103C8T6,其Flash只有64KB,所以该数组的大小绝不能超过64KB,否则会因为存储空间不足而通不过编译。这里我留一定的Flash余量给其他变量及程序占用,给数组分配60KB的空间。而该屏幕一个像素点需要2B的空间,也就是一共能存储60K÷2=30000个像素点的状态。再对30000开根号,约等于173。所以这里将最大宽度与高度设置成173x173。(如果使用的单片机是128K或者以上的flash,则可以全屏显示,设置成240x240)

取消勾选“包含图像头数据”,勾选“高位在前”(这是因为该屏幕是先发送高位再发送低位,如果不勾选,则会出现反色效果)。最后,还可以稍微将对比度拉高一点(因为IPS屏色彩不够鲜艳,最后的画面会有点暗淡)。

设置完成之后,点击“最大宽度和高度”右边的小按钮,即可生成预览图如下。

然后点击“保存”,选择路径后确认即可得到一个.c的文件,这里我们直接用记事本打开。可以发现里面是一个数组,元素个数为59859,正好等于173x173x2。

这里每个元素是一个2位十六进制的数,每两个元素代表一个像素点。

然后,我们将该数组复制粘贴到程序中(如下)。数组很长,可将其折叠。

这样,这步就算完成了。

3.SPI初始化

因为使用的是硬件SPI1,所以需要对SPI1进行初始化。

首先是GPIO设置,SPI1占用PA4-PA7共四个IO,这四个IO设置成复用推挽输出。另外还使用了PA1,PA2,PA3三个IO口分别做为背光、数据/命令选择、复位的控制端口。所以这里也需要给这三个IO初始化,设为推挽输出即可。然后开启对应的时钟。
然后是SPI的配置,与GPIO一样,在SPI的外设库中也有一个结构体,里面的成员是SPI的各项参数。在外设库中可以查看该结构体具体内容。

下面的代码可供参考

void SPI_UserInit(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;    //PA4为SPI_NSS,PA5为SPI_SCK,PA6为SPI_MISO,PA7为SPI_MOSI,对SPI这4个端口进行初始化GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;      //PA1为BLK背光,PA2位DC选择,PA3为屏幕复位GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;           GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           //推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);SPI_InitTypeDef SPI_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);      //使能时钟SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//设置为双向双线全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;         //设置为SPI主站。控制屏幕,单片机需做主站SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;    //设置数据大小为8位,即每次发送8位数据SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;        //设置串行时钟的稳态为时钟高SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;        //设置位捕捉的时钟活动沿为第一个上升沿SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;          //设置NSS为软件控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//设置波特率分频(该值越大,波特率越慢)SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //设置先发送高位SPI_InitStructure.SPI_CRCPolynomial = 7;          //设置CRC校验                                               SPI_Init(SPI1, &SPI_InitStructure);             //初始化SPI_Cmd(SPI1, ENABLE);     //使能SPI1
}

4.SPI写数据与写命令

与OLED屏幕一样,需要分别声明写数据和写命令的子函数。

前面讲过,该屏幕是通过DC端口来区分命令与数据,所以写数据与写命令的区别就是DC端口分别为高电平与低电平。由于写数据还分写8位数据与写16位数据,所以还得分两个写数据的函数。

需要注意,SPI本身是支持16数据帧的(通过 SPI_InitStructure.SPI_DataSize 设置),但是为了方便发送8位的数据帧,所以在SPI初始化中把DataSize设置成了8。而发送16位数据是通过先发送低8位,然后数据右移8位,再发送一次8位数据实现的。

void SPI_WriteCmd(u8 Data)   //写命令
{SPI_DC_L();SPI_I2S_SendData(SPI1,Data);
}void LCD_WriteData8(u8 Data)   //写8位数据
{SPI_DC_H();SPI_I2S_SendData(SPI1,Data);
}void LCD_WriteData16(u16 Data) //写16位数据
{SPI_DC_H();SPI_I2S_SendData(SPI1,(Data>>8) ) ;   //Date右移8位SPI_I2S_SendData(SPI1,Data);
}

5.屏幕初始化

与OLED屏幕一样,该IPS上电之后需要设置参数进行初始化。

具体需要设置的参数可查阅ST7789使用书册。可参考我以下参数。

void LCD_Init(void)
{u16 x;LCD_RES_L();Delay_ms(50);LCD_RES_H();Delay_ms(50);                //复位屏幕SPI_WriteCmd(0x3A);                      //设置颜色格式为16位真彩LCD_WriteData8(0x05);             //03 4K;05 65K;06 262KSPI_WriteCmd(0x36);         //设置屏幕方向为从上到下,从左到右LCD_WriteData8(0x00);//----------- ST7789S Frame rate setting ---------//SPI_WriteCmd(0xB2);               LCD_WriteData8(0x0C);LCD_WriteData8(0x0C);LCD_WriteData8(0x00);LCD_WriteData8(0x33);LCD_WriteData8(0x33);SPI_WriteCmd(0xB7); LCD_WriteData8(0x35);//----------- ST7789S Power setting ---------//SPI_WriteCmd(0xBB);LCD_WriteData8(0x19);SPI_WriteCmd(0xC0);LCD_WriteData8(0x2C);SPI_WriteCmd(0xC2);LCD_WriteData8(0x01);SPI_WriteCmd(0xC3);LCD_WriteData8(0x12);   SPI_WriteCmd(0xC4);LCD_WriteData8(0x20);  SPI_WriteCmd(0xC6); LCD_WriteData8(0x0F);    SPI_WriteCmd(0xD0); LCD_WriteData8(0xA4);LCD_WriteData8(0xA1);//----------- Posistive Voltage Gamma Control ---------//SPI_WriteCmd(0xE0);LCD_WriteData8(0xD0);LCD_WriteData8(0x04);LCD_WriteData8(0x0D);LCD_WriteData8(0x11);LCD_WriteData8(0x13);LCD_WriteData8(0x2B);LCD_WriteData8(0x3F);LCD_WriteData8(0x54);LCD_WriteData8(0x4C);LCD_WriteData8(0x18);LCD_WriteData8(0x0D);LCD_WriteData8(0x0B);LCD_WriteData8(0x1F);LCD_WriteData8(0x23);SPI_WriteCmd(0xE1);LCD_WriteData8(0xD0);LCD_WriteData8(0x04);LCD_WriteData8(0x0C);LCD_WriteData8(0x11);LCD_WriteData8(0x13);LCD_WriteData8(0x2C);LCD_WriteData8(0x3F);LCD_WriteData8(0x44);LCD_WriteData8(0x51);LCD_WriteData8(0x2F);LCD_WriteData8(0x1F);LCD_WriteData8(0x1F);LCD_WriteData8(0x20);LCD_WriteData8(0x23);//----------- Setting ---------//SPI_WriteCmd(0x21); SPI_WriteCmd(0x11)SPI_WriteCmd(0x29);LCD_SetRegion(0,0,239,239);SPI_DC_H();for(x=0;x<57600;x++)            //清屏{LCD_WriteData16(0xffff);}LCD_BLK_H();
}

在函数后面,我这里加入了一个循环,这是用来清屏的。因为屏幕内部显示SRAM的数据是掉电就会丢失的。每次上电后并不会显示上次断电前的图像,而是显示像下面所示的雪花屏。

清屏的原理就是重新给显示SRAM写入一次数据,用某个状态(可以理解为颜色)填充所有像素点。这里我填充的是0xffff,0xffff在RGB565中是纯白色,所以最终效果是白屏。如果填充0x0000 ,则填充的是黑色,最终效果是黑屏。当然也可以根据需要填充任何自己想要的颜色。

循环一次填充一个像素点,全屏共57600个像素点,所以循环57600次。

6.设置显示区域

显示区域是一个长方形区域,所以需要设置起点与终点。

0x2a:设置(x0,x1)

0x2b:设置(y0,y1)

void LCD_SetRegion(u16 x0, u16 y0, u16 x1, u16 y1)//设置显示区域(x0,y0,x1,y1)
{SPI_WriteCmd(0x2a);LCD_WriteData16(x0);LCD_WriteData16(x1);SPI_WriteCmd(0x2b);LCD_WriteData16(y0);LCD_WriteData16(y1);SPI_WriteCmd(0x2c);          //下面发送的是数据
}

7.SPI传输数组

STM32中SPI传输数据的库函数为        SPI_I2S_SendData(SPIx,Deta);

图片数组里共有59859个元素,所以需要发送59859个数据。发送数据前,需要将DC端口置高。

void image(void){u16 x;SPI_DC_H();        //DC置高,发送数据for(x=0;x<59860;x++)    //循环填充像素{SPI_I2S_SendData(SPI1,gImage_girl[x]);    //将图片数组的元素通过SPI发送给IPS屏}}

8.主函数

初始化GPIO、SPI、ISP屏幕,设置显示区域坐标,发送数据。

为了能让图片在屏幕正中间显示,我们需要把显示区域设置在正中间。
这里进行简单的计算,240-173=67,67/2≈33,所以可以把坐标(x0,y0)设为(33,33),
图片分辨率为173 x173,所以x1,y1的坐标应为(33+173-1,33+173-1),即205,205。这里之所以需要减1,类似于小学数学的“种树问题”。所以限定区域的函数应为(33,33,205,205)

int main(void)
{   SPI_UserInit();LCD_Init();LCD_SetRegion(33,33,205,205);image();
}

最后显示效果如下:

如果是Flash大于115KB的单片机,则可以全屏显示(相机拍摄原因,导致图片看起来有色差):

如果Flash较小,通过分多次也能显示全屏。

通过以上步骤,就能在屏幕上显示各种图片以及播放视频了。

《bad apple》演示

显示文字

本文章只是讲解并演示如何利用SPI通信在IPS屏上显示图片。不管何种单片机其Flash都是很有限的,实际运用中并不会将图片直接存在内部Flash中。最常用的方法是将图片存在SD卡,单片机再读取SD卡的数据。关于SD卡的使用,后面的文章再做讲解。

对于其他进阶用法如制作动画效果、播放视频以后再做介绍。

STM32单片机初学6-SPI通信驱动IPS彩屏相关推荐

  1. Matlab 与stm32单片机之间的串口通信

    Matlab 与stm32单片机之间的串口通信 在我们用stm32做信号处理时,我时常需要用到Matlab对我们采集的数据进行分析,拟合.按照传统的方法,我们一般都会先将数据通过串口助手打印出来,再导 ...

  2. 【STM32】基于STM32CubeIDE SPI+DMA驱动WS2812

    [STM32]基于STM32CubeIDE SPI+DMA驱动WS2812 ✨申明:本文章仅发表在CSDN网站,任何其他网见此内容均为盗链和爬取,请多多尊重和支持原创!

  3. 【GD32F427开发板试用】硬件SPI通信驱动CH376芯片,用单片机实现U盘数据下载

    本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站.作者:周文杰 SPI通信作为单片机多种基础数据传输模式中的一种,驱动外部芯片CH376实现数据 ...

  4. STM32单片机初学8-SPI flash(W25Q128)数据读写

    当使用单片机进行项目开发,涉及大量数据需要储存时(例如使用了屏幕作为显示设备,常常需要存储图片.动画等数据),单靠单片机内部的Flash往往是不够用的. 如STM32F103系列,内部Flash最多只 ...

  5. STM32使用SPI通信驱动2.4G无线射频模块发送数据

    目录 SPI介绍 SPI接口原理 SPI工作原理 SPI特征 引脚配置 结构体 库函数 SPI配置过程 SPI.h SPI.c NRF24L01无线射频模块 NRF24L01厂家驱动代码移植 NRF2 ...

  6. Matlab 与stm32单片机之间的串口通信项目实战

    前言 当我们用STM32做一些DSP运算时(比如傅里叶变化,小波变换,卡尔曼滤波等算法),我们想知道自己所码出来的程序是不是按照自己预期的运算结果来或者说检验程序的鲁棒性,那我们就需要检验每个关键步骤 ...

  7. 单片机驱动android屏幕,STM32单片机对智能手机触摸屏的驱动

    描述 由于智能手机的发展和大屏幕的兴起,触摸屏已经得到了广泛的应用.触摸屏分为两种:电阻触屏 俗称"软屏":电容触屏俗称"硬屏". 电阻触摸屏的屏体部分是一块多 ...

  8. DSP主机 与 STM32从机 的SPI通信(待续)

    目录 1. SPI数据传输速度计算 2. 从机 Timeout时间 1. SPI数据传输速度计算 主机和从机的clock时钟频率必须保持一致,这个值在DSP叫做 btiRate,在STM32叫做 Ba ...

  9. 【应用】使用STM32单片机定时器的Encoder模式驱动数字旋转编码开关

    RZ-51 6合1扩展板原理图.pdf:https://download.csdn.net/download/ZLK1214/29016144https://download.csdn.net/dow ...

最新文章

  1. pika开源:替代WebPack的全新JS构建工具
  2. linux 把根目录设置成777权限的补救方法
  3. git pull代码出现refusing to merge unrelated histories错误
  4. 设置图片圆角 或者圆形
  5. 2018-11-02 在代码中进行中文命名实践的短期目标
  6. nginx apache 服务器配置
  7. SendGrid是如何扩展它的邮件传送系统的
  8. 5.7 Components — Sending Actions From Components to Your Application
  9. linux - 流量切分线路
  10. Java并发编程实战读书笔记之死锁
  11. 使用RAID 5虚拟磁盘时,dell的perc控制器H310的性能较差
  12. L298N电机驱动模块的使用
  13. [AutoCAD.Net][事件] AUTOCAD 选择对象后触发事件
  14. Python爬虫——多线程爬取阳光问政
  15. 64码高清电视 android版,64体育app
  16. STM32操作增量式编码器(二)----使用编码器接口实现定位
  17. unity 碰撞检测的四种检测模式
  18. 欧拉筛法(线性筛)的学习理解
  19. Redis系列之——Redis-Cluster
  20. flutter桌面_Flutter如何赢得桌面

热门文章

  1. 关于RC延时电路的 时间常数 和 到达某电压的延时时间 计算
  2. elasticsearch7.x clusterAPI之settings
  3. [附源码]java毕业设计零食销售系统
  4. 设计模式-备忘录模式-java-中文版
  5. python 屏幕找图 点击,使用Python脚本在windows屏幕找图
  6. 2021年区域赛ICPC沈阳站J-Luggage Lock(代码简洁)
  7. 51假期读书笔记(上)——流畅的python
  8. C语言雪花算法,记一次雪花算法的实现
  9. thinksns java_社交APP系统ThinkSNS+技术概要
  10. Unity3D案例太空射击(Space Shooter)流程介绍与代码分析(上)