3、开发环境搭建:Keil/IAR环境配置、STM32CubeMX初始化、驱动库移植
做嵌入式开发,第一步就是搭环境。这事说简单也简单,说烦人也烦人。我见过不少新手,代码写好了,结果卡在编译不过、下载不了这种基础问题上,一查就是环境没配对。
这一章,咱们就把UWB定位的开发环境从头捋一遍。我用的是STM32F4系列,DW1000模块。你手里如果是其他型号,流程一样,个别引脚改一下就行。
3.1 工具链选型:Keil还是IAR?
先解决选择困难症。Keil MDK和IAR Embedded Workbench,这两家我都用过。说实话,没有绝对的好坏,只有习惯问题。
| 对比项 | Keil MDK | IAR EWARM |
|---|---|---|
| 编译器 | ARMCC v6(基于Clang) | IAR C/C++ Compiler |
| 代码体积优化 | 中等,v6比v5好很多 | 公认的优化能力强,尤其对速度 |
| 调试器支持 | J-Link、ST-Link原生支持 | J-Link、ST-Link、I-jet |
| 工程管理 | 分组管理,直观 | 层级目录,适合大项目 |
| License价格 | MDK-Plus约$2000 | EWARM约$3000+ |
| 国内生态 | 教程多,资料好找 | 相对少,但官方文档更详细 |
我个人习惯用Keil。原因很简单:STM32CubeMX直接生成Keil工程,省事。而且国内做UWB定位的同行,十有七八都用Keil,遇到问题好交流。
3.2 Keil环境配置要点
安装Keil MDK就不细说了,一路Next就行。但有几个关键点,我踩过坑,得提醒你。
3.2.1 器件包安装
装完MDK后,第一件事不是建工程,而是装器件包(Pack)。
- 打开Keil,点击菜单栏 Pack Installer 图标。
- 在搜索框输入你的芯片型号,比如
STM32F407VG。 - 勾选对应的 Device Family Pack,点击Install。
嗯,这里要注意:Pack版本别追新。我遇到过用最新版Pack编译老工程,结果一堆警告。建议选和CubeMX生成的版本一致。
3.2.2 编译器版本切换
Keil MDK v5默认用ARMCC v5,v6开始用ARMClang。两者语法有差异,尤其是内联汇编和__attribute__写法。
切换方法:
Project → Options for Target → Target → ARM Compiler → 选择 Use default compiler version 5。
3.2.3 下载器配置
我用ST-Link比较多,便宜又稳定。配置如下:
- 点击 Options for Target → Debug。
- 选择 ST-Link Debugger。
- 点击 Settings,确认SWD模式,速度选 4MHz(别选太高,容易不稳定)。
- 切换到 Flash Download 标签,勾选 Reset and Run。
关键一步:勾选Reset and Run后,下载完程序会自动复位运行。不然每次都要手动按复位键,调试时很烦。
3.3 STM32CubeMX初始化
CubeMX是ST官方的图形化配置工具。说白了,就是帮你生成初始化代码,省得自己手写寄存器。
3.3.1 新建工程
- 打开CubeMX,点击 New Project。
- 在 Part Number Search 输入你的芯片型号,双击选中。
- 进入 Pinout & Configuration 界面。
3.3.2 时钟树配置
UWB定位对时间精度要求高,时钟必须稳。我一般这样配:
- HSE:外部8MHz晶振
- PLL:倍频到168MHz(F407最高主频)
- APB1:42MHz(SPI、I2C用)
- APB2:84MHz(GPIO、USART用)
在 Clock Configuration 标签页,把HCLK输入168,回车,系统会自动帮你算好分频系数。
3.3.3 外设配置
UWB模块需要的外设不多,但每个都得配对。
| 外设 | 用途 | 配置要点 |
|---|---|---|
| SPI1 | 与DW1000通信 | 全双工主机,CPOL=0, CPHA=0,数据位8,MSB First |
| USART1 | 串口打印调试 | 波特率115200,8N1 |
| GPIO | DW1000复位/中断 | 推挽输出/浮空输入 |
| TIM2 | 系统心跳定时器 | 1ms中断,用于超时管理 |
具体操作:在 Pinout 界面,找到SPI1,模式选 Full-Duplex Master。然后手动分配片选引脚(NSS用软件控制,别用硬件自动)。
3.3.4 生成代码
点击 Project Manager 标签:
- Project Name:填你的项目名,比如
uwb_location - Project Location:选个路径,别用中文
- Toolchain / IDE:选 MDK-ARM V5
- Generate Under Root:勾上,这样所有文件都在一个目录下
最后点 GENERATE CODE,等几秒,工程就生成了。
3.4 驱动库移植
驱动库移植,说白了就是把DW1000的官方驱动塞进你的工程里。我用的驱动库是Decawave官方的 deca_driver,版本v2.12。
3.4.1 文件结构
先看看驱动库长什么样:
deca_driver/
├── deca_device_api.c // DW1000底层API
├── deca_device_api.h
├── deca_port.h // 平台移植接口(重点)
├── deca_spi.c // SPI读写实现(需要你改)
├── deca_spi.h
├── deca_irq.c // 中断处理
└── deca_irq.h
把整个 deca_driver 文件夹复制到你的Keil工程目录下。然后在Keil里添加分组:
- 右键工程 → Manage Project Items
- 新建分组
DW1000_Driver - 把上面那些.c文件加进去
3.4.2 平台移植接口
这是移植的核心。打开 deca_port.h,你会看到一堆宏定义和函数声明。你需要实现这几个:
// SPI读写函数
int writetospi(uint16_t headerLength, const uint8_t *headerBuffer,
uint16_t bodyLength, const uint8_t *bodyBuffer);
int readfromspi(uint16_t headerLength, const uint8_t *headerBuffer,
uint16_t readLength, uint8_t *readBuffer);
// 延时函数
void deca_sleep(unsigned int time_ms);
// 中断回调
void deca_irq_handler(void);
我一般这样实现SPI读写:
int writetospi(uint16_t headerLength, const uint8_t *headerBuffer,
uint16_t bodyLength, const uint8_t *bodyBuffer)
{
// 拉低片选
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
// 发送header
HAL_SPI_Transmit(&hspi1, (uint8_t *)headerBuffer, headerLength, 100);
// 发送body
if (bodyLength && bodyBuffer)
HAL_SPI_Transmit(&hspi1, (uint8_t *)bodyBuffer, bodyLength, 100);
// 拉高片选
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
return 0;
}
3.4.3 中断配置
DW1000通过IRQ引脚通知MCU有数据到达。在CubeMX里把IRQ引脚配成外部中断,下降沿触发。
// 在stm32f4xx_it.c中
void EXTI0_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(DW_IRQ_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(DW_IRQ_Pin);
deca_irq_handler(); // 调用驱动库的中断处理
}
}
3.4.4 验证移植是否成功
写个简单的测试代码:
#include "deca_device_api.h"
void dw1000_init_test(void)
{
// 复位DW1000
reset_DW1000();
// 读取设备ID
uint32_t dev_id = dwt_readdevid();
// 如果读到0xDECA0130,说明移植成功
if (dev_id == 0xDECA0130)
{
printf("DW1000 detected! ID: 0x%08X\n", dev_id);
}
else
{
printf("Error: Wrong ID 0x%08X\n", dev_id);
}
}
下载到板子上,串口输出 DW1000 detected! ID: 0xDECA0130,就说明环境搭好了。
3.5 本章知识体系
下面这张图,把开发环境搭建的流程串起来了。你照着这个顺序走,不会乱。
3.6 常见问题与避坑
最后,分享几个我实际项目中遇到过的问题,你遇到了别慌。
问题1:编译报错 undefined symbol
原因:驱动库文件没加进工程,或者头文件路径没配。
解决:检查工程分组,确认所有.c文件都在。然后在Options → C/C++ → Include Paths里加上 deca_driver 路径。
问题2:下载后芯片没反应
原因:多半是时钟没起振,或者复位电路有问题。
解决:用示波器量晶振引脚,看有没有8MHz波形。没有的话,检查CubeMX里HSE配置对不对。
问题3:读到的Device ID全是0xFF
原因:SPI通信失败。可能是引脚映射错了,或者片选逻辑反了。
解决:用逻辑分析仪抓SPI波形,确认CS、SCLK、MOSI、MISO都有信号。我曾经因为MOSI和MISO接反了,查了整整一上午。