当前位置:   article > 正文

S32K144使用DMA完成串口的通用代码(基于NXP SDK2.0和PE配置)_s32k146 串口 dma例程

s32k146 串口 dma例程

由于项目需要,要将S32K的串口使用DMA来实现,并且设计较为安全的机制来保证串口通信。所以自己调试了一下相关代码,供参考。

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

至此,串口发送所有的接口提供完毕。用户代码中只需要使用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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

定时器中断中轮询是否启动发送,处理逻辑代码如下

	//每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);	
    		}
    	}
    	//......其他串口的处理逻辑也是一样
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上述代码通过两个buf完全规避了串口总线的判忙等待,充分提高了DMA发送的效率。需要注意的是,由于预处理buf是预先开辟的,所以如果有长协议的快速通信,需要计算一下波特率是否满足,定时器中轮询接口的时长也要考虑,避免预处理buf填满,而没有启动发送的情况发生。
这一块相关的代码我上传到https://download.csdn.net/download/weixin_40983190/11172137,但是不清楚为什么被设置成5积分了,一般我默认下载都是1积分,挣点分用来下载别人分享的资源,如果有人觉得积分太高了,可以给我发邮件waxyx09@163.com。但是我可能不会快速回复,还请见谅。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/144615
推荐阅读
  

闽ICP备14008679号