一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

干貨|BLE入門談:從空中數(shù)據(jù)收發(fā)理解BLE(下)

 知芯世界 2021-08-10
點此查看>>從空中數(shù)據(jù)收發(fā)理解BLE(上)在使用帶BLE功能的MCU進(jìn)行應(yīng)用開發(fā)的時候,需要先熟悉BLE的API. 然而,各廠家的BLE API風(fēng)格差異很大,要比不同器件平臺硬件驅(qū)動庫HAL之間的差別更大。底層無線電部分的硬件,各家自有獨立的設(shè)計(硬件寄存器也不一定開放),況且BLE協(xié)議棧有很大一部分是軟件實現(xiàn),它不光涉及無線電部分,還需要定時器和中斷管理、電源管理,甚至用到動態(tài)內(nèi)存分配。于是要用BLE通信,協(xié)議棧部分幾十上百kB的代碼占用是很常見的(有的平臺把API實現(xiàn)放到ROM里能省部分),但難處在于不容易預(yù)測它的軟件行為,如一個API調(diào)用的執(zhí)行時間、什么時候會用回調(diào)函數(shù)、什么時候需要切換低功耗模式等等。每當(dāng)接觸一個新的BLE MCU平臺時,對BLE API的學(xué)習(xí)時間要遠(yuǎn)多于GPIO、UART這些基礎(chǔ)硬件。如果對BLE技術(shù)缺乏認(rèn)識,學(xué)習(xí)這些API更容易一頭霧水。
  
BLE協(xié)議棧包含的內(nèi)容太多了,一下弄明白太難。作為MCU應(yīng)用開發(fā),又不一定需要了解那么多,只要能實現(xiàn)需要的數(shù)據(jù)通信就夠了。跟手機(jī)用BLE通信會麻煩一點,但如果是MCU和MCU之間通信呢?用過NRF24L01嗎?它的空中數(shù)據(jù)包和BLE的數(shù)據(jù)包很相似,因為協(xié)議簡單了,沒有BLE的Profile, Service那些概念,對MCU工程師友好很多。
 
BLE應(yīng)用如果只做一個beacon的話,就是只管定期發(fā)出數(shù)據(jù),不需要建立連接的那種,其實是用不著協(xié)議棧的,甚至可能BLE API都不用到——這么說是不是一下子簡單了?比如,我只需要定時廣播一個溫度信息,真沒必要那么復(fù)雜啊。理解了BLE的數(shù)據(jù)包,就可以用不復(fù)雜的辦法來做。
  
這還有一個條件,就是能直接訪問MCU上的無線電部分硬件:得有一個開放的硬件環(huán)境,有手冊。本帖將用nRF51822來演示怎么直接操作硬件進(jìn)行數(shù)據(jù)包的收發(fā)。nRF51822是比較老的BLE MCU了,很容易從拆機(jī)的手環(huán)類電路板上找到,它后一代的nRF52xxx系列性能更好,無線部分硬件變化不大。除了nRF51xxx之外,看手里的板子能不能直接操作無線電部分硬件,就查參考手冊看對應(yīng)有沒有詳細(xì)的寄存器描述。剛結(jié)束的RSL10大賽用的板子也是可以玩的。


下面是nRF51xxx手冊中RADIO部分的硬件結(jié)構(gòu)框圖:


接收和發(fā)送部分大致是獨立的,但不能同時工作,就是半雙工的意思。要發(fā)送的數(shù)據(jù)包存放在RAM中,硬件通過DMA自動讀取,然后會加上地址、CRC、同步頭等,并經(jīng)過whitening步驟,然后用GFSK調(diào)制發(fā)送出去。接收過程是類似的,硬件通過包頭檢測、地址匹配、CRC校驗過程篩選合法的數(shù)據(jù)包,由DMA寫到RAM中指定的地址。
  
nRF51822支持的數(shù)據(jù)包格式是這樣的:


這和BLE spec中基礎(chǔ)數(shù)據(jù)包格式是兼容的(不然怎么支持BLE),所以我們將它配置成BLE的格式,就可以直接收發(fā)數(shù)據(jù)了。
  
Preamble部分是0/1交錯的同步碼,0xAA或者0x55,取決于地址部分的LSB(最先發(fā)送的那一bit),硬件負(fù)責(zé)。
  
地址部分,nRF51822的地址長度可以是3~5字節(jié),分被BASE和PREFIX兩部分。BLE的Access Address是4字節(jié),因此設(shè)置BASE長度為3字節(jié)。
  
接下來的S0、LENGTH、S1字段是可選的(長度可以設(shè)成0),如果用了,則需要看成BLE數(shù)據(jù)包的PDU的一部分。和后面的PAYLOAD部分一起組成PDU.
  
最后CRC部分由硬件負(fù)責(zé),需要設(shè)置為24-bit, 要按照BLE要求設(shè)置。
 
先試驗?zāi)芊駨目罩胁蹲降紹LE的數(shù)據(jù)包。需要提供給RADIO硬件的參數(shù)還有:(1)信道,(2)地址,(3)包長度。關(guān)于信道,為了捕捉advertising類型的包,可以設(shè)置成37、38、39信道當(dāng)中的一個。設(shè)成其它信道捕捉連接數(shù)據(jù)包,除了要根據(jù)跳頻算法不斷更改信道外,還需要知道Access Address才可以。BLE 37、38、39信道使用固定的Access Address: 0x8E89BED6, 但建立連接后用的Access Address是主設(shè)備隨機(jī)生成的,在CONNECT_REQ包中提供給從設(shè)備。包長度在BLE包PDU的第2個字節(jié),也就是把上面的S0字段長度設(shè)置為1字節(jié)后,LENGTH字段就可以對應(yīng)BLE PDU長度。nRF51822的RADIO使用LENGTH字段的信息(接收時來自空中數(shù)據(jù),發(fā)送時來自RAM數(shù)據(jù))來決定收發(fā)數(shù)據(jù)長度,不然就只能采用固定長度了。
  
為了接收37信道(中心頻率2402MHz)的advertising類型數(shù)據(jù)包,用這樣的配置:
  • NRF_RADIO->RXADDRESSES = 1; // enable address 0

  • NRF_RADIO->FREQUENCY = 2; // 2402MHz, CH37

  • NRF_RADIO->DATAWHITEIV = 37;

  • NRF_RADIO->MODE = (RADIO_MODE_MODE_Ble_1Mbit << RADIO_MODE_MODE_Pos);

  • NRF_RADIO->PREFIX0 = 0x8E;

  • NRF_RADIO->BASE0 = 0x89BED600;

  • // LFLEN=6 bits, S0LEN=1Byte, S1LEN=2bit

  • NRF_RADIO->PCNF0 = 0x00020106;

  • // STATLEN=6, MAXLEN=37, BALEN=3, ENDIAN=0 (little), WHITEEN=1

  • NRF_RADIO->PCNF1 = 0x02030025;

  • NRF_RADIO->CRCCNF = 0x103; // only PDU, 3 octets

  • NRF_RADIO->CRCINIT = 0x555555; // for advertising packet

  • NRF_RADIO->CRCPOLY = 0x100065b;

  • // set receive buffer

  • NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf;

  

啟動接收過程或發(fā)送過程要通過nRF51的task型寄存器。先看下RADIO部分的狀態(tài)轉(zhuǎn)移圖:

在DISABLED狀態(tài)通過TXEN或RXEN task啟動硬件,到TXIDLE或RXDILE的準(zhǔn)備狀態(tài),然后用START來進(jìn)行一次傳輸。從接收切換到發(fā)送,以及從發(fā)送切換到接收,必須先轉(zhuǎn)回DISABLED狀態(tài)。
  
在接收狀態(tài)下,硬件會監(jiān)聽指定地址的數(shù)據(jù)包,接收完成后轉(zhuǎn)到RXIDLE狀態(tài),并產(chǎn)生END event.

當(dāng)收到END event時,表示收到了一個數(shù)據(jù)包(地址匹配有效),然后可以訪問CRCSTATUS寄存器判斷CRC校驗是否正確。若CRC有錯,可能是數(shù)據(jù)包被干擾破壞,或者格式不正確。接收數(shù)據(jù)包的S0、LENGTH、S1、PAYLOAD字段存放到RAM中,稍有變化的是LENGTH和S1字段都被擴(kuò)展成了字節(jié)存儲。

  

我寫了一個循環(huán)來持續(xù)接收數(shù)據(jù)包,進(jìn)行37信道的監(jiān)聽。使用雙緩沖區(qū)輪流存放收到的數(shù)據(jù)包,以便一邊解析數(shù)據(jù)一邊接收。

  • for(;;)

  • {

  • NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf1;

  • NRF_RADIO->EVENTS_END = 0;

  • NRF_RADIO->TASKS_START = 1;

  • if(crcok2)

  • show_pkt(pkt_buf2);

  • else

  • uart_wstr(".");

  • if(NRF_RADIO->EVENTS_END)

  • uart_wstr("!");

  • while(! NRF_RADIO->EVENTS_END)

  • {}

  • crcok1=NRF_RADIO->CRCSTATUS;

  • NRF_RADIO->CRCINIT = 0x555555;// for advertising packet

  • NRF_RADIO->PACKETPTR = (uint32_t)pkt_buf2;

  • NRF_RADIO->EVENTS_END = 0;

  • NRF_RADIO->TASKS_START = 1;

  • if(crcok1)

  • show_pkt(pkt_buf1);

  • else

  • uart_wstr(".");

  • if(NRF_RADIO->EVENTS_END)

  • uart_wstr("!");

  • while(! NRF_RADIO->EVENTS_END)

  • {}

  • crcok2=NRF_RADIO->CRCSTATUS;

  •   }

通過對PDU第一個字節(jié)的低4位,可以判斷數(shù)據(jù)包類型,然后識別余下數(shù)據(jù)。

  • static inline void show_pkt(volatile uint8_t *buf)

  • {

  • switch(buf[0]&0xF)

  • {

  • case 6: // ADV_SCAN_IND

  • uart_wstr("s");

  • add_log(buf);

  • break;

  • case 0: // ADV_IND

  • uart_wstr("A");

  • add_log(buf);

  • break;

  • case 2: // ADV_NONCONN_IND

  • uart_wstr("n");

  • add_log(buf);

  • break;

  • case 4: // SCAN_RESP

  • uart_wstr("R");

  • break;

  • case 1: // ADV_DIRECT_IND

  • uart_wstr("i");

  • break;

  • case 3: // SCAN_REQ

  • uart_wstr("+");

  • break;

  • case 5: // CONN_REQ

  • uart_wstr("C");

  • break;

  • default:

  • uart_wstr("?");

  • break;

  • }

  • }

  

如果是包含advertising數(shù)據(jù)的包,可以將地址、數(shù)據(jù)記錄下來,待收集一段時間后進(jìn)行統(tǒng)計。

  • void add_log(uint8_t *buf)

  • {

  • int i;

  • for(i=0;i<32;i++)

  • {

  • if(adv_log.count)// not blank

  • {

  • if(memcmp(adv_log.addr, buf+3, 6)==0 && adv_log.type==buf[0])// match

  • {

  • adv_log.count++;

  • return;

  • }

  • }

  • else// add entry

  • {

  • memcpy(adv_log.addr, buf+3, 6);

  • adv_log.type = buf[0];

  • adv_log.len = buf[1]-6;

  • memcpy(adv_log.payload, buf+9, adv_log.len);

  • adv_log.count=1;

  • return;

  • }

  • }

  • }

這樣就可以發(fā)現(xiàn)周圍的一部分BLE設(shè)備了。現(xiàn)在我的程序只是接收,沒有主動發(fā)起“掃描”。但是我的程序收到了許多主動掃描的包,表明附近有設(shè)備在持續(xù)進(jìn)行瘋狂掃描……


以上演示的是單向接收。單向發(fā)送也是容易實現(xiàn)的,只要填充一個advertising包,把要發(fā)送的數(shù)據(jù)包含在內(nèi),用TX模式發(fā)送出去就是了。發(fā)送的設(shè)置和接收基本一樣。

  • void radio_adv_tx(uint8_t *pdu, uint8_t len)

  • {

  • uint8_t txpkt[40];

  • NRF_RADIO->EVENTS_READY = 0;

  • NRF_RADIO->TASKS_TXEN = 1;

  • while (NRF_RADIO->EVENTS_READY == 0);

  • // now in TXIDLE state

  • txpkt[0]=0x42;// private TX address, non-connectable

  • if(len>31)

  • len=31;

  • txpkt[1]=len+6;

  • txpkt[2]=0;

  • txpkt[3]=0x37; txpkt[4]=0x5A; txpkt[5]=0x29;

  • txpkt[6]=0xC6; txpkt[7]=0x8B; txpkt[8]=0x04;

  • memcpy(txpkt+9, pdu, len);

  • NRF_RADIO->PACKETPTR = (uint32_t)txpkt;

  • NRF_RADIO->EVENTS_END = 0;

  • NRF_RADIO->TASKS_START = 1;

  • while(! NRF_RADIO->EVENTS_END)

  • {}

  • }

  
使用一個包含名稱的advertising數(shù)據(jù),調(diào)用上面的函數(shù)。設(shè)備地址是04:8B:C6:29:5A:37, 寫在發(fā)送函數(shù)中了。

  • const uint8_t dummy_adv[]={0x02,0x01,0x06,// flags

  • 15,0x09,'A','D','V','_','D','e','m','o',' ','5','1','8','2','2'};

  • radio_adv_tx(dummy_adv,sizeof(dummy_adv));

  
定期(比如1秒)發(fā)送一次,在手機(jī)上用BLE掃描工具可以發(fā)現(xiàn)這個設(shè)備。當(dāng)然現(xiàn)在僅僅廣播了一個名稱而已,要添加自定的傳感器數(shù)據(jù)也很簡單,不過要注意31個字節(jié)長度的限制。

以上只是最初級的直接操作硬件進(jìn)行BLE數(shù)據(jù)包收發(fā)的演示,只用了單向數(shù)據(jù),因此簡單了。如果要兩個設(shè)備有應(yīng)答地交互,就需要發(fā)送方在數(shù)據(jù)包發(fā)送之后切換到接收狀態(tài),等待一小段時間看是否有應(yīng)答。BLE的連接建立起來后,主從雙方的收發(fā)方向就是在不斷地切換,如果要自己編程操作硬件實現(xiàn)這些,而不使用協(xié)議棧的API, 理論上是可以做的,問題在于有沒有必要了。
  
利用BLE MCU的無線電硬件部分,做一些調(diào)試工具是可行而且有用的。還可以做自己的私有協(xié)議通訊,那樣就不能再叫做BLE了。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    亚洲精选91福利在线观看| 日韩一区二区三区有码| 亚洲熟女乱色一区二区三区 | 国产免费自拍黄片免费看| 国产老熟女超碰一区二区三区| 国产精品午夜小视频观看| 丰满人妻熟妇乱又伦精另类视频| 国产午夜精品福利免费不| 老司机精品一区二区三区| 成人国产一区二区三区精品麻豆 | 久久老熟女一区二区三区福利| 欧美日韩有码一二三区| 激情少妇一区二区三区| 欧美日韩精品综合一区| 久久精品国产亚洲av久按摩| 亚洲天堂久久精品成人| 99视频精品免费视频播放| 欧美日韩国产另类一区二区| 亚洲一区二区三区av高清| 国产又粗又长又爽又猛的视频| 国产又色又爽又黄的精品视频| 日韩一区二区三区高清在| 欧美又黑又粗大又硬又爽| 亚洲中文字幕在线观看四区| 免费黄色一区二区三区| 超碰在线免费公开中国黄片 | 厕所偷拍一区二区三区视频| 日韩欧美在线看一卡一卡| 久久精品福利在线观看| 日韩人妻精品免费一区二区三区 | 欧美一区二区在线日韩| 精品女同一区二区三区| 亚洲精品一区二区三区日韩| 国产精品福利一级久久| 九九热视频网在线观看| 亚洲国产精品肉丝袜久久| 91欧美视频在线观看免费| 激情内射日本一区二区三区| 国产成人午夜在线视频| 一区二区三区精品人妻| 台湾综合熟女一区二区|