51单片机步进电机角度控制详解(免费提供代码+仿真)

51单片机步进电机控制详解

一、步进电机基本介绍

我个人认为,步进电机的基本原理和介绍看看其他博主的介绍就好了。我比较希望讲一下我对步进电机的关于自己一种理解方式,可能与真正步进电机的原理差的有点大。下面还是给一下我推荐的一些博主对步进电机的介绍文章。
百度步进电机链接
步进电机驱动及原理—star-air

步进电机,把名字扩展一下就是按“步”前进的电机,这里的“步”,我认为既可以解释为“脚步”,也可以解释为”步骤“。脚步就指像人一样,无论速度多快,每次只能跨一步,步进电机也是如此,无论你通电时间多长,只要脉冲不发生变化,步进电机也只走一步。而”步骤“,就当行走方式,人的行走方式行走方式是交替向前的,步进电机也一样,它的脉冲方式按照一定规律运行的。
对了,还有一个很重要的一点,这里的脉冲方式和PWM(脉宽调制)是不一样的,这里的脉冲我认为其实是对51单片机的IO口电平规律变换的频率。

1. 步进电机结构

我们这次使用的是28BYJ-48 5V DC这个型号的步进电机(实物图如下方图),所以我们的机构介绍主要针对此步进电机,若未来有更多扩展再加入更多介绍。
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的接线示意图和设计图。
28BYJ-48 5V DC步进电机接线示意图和设计图

这是我购买的步进电机的结构示意图,可能和各位的有所区别,请各位以实物为准,如果各位要设计PCB板且要把步进电机装进去,就需要对步进电机的主要结构有了解,不然必要性就不是很大了。

关于接线示意图,也就是步进电机的内部接线图,是我们针对仿真时和具体电路设计需要的,所以还是比较重要的。这里的接线介绍我推荐和仿真一起看(主要是仿真的运行),很容易就理解了,具体的运行会在后面介绍。下面大概说一下接线。

名称接线
蓝1控制线1
粉2控制线2
黄3控制线3
橙4控制线4
红55V VCC或GND(本次使用时VCC)

2. 步进电机驱动

ULN2003驱动文章推荐:【常用芯片】ULN2003工作原理及中文资料(实例:STM32驱动28BYJ48步进电机)

前面说到,步进电机运转过程中电路在0.3A~0.4A之间,而我们的51单片机拉电流1mA,灌电流10mA,所以对我们51单片而言,直接驱动步进电机是不现实的,所以我们需要加一个能承受大电流的中介。根据我们学习时用的开发板关于驱动步进电机所使用模块的是ULN2003芯片,这个芯片能为我们承受大电流,为了便于测试,我也使用了此模块。下面是这个模块的逻辑图和实物图。
ULN2003逻辑图
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高电平

二、硬件&仿真设计

0.设计要求

本次步进电机设计要求为能显示和控制步进电机具体转动角度,能显示和控制电机正转和反转

1. 硬件设计

针对此设计要求,我们需要显示模块控制输入模块步进电机模块

显示模块

LCD1602文章推荐: 快速掌握——LCD1602液晶显示(多组实验,附带源程序)

鉴于要显示正转和反转,如果使用数码管作为显示器,其显示效果是不行的,所以显示模块我使用的是LCD1602LCD1602中16指16每行支持显示16个字符,02指有两行。在仿真中为LM016L。接下来说一下介绍LCD1602的具体引脚功能,具体仿真图与实物图如下:
LCD1602仿真图
LCD1602实物图

  1. VSS:这里的VSS可以直接理解为给LCD1602的GND。没有什么可以介绍的。
  2. VDD:就是我们说的VCC高电平,这里的高电平接5V即可。
  3. V0:每个字符显示位的对比度调整,电压越高对比度越低。我们一般会在此处接一个可调电阻,用于调节对比度。
  4. RS:指令、数据选择,低电平时系统判定D0~D7输入为指令,高电平时判断输入为数据。
  5. RW:R/W为读/写信号线,高电平时进行读操作,低电平时进行写操作。
  • 当RS和R/W共同为低电平时可以写入指令或显示地址;
  • 当RS为低电平,R/W为高电平时,可以读忙信号;
  • 当 RS为高电平,R/W为低电平时,可以写入数据。
  1. E:使能端,当端口E出现下降沿时,LCD1602执行指令。
  2. D0~D7:D0~D7为8位双向数据线。
  3. A:背光源正极。
  4. 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仿真显示成这样)如下图所示:
LCD1602接线仿真

输入模块

控制输入模块采用16(4×4)键键盘。这种键盘其实是用的很多的,没啥可以介绍的,我比较推荐的是相关输入模块使用自己购买的输入模块,相关代码可以直接替换。我当时做的时候犯了一点傻,我先设计的仿真,再买的模块,相关模块差点没找到,不过最后又设计了PCB这些就没有影响了。下面是模块的具体样式和仿真图:
16键键盘具体图样
16键键盘仿真图
唯一比较注意的是我们虽然实际使用的是微动开关,但在做模拟的时候还是采用的普通的按钮(仿真名称button)具体的功能就不多说了,直接上实物接线表。

端口接线
C4P1.0
C3P1.1
C2P1.2
C1P1.3
R1P1.4
R2P1.5
R3P1.6
R4P1.7
步进电机模块

步进电机模块就型号为28BYJ-48 5V DC的步进电机和ULN2003组合。这里的使用也没有什么可以多说的。直接上仿真图和接线(实物图在上面):
步进电机模块实物图
ULN2003接线

此处需要注意ULN2003芯片的每个端口的具体含义,可以参考前面的ULN2003逻辑框图,将实物的带缺角口与逻辑图带缺角口对起,芯片有字面对准自己,此时实物端口与对照逻辑框图一样。
其次购买的步进电机不同,可能颜色标注不同,以购买实物为准。
还有一点,因为步进电机在仿真中响应速度太慢,仿真中其实无法完全模拟步进电机。在仿真中跑出来的程序有问题。不过我已经在软件中为各位弥补了这一问题,具体的修改方式看后文的Includes.h中关于对宏_PROTEUS_的设定。

ULN2003端口接线
COM5V VCC
EGND
1B51单片机P3.0
2B51单片机P3.1
3B51单片机P3.2
4B51单片机P3.3
1C步进电机C1(蓝1)
2C步进电机C2(粉2)
3C步进电机C3(黄3)
4C步进电机C4(橙4)

2. 仿真全图一览

仿真全图一览

这里没有加51单片机的最小系统板的电路,使用的晶振频率为12MHz。

3.PCB设计

PCB设计网站:嘉立创EDA(可以白嫖PCB电路板)

这个PCB设计是我一时兴起做的,用的是嘉立创EDA(可以白嫖PCB板)。又因为我们做课程设计是由我们老师提供最小系统板,我们只需要在最小系统板上加外围电路。所以设计中我也是直接针对外围电路做的设计。下面是设计图、3D图和成品图:
PCB设计图
PCB3D图正面
PCB3D图背面
实物图

PS:忘买XH插座了,所以就用排针代替了。

三、软件设计

下面,根据每个模块做软件。

1. 显示模块

因为显示模块已经介绍了,为了方便各位更改端口,直接修改LCD1602.h中的RS、RW、E、LCDMsg的参数即可。还有需要注意的是,在LCD1602.c中有一个SendX的宏定义,里面的bitFlip在线没接错的情况下需要删除掉。

LCD1602.h
// LCD1602.h
#ifndef _LCD1602_H_
#define _LCD1602_H_
// 此文件所需头文件
#include <stdio.h>
#include <reg52.h>
// 关键字替换
#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
// 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);
}

2. 输入模块

Key.h

输入模块也比较简单,为了让各位能更好的修改数据,直接修改Key.h中的KEY就能直接按键修改连接位置。

// Key.h
#ifndef _KEY_H_
#define _KEY_H_
// 此文件所需头文件
#include <reg52.h>
// 关键字替换
#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
// 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;
		}
	}
}

3. 步进电机模块

步进电机模块的数据修改需要根据基础比例来修改,不然代码会出问题。而且因为51单片机无论float还是double类型,位数都只有32位,所以浮点数的精度不会很高,建议基础比例就在这一比例。可以增加,不建议再减少了。同时,当我们修改此基础比例后,我们需要修改后面的Includes.h中的Motor结构体的一部分元素的长度,具体长度后面会做详细介绍。当然,此文件也是支持修改接线的。修改MotorLine.h中的MotorLine即可,若要修改IO口的话需要更改Motor.c中的Motor_Data中的数据。同时还有一个关于_PROTEUS_的宏,此宏用于控制我们的Motor_Revolve函数是使用在仿真中还是实物中,因为一部分原因,这两者不互通,这一点需要注意。

Motor.h
// Motor.h
#ifndef _MOTOR_H_
#define _MOTOR_H_
// 此文件所需头文件
#include <reg52.h>
// 关键字替换
#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
// 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
		}
	}
}

4. 数据整合

通过前面的函数,我们不难看出,我们使用了一个Includes.h的自定义头文件,这里的Includes.h除了要加入之外,还要在魔术棒当中进行设置才能完全加入,加入的方式如下:
_INCLUDES_加入方式

Includes.h头文件的内容如下,其中可以设置的内容有默认设置修改(DOrientation, DTurn_Zero, DAngle和DRotation),其中需要我们注意的是,里面有一个仿真设置宏_PROTEUS_,此宏用于管理产生的hex文件是用于仿真还是实物,注释掉此宏,程序将用于实物,不注释就用于仿真。

Includes.h
// Includes.h
#ifndef _INCLUDES_H_
#define _INCLUDES_H_

// 系统头文件
#include <reg52.h>
#include <stdio.h>

// 仿真设置,定义以下宏编译出的文件将能在仿真中无误运行
// #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
// 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
// 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的代码。

5.主函数

此次设计的主要核心就是main.c,代码其实比较简单,就不解释了,直接上代码然后再做介绍:

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行旋转方向反转标定角度调整旋转方向反转设置键

各功能介绍:

  • 最近角度设置:根据用户输入指定角度,系统自动调整到旋转到离此角度最近的角度。
  • 当前角度+单次旋转角度:当按下指定按键后,根据我们所设置的旋转方向旋转标定旋转角度。标定旋转角度值为Setting.Rotation中设置。
  • 角度清零:将Setting.Angle的值清零,即将当前的角度作为默认角度,可用于校正步进电机的位置。
  • 当前角度-单次旋转角度:当按下指定按键后,根据我们所设置的旋转方向的反方向旋转标定旋转角度。标定旋转角度值为Setting.Rotation中设置。
  • 标定角度设置:此模式下输入值将直接与NFundation相乘,即旋转指定量的默认角度值。
  • 最近角度调整:与最近角度设置基本相同,唯一的区别是设置的角度将根据当前角度增加或减少指定角度。
  • 旋转方向反转:修改默认旋转方向,对角度增加或减少的功能有效,其次可以在设置中调整方向反转后是角度调整还是步进电机调整。
  • 标定角度调整:与标定角度设置基本相同,唯一的区别是设置的角度将根据当前角度增加或减少指定角度。
  • 设置键:进入设置功能。

除了功能当前角度±单次旋转角度角度转换/清零没有更多界面外。其他功能都有独立界面。下面一一介绍其界面和功能键。

最近角度设置界面

最近角度设置界面
最近角度设置界面下,各按键的功能如下

按键第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)修改为正负方向选择。

设置界面

设置界面下有TurnPauseRBaseRSet几个功能。Pause下有其他界面,其他设置界面差不多,设置界面如下图:
设置界面Turn
设置界面下,各按键的功能如下

按键第1列第2列第3列第4列
第1行无功能上一个无功能退格Del
第2行上一个切换/设置下一个切换/设置
第3行无功能下一个无功能退出
第4行退出无功能退出切换/设置

输入中能进入更多设置界面的进入更多界面,否者为切换模式。

  • Turn设置转向后的操作,设置为Yes时,进行换算角度,不转动电机。设置为No时转动电机,不切换角度。
  • Pause设置单次旋转角度,即Setting.Rotation,可按按钮(2,2)、(4, 2)、(4,4)进入数据设置界面,界面如下:
    Pause设置界面
    此界面按钮与标定角度设置按钮模式相同,当此值设定为0时,功能当前角度±单次旋转角度无效。
  • RBase功能同角度清零。
  • RSet将角度与设置恢复为默认设定值,此处的恢复无法恢复角度清零产生的影响。

四、工程下载

下面,是喜闻乐见的工程代码,提供CSDN下载链接和百度的下载链接。

文件为此文章的附加资源,若无在CSDN下载的意向,可以通过百度网盘下载。
备注:此文件只包含程序和仿真,无PCB制作图,因学校设计要求,设计的PCB只有外围电路,参考价值不大。同时也无模块购买链接,需自行购买或找我要也可。程序报错可私信我共同解决(PS:不经常看CSDN私信)。

CSDN下载链接:51单片机角度控制(包含程序+仿真)
百度下载链接:51单片机角度控制(包含程序+仿真) 提取码yadu

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zzhdzs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值