返回列表 发新帖我要提问本帖赏金: 120.00元(功能说明)

[N32G430] 基于N32G430的SPI+DMA主从通信

[复制链接]
7598|6
 楼主| 呐咯密密 发表于 2023-10-20 14:01 | 显示全部楼层 |阅读模式
本帖最后由 呐咯密密 于 2023-10-20 14:07 编辑

#申请原创#
前言

又是许久没有更新原创文章了,最近因为项目的积压,导致没有闲置时间进行开发的记录积累,乘着空闲时间,将最近的开发经验进行短暂的总结。

最近进行MCU平台的更替,老项目的升级换代以及新产品的开发,主控从GDM0平台升级到N32M4平台,开发的代码需要进行全面的替换,仅此对核心的外设应用做一个记录。

此文是为了记录主从SPI的开发记录,其中一个SPI设置为主从模式,用于与传感器进行通信,另一个SPI作为从设备,用于向外部设备返回数据,考虑时间的要求,两个SPI均使用DMA进行数据搬运。





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

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

  10.     GPIO_InitType GPIO_InitStructure;

  11.     GPIO_Structure_Initialize(&GPIO_InitStructure);
  12.         
  13.     GPIO_InitStructure.Pin        = SPI_MASTER_MOSI_PIN | SPI_MASTER_CLK_PIN | SPI_MASTER_MISO_PIN ;
  14.     GPIO_InitStructure.GPIO_Mode  = GPIO_MODE_AF_PP;
  15.     GPIO_InitStructure.GPIO_Slew_Rate = GPIO_SLEW_RATE_FAST;
  16.     GPIO_InitStructure.GPIO_Alternate = SPI_MASTER_GPIO_ALTERNATE;
  17.     GPIO_Peripheral_Initialize(SPI_MASTER_GPIO, &GPIO_InitStructure);        
  18.                
  19.     GPIO_InitStructure.Pin        = GPIO_PIN_11 | GPIO_PIN_12 ;
  20.     GPIO_InitStructure.GPIO_Mode  = GPIO_MODE_AF_PP;
  21.     GPIO_InitStructure.GPIO_Alternate = GPIO_AF1_SPI2;
  22.     GPIO_Peripheral_Initialize(GPIOA, &GPIO_InitStructure);
  23.         
  24.     GPIO_InitStructure.Pin        = GPIO_PIN_6 ;
  25.     GPIO_InitStructure.GPIO_Mode  = GPIO_MODE_AF_PP;
  26.     GPIO_InitStructure.GPIO_Alternate = GPIO_AF6_SPI2;
  27.     GPIO_Peripheral_Initialize(GPIOB, &GPIO_InitStructure);        

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

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

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

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

  40.     /* Configure key EXTI line */
  41.     EXTI_InitStructure.EXTI_Line    = EXTI_LINE15;
  42.     EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
  43.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  44.     EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  45.     EXTI_Peripheral_Initializes(&EXTI_InitStructure);        
  46. }

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设置为输入,并开启外部中断(此处目的是为了适配自己的项目需求)。具体的复用管脚重映射参加下图,图片来自于官方的用户手册。

622606532168040c55.png



二、SPI初始化
  1. void SPI_Init(void)
  2. {
  3.         SPI_InitType SPI_InitStructure;
  4.         
  5.     /* GPIO configuration ------------------------------------------------------*/
  6.     SPI_GPIO_Inti();
  7.     /* log configuration ------------------------------------------------------*/
  8.         NVIC_Configuration();
  9.     /* Initializes the variable */
  10.     SPI_I2S_Reset(SPI_MASTER);
  11.         SPI_I2S_Reset(SPI_SLAVE);
  12.     /* DMA configuration ------------------------------------------------------*/
  13.         SPI_DMA_Configuration();
  14.     /* SPI_MASTER configuration ------------------------------------------------------*/
  15.     SPI_Initializes_Structure(&SPI_InitStructure);
  16.     SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
  17.     SPI_InitStructure.SpiMode       = SPI_MODE_MASTER;
  18.     SPI_InitStructure.DataLen       = SPI_DATA_SIZE_8BITS;
  19.     SPI_InitStructure.CLKPOL        = SPI_CLKPOL_LOW;
  20.     SPI_InitStructure.CLKPHA        = SPI_CLKPHA_SECOND_EDGE;
  21.     SPI_InitStructure.NSS           = SPI_NSS_SOFT;
  22.     /* It is recommended that the SPI master mode of the C version chips should not exceed 18MHz */
  23.     SPI_InitStructure.BaudRatePres  = SPI_BR_PRESCALER_4;
  24.     SPI_InitStructure.FirstBit      = SPI_FB_MSB;
  25. //    SPI_InitStructure.CRCPoly       = 7;
  26.     SPI_Initializes(SPI_MASTER, &SPI_InitStructure);
  27.         SPI_SS_Output_Enable(SPI_MASTER);
  28. //        SPI_Set_Nss_Level(SPI_MASTER, SPI_NSS_HIGH);

  29.     SPI_InitStructure.DataDirection = SPI_DIR_DOUBLELINE_FULLDUPLEX;
  30.     SPI_InitStructure.SpiMode       = SPI_MODE_SLAVE;
  31.     SPI_InitStructure.DataLen       = SPI_DATA_SIZE_8BITS;
  32.     SPI_InitStructure.CLKPOL        = SPI_CLKPOL_LOW;
  33.     SPI_InitStructure.CLKPHA        = SPI_CLKPHA_SECOND_EDGE;
  34.     SPI_InitStructure.NSS           = SPI_NSS_SOFT;
  35. //    SPI_InitStructure.SpiMode = SPI_MODE_SLAVE;
  36.     SPI_Initializes(SPI_SLAVE, &SPI_InitStructure);
  37.     SPI_Set_Nss_Level(SPI_SLAVE, SPI_NSS_LOW);
  38.         
  39.     SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
  40.     SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);
  41.         SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_TX);
  42.     SPI_I2S_DMA_Transfer_Enable(SPI_MASTER, SPI_I2S_DMA_RX);
  43.     /* Enable SPI_MASTER */

  44. //        SPI_I2S_Interrupts_Enable(SPI_SLAVE, SPI_I2S_INT_RNE);
  45.     SPI_ON(SPI_MASTER);
  46.         SPI_ON(SPI_SLAVE);        
  47. }

SPI外设的初始化中调用GPIO初始化函数、中断初始化函数和DMA初始化函数,完成SPI的整体初始化,中断以及DMA在下文介绍。

SPI1作为主设备,配置为双向通信,根据从设备的SPI协议定义CPOLCPHANSS采用软件模式,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





三、SPIDMA及中断配置
  1. void SPI_DMA_Configuration(void)
  2. {
  3.        
  4.     DMA_InitType DMA_InitStructure;
  5.     DMA_Reset(DMA_CH1);
  6.     DMA_Reset(DMA_CH2);
  7.     DMA_Reset(DMA_CH3);
  8.     DMA_Reset(DMA_CH4);

  9.     /* SPI_MASTER TX DMA config */
  10.     DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_TX_BUF[0];
  11.     DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_BYTE;
  12.     DMA_InitStructure.MemoryInc = DMA_MEM_INC_MODE_ENABLE;
  13.     DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
  14.     DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_MASTER->DAT;
  15.     DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_BYTE;
  16.     DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_MODE_DISABLE;
  17.     DMA_InitStructure.BufSize = BufferSize;
  18.     DMA_InitStructure.CircularMode = DMA_CIRCULAR_MODE_DISABLE;
  19.     DMA_InitStructure.Mem2Mem = DMA_MEM2MEM_DISABLE;
  20.     DMA_InitStructure.Priority = DMA_CH_PRIORITY_MEDIUM;
  21.     DMA_Initializes(DMA_CH1, &DMA_InitStructure);
  22.     DMA_Channel_Request_Remap(DMA_CH1, SPI_MASTER_DMA_TX_CH);
  23.    
  24.     /* SPI_MASTER RX DMA config */
  25.     DMA_InitStructure.MemAddr = (uint32_t)&READ_Data_RX_BUF[0];
  26.     DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
  27.     DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGH;
  28.     DMA_Initializes(DMA_CH2, &DMA_InitStructure);
  29.     DMA_Channel_Request_Remap(DMA_CH2, SPI_MASTER_DMA_RX_CH);
  30.    
  31.     /* SPI_Slave TX DMA config */
  32.     DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_TX_BUF[0];
  33.     DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
  34.     DMA_InitStructure.PeriphAddr = (uint32_t)&SPI_SLAVE->DAT;
  35.     DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
  36.     DMA_Initializes(DMA_CH3, &DMA_InitStructure);
  37.     DMA_Channel_Request_Remap(DMA_CH3, SPI_SLAVE_DMA_TX_CH);
  38.    
  39.     /* SPI_Slave RX DMA config */
  40.     DMA_InitStructure.MemAddr = (uint32_t)&READ_ANGLE_RX_BUF[0];
  41.     DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC;
  42.     DMA_InitStructure.Priority = DMA_CH_PRIORITY_HIGHEST;
  43.     DMA_Initializes(DMA_CH4, &DMA_InitStructure);
  44.     DMA_Channel_Request_Remap(DMA_CH4, SPI_SLAVE_DMA_RX_CH);

  45. //        DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);
  46. //        DMA_Interrupts_Enable(DMA_CH1, DMA_INT_TXC);
  47.        
  48.         DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
  49.         DMA_Interrupts_Enable(DMA_CH3, DMA_INT_TXC);
  50. //        DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_HTX);
  51. //        DMA_Interrupts_Enable(DMA_CH3, DMA_INT_HTX);       
  52.     DMA_Channel_Enable(DMA_CH3);
  53.     DMA_Channel_Enable(DMA_CH4);  
  54.     DMA_Channel_Enable(DMA_CH1);
  55.     DMA_Channel_Enable(DMA_CH2);

  56. }
  57. void NVIC_Configuration(void)
  58. {
  59.     NVIC_InitType NVIC_InitStructure;
  60.     NVIC_Priority_Group_Set(NVIC_PER1_SUB3_PRIORITYGROUP);
  61. //   
  62.     NVIC_InitStructure.NVIC_IRQChannel                   = EXTI15_10_IRQn;
  63.     NVIC_InitStructure.NVIC_IRQChannelSubPriority        = NVIC_SUB_PRIORITY_0;
  64.     NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
  65.     NVIC_Initializes(&NVIC_InitStructure);
  66. //       
  67.     NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel1_IRQn;
  68.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
  69.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
  70.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  71.     NVIC_Initializes(&NVIC_InitStructure);
  72.        
  73.     NVIC_InitStructure.NVIC_IRQChannel = DMA_Channel3_IRQn;
  74.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
  75.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_2;
  76.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  77.     NVIC_Initializes(&NVIC_InitStructure);       
  78.        
  79.         NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
  80.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PER_PRIORITY_0;
  81.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_SUB_PRIORITY_1;
  82.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  83.     NVIC_Initializes(&NVIC_InitStructure);
  84. }


DMA的配置比较简单,各个平台的配置都很相似,配置内存地址和外设地址,内存地址均为自定义的数组首地址,外设地址为SPI的数据寄存器,发送时DMA将数组数据-->SPI数据寄存器,SPI启动时变会将数据通过SPI外设送出,接收时数据保存进SPI的数据寄存器,DAM将数据搬运到数组中。

中断初始化函数中使能了DMA的通道发送完成中断和SPI的接收完成中断以及SPI2NSS下降沿中断。(此处的中断均是备选,实际无需要可不开启)

四、SPIDMA收发函数

由于两个DMA均采用正常模式,在执行完一次数据搬运以后便会停止,所以此处需要包装两个函数,用于启动DMA

  1. void SPI1_DMA_SR(void)
  2. {
  3.         bool P1,P2;
  4.         GPIOA->PBC = GPIO_PIN_4;
  5. //        SPI1->CTRL2 |= SPI_I2S_DMA_TX;
  6. //        SPI1->CTRL2 |= SPI_I2S_DMA_RX;
  7.         DMA_CH1->CHCFG &= DMA_CHANNEL_DISABLE;        
  8.         DMA_CH1->TXNUM = BufferSize;
  9.         DMA_CH1->CHCFG |= DMA_CHANNEL_ENABLE;
  10.         DMA_CH2->CHCFG &= DMA_CHANNEL_DISABLE;        
  11.         DMA_CH2->TXNUM = BufferSize;
  12.         DMA_CH2->CHCFG |= DMA_CHANNEL_ENABLE;
  13.         while(SET == SPI_I2S_Flag_Status_Get(SPI_MASTER,SPI_I2S_FLAG_BUSY));
  14.         GPIOA->PBSC = GPIO_PIN_4;
  15.                
  16.                
  17. }
  18. void SPI2_DMA_SR(void)
  19. {

  20. //    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_TX);
  21. //    SPI_I2S_DMA_Transfer_Enable(SPI_SLAVE, SPI_I2S_DMA_RX);        
  22. //    DMA_Channel_Disable(DMA_CH3);
  23. //        DMA_Buffer_Size_Config(DMA_CH3,BufferSize);
  24. //        DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
  25. //        DMA_Channel_Enable(DMA_CH3);
  26. //    DMA_Channel_Disable(DMA_CH4);
  27. //        DMA_Buffer_Size_Config(DMA_CH4,BufferSize);
  28. //    DMA_Memory_Address_Config(DMA_CH4,(uint32_t)&READ_ANGLE_RX_BUF);
  29. //    DMA_Channel_Enable(DMA_CH4);

  30.         DMA_CH3->CHCFG &= DMA_CHANNEL_DISABLE;        
  31.         DMA_CH3->TXNUM = BufferSize;
  32.         DMA_Memory_Address_Config(DMA_CH3,(uint32_t)&READ_ANGLE_TX_BUF);
  33.         DMA_CH3->CHCFG |= DMA_CHANNEL_ENABLE;
  34.         DMA_CH4->CHCFG &= DMA_CHANNEL_DISABLE;        
  35.         DMA_CH4->TXNUM = BufferSize;
  36.         DMA_CH4->CHCFG |= DMA_CHANNEL_ENABLE;
  37. }
  38. void DMA_Channel1_IRQHandler(void)
  39. {
  40.         if(DMA_Interrupt_Status_Get(DMA, DMA_CH1_INT_TXC) == SET)
  41.         {
  42.                 DMA_Interrupt_Status_Clear(DMA, DMA_CH1_INT_TXC);

  43.     while(DMA_Flag_Status_Get(DMA, DMA_CH2_TXCF) == RESET)
  44.         ;
  45.                 /*用户代码*/
  46.         }
  47. }
  48. void DMA_Channel3_IRQHandler(void)
  49. {
  50.         if(DMA_Interrupt_Status_Get(DMA, DMA_CH3_INT_TXC) == SET)
  51.         {
  52.                 DMA_Interrupt_Status_Clear(DMA, DMA_CH3_INT_TXC);
  53.                 while(SET == SPI_I2S_Flag_Status_Get(SPI_SLAVE,SPI_I2S_FLAG_BUSY));
  54.                 //SLAVE_NSS_FLAG = 0;        
  55.                 SPI2_DMA_SR();
  56.         }               

  57. }

此处封装两个函数:

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,防止下次指令到来无法送出数据。

源代码因为涉及到项目,无法发出,如果有问题可回复,我看到会给解答。



打赏榜单

21小跑堂 打赏了 120.00 元 2023-10-24
理由:恭喜通过原创审核!期待您更多的原创作品~

abner_ma 发表于 2023-10-25 15:12 | 显示全部楼层
一点深度没有,还打赏

评论

写就行了  发表于 2023-10-31 17:16
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
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

568

主题

4085

帖子

56

粉丝
快速回复 在线客服 返回列表 返回顶部