国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

51單片機步進電機角度控制詳解(免費提供代碼+仿真)

這篇具有很好參考價值的文章主要介紹了51單片機步進電機角度控制詳解(免費提供代碼+仿真)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

51單片機步進電機控制詳解

一、步進電機基本介紹

我個人認為,步進電機的基本原理和介紹看看其他博主的介紹就好了。我比較希望講一下我對步進電機的關于自己一種理解方式,可能與真正步進電機的原理差的有點大。下面還是給一下我推薦的一些博主對步進電機的介紹文章。
百度步進電機鏈接
步進電機驅動及原理—star-air

步進電機,把名字擴展一下就是按“步”前進的電機,這里的“步”,我認為既可以解釋為“腳步”,也可以解釋為”步驟“。腳步就指像人一樣,無論速度多快,每次只能跨一步,步進電機也是如此,無論你通電時間多長,只要脈沖不發(fā)生變化,步進電機也只走一步。而”步驟“,就當行走方式,人的行走方式行走方式是交替向前的,步進電機也一樣,它的脈沖方式按照一定規(guī)律運行的。
對了,還有一個很重要的一點,這里的脈沖方式和PWM(脈寬調制)是不一樣的,這里的脈沖我認為其實是對51單片機的IO口電平規(guī)律變換的頻率。

1. 步進電機結構

我們這次使用的是28BYJ-48 5V DC這個型號的步進電機(實物圖如下方圖),所以我們的機構介紹主要針對此步進電機,若未來有更多擴展再加入更多介紹。
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

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的接線示意圖和設計圖。
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

這是我購買的步進電機的結構示意圖,可能和各位的有所區(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芯片,這個芯片能為我們承受大電流,為了便于測試,我也使用了此模塊。下面是這個模塊的邏輯圖和實物圖。
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

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的具體引腳功能,具體仿真圖與實物圖如下:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

  1. VSS:這里的VSS可以直接理解為給LCD1602的GND。沒有什么可以介紹的。
  2. VDD:就是我們說的VCC高電平,這里的高電平接5V即可。
  3. V0:每個字符顯示位的對比度調整,電壓越高對比度越低。我們一般會在此處接一個可調電阻,用于調節(jié)對比度。
  4. RS:指令、數據選擇,低電平時系統(tǒng)判定D0~D7輸入為指令,高電平時判斷輸入為數據。
  5. RW:R/W為讀/寫信號線,高電平時進行讀操作,低電平時進行寫操作。
  • 當RS和R/W共同為低電平時可以寫入指令或顯示地址;
  • 當RS為低電平,R/W為高電平時,可以讀忙信號;
  • 當 RS為高電平,R/W為低電平時,可以寫入數據。
  1. E:使能端,當端口E出現下降沿時,LCD1602執(zhí)行指令。
  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仿真顯示成這樣)如下圖所示:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

輸入模塊

控制輸入模塊采用16(4×4)鍵鍵盤。這種鍵盤其實是用的很多的,沒啥可以介紹的,我比較推薦的是相關輸入模塊使用自己購買的輸入模塊,相關代碼可以直接替換。我當時做的時候犯了一點傻,我先設計的仿真,再買的模塊,相關模塊差點沒找到,不過最后又設計了PCB這些就沒有影響了。下面是模塊的具體樣式和仿真圖:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
唯一比較注意的是我們雖然實際使用的是微動開關,但在做模擬的時候還是采用的普通的按鈕(仿真名稱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組合。這里的使用也沒有什么可以多說的。直接上仿真圖和接線(實物圖在上面):
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
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單片機步進電機角度控制詳解(免費提供代碼+仿真)

這里沒有加51單片機的最小系統(tǒng)板的電路,使用的晶振頻率為12MHz。

3.PCB設計

PCB設計網站:嘉立創(chuàng)EDA(可以白嫖PCB電路板)

這個PCB設計是我一時興起做的,用的是嘉立創(chuàng)EDA(可以白嫖PCB板)。又因為我們做課程設計是由我們老師提供最小系統(tǒng)板,我們只需要在最小系統(tǒng)板上加外圍電路。所以設計中我也是直接針對外圍電路做的設計。下面是設計圖、3D圖和成品圖:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

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除了要加入之外,還要在魔術棒當中進行設置才能完全加入,加入的方式如下:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)

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)。

主界面

51單片機步進電機角度控制詳解(免費提供代碼+仿真)
主界面下,各按鍵的功能如下

按鍵 第1列 第2列 第3列 第4列
第1行 最近角度設置 當前角度+單次旋轉角度 最近角度設置 角度清零
第2行 當前角度-單次旋轉角度 標定角度設置 當前角度+單次旋轉角度 標定角度設置
第3行 最近角度調整 當前角度-單次旋轉角度 最近角度調整 角度清零
第4行 旋轉方向反轉 標定角度調整 旋轉方向反轉 設置鍵

各功能介紹:

  • 最近角度設置:根據用戶輸入指定角度,系統(tǒng)自動調整到旋轉到離此角度最近的角度。
  • 當前角度+單次旋轉角度:當按下指定按鍵后,根據我們所設置的旋轉方向旋轉標定旋轉角度。標定旋轉角度值為Setting.Rotation中設置。
  • 角度清零:將Setting.Angle的值清零,即將當前的角度作為默認角度,可用于校正步進電機的位置。
  • 當前角度-單次旋轉角度:當按下指定按鍵后,根據我們所設置的旋轉方向的反方向旋轉標定旋轉角度。標定旋轉角度值為Setting.Rotation中設置。
  • 標定角度設置:此模式下輸入值將直接與NFundation相乘,即旋轉指定量的默認角度值。
  • 最近角度調整:與最近角度設置基本相同,唯一的區(qū)別是設置的角度將根據當前角度增加或減少指定角度。
  • 旋轉方向反轉:修改默認旋轉方向,對角度增加或減少的功能有效,其次可以在設置中調整方向反轉后是角度調整還是步進電機調整。
  • 標定角度調整:與標定角度設置基本相同,唯一的區(qū)別是設置的角度將根據當前角度增加或減少指定角度。
  • 設置鍵:進入設置功能。

除了功能當前角度±單次旋轉角度角度轉換/清零沒有更多界面外。其他功能都有獨立界面。下面一一介紹其界面和功能鍵。

最近角度設置界面

51單片機步進電機角度控制詳解(免費提供代碼+仿真)
最近角度設置界面下,各按鍵的功能如下

按鍵 第1列 第2列 第3列 第4列
第1行 輸入1 輸入2 輸入3 退格Del
第2行 輸入4 輸入5 輸入6 確定
第3行 輸入7 輸入8 輸入9 取消
第4行 小數點 輸入0 小數點 設置

此界面第一行會顯示你設置的角度,第二行會顯示具體旋轉的角度,在點擊小數點后,可輸入小數點后的數。最高輸入三位小數+三位整數+小數點位。
此界面設置鍵同確認鍵。最大數固定為360.000,再大無法增加。

標定角度設置界面

51單片機步進電機角度控制詳解(免費提供代碼+仿真)
標定角度設置界面下,各按鍵的功能如下

按鍵 第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,帶自動調整功能。

最近角度調整界面

51單片機步進電機角度控制詳解(免費提供代碼+仿真)
最近角度調整界面下,各按鍵的功能如下

按鍵 第1列 第2列 第3列 第4列
第1行 輸入1 輸入2 輸入3 退格Del
第2行 輸入4 輸入5 輸入6 確定
第3行 輸入7 輸入8 輸入9 取消
第4行 正負方向選擇 輸入0 小數點 設置

此界面和最近角度設置界面類似,唯一多的是前方的+/-號,+號表示沿當前設置方向旋轉設置角度,-號表示沿當前設置方向的反方向旋轉設置角度。輸入上將按鈕(4,1)修改為正負方向選擇。

標定角度調整界面

51單片機步進電機角度控制詳解(免費提供代碼+仿真)
標定角度調整界面下,各按鍵的功能如下

按鍵 第1列 第2列 第3列 第4列
第1行 輸入1 輸入2 輸入3 退格Del
第2行 輸入4 輸入5 輸入6 確定
第3行 輸入7 輸入8 輸入9 取消
第4行 正負方向選擇 輸入0 正負方向選擇 設置

此界面和標定角度設置界面類似,唯一多的是前方的+/-號,+號表示沿當前設置方向旋轉設置角度,-號表示沿當前設置方向的反方向旋轉設置角度。輸入上將按鈕(4,1)和按鈕(4,3)修改為正負方向選擇。

設置界面

設置界面下有TurnPause、RBase、RSet幾個功能。Pause下有其他界面,其他設置界面差不多,設置界面如下圖:
51單片機步進電機角度控制詳解(免費提供代碼+仿真)
設置界面下,各按鍵的功能如下

按鍵 第1列 第2列 第3列 第4列
第1行 無功能 上一個 無功能 退格Del
第2行 上一個 切換/設置 下一個 切換/設置
第3行 無功能 下一個 無功能 退出
第4行 退出 無功能 退出 切換/設置

輸入中能進入更多設置界面的進入更多界面,否者為切換模式。

  • Turn設置轉向后的操作,設置為Yes時,進行換算角度,不轉動電機。設置為No時轉動電機,不切換角度。
  • Pause設置單次旋轉角度,即Setting.Rotation,可按按鈕(2,2)、(4, 2)、(4,4)進入數據設置界面,界面如下:
    51單片機步進電機角度控制詳解(免費提供代碼+仿真)
    此界面按鈕與標定角度設置按鈕模式相同,當此值設定為0時,功能當前角度±單次旋轉角度無效。
  • RBase功能同角度清零。
  • RSet將角度與設置恢復為默認設定值,此處的恢復無法恢復角度清零產生的影響。

四、工程下載

下面,是喜聞樂見的工程代碼,提供CSDN下載鏈接和百度的下載鏈接。

文件為此文章的附加資源,若無在CSDN下載的意向,可以通過百度網盤下載。
備注:此文件只包含程序和仿真,無PCB制作圖,因學校設計要求,設計的PCB只有外圍電路,參考價值不大。同時也無模塊購買鏈接,需自行購買或找我要也可。程序報錯可私信我共同解決(PS:不經??碈SDN私信)。

CSDN下載鏈接:51單片機角度控制(包含程序+仿真)
百度下載鏈接:51單片機角度控制(包含程序+仿真) 提取碼yadu文章來源地址http://www.zghlxwxcb.cn/news/detail-417531.html

到了這里,關于51單片機步進電機角度控制詳解(免費提供代碼+仿真)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【Proteus仿真】【51單片機】步進電機控制系統(tǒng)設計

    【Proteus仿真】【51單片機】步進電機控制系統(tǒng)設計

    本項目使用Proteus8仿真51單片機控制器,使用ULN2003電機模塊、LCD1602模塊、按鍵模塊等。 主要功能: 系統(tǒng)運行后,LCD1602顯示電機當前運行檔位、方向、狀態(tài)。 可通過按鍵K4啟動與停止,按鍵K1加速、按鍵K2減速,按鍵K3換向;檔位可在1-5擋。 最終可實現: 按鍵功能:具有加速

    2024年02月12日
    瀏覽(27)
  • 51單片機的步進電機控制系統(tǒng)(仿真+程序+報告+原理圖)

    51單片機的步進電機控制系統(tǒng)(仿真+程序+報告+原理圖)

    該系統(tǒng)由AT89C51單片機+數碼管模塊+步進電機模塊+按鍵模塊構成。 可實現功能: 1、按鍵控制步進電機正反轉、加減速、停止; 2、2個發(fā)光二極管顯示正反轉,1位7段LED數碼管顯示當前轉速檔位(共9個檔位); 3、4個紅色LED,指示電機的轉速。 protues 仿真使用的是8.10版本,由于

    2024年02月11日
    瀏覽(23)
  • 51單片機雙軸太陽能追光追日系統(tǒng)ULN2003步進電機
  • 7-3、S曲線生成器【51單片機控制步進電機-TB6600系列】

    7-3、S曲線生成器【51單片機控制步進電機-TB6600系列】

    摘要 :本節(jié)介紹步進電機S曲線生成器的計算以及使用 一.計算原理 根據上一節(jié)內容,已經計算了一條任意S曲線的函數。在步進電機S曲線加減速的控制中,需要的S曲線如圖1所示,橫軸為時間,縱軸為角速度,其中w0為起始角速度,w1為終止角速度 在S曲線加減速控制中,加減

    2024年02月08日
    瀏覽(43)
  • 【51單片機Keil+Proteus8.9】控制步進電機+LCD1602顯示狀態(tài)

    【51單片機Keil+Proteus8.9】控制步進電機+LCD1602顯示狀態(tài)

    步進電機控制 設計思路 電路設計: 選用AT89C51單片機作為電路核心部件,外加LM016L液晶顯示屏作為顯示,顯示步進電機的Fast,Slow,Stop的三個狀態(tài) 將AT89C51單片機所選引腳與LM016L控制引腳相連,再將數據通過引腳與LCD接收引腳相連。 通過AT89C51單片機P0^0和P0^2兩個引腳引出兩個

    2024年01月20日
    瀏覽(36)
  • 基于51單片機的FRID智能門禁系統(tǒng)(RFID,12864,AT24C02,步進電機......)

    基于51單片機的FRID智能門禁系統(tǒng)(RFID,12864,AT24C02,步進電機......)

    目錄 門禁系統(tǒng) 需要的器件 設計思路 流程圖實現 技術實現 實物接線圖 實物接線圖 ?函數設計 部分主程序代碼,截取片段 12864顯示模塊 步進電機模塊以及蜂鳴器 FRID射頻模塊 AT24C02模塊 功能實現及其源代碼 完整文件 ????????STC89C52,MFRC-522 RFID射頻模塊,AT24C02存儲電路,

    2024年02月07日
    瀏覽(22)
  • 單片機設計_自動追光系統(tǒng)、光源跟蹤系統(tǒng)(AT89C51 光敏電阻 步進電機)

    單片機設計_自動追光系統(tǒng)、光源跟蹤系統(tǒng)(AT89C51 光敏電阻 步進電機)

    想要更多項目私wo!!! ???????? 51 單片機雙軸自動追光系統(tǒng)主要由 STC89C52RC + 5516 光敏電阻 + ADC0832 + ULN2803 + 步進電機 + LCD1602 顯示屏組成。 ????????1.通過子電路板的上、下、左、右四個光敏電阻來感受四個方向的光強,自 動尋找光強最強的方向。四個光敏電阻的分壓電

    2024年02月11日
    瀏覽(28)
  • 51單片機STC15W4K56S4控制步進電機28BYJ-48正反轉

    51單片機STC15W4K56S4控制步進電機28BYJ-48正反轉

    步進電機28BYJ-48(12V)介紹: ? ? ? 首先,我們看下步進電機28BYJ-48(12V)外觀圖,如下: ? ? ? ? 28BYJ-48(12V)含義如下:28表示電機直徑28毫米,B表示電機?,Y表示永磁,J表示帶減速箱,48表示四相八拍。 ? ? ? 下面解釋下“4 相永磁式”的概念,28BYJ-48 的構造如下圖所示

    2023年04月17日
    瀏覽(25)
  • 【C語言】51單片機四線雙極性步進電機啟動、停機、正反轉、加減速(中斷實現)數碼管顯示速度

    【C語言】51單片機四線雙極性步進電機啟動、停機、正反轉、加減速(中斷實現)數碼管顯示速度

    ? 一、設計目標 ? ? 設計程序實現按鍵控制步進電機啟動、停機、正反轉、加減速、轉速及轉向顯示。 二、主要功能 ? ? 功能1:步進電機的啟動、重啟、停機; ? ? 功能2:步進電機正反裝; ? ? 功能3:靜態(tài)數碼管顯示速度等級; ? ? 功能4:步進電機的加減速; 三、硬

    2024年02月01日
    瀏覽(26)
  • 【附報告及視頻】51單片機四線雙極性步進電機啟動、停機、正反轉、加減速(中斷實現)數碼管顯示速度

    【附報告及視頻】51單片機四線雙極性步進電機啟動、停機、正反轉、加減速(中斷實現)數碼管顯示速度

    ? 一、設計目標 ? ? 設計程序實現按鍵控制步進電機啟動、停機、正反轉、加減速、轉速及轉向顯示。 二、主要功能 ? ? 功能1:步進電機的啟動、重啟、停機; ? ? 功能2:步進電機正反裝; ? ? 功能3:靜態(tài)數碼管顯示速度等級; ? ? 功能4:步進電機的加減速; 三、硬

    2024年02月10日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包