STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)

*资源下载

一个基站和两个标签实现官方twr测距例程下载链接:一基站两标签测距例程下载
官方dwm1000模块例程下载链接:官方源码下载链接

1.本篇简介

STM32+DWM1000开发uwb测距系列教程之一
上一篇 文章主要简单介绍了一下官方最新示例代码的打开和基本工程目录结构。
本篇在前一篇的基础上,进行工程移植,移植思路是,首先保持官方口味不变,因为官方代码应该是正常可用的,另外例程丰富;其次是尽可能使用cube mx,在不考虑程序执行效率的外加因素的情况下,stm32开发,使用cubemx 是最快捷方便的,并且出错概率也是最小的,当然stm32相关的手册是必须放在手边的,有备无患(事实证明,确实是这样)。

2 移植前规划

官方使用的stm32芯片是stm32f105xc的芯片,本次使用的芯片是stm32f103rbt6和GD32F103RET6,GD32的片子暂且当STM32来用(事实证明,这样没有任何问题)。
官方主要使用了USB CDC接口作为和上位机通信的接口,在这里使用UART1接口代替,作为程序调试的printf输出使用;
官方使用了LCD,在这里,使用oled128*64,接口为SPI接口,使用stm32的spi2
官方dwm1000和stm32通信主要使用了spi1,在这里保持一致。
另外官方demo中dwm1000执行过程中使用到了us级和ms级延时,这里为了保证后期扩展/增加rtos的可能性,systick时钟保持默认。dwm相关延时操作使用timer4来进行。
总结以上,需要进行的操作有

序号功能官方代码移植
1printf删掉USB部分硬件配置UART1,并对printf进行重定向
2输出显示删除原来LCD部分代码增加OLED接口对应spi硬件初始化及OLED初始化、显示灯API
3dwm1000修改硬件不通接口部分在这里,尽量给原程序保持硬件接口一致。但因为身边开发板的原因,不得已部分引脚不兼容,做了修改。
4其它暂无暂无

dwm1000与STM32连接硬件接口差异表:

序号stm32f105dwm1000接口stm32f103rbt6
1PB5IRQnPA1
2PB0WAKEUPPC4
3PA0RESETPA3
4PA4NSSPA4
5PA5SCKPA5
6PA6MISOPA6
7PA7MOSIPA7

STM32F105官方demo引脚分配
移植后对应引脚分配

3 使用stm32 cubemx生成硬件初始化工程

*注**意:*这里边涉及到dwm1000接口,io引脚名称跟原工程保持一致,这样可以减少大量的后期程序移植修改工作
按照上边的接口,完成基本的硬件配置,这里主要有时钟、SWD JTAG接口、systick使能系统滴答时钟、UART1的初始化、timer4的初始化(为了实现硬件定时方便,这里使用了LL库),SPI1和SPI2的初始化(两者时钟极性和相位根据dwm1000和oled进行不同设置)。
spi1(dwm1000)的初始化(不使用中断):
dwm1000 spi接口初始化
spi2(OLED)的初始化:
oled spi接口初始化
其余gpio初始化:
gpio初始化
其中,led1、led2、led3接口为完结led,key为用户按键,保留。
timer4定时器初始化(不使用中断)
定时器初始化
因为要使用us级延时,所以,系统时钟72Mhz,选择了9分频,1us计数值为8,ARR值等于8000的话,正好1ms。这里要兼顾us级延时的精度和最大延时的时间,ARR为16位,最大值0xFFFF。
可以根据延时值去动态修改预分频和计数周期ARR的值,这样既保证了延时精度有能时间ms级延时时间长度。
工程设置1
工程设置2
工程设置3
主要的细节如上,设置完成后,就可以单击“GENERATE CODE”生成MDK工程代码。

4 打开工程并添加官方驱动库

打开上边生成的工程目录,复制原工程下的compiler、decadriver、platform文件夹,粘贴至目标工程的Drivers文件夹下,另外复制OLED相关驱动及API接口文件至oled_driver文件夹下。复制原工程下的examples文件夹粘贴至目标工程的core文件夹下。
在这里插入图片描述
在这里插入图片描述
最后的工程目录结构如下:
在这里插入图片描述
在keil中,右键单击工程名称,选择“manage project items”进行分组管理,添加如下的分组
分组信息
并依次给每一个分组添加对应文件夹下的c文件。
oled见上图
decadriver
platform
compilier
上图可加可不加。
example
example分组下可以选择一个或者多个*****_main.c(这个文件为每一个example的主要代码文件)的文件,实际使用只用一个,后边详解。

5 include文件路径添加

添加相关头文件路径

6 精确延时函数实现

这一步非必须,只要实现us级延时和ms级延时就可以了。
分别在src文件夹下和inc文件夹下新建delay.c文件和delay.h文件,文件代码如下:
delay.c:

#include "delay.h"

static uint8_t   fac_us = 8; //us延时倍乘数
static uint16_t fac_ms = 8000; //ms延时倍乘数

/*******************************************************************************
** 函数名称: delay_init
** 功能描述: 延时初始化程序,暂时未使用,使用cubeMX生成的通用定时器初始化函数
********************************************************************************/
void delay_init()
{
   
}

/*******************************************************************************
** 函数名称: delay
** 功能描述: 用于通用定时器延时时间函数,传入参数为定时器预装值,定时器向上计数
** 参数说明: delay_t: [输入/出]
** 返回说明: None
** 创建人员: wht
** 创建日期: 2020-05-04
********************************************************************************/
static void delay(uint16_t delay_t)
{
    uint16_t temp;
    TIM4->ARR = delay_t; //时间加载
    TIM4->CNT = 0;       //清空计数器
    TIM4->SR = 0;      //清空状态
    TIM4->CR1 = TIM_CR1_CEN; //使能定时器
    do
    {
        temp = TIM4->SR ;
    }
    while(!(temp & LL_TIM_SR_UIF)); //等待时间到达
    TIM4->CR1 &= ~TIM_CR1_CEN;     //关闭
    TIM4->CNT = 0X00;       //清空
}
/*******************************************************************************
** 函数名称: delay_us
** 功能描述: 延时us函数,延时小于等于8191(系统72MHZ时钟,9分频,1us计数值为8)
** 参数说明: nus: [输入/出]
** 返回说明: None
** 创建人员: wht
** 创建日期: 2020-05-04
********************************************************************************/
void delay_us(uint16_t nus)
{
    delay(nus * fac_us);
}

/*******************************************************************************
** 函数名称: delay_ms
** 功能描述: 延时ms函数,延时小于等于8ms时使用通用定时器,大于8ms时使用systick
** 参数说明: nms: [输入/出]
** 返回说明: None
** 创建人员: wht
** 创建日期: 2020-05-04
********************************************************************************/
void delay_ms(uint16_t nms)
{
    if(nms > 8)
        HAL_Delay(nms);
    else
    {
        delay(nms * fac_ms);
    }
}
/********************************End of File************************************/

delay.h:

 
#ifndef __DELAY_H_
#define __DELAY_H_
			   
#include "main.h"

void delay_init(void);
void delay_ms(uint16_t nms);
void delay_us(uint16_t nus);

#endif
 
/********************************End of File************************************/

7 dwb接口函数修改

7.1 deca_spi.c

主要实现writetospi()函数和readfromspi()函数,同时修改局部优化等级指令。使用的都是HAL的库函数,主要实现了stm32和dwm1000的spi接口读写数据通信接口函数,比较容易修改,再此不表。

7.2 port.c

7.2.1 portGetTickCnt()

保持原样,在port_wakeup_dw1000_fast()函数中有用到。获取的是systick中断里更新的uwTick值。

7.2.2 usleep()延时函数
#pragma -O0
int usleep(uint32_t usec)
{
	delay_us(usec);
    return 0;
}

这里其实需要注意数据越界问题,或者统一函数入口参数

7.2.3 Sleep()延时函数
__INLINE void  Sleep(uint32_t x)
{
    HAL_Delay(x);
}

这里同上,需要注意数据越界问题,或者统一函数入口参数

7.2.4 reset_DW1000()函数实现

几乎不需要改动

7.2.5 setup_DW1000RSTnIRQ() 设置复位引脚工作模式

在main.h中添加如下宏定义,

#define DW_RESET_EXTI_IRQn EXTI3_IRQn

修改setup_DW1000RSTnIRQ()为:

void setup_DW1000RSTnIRQ(int enable)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if(enable) /* 1 中断模式*/
    {
        // Enable GPIO used as DECA RESET for interrupt
        GPIO_InitStruct.Pin = DW_RESET_Pin;
        GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(DW_RESET_GPIO_Port, &GPIO_InitStruct);

        HAL_NVIC_EnableIRQ(DW_RESET_EXTI_IRQn);     //pin #0 -> EXTI #0
        HAL_NVIC_SetPriority(DW_RESET_EXTI_IRQn, 3, 0);
    }
    else     /* 0 gpio模式*/
    {
        HAL_NVIC_DisableIRQ(DW_IRQn_EXTI_IRQn);    //pin #0 -> EXTI #0

        //put the pin back to tri-state ... as
        //output open-drain (not active)
        GPIO_InitStruct.Pin = DW_RESET_Pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(DW_RESET_GPIO_Port, &GPIO_InitStruct);
        HAL_GPIO_WritePin(DW_RESET_GPIO_Port, DW_RESET_Pin, GPIO_PIN_SET);
    }
}
7.2.6 port_wakeup_dw1000_fast()函数,不需要修改
7.2.7 添加对应的example c文件到工程,并添加相应的宏定义

原工程examples文件夹下,每一个示例程序都在一个子文件夹下,本例中借ex_05b_main.c来进行使用说明。
添加ex_05b_main.c到工程中,然后点魔术棒,打开工程的options选项,在C/C++一栏的define中输入EX_05B_DEF 。进行预定义,因为整个x_05b_main.c文件内的内容都在如下的宏定义范围内。

#ifdef EX_05B_DEF
...
...
...
#endif

在main.c中main()函数之前加入如下语句:

extern int dw_main ( void ); //声明外部函数引用

在main()中的while(1)语句上方调用dw_main()函数。

7.2.8 其它工作

移植部分告一段落,剩下的工作就是要把原工程中未使用的函数,比如usb部分的代码删掉、lcd部分的代码,需要在输出显示的地方使用oled相关函数替换或者使用printf来输出到上位机显示,剩下的代码删掉。以及未使用的led接口函数、按键接口函数等删掉或者暂时注释掉即可。

8 代码分析及调试

int dw_main(void)
{
    /* Display application name on LCD. */
    printf(APP_NAME);
		printf("\r\n");
	
    dwt_spicswakeup ( dummy_buffer, DUMMY_BUFFER_LEN );	
	

    /* Reset and initialise DW1000.
     * For initialisation, DW1000 clocks must be temporarily set to crystal speed. After initialisation SPI rate can be increased for optimum
     * performance. */
    reset_DW1000(); /* Target specific drive of RSTn line into DW1000 low for a period. */
    port_set_dw1000_slowrate();
    if (dwt_initialise(DWT_LOADUCODE) == DWT_ERROR)
    {
        printf("INIT FAILED\r\n");
        while (1)
        { };
    }
    port_set_dw1000_fastrate();

每一个示例源文件中,dw_main()函数的前几行都是基本一样的,同时每一条指令都有英文解释,比较容易理解。dwt_initialise()中调用了dwt_readdevid()来获取dwm1000的ID号,为uint32位整数,可以使用这个函数来判断stm32和dwm1000通讯是否正常,

	dwt_spicswakeup ( dummy_buffer, DUMMY_BUFFER_LEN );	
    reset_DW1000(); /* Target specific drive of RSTn line into DW1000 low for a period. */
    port_set_dw1000_slowrate();
    uint32  dwt_ID=0;
    dwt_ID = dwt_readdevid();
    printf("ID:%lx\r\n",dwt_ID);

如果上位机收到的ID等于0xDECA0130,那么恭喜你,万里长征一大步就完成了,剩下的就愉快的写代码吧。

  • 18
    点赞
  • 167
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答1: dwm1000是一种高精度的宽带无线电位置估计模块。dwm1000在诸多应用领域都有所应用,例如室内定位,自动驾驶,工业自动化等。而dwm1000stm32驱动库是用于驱动dwm1000模块的一种软件库,它可以让用户更加便捷地使用dwm1000模块。 dwm1000stm32驱动库的具体功能包括dwm1000模块初始化,发送和接收数据帧,解析数据帧等。同时,dwm1000stm32驱动库还可以与STM32系列单片机进行无缝连接,实现可靠的数据传输。此外,dwm1000stm32驱动库还支持基于“time-of-flight”技术的实时定位信息传输,这一技术使得dwm1000模块在室内定位应用中具有更高的准确度和稳定性。 总体来说,dwm1000stm32驱动库为用户提供了高效且方便的使用dwm1000模块的软件工具,大大简化了dwm1000模块在应用中的开发和应用过程,推动了宽带无线电位置估计技术的发展。 ### 回答2: dwm1000stm32驱动库是一种软件组件,用于在STM32单片机上控制DWM1000无线定位芯片。它提供了一些API函数,可以帮助开发者快速启动和配置DWM1000芯片,实现距离测量、位置定位、数据传输等功能。通过使用这个驱动库,可以大大减少开发时间和工作量,提高产品的稳定性和可靠性。 该驱动库具有以下特点: 1.易于使用。驱动库提供了简单易用的API函数,使得开发人员可以快速和容易地控制DWM1000芯片,并实现各种功能。 2.高性能。驱动库支持STM32单片机的硬件加速功能,可以提高DWM1000芯片的性能和精度。 3.可扩展性。开发人员可以根据自己的需要修改和扩展该驱动库,以适应不同的应用场景。 总之,dwm1000stm32驱动库是一个非常有用的软件工具,可以帮助开发者更好地掌控无线定位芯片,大大提高开发效率和产品的性能和稳定性。 ### 回答3: DWM1000是一种基于Ultra Wide Band(超宽带)技术的定位模块,能够实现高度准确的无线定位。而dwm1000stm32驱动库则是一种用于控制和使用DWM1000模块的软件库。dwm1000stm32驱动库适用于基于STM32芯片的单片机系统,用户可以通过调用其提供的接口函数来实现与DWM1000模块之间的通讯和数据传输。 dwm1000stm32驱动库的主要功能包括配置DWM1000模块的寄存器、发送和接收数据包、计算传输距离、调整模块的工作频率等。使用该驱动库可以省去用户自行编写通讯协议和底层控制代码的繁琐过程,同时提高开发效率和准确性。此外,dwm1000stm32驱动库还提供了丰富的示例程序和文档支持,方便用户快速上手和开发。 总的来说,dwm1000stm32驱动库是一种非常实用和便捷的工具,可用于快速开发基于DWM1000模块的定位应用。通过使用该驱动库,用户可以更加专注于应用逻辑的开发,提高开发效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值