基于80C51汇编/C语言的步进电机驱动程序
这是某大学的单片机实习的相关内容:用汇编或C语言在80C51单片机上编写步进电机驱动程序。在此整理后发布。
课题要求
利用THKSCM-2单片机试验台上的各种模块,完成步进电机控制系统的设计与实现,包括硬件连接和软件设计。具体功能要求如下:
- 使用AT89C51单片机,控制步进电机的旋转;
- 使用按键控制步进电机的启动与停止:例如,电机运行时,按下某个按键,电机停转;电机停转时,按下该按键,电机开始运行;
- 使用按键控制步进电机的旋转方向:例如,按下某个按键后,电机旋转方向改变;
- 使用按键控制步进电机的旋转速度,且速度至少有三挡,速度挡位需要用七段式数码管或其他显示设备显示:例如,按下某个按键后,电机旋转速度增加,且使用串行静态显示模块展示当前速度挡位。
- 在上述功能的基础上,可以添加其它与步进电机控制和运转相关的功能:如使用LCD屏幕进行显示,添加按键触动声音等。
总体设计
结合THKSCM-2单片机的实际情况,设计并实现了如下图所示的步进电机控制系统,支持5级速度调节和正反转调节,可以随时启动或者停止。电机的速度等级、启动/停止状态和旋转方向显示于串行静态显示模块。系统总体设计结构图如下:
设计其操作逻辑如下:
- 启动时,电机静止;
- 按下K0按钮,电机启动,再次按下K0按钮,电机停止,显示器内容改变,扬声器发出中频声音;
- 按下K1按钮,电机旋转方向改变,显示器内容改变,扬声器发出中频声音。无论电机旋转或静止,该项功能总是可用;
- 按下K2按钮,电机旋转速度增加(最多到5级),显示器内容改变,扬声器发出高频声音。无论电机旋转或静止,该项功能总是可用;
- 按下K3按钮,电机旋转速度降低(最少到1级),显示器内容改变,扬声器发出低频声音。无论电机旋转或静止,该项功能总是可用;
- 第k级转速约为75/(6-k) r/min。
硬件设计
为了实现总体方案中的各项功能,我们使用了THKSCM-2实验装置上的AT89C51仿真器模块、键盘模块、串行静态显示模块、音频驱动模块和步进电机模块。
- 单片机晶振:采用内部震荡电路;
- 单片机复位:采用按键复位电路;
- 键盘:采用独立式键盘,软件去抖动;
- 音频驱动:根据试验台上所具有的设备,我们采用电动式扬声器作为发声设备。
- 显示设备:为了节约外部接口,我们选择占用端口最少的串行静态显示进行显示工作。
- 步进电机驱动
步进电动机须以脉冲电流来驱动。若以每圈20个励磁信号来计算,则每个励磁信号前进18°,其旋转角度与脉冲个数成正比。正、反转可由脉冲顺序来控制。
步进电动机的励磁方式可分三种:
(1) 1相励磁法:同一时刻只有一个线圈导通。消耗电力小,精确度良好,但转矩小,振动较大,每送一励磁信号可走18度。励磁顺序为A→B→C→D→A(正转)。
(2) 2相励磁法:同一时刻有二个线圈同时导通。因其转矩大,振动小,故为目前使用最多的励磁方式,每送一励磁信号可走18度。励磁顺序为AB→BC→CD→DA→AB(正转)。
(3) 1-2相励磁法:为1相与2相轮流交替导通。每送一励磁信号可走9度,分辨率提且运转平滑,亦被广泛采用。励磁顺序为A→AB→B→BC→C→CD→D→DA→A(正转)。
为使步进电动机反转,只要将励磁信号反向传送即可。
典型步进电机驱动电路如下图所示:
软件设计
为实现上述功能,我们采用80C51的汇编语言编写程序,模块化的实现了步进电机控制、键盘扫描等功能。整体架构为:以定时器中断控制步进电机,以查询式方法进行按键检测。
步进电机控制逻辑
步进电机的控制在定时器0的中断中实现。定时器固定10ms触发一次,在方式1下,其定时常数为
2
16
−
10
×
1000
=
55536
=
(
D
8
F
0
)
H
2^{16}-10×1000=55536=(D8F0)_H
216−10×1000=55536=(D8F0)H。变量
T
c
T_c
Tc用于指示步进周期:用计数变量
C
i
C_i
Ci记录进入中断的次数,每当
C
i
C_i
Ci=
T
c
T_c
Tc触发一次电机步进动作,同时
C
i
C_i
Ci清零。通过控制
T
c
T_c
Tc即可达到调速的目的:
T
c
T_c
Tc越大,周期越长,转速越慢;
T
c
T_c
Tc越小,周期越短,转速越快。速度等级和
T
c
T_c
Tc的换算关系是:
T
c
=
6
−
L
s
T_c=6-L_s
Tc=6−Ls,其中
L
s
L_s
Ls表示速度等级,
L
s
∈
1
,
2
,
3
,
4
,
5
L_s∈{1,2,3,4,5}
Ls∈1,2,3,4,5。
每次步进的动作,实质上是一个查表指针
P
s
t
e
p
P_{step}
Pstep的前移/后移(由旋转方向决定)。考虑到需要循环移动,需要特判当前指针的位置是否在边界上,如果是,则要移动到另一个边界上。
键盘控制逻辑
键盘检测在主程序中循环查询实现。键盘连接到单片机P2口,通过相隔20ms的两次读取消除抖动。根据ACC中位的情况,执行对应的操作。操作执行完毕后,设置音调和显存,刷新显示并播放按键按下时的声音。上述操作循环运行。
声音播放逻辑
声音播放的表现为电平震荡。某一时刻电平振荡频率高,则音调高;电平振荡频率低,则音调低。其运行逻辑如下:
显示逻辑
显示输出则是经典的串行静态显示输出。将要显示的内容逐位由DIN口输入,同时配一个CLK时钟波形。其运行逻辑如下:
启动逻辑
程序启动时,需要设置时间常数、速度等级和显存中的初始内容,并刷新一次显示,最后开启定时中断,进入主循环。
源代码
汇编语言
; This program drives a step motor which could be controlled by four buttons.
; When pressing on the buttons, the buzzer will beep once.
; The following connecting method is specific to THKSCM-2 experiment equipment.
; B2 key board: K0 == P2.0; K1 == P2.1; K2 == P2.2; K3 == P2.3
; C2 audio board: SP- == GND; SP+ == P2.5
; I3 serial static display board: DIN == P2.6, CLK == P.27
; M2 step motor board: SA == P1.1, SB == P1.2, SC == P1.3, SD == P1.4, noting that not using P1.0 is due to the malfunction of P1.0
motor_speed equ 40h
motor_delay equ 41h
motor_step equ 4ah
counter equ 4bh
motor_mode bit 10h
;Display buffer
disp0 equ 42h
disp1 equ 43h
disp2 equ 44h
disp3 equ 45h
disp4 equ 46h
disp5 equ 47h
disp6 equ 48h
disp7 equ 49h
;Character code (index in table, starting from 00h)
chr_f equ 0fh
chr_o equ 12h
chr_n equ 13h
chr_r equ 14h
chr_empty equ 10h
;Audio pin
beep_freq equ 4ch
music bit P2.5
;Serial static display pins
din bit P2.6
clk bit P2.7
;Temporary storage
tmp_bit bit 11h
tmp equ 51h
tmp2 equ 52h
org 0000h
ljmp main
org 000bh
ljmp int_proc
org 0200h
step_table: ; This table indicates how to drive the step motor.
db 00000010b,00000110b,00000100b,00001100b
db 00001000b,00011000b,00010000b,00010010b
digit_table: ; This table describes the form of the characters on the 7-segment digital display
DB 3FH,06H,5BH,4FH,66H,6DH
DB 7DH,07H,7FH,6FH,77H,7CH
DB 39H,5EH,7BH,71H,00H,40H
DB 01011100b,01010100b,01010000b
clear_disp_buff: ;This procedure will clear the display buffer.
mov disp0,#chr_empty
mov disp1,#chr_empty
mov disp2,#chr_empty
mov disp3,#chr_empty
mov disp4,#chr_empty
mov disp5,#chr_empty
mov disp6,#chr_empty
mov disp7,#chr_empty
ret
disp_once: ;This procedure will flush the display buffer to the display.
mov R0,#disp0
mov R1,#tmp2
mov R2,#8
__DP10:
mov DPTR,#digit_table
mov A,@R0
movc A,@A+DPTR
mov @R1, A
inc R0
inc R1
djnz R2,__DP10
mov R0, #tmp2
mov R1, #8
__DP12:
mov R2,#8
mov A,@R0
__DP13:
rlc A
mov din,C
clr clk
setb clk
djnz R2,__DP13
inc R0
djnz R1,__DP12
ret
int_proc: ; This procedure processes the T0 interrupt.
push ACC
inc counter
mov A,counter
cjne A,motor_delay,__int_go_ret
lcall motor_step_next
mov counter,#0
__int_go_ret:
mov TH0,#0d8h
mov TL0,#0f0h
pop ACC
reti
motor_step_next: ; This procedure controls the motion of step motor.
mov DPTR,#step_table
mov A,motor_step
movc A,@A+DPTR
mov P1,A
jnb motor_mode, __motor_step_next_pos ;0 = clockwise, 1 = anti-clockwise
__motor_step_next_neg:
mov A,motor_step
cjne A,#0,__motor_step_next_beg_if
mov motor_step,#7
ret
__motor_step_next_beg_if:
dec motor_step
ret
__motor_step_next_pos:
inc motor_step
mov A,motor_step
cjne A,#8,__motor_step_next_ret
mov motor_step,#0
__motor_step_next_ret:ret
set_step_speed: ; After setting step_speed, you should call this procedure to automatically calculate the desired pause between two alternations of the step motor driver.
push ACC
push PSW
mov C,TR0
mov tmp_bit,C
clr EA
mov A,motor_speed
mov disp0,A
lcall disp_once
mov counter,#0
mov A,#6
subb A,motor_speed
mov motor_delay,A
setb EA
mov C,tmp_bit
mov TR0,C
pop PSW
pop ACC
ret
spd_add: ; Accelerate for 1 level.
mov R2,motor_speed
cjne R2,#5,__spd_add_do
ret
__spd_add_do:
inc motor_speed
lcall set_step_speed
ret
spd_dec: ; Decelerate for 1 level.
mov R2,motor_speed
cjne R2,#1,__spd_dec_do
ret
__spd_dec_do:
dec motor_speed
lcall set_step_speed
ret
delay: ; Halt for a while
mov R1,#200
__delay_d1:
mov R2,#200
djnz R2,$
djnz R1,__delay_d1
ret
play_music: ; Halt for a while, and beep once at the same time, frequency of the beep should be stored at R3
setb music
mov R1,#200
__music_d1:
cpl music
mov R2,beep_freq
djnz R2,$
djnz R1,__music_d1
clr music
ret
kb_scan: ; Keyboard procedure
mov A,P2
lcall delay
mov B,P2
cjne A,B,__kb_scan_ret
jnb ACC.0, __kb_scan_k0
jnb ACC.1, __kb_scan_k1
jnb ACC.2, __kb_scan_k2
jnb ACC.3, __kb_scan_k3
__kb_scan_ret:ret
__kb_scan_k0: ; start/stop
cpl TR0
jnb TR0,__kb_scan_stop
MOV disp2,#chr_o
MOV disp3,#chr_n
MOV disp4,#chr_empty
sjmp __kb_scan_k0_endif
__kb_scan_stop:
MOV disp2,#chr_o
MOV disp3,#chr_f
MOV disp4,#chr_f
__kb_scan_k0_endif:
LCALL disp_once
mov beep_freq,#200
sjmp __finn
__kb_scan_k1: ; alter the rotation direction: clockwise/anti-clockwise
cpl motor_mode
JB motor_mode,FOR_SHOW
MOV disp6,#chr_f
JMP SHOW_COMPLETE
FOR_SHOW:
MOV disp6,#chr_r
SHOW_COMPLETE:
lcall disp_once
mov beep_freq,#200
sjmp __finn
__kb_scan_k2: ;accelerate
lcall spd_add
mov beep_freq,#150
sjmp __finn
__kb_scan_k3: ;decelerate
lcall spd_dec
mov beep_freq,#250
sjmp __finn
__finn:
lcall play_music
ret
org 1000h
main:
;initialize interrupt
clr motor_mode
lcall clear_disp_buff
mov TMOD,#01h
mov TH0,#0d8h
mov TL0,#0f0h
setb ET0
setb EA
setb TR0
;set inital speed level
mov motor_speed,#3
mov disp2,#chr_o
mov disp3,#chr_n
mov disp6,#chr_f
lcall set_step_speed
;main loop
__main_loop:
lcall kb_scan
sjmp __main_loop
end
C语言
/**************************
* THKSCM-2试验台:步进电机程序
* 硬件连接:
* B2板: K0 == P2.0; K1 == P2.1; K2 == P2.2; K3 == P2.3
* C2板: SP- == GND; SP+ == P2.5
* I3板: DIN == P2.6, CLK == P.27
* M2板: SA == P1.1, SB == P1.2, SC == P1.3, SD == P1.4
**************************/
#include "reg52.h"
typedef unsigned char BYTE;
/**************************
* 显示部分
**************************/
sbit DISP_DIN=P2^6; //串行静态显示DIN引脚
sbit DISP_CLK=P2^7; //串行静态显示CLK引脚
//字库,只写了0~9,a~f,r,o,n
BYTE CHARS_D[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//0~9
BYTE CHARS_A[]={0x77,0x7c,0x39,0x5e,0x7b,0x71,0x00,0x74,0x04,0x16, //a~j,g不支持
0x00,0x38,0x00,0x54,0x5c,0x73,0x77,0x50,0x00,0x00, //k~t,k,m,s,t不支持
0x1c,0x00,0x00,0x00,0x6e,0x00}; //u~z,v,w,x,z不支持
BYTE DISP_BUFF[8]; //输出缓冲
//设置某一位的字符
void set_buff_at(int pos,char x){
if(x>='0' && x<='9') DISP_BUFF[pos]=CHARS_D[x-'0'];
else if(x>='a' && x<='z') DISP_BUFF[pos]=CHARS_A[x-'a'];
else if(x>='A' && x<='Z') DISP_BUFF[pos]=CHARS_A[x-'A'];
else if(x=='.') DISP_BUFF[pos]=0x80;
else if(x=='-') DISP_BUFF[pos]=0x40;
else DISP_BUFF[pos]=0x00;
}
//设置从某一位的字符串
void set_buff_from(int i,char buff[]){
int j=0;
for(;i<8 && buff[j]!='\0';++i,++j){set_buff_at(i,buff[j]);}
}
//清空输出缓冲
void clear_disp_buff(){
int i=0;
for(;i<8;++i){DISP_BUFF[i]=0x00;}
}
//将缓冲区的内容输出到硬件
void flush_disp_buff(){
BYTE i,j,tmp;
for(i=0;i<8;++i){
tmp=DISP_BUFF[i];
for(j=0;j<8;++j){
DISP_DIN=tmp&0x80;
DISP_CLK=0;
DISP_CLK=1;
tmp<<=1;
}
}
}
/**************************
* 步进电机驱动部分
**************************/
int MOTOR_SPD,MOTOR_DELAY,MOTOR_STEP,MOTOR_MODE,COUNTER;
BYTE STEPS[]={0x02,0x06,0x04,0x0C,0x08,0x18,0x10,0x12};
//设置速度等级:1~5,1最慢,5最快
void set_motor_speed(int spd){
if(spd<=0) spd=1;
if(spd>5) spd=5;
MOTOR_SPD=spd;
set_buff_at(0,spd+'0');
MOTOR_DELAY=((6-MOTOR_SPD)<<1)+1;
COUNTER=0;
}
//切换到步进电机的下一个状态
void motor_next(){
P1=STEPS[MOTOR_STEP];
if(MOTOR_MODE){
++MOTOR_STEP;
if(MOTOR_STEP==8) MOTOR_STEP=0;
}else{
if(MOTOR_STEP==0) MOTOR_STEP=7;
else --MOTOR_STEP;
}
}
/**************************
* 蜂鸣器部分
**************************/
void delay_ms(unsigned int x){
while(x--){int y=98;while(y--);}
}
void delay_us(unsigned int x){
x>>=2;while(x--);
}
sbit BEEP_PIN=P2^5; //指定蜂鸣器用引脚
//函数:beep(持续时间ms,声音频率hz)
void beep(unsigned int time_ms, unsigned int freq_hz){
int interval_us=1000000/freq_hz;
int cnt=time_ms*1000/interval_us-1;
while(cnt--){
delay_us(interval_us);
BEEP_PIN=~BEEP_PIN;
}
delay_us(interval_us);
BEEP_PIN=1;
}
/**************************
* 定时器部分
**************************/
BYTE __TH0,__TL0;
#define RESET_TIMER0 (TH0=__TH0,TL0=__TL0)
void set_timer0_peroid(int time_us){
int con=65536-time_us;
__TH0=con&0xf0;
__TL0=con&0x0f;
RESET_TIMER0;
}
void start_timer0(){
EA=1;TR0=1;ET0=1;
}
//定时器中断
void timer0_int() interrupt 1{
++COUNTER;
if(COUNTER>=MOTOR_DELAY){
COUNTER=0;
motor_next();
}
RESET_TIMER0;
}
/**************************
* 主函数
**************************/
//判断某键是否按下
#define is_key_down(kval,bit) (!((kval)&(1<<(bit))))
void main(){
//初始化
clear_disp_buff();
set_motor_speed(3);
set_buff_from(2,"on");
set_buff_at(6,'f');
flush_disp_buff();
set_timer0_peroid(20000);
start_timer0();
while(1){
BYTE t=P2;int tone_freq=2000;
delay_ms(50);
if(P2!=t){
delay_ms(50);
continue;
}
if(is_key_down(t,0)){ //启停
TR0=~TR0;
if(TR0){
set_buff_from(2,"on ");
}else{
set_buff_from(2,"off");
}
tone_freq=2000;
}else if(is_key_down(t,1)){ //rotation direction
MOTOR_MODE=~MOTOR_MODE;
set_buff_at(6,MOTOR_MODE?'r':'f');
tone_freq=2000;
}else if(is_key_down(t,2)){ //accelerate
set_motor_speed(MOTOR_SPD+1);
tone_freq=3000;
}else if(is_key_down(t,3)){ //decelerate
set_motor_speed(MOTOR_SPD-1);
tone_freq=1000;
}else{
delay_ms(50);
continue;
}
flush_disp_buff();
beep(100,tone_freq);
}
}