发新帖本帖赏金 120.00元(功能说明)我要提问
返回列表
打印
[N32G430]

基于N32G430的SPI+DMA主从通信

[复制链接]
6467|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 呐咯密密 于 2023-10-20 14:07 编辑

#申请原创#
前言

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

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

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





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




二、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协议定义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及中断配置
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[0];
    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[0];
    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[0];
    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[0];
    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的接收完成中断以及SPI2NSS下降沿中断。(此处的中断均是备选,实际无需要可不开启)

四、SPIDMA收发函数

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

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



使用特权

评论回复

打赏榜单

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

沙发
abner_ma| | 2023-10-25 15:12 | 只看该作者
一点深度没有,还打赏

使用特权

评论回复
评论
qintian0303 2023-10-31 17:16 回复TA
写就行了 
板凳
yangjiaxu| | 2023-10-31 16:51 | 只看该作者
SPI+DMA确实是一个非常给力的搭配,会很快的实现数据的交互

使用特权

评论回复
地板
lemonboard| | 2023-11-1 17:45 | 只看该作者
还可以这么玩啊!
晚上回家我也试试去

使用特权

评论回复
5
菜鸟一枚01| | 2023-12-21 16:41 | 只看该作者
楼主可以加一下我吗?我的不好使,和您配置的一样啊

使用特权

评论回复
6
菜鸟一枚01| | 2023-12-21 16:41 | 只看该作者
楼主加我微信18204508972

使用特权

评论回复
发新帖 本帖赏金 120.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

483

主题

3815

帖子

47

粉丝