[開源項目]藍點無限TWR算法-多基站多標簽固件


初衷:           
       隨着UWB大力發展,國內實際應用逐步落地。 對於UWB的需求已經不是停留在實驗測試階段,
       目前逐步進入商用大環境,很多廠商特殊需要一定要選用TWR,無法接收TDOA。
       但是受限於目前大部分TWR方案一般只能支持3-4基站定位,很多廠商無法實現UWB項目快速落地。
       基於以上,我們打算開源一套多基站多標簽固件,方便高需求客戶見解和二次開發。
       同時,對於學習測試的客戶,建議依然使用基本版本3-4基站定位,這種多基站多標簽實現增加了更多邏輯部分的實現,不適合入門學習。

軟件流程:

關鍵流程說明:
除了完成基本測距以外,這里主要需要完成動態識別功能,標簽需要動態識別它周圍的基站,時刻可以保持與周圍3-4個基站進行測距。

固件Base:
這個固件依然基於我們的開源框架項目51uwb_base進行二次開發,
更多開源框架相關參見鏈接:http://51uwb.cn/forum.php?mod=viewthread&tid=165&extra=page%3D1


固件匹配Python開源上位機:
上位機使用我們目前已經開源的純python版本上位機,相關鏈接:http://51uwb.cn/forum.php?mod=viewthread&tid=401&extra=page%3D1


固件重要問題說明:
1 串口輸出,不同以往,這里用的是標簽串口
2 上位機接口目前是TCP,需要使用串口轉TCP工具:串口轉TCP參見視頻https://www.bilibili.com/video/BV1rv411p7Hj/


固件源碼:
固件源碼已經放到git上,V1.0 版本開發完成,請詳細看下面的描述
https://tuzhuke@bitbucket.org/tuzhuke/bp30_multianthor.git


固件開發記錄
Day1:
githash:a387f5cdbbff3b6b1c818eaf459b4ad2a6fe24c0

主要完成功能,標簽發送廣播信號,基站接收廣播信號。標簽發送的廣播信號需要包含已經識別的基站地址。

Step1 在標簽中定義一個存放基站的結構體數組

struct Anthor_Information
{
     uint16 short_address;//基站16bit 短地址
     uint16 distance;//距離信息,高8 低8bit
     uint32 last_time;//上次通信時間
     uint8  rssi_info;//上次通信RSSI值記錄
    unsigned char alive; //是否已經識別或者是否已經丟失
} anthor_info[MAX_ANTHOR

Step2 在標簽中發送廣播信號,廣播信號包含了已經識別的基站,如果基站收到這個信息,發現數據包中已經有自己地址就無需反饋,否則反饋信息給標簽。函數試下如下:

點擊查看代碼
    /*******************************************************************************
    * 函數名  : BPhero_TAG_Broadcast
    * 描述    : 標簽啟動發送廣播信息給各個基站,信息數據包包括了基站短地址
    * 輸入    : 無
    * 輸出    : 無
    * 返回值  : 無
    * 說明    : 發送broadcast信息(B信息)給所有基站
    *******************************************************************************/

    void BPhero_TAG_Broadcast(void)
    {
        uint8 index = 0 ;
        uint8 strlen = 0;
        msg_f_send.destAddr[0] = 0xFF;
        msg_f_send.destAddr[1] = 0xFF;

        msg_f_send.seqNum = distance_seqnum;
        msg_f_send.messageData[0]='B';//broadcast message
        strlen = strlen + 1;

        uint8 *pAnthor_Str = &msg_f_send.messageData[1];
        //后面跟基站信息
        for(index = 0 ; index < MAX_ANTHOR; index++)
        {
            if(anthor_info[index].alive == 1)
            {
                sprintf(pAnthor_Str, "%04X:",anthor_info[index].short_address);
                pAnthor_Str = pAnthor_Str + 5;
                strlen = strlen + 5;
            }
        }

        //GPIOB.5設定,兼容之前帶PA的模塊-->如需求請聯系www.51uwb.cn
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, !GPIO_PIN_RESET);//PA node ,enable pa
        //寫入數據
        dwt_writetxdata(11 + strlen,(uint8 *)&msg_f_send, 0) ;  // write the frame data
        dwt_writetxfctrl(11 + strlen, 0);
        dwt_starttx(DWT_START_TX_IMMEDIATE);        //啟動發送
        while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))        //等待發送完成
        { };
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS);//清除發送完成標志
        poll_tx_ts = get_tx_timestamp_u64();//讀取發送時間戳

        //清空接收緩存,待收到數據時使用
        for (int i = 0 ; i < FRAME_LEN_MAX; i++ )
        {
            rx_buffer[i] = '\0';
        }
        dwt_enableframefilter(DWT_FF_DATA_EN);        //啟動幀過濾功能
        dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS*10);//設定接收延時函數
        dwt_rxenable(0);//啟動接收機
        //sequence控制
        if(++distance_seqnum == 255)
            distance_seqnum = 0;
    }

Step3 在標簽中調用上述函數廣播發送,我們使用之前代碼定時器回調函數,通過回調函數,可以周期性發送

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == htim3.Instance)
        {
            HAL_TIM_Base_Stop(&htim3);
            {
                dwt_forcetrxoff();
                TAG_SendOut_Messge();
                BPhero_TAG_Broadcast();
            }

            HAL_TIM_Base_Start(&htim3);
        }
    }

Step4: 在基站接收broadcast “B”信號,在rx_main.c中收到“B信號”后打印一串字符。

 case 'B':
                printf("receive B message\n");
                break;

分別編譯標簽和基站的進行測試。串口數據如下圖,表明數據可以正常收到。

Day2:
githash: e23e4e1e3bf681d1125036e5dbbf5a07fe363fdc

主要完成基站收到B信息后,以R信息回復給標簽,標簽收到信息提取短地址,並更新自己的結構體數組

Step1 修改標簽廣播格式,在數據包中增加已知基站的個數。

  //后面跟基站信息
    for(index = 0 ; index < MAX_ANTHOR; index++)
    {
        if(anthor_info[index].alive == 1)
        {
            sprintf(pAnthor_Str, "%04X:",anthor_info[index].short_address);
            pAnthor_Str = pAnthor_Str + 5;
            strlen = strlen + 5;
                                          anthor_count++;
        }
    }
                msg_f_send.messageData[1] = anthor_count;

Step2: 修改基站接收處理,目前只簡單反饋信息給標簽,以“R”信息回復到標簽,同時將標簽數據包中的“現有基站個數”打印出來用於debug

    case 'B':
                printf("receive B anthor = %d\n",msg_f->messageData[1]);
                {
                    msg_f_send.messageData[0]='R';//Poll message
                    //后面修改這個數據長度
                    dwt_writetxdata(11 + 1, (uint8 *)&msg_f_send, 0) ; // write the frame data
                    dwt_writetxfctrl(11 + 1, 0);
                    dwt_starttx(DWT_START_TX_IMMEDIATE);
                    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
                    { };
                }
                break;

Step3: 標簽收到信息,提取基站信息中的地址

       case 'R':
      address = msg_f_recv->sourceAddr[1]<<8|msg_f_recv->sourceAddr[0];
      printf("receive R message 0x%04X\n",address);
      Update_Anthor_Info(address);
      break;
    uint8 Update_Anthor_Info(uint32 shortaddress)
    {
       uint8 index = 0;
       printf("shortaddress = 0x%04X\n",shortaddress);
       //后面跟基站信息
        for(index = 0 ; index < MAX_ANTHOR; index++)
        {
            if(anthor_info[index].alive == 0)
            {
                                            anthor_info[index].short_address = shortaddress;
                                            anthor_info[index].alive = 1;        
                                            return 1;
            }
        }
                    return 0;
    }

Day3:
githash:4d1b64584706426c2a71174d7026cbe7696f2a4b

今天完成了基本功能開發,可以作為V1.0版本。
主要開發內容:基站解析標簽發送的廣播B信號,標簽匯總R信號基站,如果收到R基站大於等於4個開始測距,如果測距的時候發現基站丟失,重新啟動廣播B信號。
1 基站解析標簽廣播B信號,匹配是否有自己的地址,有地址忽略,沒有地址回復R信號

點擊查看代碼
     case 'B':
                printf("receive B anthor = %d\n",msg_f->messageData[1]);
                Num_Anthor = msg_f->messageData[1];
                Sourceaddress =  msg_f->sourceAddr[1]<<8| msg_f->sourceAddr[0];
                pAnthor_Str = &msg_f->messageData[2];
                match_flag = 0;
                for (Index = 0; Index < Num_Anthor; Index++)
                {
                    printf("receive address = %04X\n",(pAnthor_Str[1]<<8|pAnthor_Str[0]));

                    if(SHORT_ADDR == (pAnthor_Str[1]<<8|pAnthor_Str[0])) //匹配成功
                    {
                        printf("match\n");
                        match_flag = 1;

                    }
                    pAnthor_Str = pAnthor_Str +3 ;
                }

                if(match_flag == 0)//沒有匹配到,發送一個反饋信息
                {
                    msg_f_send.messageData[0]='R';//Poll message
                    //后面修改這個數據長度
                    dwt_writetxdata(11 + 1, (uint8 *)&msg_f_send, 0) ; // write the frame data
                    dwt_writetxfctrl(11 + 1, 0);
                    dwt_starttx(DWT_START_TX_IMMEDIATE);
                    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
                    { };
                }
                break;

2 標簽匯總基站反饋的R信號,其實這部分代碼在day2 已經完成,無需修改。
3 判斷收到R信號個數,這個在定時廣播里判斷的。如果小於4,持續執行廣播收集。

  HAL_TIM_Base_Stop(&htim3);
        {
            dwt_forcetrxoff();
            if(Count_Anthor() < 4)
            {
                gProcess_Dis = 0;
                BPhero_TAG_Broadcast();
                gSend_index = 0;

            }

具體實現函數

    uint8 Count_Anthor()
    {
        uint8 index = 0;
        uint8 count = 0;
        //后面跟基站信息
        for(index = 0 ; index < MAX_ANTHOR; index++)
        {
            if(anthor_info[index].alive == 1)
            {
                count++;
            }
        }
        return count;
    }

 3 當R信號基站數量等於4個,開始啟動測距

點擊查看代碼
    if(Count_Anthor() < 4)
            {
                gProcess_Dis = 0;
                BPhero_TAG_Broadcast();
                gSend_index = 0;

            }
            else
            {
                if(gSend_index ==Count_Anthor())
                {
                    gSend_index= 0;
                    Send_Dis_To_Anthor0();
                } else
                {
                    gProcess_Dis = 1;
                    BPhero_Distance_Measure_Specail_ANTHOR();// 從1 2 3 4發送
                }
            }

這里的Send_Dis_To_Anthor0()是沿用之前的函數名,其實在這個里面實現了數據格式組裝並在串口打印,以及調用函數在液晶顯示。
BPhero_Distance_Measure_Specail_ANTHOR()主要功能就是啟動測距,測距對象是收集到R信號的基站。

點擊查看代碼
    void BPhero_Distance_Measure_Specail_ANTHOR(void)
    {
        uint16 destaddress = Find_Address();
       // printf("Send Index = %d, Address = 0x%04X\n",gSend_index,destaddress);
        msg_f_send.destAddr[0] =(destaddress) &0xFF;
        msg_f_send.destAddr[1] =  ((destaddress)>>8) &0xFF;

        msg_f_send.seqNum = distance_seqnum;
        msg_f_send.messageData[0]='P';//Poll message

        //GPIOB.5設定,兼容之前帶PA的模塊-->如需求請聯系www.51uwb.cn
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, !GPIO_PIN_RESET);//PA node ,enable pa
        //寫入數據
        dwt_writetxdata(12,(uint8 *)&msg_f_send, 0) ;  // write the frame data
        dwt_writetxfctrl(12, 0);
        dwt_starttx(DWT_START_TX_IMMEDIATE);        //啟動發送
        while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))        //等待發送完成
        { };
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS);//清除發送完成標志
        poll_tx_ts = get_tx_timestamp_u64();//讀取發送時間戳

        //清空接收緩存,待收到數據時使用
        for (int i = 0 ; i < FRAME_LEN_MAX; i++ )
        {
            rx_buffer[i] = '\0';
        }
        dwt_enableframefilter(DWT_FF_DATA_EN);        //啟動幀過濾功能
        dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);//設定接收延時函數
        dwt_rxenable(0);//啟動接收機
        //sequence控制
        if(++distance_seqnum == 255)
            distance_seqnum = 0;
    }

4 中斷回調函數中處理timeout,如果測距對象基站沒有反饋,標簽發生timeout中斷,則立即將該基站islive 設置為0,帶下次統計,發現基站數量小於4,則標簽重新發送廣播信號收集基站

  else
    {
        if(gProcess_Dis == 1)
        {
            printf("timeout address 0x%04X\n",Find_Address());
            Delete_Anthor(Find_Address());
        }

其它更新:
git hash:4892896b3d09e6009dfbe3d537e866f2c94d2d36
修改了一個祖傳代碼bug,使用了野指針....


git hash:d2e01e118126c9ce9c84337126db3e92d23ed3ba
修改了UWB中斷,讓UWB中斷只處理接收成功和timeout兩種事件,其他事件均不處理

同時,調試的時候發現上位機當收到異常數據無法處理,導致異常退出

    def twr_main(input_string):
        print(input_string)
        error_flag, result_dic = Process_String_Before_Udp(input_string)
        if error_flag == 0:
            [location_result, location_seq, location_addr, location_x, location_y] = Compute_Location(result_dic)
            return location_result, location_seq, location_addr, location_x, location_y
        return 0, 0, 0, 0, 0

 

當發生異常,增加return 0, 0, 0, 0, 0,代碼同步更新到上位機部分鏈接。


自此,通過三天,零散的時間,開發出一套可以動態識別基站並完成測距的固件代碼,代碼編寫時間和測試debug時間基本是1:5,更多的是細節考慮步驟導致異常.
開發過程中遇到一個很詭異的異常,當其中一個基站地址將短地址設置為0x0006后,標簽識別成功但是無法完成測距,通過加debug信息最終發現是由於標簽測距完成后有一個濾波器,濾波器設置的最大基站數目為5,導致數組越界訪問。

三天零散的開發時間,可以說這次開發非常順利,一是由於有一個比較完備的代碼框架,基於之前的代碼框架開發,可以減少對於底層的依賴,只需要實現邏輯部分即可。而邏輯部分其實在很早之前就有想法,通過想法落實到流程圖,規划每一步要做什么。目前的代碼還沒有切合到流程圖上,流程圖中,我的想法是即便有4個基站可以定位,依然周期性的發送廣播,發現更多的基站,選取附近的基站做參考。 由於時間關系,這個部分可能留給各位看官了。


關於硬件,目前我們的代碼,基於硬件是我們自研的BP30,使用主控是F4。同時可以無縫在BP400 上使用。 如果沒有我們的硬件,可以適當進行移植,匹配主控。

最后,歡迎交流分享!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM