最近调试华大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所有,任何人未经允许禁止转载。