《机器人操作系统(ROS)浅析》笔记

2023-05-16

机器人操作系统(ROS)浅析

这是看了《A Gentle Introduction to ROS 》这本书后记的笔记,网上刚好找得到中文版的,就看中文版了,欢迎大佬批评指正,如需书的pdf可留下邮箱。

1.文件架构

工作空间

  • 顾名思义就是进行操作的地方,放置有编译空间,
  • 初始化工作空间:在src文件夹下创建了一个 CMakeLists.txt 的文件,目的是告诉系统,这个是ROS的工作空间

为了可以正常使用它,我们需要设置环境变量,让环境变量在所有终端都可以使用

  • src文件下放置你所需要的功能包

功能包

  • 功能包的创建
    • 会自动生成src文件和CMakeLists.txt、package.xml文件
catkin_creat_package [功能包名字] [依赖]
依赖也可以在CMakeLists.txt、package.xml文件中进行添加
  • package.xml
    • 我们可以在这里添加我们需要的运行依赖、编译依赖
  • CMakeLists.txt
    • 规定了依赖哪些功能包,编译生成什么文件,如何编译等流程
    • 当我们在工作空间目录下进行编译时,catkin编译系统首先会找到每个package下的 CMakeLists.txt ,然后按照规则来编译构建。

2.节点

2.1节点管理器ROS Master

作用

  • ROS的通讯是基于节点来实现的,而ROSMaster起到了统一管理的作用
  • 节点之间需要能够互相通信,而实现这一步最关键的就是ROSMaster

使用

  • 必须roscore启动节点管理器
  • 使用rosrun启动节点只能开启一个节点,后面将会介绍roslaunch来启动节点,与rosrun不同的是,launch文件可以启动同时启动多个节点,并且会开启节点管理器

2.2节点

ROSwiki:一旦启动roscore后,便可以运行ROS程序了。ROS程序的运行
实例被称为节点(node)

使用rosrun命令来启动节点

rosrun [功能包名称] [节点名称(一般是生成的可执行文件)]
  • 如果该节点是发布消息的,那么就需要定义好消息的话题,数据类型
  • 如果该节点是接受消息的,那么就需要写明要接受的话题

查看节点

查看节点列表 rosnode list
查看特定节点 rosnode info node-name
终止节点 rosnode kill node-name

查看节点构成图

rqt_graph

在这个命令中,r 代表 ROS,qt 指的是用来实现这个可视化程序的 Qt 图形界面(GUI)工具包。输入该命令之后,你将会看到一个图形界面,其中大部分区域用于展示当前系统中的节点。

  • 注意:rqt_graph本身就是一个节点

3.话题和消息

节点管理器的作用的地方就是节点与节点之间的话题和消息

  • 获取当前活跃话题:rostopic list
  • 为 了 查 看 某 个 话 题 上 发 布 的 消 息 :rostopic echo topic-name
  • 查看消息类型:rosmsg show message-type-name

4.编写ROS程序

1.Hello_ROS

1.创建工作空间和功能包

2.在工作空间下创建src文件来放置功能包

3.按照自己需要的功能编写代码,注意:

  • 头文件需要包含ros/ros.h
  • 如果包含了消息类型就需要包含指定的头文件

4.代码完成后开始配置文件

  • CMakeLists.txt
    • 需要写明文件包含了哪些头文件
    • 添加文件编译选项
  • package.xml
    • 添加文件的编译运行依赖

5.进行编译

  • 如果是第一次进行编译,需要source一下环境

    source devel/setup.bash
    

6.运行

  • roscore启动节点管理器

  • rosrun启动节点

贴上代码

 // This is a ROS version of the standard "hello , world"
 // program.

 // This header defines the standard ROS classes .
 #include <ros/ros.h>

 int main ( int argc , char **argv ) {
 // Initialize the ROS system .
 ros::init ( argc , argv , "hello_ros" ) ;

 // Establ ish this program as a ROS node .
 ros::NodeHandle nh ;

 // Send some output as a log message .
 ROS_INFO_STREAM( "Hello,ROS!" ) ;
 }

2.发布者程序

2.1代码部分

  • 因为该程序中包含了消息类型,因此我们需要添加头文件
  • 用双分号(::)来区分开包名和类型名,双分号也称为范围解析运算符
  • main函数中首先进行初始化,并定义节点名称
  • 接着创建句柄
    • 句柄的作用是为了更方便的开启和关闭节点
  • 创建发布者对象
    • ros::Publisher pub = node_handle.advertise<message_type>(
      topic_name, queue_size);
    • 队列长度不是指存放那个多少字节,而是发布消息序列的大小
  • 进入循环( ros::ok () 检测的是程序作为 ROS 节点是否仍处于运行良好的状态。它会一直返回 true)
    • 创建消息对象
    • 发布消息
    • 定义消息发布格式

2.2编译部分

修改两个文件中的声明

贴上代码

 // This program publishes randomly−generated velocity
 // messages for turtlesim .
 #include <ros/ros.h>
 #include <geometry_msgs/Twist.h> // For geometry_msgs:: Twist
 #include <stdlib.h> // For rand() and RAND_MAX

 int main ( int argc , char **argv ) {
 // Initialize the ROS system and become a node .
 ros::init ( argc , argv , "publish_velocity" ) ;
 ros::NodeHandle nh ;

 // Create a publisher obj ect .
 ros::Publisher pub = nh.advertise <geometry_msgs::Twist >(
 "turtle1/cmd_vel" , 1000) ;

 // Seed the random number generator .
 srand ( time ( 0 ) ) ;

 // Loop at 2Hz until the node is shut down.
 ros::Rate rate(2);
 while ( ros::ok () ) {
 // Create and fill in the message . The other four
 // fields , which are ignored by turtl esim , default to 0.
 geometry_msgs::Twist msg;
 msg.linear.x = double (rand())/double(RAND_MAX) ;
 msg.angular.z = 2*double(rand())/double(RAND_MAX)-1;

 // Publish the message .
 pub.publish(msg);

 // Send a message to rosout with the details .
 ROS_INFO_STREAM( "Sending random velocity command : "
 << " linear=" << msg.linear.x
 << " angular=" << msg.angular.z) ;

 // Wait untilit's time for another iteration .
 rate.sleep ( ) ;
 }
}

3.订阅者程序

与发布者差不多,下边介绍发布者中没有的内容

  • 回调函数:订阅者功能实现的地方

给ROS控制权

  • ros::spinOnce():要求ROS执行所有挂起的回调函数,然后返回控制权
    ros::spin():要求 ROS 等待并且执行回调函数,直到这个节点关机。
    换句话说,ros::spin()大体等于这样一个循环:
    while(ros::ok( ))
    {
    ros::spinOnce();
    }
  • 使用 ros::spinOnce()还是使用 ros::spin()的建议如下:你的程
    序除了响应回调函数,还有其他重复性工作要做吗?如果答案是
    “否”,那么使用 ros::spin();否则,合理的选择是写一个循环,做
    其他需要做的事情,并且周期性地调用 ros::spinOnce()来处理回调。

贴上代码

 // This program subscribes to turtle1/pose and shows its
 // messages on the screen .
 #include <ros/ros.h>
 #include <turtlesim/Pose.h>
 #include <iomanip> // for std::setprecision and std::fixed

 // A callback function . Executed each time a new pose
 // message arrives .
 void poseMessageReceived (const turtlesim::Pose &msg ) {
 ROS_INFO_STREAM( std::setprecision(2) << std::fixed
 << "position =("<< msg.x << "," << msg.y << ")"
 << "*direction=" << msg.theta ) ;
 }

 int main ( int argc , char **argv ) {
 // Initialize the ROS system and become a node .
 ros::init ( argc , argv , "subscribe_to_pose" ) ;
 ros::NodeHandle nh ;

 // Create a subscri ber object .
 ros::Subscriber sub = nh.subscribe ("turtle1/pose" ,1000,
 &poseMessageReceived ) ;

 // Let ROS take over .
 ros::spin ( ) ;
 }

5.日志消息

1.级别

DEBUG、INFO、WARN、ERROR、FATAL

  • 级别旨在提供一种区分和管理日志消息的全局方法

2.生成日志消息

  • 生成简单的日志消息

    #这些都是宏
    ROS_DEBUG_STREAM(message);
    ROS_INFO_STREAM(message);
    ROS_WARN_STREAM(message);
    ROS_ERROR_STREAM(message);
    ROS_FATAL_STREAM(message);
    
  • 生成一次性日志消息

    ROS_DEBUG_STREAM_ONCE(message);
    ROS_INFO_STREAM_ONCE (message);
    ROS_WARN_STREAM_ONCE (message);
    ROS_ERROR_STREAM_ONCE (message);
    ROS_FATAL_STREAM_ONCE (message);
    

3.查看日志消息

rqt_console:显示所有结点的日志消息

4.检查和清除日志文件

  • 查看当前账户中被ROS日志消耗的硬盘空间 :rosclean check

  • 如果日志正在消耗过多的硬盘空间,可以通过下面的命令删除所有已经存在的日志:rosclean purge

6.launch文件

launch文件的作用

  • 同时启动节点管理器和多个节点

  • 启动方式

    roslaunch package-name launch-file-name
    

如何编写launch文件

1.根元素

<launch>
...
<launch>

2.写节点

<node
pkg=package-name”
type=executable-name”
name=node-name”
/>

注意:如果有remap或在param元素这样的子节点,就需要加上</node>标签,称为显式结束标签
<node pkg=”...” type=”...” name=”...”></node>

3.节点中的元素

  • pkg、type就不在这多讲了
  • name给节点指派了名称,可以覆盖其他赋予节点的名称
  • output=screen” 可以在控制台中输出信息(等校于roslaunch –screen package-name launch-file-name)
  • respawn=“true” 请求复位,可以在节点停止的时候重启该节点,常常用于必要节点
  • required=”true”在一个必要节点关闭的时候可以关闭其他节点

launch文件的缺点:对于launch文件启动的节点是共用一个终端的,如果有需要控制台控制的节点则建议使用rosrun单独启动,例如键盘控制节点

  • 节点元素使用启动前缀(launch-prefix)属性:launch-prefix=“command-prefix”

  • xterm–e rosrun turtlesim turtle_teleop_key,xterm-e会开启一个终端窗口来运行rosrun的代码

4.包含其他launch文件

<include file="path-to-launch-file”>(这里路径需要完整路径,建议使用下边这种方法)
或者
<include file=$(find package-name)/launch-file-name”>

5.启动参数(这个目前没怎么用过但还是记一下笔记吧)

  • 我们可以在roslaunch命令行中修改参数,但是只能改default的值,改不了value的值,因此有了下边这种写法
<incluce file=”path–to-launch-file”>
<arg name=”arg-name” value=”arg-value”/>
...
</include>

6.创建组

  • 可以将许多节点放在 同一命名空间下,写法为
<group ns=”namespace”/>
...
</group>
  • 且组可以有条件的使能或者禁用一个节点
<group if=”0 or 1”/>
</group>
在这里,如果if的属性值是1,那么该标签内元素就正常包含,反之会被忽略
unless意思相反

注意:1.一般不会直接设置为0或1,灵活运用启动参数,丰富启动文件的可配置性

2.组不是必须的,当然我们也可以单独为每个节点配置if,ns等属性

3.有些属性不能通过组来传递,例如output=”screen”,我们需要单独为节点设置该属性

  • csdn上看到这边笔记也很棒:launch文件

7.参数

1.基础操作

  • 查看参数列表:rosparam list
  • 查询某个参数的值:rosparam get /参数
    • 例如:rosparam get /rosistro 得到的是ros的操作版本
  • 设置参数:rosparam set 参数名 值
同时设置一个命名空间中的几个参数
rosparam set /duck_colors "huey: red
dewey: blue
louie: green
webby: pink"
注意:第一个引号告诉bash 命令尚未完成,因此按下回车时终端将会插入一个换行符而不是执行命令
  • 创建参数文件:rosparam dump filename namespace
  • 加载参数文件:rosparam load filename namespace(namespace默认是全局命名空间/)

2.案例

  • 设置背景颜色后,只有调用turtlesim_node的/clear服务后才会从参数服务器读取参数的值
    • 调用该服务:rosservice call /clear

3.使用ROS参数的C++接口

void ros::param::set(parameter_name, input_value);
bool ros::param::get(parameter_name, output_value);

4.启动文件中设置参数

<node
pkg="agitr"
type="pubvel_with_max"
name="publish_velocity"
/>
<param name="max_vel" value="3" />
</node>

8.服务

  • 服务调用是双向的,节点消息发出去后会等待响应
  • 实现的是一对一的通讯,每个服务由一个节点发起,响应也返回同一个节点。每一个消息都和一个话题相关,该话题可能有很多个发布者和订阅者

1.概念

客户端(client):发送请求到服务器节点等待响应

服务端(server):接受到请求后作出响应,响应可以是配置,改变某些行为

请求和响应携带的内容由数据类型决定,数据类型分为两部分:客户端和服务端

2.服务查询

rosservice list:查询活跃的服务,输出的服务名称是全局名称

rosnode info node-name:查看某个节点的服务类型

rosservice node service-name:查看服务是由哪个节点发出的

rosservice info service-name:查看服务的数据类型

roscrv show service-date-type-name:查看服务数据类型的详情(即发布和响应的数据字段,字段可以同时为空)

  • 注意区分rosservice和rossrv的区别

    TopicsServices
    active thingsrostopicrosservice
    date typesrosmsgrossrv

3.两类服务类型

  • 一些服务:由特定节点获取或传递信息,例如get_loggers 和 set_logger_level
  • 其他服务:不针对某些特定节点的服务,我们调用该服务时只关心服务的结果,而不关心服务是由哪个节点产生的

4.从命令行调用服务

  • rosservice call service-name request-content

    例如:rosservice call /spawn 3 3 0 Mikey 这条服务调用的效果是在现有仿真器中,位置 ( x , y ) = (3,3) 处创建一个名为“Mikey”的新海龟,其朝向角度 θ = 0 。这些新的资源都在新的命名空间Mikey中,很好的阐明了命名空间可以有效的阻止命名冲突

5.客户端程序

1.声明请求和响应的类型

#include <package_name/type_name.h>

2.创建客户端对象

首先ros::init和创建NodeHandle句柄

ros::ServiceClient client = node_handle.serviceClient<service_type>(
service_name);
  • node_handle 是常用的 ros::NodeHandle 对象,这里我们将调用它的 serviceClient 方法
  • service_type 是我们在上面头文件中定义的服务对象的数据类型
  • service_name 是一个字符串,说明了我们想要调用的服务名称。再次强调,这应当是一个相对名称,虽然也可以声明为全局名称。

与发布者不同的是,服务不需要设置队列大小,服务调用直到响应后才会返回,所以不用维持发送状态

3.创建请求和响应对象

定义请求和响应的类,命名为Request和Response

4.声明依赖

编辑 CMakeLists.txt 和清单文件 packag.xml

贴上代码

// This program spawns a new turtlesim turtle by calling
 // the appropriate service .
 #include <ros/ros.h>

 // The srv class for the service .
 #include <turtlesim/Spawn.h>

 int main( int argc , char ** argv ) {
 ros::init( argc , argv , "spawn_turtle") ;
 ros::NodeHandle nh ;

 // Create a client object for the spawn service . This
 // needs to know the data type of the service and its
 // name.
 ros::ServiceClient spawnClient
 = nh.serviceClient <turtlesim::Spawn>("spawn") ;

 // Create the request and response objects.
 turtlesim::Spawn::Request req ;
 turtlesim::Spawn::Response resp ;

 // Fill in the request data members.
 // 不能在这里给Response对象赋值
 req.x = 2;
 req.y = 3;
 req.theta = M_PI/2;
 req.name = "Leo" ;

 // Actually c a l l the service. This won't return until
 // the service is complete .
 // 这一步是在调用服务
 bool success = spawnClient.call( req , resp ) ;

 // Check for success and use the response .核对服务调用的返回值
 i f ( success ) {
 ROS_INFO_STREAM("Spawned␣ a␣ turtle␣ named␣ "
 << resp.name) ;
 } else {
 ROS_ERROR_STREAM("Failed␣ to␣ spawn.") ;
 }

 }

6.服务器程序

  • 当接受到请求时,进入回调函数,Request来自于客户端的数据

1.编写服务的回调函数

bool function_name(
package_name::service_type::Request &req),
package_name::service_type::Response &resp)
) {
...
}

当回调函数返回true表明成功,false表明失败

2.创建对象

ros::ServiceServer server = node_handle.advertiseService(
service_name,pointer_to_callback_function);

最后一个参数是指向回调函数的指针

3.注意

因为用到了回调函数,所以我们需要提供ROS控制权

贴上代码

 // This program t oggles between rotation and translation
 // commands, based on calls to a service .
 #include <ros/ros.h>
 #include <std_srvs/Empty.h>
 #include <geometry_msgs/Twist.h>

 bool forward = true ;
 bool toggleForward (
 std_srvs::Empty::Request &req ,
 std_srvs::Empty::Response &resp
 ) {
 forward = !forward ;
 ROS_INFO_STREAM("Now␣ sending␣ " << ( forward ?
 "forward" : " rotate ") << "␣ commands.") ;
 return true ;
 }

 int main( int argc , char ** argv ) {
 ros::i n i t ( argc , argv , "pubvel_toggle") ;
 ros::NodeHandle nh ;

 // Register our service with the master .
 ros::ServiceServer server = nh.advertiseService (
 "toggle_forward" , &toggleForward ) ;

 // Publish commands, using the latest value for forward ,
 // until the node shuts down.
 ros::Publisher pub = nh.advertise <geometry_msgs::Twist>(
 "turtle1/cmd_vel" , 1000) ;
 ros::Rate rate (2) ;
 while ( ros::ok () ) {
 geometry_msgs::Twist msg ;
 msg.linear.x = forward ? 1.0 : 0.0 ;
 msg.angular.z = forward ? 0.0 : 1.0 ;
 pub.publish (msg) ;
 ros::spinOnce () ;
 rate.sleep () ;
 }
 }

缺陷分析

  • 代码中有延迟,原因是sleep系统会以较低的频率进行循环,大部分时间处于休眠,大部分服务调用在sleep执行时到达,等待后再执行

解决方案

  • 使用两个线程:一个发布消息,一个处理服务回调
  • 使用ros::spin来代替sleep/ros::spinOnce循环,并且利用计数器回调函数(timer callback) 来发布消息

9.消息的录制与回放

使用包(bag)文件来录制和回放信息

通过 rosbag,我们能够将发布在一个或者多个话题上的消息录制到一个包文件中,然后可以回放这些消息,重现相似的运行过程。将这两种能力结合,便形成了测试某些机器人软件的有效方式:我们可以偶尔运行机器人,运行过程中录制关注的话题,然后多次回放与这些话题相关的消息,同时使用处理这些数据的软件进行实验。

1.录制和回放包文件

  • 录制包文件:rosbag record -O filename.bag topic-names(如果不指定文件名,就会基于日期和时间自动生成文件名)

  • 也可以使用rosbag record -a记录当前所有话题的消息

  • Ctrl+C停止rosbag

  • 回放包文件:rosbag play filename.bag

  • 检查文件包:rosbag info filename.bag

2.注意

rosbag如果没有记录初始状态,则在回放时会在结束时刻的位置开始回放

3.放入launch文件

方法:包括适当的节点元素

  • 录制节点

    <node
    pkg="rosbag"
    name="record"
    type="record"
    args="-O filename.bag topic-names"
    />
    
  • 回放节点

    <node
    pkg="rosbag"
    name="play"
    type="play"
    args="filename.bag"
    />
    

10.提升

1.规范代码

2.将数据可视化

3.创建消息和服务类型

4.使用TF工具管理多个坐标系

5.使用Gazebo仿真

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《机器人操作系统(ROS)浅析》笔记 的相关文章

随机推荐

  • qglobal.h:45: error: C1083: 无法打开包括文件:

    看了很多人的错误调试记录就是不行 xff0c 后来在百度知道里面看到这个 xff0c 添加进入后就可以了 在系统环境变量path 添加下面内容即可 SystemRoot system32
  • 卡尔曼滤波器学习笔记(二)

    扩展卡尔曼滤波器的原理及应用 经典的卡尔曼滤波只适用于线性且满足高斯分布的系统 xff0c 但实际工程中并不是这么简单 xff0c 比如飞行器在水平运动时有可能伴随着自身的自旋 xff0c 此时的系统并不是线性的 xff0c 这时就需要应用
  • 串口回环测试

    串口lookback测试 基础知识 通用异步收发传输器 xff08 Universal Asynchronous Receiver Transmitter xff09 xff0c 通常称作UART UART 是一种通用的数据通信协议 xff
  • STM32学习--USART

    串口工作方式 1 查询 xff1a 串口程序不断循环查询 xff0c 看看有没有数据需要它发送 2 中断 xff1a 平时串口只要打开中断即可 如果有中断来即需要传输数据 xff0c 它就马上进行数据的传输 STM32编程 xff1a 1
  • Atitit HTTP 认证机制基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)attilax总结Atitit HT

    Atitit HTTP 认证机制 基本验证 Basic Authentication 和摘要验证 Digest Authentication attilax总结 1 1 最广泛使用的是基本验证 Basic Authentication 和摘
  • 实验二 读取和理解激光雷达数据

    1 体验内容 xff08 1 xff09 为rplidar添加USB权限 注 xff1a 实验室的rplidar A2买得比较早 xff0c 硬件版本可能与github程序不匹配 xff0c 出现运行错误 xff0c 解决方法为到 Wind
  • Linux下使用Netfilter框架编写内核模块(统计协议层ping特定地址丢包数)

    一 linux内核中neitfilter的处理过程 1 5个HOOK点的执行点说明 xff1a 数据包从进入系统 xff0c 进行IP校验以后 xff0c 首先经过第一个HOOK函数NF IP PRE ROUTING进行处理 xff1b 然
  • 基于TCP 实现客户端之间通信【1】

    前段时间学习了基于TCP协议下实现服务器与一个客户端的通信 xff0c 服务器与多个客户端之间的通信 xff0c 以及客户端之间的互相通信 下面就是我写的利用TCP和多线程技术实现客户端之间互相通信的代码 xff1a 服务器端 xff1a
  • 回环接口(loop-back/loopback)

    回环接口 xff08 loop back loopback xff09 Moakap 整理 Loopback 接口是一个虚拟网络接口 xff0c 在不同的领域 xff0c 其含义也大不一样 1 xff0e TCP IP 协议栈中的 loop
  • socket封装HTTP请求

    之前写过两个socket封装的HTTP GET请求 xff0c 可是知其然 xff0c 不知所以然 这次写POST请求就有点懵逼了 还是从大佬的文章出发 xff1a https blog csdn net a19881029 article
  • access中,如何删除不可见空格

    1 可以使用 Trim 函数 xff0c 删除字符串 首 尾 的正常空格 span class token keyword update span 表名 span class token keyword set span 字段 span c
  • Raspberry Pi树莓派分类和其相似产品介绍

    文章目录 前言一 1代树莓派二 2代树莓派三 3代树莓派四 4代树莓派五 树莓派Pico六 目前可替代板子介绍引用 前言 树莓派官网 作为小型计算机的代表 xff0c 树莓派可是作为开山鼻祖 xff0c 本文聊一下目前树莓派几代板子发展历程
  • ARM汇编指令ldr和MOV的区别

    1 八位图 数据 2 MOV指令 MOV指令可以把立即数或者寄存器内容 xff08 注意 xff1a 这里绝对不可以是内存 xff01 xff01 xff09 传递给一个寄存器 MOV对于立即数是有要求的 xff0c 就是上边的 8位图 数
  • GPS NEMA 0183协议

    一 NMEA0183标准语句 GPS常用语句 GPGGA 例 xff1a GPGGA 092204 999 4250 5589 S 14718 5084 E 1 04 24 4 19 7 M 0000 1F 字段0 xff1a GPGGA
  • 使用setvbuf更改printf的默认buffer 行为

    有3种buffer行为 xff0c 不缓冲 xff0c 基于块的缓冲 和 基于行的缓冲 stdout xff08 printf xff09 默认是基于行的缓冲 xff0c 即写到stdout的字符都会被缓冲起来直到一个换行符输出的时候 xf
  • linux 下 tcpdump详解 后篇(自己实现抓包过滤)

    一 概述 在了解了tcpdump的原理后 xff0c 你有没有想过自己去实现抓包过滤 xff1f 可能你脑子里有个大概的思路 xff0c 但是知道了理论知识 xff0c 其实并不能代表你完全的理解 只要运用后 xff0c 你才知道哪些点需要
  • 结构体与共同体(联合体)的妙用

    结构体与共同体 xff08 联合体 xff09 的妙用 学习过C语言后 xff0c 大家都了解了结构体与共同体 两者之间的区别是 xff1a 共同体 xff1a 使几个不同类型的变量共占一段内存 相互覆盖 所占内存长度是各最长的成员占的内存
  • [北力电子] 无人机4G图传数传一体 pixhawk mavlink GSLINK 720P

    随着无人机和4G技术的发展 xff0c 实时监看空中视角的画面已经成为可能GSLINK突破传统的传输方式 xff0c 利用了4G网络将数据和视频流融为一体进行无限距离的传输 用户使用EC2地面站 xff08 Mission Planner
  • C#笔记(基础篇)

    简介 第一次发博客 xff0c 欢迎交流沟通 因为学习虚拟现实需要所以暑假一个月学了点C xff0c 做了笔记 xff0c 在这进行分享 xff0c 欢迎浏览 有些代码打在VS中 没有记录在笔记里 请见谅 xff08 视频指路 xff09
  • 《机器人操作系统(ROS)浅析》笔记

    机器人操作系统 xff08 ROS xff09 浅析 这是看了 A Gentle Introduction to ROS 这本书后记的笔记 xff0c 网上刚好找得到中文版的 xff0c 就看中文版了 xff0c 欢迎大佬批评指正 xff0