文章目录
- 一:SPI通讯
- 1:什么是SPI通讯
- 2:SPI优点
- 3:通讯原理
- 4:工作时许
- 5:OLED屏幕的SPI工作过程
- 二:ESP8266 的SPI通讯
- 1:概述:
- 2:8266的标准SPI、Dual SPI和Quad-SPI
- 3:8266 SPI模块说明
- 3.1 模块特点
- 3.2 主机协议格式
- 3.3 现有 API ⽀持的 SPI 主机通信格式
- 4:SPI模块API函数说明
- 4.1 void spi_master_init(uint8 spi_no)
- 4.2 void spi_mast_byte_write(uint8 spi_no,uint8 data)
- 5.SPI 接口说明
- 5.1 数据结构
- 5.1.1 枚举值
- 5.1.2结构体
- 5.1.3 常量
- 5.2 接口说明
- 5.2.1 SPIInit
- 5.2.2 SPIMasterSendData
- 5.2.2 SPICsPinSelect
- 6.编写SPI函数
- 6.1 HSPI初始化函数
- 6.2 发送一个字节的数据
- 6.3 写8位数据
- 6.4 写入16为数据
- 6.5 写入字节或者命令
- 7. 显示效果
- 三. 1.4寸TFT彩屏驱动学习
- 1.模块引脚说明
- 2.时序介绍
- 3.驱动说明
- 4.软件程序
- 4.1 液晶屏初始化程序
- 4.2 设置lcd显示区域
- 4.3 画点
- 4.4 背景区域填充(240x240)
- 4.5 显示图像
- 4.6 显示效果
近期做了个ESP8266用I2C驱动OLED播放动画的程序,结果动画非常不流畅,看网友建议用8266的SPI驱动,就来学习下8266 SPI通讯驱动。本人业余爱好,只当作学习笔记,有错误请指教。
一:SPI通讯
1:什么是SPI通讯
SPI, serial peripheral interface, 串行外围设备接口。高速的,全双工的,同步通信总线。有四个引脚:SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
2:SPI优点
全双工
通讯简单
传播速率快
3:通讯原理
以主从方式工作
MOSI(SDO): 主器件数据输出,从器件数据输入。
MISO(SDI):主器件数据输入,从器件数据输出。
SCLK : 时钟信号,由主器件产生。SPI串行传输,数据一位一位从MSB或LSB开始传输,产生相应的脉冲沿时,MOSI,MISO才进行数据传输。
CS:从器件使能信号,由主器件控制。
CS控制芯片是否被选中,只有片选信号为实现约定的使能信号时(高电位或地电位),对此芯片的操作才有效,这也就允许同一总线上连接多个SPI设备。
4:工作时许
1): SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以(单向传输时)。也是所有基于SPI的设备共有的,它们是SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
(1)SDO/MOSI-主设备数据输出,从设备数据输入;
(2)SDIMISO-主设备数据输入,从设备数据输出;
(3)SCLK-时钟信号, 由主设备产生;
(4)CS/sS-从设备使能信号,由主设备控制。当有多个从设备的时候,因为每个从设备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需要将从设备对应的片选引脚电平拉低或者是拉高。
2):需要说明的是,我们SPI通信有4种不同的模式,不同的从设备可能在出厂是就是配置为某种模式, 这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL (时钟极性)和CPHA (时钟相位)控制我们主设备的通信模式,具体如下:
Modeo: CPOL=0, CPHA=0
Mode1: CPOL-0, CPHA-1
Mode2: CPOL=1, CPHA-0
Mode3: CPOL=1, CPHA=1
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位СРНA
5:OLED屏幕的SPI工作过程
四线串行接口由串行时钟:
SCLK,串行数据: SDIN, D/C, CS组成。在4线SPI模式下, Do扮演SCLK, D1扮演SDIN。对于没有用到的数据引脚, D2应该保持开路。D3到D7引脚, E和R/W# (WR#)引脚可以连接到一个额外的地上。
SDIN按照从高位到低位的顺序在SCLK的每一个上升沿将数据移入一个8为移位寄存器中,
D/C#每8个时钟采集一次,同时决定字节数据是写入数据RAM还是命令寄存器
二:ESP8266 的SPI通讯
1:概述:
ESP8266EX共有17个GPIO管脚,通过配置适当的寄存器可以给它们分配不同的功能。每个GPIO都可以配置为内部上拉/下拉,或者被设置为高阻。当被配置为输入时,可通过读取寄存器获取输入值;输入也可以被设置为边缘触发或电平触发来产生CPU中断。简言之, 1管脚是双向、非反相和三态的,带有三态控制的输入和输出缓冲器。这些管脚可以与其他功能复用,例如12C, 12S, UART,PWM、IR遥控等。
ESP8266EX拥有1个通用从机/主机SPI, 1个从机SDIO /SPI,和1个通用从机/主机HSPI。所有接口的功能均由硬件实现。
NodeMcu的SPI(注意与HSPI区分)引脚(SD0-SD3、CLK、CMD)专门用于与ESP-12E的外接flash芯片进行Quad-SPI通信,因此不能用于SPI应用。
基于ESP8266的NodeMcu具有HSPI,具有4个可用于SPI通信的引脚(GPIO12-GPIO15)。通过这个SPI接口,我们可以将任何支持SPI的设备与NodeMcu连接起来,并与其进行通信
2:8266的标准SPI、Dual SPI和Quad-SPI
1.标准SPI
标准SPI通常就叫做SPI,它是一种串行外设接口规范,有4根引脚信号:clk、cs、mosi、miso;
2.Dual SPI
它只是针对SPI Flash而言,不是针对所有SPI外设。对于SPI Flash,全双工并不常用,因此扩展了mosi和miso的用法,让它们工作在半双工,用以加倍数据传输。也就是对于Dual SPI Flash,可以发送一个命令字节进入dual mode,这样mosi变成SIO0(serial io 0),mosi变成SIO1(serial io 1),这样一个时钟周期内就能传输2个bit数据,加倍了数据传输;
3.Quad SPI
与Dual SPI类似,也是针对SPI Flash,Quad SPI Flash增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit。所以可以理解为:在传输速度上,Quad SPI=2Dual SPI=4SPI。
所以对于SPI Flash,有标准spi flash,dual spi , quad spi 三种类型,分别对应3-wire, 4-wire, 6-wire,在相同clock下,线数越多,传输速率越高。
3:8266 SPI模块说明
通讯几种模式:使用SDIO通讯的兼容模式,使用SPI模块,使用SPI混合模式 ,
这里驱动屏幕,我们使用自带的SPI模块
3.1 模块特点
- 支持标准的主机(Master)和从机(Slave)模式。
- 支持长度可编程的硬件指令(CMD)和地址(ADDR) ,指令最大16位,地址最长64位。
- 数据缓冲区最大64字节,以word对齐。
- Slave模式下可编程的读写状态寄存器。
- 3个片选(CS)管脚。
- 主机模式时钟频率高达80 MHz,从机模式钟频率最高为20 MHz
- 可选的时钟极性。
- 可选的MSB (最高有效位)和LSB (最低有效位)传输。
- 可选的数据缓冲区高字节或低字节传输。
- 传输结束、读写数据、读写状态多个中断源可选择。
3.2 主机协议格式
SPI 主机⽀持的通信格式
ESP8266 SPI主机通信格式为命令+地址+读/写数据,具体为:
命令: 必须存在;长度, 1~16位;主机输出从机输入(MOS) 。
地址: 可选;长度, 0~32位;主机输出从机输入(MOSI)
数据写或读:可选;⻓度,0 - 512 位(64 字节);主机输出从机输⼊(MOSI)或
主机输⼊从机输出(MISO)。
3.3 现有 API ⽀持的 SPI 主机通信格式
ESP8266 SPI的API函数中给出两个固定的主机初始化模式,一个模式支持大多数以字节·单位的常规SPI通信,另一个模式专为驱动一种彩色LCD屏设计,该设备需要一次9位的非标准SPI通信格式,。
4:SPI模块API函数说明
4.1 void spi_master_init(uint8 spi_no)
功能:
常规 SPI 主机初始化函数,波特率为 CPU 时钟的 4 分频,初始化后可以使⽤除
spi_lcd_9bit_write 以外的所有主机函数。
参数 | 说明 |
uint8 spi_no | 所使⽤ SPI 模块号,只能输⼊ SPI 与 HSPI,其他输⼊⽆效。 |
4.2 void spi_mast_byte_write(uint8 spi_no,uint8 data)
功能:
完成基本的⼀字节的主机发送
参数 | 说明 |
uint8 spi_no | 所使⽤ SPI 模块号,只能输⼊ SPI 与 HSPI,其他输⼊⽆效。 |
uint8 data | 8 位发送数据。 |
5.SPI 接口说明
5.1 数据结构
5.1.1 枚举值
SpiMode
值 | 说明 |
SpiMode_Master | 主机模式 |
SpiMode_Slave | 从机模式 |
SpiSubMode
值 | 说明 |
SpiSubMode_0 | SPI_CPOL(0) SPI_CPHA(0) |
SpiSubMode_1 | SPI_CPOL(0) SPI_CPHA(1) |
SpiSubMode_2 | SPI_CPOL(1) SPI_CPHA(0) |
SpiSubMode_3 | SPI_CPOL(1) SPI_CPHA(1) |
SpiSpeed
值 | 说明 |
SpiSpeed_0_5MHz | SPI 速率 0.5 MHz |
SpiSpeed_1MHz | SPI 速率 1 MHz |
SpiSpeed_2MHz | SPI 速率 2 MHz |
SpiSpeed_5MHz | SPI 速率 5 MHz |
SpiSpeed_8MHz | SPI 速率 8 MHz |
SpiSpeed_10MHz | SPI 速率 10 MHz |
SpiBitOrder
值 | 说明 |
SpiBitOrder_MSBFirst | ⾸先传输 MSB |
SpiBitOrder_LSBFirst | ⾸先传输 LSB |
SpiIntSrc
值 | 说明 |
SpiIntSrc_TransDone | 传输完成中断标志 |
SpiIntSrc_WrStaDone | 写状态寄存器中断标志 |
SpiIntSrc_RdStaDone | 读状态寄存器中断标志 |
SpiIntSrc_WrBufDone | 写数据寄存器中断标志 |
SpiIntSrc_RdBufDone | 读数据寄存器中断标志 |
SpiPinCS
值 | 说明 |
SpiPinCS_0 CS0 | 管脚 |
SpiPinCS_1 CS1 | 管脚 |
SpiPinCS_2 CS2 | 管脚 |
5.1.2结构体
SpiAttr
SPI 配置参数
typedef struct
{
SpiMode mode; ///<主从模式选择
SpiSubMode subMode; ///< SPI 时钟极性选择
SpiSpeed speed; ///< SPI 速率设置
SpiBitOrder bitOrder; ///< SPI 传输位顺序设置
} SpiAttr;
SpiData
SPI 传输的数据结构体
typedef struct
{
uint16_t cmd; ///< 命令值
uint8_t cmdLen; ///< 命令字节长度
uint32_t *addr; ///< 指向地址值
uint8_t addrLen; ///<地址字节长度
uint32_t *data; ///< 指向数据缓冲区
uint8_t dataLen; ///< 数据字节长度
} SpiData;
==SpiIntInfo
SPI 中断配置信息结构体 ==(不学习)
5.1.3 常量
ESP8266作为从机的命令(不学习)
5.2 接口说明
5.2.1 SPIInit
接口描述
SPI模块初始化
接口函数
void SPIInit
参数 | 说明 |
spiNum | [in] 选择 SPI 和 HSPI。 |
pAttr | [in] ⼀个指向 SpiAttr 结构体的指针。 |
返回值
无
5.2.2 SPIMasterSendData
接⼝描述
主机模式下根据 pInData 指定的命令地址和数据完成⼀次传输。
接⼝函数
int SPIMasterSendData(SpiNum spiNum, SpiData* pInData);
参数 | 说明 |
spiNum | [in] 选择 SPI 和 HSPI。 |
pInData | [in] ⼀个指向 SpiData 结构体的指针,需要指明传输的 |
命令、地址和数据的缓冲区和⻓度。
返回值
• 0:成功
• 其他:失败
说明:
DATA 先发送 word 的低字节。
5.2.2 SPICsPinSelect
接⼝描述
选择 CS 管脚。
接⼝函数
void SPICsPinSelect(SpiNum spiNum, SpiPinCS pinCs);
参数 | 说明 |
spiNum | [in] 选择 SPI 和 HSPI。 |
pinCs | [in] 需要选择的管脚。 |
返回值
⽆
6.编写SPI函数
将OLED屏幕设置成如下端口
SCL D5
SDA D7
RES D4
DC D1
BLK D8
6.1 HSPI初始化函数
void spi_initialize()
{
//IO口初始化
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2);
SPIInit(SpiNum_HSPI, &hSpiAttr);
//预处理命令
#define LCD_DC_Clr GPIO_OUTPUT_SET(5,0); //命令
#define LCD_DC_SET GPIO_OUTPUT_SET(5,1); //DC释放 数据发送
//spi初始化
SpiAttr hSpiAttr;
hSpiAttr.bitOrder = SpiBitOrder_MSBFirst;
hSpiAttr.speed = SpiSpeed_10MHz;
hSpiAttr.mode = SpiMode_Master;
hSpiAttr.subMode = SpiSubMode_0;
6.2 发送一个字节的数据
void Send_data8(uint8 data)
{
system_soft_wdt_feed() ;//喂狗函数
SpiData pDat;
pDat.cmd = data; ///<将第一个命令字节设置为传输数据
pDat.cmdLen = 1; ///< 1个字节长度
pDat.addr = NULL; ///< 空
pDat.addrLen = 0; ///< 空
pDat.data = NULL; ///< 空
pDat.dataLen = 0; ///<空
SPIMasterSendData(SpiNum_HSPI, &pDat); //完成一次数据传输的主机函数
}
6.3 写8位数据
void Lcd_Write_Data8(uint8 data)
{
Send_data8(data);
}
6.4 写入16为数据
void Lcd_Write_Data16(uint16 data)
{
Send_data8(data>>8);
Send_data8(data);
}
6.5 写入字节或者命令
//向 SSD1360写入一个字节
//dat要写入的数据或者命令
//cmd:数据和命令标志 0表示命令 1表示数据
void OLED_WR_Byte(u8 dat,u8 cmd)
{
if(cmd==0)
{
LCD_DC_Clr;//DC下拉写命令
Lcd_Write_Data8(dat);//写入命令
LCD_DC_SET;
os_printf("\r\n 写入命令\r\n");
}
else
{
Lcd_Write_Data8(dat);//写入数据
os_printf("\r\n 写入数据\r\n");
}
}
7. 显示效果
OLED播放#烂苹果代码下载地址
三. 1.4寸TFT彩屏驱动学习
1.模块引脚说明
分辨率:240*240
驱动IC:ST7789
颜色:全彩
显示区域:23.4x23.4mm
通信接口:4线SPI
管脚定义:左下角为1,左上角为7
1.GND(电源地)
2.VCC(电源正3.3V)
3.SCL(SPI时钟)
4.SDA(SPI数据)
5.RES(复位)
6.DC(数据和命令控制)
7.BLK(LCD背光控制,低电平关闭背光)
2.时序介绍
1.3’TFTLCD 时序,SCL ,SDA, DC,三条线就可以和机器通讯,复位脚接高,BLK可接与不接,可用PWM来调节背光亮度
3.驱动说明
1.3’TFTLCD 模块采用 ST7789V2 作为 LCD 驱动器,显示数据可以直接存储在24032018 位片上的 RAM 中,它可以在没有外部操作时钟的情况下执行显示数据 RAM 读 /写操作,以最小化功耗。
RST 是 LCD 的硬复位脚,低电平有效,用于复位 ST7789V2 芯片,实现液晶的复位,在每次初始化之前,我们强烈建议大家先执行硬复位,再做初始化
ST7789V2 自带 LCD RAM(2403203 字节),并且最高支持 18 位颜色深度(262K色),不过我们一般使用 16 位颜色深度(65K 色),RGB565 格式,这样可以在 16 位模式下达到最快的速度。在 16 位模式下ST7789V2 采用 RGB565 格式存储颜色数据,此时 MCU的 16 位数据与 LCD RAM 的对应关系如图 2.4.1 所示:
MCU 的 16 位数据中,最低 5 位代表蓝色,中间 6 位位绿色,最高 5 位为红色。数值越大,表示该颜色越深。
任何 LCD,使用流程都可以简单的用以上流程图表示。其中硬复位和初始化序列,只需要执行一次即可。而画点流程就是:坐标->写 GRAM 指令->写入颜色数据,然后在 LCD上面,我们就可以看到对应的点显示我们写入的颜色了。
4.软件程序
4.1 液晶屏初始化程序
void lcd_initial()
{
GPIO_OUTPUT_SET(2,0);//复位引脚接地
GPIO_OUTPUT_SET(2,1);//复位引脚释放
Lcd_WriteIndex(0x36);
Lcd_WriteData(0x00);
Lcd_WriteIndex(0x3A);
Lcd_WriteData(0x05);
Lcd_WriteIndex(0xB2);
Lcd_WriteData(0x0C);
Lcd_WriteData(0x0C);
Lcd_WriteData(0x00);
Lcd_WriteData(0x33);
Lcd_WriteData(0x33);
Lcd_WriteIndex(0xB7);
Lcd_WriteData(0x35);
Lcd_WriteIndex(0xBB);
Lcd_WriteData(0x19);
Lcd_WriteIndex(0xC0);
Lcd_WriteData(0x2C);
Lcd_WriteIndex(0xC2);
Lcd_WriteData(0x01);
Lcd_WriteIndex(0xC3);
Lcd_WriteData(0x12);
Lcd_WriteIndex(0xC4);
Lcd_WriteData(0x20);
Lcd_WriteIndex(0xC6);
Lcd_WriteData(0x0F);
Lcd_WriteIndex(0xD0);
Lcd_WriteData(0xA4);
Lcd_WriteData(0xA1);
Lcd_WriteIndex(0xE0);
Lcd_WriteData(0xD0);
Lcd_WriteData(0x04);
Lcd_WriteData(0x0D);
Lcd_WriteData(0x11);
Lcd_WriteData(0x13);
Lcd_WriteData(0x2B);
Lcd_WriteData(0x3F);
Lcd_WriteData(0x54);
Lcd_WriteData(0x4C);
Lcd_WriteData(0x18);
Lcd_WriteData(0x0D);
Lcd_WriteData(0x0B);
Lcd_WriteData(0x1F);
Lcd_WriteData(0x23);
Lcd_WriteIndex(0xE1);
Lcd_WriteData(0xD0);
Lcd_WriteData(0x04);
Lcd_WriteData(0x0C);
Lcd_WriteData(0x11);
Lcd_WriteData(0x13);
Lcd_WriteData(0x2C);
Lcd_WriteData(0x3F);
Lcd_WriteData(0x44);
Lcd_WriteData(0x51);
Lcd_WriteData(0x2F);
Lcd_WriteData(0x1F);
Lcd_WriteData(0x1F);
Lcd_WriteData(0x20);
Lcd_WriteData(0x23);
Lcd_WriteIndex(0x21);
Lcd_WriteIndex(0x11);
Lcd_WriteIndex(0x29);
}
4.2 设置lcd显示区域
void Lcd_SetRegion(unsigned int x_start,unsigned int y_start,unsigned int x_end,unsigned int y_end)
{
Lcd_WriteIndex(0x2a);//列地址设置命令
Lcd_WriteData(0x00);//列开始地址高8位
Lcd_WriteData(x_start);//列开始地址低8位
Lcd_WriteData(0x00);//列结束地址高8位
Lcd_WriteData(x_end);//列结束地址低8位
Lcd_WriteIndex(0x2b);//行地址设置命令
Lcd_WriteData(0x00);
Lcd_WriteData(y_start);
Lcd_WriteData(0x00);
Lcd_WriteData(y_end);
Lcd_WriteIndex(0x2c);
}
4.3 画点
void PutPixel(unsigned int x_start,unsigned int y_start,unsigned int color)
{
Lcd_SetRegion(x_start,y_start,x_start+1,y_start+1);//设置光标位置
LCD_WriteData_16Bit(color);//写入颜色数据
}
4.4 背景区域填充(240x240)
void dsp_single_colour(int color)
{
unsigned char i,j;
Lcd_SetRegion(0,0,240-1,240-1);//设置填充范围
for (i=0;i<240;i++)
for (j=0;j<240;j++)
LCD_WriteData_16Bit(color);
}
4.5 显示图像
由于图片数据过大,必须存储外部Flash
void showimage()
{
int j,k;
unsigned long i;
dsp_single_colour(WHITE); //清屏幕
for(k=0;k<240;k++)
{
spi_flash_read((0xD0*4096+k*480),(uint32*)hz16,480); //调用外部FLSH,读取图片数据
Lcd_SetRegion(0,k,240,k+1);
for(i=0;i<240;i++)
{
Lcd_Write_Data8(hz16[i*2+1]);
Lcd_Write_Data8(hz16[i*2]);
}
}
}
4.6 显示效果