51單片機步進電機控制詳解
一、步進電機基本介紹
我個人認為,步進電機的基本原理和介紹看看其他博主的介紹就好了。我比較希望講一下我對步進電機的關于自己一種理解方式,可能與真正步進電機的原理差的有點大。下面還是給一下我推薦的一些博主對步進電機的介紹文章。
百度步進電機鏈接
步進電機驅動及原理—star-air
步進電機,把名字擴展一下就是按“步”前進的電機
,這里的“步”,我認為既可以解釋為“腳步”,也可以解釋為”步驟“。腳步就指像人一樣,無論速度多快,每次只能跨一步,步進電機也是如此,無論你通電時間多長,只要脈沖不發(fā)生變化,步進電機也只走一步。而”步驟“,就當行走方式,人的行走方式行走方式是交替向前的,步進電機也一樣,它的脈沖方式按照一定規(guī)律運行的。
對了,還有一個很重要的一點,這里的脈沖方式和PWM(脈寬調制)是不一樣的,這里的脈沖我認為其實是對51單片機的IO口電平規(guī)律變換的頻率。
1. 步進電機結構
我們這次使用的是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
的接線示意圖和設計圖。
這是我購買的步進電機的結構示意圖,可能和各位的有所區(qū)別,請各位以實物為準,如果各位要設計PCB板且要把步進電機裝進去,就需要對步進電機的主要結構有了解,不然必要性就不是很大了。
關于接線示意圖,也就是步進電機的內部接線圖,是我們針對仿真時和具體電路設計需要的,所以還是比較重要的。這里的接線介紹我推薦和仿真一起看(主要是仿真的運行),很容易就理解了,具體的運行會在后面介紹。下面大概說一下接線。
名稱 | 接線 |
---|---|
藍1 | 控制線1 |
粉2 | 控制線2 |
黃3 | 控制線3 |
橙4 | 控制線4 |
紅5 | 5V VCC或GND(本次使用時VCC) |
2. 步進電機驅動
ULN2003驅動文章推薦:【常用芯片】ULN2003工作原理及中文資料(實例:STM32驅動28BYJ48步進電機)
前面說到,步進電機運轉過程中電路在0.3A~0.4A
之間,而我們的51單片機拉電流1mA,灌電流10mA,所以對我們51單片而言,直接驅動步進電機是不現實的,所以我們需要加一個能承受大電流的中介。根據我們學習時用的開發(fā)板關于驅動步進電機所使用模塊的是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液晶顯示(多組實驗,附帶源程序)
鑒于要顯示正轉和反轉,如果使用數碼管作為顯示器,其顯示效果是不行的,所以顯示模塊
我使用的是LCD1602
。LCD1602
中16指16每行支持顯示16個字符,02指有兩行。在仿真中為LM016L
。接下來說一下介紹LCD1602的具體引腳功能,具體仿真圖與實物圖如下:
VSS
:這里的VSS可以直接理解為給LCD1602的GND。沒有什么可以介紹的。VDD
:就是我們說的VCC高電平,這里的高電平接5V即可。V0
:每個字符顯示位的對比度調整,電壓越高對比度越低。我們一般會在此處接一個可調電阻,用于調節(jié)對比度。RS
:指令、數據選擇,低電平時系統(tǒng)判定D0~D7輸入為指令,高電平時判斷輸入為數據。RW
:R/W為讀/寫信號線,高電平時進行讀操作,低電平時進行寫操作。
- 當RS和R/W共同為低電平時可以寫入指令或顯示地址;
- 當RS為低電平,R/W為高電平時,可以讀忙信號;
- 當 RS為高電平,R/W為低電平時,可以寫入數據。
E
:使能端,當端口E
出現下降沿
時,LCD1602執(zhí)行指令。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) |
2. 仿真全圖一覽
這里沒有加51單片機的最小系統(tǒng)板的電路,使用的晶振頻率為12MHz。
3.PCB設計
PCB設計網站:嘉立創(chuàng)EDA(可以白嫖PCB電路板)
這個PCB設計是我一時興起做的,用的是嘉立創(chuàng)EDA(可以白嫖PCB板)。又因為我們做課程設計是由我們老師提供最小系統(tǒng)板,我們只需要在最小系統(tǒng)板上加外圍電路。所以設計中我也是直接針對外圍電路做的設計。下面是設計圖、3D圖和成品圖:
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;} // 寫入數據
// 發(fā)送數據,注意如果線沒接錯就把下面的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.h
頭文件的內容如下,其中可以設置的內容有默認設置修改(DOrientation, DTurn_Zero, DAngle和DRotation),其中需要我們注意的是,里面有一個仿真設置宏_PROTEUS_
,此宏用于管理產生的hex文件是用于仿真還是實物,注釋掉此宏,程序將用于實物,不注釋就用于仿真。
Includes.h
// Includes.h
#ifndef _INCLUDES_H_
#define _INCLUDES_H_
// 系統(tǒng)頭文件
#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是用于節(jié)省內容空間設置的,放棄原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結構體定義,里面包含了我們所設置的功能,而且為了簡化代碼且實現循環(huán)增加,如果我們要修改步進電機的基礎值,還需要修改這里的值,修改后代碼才能正常運行。修改要求為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行 | 旋轉方向反轉 | 標定角度調整 | 旋轉方向反轉 | 設置鍵 |
各功能介紹:
- 最近角度設置:根據用戶輸入指定角度,系統(tǒng)自動調整到旋轉到離此角度最近的角度。
- 當前角度+單次旋轉角度:當按下指定按鍵后,根據我們所設置的旋轉方向旋轉標定旋轉角度。標定旋轉角度值為Setting.Rotation中設置。
- 角度清零:將Setting.Angle的值清零,即將當前的角度作為默認角度,可用于校正步進電機的位置。
- 當前角度-單次旋轉角度:當按下指定按鍵后,根據我們所設置的旋轉方向的反方向旋轉標定旋轉角度。標定旋轉角度值為Setting.Rotation中設置。
- 標定角度設置:此模式下輸入值將直接與NFundation相乘,即旋轉指定量的默認角度值。
- 最近角度調整:與最近角度設置基本相同,唯一的區(qū)別是設置的角度將根據當前角度增加或減少指定角度。
- 旋轉方向反轉:修改默認旋轉方向,對角度增加或減少的功能有效,其次可以在設置中調整方向反轉后是角度調整還是步進電機調整。
- 標定角度調整:與標定角度設置基本相同,唯一的區(qū)別是設置的角度將根據當前角度增加或減少指定角度。
- 設置鍵:進入設置功能。
除了功能當前角度±單次旋轉角度和角度轉換/清零沒有更多界面外。其他功能都有獨立界面。下面一一介紹其界面和功能鍵。
最近角度設置界面
最近角度設置界面下,各按鍵的功能如下
按鍵 | 第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行 | 退出 | 無功能 | 退出 | 切換/設置 |
輸入中能進入更多設置界面的進入更多界面,否者為切換模式。
- Turn設置轉向后的操作,設置為Yes時,進行換算角度,不轉動電機。設置為No時轉動電機,不切換角度。
-
Pause設置單次旋轉角度,即Setting.Rotation,可按按鈕(2,2)、(4, 2)、(4,4)進入數據設置界面,界面如下:
此界面按鈕與標定角度設置按鈕模式相同,當此值設定為0時,功能當前角度±單次旋轉角度無效。 - RBase功能同角度清零。
- RSet將角度與設置恢復為默認設定值,此處的恢復無法恢復角度清零產生的影響。
四、工程下載
下面,是喜聞樂見的工程代碼,提供CSDN下載鏈接和百度的下載鏈接。
文件為此文章的附加資源,若無在CSDN下載的意向,可以通過百度網盤下載。
備注:此文件只包含程序和仿真,無PCB制作圖,因學校設計要求,設計的PCB只有外圍電路,參考價值不大。同時也無模塊購買鏈接,需自行購買或找我要也可。程序報錯可私信我共同解決(PS:不經??碈SDN私信)。
文章來源:http://www.zghlxwxcb.cn/news/detail-417531.html
CSDN下載鏈接:51單片機角度控制(包含程序+仿真)
百度下載鏈接:51單片機角度控制(包含程序+仿真) 提取碼yadu文章來源地址http://www.zghlxwxcb.cn/news/detail-417531.html
到了這里,關于51單片機步進電機角度控制詳解(免費提供代碼+仿真)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!