行业知识
【GD32】06 - SPI (含ST7735S TFT-LCD屏幕驱动)
这篇文章我们看看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); // 使能GPIOArcu_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_7void 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 160void 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_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 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