DWM1000DISCOVERY TWR方式定位程序分析

DWM1000DISCOVERY默认程序使用的是STM32Cube_FW_F0_V1.5.0_DWM1000DISCOVERY_FACTORY_20160412.zip这个版本,这个程序可以在分享的资料的Code目录下找到,改程序是基于STM32官方STM32Cube_FW_F0_V1.5.0程序修改过来的,只保留了MDK工程目录。

对应的DWM1000DISCOVERY开发板请到如下地址查看:

https://item.taobao.com/item.htm?spm=a230r.1.14.15.4auZz7&id=528003782720&ns=1&abbucket=12#detail

Keil编译:

因此首先需要安装MDK5.14版本的Keil,在Tool目录下也共享了安装程序,请自行安装。Keil搞定之后,找到如下目录\STM32Cube_FW_F0_V1.5.0_justin\Projects\STM32F072RB-Nucleo\DWM1000DISCOVERY\MDK-ARM,就可以开启Keil,编译;有的朋友可能有如下问题:

因为代码是从linux下移植过来的,编译器不同,所以结构体和联合体这里定义会有所不同解决的方法是在联合体前面加入如下程序:

#pragma anon_unions

加入之后,就可以顺利编译通过。

也有的朋友出现如下错误:

STM32F072RB-Nucleo\STM32F072RB-Nucleo.axf: Error: L6411E: No compatible library exists with a definition of startup symbol __main.

问题原因是MDK与ADS冲突了,解决方法如下

方法一:卸载,同时把ADS1.2的环境变量删除:

我的电脑->属性->高级-环境变量-path里面把C:\Program Files\ARM\ADSv1_2等相似的5个变量给删除了就可以了
法二:MDK与ADS共存
在我的电脑->属性->高级-环境变量-path里面增加一个变量:
增加环境变量: ARMCC5LIB
变量值:C:\Keil\ARM\ARMCC\lib
代码分析:
代码只有一个,包括标签Tag和基站Anchor的程序,通过串口USART1配置不同的角色,配置角色和地址的过程可以参考:
<a target=_blank href="http://blog.csdn.net/xingqingly/article/details/51121481" target="_blank">http://blog.csdn.net/xingqingly/article/details/51121481</a>
注意角色不同,但是地址不能配置为相同的,也就是说Tag地址默认是0,Anchor地址一定不要再出现0,地址在整个系统中是唯一的,这点要注意,否则Tag和Anchor的地址相同,无法通信。</span>
main函数大致流程如下:
(1) 首先初始化外设接口,这里我们主要用到GPIO控制LED,I2C1控制eeprom和lps25h,串口USART1用于配置地址和角色,SPI1控制DWM1000,USB目前没用到。
<pre name="code" class="cpp">/* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();
  MX_SPI1_Init();
  MX_USB_DEVICE_Init();

初始化完成后,所有的LED亮:


<pre name="code" class="cpp"> // Light up all LEDs to test
  ledOn(ledRanging);
  ledOn(ledSync);
  ledOn(ledMode);

(2) 初始化外设,自测,首先自测的是lps25h,目前板子上已经不焊接改芯片,所以这部分代码必须注释掉


<span style="font-size:14px;"></span><pre name="code" class="cpp">  // Initializing pressure sensor (if present ...)
  lps25hInit(&hi2c1);
  printf("TEST\t: Initializing pressure sensor ... ");
  if (lps25hTestConnection()) {
    printf("[OK]\r\n");
    lps25hSetEnabled(true);
  } else {
    printf("[FAIL] (%u)\r\n", (unsigned int)hi2c1.ErrorCode);
    selftestPasses = false;
  }

  printf("TEST\t: Pressure sensor self-test ... ");
  if (lps25hSelfTest()) {
    printf("[OK]\r\n");
  } else {
    printf("[FAIL]\r\n");
    selftestPasses = false;
  }

(3) 初始化eeprom,进行简单的读操作,验证是否正常


<span style="font-size:14px;"></span><pre name="code" class="cpp">// Initializing i2c eeprom
  eepromInit(&hi2c1);
  printf("TEST\t: EEPROM self-test ... ");
  if (eepromTest()) {
    printf("[OK]\r\n");
  } else {
    printf("[FAIL]\r\n");
    selftestPasses = false;
  }

(4) 最后自检dwm1000


<span style="font-size:14px;"></span><pre name="code" class="cpp"> printf("TEST\t: Initialize DWM1000 ... ");
  dwInit(dwm, &dwOps);       // Init libdw
  dwOpsInit(dwm);
  result = dwConfigure(dwm); // Configure the dw1000 chip
  if (result == 0) {
    printf("[OK]\r\n");
    dwEnableAllLeds(dwm);
  } else {
    printf("[ERROR]: %s\r\n", dwStrError(result));
    selftestPasses = false;
  }

(5) 以上外设,如果任何一个自检失败,都会将selftestPasses参数设置为false,也就是自检失败,这时code将会停住在while(1)中。所以如果哪个外设没有,一定要将其注释掉。


<span style="font-size:14px;"></span><pre name="code" class="cpp"> if (!selftestPasses) {
    printf("TEST\t: One or more self-tests failed, blocking startup!\r\n");
    while(1);
  }

(6) 自检成功后,接下来对eeprom进行配置,eeprom内部存储格式,参考如下文档:

lps-node-firmware EEPROM数据格式

  cfgInit();

  if (cfgReadU8(cfgAddress, &address[0])) {
    printf("CONFIG\t: Address is 0x%X\r\n", address[0]);
  } else {
    printf("CONFIG\t: Address not found!\r\n");
  }

  if (cfgReadU8(cfgMode, &mode)) {
    printf("CONFIG\t: Mode is ");
    switch (mode) {
      case modeAnchor: printf("Anchor\r\n"); break;
      case modeTag: printf("Tag\r\n"); break;
      case modeSniffer: printf("Sniffer\r\n"); break;
      default: printf("UNKNOWN\r\n"); break;
    }
  } else {
    printf("Device mode: Not found!\r\n");
  }

  uint8_t anchorListSize = 0;
  if (cfgFieldSize(cfgAnchorlist, &anchorListSize)) {
    if (cfgReadU8list(cfgAnchorlist, (uint8_t*)&anchors, anchorListSize)) {
      printf("CONFIG\t: Tag mode anchor list (%i): ", anchorListSize);
      for (i = 0; i < anchorListSize; i++) {
        printf("0x%02X ", anchors[i]);
      }
      printf("\r\n");
    } else {
      printf("CONFIG\t: Tag mode anchor list: Not found!\r\n");
    }
  }

总的来讲就是存储当前节点的角色,基站anchor有多少个,等下次重新上电的时候直接读取eeprom记录的角色和地址信息,来执行相应的程序。


<span style="font-size:14px;">之后,所有的LED灯灭,然后进入配置dwm参数的程序:</span>
<span style="font-size:14px;"></span><pre name="code" class="cpp">  ledOff(ledRanging);
  ledOff(ledSync);
  ledOff(ledMode);

(7) dwm1000无线参数配置


<span style="font-size:14px;"></span><pre name="code" class="cpp">dwTime_t delay = {.full = ANTENNA_DELAY/2};
  dwSetAntenaDelay(dwm, delay);

  dwAttachSentHandler(dwm, txcallback);
  dwAttachReceivedHandler(dwm, rxcallback);

  dwNewConfiguration(dwm);
  dwSetDefaults(dwm);
  dwEnableMode(dwm, MODE_SHORTDATA_FAST_ACCURACY);
  dwSetChannel(dwm, CHANNEL_2);
  dwSetPreambleCode(dwm, PREAMBLE_CODE_64MHZ_9);

  dwCommitConfiguration(dwm);

需要注意的是几点:


<span style="font-size:14px;">注册回调函数txcallback和rxcallback,这里的程序会在dwm1000中断引脚IRQ触发的时候调用的函数,可以追下中断处理函数:</span>
<span style="font-size:14px;"></span><pre name="code" class="cpp">void EXTI0_1_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

会发现调用了dwHandleInterrupt函数

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  switch (GPIO_Pin) {
    case DWM_IRQ_PIN:
      do{
          dwHandleInterrupt(dev);
      } while(checkIrq() != 0); //while IRS line active (ARM can only do edge sensitive interrupts)
      HAL_NVIC_ClearPendingIRQ(DWM_IRQn);
      break;
    case GPIO_PIN_12:
      //instance_notify_DW1000_inIDLE(1);
      break;
    default:
      break;
  }
}

展开dwHandleInterrupt函数会发现调用了


<span style="font-size:14px;"></span><pre name="code" class="cpp">void dwHandleInterrupt(dwDevice_t *dev) {
	// read current status and handle via callbacks
	dwReadSystemEventStatusRegister(dev);
	if(dwIsClockProblem(dev) /* TODO and others */ && _handleError != 0) {
		(*_handleError)();
	}
	if(dwIsTransmitDone(dev) && dev->handleSent != 0) {
		(*dev->handleSent)(dev);
		dwClearTransmitStatus(dev);
	}
	if(dwIsReceiveTimestampAvailable(dev) && _handleReceiveTimestampAvailable != 0) {
		(*_handleReceiveTimestampAvailable)();
		dwClearReceiveTimestampAvailableStatus(dev);
	}
	if(dwIsReceiveFailed(dev) && _handleReceiveFailed != 0) {
		(*_handleReceiveFailed)();
		dwClearReceiveStatus(dev);
		if(dev->permanentReceive) {
			dwNewReceive(dev);
			dwStartReceive(dev);
		}
	} else if(dwIsReceiveTimeout(dev) && dev->handleReceiveTimeout != 0) {
		(*dev->handleReceiveTimeout)(dev);
		dwClearReceiveStatus(dev);
		if(dev->permanentReceive) {
			dwNewReceive(dev);
			dwStartReceive(dev);
		}
	} else if(dwIsReceiveDone(dev) && dev->handleReceived != 0) {
		(*dev->handleReceived)(dev);
		dwClearReceiveStatus(dev);
		if(dev->permanentReceive) {
			dwNewReceive(dev);
			dwStartReceive(dev);
		}
	}
	// clear all status that is left unhandled
	dwClearAllStatus(dev);
}

其中:


<span style="font-size:14px;">(*dev->handleSent)(dev)是处理发送,那因为之前注册回调函数</span><span style="font-size:14px;">txcallback,所以这里其实调用的就是main中的</span><span style="font-size:14px;">txcallback;</span>
<span style="font-size:14px;">dev->permanentReceive是处理接收,之前注册的回调函数是r</span><span style="font-size:14px;">xcallback</span><span style="font-size:14px;">,所以这里其实调用的就是main中的</span><span style="font-size:14px;">r</span><span style="font-size:14px;">xcallback。</span><span style="font-size:14px;">
</span><span style="font-size:14px;">
</span><span style="font-size:14px;">(8) 初始化dwm1000无线发送包头和局域网ID </span>
<span style="font-size:14px;"></span><pre name="code" class="cpp">// Initialize the packet in the TX buffer
  MAC80215_PACKET_INIT(txPacket, MAC802154_TYPE_DATA);
  txPacket.pan = 0xbccf;

(9) 如果角色为基站anchor或者低功耗sniffer(目前sniffer没有使用),dwm1000直接进入接收状态,之后的sniffer先不做分析。


<span style="font-size:14px;"></span><pre name="code" class="cpp">  if (mode == modeAnchor || mode == modeSniffer) {
    dwNewReceive(dwm);
    dwSetDefaults(dwm);
    dwStartReceive(dwm);
  }

(10) 进入while(1)大循环,其中配置地址和角色的地方可以看到:


<span style="font-size:14px;"></span><pre name="code" class="cpp"> // Accepts serial commands
    if (HAL_UART_Receive(&huart1, (uint8_t*)&ch, 1, 0) == HAL_OK) {
      bool configChanged = false;
      if (ch >= '0' && ch <= '9') {
        printf("Updating address from 0x%02X to 0x%02X\r\n", address[0], ch - '0');
        cfgWriteU8(cfgAddress, ch - '0');
        if (cfgReadU8(cfgAddress, &address[0])) {
          printf("Device address: 0x%X\r\n", address[0]);
        } else {
          printf("Device address: Not found!\r\n");
        }
        configChanged = true;
      }
      if (ch == 'A' || ch == 'T' || ch == 'S' || ch == 'a' || ch == 't' || ch == 's') {
        // Print current mode
        if (cfgReadU8(cfgMode, &mode)) {
          printf("Previous device mode: ");
          switch (mode) {
            case modeAnchor: printf("Anchor\r\n"); break;
            case modeTag: printf("Tag\r\n"); break;
            case modeSniffer: printf("Sniffer\r\n"); break;
            default: printf("UNKNOWN\r\n"); break;
          }
        } else {
          printf("Previous device mode: Not found!\r\n");
        }

        // Write new changes
        switch (ch) {
          case 'A': case 'a': cfgWriteU8(cfgMode, modeAnchor); break;
          case 'T': case 't': cfgWriteU8(cfgMode, modeTag); break;
          case 'S': case 's': cfgWriteU8(cfgMode, modeSniffer); break;
        }

        // Print out to verify
        if (cfgReadU8(cfgMode, &mode)) {
          printf("New device mode: ");
          switch (mode) {
            case modeAnchor: printf("Anchor\r\n"); break;
            case modeTag: printf("Tag\r\n"); break;
            case modeSniffer: printf("Sniffer\r\n"); break;
            default: printf("UNKNOWN\r\n"); break;
          }
        } else {
          printf("New device mode: Not found!\r\n");
        }
        configChanged = true;
      }

      if (ch == 'D' || ch == 'd') {
        configChanged = true;
        printf("Resetting EEPROM configuration...");
        if (cfgReset())
          printf("OK\r\n");
        else
          printf("ERROR\r\n");
      }
      if (configChanged) {
        printf("EEPROM configuration changed, restart for it to take effect!\r\n");
      }

    }

(11) 如果角色是标签Tag,需要主动发送数据给基站进行测距


<span style="font-size:14px;"></span><pre name="code" class="cpp">if (mode == modeTag) {
       for (i=0; i<anchorListSize; i++) {
         printf ("Interrogating anchor %d\r\n", anchors[i]);
         base_address[0] = anchors[i];
         dwIdle(dwm);

         txPacket.payload[TYPE] = POLL;
         txPacket.payload[SEQ] = ++curr_seq;

         memcpy(txPacket.sourceAddress, address, 8);
         memcpy(txPacket.destAddress, base_address, 8);

         dwNewTransmit(dwm);
         dwSetDefaults(dwm);
         dwSetData(dwm, (uint8_t*)&txPacket, MAC802154_HEADER_LENGTH+2);

         dwWaitForResponse(dwm, true);
         dwStartTransmit(dwm);

         HAL_Delay(30);
       }
     }
<span style="font-size:14px;">测距的方法是根据TWR方式做的,需要标签Tag和基站之间交互数据计算时间差来最后得出距离,具体方法可以参考:</span>
<span style="font-size:14px;">aps013_dw1000_and_two_way_ranging.pdf这篇文档,另外</span>注意到这里发送的第一个数据类型是:poll

<span style="font-size:14px;">txPacket.payload[TYPE] = POLL;</span>
<span style="font-size:14px;">单纯看程序有很多时间节点,会有点晕,所以这里画出时间轴,分别标出标签tag和基站anchor获取的时间戳:</span>
<span style="font-size:14px;"><img src="http://img.blog.csdn.net/20160415123728779" alt="" />

最后基站anchor会发送report信息给标签tag,那report中包含的信息有基站这边前面所记录的poll_rx, answer_tx和final_rx,这样再结合标签tag之前获取的</span><span style="font-size:14px;">poll_tx, answer_rx和final_tx,然后参考:</span><span style="font-size:14px;">dw1000_user_manual_2.06.pdf 附录3</span><span style="font-size:14px;">: Two-Way Ranging的介绍12.3.2  Using three messages这种方法,得到飞行时间信息:</span>
<span style="font-size:14px;"><img src="http://img.blog.csdn.net/20160415124305255" alt="" />
</span>
<span style="font-size:14px;">也就是程序中:</span>
<span style="font-size:14px;"></span><pre name="code" class="cpp">tprop_ctn = ((tround1*tround2) - (treply1*treply2)) / (tround1 + tround2 + treply1 + treply2);

之后再乘以光速C,得到距离信息distance:


<pre name="code" class="cpp" style="font-size:14px;">tprop = tprop_ctn/tsfreq;
      distance = C * tprop;

      printf("distance %d: %5dmm\r\n", rxPacket.sourceAddress[0], (unsigned int)(distance*1000));

注意printf会打印到串口1距离信息,所以标签Tag这边需要接串口到PC获取距离。


<span style="font-size:14px;">以上是程序的大致流程,感谢大家的大力支持。</span>
<span style="font-size:14px;">

</span>
<span style="font-size:14px;">

</span>
<span style="font-size:14px;">

</span>
<span style="font-size:14px;">
</span>
<span style="font-size:14px;">
</span>
时间: 2024-12-18 01:33:04

DWM1000DISCOVERY TWR方式定位程序分析的相关文章

C++异常机制的实现方式和开销分析 (大图,编译器会为每个函数增加EHDL结构,组成一个单向链表,非常著名的“内存访问违例”出错对话框就是该机制的一种体现)

白杨 http://baiy.cn 在我几年前开始写<C++编码规范与指导>一文时,就已经规划着要加入这样一篇讨论 C++ 异常机制的文章了.没想到时隔几年以后才有机会把这个尾巴补完 :-). 还是那句开场白:“在恰当的场合使用恰当的特性” 对每个称职的 C++ 程序员来说都是一个基本标准.想要做到这点,就必须要了解语言中每个特性的实现方式及其时空开销.异常处理由于涉及大量底层内容,向来是 C++ 各种高级机制中较难理解和透彻掌握的部分.本文将在尽量少引入底层细节的前提下,讨论 C++ 中这一

Linux程序分析工具:ldd和nm

ldd和nm是Linux下两个非常实用的程序分析工具.其中,ldd是用来分析程序运行时需要依赖的动态链接库的工具,nm是用来查看指定程序中的符号表信息的工具. 1 ldd 格式:ldd [options] file    功能:列出file运行所需的共享库 参数: -d    执行重定位并报告所有丢失的函数 -r    执行对函数和对象的重定位并报告丢失的任何函数或对象 首先,ldd不是一个可执行程序,而是一个shell脚本.ldd能够显示可执行模块的dependency,其原理是通过设置一系列

UWP中新加的数据绑定方式x:Bind分析总结

UWP中新加的数据绑定方式x:Bind分析总结 0x00 UWP中的x:Bind 由之前有过WPF开发经验,所以在学习UWP的时候直接省略了XAML.数据绑定等几个看着十分眼熟的主题.学习过程中倒是也没遇到麻烦.直到在园子里看到了这篇文章: http://www.cnblogs.com/gaoshang212/p/4534138.html 原来UWP的绑定中新加了个x:Bind,从文章中可以看到x:Bind的效率是很高的.找到MSDN(数据绑定)看了一下(完整的学习目录可参见: http://w

ELF格式的重定位原理分析

前面有篇文章分析了ELF格式,也只是让我们对目标文件有了一个大概的了解,并没有说明一个十分重要的问题:重定位,今天重新看了下重定位的资料,终于弄懂了重定位的过程,下面来做一个分析. 我们将使用下面两个源代码中的文件a.c和b.c展开分析: //a.c extern int shared; int main() { int a=100; swap(&a,&shared); } //b.c int shared=1; void swap(int *a,int *b) { *a^=*b^=*a^

基于时间片轮转程序分析进程调度

张雨梅   原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10000 背景知识 一般程序运行过程中都会发生中断,发生中断时,CPU先把当前的内容保存,然后执行中断程序,中断返回时,根据保存的内容恢复现场.这次实验用一个简单的时间片轮转程序分析进程调度的过程. 实验过程 使用实验楼的虚拟机操作,实验代码在mykernel中找,包括3个c文件,mypcb.h,mymain.c,myinterrupt.c. 打开

linux程序分析工具介绍(一)—-”/proc”

写在最前面:在开始本文之前,笔者认为先有必要介绍一下linux下的man,如果读者手头用linux系统,直接在终端输入man man便可以看到详细的说明,我在这里简单的总结一下,man命令是用来查看linux下各种命令.工具等的用户手册(manual)的.一种比较常用的用法是"man n field",这里的n是要查找的手册了类型,field是关键字.在这里介绍一下n: 0 /usr/include下的头文件 1 可执行程序和shell命令 2 系统调用 3 系统库函数 4 /dev下

linux程序分析工具介绍(二)—-ldd,nm

本文要介绍的ldd和nm是linux下,两个用来分析程序很实用的工具.ldd是用来分析程序运行时需要依赖的动态库的工具:nm是用来查看指定程序中的符号表相关内容的工具.下面通过例子,分别来介绍一下这两个工具: 1. ldd, 先看下面的例子, 用ldd查看cs程序所依赖的动态库: [email protected]:~/Public$ ldd cs linux-gate.so.1 => (0xffffe000) libz.so.1 => /lib/libz.so.1 (0xb7f8c000)

数据导入HBase最常用的三种方式及实践分析

数据导入HBase最常用的三种方式及实践分析         摘要:要使用Hadoop,需要将现有的各种类型的数据库或数据文件中的数据导入HBase.一般而言,有三种常见方式:使用HBase的API中的Put方法,使用HBase 的bulk load工具和使用定制的MapReduce Job方式.本文均有详细描述. [编者按]要使用Hadoop,数据合并至关重要,HBase应用甚广.一般而言,需要 针对不同情景模式将现有的各种类型的数据库或数据文件中的数据转入至HBase 中.常见方式为:使用H

关于“无法定位程序输入点gzdirect于动态链接库zlib1.dll”的问题

费劲N多力气编译通过之后,最后启动程序过程中却突然得到“无法定位程序输入点gzdirect于动态链接库zlib1.dll”的问题, 分析究其原因是定位不到zlib1.dll,都知道,程序在找dll的时候如果工程目录中没有指定,会到$HOME/Windows/System32这个文件夹下面寻找需要的dll文件, 那么,就去System32文件夹下面看看是不是存在zlib1.dll这个文件,我翻看我的文件夹,发现没有这个文件,于是下载zlib1.dll拷贝到文件夹下.如果存在该文件, 还是出现上面那