目录
*资源下载
一个基站和两个标签实现官方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来进行。
总结以上,需要进行的操作有
序号 | 功能 | 官方代码 | 移植 |
---|---|---|---|
1 | printf | 删掉USB部分 | 硬件配置UART1,并对printf进行重定向 |
2 | 输出显示 | 删除原来LCD部分代码 | 增加OLED接口对应spi硬件初始化及OLED初始化、显示灯API |
3 | dwm1000 | 修改硬件不通接口部分 | 在这里,尽量给原程序保持硬件接口一致。但因为身边开发板的原因,不得已部分引脚不兼容,做了修改。 |
4 | 其它 | 暂无 | 暂无 |
dwm1000与STM32连接硬件接口差异表:
序号 | stm32f105 | dwm1000接口 | stm32f103rbt6 |
---|---|---|---|
1 | PB5 | IRQn | PA1 |
2 | PB0 | WAKEUP | PC4 |
3 | PA0 | RESET | PA3 |
4 | PA4 | NSS | PA4 |
5 | PA5 | SCK | PA5 |
6 | PA6 | MISO | PA6 |
7 | PA7 | MOSI | PA7 |
3 使用stm32 cubemx生成硬件初始化工程
*注**意:*这里边涉及到dwm1000接口,io引脚名称跟原工程保持一致,这样可以减少大量的后期程序移植修改工作
按照上边的接口,完成基本的硬件配置,这里主要有时钟、SWD JTAG接口、systick使能系统滴答时钟、UART1的初始化、timer4的初始化(为了实现硬件定时方便,这里使用了LL库),SPI1和SPI2的初始化(两者时钟极性和相位根据dwm1000和oled进行不同设置)。
spi1(dwm1000)的初始化(不使用中断):
spi2(OLED)的初始化:
其余gpio初始化:
其中,led1、led2、led3接口为完结led,key为用户按键,保留。
timer4定时器初始化(不使用中断)
因为要使用us级延时,所以,系统时钟72Mhz,选择了9分频,1us计数值为8,ARR值等于8000的话,正好1ms。这里要兼顾us级延时的精度和最大延时的时间,ARR为16位,最大值0xFFFF。
可以根据延时值去动态修改预分频和计数周期ARR的值,这样既保证了延时精度有能时间ms级延时时间长度。
主要的细节如上,设置完成后,就可以单击“GENERATE CODE”生成MDK工程代码。
4 打开工程并添加官方驱动库
打开上边生成的工程目录,复制原工程下的compiler、decadriver、platform文件夹,粘贴至目标工程的Drivers文件夹下,另外复制OLED相关驱动及API接口文件至oled_driver文件夹下。复制原工程下的examples文件夹粘贴至目标工程的core文件夹下。
最后的工程目录结构如下:
在keil中,右键单击工程名称,选择“manage project items”进行分组管理,添加如下的分组
并依次给每一个分组添加对应文件夹下的c文件。
oled见上图
上图可加可不加。
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,那么恭喜你,万里长征一大步就完成了,剩下的就愉快的写代码吧。