CSR8670的UART功能的应用笔记

1. 功能定义

CSR8670与MCU之间通过UART端口交换数据。

1.1. 通信规则

MCU将指令以预先定义的数据格式发送数据给CSR8670,CSR8670解析后执行对应的事件,并向MCU发送执行结果。

CSR8670会将运行过程中发生的关键状态变化通知给MCU。

1.2. 数据格式

1.2.1. MCU->CSR8670

header length command parm chechsum tailer
1byte (1+n)byte 1byte nbyte 1byte 1byte

1.2.2. CSR8670->MCU

header length command parm chechsum tailer
1byte (1+n)byte 1byte nbyte 1byte 1byte

2. 应用配置

2.1. UART接口硬件配置

PStool -> UART configuration when under VM control -> 08a0

  • 1停止位
  • 无奇偶校验位
  • 关闭硬件流控制

PStool -> UART baud rate in bits per second -> 38400

2.2. 开发环境配置

xIDE -> project -> properties -> transport -> raw

3. 编写程序

3.1. 参考代码

参考CSR官方手册:I2C and UART Serial Interface Code Examples, CS-327754-AN-1。

定义一个结构体:

typedef struct 
{ 
    TaskData task; 
    Sink uart_sink; 
    Source uart_source; 
}UARTStreamTaskData; 

UARTStreamTaskData theUARTStreamTask; 

初始化UART流,并绑定任务钩子函数UARTStreamMessageHandler:

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(); 
    PanicNull(theUARTStreamTask.uart_sink); 

    /* Get the source for the uart */ 
    theUARTStreamTask.uart_source = StreamUartSource(); 
    PanicNull(theUARTStreamTask.uart_source); 

    /* Register uart source with task */ 
    MessageSinkTask(StreamSinkFromSource(theUARTStreamTask.uart_source), 
&theUARTStreamTask.task); 
} 

StreamUartConfigure的配置与2.1章的UART接口硬件配置一致。

Source绑定UART的接收数据缓冲区,sink绑定UART的发送数据缓冲区,缓冲区大小是512字节。

Source和sink的消息都会发送给UARTStreamMessageHandler,任何任务都可以发送消息给这个任务,包括theUARTStreamTask自己。

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; } }

参考代码实现的是一个echo的例子,即收到的数据不经任何处理会再次发送出去。如果接收缓冲区内有了新的数据,theUARTStreamTask会向UARTStreamMessageHandler发送MESSAGE_MORE_DATA消息,程序会执行到串口接收函数:

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); 

    /* Discards the specified amount of bytes from the front of the specified 
       source */
    SourceDrop(src, length); 
} 

接收函数首先检查source中是否有数据,如果有的话将数据的首地址以及数据的长度传递给发送函数。

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); 

    /*printf("%c", (*(dest+offset)));*/ /* Flush the data out to the uart */ PanicZero(SinkFlush(theUARTStreamTask.uart_sink, length)); } 

发送函数首先检查发送缓冲区是否有足够空间容纳新的数据。如果空间足够,待发送的数据被复制,并调用SinkFlush发送。

发送函数执行完毕后,接收函数调用sourcedrop将接收缓冲区清空。

如果新的数据被发送成功,UARTStreamMessageHandler会收到新的消息MESSAGE_MORE_SPACE,提示用户可以写入更多数据。这个功能在参考代码中未被使用。

3.2. 代码问题

参考代码有如下问题:

  • MCU发送1帧6字节的数据会触发两次MESSAGE_MORE_DATA,且第一次触发消息处理时,接收缓冲区内的数据只有1字节
  • 如果发送数据的速度慢于接收数据的速度,接收缓冲区会溢出,导致程序崩溃
  • 没有给出任务间消息通信机制的示例

3.2.1 解决第一个问题:

  • 在单次收到MESSAGE_MORE_DATA时尝试判断当前接收缓冲区内的数据是否足够完成单次解析所需的最小字节数。如果不足最小字节数,生成一个清空接收缓冲区的消息。这个消息会在100ms后发送给theUARTStreamTask,这样既可以保证接收缓冲区内没有不完整的数据,也使得在下一个消息到来时,接收缓冲区内有足够数量的数据。

  • 如果接收缓冲区内的数据长度大于最小可处理的字节数,将缓冲区的首字节当做header的比对字节做解析。如果解析失败,将比对字节向后移位1字节,直到解析出1条指令。解析出有效指令后,把这个指令对应的数据从接收缓冲区内删除。

  • 如果接收缓冲区收到的数据不能通过校验,这帧数据也会从接收缓冲区内删除。

  • 如果接收缓冲区在两次MESSAGE_MORE_DATA消息间隔期间积存了大量待处理的数据,尝试删除一部分数据,以防系统负载太大而死机。

/* 接收缓冲区内数据太多,删除一部分 */
if (uLength > BT_UART_BUFFER_RECV_DROP_LENGTH)
{
    UART_DEBUG(("UART: source length %d, source drop\n", uLength));

    MessageCancelAll(&theUARTStreamTask.task, MESSAGE_ID_DROP_BUFFER);
    SourceDrop(src, uLength);

    return;
}

while(uLength)
{    /* 判断接收缓冲区内的数据长度是否小于最小帧的长度 */
    if (uLength < BT_UART_BUFFER_LENGTH_MIN)
    {
        UART_DEBUG(("UART: source length not enough\n"));
        mSOURCE_DROP_AFTER_MS(100);

        return;
    }
    else
    {    /* 判断是否是一帧数据的header */
        if (pData[BT_UART_START_IDX] == BT_UART_HEADER_BYTE)
        {    /* 判断一帧数据的长度是否有效 */
            if ((pData[BT_UART_LENGTH_IDX] < BT_UART_DATA_LENGTH_MAX) && 
                 (pData[BT_UART_LENGTH_IDX] > BT_UART_DATA_LENGTH_MIN) &&
                 (pData[BT_UART_LENGTH_IDX] > BT_UART_DATA_LENGTH_OFFSET))
             {    /* 判断是否是一帧数据的tailer */
                if ((pData[BT_UART_CMD_LENGTH_OFFSET + pData[BT_UART_LENGTH_IDX]]) == BT_UART_TAILER_BYTE)
                {
                    uDataLen = pData[BT_UART_LENGTH_IDX] - BT_UART_DATA_LENGTH_OFFSET;
                    if (BTPrtcl_nucCalCheckSum((uint8 *)pData) == (pData[BT_UART_CHECKSUM_OFFSET_IDX + uDataLen]))
                    {    /* 校验通过,检查是否有新的应答数据等待被发送*/
                        if (!IsUARTSendBusy())
                        {    /* 解析指令 */
                            uart_prase_data((uint8 *)pData, uDataLen);
                        }
                    }
                    /* 取消所有清空缓冲区的消息 */
                    MessageCancelAll(&theUARTStreamTask.task, MESSAGE_ID_DROP_BUFFER);
                    /* Discards the specified amount of bytes from the front of the specified source */
                    SourceDrop(src, (BT_UART_CHECKSUM_OFFSET_IDX+uDataLen+2));
                    uLength -= (BT_UART_CHECKSUM_OFFSET_IDX+uDataLen+2);

                    continue;
                }
             }
        }
        pData += 1;
        uLength -= 1;
    }
}

3.2.2 解决第二个问题:

如果theUARTStreamTask已经收不到MESSAGE_MORE_SPACE消息,停止再解析新的消息。

if (BTPrtcl_nucCalCheckSum((uint8 *)pData) == (pData[BT_UART_CHECKSUM_OFFSET_IDX + uDataLen]))
{
    if (!IsUARTSendBusy())
    {
        uart_prase_data((uint8 *)pData, uDataLen);
    }
}

3.2.3 解决第三个问题:

其它任务向theUARTStreamTask发送消息的例子如下:

if (gTheSinkState == deviceA2DPStreaming)
{
    uUartDataBuffer[0] = FALSE;
    UARTSendResponse(BT_GET_A2DP_STATUS, uUartDataBuffer, 1);
}

uUartDataBuffer[0] = pNewState;
UARTSendResponse(BT_GET_CONNECT_STATUS, uUartDataBuffer, 1);

PanicUnlessNew向MMU申请一块内存单元,将待发送的数据复制到这个内存中。

void UARTSendResponse(uint8 cmd, uint8 *data, uint8 length)
{   
    sUartSendMessage *sMessage = PanicUnlessNew(sUartSendMessage);

    sMessage->uCmd = cmd;
    memcpy(sMessage->uData, data, length);
    sMessage->uLength = length;

    MessageSend(&theUARTStreamTask.task, MESSAGE_ID_SEND_RESPONSE, sMessage);
}

发送MESSAGE_ID_SEND_RESPONSE给theUARTStreamTask。

void UARTStreamMessageHandler (Task pTask, MessageId pId, Message pMessage) 
{ 
    if (pId == MESSAGE_MORE_DATA)
    {
        uart_data_stream_rx_data(((MessageMoreData *)pMessage)->source); } else if (pId == MESSAGE_MORE_SPACE) { uSendBusy = FALSE; } else if (pId == MESSAGE_ID_DROP_BUFFER) { uart_drop_buffer(StreamUartSource()); } else if (pId == MESSAGE_ID_SEND_RESPONSE) { uart_data_send_response(((sUartSendMessage *)pMessage)->uCmd, ((sUartSendMessage *)pMessage)->uData, ((sUartSendMessage *)pMessage)->uLength); } else { UART_DEBUG(("UART: Unhandled message %d", pId)); } }

参照1.2.定义的数据格式填充数据,调用SinkFlush发送给MCU。

void uart_data_send_response(uint8 cmd, uint8 *data, uint8 length)
{
    uint16 offset = 0; 
    uint8 *pData = NULL; 
    uint8 uBuffer[BT_UART_BUFFER_LENGTH];
    uint8 uLength = length;
    /* 填充header、length、command */
    uBuffer[BT_UART_START_IDX] = BT_UART_HEADER_BYTE;
    uBuffer[BT_UART_LENGTH_IDX] = (length+BT_UART_CAL_LENGTH_OFFSET);
    uBuffer[BT_UART_CMD_IDX] = cmd;
    /* 填充data */
    memcpy((uBuffer+BT_UART_FIRST_DATA_IDX), data, length);
    /* 计算checksum并填充,填充tailer */
    uBuffer[BT_UART_CHECKSUM_OFFSET_IDX+length] = BTPrtcl_nucCalCheckSum(uBuffer);
    uBuffer[BT_UART_CHECKSUM_OFFSET_IDX+length+BT_UART_TAILER_LENGTH_OFFSET] = BT_UART_TAILER_BYTE;
    uLength += (BT_UART_CHECKSUM_OFFSET_IDX+BT_UART_TAILER_LENGTH_OFFSET+1);

    /* Claim space in the sink, getting the offset to it */ 
    offset = SinkClaim(theUARTStreamTask.uart_sink, uLength); 
    if(offset == 0xFFFF)
        Panic();

    /* Map the sink into memory space */ 
    pData = SinkMap(theUARTStreamTask.uart_sink); 
    PanicNull(pData); 

    /* Copy data into the claimed space */ 
    memcpy(pData+offset, uBuffer, uLength); 

    /* Flush the data out to the uart */ 
    PanicZero(SinkFlush(theUARTStreamTask.uart_sink, uLength)); 

    uSendBusy = TRUE;
}

4 总结

MCU向CSR8670发送数据的间隔不宜长时间保持在50ms以下,短时间内不会有问题,但时间长的话系统会来不及处理消息,有几率导致系统崩溃。

你可能感兴趣的:(UART,CSR8670)