本系列教程將結(jié)合TI推出的CC254x SoC 系列,講解從環(huán)境的搭建到藍(lán)牙4.0協(xié)議棧的開發(fā)來深入學(xué)習(xí)藍(lán)牙4.0的開發(fā)過程。教程共分為六部分,本文為第四部分:
第四部分知識點(diǎn):
第十六節(jié) 協(xié)議棧LED實(shí)驗(yàn)
第十七節(jié) 協(xié)議棧LCD顯示
第十八節(jié) 協(xié)議棧UART實(shí)驗(yàn)
第十九節(jié) 協(xié)議棧五向按鍵
第二十節(jié) 協(xié)議棧Flash數(shù)據(jù)存儲
?
有關(guān)TI 的CC254x芯片介紹,可點(diǎn)擊下面鏈接查看:
主流藍(lán)牙BLE控制芯片詳解(1):TI CC2540
?
由淺入深,藍(lán)牙4.0/BLE協(xié)議棧開發(fā)攻略大全(1)
由淺入深,藍(lán)牙4.0/BLE協(xié)議棧開發(fā)攻略大全(2)
由淺入深,藍(lán)牙4.0/BLE協(xié)議棧開發(fā)攻略大全(3)
?
有關(guān)本文的工具下載,大家可以到以下這個(gè)地址:
第十六節(jié) 協(xié)議棧LED實(shí)驗(yàn)
TI的協(xié)議棧中在HAL層已經(jīng)有了LED的驅(qū)動,我們只需要針對我們的開發(fā)板進(jìn)行配置即可,我們的開發(fā)板有兩個(gè)LED,分別對應(yīng)P1.0和P1.1。這個(gè)在裸機(jī)開發(fā)的時(shí)候已經(jīng)介紹了。
為了保持協(xié)議棧原有的代碼不變,我們在BLE-CC254x-1.4.0Componentshal arget目錄下新建一個(gè)文件夾,使它適應(yīng)我們的開發(fā)板。
打開LED實(shí)驗(yàn)工程LEDExample,選擇MT254xboard,并且在工程配置中要定義HAL_LED=TRUE,下載到開發(fā)板運(yùn)行,可以看到兩個(gè)LED同時(shí)在閃爍。
那我們的實(shí)現(xiàn)代碼在哪里呢?其實(shí)在協(xié)議棧中實(shí)現(xiàn)這個(gè)很簡單,在啟動事件中我們調(diào)用了一個(gè)HalLedSet函數(shù),并且設(shè)置了兩個(gè)LED同時(shí)閃爍。
就是這么簡單,協(xié)議棧已經(jīng)把其它事情做好了,只需要我們調(diào)用設(shè)置函數(shù)即可。設(shè)置的模式總共有5種。
#define HAL_LED_MODE_OFF 0x00 // 關(guān)閉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 // 翻轉(zhuǎn)LED狀態(tài)
為了適應(yīng)不同的需求,我們可能需要更改LED的輸出引腳,如圖板級配置在hal_board_cfg.h文件中。
這里我們的開發(fā)板只有兩個(gè)LED,所以我們在這里根據(jù)開發(fā)板的實(shí)際情況修改相應(yīng)的IO口。
第十七節(jié) 協(xié)議棧LCD顯示實(shí)驗(yàn)
打開LCD12864的實(shí)驗(yàn)工程,一樣的在工程配置中打開LCD,選擇MT254xboard然后直接編譯下載,我們可以看到LCD上已經(jīng)有顯示了。
這些顯示來自哪里呢?
在初始化函數(shù)中可以看到圖中的函數(shù)調(diào)用,這里是將字符串顯示到LCD的第一行。
在事件回調(diào)函數(shù)中可以看到這里將本機(jī)地址顯示到第二行,將字符串Initialized顯示到第三行,但是為什么我們在第三行沒有看到這行字符串呢?而顯示的字符串是Advertising ,這是因?yàn)橄到y(tǒng)啟動后運(yùn)行非???,在我們還沒反應(yīng)過來的時(shí)候已經(jīng)進(jìn)入了廣播狀態(tài),并且將原來的字符串覆蓋了,所以我們最后只能看到Advertising 了。
HalLcdWriteString是將第一個(gè)參數(shù)指向的字符串顯示到第二個(gè)參數(shù)指定第幾行中,例如我們需要在第5行顯示系統(tǒng)啟動信息,我們可以在啟動事件中,添加如下代碼。
這里我們來介紹一下Lcd驅(qū)動的實(shí)現(xiàn),在Hal_lcd.h文件中申明了以下函數(shù),這些函數(shù)的功能都有英文注釋,這里我就不再累述了。
/*
* 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 );
協(xié)議棧中很多地方都調(diào)用了這些函數(shù),我們?nèi)绻刮覀兊?a href="http://www.ttokpm.com/v/tag/1751/" target="_blank">硬件能夠兼容協(xié)議棧,被協(xié)議棧使用,就需要實(shí)現(xiàn)這些函數(shù)的定義,當(dāng)然,為了適應(yīng)我們的開發(fā)板,我已經(jīng)實(shí)現(xiàn)了這些函數(shù),實(shí)現(xiàn)都在hal_lcd.c中。
第十八節(jié) 協(xié)議棧UART實(shí)驗(yàn)
協(xié)議棧中已經(jīng)用了串口的驅(qū)動,我們要做的只是對串口進(jìn)行初始化,然后就可以進(jìn)行串口數(shù)據(jù)的收發(fā)了。
用使用串口,第一步,需要打開使能串口功能,通過配置工程來實(shí)現(xiàn),這里注意,我們現(xiàn)在不使用USB的CDC類來實(shí)現(xiàn)串口,所以HAL_UART_USB=FALSE。
HAL_UART=TRUE
HAL_UART_USB=FALSE
要使用串口必須先初始化相應(yīng)的串口,那該如何初始化呢?在Hal_uart.h文件中我們可以看到如下函數(shù)。
uint8 HalUARTOpen(uint8 port, halUARTCfg_t *config);
這個(gè)函數(shù)就是用來初始化串口的,這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)指定串口號,第二個(gè)是串口的配置參數(shù)。我們來看看這個(gè)結(jié)構(gòu)體的定義:
typedef struct
{
bool configured; // 配置與否
uint8 baudRate; // 波特率
bool flowControl; // 流控制
uint16 flowControlThreshold;
uint8 idleTimeout; // 空閑時(shí)間
halUARTBufControl_t rx; // 接收
halUARTBufControl_t tx; // 發(fā)送
bool intEnable; // 中斷使能
uint32 rxChRvdTime; // 接收數(shù)據(jù)時(shí)間
halUARTCBack_t callBackFunc; // 回調(diào)函數(shù)
}halUARTCfg_t;
這個(gè)結(jié)構(gòu)體成員很多,但是我們在使用串口的時(shí)候并不需要使用所有的成員。
void Serial_Init(void)
{
halUARTCfg_t SerialCfg = {0};
SerialCfg.baudRate = HAL_UART_BR_115200; // 波特率
SerialCfg.flowControl = HAL_UART_FLOW_OFF; // 流控制
SerialCfg.callBackFunc = SerialCb; // 回調(diào)函數(shù)
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 ”));
}
在串口回調(diào)函數(shù)中我們只做一件事,將串口接收到的數(shù)據(jù)顯示到LCD中并且原樣的從串口輸出。回調(diào)函數(shù)的實(shí)現(xiàn)如下:
static void SerialCb( uint8 port, uint8 events )
{
uint8 RxBuf[64]={0};
if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 發(fā)送區(qū)滿或者空
{
return;
}
uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 讀取接收據(jù)量
usRxBufLen = MIN(64,usRxBufLen);
uint16 readLen = HalUARTRead(HAL_UART_PORT_0, RxBuf, usRxBufLen);
HalUARTWrite(HAL_UART_PORT_0, RxBuf, usRxBufLen);
}
實(shí)驗(yàn)現(xiàn)象,從實(shí)驗(yàn)現(xiàn)象中可以看到,一開始在串口中輸出了一個(gè)標(biāo)志字符串,然后我們通過串口發(fā)送了0123456789,然后數(shù)據(jù)原樣的從串口輸出了,這和我們預(yù)期的結(jié)果是一樣的。
但是我們發(fā)現(xiàn)LCD上的顯示和我們預(yù)期的不一樣,LCD上只顯示了6789,前面的數(shù)據(jù)并沒有顯示,這是怎么一回事呢?進(jìn)行單步調(diào)試可以發(fā)現(xiàn),我們發(fā)送一次數(shù)據(jù),回調(diào)函數(shù)被回調(diào)了兩次,第一次回調(diào)只接受到了012345,第二次回調(diào)接收到了6789,而在LCD上的顯示第二次覆蓋了第一次的顯示,所以我們會看到這種現(xiàn)象,解決的辦法,我們需要定義一個(gè)數(shù)據(jù)幀的時(shí)間間隔,當(dāng)接收數(shù)據(jù)的間隔超過了此間隔就認(rèn)為接收結(jié)束。
下面我們改寫接收處理,我們在接收到數(shù)據(jù)后開啟定時(shí)器,定時(shí)5ms這樣,當(dāng)接收間隔大于5ms后,我們就可以在定時(shí)事件中處理串口接收到的數(shù)據(jù)。
static void SerialCb( uint8 port, uint8 events )
{
if((events & HAL_UART_TX_EMPTY)||( events & HAL_UART_TX_FULL )) // 發(fā)送區(qū)滿或者空
{
return;
}
uint16 usRxBufLen = Hal_UART_RxBufLen(HAL_UART_PORT_0); // 讀取接收據(jù)量
if(usRxBufLen)
{
usRxBufLen = MIN(128,usRxBufLen);
uint16 readLen = HalUARTRead(HAL_UART_PORT_0, &SerialRxBuf[RxIndex], usRxBufLen); // 讀取數(shù)據(jù)到緩沖區(qū)
RxIndex += readLen;
readLen %= 128;
osal_start_timerEx(simpleBLEPeripheral_TaskID, UART_EVENT, 5); // 啟動定時(shí)器
}
}
事件處理代碼:
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);
}
經(jīng)過這樣的處理后,可以發(fā)現(xiàn)我們剛剛的問題已經(jīng)解決了。
到這里串口已經(jīng)可以正常使用了,為了更加方便的使用串口,我在這里添加一個(gè)函數(shù)實(shí)現(xiàn)標(biāo)準(zhǔn)C中printf,這樣更有利于我們輸出。
int SerialPrintf(const char*fmt, 。。。)
{
uint32 ulLen;
va_list ap;
char *pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN); // 開辟緩沖區(qū)
va_start(ap, fmt);
ulLen = vsprintf(pBuf, fmt, ap); // 用虛擬打印函數(shù)實(shí)現(xiàn)
va_end(ap);
HalUARTWrite(HAL_UART_PORT_0, (uint8*)pBuf, ulLen); // 從串口0輸出
osal_mem_free(pBuf); // 釋放內(nèi)存空間
return ulLen;
}
我們可以像使用C標(biāo)準(zhǔn)中的printf來使用這個(gè)函數(shù),例如我們將LCD的輸出全部導(dǎo)向串口的輸出,在HalLcdWriteString的實(shí)現(xiàn)中添加串口輸出代碼,如下圖:
重新編譯并且燒錄后可以看到LCD的輸出和串口的輸出是一樣的了。
第十九節(jié) 協(xié)議棧五向按鍵
和前面幾個(gè)一樣,按鍵的驅(qū)動在協(xié)議棧中也已經(jīng)有了,我們只需要做一些小的修改,使它適應(yīng)我們的開發(fā)板即可。
1. 修改工程配置,使能按鍵功能。
2. 在我們的工程中要使用按鍵功能,僅僅打開配置選項(xiàng)是不夠的。因?yàn)閰f(xié)議棧代碼默認(rèn)只有MINIDK開發(fā)板才有按鍵。
從這里可以看到(類似的地方有很多),如果要使能按鍵功能還需要定義CC2540_MINIDK,但是閱讀整個(gè)協(xié)議棧你會發(fā)現(xiàn),定義 CC2540_MINIDK后還會打開其它的功能,而那些功能并不是我們想要的,所以在這里我們使用另外一種方法來實(shí)現(xiàn)。我們定義我們的開發(fā)板也能使用按鍵功能,所以在工程配置中添加MT254xboard=TRUE,然后在按鍵功能有宏開關(guān)的地方加入這個(gè)條件。具體位置參見代碼。
按下相應(yīng)的按鍵后可以看到串口輸出相應(yīng)的按鍵值。五向按鍵的工作原理在裸機(jī)開發(fā)的時(shí)候已經(jīng)講過了,在協(xié)議棧中已經(jīng)有相應(yīng)的驅(qū)動代碼了,無需我們編寫,只需要按照實(shí)際情況改寫即可。例如我們的開發(fā)板每個(gè)按鍵對應(yīng)的電壓值和原來的值并不一樣,所以我們這里改寫了每個(gè)按鍵值的電壓范圍。
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;
}
第二十節(jié) 協(xié)議棧Flash數(shù)據(jù)存儲
CC254x自帶了256K Flash,這256K的儲存空間不僅可以儲存代碼,也可以儲存用戶的數(shù)據(jù),協(xié)議棧自帶了SNV管理代碼,我們只需要學(xué)會使用即可。
SNV的使用只有兩個(gè)函數(shù),分別是讀函數(shù)osal_snv_read和寫函數(shù)osal_snv_write,在SNV的儲存中,儲存的每個(gè)數(shù)據(jù)都有一個(gè)唯一的ID,SNV也正是利用這個(gè)ID來管理儲存在Flash中的數(shù)據(jù),在BLE的協(xié)議棧中,藍(lán)牙自身數(shù)據(jù)儲存用了一部分ID,我們儲存的數(shù)據(jù)ID不可使用這些ID,在bcomdef.h中有這些ID的定義。
下面我們往SNV中存入串口接收到的數(shù)據(jù),然后開發(fā)板斷電重啟后讀取出這串字符串并通過串口發(fā)送出去,來演示SNV的斷電保存。
首先我們定義一個(gè)我們儲存數(shù)據(jù)的ID,注意不能和已經(jīng)有的定義沖突。
#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的值并通過串口輸出讀取結(jié)果,如果讀取成功,則會將讀取結(jié)果打印到PC端,如果讀取失敗,則會提示讀取失敗。
在串口接收事件中將接收到的數(shù)據(jù)存入SNV中,并且也進(jìn)行相應(yīng)的提示。
將工程編譯下載后,可以看到現(xiàn)象如下:
第一次上電可以看到,提示讀取數(shù)據(jù)失敗了,說明第一次運(yùn)行時(shí)是沒有存儲數(shù)據(jù)的,接下來我們通過串口發(fā)送字符串 MT254xboard SNV Test字符串。
可以看到成功的將我們發(fā)送過去的字符存入了SNV中,那是否成功存入呢?我們將開發(fā)板斷電后重啟,看看第二次上電是否能夠讀取出我們存入的數(shù)據(jù)。
重啟后可以發(fā)現(xiàn)我們成功的讀取出了第一次存入的數(shù)據(jù),說明我們成功的將數(shù)據(jù)存入了SNV中。
評論
查看更多