关于z-stack串口的一些看法

关于z-stack串口的一些看法

         用户需要使用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,程序是怎么区分里面的数据是发的还是收的呢?


你可能感兴趣的:(zigbee相关)