本系列教程將結合TI推出的CC254x SoC 系列,講解從環境的搭建到藍牙4.0協議棧的開發來深入學習藍牙4.0的開發過程。教程共分為六部分,本文為第四部分:
第四部分知識點:
第十六節 協議棧LED實驗
第十七節 協議棧LCD顯示
第十八節 協議棧UART實驗
第十九節 協議棧五向按鍵
第二十節 協議棧Flash數據存儲
?
有關TI 的CC254x芯片介紹,可點擊下面鏈接查看:
有關本文的工具下載,大家可以到以下這個地址:
第十六節 協議棧LED實驗
TI的協議棧中在HAL層已經有了LED的驅動,我們只需要針對我們的開發板進行配置即可,我們的開發板有兩個LED,分別對應P1.0和P1.1。這個在裸機開發的時候已經介紹了。
為了保持協議棧原有的代碼不變,我們在BLE-CC254x-1.4.0Componentshal arget目錄下新建一個文件夾,使它適應我們的開發板。
打開LED實驗工程LEDExample,選擇MT254xboard,并且在工程配置中要定義HAL_LED=TRUE,下載到開發板運行,可以看到兩個LED同時在閃爍。
那我們的實現代碼在哪里呢?其實在協議棧中實現這個很簡單,在啟動事件中我們調用了一個HalLedSet函數,并且設置了兩個LED同時閃爍。
就是這么簡單,協議棧已經把其它事情做好了,只需要我們調用設置函數即可。設置的模式總共有5種。
#define HAL_LED_MODE_OFF 0x00 // 關閉LED
#define HAL_LED_MODE_ON 0x01 // 打開LED
#define HAL_LED_MODE_BLINK 0x02 // 閃爍一次
#define HAL_LED_MODE_FLASH 0x04 // 不斷的閃爍,最多255次
#define HAL_LED_MODE_TOGGLE 0x08 // 翻轉LED狀態
為了適應不同的需求,我們可能需要更改LED的輸出引腳,如圖板級配置在hal_board_cfg.h文件中。
這里我們的開發板只有兩個LED,所以我們在這里根據開發板的實際情況修改相應的IO口。
第十七節 協議棧LCD顯示實驗
打開LCD12864的實驗工程,一樣的在工程配置中打開LCD,選擇MT254xboard然后直接編譯下載,我們可以看到LCD上已經有顯示了。
這些顯示來自哪里呢?
在初始化函數中可以看到圖中的函數調用,這里是將字符串顯示到LCD的第一行。
在事件回調函數中可以看到這里將本機地址顯示到第二行,將字符串Initialized顯示到第三行,但是為什么我們在第三行沒有看到這行字符串呢?而顯示的字符串是Advertising ,這是因為系統啟動后運行非常快,在我們還沒反應過來的時候已經進入了廣播狀態,并且將原來的字符串覆蓋了,所以我們最后只能看到Advertising 了。
HalLcdWriteString是將第一個參數指向的字符串顯示到第二個參數指定第幾行中,例如我們需要在第5行顯示系統啟動信息,我們可以在啟動事件中,添加如下代碼。
這里我們來介紹一下Lcd驅動的實現,在Hal_lcd.h文件中申明了以下函數,這些函數的功能都有英文注釋,這里我就不再累述了。
/*
* Initialize LCD Service
*/
extern void HalLcdInit(void);
/*
* Write a string to the LCD
*/
extern void HalLcdWriteString ( char *str, uint8 option);
/*
* Write a value to the LCD
*/
extern void HalLcdWriteValue ( uint32 value, const uint8 radix, uint8 option);
/*
* Write a value to the LCD
*/
extern void HalLcdWriteScreen( char *line1, char *line2 );
/*
* Write a string followed by a value to the LCD
*/
extern void HalLcdWriteStringValue( char *title, uint16 value, uint8 format, uint8 line );
/*
* Write a string followed by 2 values to the LCD
*/
extern void HalLcdWriteStringValueValue( char *title, uint16 value1, uint8 format1, uint16 value2, uint8 format2, uint8 line );
/*
* Write a percentage bar to the LCD
*/
extern void HalLcdDisplayPercentBar( char *title, uint8 value );
協議棧中很多地方都調用了這些函數,我們如果要使我們的硬件能夠兼容協議棧,被協議棧使用,就需要實現這些函數的定義,當然,為了適應我們的開發板,我已經實現了這些函數,實現都在hal_lcd.c中。
第十八節 協議棧UART實驗
協議棧中已經用了串口的驅動,我們要做的只是對串口進行初始化,然后就可以進行串口數據的收發了。
用使用串口,第一步,需要打開使能串口功能,通過配置工程來實現,這里注意,我們現在不使用USB的CDC類來實現串口,所以HAL_UART_USB=FALSE。
HAL_UART=TRUE
HAL_UART_USB=FALSE
要使用串口必須先初始化相應的串口,那該如何初始化呢?在Hal_uart.h文件中我們可以看到如下函數。
uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config);
這個函數就是用來初始化串口的,這個函數有兩個參數,第一個指定串口號,第二個是串口的配置參數。我們來看看這個結構體的定義:
typedef struct
{
bool configured; // 配置與否
uint8 baudRate; // 波特率
bool flowControl; // 流控制
uint16 flowControlThreshold;
uint8 idleTimeout; // 空閑時間
halUARTBufControl_t rx; // 接收
halUARTBufControl_t tx; // 發送
bool intEnable; // 中斷使能
uint32 rxChRvdTime; // 接收數據時間
halUARTCBack_t callBackFunc; // 回調函數
}halUARTCfg_t;
這個結構體成員很多,但是我們在使用串口的時候并不需要使用所有的成員。
void Serial_Init(void)
{
halUARTCfg_t SerialCfg = {0};
SerialCfg.baudRate = HAL_UART_BR_115200; // 波特率
SerialCfg.flowControl = HAL_UART_FLOW_OFF; // 流控制
SerialCfg.callBackFunc = SerialCb; // 回調函數
SerialCfg.intEnable = TRUE;
SerialCfg.configured = TRUE;
HalLcdWriteString( “Open Uart0”, HAL_LCD_LINE_5 ); // 在第5行顯示啟動信息
HalUARTOpen(HAL_UART_PORT_0, &SerialCfg);
HalUARTWrite(HAL_UART_PORT_0, “Hello MT254xBoard ”, osal_strlen(“Hello MT254xBoard ”));
}
在串口回調函數中我們只做一件事,將串口接收到的數據顯示到LCD中并且原樣的從串口輸出。回調函數的實現如下:
static void SerialCb( uint8 port, uint8 events )
{
uint8 RxBuf[64]={0};
if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 發送區滿或者空
{
return;
}
uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 讀取接收據量
usRxBufLen = MIN(64,usRxBufLen);
uint16 readLen = HalUARTRead(HAL_UART_PORT_0, RxBuf, usRxBufLen);
HalUARTWrite(HAL_UART_PORT_0, RxBuf, usRxBufLen);
}
實驗現象,從實驗現象中可以看到,一開始在串口中輸出了一個標志字符串,然后我們通過串口發送了0123456789,然后數據原樣的從串口輸出了,這和我們預期的結果是一樣的。
但是我們發現LCD上的顯示和我們預期的不一樣,LCD上只顯示了6789,前面的數據并沒有顯示,這是怎么一回事呢?進行單步調試可以發現,我們發送一次數據,回調函數被回調了兩次,第一次回調只接受到了012345,第二次回調接收到了6789,而在LCD上的顯示第二次覆蓋了第一次的顯示,所以我們會看到這種現象,解決的辦法,我們需要定義一個數據幀的時間間隔,當接收數據的間隔超過了此間隔就認為接收結束。
下面我們改寫接收處理,我們在接收到數據后開啟定時器,定時5ms這樣,當接收間隔大于5ms后,我們就可以在定時事件中處理串口接收到的數據。
static void SerialCb( uint8 port, uint8 events )
{
if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 發送區滿或者空
{
return;
}
uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 讀取接收據量
if(usRxBufLen)
{
usRxBufLen = MIN(128,usRxBufLen);
uint16 readLen = HalUARTRead(HAL_UART_PORT_0, &SerialRxBuf[RxIndex], usRxBufLen); // 讀取數據到緩沖區
RxIndex += readLen;
readLen %= 128;
osal_start_timerEx(simpleBLEPeripheral_TaskID, UART_EVENT, 5); // 啟動定時器
}
}
事件處理代碼:
if ( events & UART_EVENT )
{
HalLcdWriteString( (char*)SerialRxBuf, HAL_LCD_LINE_6 ); // 在第5行顯示啟動信息
HalUARTWrite(HAL_UART_PORT_0, SerialRxBuf, osal_strlen(SerialRxBuf));
osal_memset(SerialRxBuf, 0, 128);
return (events ^ UART_EVENT);
}
經過這樣的處理后,可以發現我們剛剛的問題已經解決了。
到這里串口已經可以正常使用了,為了更加方便的使用串口,我在這里添加一個函數實現標準C中printf,這樣更有利于我們輸出。
int SerialPrintf(const char*fmt, 。。。)
{
uint32 ulLen;
va_list ap;
char *pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN); // 開辟緩沖區
va_start(ap, fmt);
ulLen = vsprintf(pBuf, fmt, ap); // 用虛擬打印函數實現
va_end(ap);
HalUARTWrite(HAL_UART_PORT_0, (uint8*)pBuf, ulLen); // 從串口0輸出
osal_mem_free(pBuf); // 釋放內存空間
return ulLen;
}
我們可以像使用C標準中的printf來使用這個函數,例如我們將LCD的輸出全部導向串口的輸出,在HalLcdWriteString的實現中添加串口輸出代碼,如下圖:
重新編譯并且燒錄后可以看到LCD的輸出和串口的輸出是一樣的了。
第十九節 協議棧五向按鍵
和前面幾個一樣,按鍵的驅動在協議棧中也已經有了,我們只需要做一些小的修改,使它適應我們的開發板即可。
1. 修改工程配置,使能按鍵功能。
2. 在我們的工程中要使用按鍵功能,僅僅打開配置選項是不夠的。因為協議棧代碼默認只有MINIDK開發板才有按鍵。
從這里可以看到(類似的地方有很多),如果要使能按鍵功能還需要定義CC2540_MINIDK,但是閱讀整個協議棧你會發現,定義 CC2540_MINIDK后還會打開其它的功能,而那些功能并不是我們想要的,所以在這里我們使用另外一種方法來實現。我們定義我們的開發板也能使用按鍵功能,所以在工程配置中添加MT254xboard=TRUE,然后在按鍵功能有宏開關的地方加入這個條件。具體位置參見代碼。
按下相應的按鍵后可以看到串口輸出相應的按鍵值。五向按鍵的工作原理在裸機開發的時候已經講過了,在協議棧中已經有相應的驅動代碼了,無需我們編寫,只需要按照實際情況改寫即可。例如我們的開發板每個按鍵對應的電壓值和原來的值并不一樣,所以我們這里改寫了每個按鍵值的電壓范圍。
uint8 halGetJoyKeyInput(void)
{
/* The joystick control is encoded as an analog voltage.
* Read the JOY_LEVEL analog value and map it to joy movement.
*/
uint16 adc;
uint8 ksave0 = 0;
uint8 ksave1;
/* Keep on reading the ADC until two consecutive key decisions are the same. */
do
{
ksave1 = ksave0; /* save previouse key reading */
adc = HalAdcRead (HAL_KEY_JOY_CHN, HAL_ADC_RESOLUTION_10);
if ((adc 》= 2) && (adc 《= 95)) // 85 right
{
ksave0 |= HAL_KEY_RIGHT;
}
else if ((adc 》= 96) && (adc 《= 110)) // 101 cent
{
ksave0 |= HAL_KEY_CENTER;
}
else if ((adc 》= 111) && (adc 《= 140)) // 127 up
{
ksave0 |= HAL_KEY_UP;
}
else if ((adc 》= 141) && (adc 《= 200)) // 170 left
{
ksave0 |= HAL_KEY_LEFT;
}
else if ((adc 》= 201) && (adc 《= 300)) // 257 down
{
ksave0 |= HAL_KEY_DOWN;
}
} while (ksave0 != ksave1);
return ksave0;
}
第二十節 協議棧Flash數據存儲
CC254x自帶了256K Flash,這256K的儲存空間不僅可以儲存代碼,也可以儲存用戶的數據,協議棧自帶了SNV管理代碼,我們只需要學會使用即可。
SNV的使用只有兩個函數,分別是讀函數osal_snv_read和寫函數osal_snv_write,在SNV的儲存中,儲存的每個數據都有一個唯一的ID,SNV也正是利用這個ID來管理儲存在Flash中的數據,在BLE的協議棧中,藍牙自身數據儲存用了一部分ID,我們儲存的數據ID不可使用這些ID,在bcomdef.h中有這些ID的定義。
下面我們往SNV中存入串口接收到的數據,然后開發板斷電重啟后讀取出這串字符串并通過串口發送出去,來演示SNV的斷電保存。
首先我們定義一個我們儲存數據的ID,注意不能和已經有的定義沖突。
#define BLE_NVID_USER_CFG_START 0x80 //!《 Start of the USER Configuration NV IDs
#define BLE_NVID_USER_CFG_END 0x89 //!《 End of the USER Configuration NV IDs
我們在啟動事件中讀取SNV中0x80的值并通過串口輸出讀取結果,如果讀取成功,則會將讀取結果打印到PC端,如果讀取失敗,則會提示讀取失敗。
在串口接收事件中將接收到的數據存入SNV中,并且也進行相應的提示。
將工程編譯下載后,可以看到現象如下:
第一次上電可以看到,提示讀取數據失敗了,說明第一次運行時是沒有存儲數據的,接下來我們通過串口發送字符串 MT254xboard SNV Test字符串。
可以看到成功的將我們發送過去的字符存入了SNV中,那是否成功存入呢?我們將開發板斷電后重啟,看看第二次上電是否能夠讀取出我們存入的數據。
重啟后可以發現我們成功的讀取出了第一次存入的數據,說明我們成功的將數據存入了SNV中。
評論