4. UWB模块驱动开发:SPI通信初始化、模块寄存器配置、收发模式设置

驱动开发,说白了就是让MCU和UWB模块能“说上话”。

我刚开始做这个的时候,以为只要把SPI接上就能跑。结果呢?数据全是乱的。后来才明白,驱动开发有三座大山要翻:SPI通信初始化、模块寄存器配置、收发模式设置。今天咱们就一座一座爬。

核心要点:UWB模块驱动开发 = 打通物理层(SPI) + 配置逻辑层(寄存器) + 设定应用层(收发模式)。缺一不可。

4.1 SPI通信初始化:让MCU和UWB模块“握手”

SPI是同步串行接口,四根线:SCLK、MOSI、MISO、CS。UWB模块通常作为从机,MCU作为主机。

我习惯先确认模块的SPI模式。大多数UWB模块(比如DW1000)支持SPI模式0(CPOL=0, CPHA=0)。

小技巧:初始化前,先拿示波器看模块的CS引脚电平。如果模块在上电后CS被拉高,说明它已经准备好接收SPI命令了。我遇到过几次因为CS没拉高,导致SPI通信失败的坑。

下面是一个典型的SPI初始化代码(以STM32 HAL库为例):

// SPI句柄定义
SPI_HandleTypeDef hspi1;

// SPI初始化函数
void MX_SPI1_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;          // 主机模式
    hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;     // 8位数据
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;   // CPOL=0
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;       // CPHA=0
    hspi1.Init.NSS = SPI_NSS_SOFT;               // 软件管理CS
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 分频
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;      // MSB先行
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 10;
    
    if (HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        Error_Handler(); // 初始化失败处理
    }
}

这里有个细节:波特率预分频。UWB模块的SPI时钟频率通常有限制。DW1000最高支持20MHz,但实际项目中我建议从低速开始(比如2MHz),等通信稳定后再往上提。

注意:SPI时钟频率太高,信号容易受干扰。我在一个项目中用过10MHz,结果在强电磁环境下经常丢数据。降到4MHz后,问题就解决了。别一味追求高速,稳定才是王道。

4.2 模块寄存器配置:读懂UWB模块的“说明书”

SPI打通了,接下来就是配置寄存器。UWB模块内部有一堆寄存器,控制着芯片的各种行为。

我个人习惯把寄存器配置分成三类:

  • 系统配置寄存器:控制芯片工作模式、时钟源、中断使能等。
  • 信道配置寄存器:设定UWB信道、脉冲频率、数据速率等。
  • 帧过滤寄存器:设定接收帧的过滤规则。

以DW1000为例,它的寄存器地址是16位的,数据是32位的。读写操作通过SPI传输,格式如下:

操作 命令字节 地址(2字节) 数据(N字节)
读寄存器 0x00 高字节 + 低字节 读取N字节
写寄存器 0x80 高字节 + 低字节 写入N字节

写一个寄存器的代码示例:

// 写DW1000寄存器
void dw1000_write_reg(uint16_t reg_addr, uint32_t data)
{
    uint8_t cmd_buf[3];
    
    // 构造命令:写操作 + 地址
    cmd_buf[0] = 0x80;                    // 写命令
    cmd_buf[1] = (reg_addr >> 8) & 0xFF;  // 地址高字节
    cmd_buf[2] = reg_addr & 0xFF;         // 地址低字节
    
    // 拉低CS
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
    
    // 发送命令和地址
    HAL_SPI_Transmit(&hspi1, cmd_buf, 3, HAL_MAX_DELAY);
    
    // 发送数据(4字节,小端模式)
    uint8_t data_buf[4];
    data_buf[0] = data & 0xFF;
    data_buf[1] = (data >> 8) & 0xFF;
    data_buf[2] = (data >> 16) & 0xFF;
    data_buf[3] = (data >> 24) & 0xFF;
    HAL_SPI_Transmit(&hspi1, data_buf, 4, HAL_MAX_DELAY);
    
    // 拉高CS
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}

避坑指南:我曾经在写寄存器时忘了检查模块的“忙”状态。结果连续写入过快,模块没反应过来,数据全写歪了。建议每次写寄存器前,先读一下状态寄存器,确认模块空闲。

4.3 收发模式设置:让UWB模块“开口说话”

寄存器配好了,接下来就是设置收发模式。UWB模块通常有三种模式:

  • 发送模式:模块发送UWB信号。
  • 接收模式:模块监听UWB信号。
  • 空闲模式:模块待机,省电。

收发模式的切换,核心是操作模块的系统控制寄存器。以DW1000为例,它的系统控制寄存器地址是0x0D,通过写入不同的值来切换模式:

模式 寄存器值 说明
空闲 0x00000000 停止所有操作
发送 0x00000001 启动发送流程
接收 0x00000002 启动接收流程

发送模式的代码示例:

// 设置发送模式
void dw1000_set_tx_mode(void)
{
    // 先确保模块空闲
    dw1000_write_reg(0x0D, 0x00000000);
    delay_ms(1); // 等待模块稳定
    
    // 写入发送命令
    dw1000_write_reg(0x0D, 0x00000001);
    
    // 检查发送是否启动
    uint32_t status = dw1000_read_reg(0x0F); // 读状态寄存器
    if (status & 0x00000001)
    {
        // 发送启动成功
    }
    else
    {
        // 发送启动失败,处理错误
    }
}

接收模式类似,只是把值换成0x00000002。但有个坑:接收超时。如果模块一直收不到信号,它会一直等下去,浪费功耗。我建议设置一个接收超时时间。

注意:DW1000的接收超时是通过系统控制寄存器的高16位设置的。比如设置超时时间为10ms,需要计算对应的时钟周期数。我踩过这个坑,超时时间设太短,导致正常信号也被丢弃了。

4.4 知识体系总览

下面这张图,把本章的核心逻辑串起来了:

UWB模块驱动开发知识体系 UWB模块驱动开发核心流程 SPI通信初始化 模式0 / 波特率 / CS管理 模块寄存器配置 系统 / 信道 / 帧过滤 收发模式设置 发送 / 接收 / 空闲 系统配置寄存器 信道配置寄存器 帧过滤寄存器 关键提醒 SPI初始化是基础,寄存器配置是核心,收发模式是应用。 每一步都要验证,否则后面定位不准,你都不知道问题出在哪。 驱动开发完成 → 进入UWB定位算法

4.5 调试与验证

驱动写完了,怎么知道对不对?我一般用三步验证法:

  1. 回读验证:写一个寄存器,再读出来,对比数据是否一致。这是最基础的。
  2. 中断验证:配置模块产生一个中断(比如发送完成中断),看MCU能不能收到。
  3. 信号验证:用示波器看SPI总线的波形,确认时序正确。

个人经验:我曾经在回读验证时发现数据总是错位。查了半天,原来是SPI的MSB/LSB设置反了。DW1000要求MSB先行,我设成了LSB先行。这种低级错误,示波器一看就露馅。

嗯,驱动开发这块,说白了就是耐心活。别急,一步一步来,把基础打牢了,后面的定位算法才能跑得稳。