我个人认为,步进电机的基本原理和介绍看看其他博主的介绍就好了。我比较希望讲一下我对步进电机的关于自己一种理解方式,可能与真正步进电机的原理差的有点大。下面还是给一下我推荐的一些博主对步进电机的介绍文章。
百度步进电机链接
步进电机驱动及原理—star-air
步进电机,把名字扩展一下就是按“步”前进的电机
,这里的“步”,我认为既可以解释为“脚步”,也可以解释为”步骤“。脚步就指像人一样,无论速度多快,每次只能跨一步,步进电机也是如此,无论你通电时间多长,只要脉冲不发生变化,步进电机也只走一步。而”步骤“,就当行走方式,人的行走方式行走方式是交替向前的,步进电机也一样,它的脉冲方式按照一定规律运行的。
对了,还有一个很重要的一点,这里的脉冲方式和PWM(脉宽调制)是不一样的,这里的脉冲我认为其实是对51单片机的IO口电平规律变换的频率。
我们这次使用的是28BYJ-48 5V DC
这个型号的步进电机(实物图如下方图),所以我们的机构介绍主要针对此步进电机,若未来有更多扩展再加入更多介绍。
28BYJ-48 5V DC
步进电机是五线四相直流驱动步进电机,运转过程中电流在0.3A~0.4A(个人测量,数据未必准确),它的一些驱动参数如下:
数据名 | 参数 |
---|---|
直径 | 28mm |
电压 | 5V |
步进角度 | 5.625 × 1 / 64 |
减速比 | 1 / 64 |
单个重 | 0.04kg |
在此处,我们需要注意的主要为电压与步进角度,它是由5V
电压驱动,步进角度为5.625×1/64
,这里给的5.625×1/64
代表它每次脉冲转动的角度是5.625÷64=0.087890625°
,而不是单纯的5.625度,这一点比较容易理解错,一个不注意写出来的程序就是错误的。下面,是28BYJ-48 5V DC
的接线示意图和设计图。
这是我购买的步进电机的结构示意图,可能和各位的有所区别,请各位以实物为准,如果各位要设计PCB板且要把步进电机装进去,就需要对步进电机的主要结构有了解,不然必要性就不是很大了。
关于接线示意图,也就是步进电机的内部接线图,是我们针对仿真时和具体电路设计需要的,所以还是比较重要的。这里的接线介绍我推荐和仿真一起看(主要是仿真的运行),很容易就理解了,具体的运行会在后面介绍。下面大概说一下接线。
名称 | 接线 |
---|---|
蓝1 | 控制线1 |
粉2 | 控制线2 |
黄3 | 控制线3 |
橙4 | 控制线4 |
红5 | 5V VCC或GND(本次使用时VCC) |
ULN2003驱动文章推荐:【常用芯片】ULN2003工作原理及中文资料(实例:STM32驱动28BYJ48步进电机)
前面说到,步进电机运转过程中电路在0.3A~0.4A
之间,而我们的51单片机拉电流1mA,灌电流10mA,所以对我们51单片而言,直接驱动步进电机是不现实的,所以我们需要加一个能承受大电流的中介。根据我们学习时用的开发板关于驱动步进电机所使用模块的是ULN2003
芯片,这个芯片能为我们承受大电流,为了便于测试,我也使用了此模块。下面是这个模块的逻辑图和实物图。
ULN2003其实相当于7个开关,每个开关的控制端(1~7B)由单片机控制,控制端为高电平(>2.5V)开关接地,低电平时接高电平,就是接了一个取反的电路。介于这种情况,为了方便我们控制步进电机的时候,51单片机IO口高电平时即为通电,所以我们步进电机的红色5号线接VCC(比如在端口1B为高电平,输出端口1C就为低电平,而红色5号线为VCC,1C与步进电机控制线相连,相连后形成电势差,电流导通)。下面时ULN2003的接线。
名称 | 接线 |
---|---|
1B~7B | 控制端口1~7 |
1C~7C | 输出端口1~7(输入输出口相对应) |
E | 接地 |
COM | 接5V高电平 |
本次步进电机设计要求为能显示和控制步进电机具体转动角度
,能显示和控制电机正转和反转
。
针对此设计要求,我们需要显示模块
、控制输入模块
和步进电机模块
。
LCD1602文章推荐: 快速掌握——LCD1602液晶显示(多组实验,附带源程序)
鉴于要显示正转和反转,如果使用数码管作为显示器,其显示效果是不行的,所以显示模块
我使用的是LCD1602
。LCD1602
中16指16每行支持显示16个字符,02指有两行。在仿真中为LM016L
。接下来说一下介绍LCD1602的具体引脚功能,具体仿真图与实物图如下:
VSS
:这里的VSS可以直接理解为给LCD1602的GND。没有什么可以介绍的。VDD
:就是我们说的VCC高电平,这里的高电平接5V即可。V0
:每个字符显示位的对比度调整,电压越高对比度越低。我们一般会在此处接一个可调电阻,用于调节对比度。RS
:指令、数据选择,低电平时系统判定D0~D7输入为指令,高电平时判断输入为数据。RW
:R/W为读/写信号线,高电平时进行读操作,低电平时进行写操作。
- 当RS和R/W共同为低电平时可以写入指令或显示地址;
- 当RS为低电平,R/W为高电平时,可以读忙信号;
- 当 RS为高电平,R/W为低电平时,可以写入数据。
E
:使能端,当端口E
出现下降沿
时,LCD1602执行指令。D0~D7
:D0~D7为8位双向数据线。A
:背光源正极。K
:背光源负极。
LCD1602的实物与仿真相比,仿真缺少A,K两个端口。除此之外,其他是完全一样的,在实际接线中,我们只需要记得给A,K接上5V和GND就行了。通过对上面LCD1602的了解,我们现在需要对LCD1602正式接线了。具体接线方式我打算如此接线:
端口 | 接线 |
---|---|
VSS | 接GND |
VDD | 接5V VCC |
V0 | 接10kΩ滑动电阻(最后因为10k的没找着,将就接了一个1k的) |
RS | 接控制线P2.0 |
RE | 接控制线P2.1 |
E | 接控制线P2.2 |
D0~D7 | 接数据传输线P0 |
A | 接5V VCC |
K | 接GND |
仿真(不知道为啥,LCD1602仿真显示成这样)如下图所示:
控制输入模块
采用16(4×4)键键盘。这种键盘其实是用的很多的,没啥可以介绍的,我比较推荐的是相关输入模块使用自己购买的输入模块,相关代码可以直接替换。我当时做的时候犯了一点傻,我先设计的仿真,再买的模块,相关模块差点没找到,不过最后又设计了PCB这些就没有影响了。下面是模块的具体样式和仿真图:
唯一比较注意的是我们虽然实际使用的是微动开关,但在做模拟的时候还是采用的普通的按钮(仿真名称button
)具体的功能就不多说了,直接上实物接线表。
端口 | 接线 |
---|---|
C4 | P1.0 |
C3 | P1.1 |
C2 | P1.2 |
C1 | P1.3 |
R1 | P1.4 |
R2 | P1.5 |
R3 | P1.6 |
R4 | P1.7 |
步进电机模块
就型号为28BYJ-48 5V DC
的步进电机和ULN2003
组合。这里的使用也没有什么可以多说的。直接上仿真图和接线(实物图在上面):
ULN2003接线
此处需要注意ULN2003芯片的每个端口的具体含义,可以参考前面的ULN2003逻辑框图,将实物的带缺角口与逻辑图带缺角口对起,芯片有字面对准自己,此时实物端口与对照逻辑框图一样。
其次购买的步进电机不同,可能颜色标注不同,以购买实物为准。
还有一点,因为步进电机在仿真中响应速度太慢,仿真中其实无法完全模拟步进电机。在仿真中跑出来的程序有问题。不过我已经在软件中为各位弥补了这一问题,具体的修改方式看后文的Includes.h
中关于对宏_PROTEUS_
的设定。
ULN2003端口 | 接线 |
---|---|
COM | 5V VCC |
E | GND |
1B | 51单片机P3.0 |
2B | 51单片机P3.1 |
3B | 51单片机P3.2 |
4B | 51单片机P3.3 |
1C | 步进电机C1(蓝1) |
2C | 步进电机C2(粉2) |
3C | 步进电机C3(黄3) |
4C | 步进电机C4(橙4) |
这里没有加51单片机的最小系统板的电路,使用的晶振频率为12MHz。
PCB设计网站:嘉立创EDA(可以白嫖PCB电路板)
这个PCB设计是我一时兴起做的,用的是嘉立创EDA(可以白嫖PCB板)。又因为我们做课程设计是由我们老师提供最小系统板,我们只需要在最小系统板上加外围电路。所以设计中我也是直接针对外围电路做的设计。下面是设计图、3D图和成品图:
PS:忘买XH插座了,所以就用排针代替了。
下面,根据每个模块做软件。
因为显示模块已经介绍了,为了方便各位更改端口,直接修改LCD1602.h
中的RS、RW、E、LCDMsg的参数即可。还有需要注意的是,在LCD1602.c
中有一个SendX的宏定义,里面的bitFlip在线没接错的情况下需要删除掉。
// LCD1602.h
#ifndef _LCD1602_H_
#define _LCD1602_H_
// 此文件所需头文件
#include
#include
// 关键字替换
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
/* 1602显示器 */
// P0做数据传输 P0.0~7 <-----> D0~D7
// P2做控制端口 P2.0~2 <-----> RS RW E
sbit RS = P2^0; // 此处修改RS端口
sbit RW = P2^1; // 此处修改RW端口
sbit E = P2^2; // 此处修改E端口
#define LCDMsg P0 // 定义数据输出口
#define WriteCo {RS = 0; RW = 0;} // 写入指令 / 显示地址
#define WriteDa {RS = 1; RW = 0;} // 写入数据
// 发送数据,注意如果线没接错就把下面的bitFlip(Msg)直接替换为Msg.这是我画PCB的时候出错设计的软件修补。
#define SendX(X, Msg) {Write##X; LCDMsg = bitFlip(Msg); E = 1; Delay3ms(); E = 0;}
// 1602命令
#define CL 0x01 // clear 清屏
#define RC 0x02// Rest Cursor 光标复位
#define SC(ID, Word) (0x04 | ID << 1 | Word) // Set Cursor光标设置,ID:光标移动0左1右,Word置1使文字移动
#define SW(D, C, B) (0x08 | D << 2 | C << 1 | B) // 显示设置(置1有效)D:屏幕显示 C:光标显示 B:光标闪烁
#define MC(SC, RL) (0x10 | SC << 3 | RL << 2) // SC:1动文字0动光标 RL:光标移动0左1右
#define SF(DL, N, F) (0x20 | DL << 4 | N << 3 | F << 2) // Set Function 功能设置 DL:1为4位总线,0为8位总线 N:0为单行显示,1为双行显示,F:0显示5X7的点阵字符,1显示5X10的显示字符
#define ST(T) (0x40 | (T & 0x3F)) // 设置字符表地址
#define SS(S) (0x80 | (S & 0x7F)) // 设置存储地址
// LCD初始化
void LCD_Init();
// 显示字符串
void LCD_ShowString(bit, u8, u8*);
// 显示数字
void LCD_ShowNum(bit, u8, u8*, u16);
// 显示浮点数
void LCD_ShowFloat(bit, u8, u8*, float);
#endif
// LCD1602.c
#ifdef _INCLUDES_
#include "Includes.h"
#ifndef _LCD1602_H_
#error "未加装LCD1602.h文件。"
#endif
#else
#include "LCD1602.h"
#endif
// LCD初始化
void LCD_Init(){
SendX(Co, SF(1, 1, 0)); // 4总线,双行显示,5X7
SendX(Co, SW(1, 0, 0)); // 4总线,双行显示,5X7
SendX(Co, SC(1, 0)); // 数据读写操作后,光标自动加一,画面不动
SendX(Co, CL); // 清屏
}
// 显示字符串
// 传参:行(0为第一行,1为第2行), 列,字符串。
void LCD_ShowString(bit Line, u8 Col, u8* Str){
if (Line){
SendX(Co, SS(Col | 0x40));
} else {
SendX(Co, SS(Col));
}
while(*Str != '\0'){
SendX(Da, *(Str++));
}
}
// 显示整数
// 传参:行(0为第一行,1为第2行), 显示格式(和C语言printf中的相同),数字。
void LCD_ShowNum(bit Line, u8 Col, u8* Sta, u16 Num){
u8 Mes[10];
sprintf(Mes, Sta, Num);
LCD_ShowString(Line, Col, Mes);
}
// 显示浮点数
// 传参:行(0为第一行,1为第2行), 显示格式(和C语言printf中的相同),小数。
void LCD_ShowFloat(bit Line, u8 Col, u8* Sta, float Num){
u8 Mes[10];
sprintf(Mes, Sta, Num);
LCD_ShowString(Line, Col, Mes);
}
输入模块也比较简单,为了让各位能更好的修改数据,直接修改Key.h
中的KEY就能直接按键修改连接位置。
// Key.h
#ifndef _KEY_H_
#define _KEY_H_
// 此文件所需头文件
#include
// 关键字替换
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
/* 键盘设计 */
/*
P1 <---> 16键键盘
16键键盘步进
1 2 3 删除
4 5 6 确定
7 8 9 取消
正 0 反 设置
*/
// 修改这里更改按键连接位置
#define KEY P1
// 按键功能定义
#define NUM_1 0xE7
#define NUM_2 0xEB
#define NUM_3 0xED
#define DEL 0xEE
#define NUM_4 0xD7
#define NUM_5 0xDB
#define NUM_6 0xDD
#define ENTER 0xDE
#define NUM_7 0xB7
#define NUM_8 0xBB
#define NUM_9 0xBD
#define CANCEL 0xBE
#define CORRECT 0x77
#define NUM_0 0x7B
#define ANTI 0x7D
#define SET 0x7E
#define UP NUM_2
#define RIGHT NUM_6
#define LEFT NUM_4
#define DOWN NUM_8
#define YES NUM_5
// 按键读取, 返回参数:键盘按下位置,未检测到为 0
u8 GetKey(bit);
#endif
// Key.c
#ifdef _INCLUDES_
#include "Includes.h"
#ifndef _KEY_H_
#error "未加装Key.h文件。"
#endif
#else
#include "Key.h"
#endif
// 按键读取, 返回参数:键盘按下位置,未检测到为 0
// 传参Keep_Key为是否等待按键抬起1是,0否
u8 GetKey(bit Keep_Key){
u8 i, j;
KEY = 0xF0;
Delay5ms();
i = KEY;
if(i == 0xF0){
return 0;
} else {
Delay5ms();
if(KEY == i){
KEY = 0x0F;
Delay1ms();
j = KEY & 0x0F;
if(j == 0x0F){
return 0;
} else {
Delay5ms();
if (j == KEY & 0x0F){
if(Keep_Key){
while(KEY & 0x0F != 0x0F) ;
}
return i | j;
} else {
return 0;
}
}
}
else{
return 0;
}
}
}
步进电机模块的数据修改需要根据基础比例来修改,不然代码会出问题。而且因为51单片机无论float
还是double
类型,位数都只有32位,所以浮点数的精度不会很高,建议基础比例就在这一比例。可以增加,不建议再减少了。同时,当我们修改此基础比例后,我们需要修改后面的Includes.h
中的Motor
结构体的一部分元素的长度,具体长度后面会做详细介绍。当然,此文件也是支持修改接线的。修改MotorLine.h
中的MotorLine即可,若要修改IO口的话需要更改Motor.c
中的Motor_Data中的数据。同时还有一个关于_PROTEUS_
的宏,此宏用于控制我们的Motor_Revolve函数是使用在仿真中还是实物中,因为一部分原因,这两者不互通,这一点需要注意。
// Motor.h
#ifndef _MOTOR_H_
#define _MOTOR_H_
// 此文件所需头文件
#include
// 关键字替换
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
//电机接线 P3.0 -> P3.4
#define MotorLine P3
// 基础数据
// 基础比例:8 数据设置要求:2的整数倍
#define DData 512 // 总转动量 数据设置要求 64 * 基础比例
#define DNum 8 // 旋转最低值 数据设置要求: 64 / 基础比例
#define NFundation 0.703125 // 基础转角 数据设置要求: 5.625 / 基础比例
#define MotorNum 8 // 设定转动数据
// 电机旋转
void Motor_Revolve(u8, u16, bit, bit);
#endif
// Motor.c
#ifdef _INCLUDES_
#include "Includes.h"
#ifndef _MOTOR_H_
#error "未加装Motor.h文件。"
#endif
#else
#include "Motor.h"
#define Delay1ms() Delayms(12, 169)
#define Delay5ms() Delayms(59, 90)
void Delayms(u8 i, u8 j){
do{
while (--j);
} while (--i);
}
#endif
u8 code Motor_Data[MotorNum] = {0x09, 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08};
// 电机旋转
// 传参:起始角,旋转角,旋转方向,默认方向
void Motor_Revolve(u8 Start, u16 Num, bit Orientation, bit NOrien){
#ifndef _PROTEUS_
Num *= DNum;
#endif
if(NOrien && Start) Start = MotorNum - Start;
if(Orientation){
while(Num-- != 0){
Start = (Start == 0) ? MotorNum - 1 : Start - 1;
MotorLine = Motor_Data[Start];
#ifndef _PROTEUS_
Delay1ms();
#else
Delay5ms();
#endif
}
} else {
while(Num-- != 0){
Start = (Start >= MotorNum - 1) ? 0 : Start + 1;
MotorLine = Motor_Data[Start];
#ifndef _PROTEUS_
Delay1ms();
#else
Delay5ms();
#endif
}
}
}
通过前面的函数,我们不难看出,我们使用了一个Includes.h
的自定义头文件,这里的Includes.h
除了要加入之外,还要在魔术棒当中进行设置才能完全加入,加入的方式如下:
Includes.h
头文件的内容如下,其中可以设置的内容有默认设置修改(DOrientation, DTurn_Zero, DAngle和DRotation),其中需要我们注意的是,里面有一个仿真设置宏_PROTEUS_
,此宏用于管理产生的hex文件是用于仿真还是实物,注释掉此宏,程序将用于实物,不注释就用于仿真。
// Includes.h
#ifndef _INCLUDES_H_
#define _INCLUDES_H_
// 系统头文件
#include
#include
// 仿真设置,定义以下宏编译出的文件将能在仿真中无误运行
// #define _PROTEUS_
// 公共部分
#include "Communal.h"
// 按键部分
#include "Key.h"
// LCD部分
#include "LCD1602.h"
// 步进电机部分
#include "Motor.h"
// 默认设置
#define DOrientation 1
#define DTurn_Zero 1
#define DAngle 0
#define DRotation 1
// 设置信息保存
typedef struct Motor{
u8 Orientation : 1; // 方向设置,正(Correct)1、反(Anti)0
u8 Turn_Zero : 1; // 转向置零,是(Yes)1、否(No)0
u8 CH : 1; // 正负号输入设置 CH和CHH是用于节省内容空间设置的,放弃原bit位
u8 CHH : 1; // 正负号输入返回设置
u8 : 4; // 对齐空位
u16 Angle : 9; // 旋转角度基础值 长度设置要求: log(2, Motor.h中的DData的值)
u16 Rotation : 9; // 单次旋转角度设置 长度设置要求:
} Motor;
#define ShowFloat(LINE, COL, NUM) LCD_ShowFloat(LINE, COL, "%7.3f", NUM * NFundation)
#define ShowNum(COL, NUM) LCD_ShowNum(0, COL, "%3d", NUM)
#define ShowString(LINE, COL, STR) LCD_ShowString(LINE, COL, STR)
#define Revolve(Orien, Num) Motor_Revolve(Setting.Angle % MotorNum, Num, Orien, Setting.Orientation)
#endif
这个头文件是专门针对我们设计的文件所制作的。里面有一个Motor
的struct结构体定义,里面包含了我们所设置的功能,而且为了简化代码且实现循环增加,如果我们要修改步进电机的基础值,还需要修改这里的值,修改后代码才能正常运行。修改要求为Motor.h中DData关于2的对数的值
。
文件中还加了公共部分的代码。公共部分的代码如下:
// Communal.h
/* 公共部分 */
#ifndef _COMMUNAL_H_
#define _COMMUNAL_H_
// 关键字替换
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
// 常见延时表 --- 12MHz
#define Delay1ms() Delayms(12, 169)
#define Delay3ms() Delayms(36, 1)
#define Delay5ms() Delayms(59, 90)
#define Delay10ms() Delayms(117, 184)
#define Delay20ms() Delayms(234,115)
// 基本延时函数
void Delayms(u8, u8);
// 二进制数据反向
u8 bitFlip(u8);
#endif
// Communal.c
#ifdef _INCLUDES_
#include "Includes.h"
#ifndef _COMMUNAL_H_
#error "未加装Communal.h文件。"
#endif
#else
#include "Communal.h"
#endif
#ifdef _INCLUDES_
#pragma message("已打开_INCLUDES_,此工程包含Includes.h.")
#endif
#ifdef _PROTEUS_
#pragma message("已打开_PROTEUS_,编译后hex文件需用于Proteus仿真中.");
#endif
// 基本延时函数
void Delayms(u8 i, u8 j){
do{
while (--j);
} while (--i);
}
// 二进制数据反向
// 传参:待反转数据
u8 bitFlip(u8 Date){
u8 ret;
u8 i;
for(i = 0; i < 8; i++){
ret <<= 1;
ret += Date & 0x01;
Date >>= 1;
}
return ret;
}
本来这个文件的作用只是加Delay的相关函数的。然后因为我PCB设计出了失误(手动捂脸),把D0~D7的数据端口画颠倒了,所以加了一个二进制数据翻转bitFlip
的代码。
此次设计的主要核心就是main.c
,代码其实比较简单,就不解释了,直接上代码然后再做介绍:
// main.c
#include "Includes.h"
Motor Setting = {DOrientation, DTurn_Zero, 0, 1, DAngle, DRotation};
// 获取数字
u16 GetNum(u16 Data, u8 *showMes){
u8 num = 0;
SendX(Co, CL);
ShowString(0, 0, showMes);
Setting.CHH = 1;
if(Setting.CH) ShowString(0 ,11, "+");
ShowString(1, 0, "*");
ShowFloat(1, 1, 1);
ShowString(1, 8, "=");
ShowFloat(1, 9, Data);
SendX(Co, SW(1, 1, 1));
ShowNum(12, Data);
while(1){
switch(GetKey(1)){
case NUM_9:
num++;
case NUM_8:
num++;
case NUM_7:
num++;
case NUM_6:
num++;
case NUM_5:
num++;
case NUM_4:
num++;
case NUM_3:
num++;
case NUM_2:
num++;
case NUM_1:
num++;
case NUM_0:
Data = Data * 10 + num;
num = 0;
if(Data > DData){
Data = DData;
}
ShowFloat(1, 9, Data);
ShowNum(12, Data);
break;
case CORRECT:
if(Setting.CH){
if(Setting.CHH == 1) ShowString(0, 11, "-");
else ShowString(0, 11, "+");
SendX(Co, SS(15));
Setting.CHH = !Setting.CHH;
break;
}
case ANTI:
if(Setting.CH){
if(Setting.CHH == 1) ShowString(0, 11, "-");
else ShowString(0, 11, "+");
SendX(Co, SS(15));
Setting.CHH = !Setting.CHH;
break;
}
case ENTER:
case SET:
if(Setting.CH) return Data == 0 ? 0xFFFF : Data;
else return Data;
case CANCEL:
return 0xFFFF;
case DEL:
Data /= 10;
ShowFloat(1, 9, Data);
ShowNum(12, Data);
}
}
}
// 输入角度自匹配
u16 GetAngle(){
float Data = 0;
u8 i, Point = 0;
u16 NearNum = 0;
float GetNum = 0;
SendX(Co, CL);
ShowString(0, 0, "Set");
Setting.CHH = 1;
if(Setting.CH) ShowString(0, 7, "+");
ShowString(1, 0, "Angle");
ShowFloat(1, 9, NearNum);
LCD_ShowFloat(0, 8, "%7g", Data);
SendX(Co, SW(1, 1, 1));
while(1){
switch(GetKey(1)){
case NUM_9:
GetNum++;
case NUM_8:
GetNum++;
case NUM_7:
GetNum++;
case NUM_6:
GetNum++;
case NUM_5:
GetNum++;
case NUM_4:
GetNum++;
case NUM_3:
GetNum++;
case NUM_2:
GetNum++;
case NUM_1:
GetNum++;
case NUM_0:
if(Point == 0){
Data *= 10;
Data += GetNum;
} else if(Point <= 3) {
for(i = 0; i < Point; i ++){
GetNum /= 10;
}
Point += 1;
Data += GetNum;
} else break;
if(Data > 360){
Data = 360;
}
NearNum = (u16)(Data / NFundation + 0.5);
ShowFloat(1, 9, NearNum);
LCD_ShowFloat(0, 8, "%7g", Data);
GetNum = 0;
break;
case CORRECT:
if(Setting.CH){
if(Setting.CHH == 1) ShowString(0, 7, "-");
else ShowString(0, 7, "+");
SendX(Co, SS(15));
Setting.CHH = !Setting.CHH;
break;
}
case ANTI:
if(Point == 0){
ShowString(0, 6, ".");
SendX(Co, SS(15));
Point = 1;
} else if(Point == 1){
ShowString(0, 6, " ");
SendX(Co, SS(15));
Point = 0;
}
break;
case ENTER:
case SET:
if(Setting.CH) return NearNum == 0 ? 0xFFFF : NearNum;
else return NearNum;
case CANCEL:
return 0xFFFF;
case DEL:
if(Point == 0){
Data = (u16)Data / 10;
} else if(Point == 1){
ShowString(0, 6, " ");
SendX(Co, SS(15));
Point = 0;
break;
} else {
for(i = 0; i < Point - 1; i++){
Data *= 10;
}
Data = (u16)(Data/10);
for(i = 0; i < Point - 2; i++){
Data /= 10.0;
}
Point -= 1;
}
NearNum = (u16)(Data / NFundation + 0.5);
ShowFloat(1, 9, NearNum);
LCD_ShowFloat(0, 8, "%7g", Data);
break;
}
}
}
// 设置
void Motor_Set(){
extern Motor Setting;
// 转向置零,背景灯,单次旋转角度,重置基准旋转角,重置
u8 code msg[5][6] = {"Turn ", "Pause", "RBase", "RSet "};
u8 Key, ch = 0;
SendX(Co, CL);
ShowString(0, 0, "Other Setting");
ShowString(1, 0, msg[0]);
if(Setting.Turn_Zero) ShowString(1, 13, "Yes");
else ShowString(1, 13, " NO");
while(1){
Key = GetKey(1);
switch(Key){
case UP:
case LEFT:
if(!ch) ch = 3;
else ch--;
goto Moto_Set_1;
case DOWN:
case RIGHT:
if(ch == 3) ch = 0;
else ch++;
Moto_Set_1:
ShowString(1, 0, msg[ch]);
switch(ch){
case 0:
if(Setting.Turn_Zero) ShowString(1, 9, " YES");
else ShowString(1, 9, " NO");
break;
case 1:
ShowFloat(1, 9, Setting.Rotation);
break;
case 2:
ShowString(1, 9, " ");
break;
case 3:
ShowString(1, 13, " ");
}
break;
case YES:
case ENTER:
case SET:
switch(ch){
case 0:
Setting.Turn_Zero = !Setting.Turn_Zero;
if(Setting.Turn_Zero) ShowString(1, 13, "YES");
else ShowString(1, 13, " NO");
break;
break;
case 1:
Key = GetNum(Setting.Rotation, msg[1]);
if(Key == 0xFFFF) Setting.Rotation = 1;
else Setting.Rotation = Key;
SendX(Co, CL);
ShowString(0, 0, "Other Setting");
ShowString(1, 0, msg[ch]);
ShowFloat(1, 9, Setting.Rotation);
break;
case 2:
Setting.Angle = 0;
return;
case 3:
if(Setting.Angle > DData / 2) Revolve(Setting.Orientation, DData - Setting.Angle);
else Revolve(!Setting.Orientation, Setting.Angle);
Setting.Orientation = DOrientation;
Setting.Angle = DAngle;
Setting.Turn_Zero = DTurn_Zero;
Setting.Rotation = DRotation;
return;
}
break;
case CANCEL:
case DEL:
case CORRECT:
case ANTI:
return;
}
}
}
// 主界面显示
void MainShow(){
SendX(Co, SW(1, 0, 0));
SendX(Co, CL);
ShowString(0, 0, "Angle:");
ShowFloat(0, 9, Setting.Angle);
ShowString(1, 0, "Dirction:");
if(Setting.Orientation)
ShowString(1, 9, "Correct");
else
ShowString(1, 9, " Anti");
}
// 主函数
void main(){
u16 Key;
LCD_Init();
MainShow();
while(1){
switch(GetKey(0)){
case UP:
case RIGHT: // 上/右
Revolve(Setting.Orientation, Setting.Rotation);
Setting.Angle += Setting.Rotation;
ShowFloat(0, 9, Setting.Angle);
break;
case LEFT:
case DOWN: // 左/下
Revolve(!Setting.Orientation, Setting.Rotation);
Setting.Angle -= Setting.Rotation;
ShowFloat(0, 9, Setting.Angle);
break;
case CORRECT: // 正方向
case ANTI: // 反方向 更改:方向切换
if(!Setting.Orientation){
ShowString(1, 9, "Correct");
if(Setting.Turn_Zero){
Setting.Angle = DData - Setting.Angle;
ShowFloat(0, 9, Setting.Angle);
} else {
if(Setting.Angle > DData / 4)
Revolve(Setting.Orientation, DData - Setting.Angle * 2);
else
Revolve(!Setting.Orientation, Setting.Angle * 2);
}
Setting.Orientation = 1;
} else {
ShowString(1, 9, " Anti");
if(Setting.Turn_Zero){
Setting.Angle = DData - Setting.Angle;
ShowFloat(0, 9, Setting.Angle);
} else {
if(Setting.Angle > DData / 4)
Revolve(Setting.Orientation, DData - Setting.Angle * 2);
else
Revolve(!Setting.Orientation, Setting.Angle * 2);
}
Setting.Orientation = 0;
}
break;
case NUM_0: // 累加
Setting.CH = 1;
case YES:
case ENTER: // 确定/回车
Key = GetNum(0, "Angle");
goto Adjustment;
break;
case NUM_7: // 角度输入
case NUM_9:
Setting.CH = 1;
case NUM_1:
case NUM_3:
Key = GetAngle();
Adjustment:
if(Setting.CH){
if(Key != 0xFFFF){
Revolve(Setting.CHH == 1 ? Setting.Orientation : !Setting.Orientation, Key);
if(Setting.CHH){
Setting.Angle += Key;
}else{
Setting.Angle -= Key;
}
}
Setting.CH = 0;
} else {
if(Key != 0xFFFF){
if(Key > Setting.Angle){
if (Key - Setting.Angle > DData / 2)
Revolve(!Setting.Orientation, DData - Key + Setting.Angle);
else
Revolve(Setting.Orientation, Key - Setting.Angle);
} else {
if(Setting.Angle - Key > DData / 2)
Revolve(Setting.Orientation, DData- Setting.Angle + Key);
else
Revolve(!Setting.Orientation, Setting.Angle - Key);
}
Setting.Angle = Key == DData ? 0 : Key;
}
}
MainShow();
break;
case SET: // 设置
Motor_Set();
MainShow();
break;
case CANCEL: // 角度清零
case DEL:
Setting.Angle = 0;
ShowFloat(0, 9, Setting.Angle);
break;
}
}
}
此次设计,主要针对步进电机的转动设置,我设计了几大界面,主界面、标定角度设置界面、标定角度设置调整、最近角度设置、最近角度调整以及其他设置功能,设计根据按键进行介绍,按键的功能将直接以表格的形式呈现,后期详解该功能将以坐标写出,比如第3行第4列的按钮坐标为(3, 4)。
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 最近角度设置 | 当前角度+单次旋转角度 | 最近角度设置 | 角度清零 |
第2行 | 当前角度-单次旋转角度 | 标定角度设置 | 当前角度+单次旋转角度 | 标定角度设置 |
第3行 | 最近角度调整 | 当前角度-单次旋转角度 | 最近角度调整 | 角度清零 |
第4行 | 旋转方向反转 | 标定角度调整 | 旋转方向反转 | 设置键 |
各功能介绍:
除了功能当前角度±单次旋转角度和角度转换/清零没有更多界面外。其他功能都有独立界面。下面一一介绍其界面和功能键。
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 输入1 | 输入2 | 输入3 | 退格Del |
第2行 | 输入4 | 输入5 | 输入6 | 确定 |
第3行 | 输入7 | 输入8 | 输入9 | 取消 |
第4行 | 小数点 | 输入0 | 小数点 | 设置 |
此界面第一行会显示你设置的角度,第二行会显示具体旋转的角度,在点击小数点后,可输入小数点后的数。最高输入三位小数+三位整数+小数点位。
此界面设置键同确认键。最大数固定为360.000,再大无法增加。
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 输入1 | 输入2 | 输入3 | 退格Del |
第2行 | 输入4 | 输入5 | 输入6 | 确定 |
第3行 | 输入7 | 输入8 | 输入9 | 取消 |
第4行 | 确定 | 输入0 | 确定 | 设置 |
此界面第一行会显示你设置的基值,第二行会显示乘以Motor.h中宏定义的DFundation
后具体设置的角度,只能输入整数。
此界面设置键同确认键。最大数固定为Motor.h中宏定义的DData
,带自动调整功能。
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 输入1 | 输入2 | 输入3 | 退格Del |
第2行 | 输入4 | 输入5 | 输入6 | 确定 |
第3行 | 输入7 | 输入8 | 输入9 | 取消 |
第4行 | 正负方向选择 | 输入0 | 小数点 | 设置 |
此界面和最近角度设置界面类似,唯一多的是前方的+/-号,+号表示沿当前设置方向旋转设置角度,-号表示沿当前设置方向的反方向旋转设置角度。输入上将按钮(4,1)修改为正负方向选择。
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 输入1 | 输入2 | 输入3 | 退格Del |
第2行 | 输入4 | 输入5 | 输入6 | 确定 |
第3行 | 输入7 | 输入8 | 输入9 | 取消 |
第4行 | 正负方向选择 | 输入0 | 正负方向选择 | 设置 |
此界面和标定角度设置界面类似,唯一多的是前方的+/-号,+号表示沿当前设置方向旋转设置角度,-号表示沿当前设置方向的反方向旋转设置角度。输入上将按钮(4,1)和按钮(4,3)修改为正负方向选择。
设置界面下有Turn、Pause、RBase、RSet几个功能。Pause下有其他界面,其他设置界面差不多,设置界面如下图:
设置界面下,各按键的功能如下
按键 | 第1列 | 第2列 | 第3列 | 第4列 |
---|---|---|---|---|
第1行 | 无功能 | 上一个 | 无功能 | 退格Del |
第2行 | 上一个 | 切换/设置 | 下一个 | 切换/设置 |
第3行 | 无功能 | 下一个 | 无功能 | 退出 |
第4行 | 退出 | 无功能 | 退出 | 切换/设置 |
输入中能进入更多设置界面的进入更多界面,否者为切换模式。
下面,是喜闻乐见的工程代码,提供CSDN下载链接和百度的下载链接。
文件为此文章的附加资源,若无在CSDN下载的意向,可以通过百度网盘下载。
备注:此文件只包含程序和仿真,无PCB制作图,因学校设计要求,设计的PCB只有外围电路,参考价值不大。同时也无模块购买链接,需自行购买或找我要也可。程序报错可私信我共同解决(PS:不经常看CSDN私信)。
CSDN下载链接:51单片机角度控制(包含程序+仿真)
百度下载链接:51单片机角度控制(包含程序+仿真) 提取码yadu