这篇文章我们看看GD32中如何实现SPI,软件SPI的话可以参考我之前的文章,把对应操作GPIO口的代码从STM32改成GD32就可以使用了。
【快速上手STM32】SPI通信协议&&1.8寸TFT-LCD(ST7735S)_tftlcdst7735和stm32-CSDN博客文章浏览阅读3.7k次,点赞46次,收藏60次。SPI,英文全称Serial Peripheral Interface,即串行外围设备接口,是一种高速、全双工、同步的串行通信总线。我们之前说过I2C,那么我们就拿I2C和SPI做个对比。SPI和I2C对比,优势在于SPI的传输速率比I2C快得多,劣势在于SPI需要用的通信线比较多。_tftlcdst7735和stm32https://blog.csdn.net/m0_63235356/article/details/136977377?spm=1001.2014.3001.5501这边就来看看硬件SPI如何使用。
和之前不一样,我这篇文章使用的GD32是GD32F407VET6,其实大体上没差别,因为我们使用的是固件库。要注意的就是两种型号的单片机的引脚SPI资源不一样,不同的SPI对应的引脚是不一样的,这个要查手册去对应上,不过相同的SPI资源好像引脚是一样的,比如说GD32E230的SPI0和GD32F407的引脚是一样的。那么我们接下来就使用SPI0来做示范,这样不管是哪个型号的板子都可以兼容。
流程上都是先配置好GPIO为复用模式,接着是初始化SPI,然后就直接可以用了,GD32的硬件SPI还是非常方便的。
第一步先配置时钟。
要配置的有硬件SPI0,以及对应的引脚所在的GPIO端口,因为SPI0对应的引脚都在GPIOA,因此我们只需要使能GPIOA的时钟,如果是其他的SPI资源的话可能就要使能多个端口了。
rcu_periph_clock_enable(RCU_GPIOA); // 使能GPIOA
rcu_periph_clock_enable(RCU_SPI0); // 使能SPI0
接下来我们要开启引脚复用,SPI一共是四个引脚,MISO,MOSI,SS,SCK,其中SS一般来说我们不让他硬件控制,通常我们会另外软件控制,因为硬件SPI的SS是固定死的,这样可能会和我们其他要用的引脚冲突,而且使用硬件SPI就是为了速度更快,而SS软件另外控制并不会影响多少速度,因此我们就算使用硬件SPI,也会让SS引脚使用软件控制。
所以开启引脚复用只需要开启另外三个即可。
#define Z_SPI_PORT GPIOA
#define Z_SPI_SS GPIO_PIN_4
#define Z_SPI_SCK GPIO_PIN_5
#define Z_SPI_MISO GPIO_PIN_6
#define Z_SPI_MOSI GPIO_PIN_7
上面是宏定义,但是其实好像没什么必要,因为硬件SPI的引脚是固定死的,留着宏定义是因为代码是之前软件SPI的代码,懒得改了就留下来了。大家看下面示例代码的时候看见“Z_”开头的知道是SPI引脚的宏定义就可以了。
//开启引脚复用
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_SCK);
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_MISO);
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_MOSI);
接下来就是配置GPIO口的模式了。
SS引脚我们不用硬件SPI的,我们单独控制,直接配置为输出模式即可,因为默认我们不选中,所以加个上拉电阻。
其他三个引脚我们选择复用模式(GPIO_MODE_AF),因为是交给硬件SPI了,所以我们不加电阻。
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, Z_SPI_SS);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_SCK);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_MISO);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_MOSI);
接下来就是配置输出模式了。
都配置为输出模式即可,速度选择50MHz就够用了,一般用于通信的引脚的输出频率为两倍传输速率就可以了。
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_SS);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_SCK);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_MISO);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_MOSI);
配置完引脚,接下来就可以配置SPI了。
这是从GD32F407固件库手册里截取的,如果是GD32E230的话SPI就只有0和1,没有2345了,其他是一样的。
device_mode :一般我们就选择主机模式SPI_MASTER。
trans_mode : 选择全双工模式SPI_TRANSMODE_FULLDUPLEX,说实话,除了仅接受之外的其他两个选项没看懂。
frame_size : 数据帧格式我们都是选择8bit的SPI_FRAMESIZE_8BIT,如果要发送16bit的时候发送两次8bit就行了。
nss : 前面说了,我们SS就软件控制就行了SPI_NSS_SOFT,因此这边选择软件控制。
endian : 大端小端就是高位在前在后的区别,我们SPI基本上都是高位在前,因此选择大端模式SPI_ENDIAN_MSB。
spi_init_struct.clock_polarity_phase : 相位和极性配置,说通俗一点就是选择SPI的模式。
一般来说有四种模式。
时序图可以参考下面的。
不懂要通信的模块支持哪种模式的话就默认选模式0就行SPI_CK_PL_LOW_PH_1EDGE,一般来说模式0是都支持的,实在不行就四种模式都试一遍(但是得保证其他环节不出错)。
prescale : 这是预分频器,这就要看看SPI的速率以及时钟频率了。
我们先来看看SPI的时钟频率。
SPI0挂载在APB2上,频率是84MHz。那么我们分频可以分个8,也就是差不多10MHz的速率。
因为我问的科大讯飞的大模型,它说ST7735S支持的速率是12MHz,当然了,仅供参考。因为我2分频之后也是可以正常使用ST7735S的。
综上,我们安装下面这样配置就可以了。
spi_parameter_struct spi_init_struct;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // 模式0
spi_init_struct.nss = SPI_NSS_SOFT; // 软件cs
spi_init_struct.prescale = SPI_PSC_8; // 8分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位在前
spi_init(SPI0, &spi_init_struct);
接下来就是使能。
spi_enable(SPI0);
配置完上面之后我们就可以直接发送数据了。
因为我们之前配置的是8bit,因此第二个参数传入8位的数据就可以了,这边形参类型是16bit是为了兼容两种传输格式。
spi_i2s_data_transmit(SPI0, data);
然后我们注意到了函数的名称是SPI_i2s开头的,因此i2s发送数据的函数也是用的这个。
接收数据的话我们用另外一个函数。
在发送和接收之前,我们需要检查缓冲区是否满足要求——发送的时候缓冲区是否为空,接收的时候缓冲区是否有数据。
常用的标志我用红框框出来了,完整的标志可以到固件库手册里查看。
因此我们发送一次数据可以封装起来。也就是在发送数据之前先检测发送缓冲区为空,在接收之前检测接收缓冲区非空。而且SPI它通信是类似于交换的,所以我们发送和接收放在一起。
uint8_t Z_SPI_SwapByte(uint8_t data){
//等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI0, data);
//等待接收缓冲区不空
while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE));
return spi_i2s_data_receive(SPI0);
}
有了上面这些函数,我们就可以使用硬件SPI了,完整的配置代码可以参考下面。
#define Z_SPI_PORT GPIOA
#define Z_SPI_SS GPIO_PIN_4
#define Z_SPI_SCK GPIO_PIN_5
#define Z_SPI_MISO GPIO_PIN_6
#define Z_SPI_MOSI GPIO_PIN_7
void Z_SPI_SetSS(uint8_t val){ //封装一下选中SS
gpio_bit_write(Z_SPI_PORT, Z_SPI_SS,val);
}
void Z_SPI_Init(void){
rcu_periph_clock_enable(RCU_GPIOA); // 使能GPIOA外设时钟
rcu_periph_clock_enable(RCU_SPI0); // 使能SPI0外设时钟
//配置引脚复用
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_SCK);
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_MISO);
gpio_af_set(Z_SPI_PORT, GPIO_AF_5, Z_SPI_MOSI);
//配置引脚模式
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, Z_SPI_SS);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_SCK);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_MISO);
gpio_mode_set(Z_SPI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, Z_SPI_MOSI);
//配置输出模式
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_SS);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_SCK);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_MISO);
gpio_output_options_set(Z_SPI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, Z_SPI_MOSI);
Z_SPI_SetSS(1); //默认不选中
spi_parameter_struct spi_init_struct;
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // SPI模式0
spi_init_struct.nss = SPI_NSS_SOFT; // 软件SS
spi_init_struct.prescale = SPI_PSC_8; // 8分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位在前
spi_init(SPI0, &spi_init_struct);
//使能SPI
spi_enable(SPI0);
}
uint8_t Z_SPI_SwapByte(uint8_t data){
//等待发送缓冲区为空
while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_TBE));
spi_i2s_data_transmit(SPI0, data);
//等待接收缓冲区非空
while(RESET == spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE));
return spi_i2s_data_receive(SPI0);
}
有了SPI,我们就可以直接封装ST7735S的驱动了,具体ST7735S的指令什么的我就不在这边啰嗦了,之前的文章有小小地介绍一下,文章链接在本文的开头。下面就直接把驱动代码贴出来了(需要包含上面的示例代码)。
/*
ST7725S驱动代码
*/
#define Z_ST7735S_RST_GPIO GPIO_PIN_0
#define Z_ST7735S_DC_GPIO GPIO_PIN_1
#define Z_ST7735S_PORT GPIOA
#define Z_ST7735S_WIDTH 128
#define Z_ST7735S_HIGH 160
void Z_ST7735S_SetRST(uint8_t val){
gpio_bit_write(Z_ST7735S_PORT,Z_ST7735S_RST_GPIO,val);
}
void Z_ST7735S_SetDC(uint8_t val){
gpio_bit_write(Z_ST7735S_PORT,Z_ST7735S_DC_GPIO,val);
}
//发送指令,需要把DC拉低
void Z_ST7735S_SendCommand(uint8_t command){
Z_SPI_SetSS(0);
Z_ST7735S_SetDC(0);
Z_SPI_SwapByte(command);
Z_SPI_SetSS(1);
}
//发送数据,需要把DC拉高
void Z_ST7735S_SendData(uint8_t data){
Z_SPI_SetSS(0);
Z_ST7735S_SetDC(1);
Z_SPI_SwapByte(data);
Z_SPI_SetSS(1);
}
//发送16位的数据
void Z_ST7735S_Send16bitsRGB(uint16_t rgb)
{
Z_ST7735S_SendData(rgb >> 8);
Z_ST7735S_SendData(rgb);
}
//初始化ST7735S,其中一段初始化序列厂家会提供
void Z_ST7735S_Init(void){
Z_SPI_Init();
// 除了上面SPI初始化的GPIO口,还需要额外初始化RST和DC
if(Z_ST7735S_PORT==GPIOA) rcu_periph_clock_enable(RCU_GPIOA);
else if(Z_ST7735S_PORT==GPIOB) rcu_periph_clock_enable(RCU_GPIOB);
else if(Z_ST7735S_PORT==GPIOC) rcu_periph_clock_enable(RCU_GPIOC);
gpio_mode_set(Z_ST7735S_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,Z_ST7735S_RST_GPIO);
gpio_output_options_set(Z_ST7735S_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,Z_ST7735S_RST_GPIO);
gpio_mode_set(Z_ST7735S_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,Z_ST7735S_DC_GPIO);
gpio_output_options_set(Z_ST7735S_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,Z_ST7735S_DC_GPIO);
Z_ST7735S_SetRST(0);
delay_ms(1);
Z_ST7735S_SetRST(1);
delay_ms(120);
// 厂家提供的固定的初始化代码
Z_ST7735S_SendCommand(0x11); // Sleep out
delay_ms(120); // Delay 120ms
//------------------------------------ST7735S Frame Rate-----------------------------------------//
Z_ST7735S_SendCommand(0xB1);
Z_ST7735S_SendData(0x05);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendCommand(0xB2);
Z_ST7735S_SendData(0x05);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendCommand(0xB3);
Z_ST7735S_SendData(0x05);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendData(0x05);
Z_ST7735S_SendData(0x3C);
Z_ST7735S_SendData(0x3C);
//------------------------------------End ST7735S Frame Rate---------------------------------//
Z_ST7735S_SendCommand(0xB4); // Dot inversion
Z_ST7735S_SendData(0x03);
//------------------------------------ST7735S Power Sequence---------------------------------//
Z_ST7735S_SendCommand(0xC0);
Z_ST7735S_SendData(0x28);
Z_ST7735S_SendData(0x08);
Z_ST7735S_SendData(0x04);
Z_ST7735S_SendCommand(0xC1);
Z_ST7735S_SendData(0XC0);
Z_ST7735S_SendCommand(0xC2);
Z_ST7735S_SendData(0x0D);
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendCommand(0xC3);
Z_ST7735S_SendData(0x8D);
Z_ST7735S_SendData(0x2A);
Z_ST7735S_SendCommand(0xC4);
Z_ST7735S_SendData(0x8D);
Z_ST7735S_SendData(0xEE);
//---------------------------------End ST7735S Power Sequence-------------------------------------//
Z_ST7735S_SendCommand(0xC5); // VCOM
Z_ST7735S_SendData(0x1A);
Z_ST7735S_SendCommand(0x36); // MX, MY, RGB mode
Z_ST7735S_SendData(0xC0);
//------------------------------------ST7735S Gamma Sequence---------------------------------//
Z_ST7735S_SendCommand(0xE0);
Z_ST7735S_SendData(0x04);
Z_ST7735S_SendData(0x22);
Z_ST7735S_SendData(0x07);
Z_ST7735S_SendData(0x0A);
Z_ST7735S_SendData(0x2E);
Z_ST7735S_SendData(0x30);
Z_ST7735S_SendData(0x25);
Z_ST7735S_SendData(0x2A);
Z_ST7735S_SendData(0x28);
Z_ST7735S_SendData(0x26);
Z_ST7735S_SendData(0x2E);
Z_ST7735S_SendData(0x3A);
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(0x01);
Z_ST7735S_SendData(0x03);
Z_ST7735S_SendData(0x13);
Z_ST7735S_SendCommand(0xE1);
Z_ST7735S_SendData(0x04);
Z_ST7735S_SendData(0x16);
Z_ST7735S_SendData(0x06);
Z_ST7735S_SendData(0x0D);
Z_ST7735S_SendData(0x2D);
Z_ST7735S_SendData(0x26);
Z_ST7735S_SendData(0x23);
Z_ST7735S_SendData(0x27);
Z_ST7735S_SendData(0x27);
Z_ST7735S_SendData(0x25);
Z_ST7735S_SendData(0x2D);
Z_ST7735S_SendData(0x3B);
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(0x01);
Z_ST7735S_SendData(0x04);
Z_ST7735S_SendData(0x13);
//------------------------------------End ST7735S Gamma Sequence-----------------------------//
Z_ST7735S_SendCommand(0x3A); // 65k mode
Z_ST7735S_SendData(0x05);
Z_ST7735S_SendCommand(0x29); // Display on
}
// 指定范围
void Z_ST7735S_SpecifyScope(uint8_t xs, uint8_t xe, uint8_t ys, uint8_t ye)
{
Z_ST7735S_SendCommand(0x2A); // 指定列范围
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(xs);
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(xe);
Z_ST7735S_SendCommand(0x2B); // 指定行范围
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(ys);
Z_ST7735S_SendData(0x00);
Z_ST7735S_SendData(ye);
Z_ST7735S_SendCommand(0x2C); // 开始内存写入
}
//将整个屏幕都填充上相同的颜色
void Z_ST7735S_RefreshAll(uint16_t rgb)
{
Z_ST7735S_SpecifyScope(0, Z_ST7735S_WIDTH, 0, Z_ST7735S_HIGH);
for (uint16_t j = 0; j < Z_ST7735S_HIGH; ++j)
{
for (uint16_t i = 0; i < Z_ST7735S_WIDTH; ++i)
{
Z_ST7735S_Send16bitsRGB(rgb);
}
}
}
//将指定区域的屏幕填充上相同的颜色
void Z_ST7735S_RefreshArea(uint8_t start_x,uint8_t end_x,uint8_t start_y,uint8_t end_y,uint16_t rgb){
if(start_x>Z_ST7735S_WIDTH||start_y>Z_ST7735S_HIGH||end_x>Z_ST7735S_WIDTH||end_y>Z_ST7735S_HIGH) return ;
Z_ST7735S_SpecifyScope(start_x,end_x,start_y,end_y);
for (uint16_t j = start_y; j <= end_y; ++j){
for (uint16_t i = start_x; i <= end_x; ++i){
Z_ST7735S_Send16bitsRGB(rgb);
}
}
}
//将指定区域填充上颜色数组(可以不同)
void Z_ST7735S_DrawArea(uint8_t start_x,uint8_t end_x,uint8_t start_y,uint8_t end_y,uint16_t* rgb){
if(start_x>Z_ST7735S_WIDTH||start_y>Z_ST7735S_HIGH||end_x>Z_ST7735S_WIDTH||end_y>Z_ST7735S_HIGH) return ;
Z_ST7735S_SpecifyScope(start_x,end_x,start_y,end_y);
for (uint16_t j = start_y; j <= end_y; ++j){
for (uint16_t i = start_x; i <= end_x; ++i){
Z_ST7735S_Send16bitsRGB(*rgb);
rgb++;
}
}
}
//const unsigned char number[10][10] = {
// {0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00}, /*"0",0*/
// {0x00, 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00}, /*"1",1*/
// {0x00, 0x00, 0x3C, 0x42, 0x02, 0x0C, 0x10, 0x20, 0x7E, 0x00}, /*"2",2*/
// {0x00, 0x00, 0x3C, 0x46, 0x04, 0x0C, 0x02, 0x42, 0x3C, 0x00}, /*"3",3*/
// {0x00, 0x00, 0x0C, 0x14, 0x24, 0x44, 0x7C, 0x04, 0x0C, 0x00}, /*"4",4*/
// {0x00, 0x00, 0x3E, 0x40, 0x58, 0x66, 0x02, 0x42, 0x3C, 0x00}, /*"5",5*/
// {0x00, 0x00, 0x3C, 0x40, 0x58, 0x66, 0x42, 0x42, 0x3C, 0x00}, /*"6",6*/
// {0x00, 0x00, 0x7E, 0x44, 0x04, 0x08, 0x10, 0x10, 0x10, 0x00}, /*"7",7*/
// {0x00, 0x00, 0x3C, 0x42, 0x62, 0x3C, 0x42, 0x42, 0x3C, 0x00}, /*"8",8*/
// {0x00, 0x00, 0x3C, 0x42, 0x42, 0x66, 0x1A, 0x04, 0x78, 0x00}, /*"9",9*/
//};
//void Z_ST7735S_ShowChar(uint8_t x, uint8_t y, uint8_t ch, uint16_t rgb)
//{
// if (ch >= 10)
// return;
// Z_ST7735S_SpecifyScope(x, x + 7, y, y + 9);
// for (uint8_t i = 0; i < 10; ++i)
// {
// uint8_t temp = number[ch][i];
// for (uint8_t j = 0; j < 8; ++j)
// {
// if ((temp & 0x80) != 0)
// Z_ST7735S_Send16bitsRGB(rgb);
// else
// Z_ST7735S_Send16bitsRGB(0x0000);
// temp <<= 1;
// }
// }
//}
接下来小小演示一下。
#include "board.h"
#include "Z_TFT.h"
int main(void){
board_init();
Z_ST7735S_Init();
while (1){
for(uint16_t i=0;i<0xFFFF;i+=50){
Z_ST7735S_RefreshAll(i);
delay_ms(20);
}
}
}
board.h是立创开发板提供的模板,board_init里面是进行一些初始化。
然后我把上面ST7735S的驱动代码封装成Z_TFT.h了,这个代码就是每隔20ms刷新一次屏幕,就是看看能不能正常驱动屏幕的。
可以看的出来是可以正常驱动的。