CC2541/40之低功耗串口RXTIMEOUT回調

原創博客,如有轉載,註明出處——在金華的電子民工林
串口是我們用的最多的,也最實用的一個功能,在BLE低功耗下,很多人不知道怎麼添加串口,以及怎麼更好的實現串口,以及如何滿足低功耗的需求,現在寫這篇文章供大家參考,拋磚引玉,多多指教。
本文章基於CC2541協議棧1.40版本,高於這個版本的僅做參考,自行改正。
首先講個基礎問題,就是串口在哪個位置,怎麼設置?
CC2541/40的串口位置共4個選擇,就是UART0的位置1,2;UART1的位置1,2。首先,我們選擇用UART0還是UART1,如下圖,在預定義裏設置。
設置UART0或者1
怎麼設置呢?看方框裏,如果你選用UART0,那在右邊的預定義裏填入HAL_UART_DMA=1;如果選用UART1,則HAL_UART_DMA=2;另外,我們使用串口,必須在預定義裏寫上HAL_UART=TRUE;如下:
在這裏插入圖片描述
這裏的LCD我屏蔽掉了,LCD用SPI的話,可能會和UART衝突,這個自己留意。
好了,選擇好了哪個UART,就是選擇位置了,位置受寄存器控制,如下圖:
在這裏插入圖片描述
這個寄存器的0位,1位,分別設置串口的位置,默認是0.一般我們在串口初始化的時候賦值這個寄存器。

void serialAppInitTransport( )
{
  halUARTCfg_t uartConfig;

  // configure UART
  uartConfig.configured           = TRUE;
  uartConfig.baudRate             = SBP_UART_BR;//波特率
  uartConfig.flowControl          = SBP_UART_FC;//流控制
  uartConfig.flowControlThreshold = SBP_UART_FC_THRESHOLD;//流控制閾值,當開啓flowControl時,該設置有效
  uartConfig.rx.maxBufSize        = SBP_UART_RX_BUF_SIZE;//uart接收緩衝區大小
  uartConfig.tx.maxBufSize        = SBP_UART_TX_BUF_SIZE;//uart發送緩衝區大小
  uartConfig.idleTimeout          = SBP_UART_IDLE_TIMEOUT;
  uartConfig.intEnable            = SBP_UART_INT_ENABLE;//是否開啓中斷
  uartConfig.callBackFunc         = sbpSerialAppCallback;//uart接收回調函數,在該函數中讀取可用uart數據
  /*
#define HAL_UART_BR_9600   0x00
#define HAL_UART_BR_19200  0x01
#define HAL_UART_BR_38400  0x02
#define HAL_UART_BR_57600  0x03
#define HAL_UART_BR_115200 0x04*/
 // uartConfig.baudRate = DefaultInformation.baudrate;      //波特率對應的序列號。現在選的是04
  // start UART
  // Note: Assumes no issue opening UART port.
  (void)HalUARTOpen( SBP_UART_PORT, &uartConfig );
 /* PERCFG |= 0x02;
  P2DIR |= 0x40;
  P2SEL |=0x40;*/
  return;
}

這是串口初始化程序,最後面幾句就是設置串口位置的,如果你使用默認位置1,不需要設置。
初始化程序中,uartConfig.idleTimeout實際是沒用上的,程序中也是沒用起來,協議棧默認低功耗下是使用4線的流控方式,但是我們實際應用中,用的2線比較多,而且串口的Timeout中斷非常實用,現在很多使用者的困擾就是串口回調,1ms進去一次,每次進去不是完整的一包數據,現在我們想辦法改成空閒模式,就是當過了多少時間後,串口沒有新的數據進來,那我們就執行串口回調程序,通過數據包與數據包之間的大的時間間隔,從而完整的接收整包數據。

我們理想中的串口回調程序應該如下:

void sbpSerialAppCallback(uint8 port, uint8 event)
{

  uint8 numBytes;
  uint8  pktBuffer[SBP_UART_RX_BUF_SIZE];
  if(event&HAL_UART_RX_TIMEOUT)
  {
	numBytes = Hal_UART_RxBufLen(port);
	xxxxxxxxx;			//用戶處理程序
  }
  else if(event&HAL_UART_RX_FULL)
  {
				//防止接收滿的意外處理程序。
  }
  (void)event;  
}

爲了實現串口回調這麼簡單的進行處理,我們開始修改協議棧的程序。
首先要看懂協議棧的程序,協議棧的串口低功耗使用的是流控,那我們先把流控都給幹掉。
以下程序都在hal_uart_dma.c的文件下修改。
首先,我們不用流控了,先把流控相關的幹掉,如下圖:
在這裏插入圖片描述
就是流控定義的引腳,先給屏蔽了,編譯一下,錯誤一大堆,沒事,錯誤都是代表未定義的,那就把錯誤的行全都屏蔽掉,這裏比較多,不一一描述,實際上就是把DMA_PM這個定義判斷下的程序都給屏蔽了。

  if (DMA_PM)
  {
    // Setup GPIO for interrupts by falling edge on DMA_RDY_IN.
/*    PxIEN |= DMA_RDYIn_BIT;
    PICTL |= PICTL_BIT;

    HAL_UART_DMA_CLR_RDY_OUT();
    PxDIR |= DMA_RDYOut_BIT;*/
  }
static void HalUARTOpenDMA(halUARTCfg_t *config)
{
  dmaCfg.uartCB = config->callBackFunc;

  // Only supporting subset of baudrate for code size - other is possible.
  HAL_ASSERT((config->baudRate == HAL_UART_BR_9600) ||
                  (config->baudRate == HAL_UART_BR_19200) ||
                  (config->baudRate == HAL_UART_BR_38400) ||
                  (config->baudRate == HAL_UART_BR_57600) ||
                  (config->baudRate == HAL_UART_BR_115200));

  if (config->baudRate == HAL_UART_BR_57600 ||
      config->baudRate == HAL_UART_BR_115200)
  {
    UxBAUD = 216;
  }
  else
  {
    UxBAUD = 59;
  }

  switch (config->baudRate)
  {
    case HAL_UART_BR_9600:
      UxGCR = 8;
      break;
    case HAL_UART_BR_19200:
      UxGCR = 9;
      break;
    case HAL_UART_BR_38400:
    case HAL_UART_BR_57600:
      UxGCR = 10;
      break;
    default:
      // HAL_UART_BR_115200
      UxGCR = 11;
      break;
  }

  if (DMA_PM || config->flowControl)
  {
//    UxUCR = UCR_FLOW | UCR_STOP;      // 8 bits/char; no parity; 1 stop bit; stop bit hi.
//    PxSEL |= HAL_UART_Px_CTS;         // Enable Peripheral control of CTS flow control on Px.
    UxUCR = UCR_STOP;                 // 8 bits/char; no parity; 1 stop bit; stop bit hi.
  }
  else
  {
    UxUCR = UCR_STOP;                 // 8 bits/char; no parity; 1 stop bit; stop bit hi.
  }

  UxCSR = (CSR_MODE | CSR_RE);

  if (DMA_PM)
  {
 /*   PxIFG = 0;
    PxIF = 0;
    IENx |= IEN_BIT;*/
  }
  else if (UxUCR & UCR_FLOW)
  {
    // DMA Rx is always on (self-resetting). So flow must be controlled by the S/W polling the
    // circular Rx queue depth. Start by allowing flow.
 //   HAL_UART_DMA_SET_RDY_OUT();
 //   PxDIR |= HAL_UART_Px_RTS;
  }

#if HAL_UART_TX_BY_ISR
  UTXxIF = 1;  // Prime the ISR pump.
#endif
}

  if (!DMA_PM && (UxUCR & UCR_FLOW))
  {
 //   HAL_UART_DMA_SET_RDY_OUT();  // Re-enable the flow asap (i.e. not wait until next uart poll).    
  }

接下來就是最主要的修改了。首先我們要實現接收空閒回調,那必須修改幾個關鍵的定義,如下:

// Minimum delay before allowing sleep and/or clearing DMA ready-out after a DMA ready-in ISR.
// ST-ticks for 6-msecs plus 1 tick added for when the dmaRdyDly is forced from zero to 0xFF.
// If a greater delay than 6-msec is configured, then the logic should be changed to use a uint16.
//efine DMA_PM_DLY                 198   // 32768 * 0.006 + 1 -> 198.
// This delay should be set as short as possible to work with the max expected latency in the sender
// between its asserting ready-out and its checking of the ready-in response. The RBA Master
// logic in the internal uart-to-uart bridge app checks for ready-in immediately,
// so this is just set to zero.
#define DMA_PM_DLY                 33*5//0//33*5 //0

// The timeout tick is at 32-kHz, so multiply msecs by 33.
#define HAL_UART_MSECS_TO_TICKS    33

#if !defined HAL_UART_DMA_RX_MAX
#define HAL_UART_DMA_RX_MAX        128
#endif
#if !defined HAL_UART_DMA_TX_MAX
#define HAL_UART_DMA_TX_MAX        HAL_UART_DMA_RX_MAX
#endif
#if !defined HAL_UART_DMA_HIGH
#define HAL_UART_DMA_HIGH         (HAL_UART_DMA_RX_MAX - 1)
#endif
#if !defined HAL_UART_DMA_IDLE
#define HAL_UART_DMA_IDLE         (3 * HAL_UART_MSECS_TO_TICKS)
#endif
#if !defined HAL_UART_DMA_FULL
#define HAL_UART_DMA_FULL         (HAL_UART_DMA_RX_MAX - 16)
#endif

這裏是我修改後的,原文#define DMA_PM_DLY 0
我們改成533,代表了延遲5ms進入休眠,因爲串口需要高頻的晶振才能工作。
#define HAL_UART_DMA_IDLE (3
HAL_UART_MSECS_TO_TICKS)
這個定義,代表串口接收空閒3ms後,調用回調程序。
這個很重要。
然後最最主要的是下面的程序,就是串口回調,實際上是在輪詢中產生的,程序如下:

/******************************************************************************
 * @fn      HalUARTPollDMA
 *
 * @brief   Poll a USART module implemented by DMA, including the hybrid solution in which the Rx
 *          is driven by DMA but the Tx is driven by ISR.
 *
 * @param   none
 *
 * @return  none
 *****************************************************************************/
static void HalUARTPollDMA(void)
{
  uint8 evt = 0;
  uint16 cnt;
#if HAL_UART_DMA_IDLE
  
  static uint16 Lastcnt;
  uint8 st0,rxd;
#endif  
#if DMA_PM
//  PxIEN &= ~DMA_RDYIn_BIT;  // Clear to not race with DMA_RDY_IN ISR.
  {
    if (dmaRdyIsr ||  HalUARTBusyDMA())
    {
      // Master may have timed-out the SRDY asserted state & may need a new edge.
#if HAL_UART_TX_BY_ISR
      if (dmaCfg.txHead != dmaCfg.txTail)
#else
      if (!HAL_UART_DMA_RDY_IN() && ((dmaCfg.txIdx[0] != 0) || (dmaCfg.txIdx[1] != 0)))
#endif
      {
//        HAL_UART_DMA_CLR_RDY_OUT();
      }
      dmaRdyIsr = 0;

      if (dmaRdyDly == 0)
      {
        (void)osal_set_event(Hal_TaskID, HAL_PWRMGR_HOLD_EVENT);
      }

      if ((dmaRdyDly = ST0) == 0)  // Reserve zero to signify that the delay expired.
      {
        dmaRdyDly = 0xFF;
      }
 //     HAL_UART_DMA_SET_RDY_OUT();
    }
    else if ((dmaRdyDly != 0) && (!DMA_PM_DLY || ((uint8)(ST0 - dmaRdyDly) > DMA_PM_DLY)))
    {
      dmaRdyDly = 0;
      (void)osal_set_event(Hal_TaskID, HAL_PWRMGR_CONSERVE_EVENT);
    }
  }
  PxIEN |= DMA_RDYIn_BIT;
#endif

#if !HAL_UART_TX_BY_ISR
  HalUARTPollTxTrigDMA();
#endif


  cnt = HalUARTRxAvailDMA();  // Wait to call until after the above DMA Rx bug work-around.

#if HAL_UART_DMA_IDLE
  st0 = ST0;
  if(cnt != 0)
  {
    if(Lastcnt==cnt)                    //沒有收到新串口數據,就開始計時超時。
    {
      rxd = st0 - dmaCfg.rxTick;
      if (rxd > HAL_UART_DMA_IDLE)
      {
        evt = HAL_UART_RX_TIMEOUT;
        Lastcnt = 0;
      }
    }
    else                        //如果不相等,重新賦值比較時間
    {
      dmaCfg.rxTick = st0;
      Lastcnt = cnt;
    }
  }
 /* 
  if (dmaCfg.rxTick)
  {
    // Use the LSB of the sleep timer (ST0 must be read first anyway) to measure the Rx timeout.
    if ((ST0 - dmaCfg.rxTick) > HAL_UART_DMA_IDLE)
    {
      dmaCfg.rxTick = 0;
      evt = HAL_UART_RX_TIMEOUT;
    }
  }
  else if (cnt != 0)
  {
    dmaCfg.rxTick = ST0;
    if ((dmaCfg.rxTick = ST0) == 0)  // Zero signifies that the Rx timeout is not running.
    {
      dmaCfg.rxTick = 0xFF;
    }
  }*/
#else
  if (cnt != 0)
  {
    evt = HAL_UART_RX_TIMEOUT;
  }
#endif

  if (cnt >= HAL_UART_DMA_FULL)
  {
    evt |= HAL_UART_RX_FULL;
  }
  else if (cnt >= HAL_UART_DMA_HIGH)
  {
    evt |= HAL_UART_RX_ABOUT_FULL;

    if (!DMA_PM && (UxUCR & UCR_FLOW))
    {
//      HAL_UART_DMA_CLR_RDY_OUT();  // Disable Rx flow.
    }
  }

  if (dmaCfg.txMT)
  {
    dmaCfg.txMT = FALSE;
    evt |= HAL_UART_TX_EMPTY;
  }

  if ((evt != 0) && (dmaCfg.uartCB != NULL))
  {
    dmaCfg.uartCB(HAL_UART_DMA-1, evt);
  }

  if (DMA_PM && (dmaRdyDly == 0) && !HalUARTBusyDMA())
  {
//    HAL_UART_DMA_CLR_RDY_OUT();
  }
}

屏蔽掉的是協議棧源程序,他的思路是,從接收到第一個字節開始後開始計算空閒時間,時間到了,調用回調。這樣非常不符合我們的使用習慣
我的程序就改成了,只要收到一個字節,重置時間,那就是最後一個字節接收掉,過了空閒時間還沒有新的數據進來,就去調用串口回調程序。
這裏,回調的標記位:HAL_UART_RX_TIMEOUT,和回調程序裏的判斷爲,就結合了起來。
這裏我用一個寄存器,記錄了當前的時間寄存器值,這樣更不容易出錯。
程序經過驗證,發送,接收(特定時刻)都能滿足。
但是本程序還是有缺陷的,交給大家自己考慮修改。就是發送可以任意時刻發送,接收需要16MHz高頻晶振開啓的時候才能正常接收(波特率發生器由16MHz晶振產生),大家有興趣,可以在上面的程序中,修改接收端口在空閒時開啓中斷,由串口的下降沿引起中斷後,就會執行低功耗延遲時間,在這段時間內,16MHZ晶振正常工作,那就可以接收串口信息了。
草草描述,希望對大家有所幫助,如有疑惑,加羣討論。

重申:原創博客,如有轉載,註明出處——在金華的電子民工林。

如果覺得對你有幫助,一起到羣裏探討交流。

1)友情夥伴:甜甜的大香瓜
2)聲明:喝水不忘挖井人,轉載請註明出處。
3)糾錯/業務合作:[email protected]
4)香瓜BLE之CC2541羣:127442605
5)完整開源資料下載地址:
https://shop217632629.taobao.com/?spm=2013.1.1000126.d21.hd2o8i

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章