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 知识体系总览
下面这张图,把本章的核心逻辑串起来了:
4.5 调试与验证
驱动写完了,怎么知道对不对?我一般用三步验证法:
- 回读验证:写一个寄存器,再读出来,对比数据是否一致。这是最基础的。
- 中断验证:配置模块产生一个中断(比如发送完成中断),看MCU能不能收到。
- 信号验证:用示波器看SPI总线的波形,确认时序正确。
个人经验:我曾经在回读验证时发现数据总是错位。查了半天,原来是SPI的MSB/LSB设置反了。DW1000要求MSB先行,我设成了LSB先行。这种低级错误,示波器一看就露馅。
嗯,驱动开发这块,说白了就是耐心活。别急,一步一步来,把基础打牢了,后面的定位算法才能跑得稳。