系列文章 一、官方驱动

Decawave官方提供了DW1000的芯片驱动库,整体框架如下图:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define

二、移植官方驱动

作者在本文中编写移植的驱动开源地址:HAL_Driver_Lib。直接添加到STM32CubeMX生成的工程中即可,若好用,记得github点亮星星呀~

1. 复制文件

在工程目录中新建 Hardware/DW1000,复制官方驱动中的两个文件夹到该文件夹中,如图:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define_02

2. 添加文件到MDK工程中

添加在 Hardware\DW1000\decadriver 中的驱动文件:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_STM32CubeMX_03
添加在 Hardware\DW1000\platform 中的平台相关文件(注意是部分文件):
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_HAL_04

添加头文件路径:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_STM32CubeMX_05

3. 移植分析

DW1000官方驱动中的文件主要有两部分:

① 一部分是decadriver文件夹之中的,这部分是DW1000中的寄存器定义及各种操作,与平台无关,属于通用文件,无需进行大的修改;
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_STM32CubeMX_06

② 另一部分是platform文件夹,负责适配DW1000官方驱动的底层通信接口,这部分需要重点修改,其中比较重要的是图中所圈出的几个文件:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_DW1000_07
这几个文件的作用及实现方法是:

  • deca_mutex.c:提供平台相关的互斥锁操作适配实现,裸机中用开关中断实现,RTOS中用互斥量实现;
  • deca_slepp.c:提供平台相关的延时函数实现,裸机中用HAL_Delay,RTOS中用系统提供的延时函数;
  • deca_spi.c:提供平台相关的SPI通信函数实现,直接使用HAL库的API实现即可;

至于别的文件,都是和整个项目有关,包括板级外设初始化代码,但这不和DW1000有关,并且在HAL库中,这部分代码已经实现,放在这里实属糟糕的设计,不用理会即可。

不过在port.c中,倒是有可怜的几个和DW1000复位相关的函数,可以供我们参考。

关于deca_mutex部分,主要是保护SPI外设的访问。因为DW1000在用户程序(后台)会使用SPI外设接口通信,在中断程序中(前台)也会用SPI接口来查询寄存器,这就导致SPI外设操作冲突,所以需要在SPI的操作函数中提供上锁保护。

4. 编写DW1000驱动文件

新建dw1000.c和dw1000.h文件,这两个文件主要为了定义一些DW1000所使用的控制引脚,实现DW1000中断引脚所连接的EXTI功能初始化,DW1000复位功能等:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_DW1000_08
添加文件到MDK中:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_初始化_09
添加头文件路径:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define_10
接着编写头文件内容,将DW1000所连接的控制引脚进行宏定义:

#ifndef _DW1000_H_
#define _DW1000_H_

#include "stm32l4xx_hal.h"

/*
	CS <-------------  PA4
	RST <------------- PB9
	WAKEUP <---------- PA8
*/
#define DW1000_CS_PORT			GPIOA
#define DW1000_CS_PIN			GPIO_PIN_4
#define DW1000_RST_PORT			GPIOB
#define DW1000_RST_PIN			GPIO_PIN_9
#define DW1000_WAKEUP_PORT		GPIOA
#define DW10000_WAKEUP_PIN		GPIO_PIN_8

/*
	IRQ --------------> PC9
*/
#define DW1000_IRQn_TYPE		EXTI9_5_IRQn
#define DW1000_IRQ_PORT		  	GPIOC
#define DW1000_IRQ_PIN			GPIO_PIN_9

/*
	SPI Interface <---> SPI1
	SPI_CS <----------> PA4
	SPI_CLK <---------> PA1
	SPI_MISO <--------> PA6
	SPI_MOSI <--------> PA12
*/
extern SPI_HandleTypeDef hspi1;
#define DW1000_SPI_Handle hspi1

void reset_DW1000(void);
void spi_set_rate_low(void);
void spi_set_rate_high(void);

#endif /* _DW1000_H_ */

5. 驱动底层适配

接下来我们挨个适配刚刚提到的三个适配文件。

① deca_mutex.c:

首先将port.h更换为dw1000.h:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_HAL_11
接着适配完成两个互斥锁函数:

decaIrqStatus_t decamutexon(void)           
{
	decaIrqStatus_t s = __HAL_GPIO_EXTI_GET_FLAG(DW1000_IRQ_PIN);
	
	if(s) {
		HAL_NVIC_DisableIRQ(DW1000_IRQn_TYPE);	 //disable the external interrupt line
	}
	return s ;   // return state before disable, value is used to re-enable in decamutexoff call
}

void decamutexoff(decaIrqStatus_t s)        // put a function here that re-enables the interrupt at the end of the critical section
{
	if(s) { //need to check the port state as we can't use level sensitive interrupt on the STM ARM
		HAL_NVIC_EnableIRQ(DW1000_IRQn_TYPE);
	}
}

② deca_slepp.c:

先将头文件替换,然后使用HAL_Delay实现:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_HAL_12
③ deca_spi.c:

先将头文件替换:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_初始化_13
然后实现四个SPI操作接口,因为HAL库工程中,SPI外设的初始化是放在main函数中的,所以open和close接口实现可以留空:

int openspi(/*SPI_TypeDef* SPIx*/)
{
	// done by main.c, default SPI used is SPI1
	
	return 0;

} // end openspi()

int closespi(void)
{

	return 0;

} // end closespi()

重点实现SPI写数据接口

int writetospi(uint16 headerLength, const uint8 *headerBuffer, uint32 bodylength, const uint8 *bodyBuffer)
{
	HAL_StatusTypeDef status;
    decaIrqStatus_t  stat;

    stat = decamutexon() ;

	/* Enable DW1000_CS */
	HAL_GPIO_WritePin(DW1000_CS_PORT, DW1000_CS_PIN, GPIO_PIN_RESET);

	/* Send Header */
	status = HAL_SPI_Transmit(&DW1000_SPI_Handle, (uint8_t *)headerBuffer, headerLength, 0xFFFF);
	if (status != HAL_OK) {
		return -1;
	}

	/* Send Body */
	status = HAL_SPI_Transmit(&DW1000_SPI_Handle, (uint8_t *)bodyBuffer, bodylength, 0xFFFF);
	if (status != HAL_OK) {
		return -1;
	}
	
	/* Disable DW1000_CS */
    HAL_GPIO_WritePin(DW1000_CS_PORT, DW1000_CS_PIN, GPIO_PIN_SET);

    decamutexoff(stat) ;

    return 0;
} // end writetospi()

重点实现SPI读数据接口:

int readfromspi(uint16 headerLength, const uint8 *headerBuffer, uint32 readlength, uint8 *readBuffer)
{

	HAL_StatusTypeDef status;
    decaIrqStatus_t  stat;

    stat = decamutexon() ;

	/* Enable DW1000_CS */
	HAL_GPIO_WritePin(DW1000_CS_PORT, DW1000_CS_PIN, GPIO_PIN_RESET);

	/* Send Header */
	status = HAL_SPI_Transmit(&DW1000_SPI_Handle, (uint8_t *)headerBuffer, headerLength, 0xFFFF);
	if (status != HAL_OK) {
		return -1;
	}

	/* Receive Body */
	status = HAL_SPI_Receive(&DW1000_SPI_Handle, (uint8_t *)readBuffer, readlength, 0xFFFF);
	if (status != HAL_OK) {
		return -1;
	}
	
	/* Disable DW1000_CS */
    HAL_GPIO_WritePin(DW1000_CS_PORT, DW1000_CS_PIN, GPIO_PIN_SET);

    decamutexoff(stat) ;

    return 0;
} // end readfromspi()

至此,官方驱动移植完成。

三、编写额外的操作函数

为了后面方便移植官方驱动中给出的example示例代码,我们参考port.c,在DW1000.c中实现下面两个函数。

1. DW1000硬件复位函数

这个函数的作用是在上电的时候,将DW1000 RST引脚拉低,将DW1000进行硬件复位,然后设置为输入高阻态即可:

void reset_DW1000(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};

	// Enable GPIO used for DW1000 reset
	// done by main.c(MX_GPIO_Init) 

	//drive the RSTn pin low
	HAL_GPIO_WritePin(DW1000_RST_PORT, DW1000_RST_PIN, GPIO_PIN_RESET);
	
	//put the pin back to tri-state ... as input
	GPIO_InitStruct.Pin = DW1000_RST_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(DW1000_RST_PORT, &GPIO_InitStruct);

  	HAL_Delay(2);
}

2. SPI外设速率更改函数

DW1000在刚上电进行初始化时,SPI需要使用最大为3Mbps的速率进行通信;在稳定之后(初始化之后),SPI可以设置为最大20Mbps的速率进行通信。

static void SPI_ChangeRate(SPI_TypeDef *SPIx, uint16_t scalingfactor)
{
	uint16_t tmpreg = 0;

	/* Get the SPIx CR1 value */
	tmpreg = SPIx->CR1;

	/*clear the scaling bits*/
	tmpreg &= 0xFFC7;

	/*set the scaling bits*/
	tmpreg |= scalingfactor;

	/* Write to SPIx CR1 */
	SPIx->CR1 = tmpreg;
}

void spi_set_rate_low(void)
{
    SPI_ChangeRate(DW1000_SPI_Handle.Instance, SPI_BAUDRATEPRESCALER_32);
}

void spi_set_rate_high(void)
{
    SPI_ChangeRate(DW1000_SPI_Handle.Instance, SPI_BAUDRATEPRESCALER_4);
}

至此,DW1000官方驱动移植完成。

四、测试DW1000驱动

接下来我们参考官方驱动下example中的代码,在main.c中测试刚刚移植的DW1000驱动是否可以正常工作,为后面移植官方示例打下基础。

① 包含头文件:

#include "dw1000.h"
#include "deca_device_api.h"
#include "deca_regs.h"

② 定义配置信息(默认配置):

/* USER CODE BEGIN PV */
/* Default communication configuration. We use here EVK1000's default mode (mode 3). */
static dwt_config_t config = {
    2,               /* Channel number. */
    DWT_PRF_64M,     /* Pulse repetition frequency. */
    DWT_PLEN_1024,   /* Preamble length. Used in TX only. */
    DWT_PAC32,       /* Preamble acquisition chunk size. Used in RX only. */
    9,               /* TX preamble code. Used in TX only. */
    9,               /* RX preamble code. Used in RX only. */
    1,               /* 0 to use standard SFD, 1 to use non-standard SFD. */
    DWT_BR_110K,     /* Data rate. */
    DWT_PHRMODE_STD, /* PHY header mode. */
    (1025 + 64 - 32) /* SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. */
};
/* USER CODE END PV */

③ 编写初始化代码(测试代码):

/* USER CODE BEGIN 2 */
printf("DW1000 UWB ic port on BearPi board By Mculover666\r\n");

reset_DW1000();

spi_set_rate_low();
if (dwt_initialise(DWT_LOADNONE) == DWT_ERROR) {
		printf("DW1000 init fail\r\n");
		Error_Handler();
}
printf("DW1000 init success\r\n");

spi_set_rate_high();

/* Configure DW1000. */
dwt_configure(&config);
printf("DW1000 configure success\r\n");

 /* USER CODE END 2 */

接着编译、下载到开发板中,复位,在串口助手中查看打印日志:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_初始化_14
DW1000初始化并配置成功,驱动可以正常工作。

五、API使用分析

1. DW1000初始化API

DW1000初始化API的定义在deca_device_api.h中,实现在deca_device.c中,其原型如下:

int dwt_initialise(uint16 config) ;

从其注释中不难看出,该API主要完成的功能有:读取Device ID、验证是否为DW1000芯片,完成DW1000初始化工作。

设备ID是一个硬编码的常量,以保持示例简单,但对于真正的产品,每个设备都应该有一个唯一的ID。为了开发目的,可以通过将生产过程中编程到DW1000中的批号和零件号值组合在一起,生成DW1000唯一的ID。然而,不能保证这不会与别人的实现冲突。

其参数config有两个,对应都有宏定义:

宏定义 意义
DWT_LOADUCODE 从ROM中加载LDE微码
DWT_LOADNONE 不从OTP存储中加载任何值

在这个例子中,LDE微码不会在调用dwt_initialise()时加载,这将防止IC生成RX时间戳。如果需要时间戳,则应该使用DWT_LOADUCODE参数。参见双向测距示例(示例5a/5b)。

返回值也有两个:

返回值 意义
DWT_SUCCESS 初始化成功
DWT_ERROR 初始化错误

使用该API时需要注意,调用之前,SPI通信速率必须设置小于3Mhz。

2. UWB信号基础

参考FCC的定义,满足10 dB带宽(fH - fL) > 500 MHz或者分数带宽2*( fH - fL)/ (fH + fL) > 0.2的信号可以称为UWB超宽带信号,且信号的功率谱密度限制于-41.3 dBm/MHz。
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_STM32CubeMX_15
IEEE 802.15.4 – 2011 standard规定的UWB物理层帧结构如下:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_初始化_16

它由两部分组成,从包含前导码和SFD(帧分隔符的开始)的同步头开始,后面是第二部分,定义了数据载荷长度的PHY头(PHR),其后跟的就是数据。

这些原始数据经调制之后发射出去,调试后的频率支持110 kbps, 850 kbps和6.8 Mbps,具体的调制方式,有兴趣请阅读DW1000用户手册附录1(APPENDIX 1: The IEEE 802.15.4 UWB physical layer)。

3. DW1000配置API(重点)

DW1000配置API的定义在deca_device_api.h中,实现在deca_device.c中,其原型如下:

void dwt_configure(dwt_config_t *config) ;

它的参数是一个配置结构体指针,该配置结构体中定义了所有配置项,如下:

typedef struct
{
    uint8 chan ;           //!< channel number {1, 2, 3, 4, 5, 7 }
    uint8 prf ;            //!< Pulse Repetition Frequency {DWT_PRF_16M or DWT_PRF_64M}
    uint8 txPreambLength ; //!< DWT_PLEN_64..DWT_PLEN_4096
    uint8 rxPAC ;          //!< Acquisition Chunk Size (Relates to RX preamble length)
    uint8 txCode ;         //!< TX preamble code
    uint8 rxCode ;         //!< RX preamble code
    uint8 nsSFD ;          //!< Boolean should we use non-standard SFD for better performance
    uint8 dataRate ;       //!< Data Rate {DWT_BR_110K, DWT_BR_850K or DWT_BR_6M8}
    uint8 phrMode ;        //!< PHR mode {0x0 - standard DWT_PHRMODE_STD, 0x3 - extended frames DWT_PHRMODE_EXT}
    uint16 sfdTO ;         //!< SFD timeout value (in symbols)
} dwt_config_t ;

结构体中每项数据成员的意义如下:

数据成员 意义
chan 信道号(定义了中心频率和带宽)
prf 脉冲重复频率
txPreambLength 发送数据的前导码长度
rxPAC 接收数据所使用的前导获取块的大小
txCode 发送前导码
rxCode 接收前导码
nsSFD 是否使用非标准的SFD来获得更好的性能
dataRate 数据速率
phrMode PHR模式
sfdTO SFD超时时间

① 信道号和推荐前导码
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define_17
② prf 表示脉冲重复频率,支持以下值:

//! constants for specifying the (Nominal) mean Pulse Repetition Frequency
//! These are defined for direct write (with a shift if necessary) to CHAN_CTRL and TX_FCTRL regs
#define DWT_PRF_16M     1   //!< UWB PRF 16 MHz
#define DWT_PRF_64M     2   //!< UWB PRF 64 MHz

③ txPreambLength 指明了发送数据的前导序列长度,支持以下值:

//! constants for specifying TX Preamble length in symbols
//! These are defined to allow them be directly written into byte 2 of the TX_FCTRL register
//! (i.e. a four bit value destined for bits 20..18 but shifted left by 2 for byte alignment)
#define DWT_PLEN_4096   0x0C    //! Standard preamble length 4096 symbols
#define DWT_PLEN_2048   0x28    //! Non-standard preamble length 2048 symbols
#define DWT_PLEN_1536   0x18    //! Non-standard preamble length 1536 symbols
#define DWT_PLEN_1024   0x08    //! Standard preamble length 1024 symbols
#define DWT_PLEN_512    0x34    //! Non-standard preamble length 512 symbols
#define DWT_PLEN_256    0x24    //! Non-standard preamble length 256 symbols
#define DWT_PLEN_128    0x14    //! Non-standard preamble length 128 symbols
#define DWT_PLEN_64     0x04    //! Standard preamble length 64 symbols

根据数据速率推荐的前导序列长度如下表:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_初始化_18
④ rxPAC 参数指明了接收数据所使用的前导获取块的大小,有以下值:

//! constants for specifying Preamble Acquisition Chunk (PAC) Size in symbols
#define DWT_PAC8        0   //!< PAC  8 (recommended for RX of preamble length  128 and below
#define DWT_PAC16       1   //!< PAC 16 (recommended for RX of preamble length  256
#define DWT_PAC32       2   //!< PAC 32 (recommended for RX of preamble length  512
#define DWT_PAC64       3   //!< PAC 64 (recommended for RX of preamble length 1024 and up

下表给出了根据期望接收数据的前导码长度推荐的PAC大小:
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define_19
DW1000开发笔记(三)基于STM32 HAL库裸机工程移植DW1000官方驱动_#define_20
⑤ txCode 和 rxCode 表示发送前导码和接收前导码,为了DW1000正确的使用,推荐设置为相同的值,并且遵循IEEE 802.15.4-2011 UWB标准,如①中的表。

⑥ nsSFD 表示是否使用交替的非标准的SFD来获取更好的性能;

⑦ dataRate 表示数据速率,支持以下值:

//! constants for selecting the bit rate for data TX (and RX)
//! These are defined for write (with just a shift) the TX_FCTRL register
#define DWT_BR_110K     0   //!< UWB bit rate 110 kbits/s
#define DWT_BR_850K     1   //!< UWB bit rate 850 kbits/s
#define DWT_BR_6M8      2   //!< UWB bit rate 6.8 Mbits/s

⑧ phrMode 表示PHR(PHY 头)模式,支持两种值,DWT_PHRMODE_STD标准模式支持5-127个字节,而DWT_PHRMODE_EXT扩展模式支持5到1023个字节:

#define DWT_PHRMODE_STD             0x0     // standard PHR mode
#define DWT_PHRMODE_EXT             0x3     // DW proprietary extended frames PHR mode

⑨ sfdTO用来设置SFD超时时间, 推荐的值是
p r e a m b l e l e n g t h + 1 + S F D l e n g t h – P A C s i z e preamble length + 1 + SFD length – PAC size preamblelength+1+SFDlengthPACsize
此项如果被设置为0,则使用默认值4161。

在实际应用程序中,为了在调节范围内获得最佳性能,可能需要将TX脉冲带宽和TX功率设置为保存在目标系统或DW1000 OTP内存中的每个设备校准值