打印
[研电赛技术支持]

GD32F103 硬件SPI通信

[复制链接]
1360|21
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2025-2-22 09:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1. SPI的通信原理
SPI既可以做主机也可以做从机。

当做主机时。MOSI,SCK,CS都是作为输出。 而作为从机时。MOSI,SCK,CS都是作为输入。



所以SPI的硬件电路应该实现这样的功能。

2. GD32/STM32的SPI框图
1. GD32框图
如下图做主机的数据流向:



如下图做从机的数据流向:



2. STM32框图
通过一些寄存器的配置来控制电路。跟GD32的差不多。

波特率配置越高,采样越快。SPI的速率越快。



3. SPI的寄存器介绍
1. 控制寄存器0(SPI_CTL0)





2. 控制寄存器1(SPI_CTL1)



3. 状态寄存器(SPI_STAT)  






4. 数据寄存器(SPI_DATA)  



4. SPI主模式配置



1. 发送数据
先判断发送主机发送缓冲器是否为空。



2. 接收数据
接收数据缓冲器是否为空。如果为空就等待,否则就接收。



5. dome (硬件SPI访问w25Q32)
NSS\SCK\MISO\MOSI  对应的 PA4\PA5\PA6\PA7引脚。

1. 具体的SPI配置步骤。
1. SPI时钟使能,SPI对应的GPIO时钟使能。复用时钟使能。

2. SPI的GOIP配置。

3. SPI的初始化配置

4. SPI使能。

2. 代码实现
spi.h

#ifndef _SPI_H
#define _SPI_H

#include "gd32f10x.h"


void w25qxx_rcu_init(void);
void w25qxx_io_init(void);
void w25qxx_spi_init(void);

#endif
spi.c

#include "spi.h"

// 使能外设时钟
void w25qxx_rcu_init(void)
{
        rcu_periph_clock_enable(RCU_GPIOA); //使能GPIOA时钟
        rcu_periph_clock_enable(RCU_AF);    //使能AF时钟
        rcu_periph_clock_enable(RCU_SPI0);  //使能SPI0时钟
}
       
// IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
void w25qxx_io_init(void)
{
        gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_6); // MISO 浮空输入
       
        gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_7); // SCK\MOSI 复用推挽
       
        gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4);// NSS片选口 普通的推挽输出
}
       
// SPI0初始化
void w25qxx_spi_init(void)
{
        spi_parameter_struct spi_struct;
        spi_struct.device_mode = SPI_MASTER;                    /*!< SPI master  做主机*/
  spi_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;         /*!< SPI transfer type 全双工 */
  spi_struct.frame_size =  SPI_FRAMESIZE_8BIT;              /*!< SPI frame size  一次8字节 */
  spi_struct.nss = SPI_NSS_SOFT;                            /*!< SPI NSS control by software 软件CS */
  spi_struct.endian = SPI_ENDIAN_MSB;                       /*!< SPI big endian or little endian  传输高字节在前*/
  spi_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /*!< SPI clock phase and polarity 空闲低电平 第一个边沿进行采样*/
  spi_struct.prescale = SPI_PSC_8;                          /*!< SPI prescaler factor 8分频*/
        spi_init(SPI0, &spi_struct);
}


w25qxx.h

#ifndef _W25QXX_SPI_H
#define _W25QXX_SPI_H

#include "gd32f10x.h"
#include "w25qxx_ins.h"
#include "gd32f10x_spi.h"

#define W25QXX_ID_1           1

#define W25QXX_SR_ID_1        1
#define W25QXX_SR_ID_2        2
#define W25QXX_SR_ID_3        3
void w25qxx_init(void);
void w25qxx_wait_busy(void);
uint8_t w25qxx_read_sr(uint8_t sregister_id);  // 读状态寄存器
       
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes);

void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes); //
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes);   // page program

void w25qxx_erase_sector(uint32_t sector_addr);
void w25qxx_erase_chip(void);

void w25qxx_write_enable(void);
void w25qxx_write_disable(void);

void w25qxx_power_down(void);
void w25qxx_wake_up(void);

void w25qxx_cs_enable(uint8_t cs_id);
void w25qxx_cs_disable(uint8_t cs_id);
uint8_t w25qxx_swap(uint8_t byte_to_send);


#endif

w25qxx.c

#include "w25qxx.h"
#include "spi.h"

void w25qxx_init(void){
        // 使能外设时钟
        w25qxx_rcu_init();
       
        // IO口进行配置,使之复用为SPI0, PA4\PA5\PA6\PA7,NSS\SCK\MISO\MOSI
        w25qxx_io_init();
       
        // SPI0初始化
        w25qxx_spi_init();
        // SPI使能
        spi_enable(SPI0);
}


// 如果SR-1的BUSY位为1的话,一直等待,直到BUSY位为0,结束等待
void w25qxx_wait_busy(void){
        while((w25qxx_read_sr(W25QXX_SR_ID_1) & 0x01) == 0x01){
                ;
        }
}

// 读状态寄存器
uint8_t w25qxx_read_sr(uint8_t sregister_id){
        uint8_t command, result;
        switch(sregister_id){
                case W25QXX_SR_ID_1:
                        command = W25QXX_READ_STATUS_REGISTER_1;
                break;
                case W25QXX_SR_ID_2:
                        command = W25QXX_READ_STATUS_REGISTER_2;
                break;
                case W25QXX_SR_ID_3:
                        command = W25QXX_READ_STATUS_REGISTER_3;
                break;
                default:
                        command = W25QXX_READ_STATUS_REGISTER_1;
                break;
        }
       
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(command);
        result = w25qxx_swap(0xFF);
        w25qxx_cs_disable(W25QXX_ID_1);
       
        return result;
}

// 读flash的数据
// *p_buffer 读回的数据的存放位置
void w25qxx_read(uint8_t *p_buffer, uint32_t read_addr, uint16_t num_read_bytes){
        uint16_t i;
       
        w25qxx_cs_enable(W25QXX_ID_1);
       
        w25qxx_swap(W25QXX_READ_DATA); //发送读数据的指令
        w25qxx_swap(read_addr >> 16);  //发送24bit地址
        w25qxx_swap(read_addr >> 8);
        w25qxx_swap(read_addr);
       
        for(i=0; i < num_read_bytes; i++){
                p_buffer = w25qxx_swap(0xFF);
        }
       
        w25qxx_cs_disable(W25QXX_ID_1);
}

//
uint8_t W25QXX_Buffer[4096];  //用来存放从sector读出的bytes
void w25qxx_write(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
        uint32_t sec_num;
        uint16_t sec_remain;
        uint16_t sec_off;
        uint16_t i;
       
        sec_num        = write_addr / 4096;              //要写入的位置处在第sec_num个扇区上
        sec_off = write_addr % 4096;
       
        sec_remain = 4096 - sec_off;
       
        if(num_write_bytes <= sec_remain){
                w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
               
                for(i = 0; i < sec_remain; i++){
                        if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
                                break;
                }
               
                if(i < sec_remain){ // 扇区没有擦除
                        w25qxx_erase_sector(sec_num * 4096);
                        for(i = 0; i < sec_remain; i++){
                                W25QXX_Buffer[i + sec_off] = p_buffer;
                        }
                        w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
                }else{              // 扇区sec_remain部分是擦除过的
                        w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
                }
        }else{
                w25qxx_read(W25QXX_Buffer, sec_num * 4096, 4096);  //扇区的数据读出来
               
                for(i = 0; i < sec_remain; i++){
                        if(W25QXX_Buffer[i + sec_off] != 0xFF)  //说明这个扇区的第i+sec_off位没有擦除
                                break;
                }
               
                if(i < sec_remain){ // 扇区没有擦除
                        w25qxx_erase_sector(sec_num * 4096);
                        for(i = 0; i < sec_remain; i++){
                                W25QXX_Buffer[i + sec_off] = p_buffer;
                        }
                        w25qxx_write_nocheck(W25QXX_Buffer, sec_num * 4096, 4096);
                }else{              // 扇区sec_remain部分是擦除过的
                        w25qxx_write_nocheck(p_buffer, write_addr, sec_remain);
                }
               
                write_addr += sec_remain;
                p_buffer += sec_remain;
                num_write_bytes -= sec_remain;
                w25qxx_write(p_buffer, write_addr, num_write_bytes);
        }
               
        //判断读出来的数据是否都为0xFF
        ;//扇区是否删除
         //判断是否跨页
}

// 调用之前先确保扇区删除
void w25qxx_write_nocheck(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
        uint16_t page_remain = 256 - write_addr % 256;
       
        if(num_write_bytes <= page_remain){
                w25qxx_write_page(p_buffer, write_addr, num_write_bytes);
        }else{
                w25qxx_write_page(p_buffer, write_addr, page_remain);
                p_buffer += page_remain;
                write_addr += page_remain;
                num_write_bytes -= page_remain;
                w25qxx_write_nocheck(p_buffer, write_addr, num_write_bytes);
        }
}

// page program
// 保证没有跨页写的前提下调用此函数往某个页上写内容
void w25qxx_write_page(uint8_t *p_buffer, uint32_t write_addr, uint16_t num_write_bytes){
        uint16_t i;
       
        w25qxx_write_enable();
       
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_PAGE_PROGRAM);
        w25qxx_swap(write_addr >> 16);  //发送24bit地址
        w25qxx_swap(write_addr >> 8);
        w25qxx_swap(write_addr);
       
        for(i = 0; i < num_write_bytes; i++){
                w25qxx_swap(p_buffer);
        }
        w25qxx_cs_disable(W25QXX_ID_1);
       
        w25qxx_wait_busy();
}

void w25qxx_erase_sector(uint32_t sector_addr){
        w25qxx_write_enable();
       
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_SECTOR_ERASE_4KB);
        w25qxx_swap(sector_addr >> 16);
        w25qxx_swap(sector_addr >> 8);
        w25qxx_swap(sector_addr);
        w25qxx_cs_disable(W25QXX_ID_1);
       
        w25qxx_wait_busy();
}

void w25qxx_erase_chip(void){
        w25qxx_write_enable();
       
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_CHIP_ERASE);
        w25qxx_cs_disable(W25QXX_ID_1);
       
        w25qxx_wait_busy();
}

void w25qxx_write_enable(void){
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_WRITE_ENABLE);
        w25qxx_cs_disable(W25QXX_ID_1);
}

void w25qxx_write_disable(void){
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_WRITE_DISABLE);
        w25qxx_cs_disable(W25QXX_ID_1);
}

// 低电量休眠
void w25qxx_power_down(void){
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_POWER_DOWN);
        w25qxx_cs_disable(W25QXX_ID_1);
}

// 唤醒
void w25qxx_wake_up(void){
        w25qxx_cs_enable(W25QXX_ID_1);
        w25qxx_swap(W25QXX_RELEASE_POWER_DOWN_HPM_DEVICE_ID);
        w25qxx_cs_disable(W25QXX_ID_1);
}

/*
brief:使能片选引脚cs
cs_id: cs引脚的序号,即第几个w25qxx flash
*/
void w25qxx_cs_enable(uint8_t cs_id){
        switch(cs_id){
                case W25QXX_ID_1:
                        gpio_bit_reset(GPIOA, GPIO_PIN_4);
                break;
                default:
                        break;
        }
}

void w25qxx_cs_disable(uint8_t cs_id){
        switch(cs_id){
                case W25QXX_ID_1:
                        gpio_bit_set(GPIOA, GPIO_PIN_4);
                break;
                default:
                        break;
        }
}

/*
主从数据交换
*/
uint8_t w25qxx_swap(uint8_t byte_to_send){
        while(spi_i2s_flag_get(SPI0, SPI_FLAG_TBE) == RESET){ // 等待SPI发送缓冲器为空
                ;
        }
        spi_i2s_data_transmit(SPI0, byte_to_send);            // 把数据放到发生缓冲器
        while(spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS) == SET){ // 等待通信结束
                ;
        }
       
        while(spi_i2s_flag_get(SPI0, SPI_FLAG_RBNE) == RESET){ // 等待SPI接收缓冲器非空
                ;
        }       
        return spi_i2s_data_receive(SPI0); /* 把接收到的数据返回(从接收缓冲器里拿出) */
}




————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_41328470/article/details/133690947

使用特权

评论回复
沙发
更多更合适ii| | 2025-2-28 16:47 | 只看该作者
SPI 是一种同步的串行通信协议,广泛用于短距离、高速度的设备之间的数据交换。

使用特权

评论回复
板凳
biechedan| | 2025-3-10 16:14 | 只看该作者
使能SPI外设时钟。
使能SPI对应的GPIO时钟。
使能复用时钟。

使用特权

评论回复
地板
loutin| | 2025-3-12 16:35 | 只看该作者
如果 SPI 通信的双方存在较大的电压差或可能受到干扰,建议使用光耦或磁耦等隔离器件进行信号隔离,以提高通信的稳定性和可靠性。

使用特权

评论回复
5
bestwell| | 2025-3-12 17:41 | 只看该作者
SPI支持8位或16位的数据帧格式,可以通过寄存器配置。

使用特权

评论回复
6
averyleigh| | 2025-3-12 22:29 | 只看该作者
如果使用DMA进行数据传输,确保DMA通道配置正确。
配置DMA的存储器宽度、传输方向、内存宽度等参数。

使用特权

评论回复
7
pentruman| | 2025-3-14 11:51 | 只看该作者
SPI支持全双工通信,即同时进行数据的发送和接收。

使用特权

评论回复
8
gygp| | 2025-3-14 15:10 | 只看该作者
SPI 通信对电源的稳定性要求较高,不稳定的电源可能会导致通信错误。

使用特权

评论回复
9
janewood| | 2025-3-14 17:14 | 只看该作者
正确配置SPI时钟信号的极性和相位,以匹配从设备的要求。

使用特权

评论回复
10
bestwell| | 2025-3-14 22:05 | 只看该作者
SPI 的时钟极性(CPOL)和相位(CPHA)需要根据通信双方的要求进行正确配置。

使用特权

评论回复
11
maqianqu| | 2025-3-15 01:08 | 只看该作者
主设备需要在通信开始时拉低片选信号选中从设备,通信结束后再拉高片选信号释放从设备。

使用特权

评论回复
12
adolphcocker| | 2025-3-15 04:21 | 只看该作者
GD32F103是一款基于ARM Cortex-M3内核的微控制器,具有丰富的外设接口

使用特权

评论回复
13
mickit| | 2025-3-15 08:54 | 只看该作者
要根据通信距离、设备性能等因素合理设置 SPI 的波特率。波特率过高可能会导致信号失真,通信不稳定;波特率过低则会影响通信效率。

使用特权

评论回复
14
sheflynn| | 2025-3-15 12:22 | 只看该作者
注意数据的字节顺序和位顺序,确保发送和接收的数据与外部设备的期望格式一致。在进行多字节数据传输时,要注意数据帧的大小和边界对齐。

使用特权

评论回复
15
pentruman| | 2025-3-15 14:16 | 只看该作者
SPI 是同步通信协议,主从设备之间需要保持时钟同步。在进行连续数据传输时,要注意数据的发送和接收时机,避免数据丢失或错误。

使用特权

评论回复
16
gygp| | 2025-3-15 18:56 | 只看该作者
根据从设备的最大通信速率和系统时钟频率,合理设置SPI时钟分频值,避免通信速率过高导致数据丢失。

使用特权

评论回复
17
jonas222| | 2025-3-15 22:19 | 只看该作者
硬件SPI可以用于与各种外部设备进行高速通信

使用特权

评论回复
18
bartonalfred| | 2025-3-16 12:22 | 只看该作者
如果使用DMA或中断,确保正确配置DMA请求和中断向量,并在中断服务例程中正确处理数据。

使用特权

评论回复
19
sanfuzi| | 2025-3-18 22:48 | 只看该作者
SPI可以工作在主模式或从模式。在主模式下,SPI控制器产生时钟信号;在从模式下,SPI控制器接收外部时钟信号。

使用特权

评论回复
20
chenci2013| | 2025-3-19 11:29 | 只看该作者
在数据传输前,通过控制NSS引脚来选中外部SPI设备。若配置为软件控制NSS,则需要手动将NSS引脚拉低以选中设备;若配置为硬件自动控制NSS,则无需此操作。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2179

主题

16464

帖子

16

粉丝