小工具知识网

当前位置: 首页 > 行业知识 > 【GD32】06 - SPI (含ST7735S TFT-LCD屏幕驱动)

行业知识

【GD32】06 - SPI (含ST7735S TFT-LCD屏幕驱动)

2026-11-12 06:43:26 来源:互联网转载

这篇文章我们看看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 

上一篇:HTML背景图片设置技巧,如何居中并垂直显示?轻松掌握背景图设置秘籍

下一篇:ISOBT下载宝典,轻松获取所需软件!