树莓派、STM32使用MAVLink协议传输imu数据
前言
在上下位机的的通讯中,如果遇到需要传输具有复杂格式的数据比如激光雷达数据,imu数据,里程计数据等传感器数据的场景,这时自己编写通讯协议往往十分复杂与耗时。因此这个时候可以使用一些现成的通讯协议来进行传输。MAVLink就是一种非常方便的多平台通讯协议。本文以传输imu数据为例,讲解上下位机如何使用MAVLink协议。
目录
一、下位机(STM32F407ZGT6)MAVLIink配置流程
一、下位机(STM32F407ZGT6)MAVLIink配置流程
1.1 硬件准备
-
开发板:stm32F407zgt6(使用的开发板为轮趣科技公司的C30-2.0D)
-
数据线:USB转Type-c数据线
1.2 软件准备
- keil5软件安装,要求下载F407系列资源包,可以正常配置407的环境
- stm32cubemx安装,本系列所写代码为stm32hal库,运用stm32cubemx可以快速编写基本的所需环境,具体教学可以上b站大学学习,学103系列的hal也可使用,区别在于103系列与407系列的引脚数量与申明不同(ps:有能力的可以使用vscode等其他软件的功能编写)
1.3 驱动安装
- 该代码运用串口1进行数据通讯,也可以使用蓝牙等方法进行通讯,在这不做展示,由于该开发板使用的usb转ttl芯片为ch9102,所以需要提前下载相关驱动包,CH340以及CH系列USB转串口驱动下载_ch9102驱动下载-CSDN博客(注意上位机要安装相关驱动包,此处是以电脑作为上位机,如果以树莓派作为上位机需要安装对应驱动包)
- mavlink库的安装有两种方法
1.如果使用的寻常变量包含在mavlink库中的话,就可以直接去官网里的链接跳转GitHub中下载标准库使用 C MAVLink 库 (mavgen) | MAVLink Guide
2.如果需要自定义变量的话需要用mavgen进行生成,需要注意的是要想使用mavgen需要提前下载python环境。(不过同样的,标准库的mavlink也可以直接用mavgen生成
MAVLink 开发人员指南 | MAVLink Guide
1.4 代码编写
- 使用stm32cubemx生成需要的基本底层代码
1.配置RCC与SYS
编辑
编辑
(黄色警告不影响程序,只是由于部分硬件占用导致部分选项无法选择导致的警告)
2.配置通讯串口
编辑
(可以在右侧面板摁住ctrl来选择串口所使用的引脚)
3.配置CLK
编辑
(大致配置内容为选择HCLK为最高频率,具体参数可以在代码内修改)
4.选择版本与生成
编辑
编辑
- 来到keil5中打开文件
1.将生成好的mavlink库放入keil5文件中,添加文件路径,这时候然后过编译会有很多报错,可以先不管,报错的原因主要是没有mavlink.c以及环境问题
2.首先解决环境问题
点击keil5中的魔术棒,切换到c/c++,修改Misc Controls的内容为
--c99 --no_strict --gnu
编辑
3.接着在加入mavlink.c文件,需要自己编写/******************************************************************************* * 文件名称:mavlink_main.c * * 摘 要:mavlink自定义文件 * * 当前版本: * 作 者: * 日 期:2018/05/30 * 编译环境:keil5 * * 历史信息: *******************************************************************************/ #include "main.h" #include "mavlink.h" #include "mavlink_types.h" #include "bsp_icm20948.h" #include "md_struct.h" #include "ringbuffer.h" #include "stdio.h" extern UART_HandleTypeDef huart1; extern IMU_DATA_t imu_data; extern ATTITUDE_DATA_t imu_attitude; #define MAVLINK_READ_BUFFER_SIZE 128 /*缓冲区管理器*/ //ringbuffer管理变量 RingBuffer m_Mavlink_RX_RingBuffMgr; /*MAVLINK接收数据缓冲区数组*/ uint8_t m_Mavlink_RX_Buff[MAVLINK_READ_BUFFER_SIZE*10]; uint8_t mavlink_byte; /*******************************函数声明**************************************** * 函数名称: void Mavlink_RB_Init(void) * 输入参数: * 返回参数: * 功 能: 初始化一个循环队列,用来管理接收到的串口数据。其实就是一个数据缓冲区 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_RB_Init(void) { //将m_Mavlink_RX_Buffm_Mavlink_RX_RingBuffMgr环队列进行关联管理。 rbInit(&m_Mavlink_RX_RingBuffMgr, m_Mavlink_RX_Buff, sizeof(m_Mavlink_RX_Buff)); } /*******************************函数声明**************************************** * 函数名称: void Mavlink_RB_Clear(void) * 输入参数: * 返回参数: * 功 能: 归零ringbuffer里面的设置。 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_RB_Clear(void) { rbClear(&m_Mavlink_RX_RingBuffMgr); } /*******************************函数声明**************************************** * 函数名称: uint8_t Mavlink_RB_IsOverFlow(void) * 输入参数: * 返回参数: 溢出为1,反之为0 * 功 能: 判断缓冲器是否溢出 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ uint8_t Mavlink_RB_IsOverFlow(void) { return m_Mavlink_RX_RingBuffMgr.flagOverflow; } /*******************************函数声明**************************************** * 函数名称: void Mavlink_RB_Push(uint8_t data) * 输入参数: data:待压入的数据 * 返回参数: * 功 能: 将接收的数据压入缓冲区 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_RB_Push(uint8_t data) { rbPush(&m_Mavlink_RX_RingBuffMgr, (uint8_t)(data & (uint8_t)0xFFU)); } /*******************************函数声明**************************************** * 函数名称: uint8_t Mavlink_RB_Pop(void) * 输入参数: * 返回参数: uint8_t 读出的数据 * 功 能: 从缓冲器读出数据 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ uint8_t Mavlink_RB_Pop(void) { return rbPop(&m_Mavlink_RX_RingBuffMgr); } /*******************************函数声明**************************************** * 函数名称: uint8_t Mavlink_RB_HasNew(void) * 输入参数: * 返回参数: * 功 能: 判断是否有新的数据 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ uint8_t Mavlink_RB_HasNew(void) { return !rbIsEmpty(&m_Mavlink_RX_RingBuffMgr); } /*******************************函数声明**************************************** * 函数名称: uint16_t Mavlink_RB_Count(void) * 输入参数: * 返回参数: * 功 能: 判断有多少个新数据 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ uint16_t Mavlink_RB_Count(void) { return rbGetCount(&m_Mavlink_RX_RingBuffMgr); } /*******************************函数声明**************************************** * 函数名称: void Mavlink_Rece_Enable(void) * 输入参数: * 返回参数: * 功 能: 使能DMA接收 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_Rece_Enable(void) { HAL_UART_Receive_DMA(&huart1, &mavlink_byte, 1); } /*******************************函数声明**************************************** * 函数名称: void Mavlink_Init(void) * 输入参数: * 返回参数: * 功 能: 初始化MAVLINK:使能接收,ringbuffer关联 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_Init(void) { Mavlink_Rece_Enable(); Mavlink_RB_Init(); } /*******************************函数声明**************************************** * 函数名称: void Mavlin_RX_InterruptHandler(void) * 输入参数: * 返回参数: * 功 能: 串口中断的处理函数,主要是讲数据压入ringbuffer管理器 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlin_RX_InterruptHandler(void) { //数据压入 rbPush(&m_Mavlink_RX_RingBuffMgr,mavlink_byte); } /*在“mavlink_helpers.h中需要使用”*/ mavlink_system_t mavlink_system = { 1, 1 }; // System ID, 1-255, Component/Subsystem ID, 1-255 /*******************************函数声明**************************************** * 函数名称: void mavlink_send_uart_bytes(mavlink_channel_t chan, const uint8_t *ch, int length) * 输入参数: * 返回参数: * 功 能: 重新定义mavlink的发送函数,与底层硬件接口关联起来 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void mavlink_send_uart_bytes(mavlink_channel_t chan, const uint8_t *ch, int length) { HAL_UART_Transmit(&huart1, (uint8_t *)ch, length, 2000); } /*******************************函数声明**************************************** * 函数名称: void Mavlink_Msg_Handle(void) * 输入参数: * 返回参数: * 功 能: 处理从QGC上位机传过来的数据信息 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ void Mavlink_Msg_Handle(mavlink_message_t msg) { switch(msg.msgid) { case MAVLINK_MSG_ID_HEARTBEAT: printf("this is heartbeat from QGC/r/n"); break; case MAVLINK_MSG_ID_SYS_STATUS: // osd_vbat = (mavlink_msg_sys_status_get_voltage_battery(&msg) / 1000.0f); //Battery voltage, in millivolts (1 = 1 millivolt) // osd_curr = mavlink_msg_sys_status_get_current_battery(&msg); //Battery current, in 10*milliamperes (1 = 10 milliampere) // osd_battery_remaining = mavlink_msg_sys_status_get_battery_remaining(&msg); //Remaining battery energy: (0%: 0, 100%: 100) break; default: break; } } /*******************************函数声明**************************************** * 函数名称: Loop_Mavlink_Parse(void) * 输入参数: * 返回参数: * 功 能: 在main函数中不间断调用此函数 * 作 者: by Across * 日 期: 2018/06/02 *******************************************************************************/ mavlink_message_t msg; mavlink_status_t status; void Loop_Mavlink_Parse(void) { if(Mavlink_RB_IsOverFlow()) { Mavlink_RB_Clear(); } while(Mavlink_RB_HasNew()) { uint8_t read = Mavlink_RB_Pop(); if(mavlink_parse_char(MAVLINK_COMM_0, read, &msg, &status)) { //信号处理函数 Mavlink_Msg_Handle(msg); //printf("Received message with ID %d, sequence: %d from component %d of system %d", msg.msgid, msg.seq, msg.compid, msg.sysid); } } } ///*测试函数代码区******************************************************************************/ static void mavlink_test_heartbeat2(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) { #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 mavlink_status_t *status = mavlink_get_channel_status(MAVLINK_COMM_0); if ((status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) && MAVLINK_MSG_ID_HEARTBEAT >= 256) { return; } #endif mavlink_heartbeat_t packet_in = { 963497464,17,84,151,218,3 }; mavlink_heartbeat_t packet1, packet2; memset(&packet1, 0, sizeof(packet1)); packet1.custom_mode = packet_in.custom_mode; packet1.type = packet_in.type; packet1.autopilot = packet_in.autopilot; packet1.base_mode = packet_in.base_mode; packet1.system_status = packet_in.system_status; packet1.mavlink_version = packet_in.mavlink_version; #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 if (status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) { memset(MAVLINK_MSG_ID_HEARTBEAT_MIN_LEN + (char *)&packet1, 0, sizeof(packet1)-MAVLINK_MSG_ID_HEARTBEAT_MIN_LEN); } #endif memset(&packet2, 0, sizeof(packet2)); mavlink_msg_heartbeat_send(MAVLINK_COMM_0 , packet1.type , packet1.autopilot , packet1.base_mode , packet1.custom_mode , packet1.system_status ); MAVLINK_ASSERT(memcmp(&packet1, &packet2, sizeof(packet1)) == 0); } static void mavlink_test_highres_imu(uint8_t system_id, uint8_t component_id, mavlink_message_t *last_msg) { #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 mavlink_status_t *status = mavlink_get_channel_status(MAVLINK_COMM_0); if ((status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) && MAVLINK_MSG_ID_HIGHRES_IMU >= 256) { return; } #endif // 使用高分辨率IMU消息结构体 mavlink_highres_imu_t packet1; memset(&packet1, 0, sizeof(packet1)); // 设置时间戳 packet1.time_usec = HAL_GetTick() * 1000; // 使用当前时间作为时间戳 // 设置加速度计数据(浮点数) packet1.xacc = imu_data.accel.x; // 加速度计 X 轴数据 packet1.yacc = imu_data.accel.y; // 加速度计 Y 轴数据 packet1.zacc = imu_data.accel.z; // 加速度计 Z 轴数据 // 设置陀螺仪数据(浮点数) packet1.xgyro = imu_data.gyro.x; // 陀螺仪 X 轴数据 packet1.ygyro = imu_data.gyro.y; // 陀螺仪 Y 轴数据 packet1.zgyro = imu_data.gyro.z; // 陀螺仪 Z 轴数据 // 设置磁力计数据(浮点数) packet1.xmag = imu_attitude.pitch; // 磁力计 X 轴数据 packet1.ymag = imu_attitude.roll; // 磁力计 Y 轴数据 packet1.zmag = imu_attitude.yaw; // 磁力计 Z 轴数据 // 不需要的字段设为0 packet1.abs_pressure = 0.0f; packet1.diff_pressure = 0.0f; packet1.pressure_alt = 0.0f; packet1.temperature = 0.0f; // 设置字段更新标志(只标记我们实际使用的字段) packet1.fields_updated = ((1 << 0) | (1 << 1) | (1 << 2) | // 加速度计 (xacc, yacc, zacc) (1 << 3) | (1 << 4) | (1 << 5) | // 陀螺仪 (xgyro, ygyro, zgyro) (1 << 6) | (1 << 7) | (1 << 8)); // 磁力计 (xmag, ymag, zmag) #ifdef MAVLINK_STATUS_FLAG_OUT_MAVLINK1 if (status->flags & MAVLINK_STATUS_FLAG_OUT_MAVLINK1) { memset(MAVLINK_MSG_ID_HIGHRES_IMU_MIN_LEN + (char *)&packet1, 0, sizeof(packet1)-MAVLINK_MSG_ID_HIGHRES_IMU_MIN_LEN); } #endif // 发送高分辨率IMU消息 mavlink_msg_highres_imu_send(MAVLINK_COMM_0, packet1.time_usec, packet1.xacc, packet1.yacc, packet1.zacc, packet1.xgyro, packet1.ygyro, packet1.zgyro, packet1.xmag, packet1.ymag, packet1.zmag, packet1.abs_pressure, packet1.diff_pressure, packet1.pressure_alt, packet1.temperature, packet1.fields_updated); } void mavlink_test(void) { static uint16_t test_count = 0; mavlink_message_t lastmsg; test_count++; // 5Hz心跳包 if((test_count % 100) == 0) { mavlink_test_heartbeat2(1, 1, &lastmsg); } // 10Hz高分辨率IMU数据 if((test_count % 50) == 0) { mavlink_test_highres_imu(1, 1, &lastmsg); } }3.mavlink缺失的文件在ringbuffer以及dma中(需要的可以去源文件中下载)
#include "ringbuffer.h" /** * @brief rbInitialize初始化配置,将缓冲区信息填入结构体 * @param pRingBuff:ringbuffer结构体 * buff:数据缓冲区 * length:缓冲区大小 * @note * @retval void * @author Acorss工作室 */ void rbInit(RingBuffer* pRingBuff, uint8_t* buff, uint16_t length) { pRingBuff->pBuff = buff; pRingBuff->pEnd = buff + length; pRingBuff->wp = buff; pRingBuff->rp = buff; pRingBuff->length = length; pRingBuff->flagOverflow = 0; } /** * @brief 清除ringbuffer结构体的相关信息 * @param pRingBuff:待处理的ringbuffer * @note * @retval void * @author Acorss工作室 */ void rbClear(RingBuffer* pRingBuff) { pRingBuff->wp = pRingBuff->pBuff; pRingBuff->rp = pRingBuff->pBuff; pRingBuff->flagOverflow = 0; } /** * @brief 压入单字节到缓冲区 * @param pRingBuff:待处理的ringbuffer * value:压入的数据 * @note * @retval void * @author Acorss工作室 */ void rbPush(RingBuffer* pRingBuff, uint8_t value) { uint8_t* wp_next = pRingBuff->wp + 1; if( wp_next == pRingBuff->pEnd ) { wp_next -= pRingBuff->length; // Rewind pointer when exceeds bound } if( wp_next != pRingBuff->rp ) { *pRingBuff->wp = value; pRingBuff->wp = wp_next; } else { pRingBuff->flagOverflow = 1; } } /** * @brief 压出单字节到缓冲区 * @param pRingBuff:待处理的ringbuffer * @note * @retval 压出的数据 * @author Acorss工作室 */ uint8_t rbPop(RingBuffer* pRingBuff) { if( pRingBuff->rp == pRingBuff->wp ) return 0; // empty uint8_t ret = *(pRingBuff->rp++); if( pRingBuff->rp == pRingBuff->pEnd ) { pRingBuff->rp -= pRingBuff->length; // Rewind pointer when exceeds bound } return ret; } /** * @brief 获取缓冲区尚未处理的字节数 * @param pRingBuff:待处理的ringbuffer * @note * @retval 待处理的字节数 * @author Acorss工作室 */ uint16_t rbGetCount(const RingBuffer* pRingBuff) { return (pRingBuff->wp - pRingBuff->rp + pRingBuff->length) % pRingBuff->length; } /** * @brief 判断缓冲区是否为空 * @param pRingBuff:待处理的ringbuffer * @note * @retval 空为1;否则为0 * @author Acorss工作室 */ int8_t rbIsEmpty(const RingBuffer* pRingBuff) { return pRingBuff->wp == pRingBuff->rp; } /** * @brief 判断缓冲区是否空 * @param pRingBuff:待处理的ringbuffer * @note * @retval 满为1;否则为0 * @author Acorss工作室 */ int8_t rbIsFull(const RingBuffer* pRingBuff) { return (pRingBuff->rp - pRingBuff->wp + pRingBuff->length - 1) % pRingBuff->length == 0; }/** ****************************************************************************** * File Name : dma.c * Description : This file provides code for the configuration * of all the requested memory to memory DMA transfers. ****************************************************************************** ** This notice applies to any and all portions of this file * that are not between comment pairs USER CODE BEGIN and * USER CODE END. Other portions of this file, whether * inserted by the user or by software development tools * are owned by their respective copyright owners. * * COPYRIGHT(c) 2018 STMicroelectronics * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of STMicroelectronics nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************** */ /* Includes ------------------------------------------------------------------*/ #include "dma.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /*----------------------------------------------------------------------------*/ /* Configure DMA */ /*----------------------------------------------------------------------------*/ /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /** * Enable DMA controller clock */ void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream3_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn); /* DMA1_Stream6_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn); } /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /** * @} */ /** * @} */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/(ps:此处的dma可以提前在stm32cubemx中打开也可手动编写,个人推荐手动编写)
-
编写主函数
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_DMA_Init(); MX_NVIC_Init(); MX_GPIO_Init(); MX_USART1_UART_Init(); Mavlink_Init(); HAL_Delay(100); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ OLED_Init(); OLED_Clear(); OLED_ShowString(0,0,"Accel:",16,1); OLED_Refresh(); __HAL_RCC_GPIOC_CLK_ENABLE(); /* USER CODE BEGIN WHILE */ while (1) { UserICM20948.Update_9axisVal(&imu_data); UserICM20948.UpdateAttitude(imu_data, &imu_attitude); mavlink_test(); Loop_Mavlink_Parse(); } /* USER CODE END 3 */ }
- 来到keil5中打开文件
1.5 上机测试
-
测试可以一步步进行,先对串口进行简单的收发实验,保证串口能用,(ps:串口问题会遇到的问题主要是软件与硬件,硬件方面检查接线以及设备是否损坏,大概率硬件都会是好的,软件方面要注意系统内部时钟的配置,这一点很容易忽视导致无法发送数据,还有就是校验位频率等参数,上下位机一一对应就行。
-
等串口调好了就可以就可以开始着手mavlink方面,根据报错信息一步步解决,如果同时报很多错就说明缺少了一个重要文件(我会将文件开源,大家可以去下载)
1.6 代码
通过网盘分享的文件:uasr1.6 代码开源t_test1-1.zip
二 、上位机(树莓派5)配置流程
2.1 环境配置
0.系统环境:ubantu24.04, 对应的ros2版本为jazzy
1.ROS配置
推荐先学习 B 站 up 主 小鱼 的 ROS2 入门教程:ROS2入门教程视频
安装ROS2可以用小鱼的ROS2一键安装命令:
wget http://fishros.com/install -O fishros && . fishros
随后跟着小鱼的课程, 只需安装一键安装包即可。
2.python虚拟环境
首先,我们要选定我们的工作区间。在这里我选择将工作区间放在桌面上。
sudo apt update
sudo apt install -y python3-venv
cd /home/a1/Desktop
python3 -m venv /home/a1/Desktop/ros2_venv
然后安装我们需要用到的库pymavlink
# 激活并升级 pip
source /home/a1/Desktop/ros2_venv/bin/activate
pip install --upgrade pip
# 安装pymavlink
pip install pymavlink
2.2 创建ros2功能包
cd /home/a1/Desktop/ROS2/imu_py/src
ros2 pkg create topic_py_1 --build-type ament_python --dependencies rclpy pymavlink
在topic_py_1/topic_py_1下创建topic_publisher.py接着我们开始编写代码
import rclpy
from rclpy.node import Node
from pymavlink import mavutil
from example_ros2_interfaces.msg import Mymsg
import time
class NodePublisher(Node):
def __init__(self,name, com='/dev/ttyACM0', baud=115200):
super().__init__(name)
self.get_logger().info("大家好,我是%s!" % name)
self.command_publisher_ = self.create_publisher(Mymsg,"/imu", 10)
self.connection = mavutil.mavlink_connection(com, baud)
self.timer = self.create_timer(0.1, self.timer_callback)
self.time_count = 0
self.timezz = 0.0
# # 新增:缓存上一帧有效 IMU 数据(初始化为重力 Z)
self._LAST_DATA = (0.0, 0.0, 0.0, 9.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
def get_imu_data1(self, timeout=0.05, flush_limit=20):
"""
在 timeout 时间窗口内非阻塞读取,获取最新一帧 HIGHRES_IMU。
没有新数据则返回上一帧缓存,绝不返回 None。
"""
if self.time_count == 0:
self.timezz = time.time()
self.time_count=1
end_t = time.time() + timeout
flush_count = 0
while time.time() < end_t and flush_count < flush_limit:
msg = self.connection.recv_match(blocking=False)
if msg is None:
time.sleep(0.002)
continue
flush_count += 1
if msg.get_type() == 'HIGHRES_IMU':
ts = float(float(msg.time_usec) / 1e6 + self.timezz)
self._LAST_DATA = (
ts,
float(msg.xacc), float(msg.yacc), float(msg.zacc),
float(msg.xgyro), float(msg.ygyro), float(msg.zgyro),
float(msg.xmag), float(msg.ymag), float(msg.zmag)
)
return self._LAST_DATA
def timer_callback(self):
"""
定时器回调函数
"""
msg = Mymsg()
msg.timestamp ,msg.xacc, msg.yacc, msg.zacc,msg.xgyro,msg.ygyro, msg.zgyro, msg.xmag, msg.ymag, msg.zmag = self.get_imu_data1()
self.command_publisher_.publish(msg)
self.get_logger().info(
f'发布:{msg.timestamp:.2f},{msg.xacc:.2f},{msg.yacc:.2f},{msg.zacc:.2f},'
f'{msg.xgyro:.2f},{msg.ygyro:.2f},{msg.zgyro:.2f},'
f'{msg.xmag:.2f},{msg.ymag:.2f},{msg.zmag:.2f}'
)
def main(args=None):
rclpy.init(args=args)
node = NodePublisher("maclink_IMU")
rclpy.spin(node)
rclpy.shutdown()
将上述代码拷贝进topic_publisher.py
这里用的了一个自定义接口(自己跟着教程学或者下载百度网盘已经打包好的功能包),放到/home/a1/Desktop/ROS2/imu_py/src下
【ROS2机器人入门到实战】ROS2接口介绍_话题接口-CSDN博客
通过网盘分享的文件:example_ros2_interfaces
链接: https://pan.baidu.com/s/1PXJ5gQhRa0dr2Jt6W8mK0g 提取码: knd8
接口协议:
float64 timestamp
float64 xacc
float64 yacc
float64 zacc
float64 xgyro
float64 ygyro
float64 zgyro
float64 xmag
float64 ymag
float64 zmag
float64 relative_ts
然后修改/home/a1/Desktop/ROS2/imu_py/src/topic_py_1/setup.py
from setuptools import find_packages, setup
package_name = 'topic_py_1'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools',
'rclpy',
'pymavlink',],
zip_safe=True,
maintainer='a1',
maintainer_email='a1@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
"pub1=topic_py_1.topic_publisher:main",
],
},
)
最后编译
cd /home/a1/Desktop/ROS2/imu_py
colcon build
这样我们就可以得到一个成功通过mavlink传输的陀螺仪数据,并且以/imu话题发布。
2.3 启动脚本
由于我们的ros2功能包中使用了第三方环境中的库,所以需要特殊的方式启动才能使得运行时程序成功导入库
创建脚本/home/a1/Desktop/run_with_env.sh
#!/bin/bash
# 设置ROS2环境
source /opt/ros/jazzy/setup.bash
source /home/a1/Desktop/ROS2/imu_py/install/setup.bash
# 设置PYTHONPATH包含指定的python包路径
export PYTHONPATH="/home/a1/Desktop/ros2_venv/lib/python3.10/site-packages:$PYTHONPATH"
# 显示当前PYTHONPATH(调试用)
echo "当前PYTHONPATH:"
echo $PYTHONPATH | tr ':' '\n'
echo "------------------------"
# 检查pymavlink是否可用
python3 -c "
try:
from pymavlink import mavutil
print('✓ pymavlink 模块可用')
except ImportError as e:
print('✗ pymavlink 模块不可用:', e)
"
# 运行节点
ros2 run topic_py_1 pub1
然后直接在终端启动该脚本即可
/home/a1/Desktop/run_with_env.sh
以下是输出示例:

数据从左到右依次是:
msg.timestamp 实时时间
msg.xacc 加速度计 X
msg.yacc加速度计 Y
msg.zacc加速度计 Z
msg.xgyro陀螺仪 X
msg.ygyro陀螺仪 Y
msg.zgyro陀螺仪 Z
msg.xmag磁力计 X
msg.ymag磁力计 Y
msg.zmag磁力计 Z
2.4 测试:创建一个订阅者读取imu数据
在topic_py_1/topic_py_1下创建topic_subscribe.py接着我们开始编写代码
import rclpy
from rclpy.node import Node
from example_ros2_interfaces.msg import Mymsg
class NodeSubscribe02(Node):
def __init__(self,name):
super().__init__(name)
self.get_logger().info("大家好,我是%s!" % name)
# 创建订阅者
self.command_subscribe_ = self.create_subscription(Mymsg,"/imu",self.command_callback,10)
def command_callback(self,msg):
self.get_logger().info(f'收到了指令:{msg.timestamp:.2f},{msg.xacc:.2f},{msg.yacc:.2f},{msg.zacc:.2f},{msg.xgyro:.2f},{msg.ygyro:.2f},{msg.zgyro:.2f},{msg.xmag:.2f},{msg.ymag:.2f},{msg.zmag:.2f}') #打印一下发布的数据
def main(args=None):
rclpy.init(args=args)
node=NodeSubscribe02("sub1")
rclpy.spin(node)
rclpy.shutdown()
接着修改/home/a1/Desktop/ROS2/imu_py/src/topic_py_1/setup.py
from setuptools import find_packages, setup
package_name = 'topic_py_1'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools',
'rclpy',
'pymavlink',],
zip_safe=True,
maintainer='a1',
maintainer_email='a1@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
"pub1=topic_py_1.topic_publisher:main",
"sub1=topic_py_1.topic_subscribe:main"
],
},
)
然后先启动发布者,在启动订阅者
cd /home/a1/Desktop/ROS2/imu_py
colcon build
# 开启发布者
/home/a1/Desktop/run_with_env.sh
# 设置ROS2环境
source /home/a1/Desktop/ROS2/imu_py/install/setup.bash
# 开启订阅者
ros2 run topic_py_1 sub1
输出示例:

2.5 代码
通过网盘分享的文件:example_ros2_interfaces
https://pan.baidu.com/share/init?surl=PXJ5gQhRa0dr2Jt6W8mK0g
https://pan.baidu.com/share/init?surl=PXJ5gQhRa0dr2Jt6W8mK0g 提取码: knd8
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)