赞
踩
1、S32K144的DMA串口实现
我使用的是NXP提供的DS32,其中的PE对DMA配置串口相当友好
在uart配置模块下选择传输类型为DMA
在DMA模块配置下,将对应的串口绑定传输通道即可
通过PE生成代码,此时项目生成的通用代码中就有了dma和uart的所有配置
2、DMA串口发送
这一部分NXP的SDK提供了完善的解决接口,只需要调用LPUART_DRV_SendData接口就可以完成DMA发送。FEATURE_LPUART_HAS_DMA_ENABLE宏是默认打开的,可以仿真跟踪一下代码,可以发现只要配置了DMA相关模块,实际发送时调用的就是圈出来的这部分代码。
3、DMA轮询实现
为了提高发送效率,所以不能使用等待的方式来完成每次串口的发送。所以设计了轮询的方式来做。具体的做法如下:
1、设计一个全局的buf,只要调用了串口的发送,就立刻将要发送的数据放入这个buf中
2、设计一个发送buf,在定时器中定期轮询DMA标记,如果DMA不忙,就去检查是否有数据要发送。
3、检查与发送buf,如果有需要发送的数据,将这段数据拷贝到发送buf中,将预发送buf标记置位。同时启动发送,DMA通道标记置位。
代码实现如下
//定义三个串口的全局预发送buf typedef struct{ uint8_t sBuf[SEND_LEN]; //发送缓存 uint16_t head; //MSG在sbuff中起始位置 uint16_t tail; //MSG在sbuff中结束位置 }uart_send_info; uart_send_info uart0_send; uart_send_info uart1_send; uart_send_info uart2_send; //定义三个发送buf uint8_t uart0_sBuf[SEND_LEN]; uint8_t uart1_sBuf[SEND_LEN]; uint8_t uart2_sBuf[SEND_LEN]; //添加三个dma发送完成标记 volatile bool uart0TransferComplete=true; volatile bool uart1TransferComplete=true; volatile bool uart2TransferComplete=true; //发送预处理,只是将要发送的数据放入预处理buf中 void pre_uart_send(uart_instance_t uartinstance,uint8_t *data,uint16_t len) { uart_send_info * uartXSend; if(UART0_INSTANCE==uartinstance) { uartXSend=&uart0_send; } if(UART1_INSTANCE==uartinstance) { uartXSend=&uart1_send; } if(UART2_INSTANCE==uartinstance) { uartXSend=&uart2_send; } //将data存入到对应的buf中去 if(uartXSend->tail+len<SEND_LEN) { memcpy((uartXSend->sBuf+uartXSend->tail),data,len); //将tail移位 uartXSend->tail=(uartXSend->tail+len)%SEND_LEN; }else{ memcpy((uartXSend->sBuf+uartXSend->tail),data,(SEND_LEN-uartXSend->tail-1)); memcpy(uartXSend->sBuf,(data+SEND_LEN-uartXSend->tail-1),len+uartXSend->tail-SEND_LEN); uartXSend->tail=(len+uartXSend->tail-SEND_LEN)%SEND_LEN; } } //串口发送代码,轮询时调用,将需要用到的DMA标记置位,如果总线忙,则不作任何处理,如果不忙,则启动发送,将预发送buf中的头指针移动到尾部 void uart_send(uart_instance_t uartinstance,uint8_t *data,uint16_t len) { lpuart_state_t * lpuartState; uart_send_info * uartXSend; if(UART0_INSTANCE==uartinstance) { lpuartState=&lpuart2_State; uartXSend=&uart0_send; uart0TransferComplete=false; } if(UART1_INSTANCE==uartinstance) { lpuartState=&lpuart1_State; uartXSend=&uart1_send; uart1TransferComplete=false; } if(UART2_INSTANCE==uartinstance) { lpuartState=&lpuart3_State; uartXSend=&uart2_send; uart2TransferComplete=false; } if(!(lpuartState->isTxBusy)){ //开始发送,置位全局预发送buf标记 LPUART_DRV_SendData(uartinstance,data,len); uartXSend->head=uartXSend->tail; //将sendbuf清掉 memset(uartXSend,0,sizeof(uint8_t)*SEND_LEN); } // 如果忙,那么这次就不做发送 } //提供一个轮询接口,在定时器中调用,如果预发送中有数据,则返回需要发送的数据长度,同时将数据拷贝到发送buf中去,这一部分功能是在get_send_data中实现的 uint16_t check_uart_buf_stat(uart_instance_t uartinstance) { uart_send_info * uartXSend; if(UART0_INSTANCE==uartinstance) { uartXSend=&uart0_send; } if(UART1_INSTANCE==uartinstance) { uartXSend=&uart1_send; } if(UART2_INSTANCE==uartinstance) { uartXSend=&uart2_send; } return get_send_data(uartinstance); } static uint16_t get_send_data(uart_instance_t uartinstance) { uart_send_info * uartXSend; uint8_t * send_buf; uint16_t send_len; if(UART0_INSTANCE==uartinstance) { uartXSend=&uart0_send; send_buf = uart0_sBuf; } if(UART1_INSTANCE==uartinstance) { uartXSend=&uart1_send; send_buf = uart1_sBuf; } if(UART2_INSTANCE==uartinstance) { uartXSend=&uart2_send; send_buf = uart2_sBuf; } if(uartXSend->tail>uartXSend->head) { send_len = uartXSend->tail-uartXSend->head; memcpy(send_buf,(uartXSend->sBuf+uartXSend->head),(uartXSend->tail-uartXSend->head)); }else if(uartXSend->tail<uartXSend->head) { send_len = SEND_LEN-uartXSend->head+uartXSend->tail; memcpy(send_buf,(uartXSend->sBuf+uartXSend->head),(SEND_LEN-uartXSend->head-1)); memcpy((send_buf+SEND_LEN-uartXSend->head),uartXSend->sBuf,uartXSend->tail+1); }else{ send_len = 0; } return send_len; }
至此,串口发送所有的接口提供完毕。用户代码中只需要使用pre_uart_send接口,不断向预处理buf中放入数据即可。所有的数据处理,都是通过定时器去轮询dma标记位来完成的。这部分代码如下。
//首先在DMA中断函数中添加标记,对应我的串口使用通道 void EDMA_DRV_IRQHandler(uint8_t virtualChannel) { const edma_chn_state_t *chnState = s_virtEdmaState->virtChnState[virtualChannel]; EDMA_DRV_ClearIntStatus(virtualChannel); if (chnState != NULL) { if (chnState->callback != NULL) { chnState->callback(chnState->parameter, chnState->status); } } //在对应串口的DMA通道中断时,将对应的发送完成标记置位。 if(5==virtualChannel) { uart0TransferComplete=true; } if(1==virtualChannel) { uart1TransferComplete=true; } if(7==virtualChannel) { uart2TransferComplete=true; } }
定时器中断中轮询是否启动发送,处理逻辑代码如下
//每QUERY_UART_BUF_TICK个TICK查询一次串口发送buf uart_tick_count++; if((uart_tick_count%QUERY_UART_BUF_TICK)==0) { //将轮询标记置位 uart_tick_count = 0; //轮询uart发送DMA通道的状态,暂时忽略通道状态做测试 if(uart0TransferComplete==true) { //如果没有要发送的数据,则返回0,此时后面的判断也不需要做 uint16_t len=check_uart_buf_stat(UART0_INSTANCE); if(len) { //如果串口0有数据,则查询对应串口是否忙状态,如果忙也不需要做操作,不忙则启动发送 uart_send(UART0_INSTANCE,uart0_sBuf,len); } } //......其他串口的处理逻辑也是一样 }
上述代码通过两个buf完全规避了串口总线的判忙等待,充分提高了DMA发送的效率。需要注意的是,由于预处理buf是预先开辟的,所以如果有长协议的快速通信,需要计算一下波特率是否满足,定时器中轮询接口的时长也要考虑,避免预处理buf填满,而没有启动发送的情况发生。
这一块相关的代码我上传到https://download.csdn.net/download/weixin_40983190/11172137,但是不清楚为什么被设置成5积分了,一般我默认下载都是1积分,挣点分用来下载别人分享的资源,如果有人觉得积分太高了,可以给我发邮件waxyx09@163.com。但是我可能不会快速回复,还请见谅。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。