使用Arduino读取RC接收机PWM信号的三种方式
注:最近在玩模型的静改动,需要使用2.4Ghz的RC控制器来控制arduino。
在网上搜到一片使用Arduino处理PWM信号的文章,觉得不错,翻译过来,希望能帮到同好。
(翻译、总结自 https://www.benripley.com/diy/arduino/three-ways-to-read-a-pwm-signal-with-arduino/)
PWM信号通过控制脉冲的宽度实现信息的传递,常用于RC模型的舵机操控等。(注:PWM是数字信号,只有低电平和高电平,高电平持续的时间即为脉冲的宽度)
使用Arduino来处理来自RC接收机的PWM信号大致有三种方式:
- pulseIn() 函数
- 外部中断 External Interrupts
- 针脚电平变化中断 Pin Change Interrupts
使用pulseIn()函数
先来说说最简单的 pulseIn()函数,该函数会等待输入信号变为高电平(在value设置为HIGH的情况下,反之LOW等待低电平),并启动定时器,当输入信号再次转为低电平时结束定时器,返回高电平的持续时间,单位为毫秒(ms)。
需要补充的是,arduino可处理的脉冲时长的范围为10微秒到3分钟。
pulseIn(pin, value, timeout) 可以手动设置timeout,超过该值返回0。
byte PWM_PIN = 3; //将PWM的信号线输入到3号引脚
int pwm_value;
void setup() {
pinMode(PWM_PIN, INPUT);//将该引脚设置为输入模式
Serial.begin(115200);
}
void loop() {
pwm_value = pulseIn(PWM_PIN, HIGH);//检测高电平
Serial.println(pwm_value);//串口输出值
}
使用pulseIn()函数是最简单的一种方式,但缺点是在定时器等待的同时无法使用CPU, 造成执行效率低下。
使用外部中断
使用外部中断可以解决CPU无法使用的效率问题,大部分的arduino有两个外部中断,分别在digital 2和digital 3引脚。中断可由电平的改变触发。
attachInterrupt(0, risingCallback, RISING);
//0号外部中断,在外部出现电平升高时执行risingCallback()
volatile int pwm_value = 0;
//Arduino docs 强调在有并发执行的线程(如中断)情况下,变量声明必许使用volatile
volatile int prev_time = 0;
void setup() {
Serial.begin(115200);
// 当在外部出现电平升高时执行risingCallback()
attachInterrupt(0, risingCallback, RISING); //启用上升中断
}
void loop() { }
void risingCallback() {
attachInterrupt(0, fallingCallback FALLING);//启用下降中断
prev_time = micros(); //开始计时
}
void fallingCallback() {
attachInterrupt(0, risingCallback, RISING);
pwm_value = micros()-prev_time;//低电平持续时间
Serial.println(pwm_value);
}
使用外部中断可以提升执行效率,但由于arduino的外部中断只能使用digital 2 和digital 3 针脚 限制了一些场景下的开发。
关于arduino的中断机制可参阅
关于中断(Interrupt)的详细介绍以及IIC、软串口、PinChangeInt库
使用引脚电平变化中断 Pin Change Interrupts
PCI 可以在任意arduino针脚上使用,PCI的手动实现较为复杂,可以使用 PinChangeInt 库
#include <PinChangeInt.h>
#define MY_PIN 5 // 可以使用任意针脚
volatile int pwm_value = 0;
volatile int prev_time = 0;
uint8_t latest_interrupted_pin;
void rising()
{
latest_interrupted_pin=PCintPort::arduinoPin;
PCintPort::attachInterrupt(latest_interrupted_pin, &falling, FALLING);
prev_time = micros();
}
void falling() {
latest_interrupted_pin=PCintPort::arduinoPin;
PCintPort::attachInterrupt(latest_interrupted_pin, &rising, RISING);
pwm_value = micros()-prev_time;
Serial.println(state);
}
void setup() {
pinMode(MY_PIN, INPUT); digitalWrite(MY_PIN, HIGH);
Serial.begin(115200);
PCintPort::attachInterrupt(MY_PIN, &rising, RISING);
}
void loop() { }