在协议栈中已经自带了串口的驱动与使用函数,所以只需要对串口进行初始化,并将串口绑定在APP层,重新编写串口回调函数就可以使用了。接下来将主要分析在协议栈中串口的初始化、串口数据的接收等。
从main函数进入系统初始化函数,再进入任务初始化函数,找到MT层任务初始化MT_TaskInit(),进入其中,将串口初始化函数与绑定函数复制到APP层任务初始化函数中。如下:
APP层想要调用MT层串口函数,必须包含头文件MT_UART.h,如图:
进入函数MT_UartInit()中,对串口进行配置,函数中配置了串口的波特率、流控、回调函数等,如图:
将波特率改为常用的115200,由于我们的串口用的TX\RX两线式,所以必须关闭流控如下:
下面来分析回调函数,当串口有数据接收时,就会调用串口回调函数。先来看下回调函数的注释与介绍:
/***************************************************************************************
* @fn MT_UartProcessZToolData
*
* @brief | SOP | Data Length | CMD | Data | FCS |
* | 1 | 1 | 2 | 0-Len | 1 |
*
* Parses the data and determine either is SPI or just simply serial data
* then send the data to correct place (MT or APP)
*
* @param port - UART port
* event - Event that causes the callback
*
*
* @return None
**************************************************************************************/
根据注释所讲,如果PC想通过串口给板子发消息,如果要使用官方的回调函数,就必须按照上面的格式发送,否则板子是收不到任何数据的。现在我们要实现的是串口可以接收到任何消息,然后原封不动的将消息发送出去,所以我们可以对官方回掉函数进行分析,然后对其进行修改,让其符合我们的需求。接下来先来分析官方回调函数:
void MT_UartProcessZToolData ( uint8 port, uint8 event )
{
uint8 ch;
uint8 bytesInRxBuffer;
(void)event; // Intentionally unreferenced parameter
while (Hal_UART_RxBufLen(port)) // 查询缓冲区信息,是否有数据
{
HalUARTRead (port, &ch, 1); // 读一个字节,读一个缓冲区清一个
switch (state) // 判断状态机
{
case SOP_STATE: // 起始位
if (ch == MT_UART_SOF) // MT_UART_SOF的值为0xFE 为开头
state = LEN_STATE;
break;
case LEN_STATE: // 长度位
LEN_Token = ch;
tempDataLen = 0;
/* Allocate memory for the data 分配内存*/
pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
MT_RPC_FRAME_HDR_SZ + LEN_Token );
if (pMsg) // 如果分配成功
{
/* Fill up what we can */
pMsg->hdr.event = CMD_SERIAL_MSG; // 串口事件编号 非常重要
pMsg->msg = (uint8*)(pMsg+1); // 定位数据位置
pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;
state = CMD_STATE1;
}
else
{
state = SOP_STATE;
return;
}
break;
case CMD_STATE1: // 命令位1
pMsg->msg[MT_RPC_POS_CMD0] = ch;
state = CMD_STATE2;
break;
case CMD_STATE2: // 命令位2
pMsg->msg[MT_RPC_POS_CMD1] = ch;
/* If there is no data, skip to FCS state */
if (LEN_Token)
{
state = DATA_STATE;
}
else
{
state = FCS_STATE;
}
break;
case DATA_STATE: // 数据位
/* Fill in the buffer the first byte of the data */
pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen++] = ch;
/* Check number of bytes left in the Rx buffer */
bytesInRxBuffer = Hal_UART_RxBufLen(port);
/* If the remain of the data is there, read them all, otherwise, just read enough */
if (bytesInRxBuffer <= LEN_Token - tempDataLen)
{
HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer);
tempDataLen += bytesInRxBuffer;
}
else
{
HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen);
tempDataLen += (LEN_Token - tempDataLen);
}
/* If number of bytes read is equal to data length, time to move on to FCS */
if ( tempDataLen == LEN_Token )
state = FCS_STATE;
break;
case FCS_STATE: // 校验位
FSC_Token = ch;
/* Make sure it's correct */
if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ + LEN_Token) == FSC_Token))
{
osal_msg_send( App_TaskID, (byte *)pMsg ); // 将数据打包发送至OSAL
}
else
{
/* deallocate the msg */
osal_msg_deallocate ( (uint8 *)pMsg ); // 清内存
}
/* Reset the state, send or discard the buffers at this point */
state = SOP_STATE; // 状态机一周完成
break;
default:
break;
}
}
}
分析代码可知,串口在接收时,首先判断数据开头是否为0xFE,然后得到数据长度给pMsg分配内存,将数据装入pMsg,最后校验完成把数据打包发送至OSAL,释放内存。所以,我们如果要将串口接收改为我们自己的,需要做的就是首先接数据,然后判断长度并分配内存,打包发送给OSAL,最后释放内存,代码如下:
void MT_UartProcessZToolData ( uint8 port, uint8 event )
{
uint8 flag = 0; // flag用于判断是否接收到数据
uint8 i = 0,len = 0 ; // len用于记录数据长度
uint8 buf[128]; // 串口缓冲区最大默认128
(void)event; // Intentionally unreferenced parameter
while (Hal_UART_RxBufLen(port)) // 查询缓冲区信息,是否有数据
{
HalUARTRead (port, &buf[len], 1); // 读一个字节,读一个缓冲区清一个
len ++;
flag = 1;
}
if(flag == 1)
{
//分配内存空间,为结构体内容 + 数据内容 + 1个记录长度的数据
pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
len + 1);
pMsg->hdr.event = CMD_SERIAL_MSG; // 串口事件编号 非常重要
pMsg->msg = (uint8*)(pMsg+1); // 定位数据位置
pMsg->msg[0] = len; // 传送至OSAL的第一个数据为长度
for(i=0;i<len;i++) // 将缓冲区数据装入数据包
{
pMsg->msg[i+1] = buf[i];
}
osal_msg_send( App_TaskID, (byte *)pMsg ); // 将数据打包发送至OSAL
osal_msg_deallocate ( (uint8 *)pMsg ); // 清内存
}
}
到这里,数据已经被接收完毕,并发送给上层了。由于串口在APP层进行初始化,并且注册在APP层,所以这个串口接收事件就被发送至APP层,我们现在需要在APP层中对该进行处理。进入APP层事件处理函数SampleApp_ProcessEvent()中,并未发现有串口事件的处理,如下:
显而易见,串口事件需要我们自己添加进去,并且需要自己编写被调用的串口相应函数,比如,按键、无线的处理函数。如下:
void SampleApp_SerialCB(mtOSALSerialData_t *SerialMsg)
{
uint8 len,*buf; // len为数据长度 buf为发送数据首地址
buf = SerialMsg -> msg; // 数据起始
len = *buf; // 获取长度
if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc, // 广播模式
SAMPLEAPP_SERIAL_CLUSTERID, // 数据类型ID 簇ID 自己动手定义
len+1, // 发送数据长度
buf, // 发送数据的地址 变量值为0
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
else
{
// Error occurred in request to send.
}
}
函数在编写过程中,由于串口事件宏定义在MT.h中,需要包含这个头文件,以及我们在发送时,簇ID为SAMPLEAPP_SERIAL_CLUSTERID,这个也是我们自己定义的,函数编写完成后,还需要在初始化函数前进行声明,如下:
这些完成之后,就可以在SampleApp_ProcessEvent()中对串口接收事件进行判断处理,如下:
到此,从PC发送消息,开发板通过串口接收消息,并通过无线发送出去已经完成,接下来需要做的就只剩下从无线拿到数据通过串口发送出去了。
在SampleApp_ProcessEvent()中找到无线接收处理函数SampleApp_MessageMSGCB(),进入其中,添加关于串口消息的处理,如下:
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
uint8 i,len;
switch ( pkt->clusterId )
{
case SAMPLEAPP_SERIAL_CLUSTERID: // 判断为串口数据
len = pkt->cmd.Data[0];
for(i=0;i<len;i++)
{
HalUARTWrite(0,&pkt->cmd.Data[i+1],1);
}
break;
case SAMPLEAPP_PERIODIC_CLUSTERID: // 判断为广播数据
if( *pkt->cmd.Data == 0) // 从数据包中找到用户数据
{
HalLedSet(1,HAL_LED_MODE_TOGGLE); // 切换LED的状态
}
break;
case SAMPLEAPP_FLASH_CLUSTERID:
flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
break;
}
}
其中,串口发送函数HalUARTWrite(),在hal_uart.h文件中声明,直接调用即可。
最后右键工程文件,进入options中,将一些关于调试的宏关闭,防止在串口打印时,打印很多无关的乱码调试信息,如下:
现象:分别给两个模块下载协调器、终端节点或者路由的代码,并将开发板分别与PC通过串口连接,打开两个串口助手,其中一个发送hello,另外一个将会显示hello,说明串口透传功能实现了。如图: