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

目录

    • 1.本篇简介
    • 2 移植前规划
    • 3 使用stm32 cubemx生成硬件初始化工程
    • 4 打开工程并添加官方驱动库
    • 5 include文件路径添加
    • 6 精确延时函数实现
    • 7 dwb接口函数修改
      • 7.1 deca_spi.c
      • 7.2 port.c
        • 7.2.1 portGetTickCnt()
        • 7.2.2 usleep()延时函数
        • 7.2.3 Sleep()延时函数
        • 7.2.4 reset_DW1000()函数实现
        • 7.2.5 setup_DW1000RSTnIRQ() 设置复位引脚工作模式
        • 7.2.6 port_wakeup_dw1000_fast()函数,不需要修改
        • 7.2.7 添加对应的example c文件到工程,并添加相应的宏定义
        • 7.2.8 其它工作
    • 8 代码分析及调试

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

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

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

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

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

打开上边生成的工程目录,复制原工程下的compiler、decadriver、platform文件夹,粘贴至目标工程的Drivers文件夹下,另外复制OLED相关驱动及API接口文件至oled_driver文件夹下。复制原工程下的examples文件夹粘贴至目标工程的core文件夹下。
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第10张图片
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第11张图片
最后的工程目录结构如下:
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第12张图片
在keil中,右键单击工程名称,选择“manage project items”进行分组管理,添加如下的分组
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第13张图片
并依次给每一个分组添加对应文件夹下的c文件。
oled见上图
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第14张图片
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第15张图片
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第16张图片
上图可加可不加。
STM32+DWM1000开发uwb测距系列教程之二:源码分析及源码移植(基于STM32 cubemx+keil MDK)_第17张图片
example分组下可以选择一个或者多个*****_main.c(这个文件为每一个example的主要代码文件)的文件,实际使用只用一个,后边详解。

5 include文件路径添加

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

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,那么恭喜你,万里长征一大步就完成了,剩下的就愉快的写代码吧。

你可能感兴趣的:(dwm000)