[基于STM32底盘控制与ROS上层导航小车制作] 第五节 实现STM32与ubuntu系统下的ROS串口DMA通信,传输底盘速度等信息
系列文章目录
第一节 stm32电机驱动与编码器读取反馈
第二节 stm32电机pid控制
第三节 stm32线速度标定
第四节 stm32添加mpu6050得到angle角度
第五节 实现STM32与ubuntu系统下的ROS串口DMA通信,传输底盘速度等信息
第六节 ROS计算和发布里程计与tf变换
系列教材包含
底盘搭建与stm32代码编写,ros激光雷达建图与导航编写,实现动态避障与导航
底盘包含
两轮差速轮(自制)
四轮全向轮(所属团队大家一起搭建)
底盘不同就是在底盘的运动分解与控制,ros部分里程计计算和本地规划器不同,两轮用的base_local_Planner,四轮用的teb_local_planner,来规划发布速度控制底盘
gjhhust / Repositories · GitHubhttps://github.com/gjhhust?tab=repositories
目录
系列文章目录
前言
一、硬件保证
1.连接
2.驱动安装
3.确认驱动
4.给串口权限
二、串口DMA通讯
1.示例代码
1.创建功能包 (my_robot)
2.添加src资源文件
1.初始化串口DMA收发
2.数据处理函数
总结
前言
经过编写完成底盘代码后,我们现在需要将底盘实时信息传输到上位机中
硬件设备:
STM32F103RC
PC机
一、硬件保证
1.连接
STM32串口 + TTL转USB模块(CH340 or CH341)+ Linux硬件设备(PC机或者树莓派)
用TTL的时候注意!RX接串口的TX,TX接RX,GND接GND其它口不需要管
2.驱动安装
不论你是PC还是树莓派,运行ros应该都是在ubuntu中,接下来需要在ubuntu中安装CH340驱动识别出单片机串口
这里引用另一位大大的文章直接安装即可
Ubuntu18.04安装ch340驱动_ldw_wdl的博客-CSDN博客
3.确认驱动
ubuntu中查看当前串口
ls -l /dev/ttyUSB*
我的ubuntu中原本就有cp210x的驱动,所以显示是两个
4.给串口权限
sudo chmod 777 /dev/ttyUSB0
此时连接已经完成(这是临时连接,固定连接后续会讲)
二、串口DMA通讯
1.示例代码
ROS部分代码
1.创建功能包 (my_robot)
geometry_msgs nav_msgs roscpp rospy sensor_msgs tf
2.添加src资源文件
串口通信文件
serial.C
#include "serial.h"using namespace std;
using namespace boost::asio;
//串口相关对象
boost::asio::io_service iosev;
boost::asio::serial_port sp(iosev, "/dev/ttyUSB0");//根据你的串口号填写
boost::system::error_code err;
/********************************************************串口发送接收相关常量、变量、共用体对象
********************************************************/
const unsigned char header[2] = {0x55, 0xaa};
const unsigned char ender[2] = {0x0d, 0x0a};
//发送左右轮速控制速度共用体
union sendData
{short d;unsigned char data[2];
}leftVelSet,rightVelSet;//接收数据(左轮速、右轮速、角度)共用体(-32767 - +32768)
union receiveData
{short d;unsigned char data[2];
}leftVelNow,rightVelNow,angleNow;/********************************************************
函数功能:串口参数初始化
入口参数:无
出口参数:
********************************************************/
void serialInit()
{sp.set_option(serial_port::baud_rate(115200));sp.set_option(serial_port::flow_control(serial_port::flow_control::none));sp.set_option(serial_port::parity(serial_port::parity::none));sp.set_option(serial_port::stop_bits(serial_port::stop_bits::one));sp.set_option(serial_port::character_size(8));
}/********************************************************
函数功能:将对机器人的左右轮子控制速度,打包发送给下位机
入口参数:机器人线速度、角速度
出口参数:
********************************************************/
void writeSpeed(double Left_v, double Right_v,unsigned char ctrlFlag)
{unsigned char buf[13] = {0};//int i, length = 0;leftVelSet.d = Left_v;//mm/srightVelSet.d = Right_v;// 设置消息头for(i = 0; i < 2; i++)buf[i] = header[i]; //buf[0] buf[1]// 设置机器人左右轮速度length = 5;buf[2] = length; //buf[2]for(i = 0; i < 2; i++){buf[i + 3] = leftVelSet.data[i]; //buf[3] buf[4]buf[i + 5] = rightVelSet.data[i]; //buf[5] buf[6]}// 预留控制指令buf[3 + length - 1] = ctrlFlag; //buf[7]// 设置校验值、消息尾buf[3 + length] = getCrc8(buf, 3 + length);//buf[8]buf[3 + length + 1] = ender[0]; //buf[9]buf[3 + length + 2] = ender[1]; //buf[10]// 通过串口下发数据boost::asio::write(sp, boost::asio::buffer(buf));
}
/********************************************************
函数功能:从下位机读取数据
入口参数:机器人左轮轮速、右轮轮速、角度,预留控制位
出口参数:bool
********************************************************/
bool readSpeed(double &Left_v,double &Right_v,double &Angle,unsigned char &ctrlFlag)
{char i, length = 0;unsigned char checkSum;unsigned char buf[1024]={0};//=========================================================//此段代码可以读数据的结尾,进而来进行读取数据的头部try{boost::asio::streambuf response;boost::asio::read_until(sp, response, "\r\n",err); copy(istream_iterator<unsigned char>(istream(&response)>>noskipws),istream_iterator<unsigned char>(),buf); } catch(boost::system::system_error &err){ROS_INFO("read_until error");} //========================================================= // 检查信息头if (buf[0]!= header[0] || buf[1] != header[1]) //buf[0] buf[1]{ROS_ERROR("Received message header error!");return false;}// 数据长度length = buf[2]; //buf[2]// 检查信息校验值checkSum = getCrc8(buf, 3 + length); //buf[10] 计算得出if (checkSum != buf[3 + length]) //buf[10] 串口接收{ROS_ERROR("Received data check sum error!");return false;} // 读取速度值for(i = 0; i < 2; i++){leftVelNow.data[i] = buf[i + 3]; //buf[3] buf[4]rightVelNow.data[i] = buf[i + 5]; //buf[5] buf[6]angleNow.data[i] = buf[i + 7]; //buf[7] buf[8]}// 读取控制标志位ctrlFlag = buf[9];Left_v =leftVelNow.d;Right_v =rightVelNow.d;Angle =angleNow.d;return true;
}
/********************************************************
函数功能:获得8位循环冗余校验值
入口参数:数组地址、长度
出口参数:校验值
********************************************************/
unsigned char getCrc8(unsigned char *ptr, unsigned short len)
{unsigned char crc;unsigned char i;crc = 0;while(len--){crc ^= *ptr++;for(i = 0; i < 8; i++){if(crc&0x01)crc=(crc>>1)^0x8C;else crc >>= 1;}}return crc;
}
serial.h
#ifndef MBOT_LINUX_SERIAL_H
#define MBOT_LINUX_SERIAL_H#include <ros/ros.h>
#include <ros/time.h>
#include <geometry_msgs/TransformStamped.h>
#include <tf/transform_broadcaster.h>
#include <nav_msgs/Odometry.h>
#include <boost/asio.hpp>
#include <geometry_msgs/Twist.h>
#include "std_msgs/String.h"//use data struct of std_msgs/String
#include "std_msgs/Float32.h"
#include "turtlesim/Pose.h"
#include "serial.h"
#include <vector>
//这些头文件是我其它功能也会遇到,因此全部加入了extern void serialInit();
extern void writeSpeed(double Left_v, double Right_v,unsigned char ctrlFlag);
extern bool readSpeed(double &Left_v,double &Right_v,double &Angle,unsigned char &ctrlFlag);
unsigned char getCrc8(unsigned char *ptr, unsigned short len);void odom_pub_calcu(void);
double sin_cal(double theta);
#endif
main.c
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "serial.h"//test send value
double left_speed_ctrl=5555.0;
double right_speed_ctrl=2222.0;
unsigned char control_flag=0x07;//test receive value
double left_speed_now=0.0;
double right_speed_now=0.0;
double angle=0.0;
unsigned char testRece4=0x00;int main(int agrc,char **argv)
{ros::init(agrc,argv,"node");ros::NodeHandle nh;ros::Rate loop_rate(10);//串口初始化serialInit();while(ros::ok()){ros::spinOnce();//向STM32端发送数据,前两个为double类型,最后一个为unsigned char类型writeSpeed(left_speed_ctrl,right_speed_ctrl,control_flag);//从STM32接收数据,输入参数依次转化为小车的线速度、角速度、航向角(角度)、预留控制位readSpeed(left_speed_now,right_speed_now,angle,testRece4);//打印数据ROS_INFO("we receive %f,%f,%f,%d\n",left_speed_now,right_speed_now,angle,testRece4);loop_rate.sleep();}return 0;
}
stm32部分
1.初始化串口DMA收发
usart.c,usart.h内只需声明一下初始化函数UART2_DMA_Init()即可
#include "usart.h"#define reBiggestSize 13
#define SendBiggestSize 13unsigned char JudgeReceiveBuffer[reBiggestSize*2];//接受最大内存
unsigned char JudgeSend[SendBiggestSize];//发送最大内存/**********************************************************************************************************
*函 数 名: UART2_DMA_Init
*功能说明: uart2初始化(速度收发)
*形 参: 无
*返 回 值: 无
**********************************************************************************************************/
void UART2_DMA_Init(void)
{//GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能USART2,GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);//USART2_TX GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA3GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART2_RX GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 // //Usart1 NVIC 配置
// NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
// NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = 115200;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式USART_Init(USART2, &USART_InitStructure); //初始化串口2USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART2, ENABLE); //使能串口2//RX DMA1 6通道USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);{DMA_InitTypeDef dma;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);DMA_DeInit(DMA1_Channel6);dma.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR);dma.DMA_MemoryBaseAddr = (uint32_t)JudgeReceiveBuffer;dma.DMA_DIR = DMA_DIR_PeripheralSRC;dma.DMA_BufferSize = reBiggestSize;dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;dma.DMA_MemoryInc = DMA_MemoryInc_Enable;dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;dma.DMA_Mode = DMA_Mode_Circular;dma.DMA_Priority = DMA_Priority_VeryHigh;dma.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel6, &dma);DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);DMA_Cmd(DMA1_Channel6, ENABLE);}//TX DMA1 7通道NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);{DMA_InitTypeDef dma;DMA_DeInit(DMA1_Channel7);dma.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR);dma.DMA_MemoryBaseAddr = (uint32_t)JudgeSend;dma.DMA_DIR = DMA_DIR_PeripheralDST;dma.DMA_BufferSize = SendBiggestSize;dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;dma.DMA_MemoryInc = DMA_MemoryInc_Enable;dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;dma.DMA_Mode = DMA_Mode_Normal;dma.DMA_Priority = DMA_Priority_VeryHigh;dma.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel7,&dma);DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);DMA_ITConfig(DMA1_Channel7, DMA1_FLAG_GL7,ENABLE);DMA_Cmd(DMA1_Channel7,DISABLE);}
}//rx满中断
void DMA1_Channel6_IRQHandler(void)
{ if(DMA_GetFlagStatus(DMA1_FLAG_TC6) == SET){DMA_ClearFlag(DMA1_FLAG_TC6);//接收处理函数JudgeBuffReceive(JudgeReceiveBuffer);}
}//Tx满中断
void DMA1_Channel7_IRQHandler(void)
{if(DMA_GetITStatus(DMA1_IT_TC7)!=RESET){DMA_ClearFlag(DMA1_FLAG_TC7 | DMA1_FLAG_GL7);DMA_ClearITPendingBit(DMA1_IT_TC7 | DMA1_FLAG_GL7);DMA_Cmd(DMA1_Channel7,DISABLE);}
}
2.数据处理函数
Datadispose.c(这里头文件也只是声明函数)
我们这里打包数据运用了非常方便的union共用体,特点就是union内的所有成员公用首地址,也就是说如果你定义一个共用体包括double data,char send[N],则你在平时只需要对data赋值,则串口发送时只能8字节8字节发送时我们的send[N]内就会自动将data拆分好,你只需要发送send数组就相当于发送data,因为他们的首地址一样,内容则一样。
#include "Data_Dispose.h"//通信协议常量
const unsigned char header[2] = {0x55, 0xaa};
const unsigned char ender[2] = {0x0d, 0x0a};
/*--------------------------------发送协议-----------------------------------
//----------------55 aa size 00 00 00 00 00 crc8 0d 0a----------------------
//数据头55aa + 数据字节数size + 数据(利用共用体) + 校验crc8 + 数据尾0d0a
//注意:这里数据中预留了一个字节的控制位,其他的可以自行扩展,更改size和数据
--------------------------------------------------------------------------*//*--------------------------------接收协议-----------------------------------
//----------------55 aa size 00 00 00 00 00 crc8 0d 0a----------------------
//数据头55aa + 数据字节数size + 数据(利用共用体) + 校验crc8 + 数据尾0d0a
//注意:这里数据中预留了一个字节的控制位,其他的可以自行扩展,更改size和数据
--------------------------------------------------------------------------*//*--------------------------------控制位-----------------------------------8位由高到底分别是
软件复位 左轮方向 右轮方向
--------------------------------------------------------------------------*//**************************************************************************
通信的发送函数和接收函数必须的一些常量、变量、共用体对象
**************************************************************************/extern unsigned char JudgeSend[SendBiggestSize];//发送最大内存
unsigned char SaveBuffer[200];//接受双缓存区//接受信息
typedef struct{int leftSpeedSet;int rightSpeedSet;unsigned char crtlFlag;char left_move;//为1时方向为正char right_move;
}Ctrl_information;
Ctrl_information chassis_ctrl ={0,0,0,1,1}; //接受上位机控制信息
extern Chassis F103RC_chassis;//底盘实时数据//发送数据(左轮速、右轮速、角度)共用体(-32767 - +32768)
typedef union
{short d;unsigned char data[2];
}sendData;//左右轮速控制速度共用体
typedef union
{short d;unsigned char data[2];
}receiveData;//发送数据(左轮速、右轮速、角度)共用体(-32767 - +32768)
union sendData
{short d;unsigned char data[2];
}leftVelNow,rightVelNow,angleNow;//左右轮速控制速度共用体
union receiveData
{short d;unsigned char data[2];
}leftVelSet,rightVelSet;/**********************************************************************************************************
*函 数 名: JudgeBuffReceive
*功能说明: 接收函数
*形 参: ReceiveBuffer[]
*返 回 值: 无
**********************************************************************************************************/
void JudgeBuffReceive(unsigned char ReceiveBuffer[])
{uint16_t cmd_id;//识别信息内容,暂未开启short k=0;short PackPoint;memcpy(&SaveBuffer[reBiggestSize],&ReceiveBuffer[0],reBiggestSize); //把ReceiveBuffer[0]地址拷贝到SaveBuffer[13], 依次拷贝13个, 把这一次接收的存到数组后方for(PackPoint=0;PackPoint<reBiggestSize;PackPoint++) //先处理前半段数据(在上一周期已接收完成){if(SaveBuffer[PackPoint]==header[0] && SaveBuffer[PackPoint + 1]== header[1]) //包头检测{ short dataLength = SaveBuffer[PackPoint + 2] ;unsigned char checkSum = getCrc8(&SaveBuffer[PackPoint], 3 + dataLength);// 检查信息校验值if (checkSum == SaveBuffer[PackPoint +3 + dataLength]) //SaveBuffer[PackPoint开始的校验位]{//说明数据核对成功,开始提取数据for(k = 0; k < 2; k++){leftVelSet.data[k] = SaveBuffer[PackPoint + k + 3]; //SaveBuffer[3] SaveBuffer[4]rightVelSet.data[k] = SaveBuffer[PackPoint + k + 5]; //SaveBuffer[5] SaveBuffer[6]} //速度赋值操作chassis_ctrl.leftSpeedSet = (int)leftVelSet.d;chassis_ctrl.rightSpeedSet = (int)rightVelSet.d;//ctrlFlagchassis_ctrl.crtlFlag = SaveBuffer[PackPoint + 7]; //SaveBuffer[7]}} memcpy(&SaveBuffer[0],&SaveBuffer[reBiggestSize],reBiggestSize); //把SaveBuffer[13]地址拷贝到SaveBuffer[0], 依次拷贝13个,把之前存到后面的数据提到前面,准备处理}
}
/**************************************************************************
函数功能:将左右轮速和角度数据、控制信号进行打包,通过串口发送给Linux
入口参数:实时左轮轮速、实时右轮轮速、实时角度、控制信号(如果没有角度也可以不发)
返回 值:无
**************************************************************************/
void usartSendData(short leftVel, short rightVel,short angle,unsigned char ctrlFlag)
{int i, length = 0;// 计算左右轮期望速度leftVelNow.d = leftVel;rightVelNow.d = rightVel;angleNow.d = angle;// 设置消息头for(i = 0; i < 2; i++)JudgeSend[i] = header[i]; // buf[0] buf[1] // 设置机器人左右轮速度、角度length = 7;JudgeSend[2] = length; // buf[2]for(i = 0; i < 2; i++){JudgeSend[i + 3] = leftVelNow.data[i]; // buf[3] buf[4]JudgeSend[i + 5] = rightVelNow.data[i]; // buf[5] buf[6]JudgeSend[i + 7] = angleNow.data[i]; // buf[7] buf[8]}// 预留控制指令JudgeSend[3 + length - 1] = ctrlFlag; // buf[9]// 设置校验值、消息尾JudgeSend[3 + length] = getCrc8(JudgeSend, 3 + length); // buf[10]JudgeSend[3 + length + 1] = ender[0]; // buf[11]JudgeSend[3 + length + 2] = ender[1]; // buf[12]//发送字符串数据/*****数据上传*****/// USART_ClearFlag(USART2,USART_FLAG_TC);
// for(i=0;i<13;i++)
// {
// USART_SendData(USART2,JudgeSend[i]);
// while (USART_GetFlagStatus(USART2,USART_FLAG_TC) == RESET); //等待之前的字符发送完成
// }DMA1_Channel7->CNDTR = SendBiggestSize; DMA_Cmd(DMA1_Channel7,ENABLE);
}/**************************************************************************
函数功能:计算八位循环冗余校验,被usartSendData和usartReceiveOneData函数调用
入口参数:数组地址、数组大小
返回 值:无
**************************************************************************/
unsigned char getCrc8(unsigned char *ptr, unsigned short len)
{unsigned char crc;unsigned char i;crc = 0;while(len--){crc ^= *ptr++;for(i = 0; i < 8; i++){if(crc&0x01)crc=(crc>>1)^0x8C;else crc >>= 1;}}return crc;
}
/**********************************END***************************************/
代码解释,
最前面的定义就是一些共用体和接受信息与发送的信息,F103RC_chassis为我自定义的底盘实时数据,在其它测车速等文件为其赋值,要是只是测试串口,只需在main函数内为其赋值即可
typedef struct
{int leftSpeedNow;//左右电机当前速度int rightSpeedNow;int angle;//当前车体角度,100倍发送int controlFlag;//控制指令
}Chassis;
在后续调用函数usartSendData(short leftVel, short rightVel,short angle,unsigned char ctrlFlag),即可在函数内打包好四个数据放入自定义的DMA发送缓冲区,然后开启DMA即可发送
接受数据结构体类似,定义好后即可,因为函数JudgeBuffReceive(),已经在usart.c的中断函数内被调用,DMA接收满数据后就会自动进入JudgeBuffReceive处理出数据并赋值
//接受信息
typedef struct{int leftSpeedSet;int rightSpeedSet;unsigned char crtlFlag;char left_move;//为1时方向为正char right_move;
}Ctrl_information;
Ctrl_information chassis_ctrl ={0,0,0,1,1}; //接受上位机控制信息
将以上代码添加入你的stm32工程内即可,然后debug内可以直接查看接受的内容,发送内容需要调用函数。
注意:如果测试在while内调用发送函数,记得延迟至少15ms,不然ros会报错栈溢出,如果你做自己的小车时,控制方案肯定是弄一个定时器在固定周期内发送数据,即可忽略
总结
这里则是联通了树莓派(虚拟机)ROS与你的stm32,这是千里之行的第一步,切记数据收发要保证,然后发送接收量适当改大也测试测试。
[基于STM32底盘控制与ROS上层导航小车制作] 第五节 实现STM32与ubuntu系统下的ROS串口DMA通信,传输底盘速度等信息相关推荐
- Ubuntu系统下使用ROS(moveit )连接ABB实体机器人(irb120)(1.控制仿真)
前言: 经过两天的踩坑,终于将ROS与ABB连接成功,可以通过rviz的gui界面拖动机械臂来控制实体ABB机械臂进行运动,因为网络上的教程(几位博主和ROS WIKI等)个别步骤的不完整,所以走了很 ...
- STM32蓝牙控制循迹避障小车——2.循迹模块
STM32蓝牙控制循迹避障小车源代码--2.循迹模块 注意-所需模块: 接线:四个循迹模块(从左到右分别为1,2,3,4) B4–第1个循迹模块的D0 B5–第2个循迹模块的D0 B6–第3个循迹模块 ...
- STM32蓝牙控制循迹避障小车源代码——4.蓝牙控制
STM32蓝牙控制循迹避障小车源代码--4.蓝牙控制 注意-所需模块: 蓝牙模块 接线: 串口通信 A2–RX A3–TX 所有的代码都是直接从工程里面复制的,实测是没有问题的. 蓝牙控制原理: 设计 ...
- STM32蓝牙控制循迹避障小车源代码——3.舵机、超声波测距模块
STM32蓝牙控制循迹避障小车源代码--3.舵机.超声波测距模块 注意-所需模块: US-015超声波模块 SG90舵机云台 接线:舵机超声波: A1–P2.7 B8–Trig B9–Echo 代码 ...
- STM32蓝牙控制循迹避障小车源代码——5.最终程序
STM32蓝牙控制循迹避障小车源代码--5.最终程序 将前面4讲的内容整合一起.主函数里用switch函数或者if语句来判断接收到的数据,改变小车的运动. 注意要将控制循迹和避障的参数单独定义一个.否 ...
- 基于虚拟机Ubuntu系统下C语言简单编写程序
目录描述 前言 一.修改系统参数 1.查看Ubuntu版本 2.原文件备份 3.下载清华源 二.编写"hello world"C语言程序 1.安装vim 2.建立hello.c 3 ...
- 基于ubuntu系统下的USB设备绑定
目录 前言 基本原理 实现方法 前言 在Ubuntu系统的使用中,没有对USB进行设备进行绑定的话,每次插拔或者顺序的不同,都会造成ttyUSB端口号的改变,不利于实际应用,一劳永逸地方法就是对其进行 ...
- Ubuntu系统下查看摄像头参数并基于OpenCV调用
目录 1. 安装v4l-utils 2. 查找连接的摄像头列表 3. 查看各相机支持的分辨率.帧率.像素格式等参数 4. 注意事项 5. OpenCV(C++)调用摄像头代码 本博文描述了在Ubunt ...
- 【STM32】HAL库——串口DMA通信(三)
前期准备: STM32CubeMX STM32RCT6核心板 IDE Keil(MDK-ARM) 关于DMA 1. 什么是DMA? DMA(Direct Memory Access,直接存储器访问) ...
最新文章
- 《AOSuite 开发手册》之AOSuite 服务端开发
- java 工厂模式 计算器_简单工厂模式实现简易计算器
- SAP Fiori attachment rename debug
- ros(7)自定义service数据
- php的开始和结束标记建议使用的是,php的开始和结束标记建议使用的是?
- 计算机科学开设的核心主干课程,以中美大学先修课程培养计算机科学核心素养的探索实践...
- 计算机网络原理第七章,北大计算机网络原理第七章.pdf
- 中国开源视频编辑软件行业市场供需与战略研究报告
- 使用数字全通滤波器对IIR滤波器进行相位补偿
- 艾肯声卡调试方法【必看】
- 4个关键,如何清晰的做好数据分析
- 规格说明书-吉林市一日游
- 获取文件夹中所有图片文件
- git中fatal: Authentication failed for 的问题
- 【PHPWord】PHPWord生成图表-柱形图 | 设置数值类别展示、展示多组数据
- android手电筒
- EXCEL 也可以使用chatGPT了,教程来了
- Windows遇到的图片查看问题。
- 从苏宁电器到卡巴斯基第21篇:单证这一年(上)
- 服务器 php 版本低,php 版本过低 怎么修改?