单片机SPI主从设备通信配置详解

1. SPI通信基础

(1) 主要特点
  • 全双工通信:主机和从机可同时发送和接收数据。

  • 同步时钟:由主机提供时钟信号(SCK),确保数据同步。

  • 高速传输:通常可达几MHz(远高于I2C和UART)。

  • 无标准协议:数据格式和含义由设备自行定义。

  • (2) 通信模式
  • 主从架构:1个主机控制1个或多个从机。

  • 从机选择:通过片选信号(CS/SS)选择目标从机。


  • 2. 硬件连接(4线制)

    信号线 全称 方向(主机视角) 作用
    SCK Serial Clock 主机→从机 同步时钟信号
    MOSI Master Out Slave In 主机→从机 主机发送数据到从机
    MISO Master In Slave Out 主机←从机 从机发送数据到主机
    CS/SS Chip Select/Slave Select 主机→从机 片选信号(低电平有效)

    更多的通信原理我就不多介绍了:

    3. 硬件资源:

    AT32F403CGT7调试开发板

    3.1 板卡原理图:

    3.2 连接方式:

    片选信号直连,用于开始数据传输使能,SPI通信时钟保持一致,从机时钟来源于主机提供,所以

    SCK和SCK直连,主机输出从机输入(MOSI)和主机输入从机输出(MISO)需要交叉反接,另外最重

    要的是两块板卡的通信地需要连上。

    板卡1引脚 板卡2引脚 备注
    PA4 — CS PA4 — CS 从机片选需要配置为输入
    PA5 — SCK PA5 — SCK 从机SCK配置为输入
    PA6 — MISO PA7 — MOSI 主从通信需要将数据MISO和MOSI反接
    PA7 — MOSI PA6 — MISO N/A
    GND GND N/A

    3.3 实物连接图:

    3.4 配置说明:

    通信方式:软件配置使用半双工的通信方式。如果配置全双工方式需要注意:SPI协议的全双工特

    性要求从机在接收时必须同时发送数据,即使发送的是无用5数据(如0xFF和0x55),这一设计是

    由SPI的硬件机制和同步通信本质决定的)

    通信配置:主机发送,从机接收,

    接收方式:从机使用中断的方式对数据进行接收(开启接收数据缓冲区满中断

    (SPI_I2S_RDBF_INT)),由于配置传输字节为8bit,所以每次传输一个字节,丛姐收到一个字节就会

    触发中断。

    主从配置方式:使用宏定义SPI_MODE区分设备主从。

    信号采样:配置空闲时钟低电平,数据从第二个边沿采样。

    4. 软件配置资源

    4.1 spi.c

    #include "spi.h"
    
    
    spi_data_info spi_data;
    bool get_data = false;
    
    static void spi_gpio_config(void)
    {
      gpio_init_type gpio_initstructure;
      crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
        
      //SPI1_CS_PA4
      gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
      if(SPI_MODE == SPI_MODE_MASTER)
      {
          gpio_initstructure.gpio_pull           = GPIO_PULL_NONE;
          gpio_initstructure.gpio_mode           = GPIO_MODE_OUTPUT;
      }
      else
      {
          gpio_initstructure.gpio_mode = GPIO_MODE_INPUT;  // 从机CS应为输入
          gpio_initstructure.gpio_pull = GPIO_PULL_UP;     // 推荐上拉防止干扰
      }
      gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
      gpio_initstructure.gpio_pins = GPIO_PINS_4;
      gpio_init(GPIOA, &gpio_initstructure);
      
      //SPI1_SCK_PA5
      gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
      gpio_initstructure.gpio_pull           = GPIO_PULL_DOWN;
      if(SPI_MODE == SPI_MODE_MASTER)
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
      }
      else
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_INPUT;
      }
      
      gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
      gpio_initstructure.gpio_pins = GPIO_PINS_5;
      gpio_init(GPIOA, &gpio_initstructure);
      //SPI1_MISO_PA6
      gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
      gpio_initstructure.gpio_pull           = GPIO_PULL_UP;
      if(SPI_MODE == SPI_MODE_MASTER)
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_INPUT;
      }
      else
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
      }
      gpio_initstructure.gpio_mode           = GPIO_MODE_INPUT;
      gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
      gpio_initstructure.gpio_pins = GPIO_PINS_6;
      gpio_init(GPIOA, &gpio_initstructure);
      //SPI1_MOSI_PA7
      gpio_initstructure.gpio_out_type       = GPIO_OUTPUT_PUSH_PULL;
      gpio_initstructure.gpio_pull           = GPIO_PULL_UP;
      if(SPI_MODE == SPI_MODE_MASTER)
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_MUX;
      }
      else
      {
          gpio_initstructure.gpio_mode           = GPIO_MODE_INPUT;
      }
      gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
      gpio_initstructure.gpio_pins = GPIO_PINS_7;
      gpio_init(GPIOA, &gpio_initstructure);
    
    }
    static void spi_config(void)
    {
        spi_gpio_config();
        spi_init_type spi_init_struct;
        crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, TRUE);
        spi_default_para_init(&spi_init_struct);
        if(SPI_MODE == SPI_MODE_MASTER)
        {
            spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;
        }
        else
        {
            spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_RX;
        }
        spi_init_struct.master_slave_mode = SPI_MODE;
        spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;
        spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_LSB;
        spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
        spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;
        spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;
        spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
        spi_init(SPI1, &spi_init_struct);
        
        nvic_irq_enable(SPI1_IRQn, 0, 0);
        if(SPI_MODE == SPI_MODE_MASTER)
        {
    //        spi_i2s_interrupt_enable(SPI1, SPI_I2S_TDBE_INT, TRUE);
        }
        else
        {
            spi_i2s_interrupt_enable(SPI1, SPI_I2S_RDBF_INT, TRUE);
        }
        spi_enable(SPI1, TRUE);
    }
    
    u8 Flash_RW_Byte(u8 ucTxData)
    {		
      while((SPI1->sts & SPI_I2S_TDBE_FLAG) == (uint16_t)RESET);
      SPI3->dt = ucTxData;
      while((SPI1->sts & SPI_I2S_RDBF_FLAG) == (uint16_t)RESET);
      return SPI1->dt;
    }
    
    static void spi_data_communications_test(void)
    {
        uint8_t data[4] = {0x01,0x02,0x03,0x04};
        static uint8_t temp = 0;
        temp++;
        for(int i = 0;i < 4;i++)
        {
            gpio_bits_reset(GPIOA,GPIO_PINS_4);
            spi_i2s_data_transmit(SPI1,data[i] + temp);
            gpio_bits_set(GPIOA,GPIO_PINS_4);
            delay_ms(20);
        }
    }
    
    void spi_data_receive_handle(void)
    {
        if(get_data)
        {
            printf("get spi data: ");
            for(int i = 0;i < spi_data.len;i++)
            {
                printf(" 0x%x",spi_data.data[i]);
            }
            printf("\r\n");
            get_data = false;
            spi_data.len = 0;
        }
    }
    
    
    //SPI中断回调函数
    void SPI1_IRQHandler(void)
    {
        if(spi_i2s_flag_get(SPI1,SPI_I2S_RDBF_FLAG))
        {
            spi_data.data[spi_data.len] = spi_i2s_data_receive(SPI1);
            spi_data.len++;
            if(spi_data.len >= 4)
            {
                get_data = true;
            }
        }
        spi_i2s_flag_clear(SPI1,SPI_I2S_RDBF_FLAG);
    }
    
    st_spi_fun spi_fun_info = 
    {
        .spi_config = spi_config,
        .spi_data_communications_test = spi_data_communications_test,
        .spi_data_receive_handle = spi_data_receive_handle,
    };
    
    

    4.2 spi.h

    #ifndef _SPI_H_
    #define _SPI_H_
    
    #include "config.h"
    
    #define SPI_MODE    SPI_MODE_MASTER         //SPI_MODE_SLAVE     SPI_MODE_MASTER
    
    typedef struct
    {
        uint16_t len;
        uint16_t data[200];
    }spi_data_info;
    
    typedef struct
    {
        void (*spi_config)(void);
        void (*spi_data_communications_test)(void);
        void (*spi_data_receive_handle)(void);
    }st_spi_fun;
    
    
    extern spi_data_info spi_data;
    extern st_spi_fun spi_fun_info;
    
    #endif
    
    

    4.3 main.c

    int main(void)
    {
      system_clock_config();
      at32_board_init();
      //GPIO初始化
      GPIO_Configuration();
      led_init();
      delay_ms(2000); 
      usart1_config(115200);
      spi_fun_info.spi_config();
      while(1)
      {
          //周期翻转小灯
          led2_default_toggle();
          led3_default_toggle();
          //根据主从模式分别进行收发逻辑
          if(SPI_MODE == SPI_MODE_MASTER)
          {
              delay_ms(2000);
              //数据发送
              spi_fun_info.spi_data_communications_test();
          }
          else
          {
              //数据接收
              spi_fun_info.spi_data_receive_handle();
              delay_ms(1000);
          }
      }
    }

    5. 运行结果:

    6. 最后分享

    第一次自己画板子,可能布线和硬件设计不太合理,后续继续改进,下次挑战分享更复杂的板

    卡,最后上传板卡实物图,记录下学习里程碑。

    作者:zhouzhouyaonvli

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机SPI主从设备通信配置详解

    发表回复