原創博客,如有轉載,註明出處——在金華的電子民工林。
串口是我們用的最多的,也最實用的一個功能,在BLE低功耗下,很多人不知道怎麼添加串口,以及怎麼更好的實現串口,以及如何滿足低功耗的需求,現在寫這篇文章供大家參考,拋磚引玉,多多指教。
本文章基於CC2541協議棧1.40版本,高於這個版本的僅做參考,自行改正。
首先講個基礎問題,就是串口在哪個位置,怎麼設置?
CC2541/40的串口位置共4個選擇,就是UART0的位置1,2;UART1的位置1,2。首先,我們選擇用UART0還是UART1,如下圖,在預定義裏設置。
怎麼設置呢?看方框裏,如果你選用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