【ROS2】Arduino系列之电机驱动实现

内容学自:赵虚左老师。学后总结! 

实现机器人移动的一种策略是:控制系统会先发布预期的车辆速度信息,然后驱动系统订阅到该信息,不断调整电机转速直至达到预期速度,调速过程中还需要时时获取实际速度并反馈给控制系统,控制系统会计算实际位移并生成里程计信息。

控制系统(ROS端)其实就是典型的发布和订阅实现,而具体到驱动系统(Arduino)层面,需要解决的问题有如下几点:带着问题学更有目标性!

1.Arduino 如何订阅控制系统发布的速度相关信息?
2.Arduino 如何发布实际速度相关信息到控制系统?
3.Arduino 如何驱动电机(正传、反转)?
4.Arduino 如何实现电机测速?
5.Arduino 如何实现电机调速?

一、硬件部分

1.1 直流减速电机

需要简单了解电机类型、机械结构以及各项参数,这些是和机器人的负载、极限速度、测速结果等休戚相关的。

包含三个部分:减速箱、电机主体、编码器

        电机主体通过输入轴与减速箱相连接,通过减速箱的减速效果,最终外端的输出轴会按照比例(取决于减速箱减速比)降低电机输入轴的转速,当然速度降低之后,将提升电机的力矩。

        尾部是AB相霍尔编码器,通过AB编码器输出的波形图,可以判断电机的转向以及计算电机转速。

需要关注的参数:

1.额定扭矩: 额定扭矩和机器人质量以及有效负荷相关,二者正比例相关,额定扭矩越大,可支持的机器人质量以及有效负荷越高;
2.减速比: 电机输入轴与输出轴的减速比例,比如: 减速比为90,意味着电机主体旋转90圈,输出轴旋转1圈。3.减速后转速: 与减速比相关,是电机减速箱输出轴的转速,单位是 rpm(转/分)减速后转速与减速前转速存在转换关系: 减速后转速 = 减速前转速 / 减速比。另外,可以根据官方给定的额定功率下的减速后转速结合车轮参数来确定小车最大速度。4.编码器精度:是指编码器旋转一圈单相(当前编码器有AB两相)输出的脉冲数;5.注意:电机输入轴旋转一圈的同时,编码器旋转一圈.如果输出轴旋转一圈,那么编码器的旋转圈数和减速比一致(比如减速比是90,那么输出轴旋转一圈,编码器旋转90圈)。6.编码器输出的脉冲数计算公式则是: 输出轴旋转一圈产生的脉冲数 = 减速比 * 编码器旋转一圈发送的脉冲数(比如:减速比为90,编码器旋转一圈输出11个脉冲,那么输出轴旋转一圈总共产生 11 * 90 也即990个脉冲)。

1.2 电机编码器 

电机编码器
M1: 电机电源+(和M2对调可以正反转 )
GND: 编码器电源-
C2: 信号线
C1: 信号线
VCC:编码器电源+
M2: 电机电源-(和M1对调可以正反转)

 1.3 电机驱动板

电机驱动板可选型号较多,比如:TB6612、L298N、L298P....但是这些电机驱动板与电机相连时,需要使用杜邦线,接线会显得凌乱,下面是一款基于L298P优化的电机驱动板,该驱动板可以使用端子线直接连接电机,接线更规整、美观。

端子线母头对应的引脚(自上而下)
母头1: 4、地线、21、20、5V输入、 5
母头2: 7、地线、18、19、5V输入、 6PS:电机驱动板使用时,需要打开USB接口处的电源开关。

 二、电机基本控制

实现:控制单个电机转动,先控制电机以某个速率正向转动N秒,再让电机停止N秒,再控制电机以某个速率逆向转动N秒,最后让电机停止N秒,如此循环。

接线:

左电机的M1与M2对应的是引脚4(DIRA)和引脚5(PWMA),引脚4控制转向,引脚5输出PWM。
右电机的M1与M2对应的是引脚6(PWMB)和引脚7(DIRB),引脚7控制转向,引脚6输出PWM。

代码: 

/** 电机转动控制* 1.定义接线中电机对应的引脚* 2.setup 中设置引脚为输出模式* 3.loop中控制电机转动*/int DIRA = 4;
int PWMA = 5;void setup() {//两个引脚都设置为 OUTPUTpinMode(DIRA,OUTPUT);pinMode(PWMA,OUTPUT);
}void loop() {//先正向转动3秒digitalWrite(DIRA,HIGH);analogWrite(PWMA,100);delay(3000);//停止3秒digitalWrite(DIRA,HIGH);analogWrite(PWMA,0);delay(3000);//再反向转动3秒digitalWrite(DIRA,LOW);analogWrite(PWMA,100);delay(3000);//停止3秒digitalWrite(DIRA,LOW);analogWrite(PWMA,0);delay(3000);/** 注意: * 1.可以通过将DIRA设置为HIGH或LOW来控制电机转向,但是哪个标志位正转或反转需要根据需求判断,转向是相对的。* 2.PWM的取值为 [0,255],该值可自己设置。* */
}

三、电机测速

3.1 编码器测速原理

使用 AB相增量式编码器测速。

原理:AB相编码器主要构成为A相与B相,每一相每转过单位的角度就发出一个脉冲信号(一圈可以发出N个脉冲信号),A相、B相为相互延迟1/4周期的脉冲输出,根据延迟关系可以区别正反转,而且通过取A相、B相的上升和下降沿可以进行单频或2倍频或4倍频测速。

3.2 测速举例

假设编码器旋转1圈输出11个脉冲,减速比为 90。伪代码如下:

单频计数:

//设置一个计数器
int count = 0;
//当A为上升沿时
if(B为高电平){count++;
}else {count--;
}
//....
//速度=单位时间内统计的脉冲的个数 / (11*90) / 单位时间

2倍频计数:

//设置一个计数器
int count = 0;
//当A为上升沿时
if(B为高电平){count++;
}else {count--;
}
//当A为下降沿时
if(B为低电平){count++;
}else {count--;
}//....
//速度=单位时间内统计的脉冲的个数 / (11*2*90) / 单位时间

4倍频计数:

//设置一个计数器
int count = 0;
//当A为上升沿时
if(B为高电平){count++;
}else {count--;
}
//当A为下降沿时
if(B为低电平){count++;
}else {count--;
}
//当B为上升沿时
if(A为低电平){count++;
} else {count--;
}
//当B为下降沿时
if(A为高电平){count++;
} else {count--;
}
//....
//速度=单位时间内统计的脉冲的个数 / (11*4*90) / 单位时间

 3.3 案例实现

案例:统计并输出电机转速。

思路:先统计单位时间内以单频或2倍频或4倍频的方式统计脉冲数,再除以一圈对应的脉冲数,最后再除以时间所得即为电机转速。

核心:计数时,需要在A相或B相的上升沿或下降沿触发时,实现计数,在此需要使用中断引脚与中断函数。

Arduino Mega 2560 的中断引脚:2 (interrupt 0), 3 (interrupt 1),18 (interrupt 5), 19 (interrupt 4), 20 (interrupt 3), 21 (interrupt 2)

3.3.1 编码实现脉冲统计
/** 测速实现:*  阶段1:脉冲数统计*  阶段2:速度计算* * 阶段1:*  1.定义所使用的中断引脚,以及计数器(使用 volatile 修饰)*  2.setup 中设置波特率,将引脚设置为输入模式*  3.使用 attachInterupt() 函数为引脚添加中断出发时机以及中断函数*  4.中断函数编写计算算法,并打印*    A.单频统计只需要统计单相上升沿或下降沿*    B.2倍频统计需要统计单相的上升沿和下降沿*    C.4倍频统计需要统计两相的上升沿和下降沿*  5.上传并查看结果 */
int motor_A = 21;//中端口是2
int motor_B = 20;//中断口是3
volatile int count = 0;//如果是正转,那么每计数一次自增1,如果是反转,那么每计数一次自减1 void count_A(){//单频计数实现//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比/*if(digitalRead(motor_A) == HIGH){if(digitalRead(motor_B) == LOW){//A 高 B 低count++;  } else {//A 高 B 高count--;  }}*///2倍频计数实现//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 2if(digitalRead(motor_A) == HIGH){if(digitalRead(motor_B) == HIGH){//A 高 B 高count++;  } else {//A 高 B 低count--;  }} else {if(digitalRead(motor_B) == LOW){//A 低 B 低count++;  } else {//A 低 B 高count--;  }  }
}//与A实现类似
//4倍频计数实现
//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 4
void count_B(){if(digitalRead(motor_B) == HIGH){if(digitalRead(motor_A) == LOW){//B 高 A 低count++;} else {//B 高 A 高count--;}} else {if(digitalRead(motor_A) == HIGH){//B 低 A 高count++;} else {//B 低 A 低count--;}}
}void setup() {Serial.begin(57600);//设置波特率  pinMode(motor_A,INPUT);pinMode(motor_B,INPUT);attachInterrupt(2,count_A,CHANGE);//当电平发生改变时触发中断函数//四倍频统计需要为B相也添加中断attachInterrupt(3,count_B,CHANGE);
}void loop() {//测试计数器输出delay(2000);Serial.println(count);
}
3.3.2 转速计算

        需要定义一个开始时间(用于记录每个测速周期的开始时刻),还需要定义一个时间区间(比如50毫秒),时时获取当前时刻,当当前时刻 - 上传结束时刻 >= 时间区间时,就获取当前计数并根据测速公式计算时时速度,计算完毕,计数器归零,重置开始时间。

        核心知识点:当使用中断函数中的变量时,需要先禁止中断noInterrupts(),调用完毕,再重启中断interrupts()。

int reducation = 90;//减速比,根据电机参数设置,比如 15 | 30 | 60
int pulse = 11; //编码器旋转一圈产生的脉冲数该值需要参考商家电机参数
int per_round = pulse * reducation * 4;//车轮旋转一圈产生的脉冲数 
long start_time = millis();//一个计算周期的开始时刻,初始值为 millis();
long interval_time = 50;//一个计算周期 50ms
double current_vel;//获取当前转速的函数
void get_current_vel(){long right_now = millis();  long past_time = right_now - start_time;//计算逝去的时间if(past_time >= interval_time){//如果逝去时间大于等于一个计算周期//1.禁止中断noInterrupts();//2.计算转速 转速单位可以是秒,也可以是分钟... 自定义即可current_vel = (double)count / per_round / past_time * 1000 * 60;//3.重置计数器count = 0;//4.重置开始时间start_time = right_now;//5.重启中断interrupts();Serial.println(current_vel);}
}void loop() {delay(10);get_current_vel();}

 四、电机调速(PID)

4.1 PID控制原理

PID算法是一种经典、简单、高效的动态速度调节方式,P代表比例,I代表积分,D代表微分。

e(t)  作为 PID 控制的输入;
u(t)  作为 PID 控制器的输出和被控对象的输入;
Kp    控制器的比例系数;
Ki    控制器的积分时间,也称积分系数;
Kd    控制器的微分时间,也称微分系数。

P

        如果实现上述场景中的车速控制,一种简单的实现方式是: 确定目标速度,获取当前速度,使用(目标速度-当前速度)*某一系数 计算结果为输出的PWM,再获取当前速度,使用(目标速度-当前速度)*某一系数 计算结果为输出的PWM并输出...如此循环在上述模型中,调速实现是一个闭环,每一次循环都会根据当前时速与目标时速的差值,再乘以以固定系数,计算出需要输出的PWM值,这其中的系数,称之为比例。

I

        上述模型算法中,最终速度与预期速度存在稳态误差,这意味着最终结果可能永远无法达成预期,解决的方法就是使用积分I。每次调速时,输出的PWM还要累加根据积分I计算的结果,以消除静态误差。

D

        当I值设置的过大时,可能会出先"超速"的情况,超速之后可能需要多次调整,产生系统震荡,解决这种情况可以使用D微分,当速度越是接近目标速度时,D就会越施加反方向力,减弱P的控制,起到类似”阻尼“的作用。通过D的使用可以减小系统震荡。

综上,PID闭环控制实现是结合了比例、积分和微分的一种控制机制,通过P可以以比例的方式计算输出,通过I可以消除稳态误差,通过D可以减小系统震荡,三者相结合,最终是要快速、精准且稳定的达成预期结果,而要实现该结果,还需要对这三个数值反复测试、调整...

4.2 PID控制实现 

在Arduino中PID算法已经被封装了,直接整合调用即可,从而提高程序的安全性与开发效率。该库是:Arduino-PID-Library。

案例:通过PID控制电机转速,预期转速为 80r/m。

4.2.1 添加Arduino-PID-Library

首先在 GitHub 下载 PID 库:

​git clone GitHub - br3ttb/Arduino-PID-Library

然后将该文件夹移动到 arduino 的 libraries下:

sudo cp -r Arduino-PID-Library /home/用户xxx/Arduino/libraries

还要重命名文件夹:

sudo mv Arduino-PID-Library ArduinoPIDLibrary

最后重启 ArduinoIDE,即可!

4.2.2 代码

PID调速中,测速是实现闭环的关键实现,所以需要复制之前的电机控制代码以及测速代码。

/** PID 调速实现:* 1.代码准备,复制并修改电机控制以及测速代码* 2.包含PID头文件* 3.创建PID对象* 4.在setup中启用自动调试* 5.调试并更新PWM*/#include <PID_v1.h>int DIRA = 4;
int PWMA = 5;int motor_A = 21;       // 中端口是2
int motor_B = 20;       // 中断口是3
volatile int count = 0; // 如果是正转,那么每计数一次自增1,如果是反转,那么每计数一次自减1void count_A()
{// 单频计数实现// 手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比/*if(digitalRead(motor_A) == HIGH){if(digitalRead(motor_B) == LOW){//A 高 B 低count++;} else {//A 高 B 高count--;}}*/// 2倍频计数实现// 手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 2if (digitalRead(motor_A) == HIGH){if (digitalRead(motor_B) == HIGH){ // A 高 B 高count++;}else{ // A 高 B 低count--;}}else{if (digitalRead(motor_B) == LOW){ // A 低 B 低count++;}else{ // A 低 B 高count--;}}
}// 与A实现类似
// 4倍频计数实现
// 手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 4
void count_B()
{if (digitalRead(motor_B) == HIGH){if (digitalRead(motor_A) == LOW){ // B 高 A 低count++;}else{ // B 高 A 高count--;}}else{if (digitalRead(motor_A) == HIGH){ // B 低 A 高count++;}else{ // B 低 A 低count--;}}
}int reducation = 90;                    // 减速比,根据电机参数设置,比如 15 | 30 | 60
int pulse = 11;                         // 编码器旋转一圈产生的脉冲数该值需要参考商家电机参数
int per_round = pulse * reducation * 4; // 车轮旋转一圈产生的脉冲数
long start_time = millis();             // 一个计算周期的开始时刻,初始值为 millis();
long interval_time = 50;                // 一个计算周期 50ms
double current_vel;// 获取当前转速的函数
void get_current_vel()
{long right_now = millis();long past_time = right_now - start_time; // 计算逝去的时间if (past_time >= interval_time){ // 如果逝去时间大于等于一个计算周期// 1.禁止中断noInterrupts();// 2.计算转速 转速单位可以是秒,也可以是分钟... 自定义即可current_vel = (double)count / per_round / past_time * 1000 * 60;// 3.重置计数器count = 0;// 4.重置开始时间start_time = right_now;// 5.重启中断interrupts();Serial.println(current_vel);}
}//-------------------------------------PID-------------------------------------------
// 创建 PID 对象
// 1.当前转速 2.计算输出的pwm 3.目标转速 4.kp 5.ki 6.kd 7.当输入与目标值出现偏差时,向哪个方向控制
double pwm; // 电机驱动的PWM值
double target = 80;
double kp = 1.5, ki = 3.0, kd = 0.1;
PID pid(&current_vel, &pwm, &target, kp, ki, kd, DIRECT);// 速度更新函数
void update_vel()
{// 获取当前速度get_current_vel();pid.Compute(); // 计算需要输出的PWMdigitalWrite(DIRA, HIGH);analogWrite(PWMA, pwm);
}void setup()
{Serial.begin(57600); // 设置波特率pinMode(18, INPUT);pinMode(19, INPUT);// 两个电机驱动引脚都设置为 OUTPUTpinMode(DIRA, OUTPUT);pinMode(PWMA, OUTPUT);attachInterrupt(2, count_A, CHANGE); // 当电平发生改变时触发中断函数// 四倍频统计需要为B相也添加中断attachInterrupt(3, count_B, CHANGE);pid.SetMode(AUTOMATIC);
}void loop()
{delay(10);update_vel();
}
4.2.3 核心代码解释

1.包含PID头文件

#include <PID_v1.h>

2.创建PID对象

//创建 PID 对象
//1.当前转速 2.计算输出的pwm 3.目标转速 4.kp 5.ki 6.kd 7.当输入与目标值出现偏差时,向哪个方向控制
double pwm;//电机驱动的PWM值
double target = 120;
double kp=1.5, ki=3.0, kd=0.1;
PID pid(&current_vel,&pwm,&target,kp,ki,kd,DIRECT);

3.setup中启用PID自动控制

pid.SetMode(AUTOMATIC);

4.计算输出值

pid.Compute();
4.2.4 PID调试 

PID控制的最终预期结果,是要快速、精准、稳定的达成预期结果,P主要用于控制响应速度,I主要用于控制精度,D主要用于减小震荡增强系统稳定性,三者的取值是需要反复调试的,调试过程中需要查看系统的响应曲线,根据响应曲线以确定合适的PID值。

在 Arduino 中响应曲线的查看可以借助于 Serial.println() 将结果输出,然后再选择菜单栏的工具下串口绘图器以图形化的方式显示响应结果:

 ☆PID调试技巧:

参数整定找最佳,从小到大顺序查
先是比例后积分,最后再专把微分加
曲线振属荡很频繁,比例度盘要放大
曲线漂浮绕大湾,比例度盘往小扳
曲线偏离回复慢,积分时间往下降
曲线波动周期长,积分时间再加长
曲线振荡频率快,先把微分降下来
动差大来波动慢。微分时间应加长
理想曲线两个波,前高后低4比1
一看二调多分析,调节质量不会低

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65451.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

119.使用AI Agent解决问题:Jenkins build Pipeline时,提示npm ERR! errno FETCH_ERROR

目录 1.Jenkins Build时的错误 2.百度文心快码AI智能体帮我解决 提问1&#xff1a;jenkins中如何配置npm的源 提问2&#xff1a;jenkins pipeline 类型为pipeline script from SCM时&#xff0c;如何配置npm源 3.最终解决方法-Jenkinsfile的修改 4.感触 1.Jenkins Build时…

Java 日期时间格式化标准

文章目录 Java日期时间格式化符号ISO 8601中的日期时间ISO 8601标准的定义ISO 8601日期时间格式 周数年份ISO 8601中的周数年份Java中的周数年份 Java跨年日期格式化BUG注意事项 Java日期时间格式化符号 JDK官网截图&#xff1a; 格式化符号梳理&#xff1a; 符号描述符号用…

高山旅游景区有效降低成本,无人机山下到山上物资吊运技术详解

在高山旅游景区&#xff0c;传统的物资运输方式往往面临人力成本高昂、效率低下等问题&#xff0c;而无人机技术的引入为这一难题提供了新的解决方案。以下是对无人机从山下到山上进行物资吊运技术的详细解析&#xff1a; 一、无人机物资吊运技术的优势 1. 降低人力成本&#…

Python爬虫 - 豆瓣图书数据爬取、处理与存储

文章目录 前言一、使用版本二、需求分析1. 分析要爬取的内容1.1 分析要爬取的单个图书信息1.2 爬取步骤1.2.1 爬取豆瓣图书标签分类页面1.2.2 爬取分类页面1.2.3 爬取单个图书页面 1.3 内容所在的标签定位 2. 数据用途2.1 基础分析2.2 高级分析 3. 应对反爬机制的策略3.1 使用 …

深度学习——回归实战

线性回归&#xff1a; 线性&#xff1a;自变量和应变量之间是线性关系&#xff0c;如&#xff1a;y wx b 回归&#xff1a;拟合一条曲线&#xff0c;使真实值和拟合值差距尽可能小 目标&#xff1a;求解参数w和b 所用算法&#xff1a;梯度下降算法 梯度下降&#…

单片机-串转并-74HC595芯片

1、74HC595芯片介绍 74HC595 是一个 8 位串行输入、并行输出的位移缓存器&#xff0c;其中并行输出为三态输出&#xff08;即高电平、低电平和高阻抗&#xff09;。 15 和 1 到 7 脚 QA--QH&#xff1a;并行数据输出 9 脚 QH 非&#xff1a;串行数据输出 10 脚 SCLK 非&#x…

探索AI在地质科研绘图中的应用:ChatGPT与Midjourney绘图流程与效果对比

文章目录 个人感受一、AI绘图流程1.1 Midjourney&#xff08;1&#xff09;环境配置&#xff08;2&#xff09;生成prompt&#xff08;3&#xff09;完善prompt&#xff08;4&#xff09;开始绘图&#xff08;5&#xff09;后处理 1.2 ChatGPT不合理的出图结果解决方案 二、主题…

【微服务】6、限流 熔断

线程隔离与容错处理 本视频主要讲解了在购物车业务中&#xff0c;因商品微服务响应慢导致的问题及解决方案&#xff0c;重点介绍了线程隔离后查询购物车业务不可用的情况&#xff0c;以及如何通过Fallback逻辑进行缓解&#xff0c;包括配置Feign调用为簇点资源、添加Fallback逻…

25年01月HarmonyOS应用基础认证最新题库

判断题 “一次开发&#xff0c;多端部署”指的是一个工程&#xff0c;一次开发上架&#xff0c;多端按需部署。为了实现这一目的&#xff0c;HarmonyOS提供了多端开发环境&#xff0c;多端开发能力以及多端分发机制。 答案&#xff1a;正确 《鸿蒙生态应用开发白皮书》全面阐释…

ELK实战(最详细)

一、什么是ELK ELK是三个产品的简称&#xff1a;ElasticSearch(简称ES) 、Logstash 、Kibana 。其中&#xff1a; ElasticSearch&#xff1a;是一个开源分布式搜索引擎Logstash &#xff1a;是一个数据收集引擎&#xff0c;支持日志搜集、分析、过滤&#xff0c;支持大量数据…

Dubbo-笔记随记一

一、实战 1 . Springboot整合 1.1 服务提供者 1.1.1 依赖 <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>3.2.10</version></dependency><dependency&g…

ETCD渗透利用指南

目录 未指定使用put操作报错 未指定操作版本使用get报错 首先etcd分为两个版本v2和v3&#xff0c;不同的API结果无论是访问URL还是使用etcdctl进行通信&#xff0c;都会导致问题&#xff0c;例如使用etcdctl和v3进行通信&#xff0c;如果没有实名ETCDCTL_API3指定API版本会直接…

使用VUE3创建个人静态主页

使用VUE3创建个人静态主页 &#x1f31f; 前言&#x1f60e;体验&#x1f528; 具体实现✨ 核心功能&#x1f3d7;️ 项目结构&#x1f680; 用这个项目部署 Git Page &#x1f4d6; 参考 &#x1f31f; 前言 作为开发者或者内容创作者&#xff0c;我们经常需要创建静态网页&a…

llm大模型学习

llm大模型 混合专家模型&#xff08;MoE&#xff09;MoE结构路由router专家expertSwitch Transformer的典型MOE模型最后MoE总结 混合专家模型&#xff08;MoE&#xff09; 模型规模是提升LLM大语言模型性能的关键因素&#xff0c;但也会增加计算成本。Mixture of Experts (MoE…

Linux入门攻坚——43、keepalived入门-1

Linux Cluster&#xff08;Linux集群的类型&#xff09;&#xff1a;LB、HA、HPC&#xff0c;分别是负载均衡集群、高可用性集群、高性能集群。 LB&#xff1a;lvs&#xff0c;nginx HA&#xff1a;keepalived&#xff0c;heartbeat&#xff0c;corosync&#xff0c;cman HP&am…

YOLOv8/YOLOv11改进 添加CBAM、GAM、SimAM、EMA、CAA、ECA、CA等多种注意力机制

目录 前言 CBAM GAM SimAM EMA CAA ECA CA 添加方法 YAML文件添加 使用改进训练 前言 本篇文章将为大家介绍Ultralytics/YOLOv8/YOLOv11中常用注意力机制的添加&#xff0c;可以满足一些简单的涨点需求。本文仅写方法&#xff0c;原理不多讲解&#xff0c;需要可跳…

【C语言】_指针与数组

目录 1. 数组名的含义 1.1 数组名与数组首元素的地址的联系 1.3 数组名与首元素地址相异的情况 2. 使用指针访问数组 3. 一维数组传参的本质 3.1 代码示例1&#xff1a;函数体内计算sz&#xff08;sz不作实参传递&#xff09; 3.2 代码示例2&#xff1a;sz作为实参传递 3…

解决“KEIL5软件模拟仿真无法打印浮点数”之问题

在没有外部硬件支持时&#xff0c;我们会使用KEIL5软件模拟仿真&#xff0c;这是是仿真必须要掌握的技巧。 1、点击“Project”&#xff0c;然后点击“Options for target 项目名字”&#xff0c;点击“Device”,选择CPU型号。 2、点击“OK” 3、点击“Target”,勾选“Use Mi…

donet (MVC)webAPI 的接受json 的操作

直接用对象来进行接收&#xff0c;这个方法还不错的。 public class BangdingWeiguiJiluController : ApiController{/// <summary>/// Json数据录入错误信息/// </summary>/// <param name"WeiguiInfos"></param>/// <returns></r…

设计模式与游戏完美开发(3)

更多内容可以浏览本人博客&#xff1a;https://azureblog.cn/ &#x1f60a; 该文章主体内容来自《设计模式与游戏完美开发》—蔡升达 第二篇 基础系统 第五章 获取游戏服务的唯一对象——单例模式&#xff08;Singleton&#xff09; 游戏实现中的唯一对象 在游戏开发过程中…