华大HC32F460 SPI DMA通信实验代码详细解析

声明:以下内容均为本人学习心得。

一、基础知识。

华大HC32F460 提供的SPI是4线式和3线式。搭载4个通道的串行外设接口,支持高速全双工串行同步传输。

4线式:SCK、MOSI、MISO、SS0~SS3。3线式:SCK、MOSI、MISO。

SPI数据发送时:传送数据先进入发送缓冲器(TX_BUFF),再将TX_BUFF的数据复制到移位寄存器(shifter),shifter依次发出数据;SPI数据接受时,数据从shifter依次移入,移入完成后再将shifter的数据复制到接收缓冲器(RX_BUFF)。数据传输时,根据移位顺序控制位SPI_CFG2.LSBF和奇偶校验控制位SPI_CR1.PAE的设置分4种情况:

  1. MSB先传,奇偶校验无效。
  2. LSB先传,奇偶校验无效。
  3. MSB先传,奇偶校验有效时。
  4. LSB先传,奇偶校验有效时。

传送格式:

  1. CPHA=0的情况,当SPI_CFG2.CPHA位为0时,SPI在SCK的奇数边沿进行数据采样,偶数边沿进行数据更新。
  2. CPHA=1的情况,当SPI_CFG2.CPHA位为1时,SPI在SCK的奇数边沿进行数据更新,偶数边沿进行数据采样。

通信方式:

  1. 全双工同步串行通信方式

当SPI_CR1.TXMDS位为“0”时,SPI运行在全双工同步串行通信方式。

注意的是:传输结束时,如果接收数据缓冲寄存器为空,SPI将会把接收到的数据从移位寄存器复制到接收数据缓冲寄存器中,接收数据缓冲寄存器满的标志位被置成1(RDFF),并产生一个接收数据满的中断请求(SPRI)。 如果接收数据缓冲寄存器还保持着上次收到的数据而没有被系统读取,SPI会将数据过载标志位置成1,本次数据接收无效,接收移位寄存器中的数据将被丢弃。

  1. 只进行发送通信方式

当SPI_CR1.TXMDS位为“1”时,SPI运行在只发送通信方式。

SPI主机模式的初始化:

①设置通信配置寄存器1(SPI_CFG1),包括有波特率的设定,使用帧数的设定,各种迟延时间的设定等。

②设置通信配置寄存器2(SPI_CFG2),包括有SS电平设定,数据移位顺序设定,各种延迟的允许位的设定,数据格式及时钟极性相位的设定等。

③如需要使用中断,请设置系统的中断寄存器。

④如需要使用DMA,请设置DMA的相关寄存器。

⑤设定输入输出管脚。

⑥设定SPI控制寄存器SPI_CR1,包括有模式及运行方式的设定,自诊断功能的设定,奇偶校验的设定等。

⑦确认SPI_CR1寄存器的设置。

⑧清除各种标志位。

⑨设置中断许可位

⑩将控制寄存器SPI_CR1的SPE位设置成1,动作开始。

SPI从机模式的初始化:

①设置通信配置寄存器1(SPI_CFG1),包括使用帧数的设定。

②设置通信配置寄存器2(SPI_CFG2),包括有传输速率、数据格式及时钟极性相位的设定等。

③如需要使用中断,请设置系统的中断寄存器。

④如需要使用DMA,请设置DMA的相关寄存器。

⑤设定输入输出管脚。

⑥设定SPI控制寄存器SPI_CR1,包括有模式及运行方式的设定,自诊断功能的设定,奇偶校验的设定等。

⑦确认SPI_CR1寄存器的设置。

⑧清除各种标志位。

⑨设置中断许可位

⑩将控制寄存器SPI_CR1的SPE位设置成1,动作开始。

SPI作为主机时的数据传送处理流程:

①等待数据发送缓冲寄存器空的中断或通过轮询方式确认数据发送缓冲寄存器处于空状态。

②向数据寄存器SPI_DR写入要发送的数据。

③重复①②步骤直到最后一个数据发送完成。

④将发送数据寄存器空中断的允许位TXIE清零,同时将SPI闲置状态中断允许位IDIE设为1。

⑤发送SPI闲置状态中断。

⑥将SPE置0,停止SPI动作,同时将IDIE清零。

数据接收处理流程:

①等待数据接收缓冲寄存器空的中断或通过轮询方式确认数据接收缓冲寄存器处于满状态。

②通过访问SPI_DR从接收缓冲寄存器读取数据

③重复①②步骤直到最后一个接收数据被读取。

④将数据接收缓冲寄存器满的中断允许位RXIE清零。

通信错误处理流程:

①等待通信错误中断或者通过轮询方式确认通信错误标志位(MODFERF/OVRERF/UDRERF/PERF)被置成1。

②确认SS0状态,排除模式故障错误。

③将SPE清零,停止SPI动作。

④将所有SPI中断允许位清零,屏蔽SPI中断。

⑤通过错误标志位确定通信错误种类,进行通讯错误处理。

⑥将错误标志位清零。

⑦启动SPI,重新开始通信。

二、实验代码

实验样例配置通过宏开关选择主机或从机模式,将主机代码下载到一块板子,从机代码下载到另一块板子,通过排线将两块板子的SPI接口相连接,主机按下按键K2,触发一次数据收发,主机和从机对收到的数据和发送的数据进行比较,相同的LED_BLUE亮,不同则LED_RED亮。

设置宏:EXAMPLE_SPI_MASTER_SLAVE = SPI_MASTER为主机模式。

              EXAMPLE_SPI_MASTER_SLAVE = SPI_SLAVE为从机模式。

①各参数配置

/* unlock/lock peripheral */
#define EXAMPLE_PERIPH_WE               (LL_PERIPH_GPIO | LL_PERIPH_EFM | LL_PERIPH_FCG | \
                                         LL_PERIPH_PWC_CLK_RMU | LL_PERIPH_SRAM)
#define EXAMPLE_PERIPH_WP               (LL_PERIPH_EFM | LL_PERIPH_FCG | LL_PERIPH_SRAM)

/* Configuration for Example */
#define EXAMPLE_SPI_MASTER_SLAVE        (SPI_MASTER)
#define EXAMPLE_SPI_BUF_LEN             (128UL)  //SPI接收或发送缓存区大小

/* SPI definition */
#define SPI_UNIT                        (CM_SPI1)
#define SPI_CLK                         (FCG1_PERIPH_SPI1)
#define SPI_TX_EVT_SRC                  (EVT_SRC_SPI1_SPTI)//SPI发送中断序号:300
#define SPI_RX_EVT_SRC                  (EVT_SRC_SPI1_SPRI)//SPI接收中断序号:299

/* DMA definition */
#define DMA_UNIT                        (CM_DMA1)
#define DMA_CLK                         (FCG0_PERIPH_DMA1 | FCG0_PERIPH_AOS)
#define DMA_TX_CH                       (DMA_CH0)
#define DMA_TX_TRIG_CH                  (AOS_DMA1_0)

#define DMA_RX_CH                       (DMA_CH1)
#define DMA_RX_INT_CH                   (DMA_INT_TC_CH1)
#define DMA_RX_TRIG_CH                  (AOS_DMA1_1)
#define DMA_RX_INT_SRC                  (INT_SRC_DMA1_TC1)
#define DMA_RX_IRQ_NUM                  (INT006_IRQn)

/* SS = PA7 */
#define SPI_SS_PORT                     (GPIO_PORT_A)
#define SPI_SS_PIN                      (GPIO_PIN_07)
#define SPI_SS_FUNC                     (GPIO_FUNC_42)
/* SCK = PA8 */
#define SPI_SCK_PORT                    (GPIO_PORT_A)
#define SPI_SCK_PIN                     (GPIO_PIN_08)
#define SPI_SCK_FUNC                    (GPIO_FUNC_43)
/* MOSI = PB0 */
#define SPI_MOSI_PORT                   (GPIO_PORT_B)
#define SPI_MOSI_PIN                    (GPIO_PIN_00)
#define SPI_MOSI_FUNC                   (GPIO_FUNC_40)
/* MISO = PC5 */
#define SPI_MISO_PORT                   (GPIO_PORT_C)
#define SPI_MISO_PIN                    (GPIO_PIN_05)
#define SPI_MISO_FUNC                   (GPIO_FUNC_41)

DMA的参数配置就不详细解读了,有不明白的可以去看我的DMA实验代码详解。

对于各个GPIO引脚的功能选择,我们需要查看数据手册的表2-2 Func32~63,里面有具体说明。

②发送、接收缓存区和标志定义

static char u8TxBuf[EXAMPLE_SPI_BUF_LEN] = "SPI Master/Slave example: Communication between two boards!";
static char u8RxBuf[EXAMPLE_SPI_BUF_LEN];
static __IO en_flag_status_t enRxCompleteFlag = RESET;

③SPI串口通信配置

static void SPI_Config(void)
{
    stc_spi_init_t stcSpiInit;
    stc_dma_init_t stcDmaInit;
    stc_irq_signin_config_t stcIrqSignConfig;

    /* Configure Port */
    GPIO_SetFunc(SPI_SS_PORT,   SPI_SS_PIN,   SPI_SS_FUNC);
    GPIO_SetFunc(SPI_SCK_PORT,  SPI_SCK_PIN,  SPI_SCK_FUNC);
    GPIO_SetFunc(SPI_MOSI_PORT, SPI_MOSI_PIN, SPI_MOSI_FUNC);
    GPIO_SetFunc(SPI_MISO_PORT, SPI_MISO_PIN, SPI_MISO_FUNC);

    /* Configuration SPI */
    FCG_Fcg1PeriphClockCmd(SPI_CLK, ENABLE);
    SPI_StructInit(&stcSpiInit);
    stcSpiInit.u32WireMode          = SPI_4_WIRE;
    stcSpiInit.u32TransMode         = SPI_FULL_DUPLEX;
    stcSpiInit.u32MasterSlave       = EXAMPLE_SPI_MASTER_SLAVE;
    stcSpiInit.u32Parity            = SPI_PARITY_INVD;
    stcSpiInit.u32SpiMode        = SPI_MD_1;//在奇数边沿数据发生变化,在偶数边沿进行数据采样
    stcSpiInit.u32BaudRatePrescaler = SPI_BR_CLK_DIV64;
    stcSpiInit.u32DataBits          = SPI_DATA_SIZE_8BIT;
    stcSpiInit.u32FirstBit          = SPI_FIRST_MSB;
    stcSpiInit.u32FrameLevel        = SPI_1_FRAME;
    (void)SPI_Init(SPI_UNIT, &stcSpiInit);

    /* DMA configuration */
    FCG_Fcg0PeriphClockCmd(DMA_CLK, ENABLE);
    (void)DMA_StructInit(&stcDmaInit);
    stcDmaInit.u32BlockSize  = 1UL;
    stcDmaInit.u32TransCount = EXAMPLE_SPI_BUF_LEN;
    stcDmaInit.u32DataWidth  = DMA_DATAWIDTH_8BIT;
    /* Configure TX */
    stcDmaInit.u32SrcAddrInc  = DMA_SRC_ADDR_INC;
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_FIX;
    stcDmaInit.u32SrcAddr     = (uint32_t)(&u8TxBuf[0]);
    stcDmaInit.u32DestAddr    = (uint32_t)(&SPI_UNIT->DR);
    if (LL_OK != DMA_Init(DMA_UNIT, DMA_TX_CH, &stcDmaInit)) {
        for (;;) {
        }
    }
    AOS_SetTriggerEventSrc(DMA_TX_TRIG_CH, SPI_TX_EVT_SRC);
    /* Configure RX */
    stcDmaInit.u32IntEn       = DMA_INT_ENABLE;
    stcDmaInit.u32SrcAddrInc  = DMA_SRC_ADDR_FIX;
    stcDmaInit.u32DestAddrInc = DMA_DEST_ADDR_INC;
    stcDmaInit.u32SrcAddr     = (uint32_t)(&SPI_UNIT->DR);
    stcDmaInit.u32DestAddr    = (uint32_t)(&u8RxBuf[0]);
    if (LL_OK != DMA_Init(DMA_UNIT, DMA_RX_CH, &stcDmaInit)) {
        for (;;) {
        }
    }
    AOS_SetTriggerEventSrc(DMA_RX_TRIG_CH, SPI_RX_EVT_SRC);

    /* DMA receive NVIC configure */
    stcIrqSignConfig.enIntSrc    = DMA_RX_INT_SRC;
    stcIrqSignConfig.enIRQn      = DMA_RX_IRQ_NUM;
    stcIrqSignConfig.pfnCallback = &DMA_TransCompleteCallback;
    (void)INTC_IrqSignIn(&stcIrqSignConfig);
    NVIC_ClearPendingIRQ(stcIrqSignConfig.enIRQn);
    NVIC_SetPriority(stcIrqSignConfig.enIRQn, DDL_IRQ_PRIO_DEFAULT);
    NVIC_EnableIRQ(stcIrqSignConfig.enIRQn);

    /* Enable DMA and channel */
    DMA_Cmd(DMA_UNIT, ENABLE);
    DMA_ChCmd(DMA_UNIT, DMA_TX_CH, ENABLE);
    DMA_ChCmd(DMA_UNIT, DMA_RX_CH, ENABLE);
}

代码中,GPIO_SetFunc(SPI_SS_PORT,   SPI_SS_PIN,   SPI_SS_FUNC); 目的就是把对应的GPIO引脚配置自己想要的功能,类比于STM32中的映射吧。

我们详细讲讲配置SPI的结构体:

typedef struct {
    uint32_t u32WireMode;           /*!< 配置4线或3线模式*/
	uint32_t u32TransMode;          /*!< 传输模式:全双工or只进行发送通信方式*/
    uint32_t u32MasterSlave;        /*!< 设置主机模式or从机模式 */
    uint32_t u32ModeFaultDetect;    /*!< 使能 模式故障错误检测*/
    uint32_t u32Parity;             /*!< 使能 SPI奇偶校验 */
    uint32_t u32SpiMode;            /*!< 配置SPI模式:主机、从机、主机时钟同步、从机时钟同*/
    uint32_t u32BaudRatePrescaler;  /*!< 配置SPI波特率预分频*/
    uint32_t u32DataBits;           /*!< 配置 传输\接收数据长度 */
    uint32_t u32FirstBit;           /*!< 配置开始位是MSB 还是 LSB */
    uint32_t u32SuspendMode;        /*!< SPI通信暂停功能 */
    uint32_t u32FrameLevel;         /*!<  SPI帧数设定位*/
} stc_spi_init_t;

④DMA传输完成回调函数

static void DMA_TransCompleteCallback(void)
{
    enRxCompleteFlag = SET;
    DMA_ClearTransCompleteStatus(DMA_UNIT, DMA_RX_INT_CH);
}

⑤DMA重装载配置

static void DMA_ReloadConfig(void)
{
    DMA_SetSrcAddr(DMA_UNIT, DMA_TX_CH, (uint32_t)(&u8TxBuf[0]));
    DMA_SetTransCount(DMA_UNIT, DMA_TX_CH, EXAMPLE_SPI_BUF_LEN);
    DMA_SetDestAddr(DMA_UNIT, DMA_RX_CH, (uint32_t)(&u8RxBuf[0]));
    DMA_SetTransCount(DMA_UNIT, DMA_RX_CH, EXAMPLE_SPI_BUF_LEN);
    /* Enable DMA channel */
    DMA_ChCmd(DMA_UNIT, DMA_TX_CH, ENABLE);
    DMA_ChCmd(DMA_UNIT, DMA_RX_CH, ENABLE);
}

这里为啥需要重装载呢,因为传输完一次,DMA指向源地址变了,需要重新回到初始的源地址,即缓存区:TxBuf[0]和RxBuf[0]

⑥主函数

int32_t main(void)
{
    /* Peripheral registers write unprotected */
    LL_PERIPH_WE(EXAMPLE_PERIPH_WE);
    /* Configure BSP */
    BSP_CLK_Init();
    BSP_LED_Init();
    BSP_KEY_Init();
    /* Configure SPI */
    SPI_Config();
    /* Peripheral registers write protected */
    LL_PERIPH_WP(EXAMPLE_PERIPH_WP);

    for (;;) {
        /* Wait key trigger in master mode */
#if (EXAMPLE_SPI_MASTER_SLAVE == SPI_MASTER)
        while (RESET == BSP_KEY_GetStatus(BSP_KEY_2)) {
        }
#endif
        enRxCompleteFlag = RESET;
        memset(u8RxBuf, 0, EXAMPLE_SPI_BUF_LEN);
        DMA_ReloadConfig();
        /* Enable SPI */
        SPI_Cmd(SPI_UNIT, ENABLE);
        /* Waiting for completion of reception */
        while (RESET == enRxCompleteFlag) {
        }
        /* Disable SPI */
        SPI_Cmd(SPI_UNIT, DISABLE);

        /* Compare u8TxBuf and u8RxBuf */
        if (0 == memcmp(u8TxBuf, u8RxBuf, EXAMPLE_SPI_BUF_LEN)) {
            BSP_LED_On(LED_BLUE);
            BSP_LED_Off(LED_RED);
        } else {
            BSP_LED_On(LED_RED);
            BSP_LED_Off(LED_BLUE);
        }
#if (EXAMPLE_SPI_MASTER_SLAVE == SPI_MASTER)
        /* Wait for the slave to be ready */
        DDL_DelayMS(10U);
#endif
    }
}

通过while (RESET == BSP_KEY_GetStatus(BSP_KEY_2))  来判断按键K2是否按下,按下为低电平,触发SPI通信。

        enRxCompleteFlag = RESET;
        memset(u8RxBuf, 0, EXAMPLE_SPI_BUF_LEN);
        DMA_ReloadConfig();

这3句目的是为了初始化 接收缓存区,如果一开始接收缓存区有数据,则SPI会触发 模式错误。

实验代码讲解到此结束,有什么疑问可以私信我~

物联沃分享整理
物联沃-IOTWORD物联网 » 华大HC32F460 SPI DMA通信实验代码详细解析

发表评论