串口接收发送数据有两种方式,一种是中断的模式,另一种是DMA方式,这里主要以中断的方式,来看一下使用串口来发送,接收数据的整个流程。这里以SerialApp例程为例子。
在mian函数中的调用HalDriverInit();函数,在函数中初始化串口,主要是配置管脚和DMA通道
void HalDriverInit (void)
{
...................................
#if (defined HAL_UART) && (HAL_UART == TRUE)
HalUARTInit();
#endif
....................................
}
从程序中可以看出要想使用协议栈中串口,初始化串口必须定义HAL_UART和HAL_UART TRUE 在hal_board_cfg.h文件中。
#ifndef HAL_UART
#if (defined ZAPP_P1) || (defined ZAPP_P2) || (defined ZTOOL_P1) || (defined ZTOOL_P2)
#define HAL_UART TRUE
#else
#define HAL_UART FALSE
#endif
#endif
然后在osal_start_system()开始系统后,会调用Hal_ProcessPoll()来读取时间和串口。
在CC2430的数据手册中有这样一段话。
Data reception on the UART is initiatedwhen a 1 is written to the UxCSR.RE bit
The UART will then search for a valid start bit on the RXDx input pin and set the
UxCSR.ACTIVE bit high. When a validstart bit has been detected the received byte is shifted into the receive register .The UxCSR.RX_BYTE bit is set and a receive interrupt is generated when the operation has completed. The received data byte is available through the UxBUF register. When UxBUF is read, UxCSR.RX_BYTE is cleared by hardware.
当有数据接收时,UxCSR.RE位将被置1,然后,UART将在RXDx的输入引脚上查找一个有效的开始位,当找到这个开始位时,将设置UxCSR.ACTIVE位为高电平。当一个有效的开始位被查找到,收到的字节将被移动到接收寄存器中。然后,UxCSR.RX_BYTE位设为1.并且,当这个接收操作完成后接收中断会被产生。接收到的数据可以通过操作UxBUF寄存器,当UxBUF寄存器的数据被读出后,UxCSR.RX_BYTE位被硬件清除。串口发生中断首先调用中断的处理函数,这个是接收的中断函数。
#if HAL_UART_0_ENABLE
HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )
{
cfg0->rxBuf[cfg0->rxHead] = U0DBUF;
if ( cfg0->rxHead == cfg0->rxMax )
{
cfg0->rxHead = 0;
}
else
{
cfg0->rxHead++;
}
}
#endif
该中断函数主要是把U0DBUF寄存器,也就是接收到数据的寄存器,把数据读取来放到UART的结构体中的,cfg0->rxBuf[],中,这个数组的内存分配是在HalUARTOpen()函数中。
SerialApp.c中有下面的定义
#if !defined( SERIAL_APP_RX_MAX )
#if (defined( HAL_UART_DMA )) && HAL_UART_DMA
#define SERIAL_APP_RX_MAX 128
#else
#define SERIAL_APP_RX_MAX 64
#endif
#endif
SerialApp_Init()函数中有下面的赋值,
uartConfig.rx.maxBufSize = SERIAL_APP_RX_MAX;
HalUARTOpen()函数中有下面的赋值:所以其cfg->rxMax=128,
cfg->rxMax = config->rx.maxBufSize;
其中rxHead这个参数始终指向像一个参数被存放到rxBuf的位置。因为硬件串口缓存器U0DBUF只能存放一个字节,如果不及时把这个接收到的转移出去,那么就会被下一个到来的字节覆盖掉,所以rxHead变量就指向了这个存放的地址,当然是基于定义的rxBuf存储空间。
而if ( cfg0->rxHead == cfg0->rxMax )这一句判断也说明的很清楚,一旦这个计数达到了定义的最大接收数量,也就是说已经把rxBuf存储空间占满了,那么就不能在继续存放了。
中断函数执行完后,就应该跳到发生中断时执行的地方,这时程序继续执行,然后在osal_start_system()开始系统后,会循环调用Hal_ProcessPoll()来读取时间和串口,
void Hal_ProcessPoll ()
{
HalTimerTick();
#if (defined HAL_UART) && (HAL_UART == TRUE)
HalUARTPoll();
#endif
}
下面是HalUARTPoll();函数的源代码,在这里有对接收到的数据进行处理的程序。
void HalUARTPoll( void )
{
#if ( HAL_UART_0_ENABLE | HAL_UART_1_ENABLE )
static uint8 tickShdw;
uartCfg_t *cfg;
uint8 tick;
#if HAL_UART_0_ENABLE
//当发生串口接收中断时cfg0就会改变,如果串口没有数据输入cfg0为空,当接收到数据时cfg0将在串口中断服务程序中被改变
if ( cfg0 )
{
cfg = cfg0;
}
#endif
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
#endif
// Use the LSB of the sleep timer (ST0 must be read first anyway).
//系统上电后,睡眠定时器就会自动启动做自增计数ST0即睡眠定时器启动到现在计算值的最低8位
tick = ST0 - tickShdw;
tickShdw = ST0;
//下面是一个无限循环
do
{
//------------发送超时时间
if ( cfg->txTick > tick )
{
cfg->txTick -= tick;
}
else
{
cfg->txTick = 0;
}
//---------------------接收超时时间
if ( cfg->rxTick > tick )
{
cfg->rxTick -= tick;
}
else
{
cfg->rxTick = 0;
}
//是使用DMA方式还是使用中断方式
#if HAL_UART_ISR
#if HAL_UART_DMA
if ( cfg->flag & UART_CFG_DMA )
{
pollDMA( cfg );
}
else//中断方式
#endif
{
pollISR( cfg );
}
#elif HAL_UART_DMA
pollDMA( cfg );
#endif
if ( cfg->rxHead != cfg->rxTail ) //不相等表示有数据
{
uint8 evt;
if ( cfg->rxHead >= (cfg->rxMax - SAFE_RX_MIN) )
{
//已保存的数据已经超过了安全界限,发送接收满事件
evt = HAL_UART_RX_FULL;
}
else if ( cfg->rxHigh && (cfg->rxHead >= cfg->rxHigh) )
{
//rxBuf[ ]接收到预设值(默认80字节),则触发事件,为什么是80,在上一篇转载的文章中有介绍,这里重点关注执行的流程。
evt = HAL_UART_RX_ABOUT_FULL;
}
else if ( cfg->rxTick == 0 )
{
//超时事件
evt = HAL_UART_RX_TIMEOUT;
}
else
{
evt = 0;
}
//如果发生事件,并且配置了回调函数则调用回调函数
if ( evt && cfg->rxCB )
{
//(cfg->flag & UART_CFG_U1F)!=0)判读是那个串口,如果是串口1则为1,否则为0
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );
}
}
#if HAL_UART_0_ENABLE
if ( cfg == cfg0 )
{
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
else
#endif
break;
}
else
#endif
break;
} while ( TRUE );
#else
return;
#endif
}
说明:(1)下面我们看一下pollISR()函数
static void pollISR( uartCfg_t *cfg )
{
//计算rxBuf[]中还有多少数据没有读出(以字节为单位)
uint8 cnt = UART_RX_AVAIL( cfg );
//如果串口没有接收到数据,也就是说没有发生过串口接收中断,那么cfg应为是为空的,则cnt=0如果发生了串口中断,则cnt计算出串口缓存中还有多少数据没有读出,这个缓存并不是硬件寄存器的缓存,而是程序中开辟一段空间
if ( !(cfg->flag & UART_CFG_RXF) )
{
//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡眠定时器来控制),而且需要把已经读出的数据数目减去!
// If anything received, reset the Rx idle timer.
if ( cfg->rxCnt != cnt )
{
cfg->rxTick = HAL_UART_RX_IDLE;
cfg->rxCnt = cnt;
}
if ( cfg->rxCnt >= (cfg->rxMax - SAFE_RX_MIN) )
{
RX_STOP_FLOW( cfg );
}
}
}
#endif
pollISR()函数主要作用就是设置rxTick和rxCn,
//关于安全界限,在程序中有下面一段:
//如果声明了流控制,为保证数据的正确接收需要在RX缓存区中预留出足够的空间。CC2430可以使用的最大串口波特率为115.2k。这个安全界限的数字跟使用的波特率还有串口tick有关。具体参考《Z-STACK问题之串口结构uartCfg_t乱说》文章。
可以看到,在初始化时rxHead=rxTail=0,如果发生接收中断,在中断服务函数中把U0DBUF寄存器中的数据传送到rxbuf[]中,这时rxHead和rxTail的值不在相等,其中,rxHead是rxBuf[]接收到数据的个数,rxTail是rxBuf[]移出的数据个数,再根据两者的差值,判断具体的事件evt。然后,根据evt和设置的回调函数,通过cfg->rxCB调用相应的回调函数。代码也是体显在下面一句。
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );
第一个参数主要是判断,是UART1还是UART0.第二个参数是触发的事件类型,那个个回调函数,具体是指向函数呢?
首先,我们在void SerialApp_Init( uint8 task_id )初始化函数中,对串口进行了配置,其中下面两句中有关于回调函数的。
#else
uartConfig.callBackFunc = rxCB;
#endif
HalUARTOpen (SERIAL_APP_PORT, &uartConfig);
其中,在HalUARTOpen()函数中,有下面的一条语句,
uint8 HalUARTOpen( uint8 port, halUARTCfg_t *config )
{
...................
cfg->rxCB = config->callBackFunc;
...................
}
也就是调用下面的rxCB函数。程序中定义了两个串口接收缓冲区:otaBuf上otaBuf2.当otaBuf中无数据时,处于空闲状态时,由otaBuf接收串口数据;当otaBuf中保留有数据时,下等待接收节点发送接收数据响应或由于某些正在重新给接收节点发送数据时,可通过otaBuf2接收数据,当otaBuf和otaBuf2都没有处于空闲状态时,说明数据没有及时发送给接收节点,发生了数据累积,缓冲区被占用,需要进行流量控制,所以直接退出接收回调函数,暂不接收数据。
static void rxCB( uint8 port, uint8 event )
{
uint8 *buf, len;
if ( otaBuf2 ) //缓冲区被占用
{
return;
}
if ( !(buf = osal_mem_alloc( SERIAL_APP_RX_CNT )) )
{
return;
}
len = HalUARTRead( port, buf+1, SERIAL_APP_RX_CNT-1 );
if ( !len ) // Length is not expected to ever be zero.
{
osal_mem_free( buf );
return;
}
if ( otaBuf ) //otaBuf正在被占用
{
otaBuf2 = buf; //otaBuf2接收数据
otaLen2 = len;
}
else
{
otaBuf = buf; //otaBuf接收数据
otaLen = len;
osal_set_event( SerialApp_TaskID, SERIALAPP_MSG_SEND_EVT );
}
}
#endif
在事件处理函数中,有下面的判断。
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
........................
if ( events & SERIALAPP_MSG_SEND_EVT )
{
SerialApp_SendData( otaBuf, otaLen );//
return ( events ^ SERIALAPP_MSG_SEND_EVT );
}
.........................
}
下面是SerialApp_SendData()函数的源代码,调用AF_DataRequest(),通过OTA发送数据。由于在数据包之前增加了序列号SerialApp_SeqTx,多次重发的数据不会被接收节点重复发送到串口。
static void SerialApp_SendData( uint8 *buf, uint8 len )
{
afStatus_t stat;
// Pre-pend sequence number to the start of the Rx buffer.
*buf = ++SerialApp_SeqTx;
otaBuf = buf;
otaLen = len+1;
stat = AF_DataRequest( &SerialApp_DstAddr,
(endPointDesc_t *)&SerialApp_epDesc,
SERIALAPP_CLUSTERID1,
otaLen, otaBuf,
&SerialApp_MsgID, 0, AF_DEFAULT_RADIUS );
if ( (stat == afStatus_SUCCESS) || (stat == afStatus_MEM_FAIL) )
{
//在设定的时间内没有发送成功,则重新发送。
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT,
SERIALAPP_MSG_RTRY_TIMEOUT );
rtryCnt = SERIALAPP_MAX_RETRIES;
}
else
{
FREE_OTABUF();//重发的次数
}
}
void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt )
{
uint8 stat;
uint8 seqnb;
uint8 delay;
switch ( pkt->clusterId )
{
// A message with a serial data block to be transmitted on the serial port.
//接收节点收到的接收数据命令,
case SERIALAPP_CLUSTERID1:
seqnb = pkt->cmd.Data[0];
// Keep message if not a repeat packet
if ( (seqnb > SerialApp_SeqRx) || // Normal
((seqnb < 0x80 ) && ( SerialApp_SeqRx > 0x80)) ) // Wrap-around
{
// Transmit the data on the serial port.接收到的发送到串口
if ( HalUARTWrite( SERIAL_APP_PORT, pkt->cmd.Data+1,
(pkt->cmd.DataLength-1) ) )
{
// Save for next incoming message
SerialApp_SeqRx = seqnb;
stat = OTA_SUCCESS;
}
else
{
stat = OTA_SER_BUSY;
}
}
else
{
stat = OTA_DUP_MSG;
}
// Select approproiate OTA flow-control delay.
delay = (stat == OTA_SER_BUSY) ? SERIALAPP_NAK_DELAY : SERIALAPP_ACK_DELAY;
// Build & send OTA response message. 发送响应消息
rspBuf[0] = stat;
rspBuf[1] = seqnb;
rspBuf[2] = LO_UINT16( delay );
rspBuf[3] = HI_UINT16( delay );
//发送接收数据响应命令
stat = AF_DataRequest( &(pkt->srcAddr),
(endPointDesc_t*)&SerialApp_epDesc,
SERIALAPP_CLUSTERID2,
SERIAL_APP_RSP_CNT ,
rspBuf,&SerialApp_MsgID, 0,
AF_DEFAULT_RADIUS );
if ( stat != afStatus_SUCCESS )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_RSP_RTRY_EVT,
SERIALAPP_RSP_RTRY_TIMEOUT );
// Store the address for the timeout retry. 存储发送超时的,目的地址
osal_memcpy(&SerialApp_RspDstAddr, &(pkt->srcAddr), sizeof( afAddrType_t ));
}
break;
// A response to a received serial data block. 接收到接收数据响应命令
case SERIALAPP_CLUSTERID2:
if ( (pkt->cmd.Data[1] == SerialApp_SeqTx) &&
((pkt->cmd.Data[0] == OTA_SUCCESS) ||
(pkt->cmd.Data[0] == OTA_DUP_MSG)) ) //目的设备接收数据的状态
{
// Remove timeout waiting for response from other device. 接收到返回的状态后,关闭定时器
osal_stop_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT );
FREE_OTABUF(); //释放缓存区
}
else
{
delay = BUILD_UINT16( pkt->cmd.Data[2], pkt->cmd.Data[3] );
// Re-start timeout according to delay sent from other device.
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT, delay );
}
break;
default:
break;
}
}
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
if ( events & SYS_EVENT_MSG ) //ZDO层接收到注册过的消息
{
afIncomingMSGPacket_t *MSGpkt;
while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive(
SerialApp_TaskID )) )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
SerialApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
SerialApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,
((keyChange_t *)MSGpkt)->keys );
break;
//接收到命令,然后执行,zigbee协议信息的传递有两种方式:消息和命令,消息长度不限,命令的大小则严格规定
case AF_INCOMING_MSG_CMD:
//执行发送过来消息命令的回调函数
SerialApp_ProcessMSGCmd( MSGpkt );
break
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt ); // Release the memory.
}
// Return unprocessed events
return ( events ^ SYS_EVENT_MSG );
}
//发送数据的事件,这里是串口通过CC2430发送数据到另一个设备
if ( events & SERIALAPP_MSG_SEND_EVT )
{
SerialApp_SendData( otaBuf, otaLen );
return ( events ^ SERIALAPP_MSG_SEND_EVT );
}
//重发数据的事件,如果发送数据没有成功的话
if ( events & SERIALAPP_MSG_RTRY_EVT )
{
if ( --rtryCnt )
{
AF_DataRequest( &SerialApp_DstAddr,
(endPointDesc_t *)&SerialApp_epDesc,
SERIALAPP_CLUSTERID1, otaLen,
otaBuf,
&SerialApp_MsgID, 0,
AF_DEFAULT_RADIUS );
osal_start_timerEx( SerialApp_TaskID,
SERIALAPP_MSG_RTRY_EVT,
SERIALAPP_MSG_RTRY_TIMEOUT );
}
else
{
FREE_OTABUF();
}
return ( events ^ SERIALAPP_MSG_RTRY_EVT );
}
//发送接收数据响应的重发事件
if ( events & SERIALAPP_RSP_RTRY_EVT )
{
afStatus_t stat = AF_DataRequest(
&SerialApp_RspDstAddr,
(endPointDesc_t *)&SerialApp_epDesc,
SERIALAPP_CLUSTERID2,
SERIAL_APP_RSP_CNT, rspBuf,
&SerialApp_MsgID, 0,
AF_DEFAULT_RADIUS );
if ( stat != afStatus_SUCCESS )
{
osal_start_timerEx( SerialApp_TaskID,
SERIALAPP_RSP_RTRY_EVT,
SERIALAPP_RSP_RTRY_TIMEOUT );
}
return ( events ^ SERIALAPP_RSP_RTRY_EVT );
}
#if SERIAL_APP_LOOPBACK
if ( events & SERIALAPP_TX_RTRY_EVT )
{
if ( rxLen )
{
if ( !HalUARTWrite( SERIAL_APP_PORT, rxBuf, rxLen ) )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_TX_RTRY_EVT,
SERIALAPP_TX_RTRY_TIMEOUT );
}
else
{
rxLen = 0;
}
}
return ( events ^ SERIALAPP_TX_RTRY_EVT );
}
#endif
return ( 0 ); // Discard unknown events.
}
在串口通信中设置了多个重发机制,增加数据通信的可靠性,首先是利用重发数据事件SERIALAPP_MSG_RTRY_EVT重发数据,重发的次数由rtyCnt设定。由于在数据包之前增加了序列号SerialApp_SeqTx,多次生发的数据不会被接收节点重复发送到串口。别外,加入了数据接收响应机制,发送节点在发送完数据后,等待接收节点返回接收数据响应,收到返回接收数据响应的命令SERIALAPP_CLUSTERID2:后,判断信息包中的接收状态参数。若接收状态为OTA_DUP_MSG,表明接收节点串口繁忙,应启动重发机制,延时后产生重发数据事件,若接收状态为OTA_SUCCESS,表明接收节点将数据成功发送到串口,就释放缓存区,等待串口接收下一包数据。
下面是串口的接受和发送的流程图:
对于串口的使用及工作流程:
首先系统调用初始化函数进行串口的初始化
void HalUARTInit( void )
{
#if HAL_UART_DMA //在默认情况下,这个东西是定义为true
halDMADesc_t *ch;//如果定义了DMA传输方式就定义一个通道号
#endif
// Set P2 priority - USART0 over USART1 if both are defined.
P2DIR &= ~P2DIR_PRIPO;
P2DIR |= HAL_UART_PRIPO;
#if HAL_UART_0_ENABLE //默认情况下为HAL_UART_0_ENABLE
// Set UART0 I/O location to P0.
PERCFG &= ~HAL_UART_0_PERCFG_BIT;
P0SEL |= HAL_UART_0_P0_RX_TX;
ADCCFG &= ~HAL_UART_0_P0_RX_TX;
U0CSR = CSR_MODE; 模式位UART模式
U0UCR = UCR_FLUSH;
#endif
#if HAL_UART_1_ENABLE
// Set UART1 I/O location to P1.
PERCFG |= HAL_UART_1_PERCFG_BIT;
P1SEL |= HAL_UART_1_P1_RX_TX;
ADCCFG &= ~HAL_UART_1_P1_RX_TX;
U1CSR = CSR_MODE;
U1UCR = UCR_FLUSH;
#endif
#if HAL_UART_DMA
// Setup Tx by DMA.
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX ); //设置发送通道
// The start address of the destination.
HAL_DMA_SET_DEST( ch, DMA_UDBUF );//目标的开始抵制
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );//传输长度
// One byte is transferred each time.
HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_BYTE );//每次传输一个字节
// The bytes are transferred 1-by-1 on Tx Complete trigger.
HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_TX );
// The source address is decremented by 1 byte after each transfer.
HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_1 );
// The destination address is constant - the Tx Data Buffer.
HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_0 );
// The DMA is to be polled and shall not issue an IRQ upon completion.
HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );
// Xfer all 8 bits of a byte xfer.
HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
// DMA Tx has shared priority for memory access - every other one.
HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );
// Setup Rx by DMA.通过DMA设置接收通道
ch = HAL_DMA_GET_DESC1234( HAL_DMA_CH_RX );
// The start address of the source.
HAL_DMA_SET_SOURCE( ch, DMA_UDBUF );
// Using the length field to determine how many bytes to transfer.
HAL_DMA_SET_VLEN( ch, HAL_DMA_VLEN_USE_LEN );
HAL_DMA_SET_WORD_SIZE( ch, HAL_DMA_WORDSIZE_WORD );
// The bytes are transferred 1-by-1 on Rx Complete trigger.
HAL_DMA_SET_TRIG_MODE( ch, HAL_DMA_TMODE_SINGLE );
HAL_DMA_SET_TRIG_SRC( ch, DMATRIG_RX );
// The source address is constant - the Rx Data Buffer.
HAL_DMA_SET_SRC_INC( ch, HAL_DMA_SRCINC_0 );
// The destination address is incremented by 1 word after each transfer.
HAL_DMA_SET_DST_INC( ch, HAL_DMA_DSTINC_1 );
// The DMA is to be polled and shall not issue an IRQ upon completion.
HAL_DMA_SET_IRQ( ch, HAL_DMA_IRQMASK_DISABLE );
// Xfer all 8 bits of a byte xfer.
HAL_DMA_SET_M8( ch, HAL_DMA_M8_USE_8_BITS );
// DMA has highest priority for memory access.
HAL_DMA_SET_PRIORITY( ch, HAL_DMA_PRI_HIGH );
#endif
}
虽然看不懂,但先别管,搞清楚基本构架再说,来继续看轮询函数
void HalUARTPoll( void )
{
#if ( HAL_UART_0_ENABLE | HAL_UART_1_ENABLE )
static uint8 tickShdw;//tickshdw这个变量时干嘛的?
uartCfg_t *cfg;看看uartCfg_t的定义
****************************************************
注:
typedef struct
{
uint8 *rxBuf;
uint8 rxHead;
uint8 rxTail;
uint8 rxMax;
uint8 rxCnt;
uint8 rxTick;
uint8 rxHigh;
uint8 *txBuf;
#if HAL_UART_BIG_TX_BUF
uint16 txHead;
uint16 txTail;
uint16 txMax;
uint16 txCnt;
#else
uint8 txHead;
uint8 txTail;
uint8 txMax;
uint8 txCnt;
#endif
uint8 txTick;
uint8 flag;
halUARTCBack_t rxCB;
} uartCfg_t;
****************************************************
uint8 tick; //这个变量时什么东东?
#if HAL_UART_0_ENABLE
if ( cfg0 )
{
cfg = cfg0;
}
****************
注:
cfg0的定义:
#if HAL_UART_0_ENABLE
static uartCfg_t *cfg0;
cfg0在哪进行的配置?
串口又是在哪个地方打开的呢??
****************
#endif
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
#endif
// Use the LSB of the sleep timer (ST0 must be read first anyway).
tick = ST0 - tickShdw;
tickShdw = ST0;//这两句话是什么意思??
**************
注:STO在哪定义?
**************
do 这他妈的是个死循环 难道不用退出来吗??
{
if ( cfg->txTick > tick )
{
cfg->txTick -= tick;
}
else
{
cfg->txTick = 0;
}
if ( cfg->rxTick > tick )
{
cfg->rxTick -= tick;
}
else
{
cfg->rxTick = 0;
}
#if HAL_UART_ISR //是中断方式还是DMA方式
************
#if !defined( HAL_UART_ISR )
#define HAL_UART_ISR FALSE
#endif
************
#if HAL_UART_DMA
if ( cfg->flag & UART_CFG_DMA )
{
pollDMA( cfg );
******************************************************************************
* @fn pollDMA
*
* @brief Poll a USART module implemented by DMA.
*
* @param cfg - USART configuration structure.
*
* @return none
*****************************************************************************
}
else
#endif
{
pollISR( cfg );
******************************************************************************
* @fn pollISR
*
* @brief Poll a USART module implemented by ISR.
*
* @param cfg - USART configuration structure.
*
* @return none
*****************************************************************************
}
#elif HAL_UART_DMA
pollDMA( cfg );
#endif
if ( cfg->rxHead != cfg->rxTail )//有数据
{
uint8 evt;
if ( cfg->rxHead >= (cfg->rxMax - SAFE_RX_MIN) )
{
evt = HAL_UART_RX_FULL;
}
else if ( cfg->rxHigh && (cfg->rxHead >= cfg->rxHigh) )
{
evt = HAL_UART_RX_ABOUT_FULL;
}
else if ( cfg->rxTick == 0 ) //超时事件?什么是超时事件?
{
evt = HAL_UART_RX_TIMEOUT;
}
else
{
evt = 0;
}
if ( evt && cfg->rxCB )//如果有事件发生并且设置了回调函数
{
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );
}
}
//(cfg->flag & UART_CFG_U1F)!=0)判读是那个串口,如果是串口1则为1,否则为0?
#if HAL_UART_0_ENABLE
if ( cfg == cfg0 )
{
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
else
#endif
break;
}
else
#endif
break;
} while ( TRUE );
#else
return;
#endif
}
看了一大截,还是越来越模糊。
我们现在主要看看中断方式,DMA方式还是算了吧,以后有时间在看。
让我们看看这个函数:
static void pollISR( uartCfg_t *cfg )
{
//计算rxBuf[]中还有多少数据没有读出(以字节为单位)
uint8 cnt = UART_RX_AVAIL( cfg );
//如果串口没有接收到数据,也就是说没有发生过串口接收中断,那么cfg应为是为空的,则cnt=0如果发生了串口中断,则cnt计算出串口缓存中还有多少数据没有读出,这个缓存并不是硬件寄存器的缓存,而是程序中开辟一段空间
if ( !(cfg->flag & UART_CFG_RXF) )
{
//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡眠定时器来控制),而且需要把已经读出的数据数目减去!
// If anything received, reset the Rx idle timer.
if ( cfg->rxCnt != cnt )
{
cfg->rxTick = HAL_UART_RX_IDLE;
cfg->rxCnt = cnt;
}
if ( cfg->rxCnt >= (cfg->rxMax - SAFE_RX_MIN) )
{
RX_STOP_FLOW( cfg );
}
}
}
#endif
虽然看不懂,但是继续看吧,谁叫我笨呢?
接下来看
@fn HalUARTOpen
*
* @brief Open a port according tp the configuration specified by parameter.
*
* @param port - UART port
* config - contains configuration information
*
* @return Status of the function call
*****************************************************************************/
uint8 HalUARTOpen( uint8 port, halUARTCfg_t *config )
根据参数指定的配置打开串口
{
uartCfg_t **cfgPP = NULL;
uartCfg_t *cfg;
#if HAL_UART_0_ENABLE
if ( port == HAL_UART_PORT_0 )
{
cfgPP = &cfg0;
}
#endif
#if HAL_UART_1_ENABLE
if ( port == HAL_UART_PORT_1 )
{
cfgPP = &cfg1;
}
#endif
HAL_UART_ASSERT( cfgPP );//这个函数是干嘛用的?
****************************
注:
****************************
#if HAL_UART_CLOSE
// Protect against user re-opening port before closing it.
HalUARTClose( port );
#else
HAL_UART_ASSERT( *cfgPP == NULL );
#endif
HAL_UART_ASSERT( (config->baudRate == HAL_UART_BR_38400) ||
(config->baudRate == HAL_UART_BR_115200) );
*cfgPP = (uartCfg_t *)osal_mem_alloc( sizeof( uartCfg_t ) );
cfg = *cfgPP;
HAL_UART_ASSERT( cfg );
cfg->rxMax = config->rx.maxBufSize;
#if !HAL_UART_BIG_TX_BUF
HAL_UART_ASSERT( (config->tx.maxBufSize < 256) );
#endif
cfg->txMax = config->tx.maxBufSize;
cfg->txBuf = osal_mem_alloc( cfg->txMax+1 );
cfg->rxHead = cfg->rxTail = 0;
cfg->txHead = cfg->txTail = 0;
cfg->rxHigh = config->rx.maxBufSize - config->flowControlThreshold;
cfg->rxCB = config->callBackFunc;
#if HAL_UART_0_ENABLE
if ( port == HAL_UART_PORT_0 )
{
// Only supporting 38400 or 115200 for code size - other is possible.
U0BAUD = (config->baudRate == HAL_UART_BR_38400) ? 59 : 216;
U0GCR = (config->baudRate == HAL_UART_BR_38400) ? 10 : 11;
U0CSR |= CSR_RE;
#if HAL_UART_DMA == 1
cfg->flag = UART_CFG_DMA;
HAL_UART_ASSERT( (config->rx.maxBufSize <= 128) );
HAL_UART_ASSERT( (config->rx.maxBufSize > SAFE_RX_MIN) );
cfg->rxBuf = osal_mem_alloc( cfg->rxMax*2 );
osal_memset( cfg->rxBuf, ~DMA_PAD, cfg->rxMax*2 );
DMA_RX( cfg );
#else
cfg->flag = 0;
HAL_UART_ASSERT( (config->rx.maxBufSize < 256) );
cfg->rxBuf = osal_mem_alloc( cfg->rxMax+1 );
URX0IE = 1;
IEN2 |= UTX0IE;
#endif
// 8 bits/char; no parity; 1 stop bit; stop bit hi.
if ( config->flowControl )
{
cfg->flag |= UART_CFG_FLW;
U0UCR = UCR_FLOW | UCR_STOP;
// Must rely on H/W for RTS (i.e. Tx stops when receiver negates CTS.)
P0SEL |= HAL_UART_0_P0_RTS;
// Cannot use H/W for CTS as DMA does not clear the Rx bytes properly.
P0DIR |= HAL_UART_0_P0_CTS;
RX0_FLOW_ON;
}
else
{
U0UCR = UCR_STOP;
}
}
#endif
#if HAL_UART_1_ENABLE
if ( port == HAL_UART_PORT_1 )
{
// Only supporting 38400 or 115200 for code size - other is possible.
U1BAUD = (config->baudRate == HAL_UART_BR_38400) ? 59 : 216;
U1GCR = (config->baudRate == HAL_UART_BR_38400) ? 10 : 11;
U1CSR |= CSR_RE;
#if HAL_UART_DMA == 2
cfg->flag = (UART_CFG_U1F | UART_CFG_DMA);
HAL_UART_ASSERT( (config->rx.maxBufSize <= 128) );
HAL_UART_ASSERT( (config->rx.maxBufSize > SAFE_RX_MIN) );
cfg->rxBuf = osal_mem_alloc( cfg->rxMax*2 );
osal_memset( cfg->rxBuf, ~DMA_PAD, cfg->rxMax*2 );
DMA_RX( cfg );
#else
cfg->flag = UART_CFG_U1F;
HAL_UART_ASSERT( (config->rx.maxBufSize < 256) );
cfg->rxBuf = osal_mem_alloc( cfg->rxMax+1 );
URX1IE = 1;
IEN2 |= UTX1IE;
#endif
// 8 bits/char; no parity; 1 stop bit; stop bit hi.
if ( config->flowControl )
{
cfg->flag |= UART_CFG_FLW;
U1UCR = UCR_FLOW | UCR_STOP;
// Must rely on H/W for RTS (i.e. Tx stops when receiver negates CTS.)
P1SEL |= HAL_UART_1_P1_RTS;
// Cannot use H/W for CTS as DMA does not clear the Rx bytes properly.
P1DIR |= HAL_UART_1_P1_CTS;
RX1_FLOW_ON;
}
else
{
U1UCR = UCR_STOP;
}
}
#endif
return HAL_UART_SUCCESS;
}
在zstack中,串口应用主要有三种方式:
(1)与zstack交互进行通信:需要考虑到系统通信的数据格式,这种应用一般更加侧重于与zstack交互。
举个例子,在协调器中我们需要利用PC给zstack发出命令需要其执行相应的命令时,这个时候我们只要在协调器的编译项加上ZTOOL_P1 MT_TASK两个选项即可,如果这个时候如果需要MT层和应用层进行交互处理用户数据(具体用户数据的格式在zstack中的串口文档中有定义,大家可以自行查看),就必须在应用层中加入
case MT_SYS_APP_MSG: // Z-Architect Messages
(具体为什么加这个如果还不清楚把zstack OSAL运行机制搞清楚之后再开发吧。)
这个时候才能串口发送的数据经过MT层处理后送到我们的应用层,这是接收过程,发送过程直接调用HalUARTWrite()函数即可。
(2)自己的串口应用,与zstack命令格式无关,但不想自己配置串口,想偷懒:这种情况下也很简单,与上述编译项不同的是,此时需要加入的编译项为:MT_TASK ZAPP_P1,当中的串口号根据实际情况而定,这个时候只要加入编译项其实就行了,在应用过程中如果想在应用层处理串口的数据,和上述情况类似,这时候需要在应用层中加入的东西有:
#include "SPIMgr.h"头文件
....
#if defined (ZAPP_P1)
SPIMgr_RegisterTaskID(SampleApp_TaskID);//在MT层注册
SPIMgr_ZAppBufferLengthRegister(7); // BUFFER大小
#endif
.....
case SPI_INCOMING_ZAPP_DATA:
Bindnode_SerialMSGCB(MSGpkt);//自己的串口数据处理函数
#if defined(ZAPP_P1)
SPIMgr_AppFlowControl ( SPI_MGR_ZAPP_RX_READY );//流控制
#endif
break;
......
这是最主要的三步,完成上述添加,大家就可以使用串口了。
(需要主要注意的是一个串口不能同时实现上面的两个功能,一定要实现上述ZAPP_Px编译和ZTOOL_Px同时存在的话,那么x一定不能相同,即要使用两个不同的串口,否则会出现冲突。)
(3)可能瞧不起zstack定义好的串口,自己单独写一个玩玩,或者想另外加一个串口实现双串口功能,这个很简单,大家看看serialapp例子就行,我不细说。
使用的协议栈版本信息: ZigBee2006ZStack-1.4.3-1.2.1
因为用现在这模块SerialApp没做成功,上电后按键没反应……两块无线龙小板子已经买来N年了.
自己想在SampleApp例子基础上修改实现串口透明传输:
串口调试助手1<————>模块1 <-----OTA-----> 模块2<————>串口调试助手2
程序修改主要如下:
****************************************************************************************
****************************************************************************************
1、
宏定义事件 #define UART_RX_CB_EVT 0x0002 (SampleApp.h)
全局变量声明: (SPIMgr.h)
extern uint8 rxlen; //接收数据长度
extern uint8* databuf; //接收数据指针
****************************************************************************************
****************************************************************************************
2、
串口回调函数rxCB: (SPIMgr.c)
#if defined (ZTOOL_P1) || defined (ZTOOL_P2)
uartConfig.callBackFunc = rxCB;
//uartConfig.callBackFunc = SPIMgr_ProcessZToolData; //回调函数
****************************************************************************************
****************************************************************************************
3、十六进制转字符函数 (SampleApp.c) 这两个函数由青竹提供.
uint8 hextoword1(uint8 t )
{
uint8 abc;
uint8 cba;
uint8 xx1;
abc=t;
cba=0xf0;
abc=(abc&cba)>>4;
if(abc<10)
{
xx1=abc+48;
}
else
{
xx1=abc+55;
}
return xx1;
}
uint8 hextoword2(uint8 t)
{
uint8 abc;
uint8 cba;
uint8 xx2;
abc=t;
cba=0x0f;
abc=abc&cba;
if(abc<10)
{
xx2=abc+48;
}
else
{
xx2=abc+55;
}
return xx2;
}
****************************************************************************************
****************************************************************************************
4、定义串口回调函数rxCB() (SPIMgr.c)
static void rxCB( uint8 port, uint8 event )
{
// uint8 rxlen; //接收数据长度
// uint8* dataybuf;//接收数据块指针
extern uint8 SampleApp_TaskID;
uint16 short_ddr;
uint8 short_ddr_H;
uint8 short_ddr_L;
// uint8 *pointer1;
// uint8 word_buffer[8];
short_ddr=NLME_GetShortAddr();
short_ddr_H=(uint8)((short_ddr&0xff00)>>8);
short_ddr_L=(uint8)short_ddr;
rxlen=Hal_UART_RxBufLen(SPI_MGR_DEFAULT_PORT); //接收缓冲区数据长度,字节为单位
databuf=osal_mem_alloc(rxlen+1+2); //多分配3字节,分配如下
databuf[0]=rxlen; //一字节存放数据长度
databuf[1]=short_ddr_H; //一字节存放源地址高8位
databuf[2]=short_ddr_L; //一字节存放源地址低8位
//databuf[rxlen+1]='n'; //一字节存放换行符
HalUARTRead ( SPI_MGR_DEFAULT_PORT, databuf+3, rxlen); //读接收缓冲区数据到内存databuf+3
if(!rxlen)
osal_mem_free( databuf ); //释放内存
osal_set_event(SampleApp_TaskID,UART_RX_CB_EVT);
// rxCB_to_SampleApp( databuf, rxlen );
}
****************************************************************************************
****************************************************************************************
5、添加:事件处理函数 (SampleApp.c)
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
afIncomingMSGPacket_t *MSGpkt;
// 显示网络地址变量
uint16 short_ddr;
uint8 yy1;
uint8 yy2;
uint8 str_1[ ]="my short address is:";
#if defined(ZDO_COORDINATOR)
uint8 str_2[ ]="build the network successfully";
#else
uint8 str_2[ ]="join the network successfully ";
#endif
uint8 str_3[ ]={'n'};
uint8 shortaddr[7];
uint8 *pointer1;
uint8 *pointer2;
uint8 *pointer3;
uint8 *pointer4;
…………(省略)
case AF_INCOMING_MSG_CMD:
SampleApp_MessageMSGCB( MSGpkt );
break;
case ZDO_STATE_CHANGE:
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (SampleApp_NwkState == DEV_ZB_COORD)
|| (SampleApp_NwkState == DEV_ROUTER)
|| (SampleApp_NwkState == DEV_END_DEVICE) )
{
//显示本地网络地址
short_ddr=NLME_GetShortAddr();
yy1=(uint8)((short_ddr&0xff00)>>8);
yy2=(uint8)short_ddr;
shortaddr[0]=48;
shortaddr[1]=120;
shortaddr[2]=hextoword1(yy1);
shortaddr[3]=hextoword2(yy1);
shortaddr[4]=hextoword1(yy2);
shortaddr[5]=hextoword2(yy2);
shortaddr[6]='n';
pointer1=&shortaddr[0];
pointer2=&str_1[0];
pointer3=&str_2[0];
pointer4=&str_3[0];
HalUARTWrite(0,pointer4,1);
HalUARTWrite(0,pointer3,29);
HalUARTWrite(0,pointer4,1);
HalUARTWrite(0,pointer2,20);
HalUARTWrite(0,pointer1,7);
HalUARTWrite(0,pointer4,1);
/
if ( events & UART_RX_CB_EVT ) //串口数据处理
{
SampleApp_SPI_SendData( databuf, rxlen+1+2 );
return (events ^ UART_RX_CB_EVT);
}
}
****************************************************************************************
****************************************************************************************
6、定义AF层数据处理函数SampleApp_MessageMSGCB() (SampleApp.c)
默认采用的簇ID为SAMPLEAPP_PERIODIC_CLUSTERID
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
uint16 short_ddr;
uint8 *pointer1;
uint8 *pointer2;
uint8 *pointer3;
uint8 *pointer4;
uint8 *pointer5;
uint8 *pointer6;
uint8 *pointer7;
uint8 *pointer8;
uint8 *pointer9;
uint8 str_1[ ]="Source address:";
uint8 str_2[ ]="Destination address:";
uint8 str_3[ ]="Data length:";
uint8 str_4[ ]="Data:";
uint8 str_5[ ]={'n'};
pointer1=&str_1[0];
pointer2=&str_2[0];
pointer3=&str_3[0];
pointer4=&str_4[0];
pointer9=&str_5[0];
uint8 Src_short_ddr_H;
uint8 Src_short_ddr_L;
uint8 Des_short_ddr_H;
uint8 Des_short_ddr_L;
uint8 word_buffer[4];
uint8 Src_shortaddr[7];
uint8 Des_shortaddr[7];
switch ( pkt->clusterId ) //判断簇ID
{
case SAMPLEAPP_PERIODIC_CLUSTERID:
// flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
// HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
pointer5=&(pkt->cmd.Data[3]); //pointer5:具体数据首地址
word_buffer[0]=(pkt->cmd.Data[0])/100+48;
word_buffer[1]=((pkt->cmd.Data[0])0)/10+48;
word_buffer[2]=(pkt->cmd.Data[0])+48;
word_buffer[3]='n';
pointer6=word_buffer; //pointer6:数据长度
//----------------
Src_short_ddr_H=pkt->cmd.Data[1];
Src_short_ddr_L=pkt->cmd.Data[2];
Src_shortaddr[0]=48;
Src_shortaddr[1]=120;
Src_shortaddr[2]=hextoword1(Src_short_ddr_H);
Src_shortaddr[3]=hextoword2(Src_short_ddr_H);
Src_shortaddr[4]=hextoword1(Src_short_ddr_L);
Src_shortaddr[5]=hextoword2(Src_short_ddr_L);
Src_shortaddr[6]='n';
pointer7=&Src_shortaddr[0]; //pointer7:源地址
//----------------
short_ddr=NLME_GetShortAddr();
Des_short_ddr_H=(uint8)((short_ddr&0xff00)>>8);
Des_short_ddr_L=(uint8)short_ddr;
Des_shortaddr[0]=48;
Des_shortaddr[1]=120;
Des_shortaddr[2]=hextoword1(Des_short_ddr_H);
Des_shortaddr[3]=hextoword2(Des_short_ddr_H);
Des_shortaddr[4]=hextoword1(Des_short_ddr_L);
Des_shortaddr[5]=hextoword2(Des_short_ddr_L);
Des_shortaddr[6]='n';
pointer8=&Des_shortaddr[0]; //pointer8:目的地址
//----------------
HalUARTWrite ( 0, pointer1, 15 ); //源地址
HalUARTWrite ( 0, pointer7, 7 );
HalUARTWrite ( 0, pointer2, 20 ); //目的地址
HalUARTWrite ( 0, pointer8, 7 );
HalUARTWrite ( 0, pointer3, 12 ); //数据长度
HalUARTWrite ( 0, pointer6, 4 );
HalUARTWrite ( 0, pointer4, 5 ); //具体数据
HalUARTWrite ( 0, pointer5, pkt->cmd.Data[0] ); //pkt->cmd.Data[0]=rxlen,为原始长度
HalUARTWrite ( 0, pointer9, 1 ); //换行符
HalUARTWrite ( 0, pointer9, 1 );
//pointer1=&(pkt->cmd.Data[3]);
//HalUARTWrite ( 0, pointer1, 6 );
break;
case SAMPLEAPP_FLASH_CLUSTERID://flash
flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
break;
}
}
****************************************************************************************
****************************************************************************************
7、定义串口数据处理函数SampleApp_SPI_SendData() (SampleApp.c)
我只有两个节点,所以这里采用最简单的单点传送方式.下载协调器程序时目标地址改为0x796F,下载终端程序时目标地址改为0x0000.默认ClusterID为SAMPLEAPP_PERIODIC_CLUSTERID.
void SampleApp_SPI_SendData( uint8 *buf, uint8 len )
{
SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0x796F; //0x796F;0x0000
// SampleApp_SendPeriodicMessage(); //测试用
if ( AF_DataRequest( &SampleApp_SPI_SendData_DstAddr,
(endPointDesc_t *)&SampleApp_epDesc,
SAMPLEAPP_PERIODIC_CLUSTERID,
len, buf,
&SampleApp_TransID,
0,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
osal_mem_free( databuf ); //必须释放内存,不然造成溢出!
}
else
{
osal_mem_free( databuf );
}
}
****************************************************************************************
****************************************************************************************
编译情况:
通信结果1:(关于开头的乱码说明参考后篇记录)
通信结果2:
最大数据只能达到52字节:(但在修改程序过程中,达到过83字节;等修改完整后只有52字节了):
****************************************************************************************
****************************************************************************************
本文转自小峰日志:http://wjf88223.blog.163.com/,如有转载请标明出处,支持作者原创!