DWM1000 本身有很多中断控制,例如发送完成中断,接收完成中断等等, 可以通过IRQ(GPIO8) 送到外部控制器。
DWM1000 IRQ pin可以挂到STM32 的外部中断上。 例如当接收到数据帧后,DWM1000 拉IRQ,STM32 实时处理接收到的数据帧。之前代码没有用到中断部分,这里补上一些相关内容。
//DW1000中断事件定义
#define DWT_INT_TFRS 0x00000080 // 帧发送完成,数据帧成功发送后触发
#define DWT_INT_LDED 0x00000400 // 微码执行完毕,DWM1000内部处理结束后触发
#define DWT_INT_RFCG 0x00004000 // 帧接收且CRC校验正确,表示接收到完整无误的数据帧
#define DWT_INT_RPHE 0x00001000 // 接收器PHY头部错误,物理层接收到的帧头部有误
#define DWT_INT_RFCE 0x00008000 // 接收器CRC错误,接收到的数据帧CRC校验失败,数据可能已损坏
#define DWT_INT_RFSL 0x00010000 // 接收器同步丢失错误,接收过程中丢失同步
#define DWT_INT_RFTO 0x00020000 // 帧等待超时,预定时间内没有接收到完整的帧
#define DWT_INT_RXOVRR 0x00100000 // 接收器溢出,数据超过接收缓冲区容量
#define DWT_INT_RXPTO 0x00200000 // 前导码检测超时,设定时间内未检测到前导码,表示无帧传输
#define DWT_INT_SFDT 0x04000000 // SFD超时,指定时间内未检测到帧起始界定符
#define DWT_INT_ARFE 0x20000000 // 帧被拒绝,由于帧过滤配置,接收到的帧不被接受
这段代码定义了一系列宏,它们代表了DWM1000(一款用于超宽带通信的集成电路)的不同中断事件标志。当DWM1000的某个特定事件发生时,相应的位在设备的状态寄存器中会被设置。这些宏定义了每个事件对应的位掩码,使得软件可以检查(通过读取状态寄存器)哪个事件已经发生。下面是每个宏定义的事件详细解释:
DWT_INT_TFRS (0x00000080)
:帧发送完成。当一帧数据成功发送之后,这个中断事件会被触发。
DWT_INT_LDED (0x00000400)
:微代码执行完毕。在DWM1000执行特定的内部处理完毕后,会触发这个事件。
DWT_INT_RFCG (0x00004000)
:帧接收并且CRC校验正确。当接收到的帧的CRC(循环冗余检查)校验是正确的,表明这是一个完整且无误的数据帧。
DWT_INT_RPHE (0x00001000)
:接收器PHY头错误。指示在物理层接收到的帧头部存在错误。
DWT_INT_RFCE (0x00008000)
:接收器CRC错误。如果接收到的帧CRC校验失败,表明数据在传输中可能已损坏。
DWT_INT_RFSL (0x00010000)
:接收器同步丢失错误。如果在接收过程中丢失了同步,这个事件会被触发。
DWT_INT_RFTO (0x00020000)
:帧等待超时。如果在预定的时间内没有接收到完整的帧,这个事件就会发生。
DWT_INT_RXOVRR (0x00100000)
:接收器溢出。如果接收缓存区溢出,即数据超过了接收缓存区的容量,就会发生这个错误。
DWT_INT_RXPTO (0x00200000)
:前导码检测超时。如果没有在设定的时间内检测到前导码,表明没有帧在传输,就会触发这个超时事件。
DWT_INT_SFDT (0x04000000)
:SFD(Start of Frame Delimiter,帧起始界定符)超时。如果在指定的时间内没有检测到SFD,这个事件会被触发。
DWT_INT_ARFE (0x20000000)
:帧被拒绝。由于帧过滤配置的原因,接收到的帧不被接受。
在实际应用中,这些中断事件可以用来处理DWM1000在通信过程中的各种情况,例如确认数据是否发送/接收成功,或者在出现错误时采取相应的错误处理措施。开发者通常会在中断服务程序中检查这些状态位,以确定需要响应的具体事件。
#define DECAIRQ GPIO_Pin_0 // 定义DWM1000中断引脚所对应的GPIO引脚编号,这里为GPIO_Pin_0,即第0号引脚
#define DECAIRQ_GPIO GPIOB // 指定DWM1000中断引脚连接的GPIO端口,这里为GPIOB
#define DECAIRQ_EXTI EXTI_Line0 // 指定与DWM1000中断引脚相对应的外部中断线路,这里为EXTI_Line0
#define DECAIRQ_EXTI_PORT GPIO_PortSourceGPIOB // 定义配置外部中断时使用的GPIO端口源,这里为GPIOB端口
#define DECAIRQ_EXTI_PIN GPIO_PinSource0 // 定义配置外部中断时使用的GPIO引脚源,这里为第0位源
#define DECAIRQ_EXTI_IRQn EXTI0_IRQn // 指定外部中断的中断请求编号(IRQn),这里为EXTI0_IRQn
#define DECAIRQ_EXTI_USEIRQ ENABLE // 定义一个宏,用于启用外部中断
#define DECAIRQ_EXTI_NOIRQ DISABLE // 定义一个宏,用于禁用外部中断
这段代码定义了一组宏,用于配置STM32微控制器上的一个外部中断(EXTI),以便与DWM1000模块的中断引脚相连。每个宏都有特定的目的:
DECAIRQ
:定义了与DWM1000中断引脚连接的GPIO引脚编号,这里是GPIO的第0位(Pin 0)。
DECAIRQ_GPIO
:指定DWM1000中断引脚连接的GPIO端口,在这里是GPIOB。
DECAIRQ_EXTI
:指定与DECAIRQ引脚相对应的外部中断线路,在这里是EXTI的线路0。
DECAIRQ_EXTI_PORT
:定义了配置外部中断时要使用的GPIO端口源,在这里是GPIOB端口。
DECAIRQ_EXTI_PIN
:定义了配置外部中断时要使用的GPIO引脚源,在这里是GPIO的第0位源。
DECAIRQ_EXTI_IRQn
:指定了外部中断的中断请求编号(IRQn),在这里是EXTI的0号中断。
DECAIRQ_EXTI_USEIRQ
:定义一个宏来启用外部中断,在配置中断时使用。
DECAIRQ_EXTI_NOIRQ
:定义一个宏来禁用外部中断,在配置中断时使用。
在STM32的配置代码中,这些宏将被用来指定和配置DWM1000的中断引脚,以确保当DWM1000产生中断信号时,STM32的相应外部中断可以正确地被触发。这通常涉及到设置GPIO引脚为输入模式,并且配置中断优先级和中断服务例程(ISR)。这样做可以确保当中断发生时,微控制器能够响应并执行ISR中定义的代码。
int NVIC_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 启用作为DECAIRQ的GPIO用于中断
GPIO_InitStructure.GPIO_Pin = DECAIRQ; // 指定中断引脚为DECAIRQ
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 将IRQ引脚设置为下拉输入,防止DW1000进入睡眠模式时产生不必要的外部中断
GPIO_Init(DECAIRQ_GPIO, &GPIO_InitStructure); // 初始化GPIO配置
/* 将EXTI线路连接到GPIO引脚 */
GPIO_EXTILineConfig(DECAIRQ_EXTI_PORT, DECAIRQ_EXTI_PIN); // 配置GPIO引脚与EXTI线路的连接
/* 配置EXTI线路 */
EXTI_InitStructure.EXTI_Line = DECAIRQ_EXTI; // 指定EXTI线路为DECAIRQ_EXTI
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置EXTI模式为中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 设置触发方式为上升沿触发,因为DWM1000默认中断极性为高
EXTI_InitStructure.EXTI_LineCmd = DECAIRQ_EXTI_USEIRQ; // 启用EXTI线路的中断
EXTI_Init(&EXTI_InitStructure); // 初始化EXTI配置
/* 设置NVIC分组为16组中断无子分组 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 配置NVIC优先级分组为16组
/* 启用并设置EXTI中断为最低优先级 */
NVIC_InitStructure.NVIC_IRQChannel = DECAIRQ_EXTI_IRQn; // 指定NVIC中断通道为DECAIRQ_EXTI_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15; // 设置抢占优先级为最低(15)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 设置子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = DECAIRQ_EXTI_USEIRQ; // 启用NVIC中断通道
NVIC_Init(&NVIC_InitStructure); // 初始化NVIC配置
return 0;
}
这段代码是STM32微控制器固件中的一个函数,其目的是配置中断向量控制器(NVIC),以便能够处理来自DWM1000模块的中断信号。代码分几个部分进行配置:
首先,函数NVIC_Configuration
定义了三个结构体变量,分别用于初始化GPIO(通用输入/输出),EXTI(外部中断/事件控制器),和NVIC。
在启用用作DECAIRQ的GPIO引脚(即DWM1000的中断引脚)后,该引脚被配置为下拉输入模式。这是为了防止在DWM1000进入睡眠模式时产生不必要的外部中断。
使用GPIO_EXTILineConfig
函数将EXTI的特定线路连接到之前配置的GPIO引脚上。
接下来,配置EXTI线路的具体参数,包括指定的线路DECAIRQ_EXTI
、设置中断模式为EXTI_Mode_Interrupt
、设置触发方式为上升沿触发EXTI_Trigger_Rising
(因为DWM1000的中断极性默认是高电平),并启用该线路的中断。
通过NVIC_PriorityGroupConfig
函数设置NVIC的分组,此处使用的是16个中断组的配置,没有子组。
配置NVIC的中断通道,即前面通过EXTI连接的DECAIRQ中断线路。设置中断的抢占优先级为最低(15),子优先级为0,并启用该中断通道。
最后调用NVIC_Init
函数应用NVIC的配置,并且函数返回0表示成功配置。
这个函数是STM32配置中断处理的典型示例,确保当DWM1000模块产生中断信号时,STM32能够正确识别该信号并调用相应的中断服务例程(ISR)来处理。
// 设置DWM1000的中断,以便对各种接收情况做出响应
dwt_setinterrupt(DWT_INT_RFCG |
(DWT_INT_ARFE | DWT_INT_RFSL | DWT_INT_SFDT | DWT_INT_RPHE | DWT_INT_RFCE | DWT_INT_RFTO /*| DWT_INT_RXPTO*/), 1);
// 启用接收带有正确CRC的帧的中断,以及其他几种错误情况的中断。
// 重新配置,仅启用接收到带有正确CRC的帧时触发的中断
dwt_setinterrupt(DWT_INT_RFCG , 1);
// 这可能是为了重置之前的中断设置,只关注接收正确的CRC帧的情况。
// 启用自动接收重新使能功能
dwt_setautorxreenable(1);
// 开启此功能后,DWM1000会在完成一次数据接收处理后自动返回接收模式,连续接收数据包无需软件重置。
这段代码涉及对DWM1000设备的中断设置。DWM1000是一种集成无线收发器,广泛用于精准的室内定位和距离测量。以下是对每一行代码的详细解释:
dwt_setinterrupt(DWT_INT_RFCG | (DWT_INT_ARFE | DWT_INT_RFSL | DWT_INT_SFDT | DWT_INT_RPHE | DWT_INT_RFCE | DWT_INT_RFTO /| DWT_INT_RXPTO/), 1);
dwt_setinterrupt
函数用于设置DWM1000的中断。这里配置多个中断源,使用位或操作(|
)来组合它们。DWT_INT_RFCG
:接收到带有正确CRC的帧时触发的中断。DWT_INT_ARFE
:由于帧过滤规则,被拒绝的帧触发的中断。DWT_INT_RFSL
:接收同步丢失错误触发的中断。DWT_INT_SFDT
:帧起始定界符超时触发的中断。DWT_INT_RPHE
:接收PHY头错误触发的中断。DWT_INT_RFCE
:接收CRC错误触发的中断。DWT_INT_RFTO
:接收帧等待超时触发的中断。1
表示启用这些中断。DWT_INT_RXPTO
(前导码检测超时)中断在这里被注释掉了,意味着在当前配置中不启用这个中断。dwt_setinterrupt(DWT_INT_RFCG , 1);
dwt_setinterrupt
函数,但这次只启用DWT_INT_RFCG
中断。这可能是为了重置之前的中断设置,只关注接收正确的CRC帧的情况。dwt_setautorxreenable(1);
dwt_setautorxreenable
函数用于设置自动重新启用接收功能。传入的参数1
意味着启用这项功能。这些函数一起配置了DWM1000的中断行为和接收模式,为连续接收和处理数据包提供了便利,同时通过中断通知主系统各种重要事件。这是在实现基于DWM1000的定位和测距系统时常见的设置步骤。
//extern void Simple_Rx_Callback(void);
extern void (*bphero_rxcallback)(void); // 声明一个外部函数指针bphero_rxcallback,指向一个无参数无返回值的函数
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)!= RESET) // 检查EXTI_Line0是否触发了中断
{
// Simple_Rx_Callback(); // 本来可以直接调用一个预定义的回调函数
(*bphero_rxcallback)(); // 调用通过函数指针指向的回调函数
EXTI_ClearITPendingBit(EXTI_Line0); // 清除EXTI_Line0上的中断等待位,准备接收下一次中断
}
}
这段代码定义了一个名为EXTI0_IRQHandler
的中断服务例程(ISR),它是STM32微控制器用来响应外部中断0(EXTI0)的函数。代码的主要功能如下:
extern void (*bphero_rxcallback)(void);
bphero_rxcallback
。这个函数指针指向一个没有参数和返回值的函数,即一个回调函数。由于它是用extern
关键字声明的,意味着这个函数指针的定义在别的地方,可能是其他源文件中。void EXTI0_IRQHandler(void)
EXTI0
的中断服务例程(ISR)的定义。当EXTI0
线上的中断事件发生时,这个函数将被系统调用。if(EXTI_GetITStatus(EXTI_Line0) != RESET)
EXTI_GetITStatus
函数检查EXTI_Line0
是否真的是触发了中断。EXTI_GetITStatus
函数会返回中断线的状态,如果不是RESET
状态(通常在STM32库中,RESET
被定义为0),则说明EXTI_Line0
上的中断事件已经发生。(*bphero_rxcallback)();
bphero_rxcallback
调用的,意味着在运行时可以指向任何具有相应签名的函数。这提供了代码的灵活性,因为可以在不修改ISR代码的情况下改变中断响应的行为。EXTI_ClearITPendingBit(EXTI_Line0);
EXTI_ClearITPendingBit
函数清除EXTI_Line0
上的中断等待位,这样做是为了防止中断服务例程被连续不断地调用。清除中断标志位是告诉中断控制器这次中断已经被处理完毕,可以继续监听后续的中断请求。注释中被注释掉的// Simple_Rx_Callback();
这行代码示例了另一种直接调用预定义回调函数的方式,但在这个例子中它被替换为了使用函数指针的方式。
void Simple_Rx_Callback()
{
uint32 status_reg = 0,i=0;
uint32 poll_tx_ts, resp_rx_ts, final_tx_ts;
uint32 poll_rx_ts_32, resp_tx_ts_32, final_rx_ts_32;
double Ra, Rb, Da, Db;
int64 tof_dtu;
char dist_str[16] = {0};
for (i = 0 ; i < FRAME_LEN_MAX; i++ )
{
rx_buffer[i] = '\0'; // 清空接收缓冲区
}
/* 禁用接收,准备读取状态。注释2相关 */
dwt_enableframefilter(DWT_FF_RSVD_EN);//禁用接收
status_reg = dwt_read32bitreg(SYS_STATUS_ID); // 读取状态寄存器
if (status_reg & SYS_STATUS_RXFCG)//如果接收到正确的帧
{
/* 接收到帧,复制到本地缓冲区 */
frame_len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023; // 读取帧长度
if (frame_len <= FRAME_LEN_MAX)
{
dwt_readrxdata(rx_buffer, frame_len, 0); // 读取接收到的数据到缓冲区
msg_f = (srd_msg_dsss*)rx_buffer; // 转换缓冲区数据到结构体以便处理
// 将源地址作为目标地址复制
msg_f_send.destAddr[0] = msg_f->sourceAddr[0];
msg_f_send.destAddr[1] = msg_f->sourceAddr[1];
// 复制源序列号
msg_f_send.seqNum = msg_f->seqNum;
switch(msg_f->messageData[0]) // 根据消息类型处理
{
case 'P': // 处理Poll消息
/* 提取接收时间戳 */
msg_f_send.messageData[0]='A'; // 设置回应消息类型为Poll ack
int temp = (int)(distance[msg_f_send.destAddr[0]]*100);//将距离转换为厘米
msg_f_send.messageData[1]=temp/100; // 距离的整数部分
msg_f_send.messageData[2]=temp%100; // 距离的小数部分
dwt_writetxdata(14, (uint8 *)&msg_f_send, 0); // 写入待发送数据
dwt_writetxfctrl(14, 0); // 设置发送帧控制
dwt_starttx(DWT_START_TX_IMMEDIATE); // 开始发送
while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
{ }; // 等待发送完成
poll_rx_ts = get_rx_timestamp_u64(); // 获取接收时间戳
break;
case 'F': // 处理Final消息
/* 提取发送和接收时间戳 */
resp_tx_ts = get_tx_timestamp_u64();
final_rx_ts = get_rx_timestamp_u64();
/* 提取嵌入在最终消息中的时间戳 */
final_msg_get_ts(&msg_f->messageData[FINAL_MSG_POLL_TX_TS_IDX], &poll_tx_ts);
final_msg_get_ts(&msg_f->messageData[FINAL_MSG_RESP_RX_TS_IDX], &resp_rx_ts);
final_msg_get_ts(&msg_f->messageData[FINAL_MSG_FINAL_TX_TS_IDX], &final_tx_ts);
/* 计算飞行时间。即使时钟环绕,32位减法也能给出正确答案 */
poll_rx_ts_32 = (uint32)poll_rx_ts; // 将poll_rx_ts转换为32位无符号整型并赋值给poll_rx_ts_32
resp_tx_ts_32 = (uint32)resp_tx_ts; // 将resp_tx_ts转换为32位无符号整型并赋值给resp_tx_ts_32
final_rx_ts_32 = (uint32)final_rx_ts; // 将final_rx_ts转换为32位无符号整型并赋值给final_rx_ts_32
Ra = (double)(resp_rx_ts - poll_tx_ts); // 计算响应接收时间与轮询发送时间的差,并转换为双精度浮点数
Rb = (double)(final_rx_ts_32 - resp_tx_ts_32); // 计算最终接收时间与响应发送时间的32位差值,并转换为双精度浮点数
Da = (double)(final_tx_ts - resp_rx_ts); // 计算最终发送时间与响应接收时间的差,并转换为双精度浮点数
Db = (double)(resp_tx_ts_32 - poll_rx_ts_32); // 计算响应发送时间与轮询接收时间的32位差值,并转换为双精度浮点数
tof_dtu = (int64)((Ra * Rb - Da * Db) / (Ra + Rb + Da + Db)); // 根据Ra, Rb, Da, Db计算飞行时间(ToF)并转换为64位整数
tof = tof_dtu * DWT_TIME_UNITS; // 计算飞行时间
distance_temp = tof * SPEED_OF_LIGHT; // 计算距离
distance[msg_f_send.destAddr[0]] = distance_temp - dwt_getrangebias(config.chan,(float)distance_temp, config.prf);//距离减去矫正系数
// 如果需要,可以在这里加入Kalman滤波或其他逻辑处理距离
#if 0 //如果一个标签可以打开kalman滤波
distance[msg_f_send.destAddr[0]] = KalMan(distance[msg_f_send.destAddr[0]]);
#endif
#if 0 //为了加快测距频率,基站尽量不要显示距离
// 如果不需要在基站显示距离,可以跳过下面的显示代码
break;
case 'M': // 处理M消息
// 将收集到的距离信息发送到电脑,数据长度16字节
USART1WriteDataToBuffer(&msg_f->messageData[1],16);
break;
default:
// 对于未知的消息类型不进行处理
break;
}
}
// 清除错误标志并重新启用接收
dwt_write32bitreg(SYS_STATUS_ID, (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR));
dwt_enableframefilter(DWT_FF_DATA_EN);
dwt_setrxtimeout(0); // 设置接收超时
dwt_rxenable(0); // 重新启用接收
}
else
{
// 清除错误标志并重新启用接收
dwt_write32bitreg(SYS_STATUS_ID, (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR));
dwt_enableframefilter(DWT_FF_DATA_EN);
dwt_rxenable(0);
}
}
这段代码是一个名为Simple_Rx_Callback
的函数,用于处理DWM1000模块的接收回调。它主要包括对接收到的消息的处理和对特定消息类型的响应。下面是对该函数中每部分代码的详细解释:
变量初始化:
status_reg
,迭代变量i
,时间戳变量poll_tx_ts
、resp_rx_ts
、final_tx_ts
等,以及计算时间飞行(ToF)所需的变量Ra
、Rb
、Da
、Db
和tof_dtu
。同时,定义了一个字符数组dist_str
来存储距离字符串。清空接收缓冲区:
rx_buffer
的内容清空。禁用接收并读取状态寄存器:
dwt_enableframefilter
函数禁用接收功能,然后通过dwt_read32bitreg
读取系统状态寄存器SYS_STATUS_ID
。检查是否接收到有效帧:
SYS_STATUS_RXFCG
(表示接收到一个CRC校验正确的帧)的位运算来检查是否成功接收到一个有效帧。帧处理:
rx_buffer
中。重新启用接收功能:
无论是否成功接收到有效帧,最后都将清除错误标志,并重新启用接收功能,准备接收下一个消息。
通过与SYS_STATUS_RXFCG
(表示接收到一个CRC校验正确的帧)的位运算来检查是否成功接收到一个有效帧。
帧处理:
rx_buffer
中。重新启用接收功能:
这个函数对接收到的消息进行解析,根据不同的消息类型执行不同的逻辑,并计算设备间的距离。这是典型的超宽带(UWB)通信中的时间差测距(TDoA)或双向测距(TWR)的实现,用于计算发送者和接收者之间的距离。此代码段特别适用于具有定位功能的系统,如室内定位、资产追踪等。