BP-50 开发框架使用解读

【独家框架】UWB DWM1000 开源项目框架 - 基础知识 51uwb.cn

[重要更新]51uwb_base code中断更新 - 基础知识 51uwb.cn

【软件资料】BP50 套件新框架定位代码实现 - 基础知识 51uwb.cn

【开源项目】纯Python TWR算法UWB上位机 - 基础知识 51uwb.cn

上位机软件通信协议更改说明 - 基础知识 51uwb.cn

Bphero-UWB 基站0 和 电脑串口数据格式定义 - 基础知识 51uwb.cn

DWM1000中断方式接收信息 代码参考 - 基础知识 51uwb.cn

配置STM32 中断引脚 – DWM1000 IRQ 【STM32 基础知识】 - 基础知识 51uwb.cn

DWM1000 自动应答 — 代码实现 - 基础知识 51uwb.cn

基站如何传输GPS数据给标签呢?要怎么更改发送帧的内容阿 - 问答社区 51uwb.cn

DWM1000 帧过滤代码实现 - tuzhuke - 博客园 (cnblogs.com)

[新人福利]UWB定位模块PCB及原理图 - 基础知识 51uwb.cn

STM32程序移植方法【STM32 基础知识】 - 基础知识 51uwb.cn

求助 DWM1000 谁有信号质量 和功率代码和计算方式 - 问答社区 51uwb.cn

UWB怎么校准? - 基础知识 51uwb.cn

关于帧过滤问题,请大神解释一下,非常感谢 - 基础知识 51uwb.cn

dw1000帧过滤问题 - 基础知识 51uwb.cn

DWM1000 帧过滤功能实现 - 基础知识 51uwb.cn

基站0串口数据刷新慢解决方案【代码优化】 - 基础知识 51uwb.cn

UWB定位上位机增加导出数据功能 - 问答社区 51uwb.cn

上位机软件 代码注释 - 问答社区 51uwb.cn

UWB标签显示坐标的两种思路 - 基础知识 51uwb.cn

DW1000的移动标签能否自己解算位置? - 问答社区 51uwb.cn

bp hero的位置数据从哪里读取啊? - 问答社区 51uwb.cn

求一个小车轨迹追踪上位机设计论文的上位机软件部分怎么写!!! - 问答社区 51uwb.cn

官方上位机修改 主窗体 名字方法 - 基础知识 51uwb.cn

卡尔曼滤波

RSSI信号强度

三边定位

BP-50 开发框架

概述

  1. 模块角色定义

    • bphero_uwb.h 文件中,通过软件定义模块角色,可以设置为基站(RX节点)或标签(TX节点)。通过注释或取消注释特定的宏定义来选择角色。
    • 如定义 RX_NODE 表示该模块为基站,定义 TX_NODE 表示为标签。
    //rx为基站,tx为标签
    #define RX_NODE   // 基站
    //#define TX_NODE //标签
    
  2. 基站和标签的地址设置

    • 基站的地址从 0x0001 开始,且在部署完毕后,应将地址为 0x0001 的基站连接到串口。
    • 标签的地址从 0x0005 开始,确保标签和基站的地址不重叠。
    //基站节点地址0x0001 0x0002 0x0003
    //部署完毕基站0x0001 链接串口
    //基站地址必须从0x0001开始!!!
    #ifdef RX_NODE
    	#define SHORT_ADDR 0x0001
    //#define LCD_ENABLE //没有液晶的时候,把这个宏定义注释掉
    #endif
    
    //标签和基站地址不能重叠
    //标签节点地址 0x0005 0x0006 0x0007
    #ifdef TX_NODE
    	#define SHORT_ADDR 0x0005
    	#define LCD_ENABLE //没有液晶的时候,把这个宏定义注释掉
    #endif
    
  3. 多基站多标签的支持

    • 通过修改 tx_main.c 文件来控制多标签和多基站的配置。
    • 对于多个基站,只需修改 MAX_ANTHOR 宏定义。基站地址应从 0x0001 开始。
    • SEPC_ADDRESS 定义为规0地址,用于在规0后向基站 0x0001 发送距离信息。DEST_BEGIN_ADDRDEST_END_ADDR 定义了基站的地址范围。
    • 对于多标签控制,可以通过调整 MAX_FREQ_HZ(定位频率)和 MAX_TX_Node(系统中最多标签节点数)来进行配置。
      //多基站控制
      /************************!!!重要宏定义!!!******************************/
    /****************多基站只需要修改MAX_ANTHOR即可***************************/
    /*****************基站的地址必须是从0x0001 开始***************************/
    #define MAX_ANTHOR 4
    //anthor range
    #define SEPC_ADDRESS 0x0000  //规0地址,每次规0后,向基站0x0001发送距离信息
    #define DEST_BEGIN_ADDR 0x0001  //基站起始地址
    #define DEST_END_ADDR   DEST_BEGIN_ADDR + MAX_ANTHOR - 1 //anthro address 0x001 0x002 0x003 for 2D ,0x0001 0x0002 0x0003 0x0004 for 3D
    
       //多标签控制
    #define MAX_FREQ_HZ 10 //定位10HZ
    #define MAX_TX_Node 2  //系统中实际存在最多标签节点
    
  4. 技术支持

    • 遇到使用中的问题,可以通过51uwb.cn网站与开发团队联系交流。

mian.c

#include "stm32f10x.h" // 引入STM32F10x系列微控制器的头文件
#include <stdio.h> // 引入标准输入输出库
#include "deca_device_api.h" // 引入Decawave UWB设备的API定义
#include "deca_regs.h" // 引入Decawave设备的寄存器定义
#include "deca_sleep.h" // 引入Decawave设备的睡眠模式处理库
#include "port.h" // 引入与硬件端口相关的定义和函数
#include "bphero_uwb.h" // 引入与该UWB系统特定功能相关的头文件
//#include "lcd_oled.h" // 被注释掉的头文件,可能用于OLED显示的库

extern int rx_main(void); // 声明外部函数rx_main,用于接收节点(基站)
extern int tx_main(void); // 声明外部函数tx_main,用于发送节点(标签)

int main(void)
{
    peripherals_init(); // 初始化微控制器的外围设备
    BPhero_UWB_Message_Init(); // 初始化UWB消息系统
    BPhero_UWB_Init(); // 初始化UWB系统

#ifdef RX_NODE
    rx_main(); // 如果定义了RX_NODE,作为接收节点执行rx_main函数 基站
#endif

#ifdef TX_NODE
    tx_main(); // 如果定义了TX_NODE,作为发送节点执行tx_main函数 标签
#endif
}

这段代码是一个用于超宽带(UWB)技术的定位系统的主函数(main)部分。我将逐行解释其功能和作用:

  1. 包含头文件

    • #include "stm32f10x.h": 引入STM32F10x系列微控制器的头文件,提供基础的硬件定义。
    • #include <stdio.h>: 引入标准输入输出库,用于基本的I/O操作。
    • #include "deca_device_api.h": 引入Decawave UWB设备的API定义。
    • #include "deca_regs.h": 引入Decawave设备的寄存器定义。
    • #include "deca_sleep.h": 引入Decawave设备的睡眠模式处理库。
    • #include "port.h": 引入与硬件端口相关的定义和函数。
    • #include "bphero_uwb.h": 引入与该UWB系统特定功能相关的头文件。
    • //#include "lcd_oled.h": 被注释掉的头文件,可能是用于OLED显示的库,但在此代码中未使用。
  2. 外部函数声明

    • extern int rx_main(void);: 声明外部函数rx_main,这是作为接收节点(基站)的主要功能函数。
    • extern int tx_main(void);: 声明外部函数tx_main,这是作为发送节点(标签)的主要功能函数。
  3. 主函数main的定义

    • peripherals_init();: 初始化微控制器的外围设备。
    • BPhero_UWB_Message_Init();: 初始化UWB消息系统,可能是用于配置消息传输相关的参数。
    • BPhero_UWB_Init();: 初始化UWB系统,可能包括设置UWB设备的参数和状态。
  4. 条件编译指令

    • #ifdef RX_NODE: 这是一个预处理指令,检查是否定义了RX_NODE。如果定义了,意味着当前模块配置为接收节点(基站)。
      • rx_main();: 在基站模式下调用rx_main函数,执行接收节点的主要操作。
    • #ifdef TX_NODE: 这是另一个预处理指令,检查是否定义了TX_NODE。如果定义了,意味着当前模块配置为发送节点(标签)。
      • tx_main();: 在标签模式下调用tx_main函数,执行发送节点的主要操作。

总结来说,这段代码是UWB系统主程序的入口,根据预编译的宏定义来决定当前设备是作为基站还是标签,并执行相应的初始化和主要功能函数。这种设计使得同一套代码可以用于不同的硬件配置,只需改变宏定义即可切换角色。

bp_filter.c

#define Filter_N 5  // 定义系统中使用的滤波器数量为5
#define Filter_D 5  // 定义每个滤波器包含的数据数量为5
int Value_Buf[Filter_N][Filter_D]= {0}; // 二维数组,存储每个滤波器的数据点
int filter_index[Filter_N] = {0}; // 数组,记录每个滤波器的当前数据点索引

// 定义滤波函数
int filter(int input, int fliter_idx )
{
    char count = 0; // 计数器变量
    int sum = 0, min, max; // 定义求和、最小值、最大值变量
    min = max = Value_Buf[fliter_idx][0]; // 初始化最小值和最大值为当前滤波器的第一个数据点的值

    // 如果输入值大于0,更新数据缓冲区和索引
    if(input > 0)
    {
        Value_Buf[fliter_idx][filter_index[fliter_idx]++]=input;
        if(filter_index[fliter_idx] == Filter_D) filter_index[fliter_idx] = 0;
    }

    // 遍历当前滤波器的所有数据点
    for(count = 0; count < Filter_D; count++)
    {
        // 找出最大值和最小值
        if (Value_Buf[fliter_idx][count] > max) max = Value_Buf[fliter_idx][count];
        if (Value_Buf[fliter_idx][count] < min) min = Value_Buf[fliter_idx][count];

        // 计算总和
        sum += Value_Buf[fliter_idx][count];
    }
    
    // 从总和中减去最大值和最小值
    sum = sum - max - min;
    
    // 返回处理后的平均值(排除了最大值和最小值)
    return (int)(sum / (Filter_D - 2));
}

这段代码定义了一个简单的滤波器函数,用于处理数字信号。代码的主要部分如下:

  1. 宏定义

    • #define Filter_N 5: 定义系统中使用的滤波器数量,这里是5个。
    • #define Filter_D 5: 定义每个滤波器包含的数据数量,这里是5个数据点。
  2. 全局变量定义

    • int Value_Buf[Filter_N][Filter_D]= {0};: 定义一个二维数组,用于存储每个滤波器的所有数据点。数组的大小由上面定义的 Filter_NFilter_D 决定。
    • int filter_index[Filter_N] = {0};: 定义一个数组,用于记录每个滤波器的当前数据点索引。
  3. 滤波函数 filter

    • int filter(int input, int fliter_idx ): 这个函数接受一个输入值 input 和一个滤波器索引 fliter_idx
    • char count = 0;: 定义一个计数器变量。
    • int sum = 0, min, max;: 定义用于计算的变量 summinmax
    • min = max = Value_Buf[fliter_idx][0];: 初始化 minmax 为当前滤波器的第一个数据点的值。
    • if(input > 0) {...}: 如果输入值大于0,则将其添加到相应滤波器的数据缓冲区中,并更新索引。
    • for(count = 0; count < Filter_D; count++) {...}: 遍历当前滤波器的所有数据点。
      • 在这个循环中,代码找出最大值和最小值,并计算所有数据点的总和。
    • sum = sum - max - min;: 从总和中减去找到的最大值和最小值。
    • return (int)(sum / (Filter_D - 2));: 返回处理后的平均值(排除了最大值和最小值)。

这个滤波器基本上是一个简化版的中值滤波器,它排除了每个样本集中的最大值和最小值,然后计算剩余值的平均值。这种方法有助于减少由极端值或噪声引起的误差。

rx_main.c

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/main.c
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * 本固件仅供指导之用,旨在为客户提供有关其产品的编码信息,以节省时间。
  * 因此,STMicroelectronics 对于任何由于本固件内容和/或客户使用此编码信息而产生的任何直接、
  * 间接或后果性损害不承担任何责任。
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

#include "stm32f10x.h" // 引入STM32F10x系列微控制器的头文件
#include <stdio.h> // 引入标准输入输出库
#include "deca_device_api.h" // 引入Decawave UWB设备的API定义
#include "deca_regs.h" // 引入Decawave设备的寄存器定义
#include "deca_sleep.h" // 引入Decawave设备的睡眠模式处理库
#include "port.h" // 引入与硬件端口相关的定义和函数
#include "lcd_oled.h" // 引入OLED显示屏的库
#include "frame_header.h" // 引入帧头文件
#include "bp_filter.h" // 引入滤波器头文件
#include "common_header.h" // 引入公共头文件
#include "bphero_uwb.h" // 引入与该UWB系统特定功能相关的头文件
#include "dwm1000_timestamp.h" // 引入与DW1000时间戳相关的头文件
#include "kalman.h" // 引入卡尔曼滤波算法的头文件

static void Handle_TimeStamp(void); // 处理时间戳的函数声明
#define MAX_ANTHOR_NODE 40 // 定义最大的基站节点数为40
struct distance_struct // 定义距离结构体
{
    double rx_distance; // 接收距离
    struct time_timestamp tx_node; // 发送节点的时间戳
    struct time_timestamp rx_node; // 接收节点的时间戳
    bool present; // 标记是否存在
    int count; // 计数器
} bphero_distance[MAX_ANTHOR_NODE]; // 定义存储最大基站节点距离的数组

typedef signed long long int64; // 定义64位有符号整型
typedef unsigned long long uint64; // 定义64位无符号整型
static uint64 poll_rx_ts; // 接收轮询时间戳
static uint64 resp_tx_ts; // 响应发送时间戳
static uint64 final_rx_ts; // 最终接收时间戳

static uint64 poll_tx_ts; // 发送轮询时间戳
static uint64 resp_rx_ts; // 响应接收时间戳
static uint64 final_tx_ts; // 最终发送时间戳

static srd_msg_dsss *msg_f; // 定义消息指针
static double tof; // 飞行时间
static double distance_temp = 0; // 临时距离
static double distance[256] = {0}; // 距离数组
extern dwt_config_t config; // 引入DW1000配置

/* 私有函数 ---------------------------------------------------------*/
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; // 定义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'; // 清空接收缓冲区
    }
    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': // 处理轮询消息
                    msg_f_send.messageData[0]='A'; // 设置轮询应答消息
                    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': // 处理最终消息
                    /* 获取响应发送和最终接收的时间戳 */
                    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); 
                    // 获取最终发送时间戳

                    /* 计算飞行时间(ToF)。即使时钟发生了回绕,32位减法也能给出正确答案 */
                    poll_rx_ts_32 = (uint32)poll_rx_ts; // 将64位时间戳转换为32位
                    resp_tx_ts_32 = (uint32)resp_tx_ts; // 将64位时间戳转换为32位
                    final_rx_ts_32 = (uint32)final_rx_ts; // 将64位时间戳转换为32位
                    Ra = (double)(resp_rx_ts - poll_tx_ts); // 计算Ra值
                    Rb = (double)(final_rx_ts_32 - resp_tx_ts_32); // 计算Rb值
                    Da = (double)(final_tx_ts - resp_rx_ts); // 计算Da值
                    Db = (double)(resp_tx_ts_32 - poll_rx_ts_32); // 计算Db值
                    tof_dtu = (int64)((Ra * Rb - Da * Db) / (Ra + Rb + Da + Db)); // 计算飞行时间单位

                    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); // 减去矫正系数得到实际距离
                    #if 0 // 如果启用了卡尔曼滤波
                    distance[msg_f_send.destAddr[0]] =  KalMan(distance[msg_f_send.destAddr[0]]); // 应用卡尔曼滤波
                    #endif

                    #if 0 // 为了加快测距频率,基站尽量不要显示距离
                    {
                        sprintf(dist_str, "an0:%3.2fm     ", distance[msg_f_send.destAddr[0]]); // 格式化距离字符串
                        OLED_ShowString(0, 4, dist_str); // 在OLED显示屏上显示距离
                    }
                    #endif

                    break;

                case 'M': // 处理其他类型的消息
                    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); // 重新启用接收功能
    }
}


int rx_main(void)//接收函数
{
    OLED_ShowString(0,0,"   51UWB Node"); // 在OLED显示屏上显示“51UWB Node”
    OLED_ShowString(0,4,"  www.51uwb.cn"); // 在OLED显示屏上显示网址“www.51uwb.cn”
    OLED_ShowString(0,2,"    Rx Node "); // 在OLED显示屏上显示“Rx Node”,表明这是接收节点

	#if 0
    KalMan_Init(); // 初始化卡尔曼滤波器,当前被注释,不会执行
    #endif
    dwt_setrxtimeout(0); // 设置DW1000的接收超时时间为0,禁用接收超时
    dwt_enableframefilter(DWT_FF_DATA_EN); // 启用DW1000的数据帧过滤功能
    dwt_rxenable(0); // 启用DW1000的接收功能

    // 设置接收回调函数
    bphero_setcallbacks(Simple_Rx_Callback); // 当接收到数据时,调用Simple_Rx_Callback函数

    while (1) // 无限循环,等待接收数据
    {
    }
}


#ifdef  USE_FULL_ASSERT
/**
  * @brief  报告源文件名和发生assert_param错误的行号。
  * @param  file: 指向源文件名的指针
  * @param  line: assert_param错误行的行号
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{
    /* 用户可以添加自己的实现来报告文件名和行号,
       例如: printf("参数错误:文件 %s 在第 %d 行\r\n", file, line) */

    /* 无限循环 */
    while (1)
    {
    }
}
#endif

/**
  * @}
  */
/**
  * @}
  */
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

这段代码是一个基于STM32F10x微控制器和Decawave的DW1000 UWB (Ultra-Wideband) 模块的定位系统程序。代码的主要组成部分如下:

文件和作者信息

  • 这部分包含了文件的基本信息,例如文件名、作者、版本、日期和简要描述。

包含的头文件

  • 包括STM32F10x系列微控制器的头文件和其他必要的库,如Decawave的UWB API、寄存器定义、睡眠处理等。

定义和结构体

  • 定义了最大基站节点数 (MAX_ANTHOR_NODE) 和 distance_struct 结构体,用于存储与每个节点相关的距离信息和时间戳。

类型定义

  • 定义了64位整型变量 int64uint64

全局变量

  • 定义了一系列变量,用于处理UWB通信中的时间戳和距离计算。

Simple_Rx_Callback 函数

  • 这是一个回调函数,用于处理接收到的UWB消息。它读取状态寄存器,处理接收到的消息,并根据消息类型执行不同的操作,如处理距离测量和发送数据。

  • 这段代码是Simple_Rx_Callback函数的实现,它是UWB(超宽带)定位系统中用于处理接收到的消息的回调函数。下面详细解释这段代码:

    1. 初始化和读取状态

      • uint32 status_reg = 0, i = 0;: 定义并初始化用于存储状态寄存器值的变量status_reg和循环变量i
      • 初始化时间戳变量和其他所需的变量。
      • for (i = 0; i < FRAME_LEN_MAX; i++) { rx_buffer[i] = '\0'; }: 清空接收缓冲区。
    2. 启用帧过滤器

      • dwt_enableframefilter(DWT_FF_RSVD_EN);: 启用帧过滤功能,但在这里它被设置为禁用接收。
    3. 读取并处理接收到的消息

      • status_reg = dwt_read32bitreg(SYS_STATUS_ID);: 读取系统状态寄存器。
      • if (status_reg & SYS_STATUS_RXFCG) { ... }: 如果接收到了有效的消息,则进入此条件块。
    4. 处理接收到的帧

      • 获取帧长度,读取接收到的数据,并根据接收到的消息类型执行不同的操作。
      • 操作包括处理轮询消息、最终消息,并根据消息类型计算飞行时间(ToF)。
    5. 计算飞行时间(ToF)并更新距离

      • 使用DW1000的时间戳来计算ToF,进而计算出距离。

      • 如果启用了卡尔曼滤波,可以对距离值进行滤波处理。

      • 这段代码是从一个UWB(Ultra-Wideband)系统的接收回调函数中摘录的,主要用于计算从一个设备到另一个设备的飞行时间(ToF,Time of Flight),进而估计它们之间的距离。下面是对代码的详细解释:

        1. 获取时间戳

          • resp_tx_ts = get_tx_timestamp_u64();:获取响应消息的发送时间戳。
          • final_rx_ts = get_rx_timestamp_u64();:获取最终接收的时间戳。
        2. 从接收到的消息中提取时间戳

          • 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);:提取最终消息的发送时间戳。
        3. 计算飞行时间(ToF)

          • 时间戳转换:将64位的时间戳转换为32位,以便进行计算。
          • 计算四个关键值(Ra, Rb, Da, Db),这些值基于不同的时间戳差来计算。
          • tof_dtu = (int64)((Ra * Rb - Da * Db) / (Ra + Rb + Da + Db));:使用这四个值来计算飞行时间单位(ToF)。
        4. 计算距离

          • 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);:从计算出的距离中减去任何由硬件或配置引入的偏差。
        5. 可选的卡尔曼滤波

          • 如果启用了卡尔曼滤波,使用KalMan函数进一步处理距离数据。
        6. 可选的显示距离

          • 如果需要在OLED显示屏上显示距离,格式化距离字符串并显示。

        这段代码的关键在于精确计算飞行时间,这是UWB定位系统测距的核心。通过精确的时间戳测量和计算,可以估计出发射器和接收器之间的距离。可选的卡尔曼滤波和显示功能提供了额外的数据处理和用户界面支持。

      这段代码是一个条件编译语句,用于控制是否应用卡尔曼滤波器(Kalman Filter)来处理UWB定位系统中的距离数据。下面是对代码的详细解释以及如何启用卡尔曼滤波:

      代码解释

      1. 条件编译指令
      • #if 0: 这是一个预处理指令,用于条件编译。当表达式的值为0时,编译器会忽略该块代码。在这个例子中,#if 0意味着紧随其后的代码不会被编译。
      1. 卡尔曼滤波应用
      • distance[msg_f_send.destAddr[0]] = KalMan(distance[msg_f_send.destAddr[0]]);: 这行代码是将卡尔曼滤波应用于计算出的距离上。KalMan函数可能是实现卡尔曼滤波算法的函数,它接收原始距离作为输入,并返回经过滤波处理的距离。
      1. 结束条件编译
      • #endif: 这标志着条件编译块的结束。

      如何启用卡尔曼滤波

      要启用这段代码中的卡尔曼滤波,需要更改预处理指令#if 0#if 1或其他非零值。这样做将使得编译器编译并包含这段代码。更改后的代码如下:

      #if 1 // 启用卡尔曼滤波
      distance[msg_f_send.destAddr[0]] = KalMan(distance[msg_f_send.destAddr[0]]);
      #endif
      

      在进行此更改后,编译并运行程序时,卡尔曼滤波算法将被应用于处理距离数据。卡尔曼滤波是一种高效的算法,用于减少测量中的随机噪声,使得距离估计更加准确和稳定。它特别适合于处理动态系统中的数据,如跟踪移动的对象。

    6. 处理特定类型的消息

      • 根据接收到的消息类型(如’P’, ‘F’, ‘M’)进行不同的处理。
    7. 重新启用接收

      • 在消息处理完毕后,重新设置系统状态,启用帧过滤和接收功能,准备接收下一个消息。
    8. 错误处理

      • 如果未接收到有效消息,则清除错误标志,并重新启用接收。

    总体来说,这个函数负责接收和处理来自其他UWB节点的消息,包括计算距离,处理不同类型的数据帧,以及更新系统状态以准备接收下一条消息。这是UWB定位系统中接收节点功能的核心部分。

rx_main 函数

  • 主要的接收函数。初始化OLED显示,设置超时,启用帧过滤器,然后进入一个无限循环等待接收消息。

  • 这段代码是rx_main函数的实现,它是一个用于超宽带(UWB)定位系统的接收节点(Rx Node)的主要函数。下面详细解释这段代码:

    1. OLED 显示初始化

      • OLED_ShowString(0,0," 51UWB Node");: 在OLED显示屏上的第0行第0列位置显示字符串" 51UWB Node"。这可能是显示固定的标题或标识。
      • OLED_ShowString(0,4," www.51uwb.cn");: 在第4行显示网址" www.51uwb.cn",可能是制造商或项目的网站。
      • OLED_ShowString(0,2," Rx Node ");: 在第2行显示" Rx Node ",表明这个节点是一个接收节点。
    2. 卡尔曼滤波初始化(如果启用):

      • #if 0: 这是一个条件编译语句,当前设置为0,意味着下面的代码不会被编译。如果将0改为1或其他非0值,则下面的代码会被编译。
      • KalMan_Init();: 初始化卡尔曼滤波器。卡尔曼滤波是一种有效的算法,用于在存在噪声的情况下估计系统的状态。
    3. DW1000 UWB模块的设置

      • dwt_setrxtimeout(0);: 设置DW1000模块的接收超时时间。这里设置为0表示禁用接收超时。
      • dwt_enableframefilter(DWT_FF_DATA_EN);: 启用帧过滤器。这里启用的是数据帧过滤,意味着模块将只处理数据帧。
      • dwt_rxenable(0);: 启用接收功能。参数0可能表示立即启动接收或某种默认配置。
    4. 设置接收回调函数

      • bphero_setcallbacks(Simple_Rx_Callback);: 设置一个回调函数Simple_Rx_Callback,当接收到数据时,这个函数会被调用。
    5. 无限循环

      • while (1) { }: 这是一个无限循环,使得程序持续运行并等待接收数据。在这个循环内部没有执行任何操作,实际的数据处理逻辑是在回调函数Simple_Rx_Callback中实现的。

    总体来说,这个函数设置了接收节点的初步配置,包括初始化显示屏,配置UWB模块,以及设置用于接收数据的回调函数。然后,它进入一个无限循环,等待并处理接收到的数据。

assert_failed 函数

  • 用于调试目的,当 USE_FULL_ASSERT 被定义时,报告源文件名和发生assert_param错误的行号。

文件结束注释

  • 表明文件属于STMicroelectronics的版权,以及文件结束的标记。

这个程序主要用于基于UWB技术的距离测量和定位。它通过处理来自UWB模块的信号,计算设备之间的时间差和距离。这对于需要精确定位的应用(如室内导航、资产跟踪)非常有用。程序中包含的不同部分是为了处理UWB信号、计算距离、显示信息以及处理错误情况。

tx_main.c

/**
  ******************************************************************************
  * @file    Project/STM32F10x_StdPeriph_Template/main.c
  * @author  MCD Application Team
  * @version V3.5.0
  * @date    08-April-2011
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * 本固件仅供指导之用,旨在为客户提供有关其产品的编码信息,以节省时间。
  * 因此,STMicroelectronics 对于任何由于本固件内容和/或客户使用此编码信息而产生的任何直接、
  * 间接或后果性损害不承担任何责任。
  *
  * <h2><center>&copy; COPYRIGHT 2011 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* 包含的头文件 -----------------------------------------------------------*/
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include <stdio.h> // 包含标准输入输出库
#include "deca_device_api.h" // 包含Decawave UWB设备的API定义
#include "deca_regs.h" // 包含Decawave设备的寄存器定义
#include "deca_sleep.h" // 包含Decawave设备的睡眠模式处理库
#include "port.h" // 包含与硬件端口相关的定义和函数
#include "frame_header.h" // 包含帧头文件
#include "common_header.h" // 包含公共头文件
#include "bphero_uwb.h" // 包含与该UWB系统特定功能相关的头文件
#include "dwm1000_timestamp.h" // 包含与DW1000时间戳相关的头文件

static unsigned char distance_seqnum = 0; // 距离测量的序列号

// 定义全局变量和类型
static srd_msg_dsss *msg_f_recv; // 定义接收消息的指针
typedef signed long long int64; // 定义64位有符号整型
typedef unsigned long long uint64; // 定义64位无符号整型
typedef unsigned int uint32; // 定义32位无符号整型

static uint64 poll_rx_ts; // 定义轮询接收时间戳
static uint64 resp_tx_ts; // 定义响应发送时间戳
static uint64 final_rx_ts; // 定义最终接收时间戳

static uint64 poll_tx_ts; // 定义轮询发送时间戳
static uint64 resp_rx_ts; // 定义响应接收时间戳
static uint64 final_tx_ts; // 定义最终发送时间戳

static void Send_Dis_To_Anthor1(void); // 发送距离信息到指定基站的函数

/************************!!!重要宏定义!!!******************************/
/****************多基站只需要修改MAX_ANTHOR即可***************************/
/*****************基站的地址必须是从0x0001 开始***************************/
#define MAX_ANTHOR 4 // 定义最大基站数量为4
#define SEPC_ADDRESS 0x0000  // 定义规0地址
#define DEST_BEGIN_ADDR 0x0001  // 定义基站起始地址
#define DEST_END_ADDR   DEST_BEGIN_ADDR + MAX_ANTHOR - 1 // 定义基站地址范围

int Final_Distance[MAX_ANTHOR] = {0}; // 存储从基站和标签之间的所有距离

char dist_str[16] = {0}; // 定义显示距离的字符串
unsigned long time_count = 0; // 定义时间计数器
unsigned long Tag_receive_poll = 0; // 标记是否接收到轮询
uint16 Dest_Address =  DEST_BEGIN_ADDR; // 定义目标地址

/* 私有函数 ---------------------------------------------------------*/
void Tx_Simple_Rx_Callback()
{
    uint32 status_reg = 0,i=0; // 定义状态寄存器和计数变量
    uint32 final_tx_time; // 定义最终传输时间

    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_recv = (srd_msg_dsss*)rx_buffer; // 转换接收缓冲区为消息格式
            msg_f_send.destAddr[0] = msg_f_recv->sourceAddr[0]; // 设置发送消息的目标地址
            msg_f_send.destAddr[1] = msg_f_recv->sourceAddr[1];
            msg_f_send.seqNum = msg_f_recv->seqNum; // 设置发送消息的序列号

            switch(msg_f_recv->messageData[0]) // 根据收到的消息类型进行处理
            {
                case 'A': // 收到轮询确认消息
                {
                    /* 检索轮询传输和响应接收时间戳。 */
                    resp_rx_ts = get_rx_timestamp_u64(); // 获取响应接收时间戳
                    final_tx_time =   dwt_readsystimestamphi32()  + 0x17cdc00/80; // 设置最终传输时间

                    dwt_setdelayedtrxtime(final_tx_time); // 设置延迟传输时间

                    /* 最终传输时间戳是我们编程的传输时间加上TX天线延迟。 */
                    final_tx_ts = (((uint64)(final_tx_time & 0xFFFFFFFE)) << 8); // 计算最终传输时间戳

                    msg_f_send.messageData[0]='F'; // 设置消息为最终消息
                    /* 在最终消息中写入所有时间戳。 */
                    final_msg_set_ts(&msg_f_send.messageData[FINAL_MSG_POLL_TX_TS_IDX], poll_tx_ts); // 设置轮询传输时间戳
                    final_msg_set_ts(&msg_f_send.messageData[FINAL_MSG_RESP_RX_TS_IDX], resp_rx_ts); // 设置响应接收时间戳
                    final_msg_set_ts(&msg_f_send.messageData[FINAL_MSG_FINAL_TX_TS_IDX], final_tx_ts); // 设置最终传输时间戳
                    dwt_writetxdata(25, (uint8 *)&msg_f_send, 0) ; // 写入帧数据
                    dwt_writetxfctrl(25, 0); // 设置传输控制
                    dwt_starttx(DWT_START_TX_DELAYED); // 开始延迟传输
                    // 计算并过滤最终距离
                    Final_Distance[(msg_f_send.destAddr[1]<<8)|msg_f_send.destAddr[0] - 1] = (msg_f_recv->messageData[1]*100 + msg_f_recv->messageData[2]); // 计算厘米单位的最终距离
                    Final_Distance[(msg_f_send.destAddr[1]<<8)|msg_f_send.destAddr[0] - 1] = filter(Final_Distance[(msg_f_send.destAddr[1]<<8)|msg_f_send.destAddr[0] - 1],\
                            (msg_f_send.destAddr[1]<<8)|msg_f_send.destAddr[0] - 1); // 过滤最终距离
                    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
                    { }; // 等待传输完成
                }
                break;
                default: // 默认情况不做处理
                    break;
            }
        }
    }
    else // 如果没有成功接收到帧
    {
        if(!(status_reg & SYS_STATUS_RXRFTO)) // 如果不是超时错误
            Tag_receive_poll = 1; // 设置为数据帧干扰
        dwt_write32bitreg(SYS_STATUS_ID, (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR|SYS_STATUS_TXFRS)); // 清除错误状态
    }
}

/************************标签发送数据给基站进行测距*****************************/
void BPhero_Distance_Measure_Specail_ANTHOR(void)
{
    msg_f_send.destAddr[0] = (Dest_Address) & 0xFF; // 设置消息的目标地址的低8位
    msg_f_send.destAddr[1] = ((Dest_Address) >> 8) & 0xFF; // 设置消息的目标地址的高8位

    msg_f_send.seqNum = distance_seqnum; // 设置消息的序列号
    msg_f_send.messageData[0] = 'P'; // 设置消息类型为轮询(Poll)消息

    dwt_writetxdata(12, (uint8 *)&msg_f_send, 0); // 将12字节的消息数据写入UWB模块
    dwt_writetxfctrl(12, 0); // 设置帧控制为12字节

    dwt_starttx(DWT_START_TX_IMMEDIATE); // 立即开始发送数据

    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS)) { }; // 等待直到消息发送完成

    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS); // 清除接收和发送完成标志
    poll_tx_ts = get_tx_timestamp_u64(); // 获取并保存轮询消息的发送时间戳

    for (int i = 0; i < FRAME_LEN_MAX; i++)
    {
        rx_buffer[i] = '\0'; // 清空接收缓冲区
    }

    dwt_enableframefilter(DWT_FF_DATA_EN); // 启用数据帧过滤
    dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS * 10); // 设置接收超时时间
    dwt_rxenable(0); // 启用接收功能

    if (++distance_seqnum == 255)
        distance_seqnum = 0; // 序列号递增,达到255则重置为0
}


/************************************************************************/
/************************************************************************/
/************************标签发送数据给基站******************************/
/********************************BEGIN***********************************/
void TAG_SendOut_Messge(void)
{
    if(Dest_Address == SEPC_ADDRESS)
    {
        Send_Dis_To_Anthor1(); // 如果目标地址是特定地址,发送汇总距离信息到特定基站
    }
    else
    {
        BPhero_Distance_Measure_Specail_ANTHOR(); // 否则,与特定基站进行距离测量
    }
    Dest_Address++; // 递增目标地址,准备下一次通信

    if(Dest_Address == DEST_END_ADDR + 1)
    {
        Dest_Address = SEPC_ADDRESS; // 如果目标地址超过最后一个基站地址,重置为特定地址
    }
}


/************************使用定时器周期性启动测距************************/
/*********************************END***********************************/
void TIM3_Int_Init_timeout(u16 arr)// 初始化TIM3作为定时器,用于超时中断
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure; // 定义时基结构体
    NVIC_InitTypeDef NVIC_InitStructure; // 定义NVIC初始化结构体
    TIM_Cmd(TIM3, DISABLE);  // 关闭TIM3以进行设置
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 使能TIM3的时钟

    arr = arr*10-1; // 根据参数计算自动重载值
    TIM_TimeBaseStructure.TIM_Period = arr; // 设置自动重载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 设置预分频值,决定计时速度
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 设置时钟分割,这里不分割
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 设置为向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 根据指定的参数初始化TIM3的时间基础设置

    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); // 使能TIM3的更新中断
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  // 设置NVIC中断通道为TIM3中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 设置抢占优先级为0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5;  // 设置子优先级为5
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
    NVIC_Init(&NVIC_InitStructure);  // 根据设定初始化NVIC

    TIM_Cmd(TIM3, ENABLE);  // 最后,使能TIM3开始计时
}


void TIM3_IRQHandler(void)   // TIM3定时器的中断服务函数
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  // 检查TIM3更新中断是否发生
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  // 清除TIM3更新中断标志,准备下一次中断
        if( Tag_receive_poll ==  0) // 检查标志位,确定是否存在数据冲突
        {
            dwt_forcetrxoff();  // 强制关闭接收/发送功能,准备发送新的数据
            // 清除上次所有标志(代码被注释掉了)
            // dwt_write32bitreg(SYS_STATUS_ID, (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR|SYS_STATUS_TXFRS));
            TAG_SendOut_Messge();  // 发送定位信息
            if(Dest_Address == DEST_BEGIN_ADDR)
            {
                // 如果目的地址是起始地址,则执行特定操作(当前为空)
            }
            time_count = portGetTickCnt();  // 更新时间计数
            TIM3->ARR = TIM3_ReLoad;  // 重新设置定时器的自动重载值,准备下一次定时
        }
        else // 如果存在数据冲突
        {
            TIM3->ARR = TIM3_Delay_Step*((SHORT_ADDR%10)+1);  // 设置一个基于短地址的随机延时值,用于动态避让
            Tag_receive_poll = 0;  // 重置数据冲突标志位
        }
        TIM_Cmd(TIM3, ENABLE);  // 重新启用TIM3,继续计数直到下一次中断
    }
}


/************************使用定LCD 显示调试距离信息***********************/
/*********************调试完毕关闭LCD显示提高刷新频率*********************/
/********************************BEGIN***********************************/
static void LCD_Display_Distance(void)  // 定义一个静态函数,用于在LCD显示距离信息
{
    char dist_str[16] = {0};  // 定义一个字符数组用于存储距离字符串

    if(Final_Distance[0]>0)  // 如果第一个距离值大于0
    {
        sprintf(dist_str, "an1:%3.2fm      ", (float)Final_Distance[0]/100);  // 格式化第一个距离值为字符串
        OLED_ShowString(0, 2,dist_str);  // 在OLED上第2行显示第一个距离
    }
    
    if(Final_Distance[1]>0)  // 如果第二个距离值大于0
    {
        sprintf(dist_str, "an2:%3.2fm      ", (float)Final_Distance[1]/100);  // 格式化第二个距离值为字符串
        OLED_ShowString(0, 4,dist_str);  // 在OLED上第4行显示第二个距离
    }
    
    if(Final_Distance[2]>0)  // 如果第三个距离值大于0
    {
        sprintf(dist_str, "an3:%3.2fm      ", (float)Final_Distance[2]/100);  // 格式化第三个距离值为字符串
        OLED_ShowString(0, 6,dist_str);  // 在OLED上第6行显示第三个距离
    }
}


static void Send_Dis_To_Anthor1(void)
{
    static int framenum = 0 ; // 初始化静态变量framenum用于追踪消息帧数

    // 以下设置消息的目的地址为0x0001
    msg_f_send.destAddr[0] =(0x0001) &0xFF; // 目标地址的低8位
    msg_f_send.destAddr[1] =  ((0x0001)>>8) &0xFF; // 目标地址的高8位

    msg_f_send.seqNum = distance_seqnum; // 设置消息的序列号

#if 0 // 计算智能车的角度部分,当前被注释掉
    // 部分跟随小车代码,计算角度
    float dis3_constans = DISTANCE3;
    float cos = 0;
    float angle = 0 ;
    float dis1 = (float)Final_Distance[0]/100; // 将距离1转换为米
    float dis2 = (float)Final_Distance[1]/100;  // 将距离2转换为米

    if(dis1 + dis3_constans < dis2 || dis2+dis3_constans < dis1)
    {
    }
    cos = (dis1*dis1 + dis3_constans* dis3_constans - dis2*dis2)/(2*dis1*dis3_constans);
    angle  = acos(cos)*180/3.1415926;
    sprintf(dist_str, "angle: %3.2f m", angle); // 将角度转换为字符串
    OLED_ShowString(0, 6,"			  "); // 清除显示
    OLED_ShowString(0, 6,dist_str); // 显示角度

    if(dis1 > 1)
    {
        if(angle > 110)
        {
            printf("turn right\r\n"); // 角度大于110度时向右转
        }
        else if(angle < 75)
        {
            printf("turn left\r\n"); // 角度小于75度时向左转
        }
        else
        {
            printf("forward\r\n"); // 角度在这两者之间时直行
        }
    }
    else
    {
        printf("stay here\r\n"); // 距离太近时停在原地
    }
#else
    // 将距离信息整个发送到地址为0x0001的基站,数据包以‘M’标记
    msg_f_send.messageData[0]='M'; // 数据包的开始标记'M'
    {
        uint8 len = 0; // 定义长度变量
        uint8 LOCATION_INFO_START_IDX = 1; // 位置信息的起始索引
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = 'm'; // 加入位置信息标记为'm'
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = 'r'; // 加入位置信息标记为'r'
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = 0x02; // 加入位置信息,指定格式或者类型标识为0x02
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = SHORT_ADDR;// 将TAG ID加入消息数据

        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(framenum&0xFF); // 加入帧号的低8位
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((framenum>>8)&0xFF); // 加入帧号的高8位

        // 加入距离信息,将每个距离分成两个字节进行发送
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((Final_Distance[0]>0?Final_Distance[0]:0xFFFF)&0xFF); // 加入第一个距离的低8位
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(((Final_Distance[0]>0?Final_Distance[0]:0xFFFF) >>8)&0xFF); // 加入第一个距离的高8位

        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((Final_Distance[1]>0?Final_Distance[1]:0xFFFF)&0xFF); // 加入第二个距离的低8位
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(((Final_Distance[1]>0?Final_Distance[1]:0xFFFF) >>8)&0xFF); // 加入第二个距离的高8位

        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((Final_Distance[2]>0?Final_Distance[2]:0xFFFF)&0xFF); // 加入第三个距离的低8位
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(((Final_Distance[2]>0?Final_Distance[2]:0xFFFF) >>8)&0xFF); // 加入第三个距离的高8位


        // 如果有超过3个基站,则包括第四个距离信息
        if(MAX_ANTHOR > 3)
        {
            msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((Final_Distance[MAX_ANTHOR-1]>0?Final_Distance[MAX_ANTHOR-1]:0xFFFF)&0xFF);
            msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(((Final_Distance[MAX_ANTHOR-1]>0?Final_Distance[MAX_ANTHOR-1]:0xFFFF) >>8)&0xFF);
        }
        else
        {
            msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)((Final_Distance[0]>0?Final_Distance[0]:0xFFFF)&0xFF);
            msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = (uint8)(((Final_Distance[0]>0?Final_Distance[0]:0xFFFF) >>8)&0xFF);
        }

        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = '\n'; // 加入换行符
        msg_f_send.messageData[LOCATION_INFO_START_IDX + (len++)] = '\r'; // 加入回车符
    }
#endif

    // 发送数据
    dwt_writetxdata(11 + 17,(uint8 *)&msg_f_send, 0) ;  // 写入帧数据
    dwt_writetxfctrl(11 + 17, 0); // 设置传输控制
    dwt_starttx(DWT_START_TX_IMMEDIATE); // 立即开始传输
    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
    { }; // 等待传输完成
    framenum++; // 增加帧数
    LCD_Display_Distance(); // 显示距离信息
}



int tx_main(void)  // 定义tx_main函数,作为主要的发送端程序
{
    OLED_ShowString(0,0,"   51UWB Node"); // 在OLED第0行显示“51UWB Node”,作为设备标题
    sprintf(dist_str, "    Tx Node"); // 将“Tx Node”字符串格式化到dist_str变量中,表示这是一个发送节点
    OLED_ShowString(0,2,dist_str); // 在OLED第2行显示dist_str变量的内容,即“Tx Node”
    OLED_ShowString(0,6,"  www.51uwb.cn"); // 在OLED第6行显示网址“www.51uwb.cn”,可能是产品或公司的网址

    bphero_setcallbacks(Tx_Simple_Rx_Callback); // 设置回调函数为Tx_Simple_Rx_Callback,用于处理接收数据
    dwt_forcetrxoff();  // 强制关闭无线传输的接收和发送功能,准备新的传输任务

    // 定时周期性发送数据
    TIM3_Int_Init_timeout(TIM3_ReLoad); // 初始化TIM3定时器,设置定时周期为TIM3_ReLoad

    while(1) // 进入无限循环
    {
        // 循环体为空,通常意味着程序的主要功能在中断或回调函数中实现
    }
}




#ifdef  USE_FULL_ASSERT
void assert_failed(uint8_t* file, uint32_t line)
{
    // 断言失败处理函数
    // 以下是报告文件名和行号的代码...
    while (1)
    {
    }
}
#endif

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

这段代码是一个较为完整的主程序,用于基于STM32F10x微控制器和Decawave UWB(Ultra-Wideband)模块的定位系统。程序的主要功能是通过UWB技术进行距离测量和定位。下面是对代码的详细解释:

代码结构和功能

  1. 文件和作者信息

    • 代码开头的注释部分包含文件描述、作者、版本、日期以及版权信息。
  2. 包含的头文件

    • #include 指令包含了所需的头文件,如STM32F10x的库文件和Decawave UWB模块的API。
  3. 全局变量和类型定义

    • 定义了用于存储时间戳、距离等信息的变量。
    • 定义了有符号和无符号的64位整数类型,以及32位的无符号整数类型。
  4. 宏定义

    • MAX_ANTHOR 定义了基站的最大数量,以及与之相关的地址范围。
  5. 距离测量相关的函数

    • Tx_Simple_Rx_Callback:接收回调函数,处理接收到的UWB消息并计算距离。

      这段代码是Tx_Simple_Rx_Callback函数的实现,用于处理超宽带(UWB)定位系统中发送节点接收到的消息。函数主要处理接收到的UWB消息,计算时间戳,以及发送回复消息。下面是对代码的详细解释:

      代码解释

      1. 初始化变量
      • uint32 status_reg = 0, i = 0;:定义并初始化用于存储状态寄存器的变量status_reg和循环计数器i
      • uint32 final_tx_time;:定义用于存储最终发送时间的变量。
      1. 禁用接收并读取状态
      • dwt_enableframefilter(DWT_FF_RSVD_EN);:禁用接收功能,启用帧过滤。
      • status_reg = dwt_read32bitreg(SYS_STATUS_ID);:读取并存储系统状态寄存器的值。
      1. 处理接收到的帧
      • if (status_reg & SYS_STATUS_RXFCG):检查是否成功接收到帧(RXFCG标志)。
      • 获取帧长度并从UWB模块的接收缓冲区读取数据。
      1. 解析接收到的消息并发送响应
      • switch(msg_f_recv->messageData[0]):根据接收到的消息类型进行不同的处理。
      • 对于'A'(轮询应答消息)类型的消息,执行以下操作:
        • 获取响应接收时间戳。
        • 计算并设置最终消息的发送时间。
        • 设置最终发送时间戳。
        • 准备并发送最终消息。
        • 计算并保存距离信息。
        • 使用滤波器处理距离数据。
        • 等待直到消息发送完成。
      1. 处理错误和超时
      • 如果未接收到有效消息并且不是因为超时,设置Tag_receive_poll标志表示可能的数据帧干扰。
      • 清除系统状态寄存器的相关标志,准备接收下一个消息。

      总结

      这个函数是UWB系统中发送节点的关键部分,负责接收来自其他节点的消息,根据这些消息计算时间戳,进而估算距离。它还处理了发送响应消息的逻辑,以及对接收到的数据进行滤波处理,以提高距离测量的准确性。此外,函数还包含了处理潜在的通信干扰和错误的逻辑。

    • BPhero_Distance_Measure_Specail_ANTHOR:用于与特定基站进行距离测量。

      这段代码是BPhero_Distance_Measure_Specail_ANTHOR函数的实现,它是超宽带(UWB)定位系统中用于与特定基站进行距离测量的函数。下面是对代码的详细解释:

      代码解释

      1. 设置目标地址
      • msg_f_send.destAddr[0] = (Dest_Address) & 0xFF;
      • msg_f_send.destAddr[1] = ((Dest_Address) >> 8) & 0xFF;:这两行代码设置消息的目标地址。Dest_Address变量表示目标基站的地址,它被分解为两个字节。
      1. 配置消息内容
      • msg_f_send.seqNum = distance_seqnum;:设置消息的序列号。
      • msg_f_send.messageData[0] = 'P';:设置消息类型为’P’,代表轮询消息。
      1. 发送消息
      • dwt_writetxdata(12, (uint8 *)&msg_f_send, 0);:写入帧数据到UWB模块。
      • dwt_writetxfctrl(12, 0);:设置帧控制。
      • dwt_starttx(DWT_START_TX_IMMEDIATE);:立即开始发送数据。
      • 循环等待直到消息发送完成。
      1. 准备接收响应
      • poll_tx_ts = get_tx_timestamp_u64();:获取并保存轮询消息的发送时间戳。
      • 清空接收缓冲区。
      • dwt_enableframefilter(DWT_FF_DATA_EN);:启用数据帧过滤。
      • dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS * 10);:设置接收超时。
      • dwt_rxenable(0);:启用接收功能。
      1. 更新序列号
      • if (++distance_seqnum == 255) distance_seqnum = 0;:递增序列号,如果达到255则重置为0。

      总结

      这个函数是UWB系统中的关键部分,负责向特定基站发送轮询消息,用于测量从发送节点到该基站的距离。它设置了目标基站的地址,配置了消息内容,发送轮询消息,并准备接收响应。该函数还处理了序列号的更新和接收超时的设置,确保了系统的连续性和稳定性。

    • TAG_SendOut_Messge:用于发送消息到基站进行距离测量。

      这段代码是TAG_SendOut_Messge函数的实现,用于超宽带(UWB)定位系统中标签节点向基站发送信息。该函数根据当前的目标地址决定是发送汇总的距离信息到特定基站还是与特定基站进行距离测量。下面是对代码的详细解释:

      代码解释

      1. 判断目标地址
      • if (Dest_Address == SEPC_ADDRESS) { ... } else { ... }:这里检查当前的目标地址Dest_Address是否等于特定的地址(SEPC_ADDRESS)。这个特定地址用于控制是否向一个特定基站发送距离汇总信息。
      1. 发送距离汇总信息或进行距离测量
      • Send_Dis_To_Anthor1();:如果目标地址是特定地址,则调用Send_Dis_To_Anthor1函数,该函数负责将汇总的距离信息发送到特定基站。
      • BPhero_Distance_Measure_Specail_ANTHOR();:如果目标地址不是特定地址,则调用BPhero_Distance_Measure_Specail_ANTHOR函数,与特定基站进行距离测量。
      1. 更新目标地址
      • Dest_Address++;:递增目标地址,以便下次发送消息到下一个基站。
      • if (Dest_Address == DEST_END_ADDR + 1) { Dest_Address = SEPC_ADDRESS; }:如果目标地址超过了最后一个基站的地址,就将其重置为特定地址(SEPC_ADDRESS)。这样可以确保标签节点轮流与所有基站通信。

      总结

      这个函数是UWB定位系统中标签节点的关键部分,负责管理与不同基站的通信。它根据当前目标地址决定是发送汇总的距离信息还是进行距离测量,并在所有基站间循环发送消息。这种机制允许系统有效地从多个基站收集距离数据,用于后续的定位计算。

  6. 定时器初始化和中断处理函数

    • TIM3_Int_Init_timeoutTIM3_IRQHandler 用于定时发送距离测量请求。

      这段代码是用于初始化STM32微控制器中的定时器3(TIM3)的函数,主要用于设置定时器的基础配置,并使其能够在指定的时间后产生中断。以下是该函数逐行解释:

      函数定义

      • void TIM3_Int_Init_timeout(u16 arr): 这是一个名为TIM3_Int_Init_timeout的函数,它接受一个名为arr的参数,这个参数与定时器超时时间有关。

      函数内部代码

      1. 变量声明:
      • TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;: 声明一个时基配置结构体。
      • NVIC_InitTypeDef NVIC_InitStructure;: 声明一个嵌套向量中断控制器(NVIC)配置结构体。
      1. 禁用定时器:
      • TIM_Cmd(TIM3, DISABLE);: 在配置之前先禁用TIM3。
      1. 时钟使能:
      • RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);: 使能TIM3的时钟。在STM32中,外设如定时器都需要手动开启时钟才能工作。
      1. 设置计时周期:
      • arr = arr*10-1;: 根据传入的参数计算自动重装载寄存器的值。这里将时间乘以10(可能是根据时钟频率和需要的时间分辨率来设定的)。
      • TIM_TimeBaseStructure.TIM_Period = arr;: 设置定时器的周期,即何时产生一个更新或中断事件。
      • TIM_TimeBaseStructure.TIM_Prescaler = 7199;: 设置预分频器的值。这里的值与时钟频率相关,用于确定定时器的计时频率。
      • TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;: 设置时钟分割。这里设置为不分割。
      • TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;: 设置为向上计数模式。
      1. 初始化定时器:
      • TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);: 用前面设置的参数初始化TIM3的时间基础。
      1. 中断配置:
      • TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );: 使能TIM3的更新中断(当计时器溢出更新时发生)。
      • NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;: 指定TIM3中断。
      • NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;: 设置中断的抢占优先级为0。
      • NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5;: 设置中断的子优先级为5。
      • NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;: 使能中断通道。
      • NVIC_Init(&NVIC_InitStructure);: 根据指定的参数初始化NVIC寄存器。
      1. 使能定时器:
      • TIM_Cmd(TIM3, ENABLE);: 最后使能TIM3,开始计时。

      总结

      这个函数通过配置STM32的定时器3来实现在给定的时间后产生一个中断的功能。它详细设置了定时器的工作周期、频率、计数模式,以及中断的优先级和使能,最终通过启用定时器来开始计时。这种类型的功能在需要精确计时的嵌入式系统应用中十分常见,比如定时采样、定时响应等。

      这段代码是STM32微控制器中TIM3定时器的中断处理函数。它主要用于在定时器达到预设时间时执行特定的任务。以下是该函数逐行的详细解释:

      函数定义

      • void TIM3_IRQHandler(void): 这是TIM3定时器的中断处理函数。

      函数内部代码

      1. 中断事件检查:
      • if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET): 这行代码检查TIM3的更新中断是否发生。如果发生,表示定时器计数到达了预设值。
      1. 清除中断标志:
      • TIM_ClearITPendingBit(TIM3, TIM_IT_Update);: 如果检测到更新中断,该行代码将清除TIM3更新中断标志,以便于定时器可以继续工作而不会被之前的中断影响。
      1. 根据标志位发送信息或进行延时:
      • if( Tag_receive_poll == 0): 检查标志位Tag_receive_poll,如果为0,表示没有数据冲突,可以进行正常的定位信息发送。

        • dwt_forcetrxoff();: 强制关闭接收和发送,准备发送新的消息。
        • TAG_SendOut_Messge();: 调用函数发送定位消息。
        • if(Dest_Address == DEST_BEGIN_ADDR){}: 一个空的条件语句,可能是预留给特定地址处理的代码。
        • time_count = portGetTickCnt();: 更新时间计数,可能用于下次发送或延时。
        • TIM3->ARR = TIM3_ReLoad;: 重新加载定时器的计数值,准备下一次中断。
      • else: 如果Tag_receive_poll不为0,表示存在数据冲突。

        • TIM3->ARR = TIM3_Delay_Step*((SHORT_ADDR%10)+1);: 设置定时器的计数值为一个基于设备地址的随机延时,作为动态避让机制。
        • Tag_receive_poll = 0;: 重置标志位,为下一次中断准备。
      1. 重新启动定时器:
      • TIM_Cmd(TIM3, ENABLE);: 再次启用TIM3,继续计数直到下一次中断。

      总结

      这个中断处理函数用于处理定时器的更新事件。它根据是否存在数据冲突来决定是立即发送定位信息还是延时发送以避免冲突。此函数的操作确保了定时器能够在特定的时间点触发发送定位信息或进行必要的延时,以优化通信和避免可能的数据干扰。

  7. LCD显示和距离信息发送

    • LCD_Display_Distance 函数用于在LCD显示屏上显示距离信息。

      这段代码定义了一个函数LCD_Display_Distance,它的作用是在LCD屏幕上显示距离信息。以下是逐行解释:

      函数定义

      • static void LCD_Display_Distance(void): 定义一个静态函数,仅在声明它的文件内部可见,用于显示距离信息。

      函数内部代码

      1. 变量声明:
      • char dist_str[16] = {0};: 定义一个字符数组dist_str用于存储将要显示的距离字符串,初始化所有元素为0。
      1. 显示第一个距离(an1):
      • if(Final_Distance[0]>0): 如果数组Final_Distance的第一个元素(表示第一个距离)大于0,则执行以下代码。
        • sprintf(dist_str, "an1:%3.2fm ", (float)Final_Distance[0]/100);: 使用sprintf将第一个距离格式化为字符串并存储在dist_str中。距离转换为米单位(假设Final_Distance中的值以厘米为单位)。
        • OLED_ShowString(0, 2,dist_str);: 调用OLED_ShowString函数在OLED屏幕的指定位置(第0列,第2行)显示距离字符串。
      1. 显示第二个距离(an2):
      • if(Final_Distance[1]>0): 如果数组Final_Distance的第二个元素(表示第二个距离)大于0,则执行以下代码。
        • sprintf(dist_str, "an2:%3.2fm ", (float)Final_Distance[1]/100);: 格式化第二个距离并存储在dist_str中。
        • OLED_ShowString(0, 4,dist_str);: 显示第二个距离字符串在OLED屏幕的第0列,第4行。
      1. 显示第三个距离(an3):
      • if(Final_Distance[2]>0): 如果数组Final_Distance的第三个元素(表示第三个距离)大于0,则执行以下代码。
        • sprintf(dist_str, "an3:%3.2fm ", (float)Final_Distance[2]/100);: 格式化第三个距离并存储在dist_str中。
        • OLED_ShowString(0, 6,dist_str);: 显示第三个距离字符串在OLED屏幕的第0列,第6行。

      总结

      这个函数用于显示三个不同的距离值(如果它们大于0)在OLED屏幕上,每个距离值显示在不同的行上。它使用sprintf函数来格式化距离值为字符串,并调用OLED_ShowString来在OLED上显示这些字符串。这种显示功能通常用于实时更新和监控距离信息,如在定位系统或距离感测设备中。

    • Send_Dis_To_Anthor1 用于将距离信息发送到特定的基站。

      这段代码看起来是一个更大程序的一部分,很可能用于无线通信或涉及智能车或机器人的跟踪系统。它用C语言编写,涉及发送和处理位置或移动指令的数据包。以下是代码不同部分的详细解释:

      函数概览

      • 函数名称: Send_Dis_To_Anthor1
      • 目的: 这个函数似乎负责向另一个设备或基站发送距离或位置数据。

      变量初始化

      • static int framenum = 0;:初始化了一个静态变量 framenum,它可能用于跟踪发送的帧数或消息数。作为静态变量,它在函数调用之间保持其值不变。

      设置目标地址

      • 代码设置了消息(msg_f_send.destAddr)的目的地址为 0x0001,将16位地址分成两个8位部分,并存储在数组中。

      序列号

      • 它将 distance_seqnum 分配给 msg_f_send.seqNum,这可能是用于跟踪消息的序列号。

      角度计算块(被注释)

      • #if 0 包围的这部分代码当前不活跃。如果它被激活,它将根据距离(dis1dis2 和一个常数 dis3_constans)使用三角函数计算角度。这个计算看来是为了决定智能车或类似设备转向或移向的方向。
      • 它使用 acos 函数来确定角度,然后根据这个角度做出决策(例如,向右转、向左转、前进或停留)。
      • 这部分还包括使用角度信息更新OLED显示。

      活动数据包准备块

      • 条件编译的另一部分(#else 块)准备了一个用于发送的数据包。它以 ‘M’ 标记,并包含一系列可能与位置或距离有关的测量和标识符。
      • 它包括帧号、标签ID和距离(Final_Distance[0] 到 Final_Distance[2],如果 MAX_ANTHOR > 3 则条件性地包括第四个距离)。
      • 距离作为16位值发送,分成两个8位块。
      • 以换行符和回车符结束消息,这通常用来表示文本或消息的行结束。

      发送数据

      • 调用 dwt_writetxdatadwt_writetxfctrl 分别设置传输数据和控制,然后调用 dwt_starttx 启动传输。
      • 代码在循环中等待,直到传输完成,通过检查系统状态寄存器的 SYS_STATUS_TXFRS 标志来判断。

      传输后

      • framenum++:发送包后帧数增加,为下一条消息做准备。
      • LCD_Display_Distance():在最后调用这个函数,可能是为了更新LCD显示最新的距离或状态信息,虽然这个函数的具体内容没有包括在内。

      注释和观察

      • 代码看起来是实时定位系统(RTLS)相关的一部分,可能涉及UWB(超宽带)标签用于跟踪和导航。
      • 使用 #if 0 是在开发或调试期间轻松切换代码块的一种方式。它通常不用于生产代码。
      • 错误处理、距离验证或如果传输失败会发生什么在这个代码片段中不明显。这些方面对于现实世界应用的健壮性至关重要。
      • 代码注释混合使用英文和看似中文,表明它可能是双语开发环境或团队的一部分。

      这段代码是特定应用的专门部分,要完全理解其上下文需要更多关于它所针对的硬件和系统架构的细节。

  8. 主函数 tx_main

    • 初始化OLED显示屏。

    • 设置回调函数。

    • 使用定时器周期性地发送距离测量请求。

      这段代码定义了一个名为tx_main的函数,看起来是用于初始化一个无线传输节点,并设置其显示和传输行为。以下是逐行的详细解释:

      函数定义

      • int tx_main(void): 定义了一个名为tx_main的函数,返回类型为int

      函数内部代码

      1. OLED显示初始化信息:
      • OLED_ShowString(0,0," 51UWB Node");: 在OLED显示屏的第0行第0列位置显示字符串" 51UWB Node",这似乎是设备的名称或标题。
      • sprintf(dist_str, " Tx Node");: 使用sprintf函数将字符串" Tx Node"格式化到dist_str变量中,表示这个设备是一个发送节点。
      • OLED_ShowString(0,2,dist_str);: 在OLED显示屏的第2行显示dist_str的内容,即" Tx Node"。
      • OLED_ShowString(0,6," www.51uwb.cn");: 在OLED显示屏的第6行显示网址" www.51uwb.cn",可能是设备的官方网站或相关信息。
      1. 设置回调函数和关闭无线传输:
      • bphero_setcallbacks(Tx_Simple_Rx_Callback);: 设置无线传输的回调函数为Tx_Simple_Rx_Callback,这通常用于处理接收到的数据或状态更改。
      • dwt_forcetrxoff();: 强制关闭无线设备的接收和发送功能,准备开始新的发送任务。
      1. 初始化定时器并进入发送循环:
      • TIM3_Int_Init_timeout(TIM3_ReLoad);: 调用TIM3_Int_Init_timeout函数初始化TIM3定时器,TIM3_ReLoad可能是一个定义好的重装载值,用于设定定时器的超时时间。
      • while(1){}: 一个空的无限循环,表示主要的工作可能在中断或回调函数中完成,这里保持程序不退出等待事件。

      总结

      tx_main函数是一个无线传输节点的主要初始化和运行函数。它首先在OLED显示屏上显示节点的信息和状态,然后设置数据接收时的回调处理,并关闭当前的无线传输以准备新的传输任务。随后,它初始化一个定时器用于周期性地发送数据,最后进入一个等待状态,等待定时器中断或接收数据。这样的设计模式在嵌入式系统和无线节点中很常见,特别是在需要周期性或按需发送数据的应用中。

  9. 断言失败处理

    • assert_failed 函数提供了一种调试机制,用于报告断言失败的详细信息。

总结

这个程序是一个UWB基于时间测距(TDoA,Time Difference of Arrival)的定位系统的示例。它涉及发送UWB信号、接收响应、计算飞行时间(ToF)和距离,以及处理和显示这些数据。通过对多个基站的测距,可以实现2D或3D定位。程序还包含了周期性发送测距请求的逻辑,以及可选的LCD显示功能。

bphero_uwb.h

#ifndef BPHERO_UWB_H  // 防止头文件重复引用
#define BPHERO_UWB_H

#include "frame_header.h"  // 包含帧头定义
#include "common_header.h"  // 包含通用头文件定义

// 定义设备角色为基站或标签
#define RX_NODE   // 定义为基站
//#define TX_NODE // 定义为标签,当前被注释

// 基站地址配置,地址从0x0001开始
#ifdef RX_NODE
	#define SHORT_ADDR 0x0001  // 为基站设置短地址
//#define LCD_ENABLE // 如果有LCD显示,则定义此宏,没有则注释
#endif

// 标签地址配置,确保与基站地址不重叠
#ifdef TX_NODE
	#define SHORT_ADDR 0x0005  // 为标签设置短地址
	#define LCD_ENABLE // 如果有LCD显示,则定义此宏,没有则注释
#endif

// 外部变量声明
extern int psduLength ;
extern srd_msg_dsss msg_f_send ; // 定义16位地址的ranging消息帧

// 常量定义
#ifndef SPEED_OF_LIGHT
#define SPEED_OF_LIGHT      (299702547.0)     // 空气中的光速,单位m/s
#endif

/* 接收帧的最大长度 */
#ifndef FRAME_LEN_MAX
#define FRAME_LEN_MAX 127
#endif

/* 天线延迟相关常量定义 */
#ifndef TX_ANT_DLY
#define TX_ANT_DLY 0
#endif

#ifndef RX_ANT_DLY
#define RX_ANT_DLY 32950
#endif

/* 时间戳相关常量定义 */
#define FINAL_MSG_POLL_TX_TS_IDX 2
#define FINAL_MSG_RESP_RX_TS_IDX 6
#define FINAL_MSG_FINAL_TX_TS_IDX 10
#define FINAL_MSG_TS_LEN 4

/* 微秒(uus)到设备时间单元(dtu)转换因子定义 */
#define UUS_TO_DWT_TIME 65536

/* 帧间延迟定义 */
#define POLL_RX_TO_RESP_TX_DLY_UUS 2600
#define RESP_TX_TO_FINAL_RX_DLY_UUS 500
#define FINAL_RX_TIMEOUT_UUS 3300
#define POLL_TX_TO_RESP_RX_DLY_UUS 150
#define RESP_RX_TO_FINAL_TX_DLY_UUS 3000 //2700将会失败
#define RESP_RX_TIMEOUT_UUS 2700

// 全局变量声明
extern uint8 rx_buffer[FRAME_LEN_MAX];  // 接收缓冲区
extern uint16 frame_len ;  // 接收到的帧的长度
extern void BPhero_UWB_Message_Init(void);  // UWB消息初始化函数声明
extern void BPhero_UWB_Init(void);  // UWB设备初始化函数声明

#endif // BPHERO_UWB_H 结束头文件保护

这段代码是一个C语言的头文件(通常以.h结尾),名为bphero_uwb.h。它是为UWB(超宽带)应用编写的,提供了一系列的宏定义、变量声明和函数原型,用于配置和操作UWB设备。以下是对该头文件中各部分的详细解释:

头文件保护

  • #ifndef BPHERO_UWB_H#endif: 这是一个常见的C语言技巧,用于防止头文件内容被重复包含。如果BPHERO_UWB_H未定义,则定义它,并包含整个文件内容,否则跳过内容。

包含的其他头文件

  • #include "frame_header.h" 和其他类似语句:包含了其他相关的头文件,这些文件中可能声明了与UWB设备操作相关的结构、常量或函数。

基站和标签定义

  • #define RX_NODE#ifdef RX_NODE:这部分代码用于根据设备是作为基站还是标签来定义不同的行为。如果设备是基站(RX_NODE),则设置其短地址为0x0001;如果设备是标签(TX_NODE),则设置其短地址为0x0005

外部变量声明

  • extern int psduLength; 和其他类似语句:声明了在其他文件中定义的全局变量,这些变量用于存储UWB通信中的数据长度、消息框架和接收缓冲区等信息。

常量定义

  • #define SPEED_OF_LIGHT 和其他类似语句:定义了一些常量,如光速、最大帧长度、天线延迟等,这些常量用于UWB距离计算和配置。

时间相关宏定义

  • #define UUS_TO_DWT_TIME 和其他与时间相关的宏:定义了与时间相关的转换因子和延迟参数。这些参数对于配置DW1000设备的定时器和处理响应超时非常关键。

函数原型

  • extern void BPhero_UWB_Message_Init(void);extern void BPhero_UWB_Init(void);:声明了在其他文件中定义的函数,这些函数用于初始化UWB消息和设备。

总结

bphero_uwb.h头文件为UWB系统的实现提供了必要的宏定义、外部变量声明和函数原型。它允许在整个项目中共享这些定义和声明,确保UWB设备的配置和操作的一致性。通过定义基站和标签、设置通信参数和声明关键函数,这个头文件为构建UWB应用提供了基础。

bphero_uwb.c

#include "bphero_uwb.h"  // 引入UWB相关的头文件
#include "port.h"        // 引入端口定义相关的头文件
#include <math.h>        // 引入数学运算相关的头文件
int psduLength = 0;  // 定义变量存储PSDU长度
srd_msg_dsss msg_f_send; // 定义结构体存储即将发送的范围测量消息帧
uint8 rx_buffer[FRAME_LEN_MAX];  // 定义接收缓冲区数组
uint16 frame_len = 0;  // 定义变量存储接收到的帧长度

// 初始化UWB消息的内容
void BPhero_UWB_Message_Init(void)
{
    // 设置帧控制字节
    msg_f_send.frameCtrl[0] = 0x1 /* 帧类型为数据帧 */ | 0x40 /* PAN ID压缩 */|0x20 /* 请求ACK */;
    msg_f_send.frameCtrl[1] = 0x8 /* 目的地址模式为16位 */ | 0x80 /* 源地址模式为16位 */;
    msg_f_send.panID[0] = 0xF0;  // 设置PAN ID
    msg_f_send.panID[1] = 0xF0;

    // 初始化序列号和消息数据
    msg_f_send.seqNum = 0;
    msg_f_send.messageData[POLL_RNUM] = 3;  // 设置新的范围编号
    msg_f_send.messageData[FCODE] = RTLS_DEMO_MSG_ANCH_POLL;  // 设置消息功能码
    psduLength = (TAG_POLL_MSG_LEN + FRAME_CRTL_AND_ADDRESS_S + FRAME_CRC);

    // 初始化源地址和目的地址
    msg_f_send.seqNum = 0;
    msg_f_send.sourceAddr[0] = SHORT_ADDR & 0xFF; 
    msg_f_send.sourceAddr[1] = (SHORT_ADDR>>8) & 0xFF; 
    msg_f_send.destAddr[0] = 0x01; 
    msg_f_send.destAddr[1] = 0x01; 
}

// 默认通信配置
dwt_config_t config =
{
    2,               /* 信道号 */
    DWT_PRF_64M,     /* 脉冲重复频率 */
    DWT_PLEN_1024,   /* 前导码长度 */
    DWT_PAC32,       /* 前导码获取块大小 */
    9,               /* 发送前导码 */
    9,               /* 接收前导码 */
    1,               /* 使用非标准SFD */
    DWT_BR_110K,     /* 数据速率 */
    DWT_PHRMODE_STD, /* PHY头模式 */
    (1025 + 64 - 32) /* SFD超时(仅接收) */
};

// 初始化UWB设备
void BPhero_UWB_Init(void)
{
    reset_DW1000();  // 重置DW1000设备
    spi_set_rate_low();  // 设置SPI为低速

    dwt_rxreset();  // 重置DW1000接收器
    // 初始化DW1000,失败则持续指示错误
    if(dwt_initialise(DWT_LOADUCODE) == -1) 
    {
        while (1)
        {
            led_on(LED_ALL); // 开启所有LED
            deca_sleep(100); // 等待
            led_off(LED_ALL); // 关闭所有LED
            deca_sleep(100);
        }
    }
    spi_set_rate_high();  // 设置SPI为高速
    dwt_configure(&config);  // 配置DW1000
    dwt_setleds(1);  // 设置LED指示灯
    dwt_SetTxPower(&config);  // 设置发送功率
    dwt_setpanid(0xF0F0);  // 设置PAN ID
    dwt_setaddress16(SHORT_ADDR);  // 设置短地址
    // 设置天线延时
    dwt_setrxantennadelay(RX_ANT_DLY); 
    dwt_settxantennadelay(TX_ANT_DLY);
    // 设置中断
    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);
}

// 计算接收功率
static float calculatePower(float base, float N, uint8_t pulseFrequency)
{
    float A, corrFac;
    // 根据脉冲频率选择相关参数
    if(DWT_PRF_16M == pulseFrequency)
    {
        A = 115.72;
        corrFac = 2.3334;
    }
    else
    {
        A = 121.74;
        corrFac = 1.1667;
    }

    // 计算并返回功率估算值
    float estFpPwr = 10.0 * log10(base / (N * N)) - A;

    // 功率校正
    if(estFpPwr <= -88)
    {
        return estFpPwr;
    }
    else
    {
        // 近似计算修正后的功率值
        estFpPwr += (estFpPwr + 88) * corrFac;
    }

    return estFpPwr;
}

// 获取接收功率
float dwGetReceivePower(void)
{
    dwt_rxdiag_t *diagnostics;
    dwt_readdiagnostics(diagnostics);
    // 获取并处理诊断信息
    float C = (&diagnostics->stdNoise)[3];
    float N = diagnostics->rxPreamCount;

    float twoPower17 = 131072.0;  // 2^17
    // 计算并返回接收功率
    return calculatePower(C * twoPower17, N, config.prf);
}

这段代码涉及初始化和配置UWB(Ultra-Wideband)设备的步骤,以及计算接收信号功率的功能。它使用了一系列的硬件抽象和配置函数,这些函数通常是特定于设备的。以下是对代码各部分的逐行解释:

头文件和全局变量

  • #include语句:引入了必要的头文件,这些头文件中包含了UWB设备操作、端口和数学运算的相关函数和定义。
  • int psduLength = 0;:定义了一个整型变量psduLength,用来存储PSDU(PHY Service Data Unit)的长度。
  • srd_msg_dsss msg_f_send;:定义了一个结构体msg_f_send,用于存储即将发送的范围测量消息帧。
  • uint8 rx_buffer[FRAME_LEN_MAX];:定义了一个接收缓冲区数组rx_buffer
  • uint16 frame_len = 0;:定义了一个变量frame_len,用来存储接收到的帧长度。

UWB消息初始化

  • void BPhero_UWB_Message_Init(void):定义了一个函数,用于初始化UWB消息的内容,包括设置帧类型、地址模式、序列号、目的地址等。

    这段代码定义了一个名为BPhero_UWB_Message_Init的函数,其主要目的是初始化用于UWB通信的消息结构体msg_f_send。以下是逐行的详细解释:

    函数定义

    • void BPhero_UWB_Message_Init(void): 定义了一个无返回值,无参数的函数,用于初始化UWB消息。

    设置帧控制信息

    • msg_f_send.frameCtrl[0] = 0x1 /*frame type 0x1 == data*/ | 0x40 /*PID comp*/|0x20/* ACK request*/;: 设置帧控制字段的第一个字节。其中0x1设置帧类型为数据帧,0x40表示PAN ID压缩,0x20表示需要ACK响应。

    设置地址模式

    • msg_f_send.frameCtrl[1] = 0x8 /*dest extended address (16bits)*/ | 0x80 /*src extended address (16bits)*/;: 设置帧控制字段的第二个字节,其中0x80x80分别设置目的地址和源地址为16位扩展地址模式。

    设置PAN ID

    • msg_f_send.panID[0] = 0xF0; msg_f_send.panID[1] = 0xF0;: 设置PAN ID为0xF0F0

    初始化序列号和消息数据

    • msg_f_send.seqNum = 0;: 初始化消息的序列号为0。
    • msg_f_send.messageData[POLL_RNUM] = 3;: 在消息数据中设置范围编号(POLL_RNUM)为3。
    • msg_f_send.messageData[FCODE] = RTLS_DEMO_MSG_ANCH_POLL;: 设置消息功能码,指明消息是轮询、响应还是其他类型。

    设置PSDU长度

    • psduLength = (TAG_POLL_MSG_LEN + FRAME_CRTL_AND_ADDRESS_S + FRAME_CRC);: 计算并设置PSDU长度。

    设置源地址和目的地址

    • msg_f_send.sourceAddr[0] = SHORT_ADDR & 0xFF; msg_f_send.sourceAddr[1] =(SHORT_ADDR>>8)& 0xFF;: 从SHORT_ADDR中提取源地址,并分为两个字节。
    • msg_f_send.destAddr[0] = 0x01; msg_f_send.destAddr[1] = 0x01;: 设置目的地址为0x0101

    总结

    BPhero_UWB_Message_Init函数负责初始化UWB消息结构体,包括设置帧类型、地址模式、序列号、消息数据和目的地址等,以准备发送。这些设置是UWB通信中必要的步骤,确保消息能够被正确构造并发送到目标设备。

UWB通信配置

  • dwt_config_t config = {...}:定义了一个结构体变量config,用于配置UWB设备的通信参数,如信道、脉冲频率、前导码长度、数据速率等。

    这段代码定义了dwt_config_t类型的变量config,该变量用于配置UWB设备(如Decawave DW1000)的通信参数。dwt_config_t通常是一个结构体,包含了一系列用于设定UWB设备操作模式的字段。以下是对各个字段的逐行解释:

    结构体定义

    • dwt_config_t config = {...};: 定义并初始化一个名为config的结构体变量。

    结构体成员

    1. 信道号:
    • 2,: 设置UWB设备的信道号为2。不同信道对应不同的频率和带宽。
    1. 脉冲重复频率:
    • DWT_PRF_64M,: 设置脉冲重复频率为64MHz。PRF是影响测距精度和范围的关键参数。
    1. 前导码长度:
    • DWT_PLEN_1024,: 设置前导码长度为1024。前导码长度影响接收器的锁定性能和通信范围。
    1. 前导码获取块大小:
    • DWT_PAC32,: 设置前导码获取块大小为32。这个参数与前导码长度相关,用于接收过程中的帧同步。
    1. 发送前导码和接收前导码:
    • 9, 9,: 分别设置发送和接收前导码为编码9。前导码是UWB信号的一部分,有助于接收器进行帧同步和信号质量评估。
    1. 使用非标准SFD:
    • 1,: 设置为使用非标准的Start of Frame Delimiter(SFD)。SFD是帧开始的标记,非标准SFD可能用于特定的应用或与特定的硬件兼容。
    1. 数据速率:
    • DWT_BR_110K,: 设置数据速率为110kbps。较低的数据速率通常可以提供更远的通信距离和更强的信号穿透能力。
    1. PHY头模式:
    • DWT_PHRMODE_STD,: 设置PHY头模式为标准模式。
    1. SFD超时:
    • (1025 + 64 - 32),: 设置SFD超时。这个参数是接收时间的一个限制,用于确定何时放弃等待SFD。

    总结

    config结构体中的这些设置对于UWB设备的性能和行为至关重要。它们影响了设备的通信范围、速率、精度和兼容性。在初始化UWB设备时正确配置这些参数,可以确保设备按照预期的方式运行。

UWB设备初始化

  • void BPhero_UWB_Init(void):定义了一个函数,用于初始化UWB设备。它包括重置设备、设置SPI速率、配置设备参数、设置LED指示灯等步骤。如果初始化失败,它会通过LED指示灯不断闪烁来提示错误。

    这段代码定义了一个名为BPhero_UWB_Init的函数,用于初始化DW1000 UWB模块。以下是逐行的详细解释:

    函数定义

    • void BPhero_UWB_Init(void): 定义了一个无返回值,无参数的函数,用于初始化UWB模块。

    设备重置和SPI速率设置

    • reset_DW1000();: 调用函数重置DW1000 UWB模块,确保从已知状态开始配置。
    • spi_set_rate_low();: 将SPI通信速率设置为低,这通常在初始化期间需要,以确保稳定的通信。

    接收器重置和设备初始化

    • dwt_rxreset();: 重置DW1000的接收器部分,以确保无残留状态或错误。
    • if(dwt_initialise(DWT_LOADUCODE) == -1): 调用dwt_initialise函数来初始化DW1000设备,如果返回-1表示初始化失败。DWT_LOADUCODE参数通常用于指示是否加载特定的微码以支持高级功能。

    错误指示

    • while (1) { ... }: 如果初始化失败,进入一个无限循环,并通过LED灯闪烁来指示错误。

    SPI速率和设备配置

    • spi_set_rate_high();: 初始化成功后,将SPI速率提高以优化性能。
    • dwt_configure(&config);: 使用前面定义的config结构体来配置DW1000的通信参数。

    LED、功率、PAN ID、地址和天线延时设置

    • dwt_setleds(1);: 启用LED指示灯,用于显示设备状态。
    • dwt_SetTxPower(&config);: 设置发送功率,通常根据配置或法规要求。
    • dwt_setpanid(0xF0F0);: 设置网络识别的PAN ID。
    • dwt_setaddress16(SHORT_ADDR);: 设置设备的短地址,用于网络中设备识别。
    • dwt_setrxantennadelay(RX_ANT_DLY);dwt_settxantennadelay(TX_ANT_DLY);: 设置接收和发送天线的延迟,这对于精确的距离测量非常重要。

    中断设置

    • 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);: 配置DW1000的中断,以响应不同的事件和错误。这些中断帮助软件正确响应接收和发送状态,以及错误条件。

    总结

    BPhero_UWB_Init函数是UWB设备的初始化脚本,负责设备的重置、配置、和错误处理。每一步都是确保UWB模块能够按照预期参数运行的关键,包括通信设置、地址分配和中断管理等。在实际应用中,这些配置需要根据具体的应用需求和法规要求进行调整。

功率计算函数

  • static float calculatePower(float base, float N, uint8_t pulseFrequency):定义了一个函数,根据给定的参数计算接收信号的功率。它使用对数函数和一些固定参数来计算功率值。

    这段代码定义了一个名为calculatePower的函数,用于根据给定的基础功率值、脉冲数量以及脉冲频率来估算UWB设备的接收信号功率。以下是逐行的详细解释:

    函数定义

    • static float calculatePower(float base, float N, uint8_t pulseFrequency): 定义了一个静态函数,返回类型为float,接收三个参数:base(基础功率值),N(脉冲数量),pulseFrequency(脉冲频率)。

    参数选择

    • float A, corrFac;: 定义两个浮点型变量,A用于存储固定的衰减因子,corrFac用于存储功率校正因子。
    • if(DWT_PRF_16M == pulseFrequency) { ... } else { ... }: 根据传入的脉冲频率选择不同的参数值。如果脉冲频率是16MHz,则设置AcorrFac为特定值;否则,使用不同的值。这些参数对于估算功率值至关重要。

    功率估算

    • float estFpPwr = 10.0 * log10(base / (N * N)) - A;: 计算接收功率的估算值。这里使用对数函数来根据输入的基础功率值和脉冲数量计算接收信号的功率水平,并从中减去固定的衰减因子。

    功率校正

    • if(estFpPwr <= -88) { return estFpPwr; }: 如果计算出的估算功率值小于或等于-88dBm,则直接返回这个值。这可能是基于实际观察或设备规格确定的阈值。
    • estFpPwr += (estFpPwr + 88) * corrFac;: 如果估算的功率值大于-88dBm,则进行额外的校正计算,使用之前确定的corrFac进行调整。这是一个近似的校正方法,可能基于经验或设备特性。

    返回结果

    • return estFpPwr;: 返回最终计算和校正后的功率值。

    总结

    calculatePower函数是一个基于特定公式和校正方法来估算UWB设备接收信号功率的函数。它根据测量的基础功率、脉冲数量和脉冲频率来计算功率值,并对结果进行校正以提供更准确的估算。这种计算对于理解和调整UWB设备的性能非常重要,特别是在需要精确控制信号强度和质量的应用中。

接收功率获取函数

  • float dwGetReceivePower(void):定义了一个函数,用于获取UWB设备接收信号的功率。它首先从设备中读取诊断信息,然后调用calculatePower函数来计算功率。

    这段代码定义了一个名为dwGetReceivePower的函数,用于获取并计算DW1000 UWB模块的接收信号功率。以下是逐行的详细解释:

    函数定义

    • float dwGetReceivePower(void): 定义了一个无参数的函数,返回类型为float,用于获取接收功率。

    变量定义与诊断信息读取

    • dwt_rxdiag_t *diagnostics;: 定义了一个指向dwt_rxdiag_t类型的指针,该类型是用于存储接收诊断信息的结构体。
    • dwt_readdiagnostics(diagnostics);: 调用函数dwt_readdiagnostics读取接收诊断信息,并将结果存储在diagnostics指针指向的结构体中。

    获取并处理诊断信息

    • float C = (&diagnostics->stdNoise)[3];: 从诊断信息中获取标准噪声级的第四个元素值,并将其赋给变量C。这个值通常与信号的噪声水平有关。
    • float N = diagnostics->rxPreamCount;: 获取接收到的前导码计数值,并将其赋给变量N。前导码计数是接收信号中前导序列的数量,与信号强度有关。

    计算接收功率

    • float twoPower17 = 131072.0;: 定义一个常数,表示(2^{17}),用于功率计算中的缩放。
    • return calculatePower(C * twoPower17, N, config.prf);: 调用calculatePower函数计算接收功率,并返回计算结果。传入的参数是调整后的标准噪声级C、前导码计数N和配置中的脉冲重复频率config.prf

    总结

    dwGetReceivePower函数是用于读取UWB模块的接收诊断信息,并基于这些信息计算接收信号的功率。这个计算涉及到对接收信号的标准噪声水平和前导码计数的测量,以及对结果的数学处理。计算出的接收功率可以用于评估信号质量、调整传输功率或进行其他信号处理操作。

总结

这段代码是UWB设备(如Decawave的DW1000)的初始化、配置和功率计算的集合。代码中使用了许多特定于设备的函数和结构体,这些通常在设备的SDK或API文档中有详细描述。整体来看,这段代码用于设备的基础设置、通信配置和功率测量,是UWB定位或通信系统中常见的操作。

stm32f10x_it.c

//extern void Simple_Rx_Callback(void);  // 外部定义的接收回调函数,当前未使用,已被注释
extern void (*bphero_rxcallback)(void);  // 声明一个外部的函数指针,指向接收处理函数

void EXTI0_IRQHandler(void)  // 定义外部中断0处理函数
{
    if(EXTI_GetITStatus(EXTI_Line0)!= RESET)  // 如果外部中断0的标志位被设置
    {
        (*bphero_rxcallback)();  // 通过函数指针调用回调函数处理中断
        EXTI_ClearITPendingBit(EXTI_Line0);  // 清除外部中断0的标志位以防止再次触发
    }
}

这段代码定义了一个名为EXTI0_IRQHandler的外部中断服务例程(External Interrupt Handler),用于STM32微控制器的第0号外部中断(EXTI0)。以下是逐行的详细解释:

注释掉的外部函数声明

  • //extern void Simple_Rx_Callback(void);: 这是一个被注释掉的外部函数声明,原本可能用于处理接收回调,但在当前上下文中未被使用。

外部函数指针声明

  • extern void (*bphero_rxcallback)(void);: 声明了一个外部的函数指针bphero_rxcallback,它指向一个无返回值、无参数的函数。这意味着bphero_rxcallback可以被指定为任何符合此形式的函数,并在中断发生时被调用。

中断服务例程定义

  • void EXTI0_IRQHandler(void): 定义了一个名为EXTI0_IRQHandler的函数,作为第0号外部中断的处理函数。

中断状态检查和处理

  • if(EXTI_GetITStatus(EXTI_Line0)!= RESET): 检查第0号外部中断线(EXTI_Line0)的中断状态。如果该中断线的中断标志位被设置(即发生了中断事件),则执行大括号内的代码。

调用回调函数

  • (*bphero_rxcallback)();: 通过函数指针调用外部定义的回调函数。这意味着当EXTI0中断发生时,会执行bphero_rxcallback所指向的函数。

清除中断标志位

  • EXTI_ClearITPendingBit(EXTI_Line0);: 清除第0号外部中断线的中断标志位,以确保中断不会再次立即触发,并允许识别新的中断事件。

总结

EXTI0_IRQHandler函数是一个外部中断处理器,用于STM32的第0号外部中断。当EXTI0发生中断时,它会调用bphero_rxcallback指向的函数处理中断事件,然后清除中断标志位。这种中断处理机制常用于响应外部事件,如按钮按下、传感器信号变化等,并使系统能够在事件发生时立即执行相关处理。通过使用函数指针,这个处理器可以非常灵活地关联到不同的回调函数,以适应不同的处理需求。

关于回调函数如何被调用

为了解释Simple_Rx_Callback函数如何被调用,我们需要看看之前提到的几个代码段是如何一起工作的。这包括函数指针的声明、设置回调函数、中断处理程序以及回调函数的调用。以下是整合代码及其调用流程的解释:

1. 函数指针的声明

extern void (*bphero_rxcallback)(void);

这行代码声明了一个名为bphero_rxcallback的全局函数指针,它可以指向任何无参数和无返回值的函数。

这行代码是C语言中的一个外部变量声明,涉及到函数指针的使用。以下是逐部分的详细解释:

extern 关键字

  • extern: 这个关键字用于声明一个变量是在其他地方定义的。在这种情况下,它告诉编译器bphero_rxcallback变量在其他文件中定义,这是一种跨文件引用全局变量的方式。

函数指针

  • void (*bphero_rxcallback)(void);: 这部分定义了一个函数指针bphero_rxcallback。让我们分解一下这个声明:
    • void (...): 表明这个函数没有返回值。
    • (*bphero_rxcallback): 这是函数指针的名称,即bphero_rxcallback
    • (void): 表明这个函数不接受任何参数。

整体含义

整体来看,这行代码声明了一个名为bphero_rxcallback的全局函数指针,该指针可以指向任何不接受参数且没有返回值的函数。在程序中,其他文件可能会定义一个实际的函数,该函数符合上述条件(即无参数无返回值),并将该函数的地址赋给bphero_rxcallback。这样,程序的其他部分就可以通过bphero_rxcallback()语法调用这个回调函数了,而具体调用的是哪个函数则取决于在运行时bphero_rxcallback被设置为指向哪个函数的地址。

应用场景

在事件驱动的编程中,如处理外部中断、接收数据等情况,函数指针作为回调函数非常常见。这允许程序设计者灵活地在运行时决定应该执行哪个函数,从而可以根据不同的事件或状态调用不同的处理函数,而无需改变调用函数的代码。这种设计提高了代码的模块化和复用性。

2. 设置回调函数

// 定义设置回调函数的函数
void bphero_setcallbacks(void (*rxcallback)(void))
{
    bphero_rxcallback = rxcallback; // 将传入的函数指针赋值给全局函数指针变量bphero_rxcallback
}

bphero_setcallbacks(Simple_Rx_Callback); // 调用bphero_setcallbacks函数,将Simple_Rx_Callback设置为接收回调函数

首先,bphero_setcallbacks函数被定义,用于将传入的函数地址赋给bphero_rxcallback函数指针。然后,bphero_setcallbacks(Simple_Rx_Callback)被调用,实际上是将Simple_Rx_Callback函数的地址赋给了bphero_rxcallback。这样,bphero_rxcallback现在指向Simple_Rx_Callback函数。

这段代码涉及到两个主要部分:一个是bphero_setcallbacks函数的定义,另一个是对该函数的调用。以下是逐行的详细解释:

函数定义:bphero_setcallbacks

  • void bphero_setcallbacks(void (*rxcallback)(void)): 定义了一个名为bphero_setcallbacks的函数,它接受一个参数,这个参数是一个指向函数的指针,具体来说,是指向一个没有参数和没有返回值的函数的指针。

函数内部代码

  • bphero_rxcallback = rxcallback;: 这行代码是函数的主体部分。它将传入的函数指针rxcallback赋值给全局变量bphero_rxcallback。此操作实质上是将bphero_rxcallback指向了rxcallback所指向的函数,这样,当需要调用回调函数时,可以通过bphero_rxcallback来调用。

函数调用

  • bphero_setcallbacks(Simple_Rx_Callback);: 这行代码调用了bphero_setcallbacks函数,并将Simple_Rx_Callback作为参数传递给这个函数。Simple_Rx_Callback是定义在程序其他部分的一个函数,它应该满足没有参数和没有返回值的条件。

总结

这段代码的主要作用是提供一种机制,允许在程序的其他部分动态地指定某个函数(在这个例子中是Simple_Rx_Callback)作为回调函数来处理特定事件。通过bphero_setcallbacks(Simple_Rx_Callback);调用,Simple_Rx_Callback函数的地址被赋值给了全局的函数指针bphero_rxcallback。在程序的其他地方,当相关事件发生,如接收到数据,可以通过调用bphero_rxcallback指向的函数来处理该事件,从而实现了回调机制。这使得程序在运行时更加灵活,可以根据需要选择或更改事件处理逻辑。

3. 外部中断处理程序

//extern void Simple_Rx_Callback(void);  // 外部定义的接收回调函数,当前未使用,已被注释
extern void (*bphero_rxcallback)(void);  // 声明一个外部的函数指针,指向接收处理函数

void EXTI0_IRQHandler(void)  // 定义外部中断0处理函数
{
    if(EXTI_GetITStatus(EXTI_Line0)!= RESET)  // 如果外部中断0的标志位被设置
    {
        (*bphero_rxcallback)();  // 通过函数指针调用回调函数处理中断
        EXTI_ClearITPendingBit(EXTI_Line0);  // 清除外部中断0的标志位以防止再次触发
    }
}

这是一个外部中断服务例程。当外部中断0触发时,它会检查中断标志位。如果中断确实发生,它通过调用(*bphero_rxcallback)();来执行Simple_Rx_Callback。因为之前已经通过bphero_setcallbacks(Simple_Rx_Callback)bphero_rxcallback指向了Simple_Rx_Callback函数,所以这里实际上是在调用Simple_Rx_Callback

总结

当系统初始化时,通过bphero_setcallbacks(Simple_Rx_Callback)Simple_Rx_Callback函数设置为回调函数。然后,每当EXTI0外部中断触发,EXTI0_IRQHandler中断服务例程将被调用,它进一步调用(*bphero_rxcallback)(),即Simple_Rx_Callback函数。这是一个典型的中断驱动的回调机制,在事件发生时,如接收到数据,特定的回调函数(在本例中为Simple_Rx_Callback)将被执行,以处理该事件。这种设计模式允许高度灵活和可配置的事件处理。

函数指针

函数指针是C语言中一个指向函数的指针,即它的值是一个函数的地址。这样的指针可以用来调用函数和传递函数作为参数给其他函数。它们在编写可重用代码或回调函数时非常有用。

函数指针的基本语法

在C语言中,函数指针的声明包含了函数的返回类型、函数指针的名字以及函数的参数类型。以下是基本的函数指针声明语法:

返回类型 (*指针变量名)(参数类型);

函数指针的使用

  1. 声明函数指针:首先需要声明一个函数指针,指明它所指向的函数的类型。
  2. 指向函数:然后,将函数指针指向一个具体的函数。
  3. 通过函数指针调用函数:一旦函数指针被赋予了一个函数的地址,就可以通过它来调用这个函数。

代码实例

假设我们有一个简单的函数,该函数接受两个整数参数并返回它们的和。然后我们使用函数指针调用这个函数。

#include <stdio.h>

// 一个简单的函数,计算两个整数的和
int add(int a, int b) {
    return a + b;
}

int main() {
    // 声明一个指向函数的指针
    int (*funcPtr)(int, int);

    // 将funcPtr指向add函数
    funcPtr = add;

    // 使用函数指针调用函数
    int sum = funcPtr(2, 3);
    printf("Sum = %d\n", sum);

    return 0;
}

在这个例子中:

  • int add(int a, int b) 是我们想要通过函数指针调用的函数。
  • int (*funcPtr)(int, int); 声明了一个函数指针funcPtr,它指向一个接受两个int类型参数并返回int类型的函数。
  • funcPtr = add;funcPtr指向add函数。
  • int sum = funcPtr(2, 3); 通过函数指针调用add函数。

函数指针的应用场景

函数指针广泛用于以下场景:

  • 回调函数:允许用户将自己的函数传递给库函数,然后在适当的时候由库函数回调。
  • 事件驱动的编程:在事件发生时调用预先指定的函数。
  • 接口实现:允许变更算法或策略,动态改变调用的函数。
  • 表驱动的代码:函数指针数组可以用于根据条件简洁地选择并执行函数。

总结

函数指针是C语言中一个强大的特性,允许编程更灵活、代码更模块化。它们使得程序可以在不同的上下文中重用函数,提高了代码的通用性和灵活性。

帧过滤

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YRr YRr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值