用户需要使用cc2530串口的话,需要进行初始化,在SampleApp.c下我们需要调用的是MT_UartInit() 和MT_UartRegisterTaskID(task_id) 。剩下的就是在需要的时候HalUARTWrite()和 HalUARTRead()就行了。但是实际上串口涉及了很多东西,让我们从main函数开始看看到底做了什么。
ZMain.c: main()
hal_drivers.c: HalDriverInit (void)
hal_uart.c: HalUARTInit() //因为协议栈的串口用的是dma的方式,所以
是转到下面的函数
_hal_uart_dma.c: HalUARTInitDMA() //配置dma的参数
_______________________________________________________________________________
{
halDMADesc_t*ch; //halDMADesc_t 结构体参考hal_dma.h
P2DIR&= ~P2DIR_PRIPO; //usart0优先
P2DIR |= HAL_UART_PRIPO; //0x00
PERCFG&= ~HAL_UART_PERCFG_BIT; //串口所用的io口设在P0上
PxSEL |= HAL_UART_Px_RX_TX; //替换得到P0SEL = 0x0c 意思是设为外设功能
ADCCFG &= ~HAL_UART_Px_RX_TX; // Make sure ADC doesnt use this.
UxCSR= CSR_MODE; // Mode isUART Mode.
UxUCR = UCR_FLUSH; // Flush it.
ch= HAL_DMA_GET_DESC1234( HAL_DMA_CH_TX ); //ch 是halDMADesc_t结构体指针,HAL_DMA_GET_DESC1234()宏定义为(dmaCh1234+((a)-1)),相当于把4个连续地址的结构体地址给了指针
HAL_DMA_SET_DEST(ch, DMA_UDBUF ); //设置dma传送的目的地址
HAL_DMA_SET_VLEN(ch, HAL_DMA_VLEN_USE_LEN );
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 );
.......
}
返回main函数
———————————————————————————————————————
OSAL.C: osal_init_system()
osal_sampleapp.c: osalInitTasks( )
MT_TASK.c: MT_TaskInit(uint8task_id)
MT_UART.C: MT_UartInit(); //这里配置的是串口的参数,回调函数设 为MT_UartProcessZToolData()
hal_uart.c: HalUARTOpen(MT_UART_DEFAULT_PORT, &uartConfig) //上面的函数配置好了,这里就打开串口。MT_UART_DEFAULT_PORT定义为0x00,是串口0
_hal_uart_dma.c: HalUARTOpenDMA(config) //这里的config就是MT_UartInit()里面配置好的
———————————————————————————————————————
{
dmaCfg.uartCB= config->callBackFunc //把前面串口的回调函数赋值给dma配置结构体的回调函数,因为串口的发送接收都会触发dma事件,dma把串口寄存器的东西拿走之后需要调用回调函数,这里应该是为了概念明确才这么做的吧?
......
// Initialize that TX DMA is not pending
dmaCfg.txDMAPending = FALSE;
dmaCfg.txShdwValid = FALSE;
}
执行完了就一层层往上返回main函数
———————————————————————————————————————
——————————————————————————————————————
z-stack中,每一层要设置什么事件都是用osal_set_event(taskid,event)这个函数去做的,taskid就是各个层的任务id,进入该层的事件处理函数后用event来区分到底发生了什么事。
所以在这里,就调用了MT_ProcessEvent(uint8task_id, uint16 events),函数里面具体在干嘛没看懂。
顺着main函数往下运行来到osal_run_system()
hal_drivers.c Hal_ProcessPoll( )
hal_uart.c: HalUARTPoll( )
_hal_uart_dma.c: HalUARTPollDMA() //轮询dma的函数,放在了系统的循环中, 则说明osal是定期去查询串口数据的
———————————————————————————————————————
{
uint16 cnt = 0; //字符计数
uint8 evt = 0; //事件号
if (HAL_UART_DMA_NEW_RX_BYTE(dmaCfg.rxHead)) //这个宏是查找有没有新收到数据的
{
rxIdx_t tail = findTail();//有的话就找到数据的末端,实际上就是用整形数来表示
// If the DMA hastransferred in more Rx bytes, reset the Rx idle timer.
if (dmaCfg.rxTail != tail)//如果在这一次轮询之前又收到数据了则两者不相等
{
dmaCfg.rxTail = tail;
// Re-sync the shadow onany 1st byte(s) received.
if (dmaCfg.rxTick == 0)//等于0表示轮询时间到
{
dmaCfg.rxShdw = ST0;//这个变量用来储存ST0的时间
}
dmaCfg.rxTick =HAL_UART_DMA_IDLE; // 设为 1ms
}
else if (dmaCfg.rxTick) //如果轮询的时间还没到
{
// Use the LSB of thesleep timer (ST0 must be read first anyway).
uint8 decr = ST0 -dmaCfg.rxShdw; //相减得到程序运行的时间
if (dmaCfg.rxTick > decr)//如果轮询的剩余时间比两次运行到这里的时间间隔还长的 话,就减去时间间隔作为剩下的轮询剩余时间
{
dmaCfg.rxTick -= decr;
dmaCfg.rxShdw = ST0;
}
else //如果剩余时间比时间间隔还长的话直接将时间置位0。这里有疑问,万一有哪个任务耗时特别长的话,那串口的数据不是会被覆盖掉?
{
dmaCfg.rxTick = 0;
}
}
cnt = HalUARTRxAvailDMA();//数数到底还有多少数据在rxbuf里
}
else //这个else是对应红色的if语句的
{
dmaCfg.rxTick = 0; //没有收到新数据则?
}
if (cnt >=HAL_UART_DMA_FULL) //如果rxbuf里面的数据大于(最大容量-16)字节
{
evt = HAL_UART_RX_FULL; //设置事件
}
else if (cnt >=HAL_UART_DMA_HIGH) //大于(最大容量的一半-16)字节
{
evt =HAL_UART_RX_ABOUT_FULL;
PxOUT |=HAL_UART_Px_RTS; // Disable Rx flow.
}
else if (cnt &&!dmaCfg.rxTick) //轮询时间到而且还有数据在rxbuf中
{
evt = HAL_UART_RX_TIMEOUT;//设置超时事件
}
if (dmaCfg.txMT) //上面部分说的都是关于接收缓存的,但是串口是既能发送也能接收的,并且都集合在这个函数里面了,所以现在要说发送的。txMT是发送缓冲区满了或者空了的标志位
{
dmaCfg.txMT = FALSE; //应该是正在发送数据的标志?
evt |= HAL_UART_TX_EMPTY;
}
if (dmaCfg.txShdwValid) //发送时间没到
{
uint8 decr = ST0;
decr -= dmaCfg.txShdw; //则减去两次程序运行到这里的时间,如果
if (decr >dmaCfg.txTick) //txTick是个定值,在HalUARTOpenDMA()已经赋值,表示多长时间轮询一次txbuf的内容,这个if语句代表了如果程序两次运行到这里的时间间隔大于了txtick,就把txShawValid设为FALSE
{
// No protection fortxShdwValid is required
// because while theshadow was valid, DMA ISR cannot be triggered
// to cause concurrentaccess to this variable.
dmaCfg.txShdwValid =FALSE;
}
}
TX使用DMA通道4,8位单字节传输,使能DMA中断。
RX使用DMA通道3,16位双字节传输,没有使用中断。
if (dmaCfg.txDMAPending &&!dmaCfg.txShdwValid) //应该发送txbuf里面的内容了,txDMAPending是数据待发送标识
{
// UART TX DMA is expectedto be fired(触发) and enough time has lapsed since last DMA ISR
// to know that DBUF canbe overwritten
halDMADesc_t *ch =HAL_DMA_GET_DESC1234(HAL_DMA_CH_TX);
halIntState_t intState;
// Clear the DMA pendingflag
dmaCfg.txDMAPending =FALSE;
HAL_DMA_SET_SOURCE(ch,dmaCfg.txBuf[dmaCfg.txSel]);
HAL_DMA_SET_LEN(ch,dmaCfg.txIdx[dmaCfg.txSel]);
dmaCfg.txSel ^= 1; //txbuf是个二维数组,上面已经设定了SOURCE,万一在dma传送的时候应用层又使用了串口发送,这时的内容只能暂时储存在txbuf的另一维上了。
HAL_ENTER_CRITICAL_SECTION(intState);
HAL_DMA_ARM_CH(HAL_DMA_CH_TX);
do
{
asm("NOP");
} while(!HAL_DMA_CH_ARMED(HAL_DMA_CH_TX)); //等待dma通道配置完毕,手册说通道进入工作状态需要9个时钟周期
HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX); //清除中断标志
HAL_DMA_MAN_TRIGGER(HAL_DMA_CH_TX); //人为触发DMA传送
HAL_EXIT_CRITICAL_SECTION(intState);
} //代码运行到这里,一旦绿色的代码执行完,就会进入dma中断,中断函数后面说,功能主要是把txbuf通过dma送到U0DBUF中
else
//对应黄色语句,不需要当前维度的txbuf,但是蓝色代码已经改变了维度了,所以这个else函数体是检查另一维度的txbuf里面有没有数据。为什么会有这种情况呢,就是前面说的第一维在做dma传送的时候,app层还在往串口发数据,这些数据都放在第2维了,现在就得把第2维的数据也dma传送掉。
{
halIntState_t his;
HAL_ENTER_CRITICAL_SECTION(his);
if((dmaCfg.txIdx[dmaCfg.txSel] != 0) && !HAL_DMA_CH_ARMED(HAL_DMA_CH_TX)
&& !HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
//(dmaCfg.txIdx[dmaCfg.txSel]!= 0这个就说明第2维有数据;HAL_DMA_CH_ARMED(HAL_DMA_CH_TX)这个为什么要是false才行呢?因为每次使用dma都要重新配置arm(翻译成准备好?),前面的if已经把这次配置的dma用掉了。并且IRQ标志位也清掉了,所以HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX)也是false。
{
HAL_EXIT_CRITICAL_SECTION(his);
HalUARTIsrDMA(); //这个函数的功能是串口dma回调函数,功能并不是把txbuf扔给U0DBUF,这个操作是dma配置好触发后自动完成的。那么这个函数在干嘛呢?后面说
}
else //如果第2维也没数据的话,那就没事干了
{
HAL_EXIT_CRITICAL_SECTION(his);
}
}
if (evt &&(dmaCfg.uartCB != NULL)) //前面赋值了那么多的evt,就是为了调用回调函数的,这个回调函数和上面的是不一样的。具体后面说
{
dmaCfg.uartCB(HAL_UART_DMA-1, evt);
}
}
———————————————————————————————————————
到此为止,关于串口的轮询机制就是这么多了,每一次的串口轮询都会做以上的步骤。现在来看看上面留下的3点“后面说”。
——————————————————————————————————————
第一个后面说是关于dma中断,凡是中断必有对应的中断处理函数,dma的中断处理函数如下:(hal_dma.c)
HAL_ISR_FUNCTION( halDmaIsr, DMA_VECTOR )
{
......
HAL_ENTER_ISR();
DMAIF = 0; //清掉中断标志位
#if HAL_UART_DMA
if(HAL_DMA_CHECK_IRQ(HAL_DMA_CH_TX))
{
HalUARTIsrDMA(); //这就是第二个后面说
}
.......
}
中断服务函数运行完了就会回到原来进入中断的地方继续运行
——————————————————————————————————————————
第二个后面说 (_hal_uart_dma.c:)
HalUARTIsrDMA( )
{
HAL_DMA_CLEAR_IRQ(HAL_DMA_CH_TX);//这里清除了中断,所以上面绿色的代码结果就是false
// Indicate that theother buffer is free now.
dmaCfg.txIdx[(dmaCfg.txSel ^ 1)] = 0; //前面的天蓝色代码块换了缓冲区,这里再换回来,意思就是这个代码块已经空了,并不是内容没抹掉了,而是被dma传送掉了,所以txidx可以归0,重新接收数据。
dmaCfg.txMT = TRUE;
// Set TX shadow
dmaCfg.txShdw = ST0;
dmaCfg.txShdwValid = TRUE; //都是在为下一次的dma传送做准备
// If there is moreTx data ready to go, re-start the DMA immediately on it.
if(dmaCfg.txIdx[dmaCfg.txSel]) //这里就是说如果第2维txbuf里面有数据,在程序下一次轮询的时候就会(运行黄色代码块)触发dma传送
{
// UART TX DMA isexpected to be fired
dmaCfg.txDMAPending = TRUE;
}
}
在下一次轮询的时候,因为橙色语句把txDMAPending设为了TRUE,所以if语句是进不去的。当decr>txTick的时候,dmaCfg.txShdwValid = FALSE,就进去if触发dma传送。所以说HalUARTIsrDMA( )这个函数就是使用另一维的txbuf,即两个txbuf的切换使用。
——————————————————————————————————————————
第三个后面说,在数据都已经通过串口接收完了之后,就应该调用串口的回调函数处理,为什么不针对串口发送的数据呢?数据都发出去了你还管它干嘛....
回调函数实际上是MT_UartProcessZToolData ( uint8port, uint8 event ),经过参数传递后是
MT_UartProcessZToolData (HAL_UART_DMA-1,evt),(HAL_UART_DMA-1)实际上就是端口0。
MT_UartProcessZToolData ( uint8 port, uint8 event )
{
uint8 ch;
uint8 bytesInRxBuffer;
(void)event; // Intentionally unreferenced parameter
while(Hal_UART_RxBufLen(port)) //这个函数返回的是rxbuf里面的字节数
{
HalUARTRead (port, &ch, 1); //把rxbuf缓冲区里面的数据读取1字节到ch里,这里接收到上位机的数据是按照特定的格式发送的,格式如下:
@brief | SOP | Data Length | CMD | Data | FCS |
* | 1 | 1 | 2 | 0-Len| 1 |
什么意思呢?就是说要想上位机发送给zigbee设备的信息要想被正确识别,那么上位机发送的数据包是一定要遵循上面的格式的。明白这一点了就能理解下面的switch语句为什么要这么写了。
switch (state)
{
case SOP_STATE:
if (ch == MT_UART_SOF) //ch的内容就是MT_UART_SOF,state改变,跳出switch,回到上面,ch再被读进一个数据,这次跳进第二个case
state = LEN_STATE;
break;
case LEN_STATE:
LEN_Token = ch; //按照数据包格式,这次读进的是整个data的长度
tempDataLen = 0;
/* 申请内存*/
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被强制类型转换为mtOSALSerialData_t,所以pMsg这时指向了MT_RPC_FRAME_HDR_SZ,这时一个3字节的空间。
关于这个宏定义,代码中这么一句说明:
/* 1st byte is thelength of the data field, 2nd/3rd bytes are command field. */
pMsg->msg[MT_RPC_POS_LEN] =LEN_Token; //MT_RPC_POS_LEN宏定义为0,意思就是下标为0的元素被赋值数据包的长度,正好对应了上面的注释
state = CMD_STATE1; //准备跳转到下一个case
}
else
{
state = SOP_STATE;
return;
}
break;
case CMD_STATE1:
pMsg->msg[MT_RPC_POS_CMD0] = ch; //这时的读到的ch应该是个命令,给了下标1的元素
state = CMD_STATE2; //准备跳转到下一个case
break;
case CMD_STATE2:
pMsg->msg[MT_RPC_POS_CMD1] = ch; //同上
/* If there is no data, skip to FCSstate */
if (LEN_Token) //命令到取完了,现在看看到底有没有数据(即data)
{
state = DATA_STATE; //有就去取数据
}
else
{
state = FCS_STATE; //没有就可以去算校验和了
}
break;
case DATA_STATE:
/* Fill in the buffer the first byte ofthe data */
pMsg->msg[MT_RPC_FRAME_HDR_SZ +tempDataLen++] = ch;
/* Check number of bytes left in the Rxbuffer */
bytesInRxBuffer =Hal_UART_RxBufLen(port); //看下rxbuf里面还剩下多少内容没读取
/*上面的bytesInRxBuffer返回的是个整形数,有2种可能。第一,rxbuf只有一个串口数据包剩下的内容;第二,有超过一个的数据包都收进了rxbuf里面。于是就有了下面的判断 */
if (bytesInRxBuffer <= LEN_Token -tempDataLen) //第一种情况的话里面又应该分2种,第一种是这个数据包已经完整的dma到rxbuf里面了,这时的情况是相等的。第二种就是还有数据在U0DBUF,还没来的及dma过来,这时就是小于的。
{
HalUARTRead (port,&pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer);
tempDataLen += bytesInRxBuffer;
}
else //这就是第2种情况。把这个数据包的剩余部分读完
{
HalUARTRead (port,&pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen);
tempDataLen += (LEN_Token -tempDataLen);
}
/* If number of bytes read is equal todata 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)) //校验成功了就通知app层
{
osal_msg_send( App_TaskID, (byte*)pMsg );
}
else //校验不成功就扔了
{
/* deallocate the msg */
osal_msg_deallocate ( (uint8 *)pMsg);
}
/* Reset the state, send or discard thebuffers at this point */
state = SOP_STATE;
break;
default:
break;
}
}
}
——————————————————————————————————————————
通知完app层之后,数据要怎么处理就是app层事情了。还留有一个疑问,串口的收发用的都是U0DBUF,程序是怎么区分里面的数据是发的还是收的呢?