华大DMA串口发送遇到的坑

最近调试华大F460KETA芯片,遇到了DMA串口发送的坑,来和大家分享一下。
华大的DMA和STM32的DMA不一样,STM32系列的DMA发送只需要使能DMA通道,就可以触发一次DMA发送,而华大的需要用用软件触发只能触发一次DMA一个字节的发送,感觉好坑,后来改用串口发送完成中断来触发DMA发送。
/*  DMA 初始化结构体: */
    typedef struct stc_dma_config
    {
        uint16_t                u16BlockSize;       ///< 设置数据块的大小, 0~1023 (0表示1024,即1块有1024个数据)
        uint16_t                u16TransferCnt;     ///< 总的传输次数,每次请求(DMA触发源触发一次)启动一个数据块的传输
        uint32_t                u32SrcAddr;         ///< 源地址
        uint32_t                u32DesAddr;         ///< 目标地址
        uint16_t                u16SrcRptSize;      ///< 源地址重复区域大小
        uint16_t                u16DesRptSize;      ///< 目标地址重复区域大小
        uint32_t                u32DmaLlp;          ///< 连锁传输的链指针
        stc_dma_nseq_cfg_t      stcSrcNseqCfg;      ///< 不连续源地址
        stc_dma_nseq_cfg_t      stcDesNseqCfg;      ///< 不连续目标地址
        stc_dma_ch_cfg_t        stcDmaChCfg;        ///< 通道设置,见下面的结构体
    }stc_dma_config_t;

    typedef struct stc_dma_ch_cfg
    {
        en_dma_address_mode_t   enSrcInc;       ///< DMA 源地址模式(自增,自减,不变)
        en_dma_address_mode_t   enDesInc;       ///< DMA 目标地址模式(自增,自减,不变)
        en_functional_state_t   enSrcRptEn;     ///< 源地址重复使能
        en_functional_state_t   enDesRptEn;     ///< 目标地址重复使能
        en_functional_state_t   enSrcNseqEn;    ///< 不连续源地址使能
        en_functional_state_t   enDesNseqEn;    ///< 不连续目标地址使能
        en_functional_state_t   enLlpEn;        ///< 连锁传输使能
        en_dma_llp_mode_t       enLlpMd;        ///< 连锁传输模式
        en_dma_transfer_width_t enTrnWidth;     ///< 1个数据的宽度
        en_functional_state_t   enIntEn;        ///< 使能中断
    }stc_dma_ch_cfg_t;

    typedef struct stc_dma_nseq_cfg
    {
        uint32_t                u32Offset;      ///< DMA no-sequence offset.
        uint16_t                u16Cnt;         ///< DMA no-sequence count.
    }stc_dma_nseq_cfg_t;
其中2个参数详细说明:


u16 BlockSize: 设置数据块的大小,最大可以配置1024个数据。
寄存器值设为1则每次传输1个数据,设为0则每次传输1024个数据。
因为此寄存器只有10位设置该值,故最大1023(0x3FF),没有1024,所以0代表1024。

u16 TransferCnt: 总的传输次数,每次请求启动一个数据块的传输,完成时传输次数
计数器减1,当减到0时发生传输完成中断。如果设置为0,表示无限次传输,每次启
动请求传输一个数据块,完成时传输次数计数器保持0不变,不会产生传输完成中断。

流程:

/* DMAC */

#define USART_DMA_UNIT                     (M4_DMA1)

#define RX_DMA_CH                          (DmaCh0)

#define RX_DMA_TRG_SEL                     (EVT_USART3_RI)

#define TX_DMA_CH                          (DmaCh1)

#define TX_DMA_TRG_SEL                     (EVT_USART3_TI)

/* USART channel definition */

#define USART_CH                        (M4_USART3)

/* USART baudrate definition */

#define USART_BAUDRATE                  (115200ul)

/* USART RX Port/Pin definition */

#define USART_RX_PORT                   (PortC)

#define USART_RX_PIN                    (Pin13)

#define USART_RX_FUNC                   (Func_Usart3_Rx)

/* USART TX Port/Pin definition */

#define USART_TX_PORT                   (PortH)

#define USART_TX_PIN                    (Pin02)

#define USART_TX_FUNC                   (Func_Usart3_Tx)

//DMA 发送完成回调函数

static void Dma_TX_IrqCallback(void)

{

    /* Clear DMA flag. */

    DMA_ClearIrqFlag(USART_DMA_UNIT, TX_DMA_CH, TrnCpltIrq); //要清除标志位

    /* Disable DMA*/

    DMA_ChannelCmd(USART_DMA_UNIT, TX_DMA_CH, Disable);//关断DMA使能

    lcd_send_circle.tail = (lcd_send_circle.tail + 1)%LCD_SEND_CIRCLE_BUF_NUM;

}

初始化DMA的发送管脚

static void dma_tx_init(void)

{

    stc_dma_config_t stcDmaInit;

    stc_irq_regi_conf_t stcIrqRegiCfg;

    /* Enable peripheral clock */

    PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_DMA1 | PWC_FCG0_PERIPH_DMA2,Enable);//注意要先初始化时钟

    /* Enable DMA. */

    DMA_Cmd(USART_DMA_UNIT,Enable);

    /* Initialize DMA. */

    MEM_ZERO_STRUCT(stcDmaInit);

    stcDmaInit.u16BlockSize = 1u; /* 1 block */

    stcDmaInit.u32SrcAddr = ((uint32_t)(0)); /* Set source address.   起始地址*/

    stcDmaInit.u32DesAddr = (uint32_t)(&USART_CH->DR);         /* Set destination address. TDR  目标地址  内存到外设电路*/

    stcDmaInit.stcDmaChCfg.enLlpEn = Disable;                   /* Disable linked list transfer. */

    stcDmaInit.stcDmaChCfg.enSrcInc = AddressIncrease;  /* Set source address mode.  内存地址递增*/

    stcDmaInit.stcDmaChCfg.enDesInc = AddressFix;        /* Set destination address mode. 外设地址固定*/

    stcDmaInit.stcDmaChCfg.enIntEn = Enable;                /* Enable interrupt. */

    stcDmaInit.stcDmaChCfg.enTrnWidth = Dma8Bit;        /* Set data width 8bit. 数据宽度为8个位。一定要约定好宽度 */

    stcDmaInit.u16TransferCnt = 1;

    DMA_InitChannel(USART_DMA_UNIT, TX_DMA_CH, &stcDmaInit);

    /* Enable the specified DMA channel. */

    DMA_ChannelCmd(USART_DMA_UNIT, TX_DMA_CH, Disable);

    /* Clear DMA flag. */

    DMA_ClearIrqFlag(USART_DMA_UNIT, TX_DMA_CH, TrnCpltIrq);

    /* Enable peripheral circuit trigger function. */

    PWC_Fcg0PeriphClockCmd(PWC_FCG0_PERIPH_AOS,Enable);//这里要注意,一定要开启触发外设时钟!!!!!

/* Set DMA trigger source. */

    DMA_SetTriggerSrc(USART_DMA_UNIT, TX_DMA_CH, TX_DMA_TRG_SEL);//设置触发源,这里是最重要,也是最坑的,一定要注意

/*设置的触发源为串口发送完成中断*/

//得先发送一个字节的数据才能产生一次发送完成中断,这是最坑的!!!!

   //这是中断优先级的设置

    /* Set DMA block transfer complete IRQ */

    stcIrqRegiCfg.enIRQn = TX_DMA_BTC_INT_IRQn;

    stcIrqRegiCfg.pfnCallback = &Dma_TX_IrqCallback; //这里是发送完成的回调函数,要清标志位

    stcIrqRegiCfg.enIntSrc = TX_DMA_BTC_INT_NUM;

    enIrqRegistration(&stcIrqRegiCfg);

    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);

    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);

    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

}

//串口的初始化配置

//要注意的是,华大单片机一共是有四组串口,USART1、USART2、USART3、USART4,华大单片机很大的有点是通过管脚的功能表可以任意复用

如下图所示,我们可以看到我们选用的是USART3。


他是属于function2功能表的,也就是说,引脚后面标着Func_Grp2的,都可以复用为USART3,比如我们这次选择的就是PC3 PH2管脚。

static void usart_init(void)

{

    stc_irq_regi_conf_t stcIrqRegiCfg;

    uint32_t u32Fcg1Periph = PWC_FCG1_PERIPH_USART1 | PWC_FCG1_PERIPH_USART2 | \

                             PWC_FCG1_PERIPH_USART3 | PWC_FCG1_PERIPH_USART4;

    const stc_usart_uart_init_t stcInitCfg = {

        UsartIntClkCkNoOutput,

        UsartClkDiv_1,

        UsartDataBits8,

        UsartDataLsbFirst,

        UsartOneStopBit,

        UsartParityNone,

        UsartSampleBit8,

        UsartStartBitFallEdge,

        UsartRtsEnable,

    };

    /* Enable peripheral clock */

    PWC_Fcg1PeriphClockCmd(u32Fcg1Periph, Enable);

    /* Initialize USART IO */

    PORT_SetFunc(USART_RX_PORT, USART_RX_PIN, USART_RX_FUNC, Disable);

    PORT_SetFunc(USART_TX_PORT, USART_TX_PIN, USART_TX_FUNC, Disable);

    /* Initialize USART */

    USART_UART_Init(USART_CH, &stcInitCfg);

    /* Set baudrate */

    USART_SetBaudrate(USART_CH, USART_BAUDRATE);

    /* Set USART RX error IRQ */

    stcIrqRegiCfg.enIRQn = USART_EI_IRQn;

    stcIrqRegiCfg.pfnCallback = &UsartErrIrqCallback;

    stcIrqRegiCfg.enIntSrc = USART_EI_NUM;

    enIrqRegistration(&stcIrqRegiCfg);

    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);

    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);

    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

    /* Set USART RX  IRQ */

    stcIrqRegiCfg.enIRQn = USART_RI_IRQn;

    stcIrqRegiCfg.pfnCallback = &UsartRxIrqCallback;

    stcIrqRegiCfg.enIntSrc = USART_RI_NUM;

    enIrqRegistration(&stcIrqRegiCfg);

    NVIC_SetPriority(stcIrqRegiCfg.enIRQn, DDL_IRQ_PRIORITY_DEFAULT);

    NVIC_ClearPendingIRQ(stcIrqRegiCfg.enIRQn);

    NVIC_EnableIRQ(stcIrqRegiCfg.enIRQn);

    /*Enable TX && RX && RX interrupt function*/

    USART_FuncCmd(USART_CH, UsartTx, Enable);

    USART_FuncCmd(USART_CH, UsartRx, Enable);

    USART_FuncCmd(USART_CH, UsartRxInt, Enable);

}

以上初始化完成,我们只需要在主函数中调用DMA发送函数即可,下面也是很关键的

#define CMD_DELAY_CNT 10

char send_buff[100] = {0x01,0x02,0x03,0x5,0x06,0x07,0x08,0x09,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x20};

void usart_tx_start_sending(uint16_t  len)

{
    static unsigned char last_flag = 0;
    static unsigned char delay_cnt= 0;   //这个是限制DMA发送频率的
  //下面这个句话,M4_DMA1 -> CHSTAT_f.CHACT判断DMA所用的通道是否处于空闲状态,M4_DMA1 -> MONDTCTL1_f.CNT 是判断DMA是否处于传输动作中,
两者条件均满足的话,则证明DMA现在空闲状态,然后判断串口的发送状态即可。


    if( (M4_DMA1 -> CHSTAT_f.CHACT == 0x00) && (M4_DMA1 -> MONDTCTL1_f.CNT == 0x00) &&(USART_GetStatus(M4_USART3,UsartTxComplete)))
    {
        if(last_flag == 0)
        {


                M4_DMA1->CHEN   = M4_DMA1->CHEN & 0xFD; //这里是将DMA使能先关掉
               //重新设置传输的起始地址,所以前面初始化的地址设置为0就可以,因为我们采用的是串口发送完成中断触发DMA,因此我们要发一组数据的话,
               //是发送这组数据的首个字节数据,也就是 ((uint32_t)(&p->buf[0])),因此我们DMA发送设置的原地址应该+ 1,也就是((uint32_t)(&p->buf[1]))!
              M4_DMA1->SAR1   = (uint32_t)(&send_buff[1]);
                
          //    我们设置块大小为1,那么传输次数就是数据的个数,如果数据的总长为为len,那么DMA传输的数据个数就是len - 1
         //   因为我们发送一组数据的首个字节触发DMA发送。
         //  我们采用的是通道1,设置数据控制寄存器为DMA_DTCTLx,它是一个32位寄存器,传输UCis胡的设置位于高16位,因此我们要设置完
      //传输次数之后先左移16位。再或上块的大小。
            M4_DMA1->DTCTL1 = ((len -1 ) << 16 ) | 0x01;
           //下面是坑的起因  发送第一个数据,直接将TDR置为第一个数据。
       
            USART_CH->DR_f.TDR = (uint32_t)&send_buff[0];
             last_flag = 1;

            }
        }
        else
        {

            if(++delay_cnt > CMD_DELAY_CNT)
            {
                delay_cnt = 0;
                last_flag = 0;
            }
        }

    }
}

以上就是DMA串口发送的坑,其实也不算是坑吧,还是对说明书读的不够细致,把自己的经验分享在这,希望能够帮助遇到这个问题的朋友。
---------------------
作者:woai32lala
链接:https://bbs.21ic.com/icview-3191656-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值