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

前言

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

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

此文是为了记录主从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协议定义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。


 



 

三、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[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的接收完成中断以及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,防止下次指令到来无法送出数据。

源代码因为涉及到项目,无法发出,如果有问题可回复,我看到会给解答。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3334546-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值