系列文章目录

第一节 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通信,传输底盘速度等信息相关推荐

  1. Ubuntu系统下使用ROS(moveit )连接ABB实体机器人(irb120)(1.控制仿真)

    前言: 经过两天的踩坑,终于将ROS与ABB连接成功,可以通过rviz的gui界面拖动机械臂来控制实体ABB机械臂进行运动,因为网络上的教程(几位博主和ROS WIKI等)个别步骤的不完整,所以走了很 ...

  2. STM32蓝牙控制循迹避障小车——2.循迹模块

    STM32蓝牙控制循迹避障小车源代码--2.循迹模块 注意-所需模块: 接线:四个循迹模块(从左到右分别为1,2,3,4) B4–第1个循迹模块的D0 B5–第2个循迹模块的D0 B6–第3个循迹模块 ...

  3. STM32蓝牙控制循迹避障小车源代码——4.蓝牙控制

    STM32蓝牙控制循迹避障小车源代码--4.蓝牙控制 注意-所需模块: 蓝牙模块 接线: 串口通信 A2–RX A3–TX 所有的代码都是直接从工程里面复制的,实测是没有问题的. 蓝牙控制原理: 设计 ...

  4. STM32蓝牙控制循迹避障小车源代码——3.舵机、超声波测距模块

    STM32蓝牙控制循迹避障小车源代码--3.舵机.超声波测距模块 注意-所需模块: US-015超声波模块 SG90舵机云台 接线:舵机超声波: A1–P2.7 B8–Trig B9–Echo 代码 ...

  5. STM32蓝牙控制循迹避障小车源代码——5.最终程序

    STM32蓝牙控制循迹避障小车源代码--5.最终程序 将前面4讲的内容整合一起.主函数里用switch函数或者if语句来判断接收到的数据,改变小车的运动. 注意要将控制循迹和避障的参数单独定义一个.否 ...

  6. 基于虚拟机Ubuntu系统下C语言简单编写程序

    目录描述 前言 一.修改系统参数 1.查看Ubuntu版本 2.原文件备份 3.下载清华源 二.编写"hello world"C语言程序 1.安装vim 2.建立hello.c 3 ...

  7. 基于ubuntu系统下的USB设备绑定

    目录 前言 基本原理 实现方法 前言 在Ubuntu系统的使用中,没有对USB进行设备进行绑定的话,每次插拔或者顺序的不同,都会造成ttyUSB端口号的改变,不利于实际应用,一劳永逸地方法就是对其进行 ...

  8. Ubuntu系统下查看摄像头参数并基于OpenCV调用

    目录 1. 安装v4l-utils 2. 查找连接的摄像头列表 3. 查看各相机支持的分辨率.帧率.像素格式等参数 4. 注意事项 5. OpenCV(C++)调用摄像头代码 本博文描述了在Ubunt ...

  9. 【STM32】HAL库——串口DMA通信(三)

    前期准备: STM32CubeMX STM32RCT6核心板 IDE Keil(MDK-ARM) 关于DMA 1. 什么是DMA? DMA(Direct Memory Access,直接存储器访问) ...

最新文章

  1. 《AOSuite 开发手册》之AOSuite 服务端开发
  2. java 工厂模式 计算器_简单工厂模式实现简易计算器
  3. SAP Fiori attachment rename debug
  4. ros(7)自定义service数据
  5. php的开始和结束标记建议使用的是,php的开始和结束标记建议使用的是?
  6. 计算机科学开设的核心主干课程,以中美大学先修课程培养计算机科学核心素养的探索实践...
  7. 计算机网络原理第七章,北大计算机网络原理第七章.pdf
  8. 中国开源视频编辑软件行业市场供需与战略研究报告
  9. 使用数字全通滤波器对IIR滤波器进行相位补偿
  10. 艾肯声卡调试方法【必看】
  11. 4个关键,如何清晰的做好数据分析
  12. 规格说明书-吉林市一日游
  13. 获取文件夹中所有图片文件
  14. git中fatal: Authentication failed for 的问题
  15. 【PHPWord】PHPWord生成图表-柱形图 | 设置数值类别展示、展示多组数据
  16. android手电筒
  17. EXCEL 也可以使用chatGPT了,教程来了
  18. Windows遇到的图片查看问题。
  19. 从苏宁电器到卡巴斯基第21篇:单证这一年(上)
  20. 服务器 php 版本低,php 版本过低 怎么修改?

热门文章

  1. matlab中矩阵的秩,matlab矩阵的秩
  2. 服务器上安装Linux系统教程
  3. 图片PDF转文字需要进行OCR文字识别
  4. ACM-ICPC 2019 南昌 邀请赛(网络赛)
  5. 一个小小的猜数游戏(挺无聊的,可以运行试试)
  6. 解决:windows10网络图标被重名名为Network
  7. STM32F7 IAP在线刷写简述 (STM32CUBE+freeRTOS)
  8. matlab 图片相减,图像相减是什么
  9. matlab由补码反码原码求值
  10. JsRender前端渲染模板-jquery方法失效