华大DMA串口发送遇到的坑
本帖最后由 woai32lala 于 2022-1-11 13:03 编辑#申请原创# #有奖活动#@21小跑堂
最近调试华大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 RXIRQ */ 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 10char send_buff = {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_tlen){
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)),因此我们DMA发送设置的原地址应该+ 1,也就是((uint32_t)(&p->buf))!
M4_DMA1->SAR1 = (uint32_t)(&send_buff);
// 我们设置块大小为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;
last_flag = 1;
}
}
else
{
if(++delay_cnt > CMD_DELAY_CNT)
{
delay_cnt = 0;
last_flag = 0;
}
}
}
}
以上就是DMA串口发送的坑,其实也不算是坑吧,还是对说明书读的不够细致,把自己的经验分享在这,希望能够帮助遇到这个问题的朋友。
谢谢分享避坑经验,哈哈。 非常好的闭坑宝典 学习了,很详细 华大的芯片收到有一段时间了,还没来得及及坑!学习了! 本帖最后由 HC11425 于 2022-1-24 16:08 编辑
小华代理商技术支持热线:131 6807 9092 喻生我手里有DMA收发例程,不定长度接受窗口数据。在附件里面,参考一下吧。 本人亲测验证收发数据正常,另外想要9600波特率,串口需要16分频。
好奇楼主为什么用发送满来触发DMA,实际数据是怎么搬运的呢? 超出了范围会有什么后果呢 介绍的真是太详细了 请问什么叫做发送满呢 不可以一次性的读取32位的寄存器吧 所有型号的都是这样子的吗 控制寄存器很关键 HC11425 发表于 2022-1-24 16:06
小华代理商技术支持热线:131 6807 9092 喻生我手里有DMA收发例程,不定长度接受窗口数据。在附件里面, ...
感谢分享,下载看看 感谢分享 那些年踩过的坑 踩过的坑以后都是经验财富 感谢分享 不太喜欢用dma串口。 华大F460KETA性能怎么样