呐咯密密 发表于 2023-10-20 14:01

基于N32G430的SPI+DMA主从通信

本帖最后由 呐咯密密 于 2023-10-20 14:07 编辑

#申请原创#
前言又是许久没有更新原创文章了,最近因为项目的积压,导致没有闲置时间进行开发的记录积累,乘着空闲时间,将最近的开发经验进行短暂的总结。最近进行MCU平台的更替,老项目的升级换代以及新产品的开发,主控从GD的M0平台升级到N32的M4平台,开发的代码需要进行全面的替换,仅此对核心的外设应用做一个记录。此文是为了记录主从SPI的开发记录,其中一个SPI设置为主从模式,用于与传感器进行通信,另一个SPI作为从设备,用于向外部设备返回数据,考虑时间的要求,两个SPI均使用DMA进行数据搬运。

static/image/hrline/1.gifhttps://bbs.21ic.com/static/image/hrline/1.gif

一、SPI的相关GPIO初始化void SPI_GPIO_Inti(void)
{
//    RCC_Pclk2_Config(RCC_HCLK_DIV1);
      EXTI_InitType EXTI_InitStructure;
    /* Enable peripheral clocks --------------------------------------------------*/
    /* spi clock enable */
    RCC_APB2_Peripheral_Clock_Enable(SPI_MASTER_PERIPH | SPI_SLAVE_PERIPH | RCC_APB2_PERIPH_AFIO);

    /* GPIO Periph clock enable */
    RCC_AHB_Peripheral_Clock_Enable(SPI_MASTER_PERIPH_GPIO | SPI_SLAVE_PERIPH_GPIO | RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB);

    GPIO_InitType GPIO_InitStructure;

    GPIO_Structure_Initialize(&GPIO_InitStructure);
      
    GPIO_InitStructure.Pin      = SPI_MASTER_MOSI_PIN | SPI_MASTER_CLK_PIN | SPI_MASTER_MISO_PIN ;
    GPIO_InitStructure.GPIO_Mode= GPIO_MODE_AF_PP;
    GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
    GPIO_InitStructure.GPIO_Alternate = SPI_MASTER_GPIO_ALTERNATE;
    GPIO_Peripheral_Initialize(SPI_MASTER_GPIO, &GPIO_InitStructure);      
               
    GPIO_InitStructure.Pin      = GPIO_PIN_11 | GPIO_PIN_12 ;
    GPIO_InitStructure.GPIO_Mode= GPIO_MODE_AF_PP;
    GPIO_InitStructure.GPIO_Alternate = GPIO_AF1_SPI2;
    GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
      
    GPIO_InitStructure.Pin      = GPIO_PIN_6 ;
    GPIO_InitStructure.GPIO_Mode= GPIO_MODE_AF_PP;
    GPIO_InitStructure.GPIO_Alternate = GPIO_AF6_SPI2;
    GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);      

    GPIO_InitStructure.Pin      = GPIO_PIN_15 ;
    GPIO_InitStructure.GPIO_Mode= GPIO_MODE_INPUT;
    GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);      

      GPIO_InitStructure.Pin            = GPIO_PIN_3;   
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_INPUT;
    GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);      

      GPIO_InitStructure.Pin            = SPI_MASTER_NSS_PIN;   
    GPIO_InitStructure.GPIO_Mode      = GPIO_MODE_OUT_PP;
      GPIO_InitStructure.GPIO_Current   = GPIO_DS_4MA;
    GPIO_Peripheral_Initialize(SPI_MASTER_GPIO, &GPIO_InitStructure);      

//    /* Configure key EXTI Line to key input Pin */
    GPIO_EXTI_Line_Set(EXTI_LINE_SOURCE15, AFIO_EXTI_PA15);

    /* Configure key EXTI line */
    EXTI_InitStructure.EXTI_Line    = EXTI_LINE15;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Peripheral_Initializes(&EXTI_InitStructure);      
}
SPI主使用的是SPI_MASTER(下文均使用SPI1),使用的GPIO如下,均为GPIOA:#define SPI_MASTER_MISO_PIN       GPIO_PIN_6#define SPI_MASTER_MOSI_PIN       GPIO_PIN_7#define SPI_MASTER_CLK_PIN      GPIO_PIN_5#define SPI_MASTER_NSS_PIN      GPIO_PIN_4
其中NSS使用软件NSS,所以此处的GPIO配置为输出而非复用。
SPI从设备使用的是SPI_SLAVE(下文均使用SPI2),使用的GPIO如下:#define SPI_SLAVE_MISO_PIN      GPIO_PIN_11#define SPI_SLAVE_MOSI_PIN      GPIO_PIN_12#define SPI_SLAVE_CLK_PIN         GPIO_PIN_6      //PB6#define SPI_SLAVE_NSS_PIN         GPIO_PIN_15      //PA15
从设备的时钟和NSS均来自外部主设备,所以NSS设置为输入,并开启外部中断(此处目的是为了适配自己的项目需求)。具体的复用管脚重映射参加下图,图片来自于官方的用户手册。
static/image/hrline/1.gifstatic/image/hrline/1.gif

二、SPI初始化void SPI_Init(void)
{
      SPI_InitType SPI_InitStructure;
      
    /* GPIO configuration ------------------------------------------------------*/
    SPI_GPIO_Inti();
    /* log configuration ------------------------------------------------------*/
      NVIC_Configuration();
    /* Initializes the variable */
    SPI_I2S_Reset(SPI_MASTER);
      SPI_I2S_Reset(SPI_SLAVE);
    /* DMA configuration ------------------------------------------------------*/
      SPI_DMA_Configuration();
    /* SPI_MASTER configuration ------------------------------------------------------*/
    SPI_Initializes_Structure(&SPI_InitStructure);
    SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
    SPI_InitStructure.SpiMode       = SPI_MODE_MASTER;
    SPI_InitStructure.DataLen       = SPI_DATA_SIZE_8BITS;
    SPI_InitStructure.CLKPOL      = SPI_CLKPOL_LOW;
    SPI_InitStructure.CLKPHA      = SPI_CLKPHA_SECOND_EDGE;
    SPI_InitStructure.NSS         = SPI_NSS_SOFT;
    /* It is recommended that the SPI master mode of the C version chips should not exceed 18MHz */
    SPI_InitStructure.BaudRatePres= SPI_BR_PRESCALER_4;
    SPI_InitStructure.FirstBit      = SPI_FB_MSB;
//    SPI_InitStructure.CRCPoly       = 7;
    SPI_Initializes(SPI_MASTER, &SPI_InitStructure);
      SPI_SS_Output_Enable(SPI_MASTER);
//      SPI_Set_Nss_Level(SPI_MASTER, SPI_NSS_HIGH);

    SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
    SPI_InitStructure.SpiMode       = SPI_MODE_SLAVE;
    SPI_InitStructure.DataLen       = SPI_DATA_SIZE_8BITS;
    SPI_InitStructure.CLKPOL      = SPI_CLKPOL_LOW;
    SPI_InitStructure.CLKPHA      = SPI_CLKPHA_SECOND_EDGE;
    SPI_InitStructure.NSS         = SPI_NSS_SOFT;
//    SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
    SPI_Initializes(SPI_SLAVE, &SPI_InitStructure);
    SPI_Set_Nss_Level(SPI_SLAVE, SPI_NSS_LOW);
      
    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);
      SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_TX);
    SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_RX);
    /* Enable SPI_MASTER */

//      SPI_I2S_Interrupts_Enable(SPI_SLAVE, SPI_I2S_INT_RNE);
    SPI_ON(SPI_MASTER);
      SPI_ON(SPI_SLAVE);      
}在SPI外设的初始化中调用GPIO初始化函数、中断初始化函数和DMA初始化函数,完成SPI的整体初始化,中断以及DMA在下文介绍。SPI1作为主设备,配置为双向通信,根据从设备的SPI协议定义CPOL和CPHA,NSS采用软件模式,SPI挂载在PCLK上,最大时钟频率是64MHz,这里进行4分频,将主SPI的频率设置为16MHz,使用SPI的主模式需要调用SPI_SS_Output_Enable(SPI_MASTER);函数,否则无法正常使用,此处与之前的使用有所区别。SPI2作为从设备,无需设置频率,从设备的时钟频率会跟随主设备,但是需要调用SPI_Set_Nss_Level(SPI_SLAVE, SPI_NSS_LOW);定义NSS的生效电平。因为使用了DMA,此处会使能DMA。
static/image/hrline/1.gifstatic/image/hrline/1.gif


三、SPI的DMA及中断配置void SPI_DMA_Configuration(void)
{
       
    DMA_InitType DMA_InitStructure;
    DMA_Reset(DMA_CH1);
    DMA_Reset(DMA_CH2);
    DMA_Reset(DMA_CH3);
    DMA_Reset(DMA_CH4);

    /* SPI_MASTER TX DMA config */
    DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_TX_BUF;
    DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_BYTE;
    DMA_InitStructure.MemoryInc = DMA_MEM_INC_MODE_ENABLE;
    DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
    DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_MASTER->DAT;
    DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_BYTE;
    DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_MODE_DISABLE;
    DMA_InitStructure.BufSize = BufferSize;
    DMA_InitStructure.CircularMode = DMA_CIRCULAR_MODE_DISABLE;
    DMA_InitStructure.Mem2Mem = DMA_MEM2MEM_DISABLE;
    DMA_InitStructure.Priority = DMA_CH_PRIORITY_MEDIUM;
    DMA_Initializes(DMA_CH1, &DMA_InitStructure);
    DMA_Channel_Request_Remap(DMA_CH1, SPI_MASTER_DMA_TX_CH);
   
    /* SPI_MASTER RX DMA config */
    DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_RX_BUF;
    DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
    DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGH;
    DMA_Initializes(DMA_CH2, &DMA_InitStructure);
    DMA_Channel_Request_Remap(DMA_CH2, SPI_MASTER_DMA_RX_CH);
   
    /* SPI_Slave TX DMA config */
    DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_TX_BUF;
    DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
    DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_SLAVE->DAT;
    DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
    DMA_Initializes(DMA_CH3, &DMA_InitStructure);
    DMA_Channel_Request_Remap(DMA_CH3, SPI_SLAVE_DMA_TX_CH);
   
    /* SPI_Slave RX DMA config */
    DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_RX_BUF;
    DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
    DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
    DMA_Initializes(DMA_CH4, &DMA_InitStructure);
    DMA_Channel_Request_Remap(DMA_CH4, SPI_SLAVE_DMA_RX_CH);

//        DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);
//        DMA_Interrupts_Enable(DMA_CH1, DMA_INT_TXC);
       
        DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
        DMA_Interrupts_Enable(DMA_CH3, DMA_INT_TXC);
//        DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_HTX);
//        DMA_Interrupts_Enable(DMA_CH3, DMA_INT_HTX);       
    DMA_Channel_Enable(DMA_CH3);
    DMA_Channel_Enable(DMA_CH4);
    DMA_Channel_Enable(DMA_CH1);
    DMA_Channel_Enable(DMA_CH2);

}
void NVIC_Configuration(void)
{
    NVIC_InitType NVIC_InitStructure;
    NVIC_Priority_Group_Set(NVIC_PER1_SUB3_PRIORITYGROUP);
//   
    NVIC_InitStructure.NVIC_IRQChannel                   = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority      = NVIC_SUB_PRIORITY_0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Initializes(&NVIC_InitStructure);
//       
    NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Initializes(&NVIC_InitStructure);
       
    NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Initializes(&NVIC_InitStructure);       
       
        NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Initializes(&NVIC_InitStructure);
}

DMA的配置比较简单,各个平台的配置都很相似,配置内存地址和外设地址,内存地址均为自定义的数组首地址,外设地址为SPI的数据寄存器,发送时DMA将数组数据-->SPI数据寄存器,SPI启动时变会将数据通过SPI外设送出,接收时数据保存进SPI的数据寄存器,DAM将数据搬运到数组中。中断初始化函数中使能了DMA的通道发送完成中断和SPI的接收完成中断以及SPI2的NSS下降沿中断。(此处的中断均是备选,实际无需要可不开启)四、SPI的DMA收发函数由于两个DMA均采用正常模式,在执行完一次数据搬运以后便会停止,所以此处需要包装两个函数,用于启动DMA。void SPI1_DMA_SR(void)
{
      bool P1,P2;
      GPIOA->PBC = GPIO_PIN_4;
//      SPI1->CTRL2 |= SPI_I2S_DMA_TX;
//      SPI1->CTRL2 |= SPI_I2S_DMA_RX;
      DMA_CH1->CHCFG &= DMA_CHANNEL_DISABLE;      
      DMA_CH1->TXNUM = BufferSize;
      DMA_CH1->CHCFG |= DMA_CHANNEL_ENABLE;
      DMA_CH2->CHCFG &= DMA_CHANNEL_DISABLE;      
      DMA_CH2->TXNUM = BufferSize;
      DMA_CH2->CHCFG |= DMA_CHANNEL_ENABLE;
      while(SET == SPI_I2S_Flag_Status_Get(SPI_MASTER,SPI_I2S_FLAG_BUSY));
      GPIOA->PBSC = GPIO_PIN_4;
               
               
}
void SPI2_DMA_SR(void)
{

//    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
//    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);      
//    DMA_Channel_Disable(DMA_CH3);
//      DMA_Buffer_Size_Config(DMA_CH3,BufferSize);
//      DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
//      DMA_Channel_Enable(DMA_CH3);
//    DMA_Channel_Disable(DMA_CH4);
//      DMA_Buffer_Size_Config(DMA_CH4,BufferSize);
//    DMA_Memory_Address_Config(DMA_CH4,(uint32_t)&READ_ANGLE_RX_BUF);
//    DMA_Channel_Enable(DMA_CH4);

      DMA_CH3->CHCFG &= DMA_CHANNEL_DISABLE;      
      DMA_CH3->TXNUM = BufferSize;
      DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
      DMA_CH3->CHCFG |= DMA_CHANNEL_ENABLE;
      DMA_CH4->CHCFG &= DMA_CHANNEL_DISABLE;      
      DMA_CH4->TXNUM = BufferSize;
      DMA_CH4->CHCFG |= DMA_CHANNEL_ENABLE;
}
void DMA_Channel1_IRQHandler(void)
{
      if(DMA_Interrupt_Status_Get(DMA, DMA_CH1_INT_TXC) == SET)
      {
                DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);

    while(DMA_Flag_Status_Get(DMA, DMA_CH2_TXCF) == RESET)
      ;
                /*用户代码*/
      }
}
void DMA_Channel3_IRQHandler(void)
{
      if(DMA_Interrupt_Status_Get(DMA, DMA_CH3_INT_TXC) == SET)
      {
                DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
                while(SET == SPI_I2S_Flag_Status_Get(SPI_SLAVE,SPI_I2S_FLAG_BUSY));
                //SLAVE_NSS_FLAG = 0;      
                SPI2_DMA_SR();
      }               

}此处封装两个函数:void SPI1_DMA_SR(void)   用于启动主设备SPI1进行传感器数据的读取。void SPI2_DMA_SR(void)   启动从设备SPI2用于向外部发送数据。连个函数均采用寄存器的方式编写,目的是为了尽可能的缩小DMA的启动时间,实测提升很大呦。不然就会出现NSS下拉以后很久才会出现时钟信号,我在DMA的启动函数中都会做此处理。而且为了方便阅读,在SPI2_DMA_SR()函数中我保留了库函数的方式,与下文的寄存器方式是对应的。在DMA通道3(对应SPI2的接收)中断服务函数中,判断中断触发,表示数据已经发送完毕,此时需要调用SPI2_DMA_SR();再次启动DMA,防止下次指令到来无法送出数据。源代码因为涉及到项目,无法发出,如果有问题可回复,我看到会给解答。

abner_ma 发表于 2023-10-25 15:12

一点深度没有,还打赏

yangjiaxu 发表于 2023-10-31 16:51

SPI+DMA确实是一个非常给力的搭配,会很快的实现数据的交互

lemonboard 发表于 2023-11-1 17:45

还可以这么玩啊!
晚上回家我也试试去

菜鸟一枚01 发表于 2023-12-21 16:41

楼主可以加一下我吗?我的不好使,和您配置的一样啊

菜鸟一枚01 发表于 2023-12-21 16:41

楼主加我微信18204508972
页: [1]
查看完整版本: 基于N32G430的SPI+DMA主从通信