CSR8675 使用串口 UART 收发功能
CSR8675 实现 UART 功能有两种方式,一种是托管连接,另一种是直接连接。
托管连接:不直接操作 Stream,通过 VM 层创建 Source 和 Sink 来实现数据传输,由库进行处理,实现比较方便。
直接连接:直接操作 Stream ,源是一个存储区域,通过对内存进行操作实现数据传输,处理数据传输效率更高。
此处暂时先用 托管连接 的方式来介绍,后面有空再更新 直接连接。
1、使用 RAW 传输
在工程属性下,将 Transport 属性改为 RAW。
2、打开调试宏
在 sink_debug.h 中加入调试宏
3、编辑串口代码,实现 loopback 功能
主要实现4个函数
- 消息处理函数
- 串口初始化函数
- 串口接收函数
- 串口发送函数
<sink_uart.h>
#ifndef __UART_H_
#define __UART_H_
void UARTStreamMessageHandler (Task pTask, MessageId pId, Message pMessage);
void uart_data_stream_rx_data(Source src);
void uart_data_stream_tx_data(const uint8 *data, uint16 length);
void uart_data_stream_init(void);
#endif /* __UART_H_ */
<sink_uart.c>
#include <stream.h>
#include <sink.h>
#include <source.h>
#include <string.h>
#include <panic.h>
#include <message.h>
#include <app/uart/uart_if.h>
#include <stdio.h>
#include <string.h>
#include "uart.h"
#include "sink_debug.h"
#ifdef DEBUG_UART
#define UART_DEBUG(x) DEBUG(x)
#else
#define UART_DEBUG(x)
#endif
typedef struct
{
TaskData task;
Sink uart_sink;
Source uart_source;
}UARTStreamTaskData;
UARTStreamTaskData theUARTStreamTask;
void uart_data_stream_init(void)
{
/* Assign task message handler */
theUARTStreamTask.task.handler = UARTStreamMessageHandler;
/* Configure uart settings */
StreamUartConfigure(VM_UART_RATE_38K4, VM_UART_STOP_ONE, VM_UART_PARITY_NONE);
/* Get the sink for the uart */
theUARTStreamTask.uart_sink = StreamUartSink();
if(theUARTStreamTask.uart_sink != 0)
PanicNull(theUARTStreamTask.uart_sink);
/* Get the source for the uart */
theUARTStreamTask.uart_source = StreamUartSource();
if(theUARTStreamTask.uart_source != 0)
PanicNull(theUARTStreamTask.uart_source);
/* Register uart source with task */
MessageSinkTask(StreamSinkFromSource(theUARTStreamTask.uart_source),&theUARTStreamTask.task);
}
void uart_data_stream_tx_data(const uint8 *data, uint16 length)
{
uint16 offset = 0;
uint8 *dest = NULL;
/* Claim space in the sink, getting the offset to it */
offset = SinkClaim(theUARTStreamTask.uart_sink, length);
if(offset == 0xFFFF) Panic();
/* Map the sink into memory space */
dest = SinkMap(theUARTStreamTask.uart_sink);
PanicNull(dest);
/* Copy data into the claimed space */
memcpy(dest+offset, data, length);
/* Flush the data out to the uart */
PanicZero(SinkFlush(theUARTStreamTask.uart_sink, length));
}
void uart_data_stream_rx_data(Source src)
{
uint16 length = 0;
const uint8 *data = NULL;
/* Get the number of bytes in the specified source before the next packet boundary */
if(!(length = SourceBoundary(src)))
return;
/* Maps the specified source into the address map */
data = SourceMap(src);
PanicNull((void*)data);
/* Transmit the received data */
uart_data_stream_tx_data(data, length);
UART_DEBUG(("UART: Rx: length = %d\n",length));
/* Discards the specified amount of bytes from the front of the specified source */
SourceDrop(src, length);
}
void UARTStreamMessageHandler (Task pTask, MessageId pId, Message pMessage)
{
switch (pId)
{
case MESSAGE_MORE_DATA:
uart_data_stream_rx_data(((MessageMoreData *)pMessage)->source);
break;
default:
break;
}
}
4、调用函数,实现 loopback
修改PSKey
01EA UART_BITRATE 改为38400
01C2 UART_CONFIG_USR 改为 0880
在 main 函数中,MessageLoop()之前,调用串口初始化函数。
按F5下载到开发板上。
接上串口,打开串口工具,串口号在电脑的设备管理器查看,波特率设置为38400,数据位为8,停止位为1,奇偶校验位为none。
但是存在一个小问题,发了10个数据,在接收函数中打印一下数据长度,会发现进入了2次接收函数,第一次接收了1个字节,第二次将剩余的字节全部接收。
操作多几次验证,发现一个规律,就是第一次总是接收1个字节,然后剩下的数据接收的长度会随机改变,原因可能是底层库通过流的方式实现,此处用的是异步串口,没有硬件流控,而且CSR8675本身的串口 buffer 空间不大,才会导致接收的时候将数据截断。
因此,在使用异步串口过程中,需要根据实际情况,对接收的数据增加拼接的处理,同时加入自定义的协议,增加帧头、帧尾、数据长度、数据校验等信息,才能够保证接收到的内容是正确的。
持续更新–2019.10.31
5、解决 UART 接收时数据分段出来的现象
MESSAGE_MORE_DATA 此 id 附带的 message,本质也是一个流,只要达到接收 buffer 上有数据,就会发送这个 MessageID。
但是,只要不对这个 buffer 进行 Drop,下一次的 MESSAGE_MORE_DATA 依然会将原来数据数据积累起来,在下一次的时候发送。
buffer 大小为4K,如果不及时 Drop,则会内存溢出挂掉
因此,只需要对收到的数据进行长度的判断:
长度足够 --> 处理收到的内容,并将其 Drop。
长度不够 --> return,退出函数,等待下一次消息。
串口数据协议可以自定义,暂时按照这种方式,可传输的数据最大为 255 Byte。可以根据需要增加 数据长度和CRC校验
head | command | length | data | end | description |
---|---|---|---|---|---|
07 | 01 | n | n Byte | 0A | 可变长度数据处理 |
07 | 02 | 1 | 1 Byte | 0A | Notification |
07 | 03 | 1 | 1 Byte | 0A | Status |
07 | 04 | 1 | 1 Byte | 0A | Ask |
下面对接收函数进行修改,思路是如果收到正确的数据,则处理掉,否则检查数据完整性,并丢掉无效数据;如果长度不够,则跳过此次接收。
void uart_data_stream_rx_data(Source src)
{
uint16 length = 0;
uint16 length_drop = 0;
const uint8 *data = NULL;
recheck:
if(!(length = SourceBoundary(src)))
return;
data = SourceMap(src);
PanicNull((void*)data);
UART_DEBUG(("rx source size = %d \n",SourceSize(src)));
/* 数据不够最小一帧,跳出,下次再处理 */
if(length < UART_DATA_MINLEN)
{
return;
}
/* 第一个位帧头,开始解析 */
else if(data[0] == UART_DATA_HEAD)
{
switch(data[1])
{
case UART_HIBY_LINK_DATA:
/* 收到的数据长度不够,跳过不处理 */
if(data[2] > length-COMMAND_LENGTH )
{
UART_DEBUG(("HiBy Link command,length = %d ,rx len = %d,NOT enough\n",data[2],length));
return;
}
/* 收到足够的数据,处理 */
else
{
length_drop = data[2] + COMMAND_LENGTH ;
UART_DEBUG(("HiBy Link command length = %d , length = %d\n",data[2],length_drop));
/* 检查帧尾是否正确 */
if(data[(length_drop - 1)] == UART_DATA_END)
{
/* send to spp */
UART_DEBUG(("HiBy Link data send : %x ... %x \n",data[3],data[2+data[2]]));
}
SourceDrop(src, length_drop);
/* 如果还有数据,则在执行一次 */
if(length > length_drop )
{
goto recheck;
}
}
break;
case UART_NOTIFICATION:
if(data[2] == 1 && data[4] == UART_DATA_END)
{
UART_DEBUG(("Notification command = %x \n",data[3]));
SourceDrop(src, UART_DATA_MINLEN);
if(length > UART_DATA_MINLEN)
{
goto recheck;
}
}
break;
case UART_STATUS:
if(data[2] == 1 && data[4] == UART_DATA_END)
{
UART_DEBUG(("Status command = %x \n",data[3]));
SourceDrop(src, UART_DATA_MINLEN);
if(length > UART_DATA_MINLEN)
{
goto recheck;
}
}
break;
case UART_ASK:
if(data[2] == 1 && data[4] == UART_DATA_END)
{
UART_DEBUG(("Ask command = %x \n",data[3]));
SourceDrop(src, UART_DATA_MINLEN);
if(length > UART_DATA_MINLEN)
{
goto recheck;
}
}
break;
default:
UART_DEBUG(("commmon error, drop min length.\n"));
SourceDrop(src, UART_DATA_MINLEN);
if(length > UART_DATA_MINLEN)
{
goto recheck;
}
break;
}
}
/* 第一个字节不是帧头,可能数据有误,查找帧头,并丢弃之前数据 */
else
{
uint16 err_data_len = 0;
while(err_data_len < length)
{
/* 找到帧头 */
if(data[err_data_len] == UART_DATA_HEAD)
{
/* 判断是否真的是帧头 ,通过检测上一个字节是否帧尾 */
if(data[err_data_len - 1] == UART_DATA_END )
{
/* 保证下一字节不溢出,并且为命令字节 ,然后去掉前面错误的数据,将正确的数据保留下来,留给下一次处理*/
if( (err_data_len + 1) < length &&
(data[err_data_len + 1] == UART_HIBY_LINK_DATA ||
data[err_data_len + 1] == UART_NOTIFICATION ||
data[err_data_len + 1] == UART_STATUS ||
data[err_data_len + 1] == UART_ASK))
{
UART_DEBUG(("Drop error data,and has find the real data.\n"));
SourceDrop(src, err_data_len);
goto recheck;
}
}
}
err_data_len ++;
}
SourceDrop(src, length);
UART_DEBUG(("Drop data, without real data,drop all data.\n"));
return;
}
}