发新帖本帖赏金 30.00元(功能说明)我要提问
123下一页
返回列表
[方案相关]

 华大DMA串口发送遇到的坑

[复制链接]
5679|46
手机看帖
扫描二维码
随时随地手机跟帖
woai32lala|  楼主 | 2022-1-7 17:52 | 显示全部楼层 |阅读模式
本帖最后由 woai32lala 于 2022-1-11 13:03 编辑

#申请原创# #有奖活动#[url=home.php?mod=space&uid=760190]@21小跑堂 [/url]
最近调试华大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个参数详细说明:
9400661d80b0eaf37c.png
u16 BlockSize: 设置数据块的大小,最大可以配置1024个数据。
寄存器值设为1则每次传输1个数据,设为0则每次传输1024个数据。
因为此寄存器只有10位设置该值,故最大1023(0x3FF),没有1024,所以0代表1024。
4238761d80d4c8853a.png
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);//设置触发源,这里是最重要,也是最坑的,一定要注意
/*设置的触发源为串口发送完成中断*/
6115361d9813f415bc.png
//得先发送一个字节的数据才能产生一次发送完成中断,这是最坑的!!!!
6985461d981cdcc1d2.png

   //这是中断优先级的设置
    /* 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。
3106661d97f516e299.png

他是属于function2功能表的,也就是说,引脚后标着Func_Grp2的,都可以复用为USART3,比如我们这次选择的就是PC3 和PH2管脚。
5023061d97fcbcbbde.png
3815361d980b7053d1.png
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现在空闲状态,然后判断串口的发送状态即可。
7503861d987d0bde71.png

    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]);
                7946461d98a2111611.png
          //    我们设置块大小为1,那么传输次数就是数据的个数,如果数据的总长为为len,那么DMA传输的数据个数就是len - 1
         //   因为我们发送一组数据的首个字节触发DMA发送。
         //  我们采用的是通道1,设置数据控制寄存器为DMA_DTCTLx,它是一个32位寄存器,传输UCis胡的设置位于高16位,因此我们要设置完
      //传输次数之后先左移16位。再或上块的大小。
            M4_DMA1->DTCTL1 = ((len -1 ) << 16 ) | 0x01;
           //下面是坑的起因  发送第一个数据,直接将TDR置为第一个数据。
       8708761d98bc81081c.png
            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串口发送的坑,其实也不算是坑吧,还是对说明书读的不够细致,把自己的经验分享在这,希望能够帮助遇到这个问题的朋友。


uart_dma_rx_tx - DMA共享.zip

9.04 MB

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 30.00 元 2022-01-13
理由:恭喜通过原创文章审核!请多多加油哦!

caigang13| | 2022-1-7 18:27 | 显示全部楼层
谢谢分享避坑经验,哈哈。

使用特权

评论回复
七毛钱| | 2022-1-11 11:31 | 显示全部楼层
非常好的闭坑宝典

使用特权

评论回复
skyred| | 2022-1-19 15:40 | 显示全部楼层
学习了,很详细

使用特权

评论回复
bensonsu| | 2022-1-22 13:09 | 显示全部楼层
华大的芯片收到有一段时间了,还没来得及及坑!学习了!

使用特权

评论回复
HC11425| | 2022-1-24 16:06 | 显示全部楼层
本帖最后由 HC11425 于 2022-1-24 16:08 编辑

小华代理商技术支持热线:131 6807 9092 喻生我手里有DMA收发例程,不定长度接受窗口数据。  在附件里面,参考一下吧。 本人亲测验证收发数据正常,另外想要9600波特率,串口需要16分频。

hc32f46x_ddl_UART_DMA_Idle interrupt.zip

2.05 MB

使用特权

评论回复
bcrachel| | 2022-1-26 14:31 | 显示全部楼层
好奇楼主为什么用发送满来触发DMA,实际数据是怎么搬运的呢?

使用特权

评论回复
tpgf| | 2022-2-3 16:01 | 显示全部楼层
超出了范围会有什么后果呢

使用特权

评论回复
keaibukelian| | 2022-2-3 16:12 | 显示全部楼层
介绍的真是太详细了

使用特权

评论回复
labasi| | 2022-2-3 16:22 | 显示全部楼层
请问什么叫做发送满呢

使用特权

评论回复
paotangsan| | 2022-2-3 16:30 | 显示全部楼层
不可以一次性的读取32位的寄存器吧

使用特权

评论回复
renzheshengui| | 2022-2-3 16:39 | 显示全部楼层
所有型号的都是这样子的吗

使用特权

评论回复
wakayi| | 2022-2-3 16:45 | 显示全部楼层
控制寄存器很关键

使用特权

评论回复
littlelida| | 2022-2-8 11:29 | 显示全部楼层
HC11425 发表于 2022-1-24 16:06
小华代理商技术支持热线:131 6807 9092 喻生我手里有DMA收发例程,不定长度接受窗口数据。  在附件里面, ...

感谢分享,下载看看

使用特权

评论回复
gyhxhuang| | 2022-2-10 14:47 | 显示全部楼层
感谢分享

使用特权

评论回复
caigang13| | 2022-2-10 18:58 | 显示全部楼层
那些年踩过的坑

使用特权

评论回复
weifeng90| | 2022-2-11 08:25 | 显示全部楼层
踩过的坑以后都是经验财富

使用特权

评论回复
yuchl| | 2022-2-11 15:32 | 显示全部楼层
感谢分享

使用特权

评论回复
myiclife| | 2022-2-13 13:50 | 显示全部楼层
不太喜欢用dma串口。

使用特权

评论回复
10299823| | 2022-2-13 16:36 | 显示全部楼层
华大F460KETA性能怎么样  

使用特权

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

本版积分规则

102

主题

497

帖子

5

粉丝