×

让Arduino控制的汽车直行

消耗积分:0 | 格式:zip | 大小:0.29 MB | 2023-02-06

20153

分享资料个

描述

互联网上有大量关于 Arduino 控制的汽车项目的描述。套件包括电机和底盘。其中一些套件是两轮驱动,第三轮作为浮动脚轮。

这种三轮配置本质上是不稳定的,即使在驱动程序中将两个电机设置为相同的速度,也不会直线行驶。车轮直径、滚动摩擦和电机特性的变化会导致偏差。克服这个问题的唯一方法是增加反馈并定期对电机速度进行修正。该项目将解释如何实施以实现直线行驶。

Arduino 控制的汽车

这是一个由 Arduino 控制的汽车示例,由两轮驱动套件构建。主要组成部分是:

两轮驱动套件,包括电机、底盘、各种螺丝和螺母、车轮和车轮编码器。

  • 阿杜诺UNO
  • L298N电机驱动桥板
  • 2 x HC-020K 编码器模块
  • 4 x ICR16340 锂离子电池和电池座
  • 切换开/关开关
  • 连接线

本文不包括如何制造汽车的描述;互联网上有很多资源可以解释所需的步骤。

成品车如下:

 

img_3640_z855wTTegG.JPG?auto=compress%2Cformat&w=740&h=555&fit=max
 

整车电路图如下:

 

schematicdiagram_Gw0R9NIhYI.png?auto=compress%2Cformat&w=740&h=555&fit=max
 

只是配置上需要注意的几点:

  • 该车采用并联/串联配置的四节 ICR16340 700 mAh 锂离子电池。其中两个串联的电池在充满电后可提供约 8 V 的电压,足以驱动电机和 Arduino 板。并联电池可能存在问题(可能会产生循环电流),作为替代方案,ICR16340 电池有 2800 mAh 版本——其中两个串联应该足以为汽车供电。
  • 电机控制由 L298N 桥驱动器模块执行。同样,有大量资源可以解释它们的运行方式以及如何连接电机和控件。
  • 该电路使用数字引脚 5 和 6 将 PWM 输出到 L298N 板以控制电机速度。这些引脚的 PWM 频率由 ATmega328P 的定时器 0 控制。数字引脚 9 和 10 的 PWM 频率由定时器 1 控制。稍后在该项目中,定时器 1 用于触发距离控制回路,因此与引脚 9 和 10 的 PWM 冲突。不要在该项目中使用这些引脚。
  • 根据您的具体接线方式,可能需要调换电机引线以获得一致的方向。此外,HC-020K 编码器模块必须在软件中与正确的电机配对。这可能需要交换中断 2 和 3 或调整软件。

脉搏过多之谜

HC-020K 编码器模块依靠 LM393 比较器来生成方波脉冲。编码器轮上的每个孔都会在方波中产生上升沿和下降沿,其频率与轮速成正比。对这些方波脉冲进行计数也会得出与行进距离成正比的总数。使用 Arduino 对这些脉冲进行计数的标准方法是将它们连接到中断并让中断服务例程 (ISR) 递增计数器(稍后提供示例代码)。

几项试图将脉冲计数与 rpm 和行进距离相协调的实验表明,脉冲计数不正确的因素约为十倍 - 十倍于许多脉冲。这使得编码器几乎毫无用处。此异常需要进一步调查

以下是 HC-020K 编码器的一些示波器轨迹:

 

screenshot1_3513k27dnJ.bmp?auto=compress%2Cformat&w=740&h=555&fit=max
 

在 5 毫秒的时间尺度上,方波的上升沿和下降沿看起来很干净。然而,如果放大到 2 微秒的时间尺度,方波的下降沿会在 5V 到 0V 的转换期间显示多个向上向下的尖峰。

 

screenshot2_4dDOvhtNGO.bmp?auto=compress%2Cformat&w=740&h=555&fit=max
 

这些尖峰中的每一个都可能触发中断并导致脉冲计数过多。它们是由 HC-020K 编码器模块上的比较器电路的性质引起的——它在开环模式下运行。

解决这个问题的正确方法是通过在输出和提供参考电压的分压器之间添加一个反馈电阻,在比较器电路中引入迟滞。有几篇文章解释了这个电路——这里是德州仪器的一个例子。

https://www.ti.com/lit/ug/tidu020a/tidu020a.pdf

要针对迟滞修改 HC-020K 编码器模块,需要从输出端到 LM393 的引脚 2 连接一个 50KOhm 电阻。由于使用了表面贴装 IC,实际上很难执行此 mod。如果你能做到——祝你好运!

一些额外的研究显示了一种替代方法。这需要在输出和地之间有一个 100nF 的电容器。这是一个更容易实现的模组,如下所示。

 

img_3638_uZYt2trHXQ.jpg?auto=compress%2Cformat&w=740&h=555&fit=max
 

 

img_3637_CboVYDCv8i.jpg?auto=compress%2Cformat&w=740&h=555&fit=max
 

电容器有效滤除高频尖峰并提供平滑过渡。产生的波不是方波,因为它被电容器在前沿和下降沿上的充电和放电所修改。示波器轨迹如下:

 

screenshot3_XqGSjsEfkt.bmp?auto=compress%2Cformat&w=740&h=555&fit=max
 

放大到5微秒的时间尺度,方波的下降沿显示单次跳变

 

screenshot4_XrcJzrHELA.bmp?auto=compress%2Cformat&w=740&h=555&fit=max
 

进行此修改后,脉冲计数与观察到的转速很好地对齐。

反馈控制

既然解开了太多脉冲的谜团,是时候将车轮编码器的反馈控制应用于电机速度了。基本思想是根据测得的脉冲数调整单个电机速度,使轮子覆盖相同的距离并沿直线驱动 Arduino。

反馈控制回路分为许多类别,但最常见的称为 PID(比例、积分和微分)回路。关于这个主题的信息很多,他们的研究是一门完整的学科。简单系统如下图:

 

controlloop_aMe0oCtBLi.png?auto=compress%2Cformat&w=740&h=555&fit=max
 

将设定点(期望输出)与反馈(实际输出)进行比较,误差用于生成对受控系统的输入。该输入将系统驱动到所需的输出,以便最终反馈等于设定点。

对于 Arduino 汽车,需要控制的输出是两个车轮的脉冲计数之差。如果此差异为零,则车轮将行驶相同的距离(假设车轮直径相等)。

几个定义:

  • 来自车轮 A(电机 A)的脉冲计数 = pulseA
  • 来自轮 B(电机 B)的脉冲计数 = pulseB
  • 脉冲计数之间的差异 = 反馈 = pError = pulseA – pulseB
  • 设定点 = 0

下面是Arduino小车基本控制策略的流程图:

 

flowchart_FatbK3YVja.png?auto=compress%2Cformat&w=740&h=555&fit=max
 

关于中断的部分

Arduino 上的控制程序使用中断有两个目的:

  • 对来自编码器的脉冲进行计数。每次编码器输出进行 1 到 0 转换时,都会在 Arduino 上触发中断。
  • 强制控制回路比较计数的脉冲并根据控制算法调整电机速度。

第一个中断的相关代码片段是

const int encoder1 = 2;
const int encoder2 = 3;
volatile int pulse1;
volatile int pulse2;

void setup(){
  pulse1 = 0;
  pulse2 = 0;

  attachInterrupt(digitalPinToInterrupt(encoder1), count1, FALLING);
  attachInterrupt(digitalPinToInterrupt(encoder2), count2, FALLING);
}

void count1(){
  // counting the number of pulses for encoder 1
  pulse1++;
}

void count2(){
  // counting the number of pulses for encoder 2
  pulse2++;

引脚 2 和 3 用于编码器的中断输入。pulse1 和 pulse2 是用于保存计数的变量。Count1 和 count2 是中断服务程序,只是增加计数器。中断在从编码器接收到的方波的下降沿触发。

第二个中断使用 ATMega328 内置的 Timer1。定时器以预定的时间间隔触发中断。然后,这会运行一个控制回路,使汽车保持直线行驶。相关代码片段如下:

void setup(){

cli();//stop interrupts

  //set timer1 interrupt at 4Hz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 4hz increments
  OCR1A = 3905;// = (16*10^6) / (4*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS12 and CS10 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  sei();//allow interrupts

}

ISR(TIMER1_COMPA_vect){

//Control loop here

}

预分频器 (0CR1A) 的值决定了中断的频率。

控制汽车(第 1 部分)

第一种直接驾驶汽车的方法使用基于流程图的简单算法。代码如下:

 int pError = 0;
pError = pulse1 - pulse2;

 // pError is positive speed up motor A and slow down motor B
 if(pError > 0){
  analogWrite(enA, (motorSpeed + 7));
  analogWrite(enB, (motorSpeed - 7));
 }

 // pError is negative speed up motor B and slow down motor A
 else if(pError < 0){
  analogWrite(enA, (motorSpeed - 7));
  analogWrite(enB, (motorSpeed + 7));
 }

 else {
  analogWrite(enA, motorSpeed);
  analogWrite(enB, motorSpeed);
 }

 digitalWrite(led, toggle);
 toggle = !toggle;

控制汽车(第 2 部分)

一种更复杂的方法是使用 PID 控制器。Arduino 库包括一个名为 FastPID 的库,它实现了 PID 控制器。添加到与 IDE 关联的库。

有关如何在以下链接中使用此库的文档

https://github.com/mike-matera/FastPID

这是代码片段

float Kp=0.6, Ki=0.4, Kd=0, Hz=4;
int output_bits = 8;
bool output_signed = false;

FastPID driveStraight(Kp, Ki, Kd, Hz, output_bits, output_signed);
ISR(TIMER1_COMPA_vect){
  static int pError;
  pError = 0;
  pError = pulse1 - pulse2;
  uint8_t output = driveStraight.step(setpoint, pError);

  analogWrite(enA, (motorSpeed - output));
  analogWrite(enB, (motorSpeed + output));

  digitalWrite(led,toggle);
  toggle = !toggle;
}

可以通过为 Kp、Kd 和 Ki 分配不同的值来调整回路的灵敏度

最后的话

希望这有助于直接驾驶!


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !