基于80C51汇编/C语言的步进电机驱动程序

基于80C51汇编/C语言的步进电机驱动程序

这是某大学的单片机实习的相关内容:用汇编或C语言在80C51单片机上编写步进电机驱动程序。在此整理后发布。

课题要求

利用THKSCM-2单片机试验台上的各种模块,完成步进电机控制系统的设计与实现,包括硬件连接和软件设计。具体功能要求如下:

  1. 使用AT89C51单片机,控制步进电机的旋转;
  2. 使用按键控制步进电机的启动与停止:例如,电机运行时,按下某个按键,电机停转;电机停转时,按下该按键,电机开始运行;
  3. 使用按键控制步进电机的旋转方向:例如,按下某个按键后,电机旋转方向改变;
  4. 使用按键控制步进电机的旋转速度,且速度至少有三挡,速度挡位需要用七段式数码管或其他显示设备显示:例如,按下某个按键后,电机旋转速度增加,且使用串行静态显示模块展示当前速度挡位。
  5. 在上述功能的基础上,可以添加其它与步进电机控制和运转相关的功能:如使用LCD屏幕进行显示,添加按键触动声音等。

总体设计

结合THKSCM-2单片机的实际情况,设计并实现了如下图所示的步进电机控制系统,支持5级速度调节和正反转调节,可以随时启动或者停止。电机的速度等级、启动/停止状态和旋转方向显示于串行静态显示模块。系统总体设计结构图如下:

在这里插入图片描述
设计其操作逻辑如下:

  1. 启动时,电机静止;
  2. 按下K0按钮,电机启动,再次按下K0按钮,电机停止,显示器内容改变,扬声器发出中频声音;
  3. 按下K1按钮,电机旋转方向改变,显示器内容改变,扬声器发出中频声音。无论电机旋转或静止,该项功能总是可用;
  4. 按下K2按钮,电机旋转速度增加(最多到5级),显示器内容改变,扬声器发出高频声音。无论电机旋转或静止,该项功能总是可用;
  5. 按下K3按钮,电机旋转速度降低(最少到1级),显示器内容改变,扬声器发出低频声音。无论电机旋转或静止,该项功能总是可用;
  6. 第k级转速约为75/(6-k) r/min。

硬件设计

为了实现总体方案中的各项功能,我们使用了THKSCM-2实验装置上的AT89C51仿真器模块、键盘模块、串行静态显示模块、音频驱动模块和步进电机模块。

  1. 单片机晶振:采用内部震荡电路;
  2. 单片机复位:采用按键复位电路;
  3. 键盘:采用独立式键盘,软件去抖动;
  4. 音频驱动:根据试验台上所具有的设备,我们采用电动式扬声器作为发声设备。
  5. 显示设备:为了节约外部接口,我们选择占用端口最少的串行静态显示进行显示工作。
  6. 步进电机驱动
    步进电动机须以脉冲电流来驱动。若以每圈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 21610×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=6Ls,其中 L s L_s Ls表示速度等级, L s ∈ 1 , 2 , 3 , 4 , 5 L_s∈{1,2,3,4,5} Ls1,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);
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值