完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本文尝试用Arduino开发版控制42步进电机,搭配通用的步进电机驱动器,实现对步进电机的转速控制和方向控制。
原材料:
2、42步进电机 可以看到这个是四线步进电机,内部两两短接,可以通过万用表测出,相同相的线随意接入驱动器的A+,A-和B+,B-即可。 3、驱动器 驱动器侧面有一排按钮,往上拨为OFF,往下为ON,其中看驱动器界面标识可见, SW1-SW3:为步进电机驱动器电流控制按钮,电流越大步进电机就越有劲,不容易丢步。 SW4:是控制限时电流按钮,即电机不转时是否给电机电流按钮,据说开启OFF可以更好的保护电机,但是为了更好地控制步进电机建议打开。 SW5-SW8:是控制电机精度的,Pulsw/rev表示的是多少个脉冲为一圈,数字越小即转的越快,数字越高越精确转的越慢。 PUL+,PUL-:步数控制,输入脉冲信号,一个脉冲(一高一低)走一步。 DIR+,DIR-:方向控制,接入高电平即正转,接入低电平则反转。 ENA+,ENA-:使能控制,一般情况悬空(都不接)即可,接入高电平步数控制失效,可以手动转动,当接入低电平恢复原有的步数控制,此时无法手动转动。 4、程序代码 保证线路连接正确的情况下,以下程序就能简单控制步进电机的运动,如果要精确控制等更精确的控制,或者用库控制的话需要更深入的研究。 代码如下: 代码如下: #define STEPPIN 9 #define DIRPIN 8 //方向位为8,脉冲位为9 void setup() { pinMode(STEPPIN, OUTPUT); pinMode(DIRPIN, OUTPUT); Serial.begin(9600); } void loop() { // Enables the motor to move in a particular direction Serial.println("Forward Begins"); digitalWrite(DIRPIN, HIGH); // 正向转1圈(200脉冲) for (int x = 0; x < 200; x ++) { digitalWrite(STEPPIN, HIGH); delayMicroseconds(500); digitalWrite(STEPPIN, LOW); delayMicroseconds(500); } Serial.println("Forward Ends"); delay(1000); // Delay for one second // Changes the rotation direction or rotates in opposite direction Serial.println("Backward Begins"); digitalWrite(DIRPIN, LOW); // 反向转3圈(600脉冲) for (int x = 0; x < 600; x ++) { digitalWrite(STEPPIN, HIGH); delayMicroseconds(500); digitalWrite(STEPPIN, LOW); delayMicroseconds(500); } Serial.println("Backward Ends"); delay(2000); //Delay for two seconds } PID调速代码: // 导入PWM库,此库可自定义PWM的频率和占空比(除了timer0相关的端口,因为timer0是负责dealay等函数的,因此不能去改动timer0) #include // 导入 PID 库的头文件 #include // 导入 定时中断 库的头文件,这个库用的是timer2,因此这个程序的其他地方不能对timer2进行修改,否则会影响这个库的功能 #include // ↓↓↓↓↓↓↓↓↓ 变量定义与初始化:PID 控制部分 ↓↓↓↓↓↓↓↓↓// // 定义三个变量,分别代表 PID 控制器的期望输入值、实际输入值、控制量 double Setpoint = 6; // 希望电机的转速为 6rad/s double Input; double Output; // 初始化 PID 的三个参数 double kp = 2, ki =15, kd = 1; //double kp = 11.46, ki =98.22, kd = 0.33; // 创建一个 PID 控制器的实例 PID myPID(&Input, &Output, &Setpoint, kp, ki, kd, DIRECT); // ↑↑↑↑↑↑↑↑↑ 变量定义与初始化:PID 控制部分 ↑↑↑↑↑↑↑↑↑// // ↓↓↓↓↓↓↓↓↓ 变量定义与初始化:PID 自整定部分 ↓↓↓↓↓↓↓↓↓// double OutputHigh,OutputLow; double outputStep = 30 ; //必须是正值 double OutputUsedinPIDAutotune = 0; double inputHistory[20]; int caculateFlag = 1; int inputHighAll = 0; int inputHighNum = 0; int inputLowAll = 0; int inputLowNum = 0; int atemp, btemp; int if_inputHistory_or_not = 0; // 决定是否开始PID自整定过程中的对Input的历史记录 // ↑↑↑↑↑↑↑↑↑ 变量定义与初始化:PID 自整定部分 ↑↑↑↑↑↑↑↑↑// // ↓↓↓↓↓↓↓↓↓ 变量定义与初始化:电机测速部分 ↓↓↓↓↓↓↓↓↓// int count = 0; // 记录在指定时间段内,编码器码盘脉冲值 double omega = 0; //电机转速 rad/s unsigned long old_time = 0; // 时间标记 double measure_sample_time = 100; // 测速周期,单位:毫秒 // ↑↑↑↑↑↑↑↑↑ 变量定义与初始化:电机测速部分 ↑↑↑↑↑↑↑↑↑// // ↓↓↓↓↓↓↓↓↓ 变量定义与初始化:MATLAB绘图部分 ↓↓↓↓↓↓↓↓↓// // 当这个变量为0的时候,串口会显示最详细的数据,此时不宜用MATLAB绘图 // 当这个变量不为0的时候,串口会只显示一些关键的数据,共1列,每4个数据为1组,第一个是当前的实际转速(rad/s),第二个是要求的转速(rad/s),第三个是当前实际的占空比,第四个是当前的时间(毫秒),此时宜用MATLAB绘图 int Use_MATLAB_to_Draw_or_not = 0; // ↑↑↑↑↑↑↑↑↑ 变量定义与初始化:MATLAB绘图部分 ↑↑↑↑↑↑↑↑↑// // ↓↓↓↓↓↓↓↓↓ 变量定义与初始化:XXX部分 ↓↓↓↓↓↓↓↓↓// // ↑↑↑↑↑↑↑↑↑ 变量定义与初始化:XXX部分 ↑↑↑↑↑↑↑↑↑// // 自定义函数:外部中断起作用时的中断处理函数,(当端口收到测速信号线(FG线)的下降沿信号时,会有一个外部中断,而中断处理函数就在此定义) ↓↓↓↓↓↓↓↓↓ void Code() { //每中断一次,记录一次 count += 1; // 编码器码盘计数加一 } // 自定义函数:定时中断起作用时的中断处理函数,用于计算电机转速 ↓↓↓↓↓↓↓↓↓ void omega_measure() { detachInterrupt(0); // 关闭外部中断0 // Serial.print("time: ");Serial.print(millis());Serial.println("毫秒"); // Serial.print("old_time: ");Serial.print(old_time);Serial.println("毫秒"); //把measure_sample_time毫秒内编码器码盘计得的脉冲数,换算为当前转速值 //转速单位 rad/s。这个编码器码盘为2044个空洞。 (2044这个数字是标定过的 我自己标定的) //电机转速,其中 count/2044 计算的是在measure_sample_time毫秒之内,转了多少圈,“2*3.141592654”是为了转换为rad制 omega =(double)count/2044*2*3.141592654*1000/measure_sample_time; // 记录被调量Input(即 omega)的历史值,20个 if (if_inputHistory_or_not == 1) { for (int i = 1; i < 20; i++) { inputHistory[20 - i] = inputHistory[20 - i - 1]; } inputHistory[0] = omega; } //在串口监视器中输出一些结果 if (Use_MATLAB_to_Draw_or_not == 0) { // 输出详细数据 Serial.println(" "); Serial.print("期望转速: ");Serial.print(Setpoint);Serial.println("rad/s"); Serial.print("实际转速: ");Serial.print(omega);Serial.println("rad/s"); Serial.print("PID自整定震荡前或者后的占空比值: ");Serial.println(Output); Serial.print("PID自整定震荡中的占空比值: ");Serial.println(OutputUsedinPIDAutotune); Serial.print("当前系统时间:");Serial.print(millis());Serial.println("毫秒"); Serial.println(" "); } else { // 输出简单数据 Serial.println(omega); Serial.println(Setpoint); Serial.println(Output); Serial.println(millis()); } //恢复到编码器测速的初始状态 count = 0; //把脉冲计数值清零,以便计算下一个measure_sample_time毫秒的脉冲计数 old_time= millis(); // 记录每次测速时的时间节点 attachInterrupt(0, Code,FALLING); // 重新开放外部中断0 } void setup() { // put your setup code here, to run once: // 2口接FG信号测速线,另外,2口是外部中断口,中断接口是in.0(Ardunio mega2560 自带的特性) pinMode(2, INPUT); //电机的编码器脉冲中断函数, 当编码器码盘的脉冲信号下跳沿的时候,中断一次(FALLING),中断函数是Code,中断接口是in.0,对应pin2口 attachInterrupt(0, Code, FALLING); // 对定时中断进行设置,每 measure_sample_time毫秒 进入一次定时中断,并调用测速函数(即:每measure_sample_time毫秒测一次速) MsTimer2::set(measure_sample_time, omega_measure); // 开启定时中断 MsTimer2::start(); // ↓↓↓↓↓↓↓↓↓设置PWM的频率,与pin口 ↓↓↓↓↓↓↓↓↓ InitTimersSafe(); // 在 mega2560 上,12口属于timer1,不会与定时中断库“MsTimer2”冲突 bool success = SetPinFrequencySafe(12, 10000); if(success) { // PWM 信号是从 12 口输出的 pinMode(12, OUTPUT); } // ↑↑↑↑↑↑↑↑↑设置PWM的频率,与pin口 ↑↑↑↑↑↑↑↑↑ // 打开串口监视器 Serial.begin(9600); //串口波特率为9600 //打开 PID 控制器 myPID.SetMode(AUTOMATIC); // 设置控制量的范围,在此应用中,控制量是占空比,范围是0~255 myPID.SetOutputLimits(0,255); // 设置计算采样周期 myPID.SetSampleTime(1); // 初始化 inputHistory 数组 for (int i = 0; i < 20; i++) { inputHistory = 0; } } void loop() { // 主程序,将重复运行: // loop()每执行一次就将当前速度传给Input Input = omega; if (Use_MATLAB_to_Draw_or_not == 0) { // 做详细计算、PID自整定等 if (millis() < 25000) { // 当运行时间小于25秒,则进行普通的 PID 控制,并且 以PID控制器计算得到的占空比运转 (稳定为先) myPID.Compute(); pwmWrite(12, Output); } // 当运行时间大于25秒,且小于30秒,则 PID 进行自整定 (强行震荡) else if(millis() < 30000) { if_inputHistory_or_not = 1; // 打开“记录被调量Input(即 omega)的历史值” // Output包含的是稳定值 OutputLow = Output - outputStep; OutputHigh = Output + outputStep; if (Input < Setpoint) { OutputUsedinPIDAutotune = OutputHigh; if (int(OutputUsedinPIDAutotune) > 255) {OutputUsedinPIDAutotune = 255;} } else if (Input > Setpoint) { OutputUsedinPIDAutotune = OutputLow; if (int(OutputUsedinPIDAutotune) < 0) {OutputUsedinPIDAutotune = 0;} } pwmWrite(12, OutputUsedinPIDAutotune); } // 震荡结束后,继续 PID 自整定 (分析波形 (即数组inputHistory[]的波形) 计算自整定后的PID参数) else if(millis() < 35000) { // 先关闭电机 pwmWrite(12, 0); // 关闭“记录被调量Input(即 omega)的历史值” if_inputHistory_or_not = 0; while (caculateFlag == 1) { for (int i = 1; i < 19; i++) { //如果是波峰 if (inputHistory > inputHistory[i - 1] && inputHistory > inputHistory[i + 1]) { inputHighAll += inputHistory; //波峰值总数增加 inputHighNum++; //波峰个数计数增加 if (inputHighNum == 1) atemp = i; //当产生第一个波峰的时候,atemp记录下此时是第几个数据(每个数据相隔100ms) btemp = i; //当对20个历史数据分析完,btemp则可记录下最后一个波峰对应的是第几个数据 } //如果是波谷,类似上面 else if (inputHistory < inputHistory[i - 1] && inputHistory < inputHistory[i + 1]) { inputLowAll += inputHistory; inputLowNum++; } } double autoTuneA = (inputHighAll / inputHighNum) - (inputLowAll / inputLowNum); //峰峰值(即波峰值 - 波谷值)的平均数A double autoTunePu = (btemp - atemp) * measure_sample_time/1000 / (inputHighNum - 1); //两个波峰之间的时间间隔Pu(s) double Ku = 4 * outputStep / (autoTuneA * 3.14159); double Kp_self = 0.6 * Ku; double Ki_self = 1.2 * Ku / autoTunePu; double Kd_self = 0.075*Ku*autoTunePu; // 暂时关闭定时中断 MsTimer2::stop(); // 在串口监视器输出自整定的最终结果 Serial.println(" "); Serial.println("******************************"); Serial.println("PID 自整定过程完成!"); Serial.println("自整定的PID参数为: "); Serial.print("Kp_self: ");Serial.println(Kp_self); Serial.print("Ki_self: ");Serial.println(Ki_self); Serial.print("Kd_self: ");Serial.println(Kd_self); Serial.println(" "); Serial.print("震荡过程中的峰峰值 A: ");Serial.print(autoTuneA);Serial.println("rad/s"); Serial.print("两个波峰之间的时间间隔Pu: ");Serial.print(autoTunePu);Serial.println("秒"); Serial.println("******************************"); Serial.println(" "); // 上述消息显示5秒 delay(5000); // 把自整定的PID参数导入PID控制器中 myPID.SetTunings(Kp_self, Ki_self, Kd_self); // Serial.println("已经将自整定的PID参数导入PID控制器中"); Serial.println("即将以自整定的PID参数进行全新的控制,请等待10秒......"); // 在延时10秒 delay(10000); caculateFlag = 0; // 重新开启定时中断 MsTimer2::start(); } } // 从速度0开始,以自整定的PID参数进行全新的控制 else { if (caculateFlag = 0) { Output = 255; Input = 0; myPID.Compute(); pwmWrite(12, Output); omega = 0; caculateFlag = 2; old_time= millis(); } else { myPID.Compute(); pwmWrite(12, Output); } } } else { // 只在一组PID参数下运算 myPID.Compute(); pwmWrite(12, Output); } } //转角θ=-ANcos(wt),转速V=ANwsin(wt) float w=3; int N=100; //N是半个周期的脉冲数,正比于正弦函数的振幅 //如果乘积Nw太大,步进电机就会丢步 float dt[400]={0}; //脉冲的时间间隔 int k; const byte pinSPEED=5; const byte pinDIREC=6; void setup() { pinMode(pinSPEED,OUTPUT); // 5号引脚发送PULSE(控制速度) pinMode(pinDIREC,OUTPUT); // 6号引脚指定SIGN (控制方向) int dtMAX=30; float t=0; for(k=1;k<=N;k++) {dt[k]=(1.0F/sin(k*PI/(N+1))> dtMAX ? dtMAX : 1.0F/sin(k*PI/(N+1))); //如果两个脉冲的时间间隔超过预设的dtMAX,就认为它是dtMAX //dtMAX的值可以根据需要自行修改 t=t+dt[k];} //for循环结束后,t代表数组dt的前N项的和 for(k=1;k<=N;k++) {dt[k]=PI*dt[k]/(w*t);} } void loop() { digitalWrite(pinDIREC,HIGH); //一个方向运动 for(k=1;k<=N;k++) {digitalWrite(pinSPEED,HIGH); digitalWrite(pinSPEED,LOW); delayMicroseconds(1E6*dt[k]);} digitalWrite(pinDIREC,LOW); //反方向运动 for(k=1;k<=N;k++) {digitalWrite(pinSPEED,HIGH); digitalWrite(pinSPEED,LOW); delayMicroseconds(1E6*dt[k]);} } |
|
|
|
只有小组成员才能发言,加入小组>>
2386 浏览 0 评论
8910 浏览 4 评论
36486 浏览 19 评论
4981 浏览 0 评论
24307 浏览 34 评论
1375浏览 2评论
1627浏览 1评论
2017浏览 1评论
1441浏览 0评论
386浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 01:59 , Processed in 1.148679 second(s), Total 77, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号