我们常说的舵机,它的学名叫做伺服电机,它是一种带有输出轴的小装置。该轴可以通过发送伺服编码信号定位到特定的位置,也就是可以定义角度。只要编码信号存在,伺服电机就会一直保持轴的角度位置。随着编码信号的改变,轴的角度位置也随之改变。
实际上,伺服系统在无线电控制的飞机上被用来控制尾舵和船舵。它们也用于无线电控制的汽车、航模等,当然还有机器人。
**
**
1,标准的舵机有3条导线,分别是:电源线(红色)、地线 (黑色或棕色)、控制线(黄色);
2,其工作原理:PWM信号由接收通道进入信号解调电路进行解调,获得一个直流偏置电压。直流偏置电压与电位器的电压比较,获得电压差并输出。该输出送入电机驱动集成电路以驱动电机正反转。当电机转动时,通过级联减速齿轮带动电位器旋转,直到电压差为O,电机停止转动。
舵机的控制信号是PWM信号,利用占空比的变化,改变舵机的位置。
3.舵机的伺服系统由可变宽度的脉冲来进行控制,控制线是用来传送脉冲的。脉冲的参数有最小值,最大值,和频率。一般而言,舵机的基准信号都是周期为20ms,宽度为1.5ms。这个基准信号定义的位置为中间位置。舵机有最大转动角度,中间位置的定义就是从这个位置到最大角度与最小角度的量完全一样。最重要的一点是,不同舵机的最大转动角度可能不相同,但是其中间位置的脉冲宽度是一定的,那就是1.5ms。如下图:
角度是由来自控制线的持续的脉冲所产生。这种控制方法叫做脉冲调制。脉冲的长短决定舵机转动多大角度。例如:1.5毫秒脉冲会到转动到中间位置(对于180°舵机来说,就是90°位置)。当控制系统发出指令,让舵机移动到某一位置,并让他保持这个角度,这时外力的影响不会让他角度产生变化,但是这个是由上限的,上限就是他的最大扭力。除非控制系统不停的发出脉冲稳定舵机的角度,舵机的角度不会一直不变。
当舵机接收到一个小于1.5ms的脉冲,输出轴会以中间位置为标准,逆时针旋转一定角度。接收到的脉冲大于1.5ms情况相反。不同品牌,甚至同一品牌的不同舵机,都会有不同的最大值和最小值。一般而言,最小脉冲为1ms,最大脉冲为2ms。如下图:
**
**舵机一般用单片机或者数字电路控制。舵机工作主要跟控制线的高电平持续时间有关系,一般按0.5ms(毫秒)划分,如果持续时间为0.5ms,1ms,1.5ms,2ms,2.5ms时,舵机会转过不同的角度。
舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度伺服为例,那么对应的控制关系是为:脉冲 设置为 0.5ms旋转角度为0度;脉冲 设置为 1.0ms旋转角度为45度;脉冲 设置为 1.5ms旋转角度为90度;脉冲 设置为 2.0ms旋转角度为135度;脉冲 设置为 2.5ms旋转角度为180度。
舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。以180度角度伺服为例,那么对应的控制关系是这样的:
0.5ms--------------0度;
1.0ms------------45度;
1.5ms------------90度;
2.0ms-----------135度;
2.5ms-----------180度;
舵机的追随特性
假设现在舵机稳定在A点,这时候CPU发出一个PWM信号,舵机全速由A点转向B点,在这个过程中需要一段时间,舵机才能运动到B点。
保持时间为Tw
当Tw≥△T时,舵机能够到达目标,并有剩余时间;
当Tw≤△T时,舵机不能到达目标;
理论上:当Tw=△T时,系统最连贯,而且舵机运动的最快。
实际过程中w不尽相同,连贯运动时的极限△T比较难以计算出来。
当PWM信号以最小变化量即(1DIV=8us)依次变化时,舵机的分辨率最高,但是速度会减慢。
**
**
/*
单片机:STM32F103RCT6/STM32F103C8T6 倍频 72M 8路舵机控制
舵机IO PA0~PA3 PB3~PB6
*/
#include "stm32f10x_conf.h"
#define tb_interrupt_open() {__enable_irq();} //总中断打开
void rcc_init(void); //主频设置
void delay_ms(unsigned int t); //毫秒级别延时
void dj_io_init(void); //舵机 IO 口初始化
void dj_io_set(u8 index, u8 level); //舵机 IO 口高低电平设置
void TIM2_Int_Init(u16 arr,u16 psc);//舵机 定时器初始化
void gpioA_pin_set(unsigned char pin, unsigned char level);
void gpioB_pin_set(unsigned char pin, unsigned char level);
//舵机脉冲数组
int duoji_pulse[8] = {1500,1500,1500,1500,1500,1500,1500,1500} , i;
int main(void) {
rcc_init();
dj_io_init();
TIM2_Int_Init(20000,71);
tb_interrupt_open();
while(1) {
for(i=0;i<8;i++) {
duoji_pulse[i] = 1000;//循环把8个舵机位置设定到1000
}
delay_ms(1000);
for(i=0;i<8;i++) {
duoji_pulse[i] = 2000;//循环把8个舵机位置设定到2000
}
delay_ms(1000);
}
}
void rcc_init(void) {
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
while(HSEStartUpStatus == ERROR);
RCC_HCLKConfig(RCC_SYSCLK_Div1);//SYSCLK
RCC_PCLK1Config(RCC_HCLK_Div2);//APB1 MAX = 36M
RCC_PCLK2Config(RCC_HCLK_Div1);//APB2 MAX = 72M
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
}
void delay_ms(unsigned int t) {
int t1;
while(t--) {
t1 = 7200;
while(t1--);
}
}
void dj_io_init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void dj_io_set(u8 index, u8 level) {
switch(index) {
case 0:gpioA_pin_set(0, level);break;
case 1:gpioA_pin_set(1, level);break;
case 2:gpioA_pin_set(2, level);break;
case 3:gpioA_pin_set(3, level);break;
case 4:gpioB_pin_set(3, level);break;
case 5:gpioB_pin_set(4, level);break;
case 6:gpioB_pin_set(5, level);break;
case 7:gpioB_pin_set(6, level);break;
default:break;
}
}
void gpioA_pin_set(unsigned char pin, unsigned char level) {
if(level) {
GPIO_SetBits(GPIOA,1 << pin);
} else {
GPIO_ResetBits(GPIOA,1 << pin);
}
}
void gpioB_pin_set(unsigned char pin, unsigned char level) {
if(level) {
GPIO_SetBits(GPIOB,1 << pin);
} else {
GPIO_ResetBits(GPIOB,1 << pin);
}
}
void TIM2_Int_Init(u16 arr,u16 psc) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //①时钟 TIM2 使能
//定时器 TIM2 初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //②初始化 TIM2
TIM_ARRPreloadConfig(TIM2, DISABLE);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //③允许更新中断
//中断优先级 NVIC 设置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2 中断
//NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x0000);
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级 0 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级 2 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
NVIC_Init(&NVIC_InitStructure); //④初始化 NVIC 寄存器
TIM_Cmd(TIM2, ENABLE); //⑤使能 TIM2
}
void TIM2_IRQHandler(void) {
static u8 flag = 0;
static u8 duoji_index1 = 0;
int temp;
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查 TIM2 更新中断发生与否
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); //清除 TIM2 更新中断标志
if(duoji_index1 == 8) {
duoji_index1 = 0;
}
if(!flag) {
TIM2->ARR = ((unsigned int)(duoji_pulse[duoji_index1]));
dj_io_set(duoji_index1, 1);
} else {
temp = 2500 - (unsigned int)(duoji_pulse[duoji_index1]);
if(temp < 20)temp = 20;
TIM2->ARR = temp;
dj_io_set(duoji_index1, 0);
duoji_index1 ++;
}
flag = !flag;
}
}
注:若学者用的是STC89C51、STC89C52单片机,可以以下面的代码作为参考,通过修改头文件等方式,依然适用
/*
单片机:STC15W4K61S4/IAP15W4K61S4 内部晶振:22.1184
*/
#include "stc15.h"
//舵机 IO 口定义 用P0的8个IO测试
sbit dj0 = P0^0;
sbit dj1 = P0^1;
sbit dj2 = P0^2;
sbit dj3 = P0^3;
sbit dj4 = P0^4;
sbit dj5 = P0^5;
sbit dj6 = P0^6;
sbit dj7 = P0^7;
void delay_ms(unsigned int t); //简单的延时
void dj_io_init(void); //舵机 IO 初始化
void dj_io_set(u8 index, u8 level); //舵机 IO 电平设置
void timer1_init(void); //舵机 定时器初始化
void timer1_reset(int t_us); //舵机 定时器初值重装
//舵机脉冲数组
int duoji_pulse[8] = {1500,1500,1500,1500,1500,1500,1500,1500} , i;
void main(void) {
//IO初始化
dj_io_init();
//舵机定时器初始化
timer1_init();
while (1) {
for(i=0;i<8;i++) {
duoji_pulse[i] = 1000;//循环把8个舵机位置设定到1000
}
delay_ms(1000);
for(i=0;i<8;i++) {
duoji_pulse[i] = 2000;//循环把8个舵机位置设定到2000
}
delay_ms(1000);
}
}
void dj_io_init(void) {
//设置标准IO
P0M1=0x00;
P0M0=0x00;
}
void dj_io_set(u8 index, u8 level) {
switch(index) {
case 0:dj0 = level;break;
case 1:dj1 = level;break;
case 2:dj2 = level;break;
case 3:dj3 = level;break;
case 4:dj4 = level;break;
case 5:dj5 = level;break;
case 6:dj6 = level;break;
case 7:dj7 = level;break;
default:break;
}
}
void delay_ms(unsigned int t) {
int t1;
while(t--) {
t1 = 3000;
while(t1--);
}
}
void timer1_init(void) {
AUXR |= 0x40; //定时器时钟1T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x00; //设置定时初值
TH1 = 0x28; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //定时器0开始计时
EA = 1; //总开关
}
void timer1_reset(int t_us) {
//本来应该x22.1184 但由于单片机用的内部晶振,有一定误差,调整到下面这个值 频率差不多50HZ
TL1 = (int)(65536-20.4184*t_us);
TH1 = (int)(65536-20.4184*t_us) >> 8;
}
void T1_IRQ(void) interrupt 3 {
static volatile u8 flag = 0;
static volatile u8 duoji_index1 = 0;
int temp;
if(duoji_index1 == 8) {
duoji_index1 = 0;
}
if(!flag) {
timer1_reset((unsigned int)(duoji_pulse[duoji_index1]));
dj_io_set(duoji_index1, 1);
} else {
temp = 2500 - (unsigned int)(duoji_pulse[duoji_index1]);
if(temp < 20)temp = 20;
timer1_reset(temp);
dj_io_set(duoji_index1, 0);
duoji_index1 ++;
}
flag = !flag;
}
/*----------------------------------------------------------------------------
相关库函数:
1.servo类成员函数
attach() 设定舵机的接口,只有9或10接口可利用。
write() 用于设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
writeMicroseconds() 用于设定舵机旋转角度的语句,直接用微秒作为参数。
attached() 判断舵机参数是否已发送到舵机所在接口。
detach() 使舵机与其接口分离,该接口(9或10)可继续被用作PWM接口。
舵机停止指令:$DST!
相关指令: 单舵机控制指令:#indexPpwmTtime!(index-舵机序号0,1,2…;pwm-舵机PWM值500-2500之间;time-舵机执行时间0-65535MS)
多舵机控制指令:{#index1Ppwm1Ttime1!#index2Ppwm2Ttime2!…}动作组指令,多个单舵机指令合并在一起,然后加个大括号
更新日志:
2017-08-09 V2.0版本
----------------------------------------------------------------------------*/
//#000P1000T100!
#include //声明调用Servo.h库
#include //声明PS2手柄库
String inString = ""; //声明一个字符串
char cmd1[10]=""; //声明一个字符数组,存储输入的指令
char cmd2[]="$DST!"; //声明一个字符数组,存储固定的指令
#define LED_PIN 13 //宏定义工作指示灯引脚
#define SERVO_NUM 6 //宏定义舵机个数
#define SERVO_TIME_PERIOD 20 //每隔20ms处理一次(累加)舵机的PWM增量
#define PS2_TIME_PERIOD 50
#define DELAY_MS 1500
/******************************************************************
宏定义PS2手柄引脚,用有有意义的字符代表相应是引脚,便于使用
******************************************************************/
#define PS2_CLK 4
#define PS2_CMD 9
#define PS2_ATT 2
#define PS2_DAT 8
//unsigned int time_max = 0;
byte servo_pin[SERVO_NUM] = {10, A2, A3, A0, A1 ,7}; //宏定义舵机控制引脚
Servo myservo[SERVO_NUM]; //创建一个舵机类
PS2X ps2x;
typedef struct { //舵机结构体变量声明
unsigned int aim = 1500; //舵机目标值
float cur = 1500.0; //舵机当前值
unsigned int time1 = 1000; //舵机执行时间
float inc= 0.0; //舵机值增量,以20ms为周期
}duoji_struct;
duoji_struct servo_do[SERVO_NUM]; //用结构体变量声明一个舵机变量组
//时间处理函数,第一个参数是上一次处理时间点,第二个参数是处理时间间隔,到达时间间隔返回1,否则返回0
bool handleTimePeriod( unsigned long *ptr_time, unsigned int time_period) {
if((millis() - *ptr_time) < time_period) {
return 1;
} else{
*ptr_time = millis();
return 0;
}
}
//接收串口发来的字符串
void uartReceive(){
while (Serial.available()>0) { //如果串口有数据
char inChar = Serial.read(); //读取串口字符
//inString += inChar;
inString.concat(inChar); //连接接收到的字符组
delayMicroseconds(100); //为了防止数据丢失,在此设置短暂延时100us
Serial.flush(); //清空串口接收缓存
}
}
//解析串口接收指令
void parseInStringCmd(){
static unsigned int index, time1, pwm1, i;//声明三个变量分别用来存储解析后的舵机序号,舵机执行时间,舵机PWM
unsigned int len; //存储字符串长度
if(inString.length() > 0) { //判断串口有数据
// time_max = 0;
if((inString[0] == '#') || (inString[0] == '{')) { //解析以“#”或者以“{”开头的指令
char len = inString.length(); //获取串口接收数据的长度
index=0; pwm1=0; time1=0; //3个参数初始化
for(i = 0; i < len; i++) { //
if(inString[i] == '#') { //判断是否为起始符“#”
i++; //下一个字符
while((inString[i] != 'P') && (i<len)) { //判断是否为#之后P之前的数字字符
index = index*10 + (inString[i] - '0'); //记录P之前的数字
i++;
}
i--; //因为上面i多自增一次,所以要减去1个
} else if(inString[i] == 'P') { //检测是否为“P”
i++;
while((inString[i] != 'T') && (i<len)) { //检测P之后T之前的数字字符并保存
pwm1 = pwm1*10 + (inString[i] - '0');
i++;
}
i--;
} else if(inString[i] == 'T') { //判断是否为“T”
i++;
while((inString[i] != '!') && (i<len)) {//检测T之后!之前的数字字符并保存
time1 = time1*10 + (inString[i] - '0'); //将T后面的数字保存
i++;
}
if((index >= SERVO_NUM) || (pwm1 > 2500) ||(pwm1<500)) { //如果舵机号和PWM数值超出约定值则跳出不处理
break;
}
//检测完后赋值
servo_do[index].aim = pwm1; //舵机PWM赋值
servo_do[index].time1 = time1; //舵机执行时间赋值
float pwm_err = servo_do[index].aim - servo_do[index].cur;
servo_do[index].inc = (pwm_err*1.00)/(servo_do[index].time1/SERVO_TIME_PERIOD); //根据时间计算舵机PWM增量
// time_max = max(time_max, time1);
#if 0 //调试的时候读取数据用
Serial.print("index = ");
Serial.println(index);
Serial.print("pwm = ");
Serial.println( servo_do[index].aim);
Serial.print("time = ");
Serial.println(servo_do[index].time1);
Serial.print("time_max = ");
Serial.println(time_max);
#endif
index = pwm1 = time1 = 0;
}
}
} else if(strcmp(strcpy(cmd2,inString.c_str()),cmd1)) { //解析以"$"开头的指令
for(i = 0; i < SERVO_NUM; i++) {
servo_do[i].aim = (int)servo_do[i].cur;
}
}
inString = "";
}
}
//舵机PWM增量处理函数,每隔SERVO_TIME_PERIOD毫秒处理一次,这样就实现了舵机的连续控制
void handleServo() {
static unsigned long systick_ms_bak = 0;
if(handleTimePeriod(&systick_ms_bak, SERVO_TIME_PERIOD))return;
for(byte i = 0; i < SERVO_NUM; i++) {
if(abs( servo_do[i].aim - servo_do[i].cur) <= abs (servo_do[i].inc) ) {
myservo[i].writeMicroseconds(servo_do[i].aim);
servo_do[i].cur = servo_do[i].aim;
} else {
servo_do[i].cur += servo_do[i].inc;
myservo[i].writeMicroseconds((int)servo_do[i].cur);
}
}
}
//处理工作指示灯,每个1S闪烁一次
void handleNled() {
static bool val = 0;
static unsigned long systick_ms_bak = 0;
if(handleTimePeriod(&systick_ms_bak, 1000))return;
digitalWrite(LED_PIN, val);
val = !val;
}
//手柄解析函数
void parsePS2() {
static unsigned long systick_ms_bak = 0;
if(handleTimePeriod(&systick_ms_bak, PS2_TIME_PERIOD))return;
ps2x.read_gamepad(); //读PS2数据
if(ps2x.ButtonPressed(PSB_L1)) { //当L1按钮按下时,发送#5P2400T1000!--此时T = 1000,就表示舵机从此刻某一位置执行到"2400"位置所用的时间为1000ms
inString = (String("#4P2400") + ("T") + (DELAY_MS) + ("!"));//以下都是类似的,就不一一注释了
} else if(ps2x.ButtonPressed(PSB_L2)) {
inString = (String("#4P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_R1)) {
inString = (String("#5P2400") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_R2)) {
inString = (String("#5P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_PAD_UP)) {
inString = (String("#1P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_PAD_DOWN)) {
inString = (String("#1P2400") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_PAD_LEFT)) {
inString = (String("#0P2400") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_PAD_RIGHT)) {
inString = (String("#0P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_PINK)) {
inString = (String("#3P2400") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_GREEN)) {
inString = (String("#2P2400") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_RED)) {
inString = (String("#3P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_BLUE)) {
inString = (String("#2P600") + ("T") + (DELAY_MS) + ("!"));
} else if(ps2x.ButtonPressed(PSB_SELECT)) {
inString = "{#0P1500T1000!#1P1500T1000!#2P1500T1000!#3P1500T1000!#4P1500T1000!#5P1500T1000!}";
} else if(ps2x.ButtonReleased(PSB_SELECT)){
}
//按键弹起时,发送“$DST!”指令,舵机停在此时的位置
if(ps2x.ButtonReleased(PSB_L1)||ps2x.ButtonReleased(PSB_R1)||
ps2x.ButtonReleased(PSB_L2)||ps2x.ButtonReleased(PSB_R2)||
ps2x.ButtonReleased(PSB_PAD_UP)||ps2x.ButtonReleased(PSB_PAD_DOWN)||
ps2x.ButtonReleased(PSB_PAD_LEFT)||ps2x.ButtonReleased(PSB_PAD_RIGHT)||
ps2x.ButtonReleased(PSB_PINK)||ps2x.ButtonReleased(PSB_RED)||
ps2x.ButtonReleased(PSB_BLUE)||ps2x.ButtonReleased(PSB_GREEN)) {
inString = "$DST!";
}
}
//初始化函数
void setup(){
ps2x.config_gamepad(PS2_CLK, PS2_CMD, PS2_ATT, PS2_DAT);//配置PS2手柄
pinMode(LED_PIN,OUTPUT); //设置引脚为输出模式
for(byte i = 0; i < SERVO_NUM; i++){
myservo[i].attach(servo_pin[i]); // 将10引脚与声明的舵机对象连接起来
}
Serial.begin(115200); //初始化波特率为115200
}
//主循环函数
void loop(){
handleNled();
uartReceive();
parsePS2();
parseInStringCmd();
handleServo();
}
以上就是我对驱动舵机的分享,希望我分享对你有所帮助。敬请关注,持续更新!一路奔跑,逐梦不止!