Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
stm32F4
设备驱动
RoboMaster
基于 RT-Thread 的 RoboMaster 电控框架(三)
发布于 2023-09-18 21:26:51 浏览:487
订阅该版
[tocm] # 基于 RT-Thread 的 RoboMaster 电控框架(三) *由于 RT-Thread 稳定高效的内核,丰富的文档教程,积极活跃的社区氛围,以及设备驱动框架、Kconfig、Scons、日志系统、海量的软件包......很难不选择 RT-Thread 进行项目开发。但也正是因为这些优点的覆盖面较广,很多初学者会觉得无从下手,但只要步入 RT-Thread 的大门,你就发现她的美好。这系列文档将作为本人基于 RT-Thread 开发 RoboMaster 电控框架的记录与分享,希望能帮助到更多初识 RT-Thread 的小伙伴,也欢迎大家交流分享,指正不足,共同进步。* ## 背景 使用的开发板为大疆的 RoboMaster-C 型开发板,基础工程为 rt-thread>bsp>stm32f407-robomaster-c ## BMI088模块开发 BMI088 为 robomaster-c 开发板上集成的6轴imu,在此为提高速度陀螺仪和加速度计均使用使用 SPI 通讯方式,驱动主要参考 [Firmament Autopilot Embedded System](https://github.com/Firmament-Autopilot/FMT-Firmware) 该飞控开源项目中的驱动程序在此基础上进行调整。 ### 添加 SPI 通信 API 首先将飞控程序中针对 RT-Thread 的 SPI 设备驱动封装的 SPI 读写函数借鉴过来: ```c #define SPI_DIR_READ 0x80 #define SPI_DIR_WRITE 0x00 /** * This function write a 8 bit reg. * * @param device the SPI device attached to SPI bus * * @param reg Register address * * @param val The value to be written * * @return RT_EOK if write successfully. */ rt_inline rt_err_t spi_write_reg8(rt_device_t spi_device, uint8_t reg, uint8_t val) { uint8_t buffer[2]; rt_size_t w_byte; buffer[0] = SPI_DIR_WRITE | reg; buffer[1] = val; w_byte = rt_spi_transfer((struct rt_spi_device*)spi_device, buffer, NULL, 2); return (w_byte == 2) ? RT_EOK : RT_ERROR; } /** * This function read a 8 bit reg. * * @param device the SPI device attached to SPI bus * * @param reg Register address * * @param buffer Buffer of read data * * @return RT_EOK if read successfully. */ rt_inline rt_err_t spi_read_reg8(rt_device_t spi_device, uint8_t reg, uint8_t* buffer) { uint8_t reg_addr; reg_addr = SPI_DIR_READ | reg; return rt_spi_send_then_recv((struct rt_spi_device*)spi_device, (void*)®_addr, 1, (void*)buffer, 1); } /** * This function read multiple contiguous 8 bit regs. * * @param device the SPI device attached to SPI bus * * @param reg Start register address * * @param buffer Buffer of read data * * @param len The number of read registers * * @return RT_EOK if read successfully. */ rt_inline rt_err_t spi_read_multi_reg8(rt_device_t spi_device, uint8_t reg, uint8_t* buffer, uint8_t len) { uint8_t reg_addr; reg_addr = SPI_DIR_READ | reg; return rt_spi_send_then_recv((struct rt_spi_device*)spi_device, (void*)®_addr, 1, (void*)buffer, len); } ``` 因为C板上 STM32 与 BMI088 是通过 SPI1 相连接,Kconfig 文件中添加 SPI1 部分并使能,并且需要进入到CubeMX 中使能 SPI1,这一步最重要的是选取引脚,这样 RTT 中的 SPI1 设备驱动才能使用: ### BMI088 驱动 主要就是先对 BMI088 上的陀螺仪和加速度计分别进行初始化,设置相关采样参数,需要注意的一点是加速度计上电后默认是 I2C 模式,需要其片选引脚上检测到电平后才会切换为 SPI 模式并保持,如果没有正确处理这一步可能会导致这个现象:上电后读不出来加速度计,reset 后又能读取。 ```c static rt_err_t accelerometer_init(void) { uint8_t accel_id; /* init spi bus */ rt_device_open(accel_spi_dev, RT_DEVICE_OFLAG_RDWR); /* dummy read to let accel enter SPI mode */ spi_read_reg8(accel_spi_dev, BMI088_ACC_BGW_CHIPID, &accel_id); rt_hw_us_delay(1000); spi_read_reg8(accel_spi_dev, BMI088_ACC_BGW_CHIPID, &accel_id); /* read accel id */ spi_read_reg8(accel_spi_dev, BMI088_ACC_BGW_CHIPID, &accel_id); if (accel_id != BMI088_ACC_BGW_CHIPID_VALUE) { LOG_W("Warning: not found BMI088 accel id: %02x", accel_id); return RT_ERROR; } /* soft reset */ spi_write_reg8(accel_spi_dev, BMI088_ACC_SOFTRESET, 0xB6); rt_hw_us_delay(2000); /* dummy read to let accel enter SPI mode */ spi_read_reg8(accel_spi_dev, BMI088_ACC_BGW_CHIPID, &accel_id); /* enter normal mode */ spi_write_reg8(accel_spi_dev, BMI088_ACC_PWR_CTRL, 0x04); rt_hw_us_delay(55000); /* set default range and bandwidth */ accel_set_range(6); /* 6g */ accel_set_sample_rate(800); /* 800Hz sample rate */ accel_set_bwp_odr(280); /* Normal BW */ /* enter active mode */ spi_write_reg8(accel_spi_dev, BMI088_ACC_PWR_CONF, 0x00); rt_hw_us_delay(1000); return RT_EOK; } static rt_err_t gyroscope_init(void) { uint8_t gyro_id; /* init spi bus */ rt_device_open(gyro_spi_dev, RT_DEVICE_OFLAG_RDWR); spi_read_reg8(gyro_spi_dev, BMI088_CHIP_ID_ADDR, &gyro_id); if (gyro_id != BMI088_GRRO_CHIP_ID) { LOG_W("Warning: not found BMI088 gyro id: %02x", gyro_id); return RT_ERROR; } /* soft reset */ spi_write_reg8(gyro_spi_dev, BMI088_BGW_SOFT_RST_ADDR, 0xB6); rt_hw_us_delay(35000); // > 30ms delay gyro_set_range(2000); /* 2000dps */ gyro_set_sample_rate(2000); /* OSR 2000KHz, Filter BW: 230Hz */ /* enable gyroscope */ __modify_reg(gyro_spi_dev, BMI088_MODE_LPM1_ADDR, REG_VAL(0, BIT(7) | BIT(5))); /* {0; 0} NORMAL mode */ rt_hw_us_delay(1000); return RT_EOK; } ``` 这里为了减小陀螺仪的零飘影响,可以对陀螺仪进行校准,得出补偿的值。 ```c static void bmi088_calibrate(void){ static float start_time; static uint16_t cali_times = 5000; // 需要足够多的数据才能得到有效陀螺仪零偏校准结果 float accel[3], gyro[3]; float gyroMax[3], gyroMin[3]; float gNormTemp, gNormMax, gNormMin; static float gyroDiff[3], gNormDiff; int16_t acc_raw[3]; start_time = dwt_get_time_s(); do { if (dwt_get_time_s() - start_time > 20) { // 校准超时 gyro_offset[0] = GxOFFSET; gyro_offset[1] = GyOFFSET; gyro_offset[2] = GzOFFSET; bmi088_g_norm = gNORM; break; } dwt_delay_s(0.005); // 开始时先置零,避免对数据读取造成影响 bmi088_g_norm = 0; gyro_offset[0] = 0; gyro_offset[1] = 0; gyro_offset[2] = 0; for (uint16_t i = 0; i < cali_times; i++) { accel_read_raw(acc_raw); accel[0] = accel_range_scale * acc_raw[0]; accel[1] = accel_range_scale * acc_raw[1]; accel[2] = accel_range_scale * acc_raw[2]; gNormTemp = sqrtf(accel[0] * accel[0] + accel[1] * accel[1] + accel[2] * accel[2]); bmi088_g_norm += gNormTemp; gyro_read_rad(gyro); for(uint8_t j = 0; j < 3; j++){ gyro_offset[j] += gyro[j]; } // 记录数据极差 if (i == 0) { gNormMax = gNormTemp; gNormMin = gNormTemp; for (uint8_t j = 0; j < 3; j++) { gyroMax[j] = gyro[j]; gyroMin[j] = gyro[j]; } } else { if (gNormTemp > gNormMax) gNormMax = gNormTemp; if (gNormTemp < gNormMin) gNormMin = gNormTemp; for (uint8_t j = 0; j < 3; j++) { if (gyro[j] > gyroMax[j]) gyroMax[j] = gyro[j]; if (gyro[j] < gyroMin[j]) gyroMin[j] = gyro[j]; } } // 数据差异过大认为收到外界干扰,需重新校准 gNormDiff = gNormMax - gNormMin; for (uint8_t j = 0; j < 3; j++) gyroDiff[j] = gyroMax[j] - gyroMin[j]; if (gNormDiff > 0.6f || gyroDiff[0] > 1.0f || gyroDiff[1] > 1.0f || gyroDiff[2] > 1.0f) break; LOG_I("gyroDiff: %f",gNormDiff); for(uint8_t j = 0; j < 3; j++){ LOG_D("gyroDiff%d: %f",j ,gyroDiff[j]); } dwt_delay_s(0.0005); } // 取平均值得到标定结果 bmi088_g_norm /= (float)cali_times; LOG_W("bmi088_g_norm: %f",bmi088_g_norm); for (uint8_t i = 0; i < 3; i++) { gyro_offset[i] /= (float)cali_times; LOG_W("gyro_offset: %f",gyro_offset[i]); } cali_count++; } while (gNormDiff > 0.3f || fabsf(bmi088_g_norm - 9.8f) > 0.5f || gyroDiff[0] > 1.0f || gyroDiff[1] > 1.0f || gyroDiff[2] > 1.0f || fabsf(gyro_offset[0]) > 0.01f || fabsf(gyro_offset[1]) > 0.01f || fabsf(gyro_offset[2]) > 0.01f); // 根据标定结果校准加速度计标度因数 accel_scale = 9.81f / bmi088_g_norm; } ``` 由于校准时间较长,并且需要处于稳定的环境下,定期或更换开发板时进行一次校准即可,校准成功后手动修改 GxOFFSET 等宏;通过在 menuconfig 中使能 BSP_BMI088_CALI 进行校准;在串口终端可以查看校准进度,如多次校准失败,适当调大误差范围。 ### 抽象设备 为提高程序的模块化,选用不同传感器时的灵活性,将 bmi088 抽象为 imu一类设备,抽象出 imu_init 、 gyro_read 、 gyro_config、accel_read、accel_config、temp_read 6个操作方法。 ```c struct imu_ops{ rt_err_t (*imu_init)(void); rt_err_t (*gyro_read)(float data[3]); rt_err_t (*gyro_config)(struct gyro_configure cfg); rt_err_t (*accel_read)(float data[3]); rt_err_t (*accel_config)(struct accel_configure cfg); float (*temp_read)(void); }; ``` 项目选用不同的磁力计传感器时,对接这些个接口即可,以 bmi088 为例: ```c struct imu_ops imu_ops = { .imu_init = bmi088_init, .gyro_read = bim088_gyro_read, .gyro_config = bim088_gyro_config, .accel_read = bim088_accel_read, .accel_config = bim088_accel_config, .temp_read = bmi088_temp_read, }; ``` 应用层需要使用磁力计时,调用 imu_ops 中的操作方法即可: ```c /* read data */ float gyro[3],acc[3],temp; imu_ops.gyro_read(gyro); imu_ops.accel_read(acc); temp = imu_ops.temp_read(); /* config */ struct gyro_configure usr_conf_g = GYRO_CONFIG_DEFAULT; struct acc_configure usr_conf_a = ACCEL_CONFIG_DEFAULT; ``` 到此就可以使用imu模块获取传感器原始数据啦 仓库地址放这里了 [HNU_RM_SHARK_C ](https://github.com/Z8MAN8/HNU_RM_SHARK_C) ,觉得不错可以点个 Star ! 感谢阿木实验室开源的基于 RT-Thread 的飞控程序! ## 存在问题及优化方向 1. 目前为了提高性能,imu设备的注册对接形式是比较简陋的; 1. 目前对 imu 设备的抽象只考虑到6轴 imu; 1. 需要注意陀螺仪校准处理部分。
1
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
螺丝松掉的人
这家伙很懒,什么也没写!
文章
42
回答
0
被采纳
0
关注TA
发私信
相关文章
1
编译stm32F407动态模块报错
2
RTT-STUDIO STM32F4 如何配置 开启FPU功能
3
STM32F405RG CAN1发送波特率设置
4
STM32F401RC 用 RT-Thread Studio 烧写不了程序
5
为什么单片机STM32F405RG只能成功烧录一次?
6
rt-thread studio生成的程序写入stm32f405rg无法运行
7
MCU探索版+RT_Thread+Webnet的疑问
8
STM32F407VG启动硬件定时器系统忙类死机
9
STM32F4的虚拟串口 的USB时钟如何配置
10
stm32客户端TCP Client连接不上pc端的TCP Server
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
PWM
cubemx
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
xusiwei1236
8
个答案
2
次被采纳
踩姑娘的小蘑菇
1
个答案
2
次被采纳
用户名由3_15位
7
个答案
1
次被采纳
bernard
4
个答案
1
次被采纳
RTT_逍遥
3
个答案
1
次被采纳
本月文章贡献
聚散无由
2
篇文章
15
次点赞
catcatbing
2
篇文章
5
次点赞
Wade
2
篇文章
4
次点赞
Ghost_Girls
1
篇文章
6
次点赞
YZRD
1
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部