在大多数Arduino入门教程中,Servo库往往是驱动舵机的首选方案。它简单易用,只需几行代码就能让舵机转动到指定角度。但当你开始构建更复杂的项目时,可能会遇到这些情况:
典型PWM信号参数对比
| 参数 | 标准舵机 | 连续旋转舵机 | 高精度舵机 |
|---|---|---|---|
| 周期 | 20ms | 20ms | 20ms |
| 最小脉宽 | 500μs | 500μs | 500μs |
| 最大脉宽 | 2500μs | 2500μs | 2500μs |
| 中位脉宽 | 1500μs | 1500μs | 1500μs |
| 分辨率 | ~10μs | ~10μs | ~1μs |
提示:所有常见舵机都遵循这个PWM标准,区别仅在于机械结构和反馈系统
Arduino UNO的PWM引脚(标记为~的3,5,6,9,10,11)由三个定时器驱动:
推荐接线方案
code复制Arduino UNO 舵机
---------- ----
5V → 红线(VCC)
GND → 黑/棕线(GND)
~9/~10 → 黄/橙线(信号)
注意:直接使用UNO的5V引脚供电时,最多驱动2-3个小扭矩舵机。大功率应用需外接电源!
让我们从最基础的脉冲生成开始。这段代码不使用任何库,直接操作数字引脚:
cpp复制const int servoPin = 9; // 使用Timer1控制的9号引脚
void setup() {
pinMode(servoPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
// 从0度转到180度再转回
for(int angle = 0; angle <= 180; angle += 10) {
setServoAngle(servoPin, angle);
delay(100);
}
for(int angle = 180; angle >= 0; angle -= 10) {
setServoAngle(servoPin, angle);
delay(100);
}
}
// 核心PWM生成函数
void setServoAngle(int pin, int angle) {
int pulseWidth = map(angle, 0, 180, 500, 2500); // 角度转脉宽
digitalWrite(pin, HIGH);
delayMicroseconds(pulseWidth); // 高电平持续时间
digitalWrite(pin, LOW);
delayMicroseconds(20000 - pulseWidth); // 补足20ms周期
}
代码优化技巧:
micros()替代delayMicroseconds()实现非阻塞控制noInterrupts()/interrupts())结合串口通信实现动态控制,方便调试和校准:
cpp复制int currentAngle = 90; // 默认中间位置
void setup() {
Serial.begin(115200);
pinMode(servoPin, OUTPUT);
setServoAngle(servoPin, currentAngle);
Serial.println("输入角度(0-180)或命令(c=校准):");
}
void loop() {
if(Serial.available()) {
char cmd = Serial.read();
if(cmd == 'c') { // 校准模式
calibrateServo();
}
else if(cmd >= '0' && cmd <= '9') {
currentAngle = Serial.parseInt();
currentAngle = constrain(currentAngle, 0, 180);
setServoAngle(servoPin, currentAngle);
Serial.print("当前角度: ");
Serial.println(currentAngle);
}
}
}
void calibrateServo() {
Serial.println("校准模式 - 输入测试脉宽(us):");
while(Serial.available()) Serial.read(); // 清空缓冲区
while(true) {
if(Serial.available()) {
int pulse = Serial.parseInt();
if(pulse == 0) break; // 输入0退出校准
pulse = constrain(pulse, 500, 2500);
digitalWrite(servoPin, HIGH);
delayMicroseconds(pulse);
digitalWrite(servoPin, LOW);
delayMicroseconds(20000 - pulse);
Serial.print("测试脉宽: ");
Serial.print(pulse);
Serial.println("us");
}
}
}
典型校准数据记录表
| 脉宽(μs) | 实际角度 | 备注 |
|---|---|---|
| 500 | 0° | 机械限位 |
| 1000 | 45° | |
| 1500 | 90° | 理论中点 |
| 2000 | 135° | |
| 2500 | 180° | 机械限位 |
1. 消除舵机抖动技巧
2. 多舵机同步方案
cpp复制// 非阻塞式多舵机控制框架
unsigned long prevMicros = 0;
const int SERVO_COUNT = 3;
int servoPins[SERVO_COUNT] = {9, 10, 11};
int servoAngles[SERVO_COUNT] = {0, 45, 90};
void updateServos() {
static int currentServo = 0;
unsigned long currentMicros = micros();
if(currentMicros - prevMicros >= 400) { // 每个舵机间隔400μs
prevMicros = currentMicros;
// 结束上一个脉冲
if(currentServo > 0) {
digitalWrite(servoPins[currentServo-1], LOW);
}
// 开始新脉冲
if(currentServo < SERVO_COUNT) {
int pulse = map(servoAngles[currentServo], 0, 180, 500, 2500);
digitalWrite(servoPins[currentServo], HIGH);
servoPulseEnd[currentServo] = currentMicros + pulse;
currentServo++;
} else {
// 所有舵机脉冲结束,等待周期完成
if(currentMicros - cycleStart >= 20000) {
currentServo = 0;
cycleStart = currentMicros;
}
}
}
// 检查并结束到期的脉冲
for(int i=0; i<SERVO_COUNT; i++) {
if(currentMicros >= servoPulseEnd[i]) {
digitalWrite(servoPins[i], LOW);
}
}
}
3. 异常情况处理
标准舵机经过简单改造可实现连续旋转:
物理改造:
控制逻辑:
cpp复制void setRotationSpeed(int pin, int speed) { // speed: -100到+100
int pulse = map(abs(speed), 0, 100, 1500, speed>0 ? 2500 : 500);
digitalWrite(pin, HIGH);
delayMicroseconds(pulse);
digitalWrite(pin, LOW);
delayMicroseconds(20000 - pulse);
}
速度控制参考值
| 脉宽(μs) | 旋转方向 | 速度 |
|---|---|---|
| 500 | 逆时针 | 最快 |
| 1500 | 停止 | 无 |
| 2500 | 顺时针 | 最快 |
在实际机器人项目中,这种改造常用于驱动轮式底盘。通过精确控制两个连续旋转舵机的速度差,可以实现差速转向。