用单片机按键控制步进电机转动的程序

来源:网络  作者:网络转载   2019-09-26 阅读:243

结合按键程序,我们设计这样一个功能程序:按数字键1~9,控制电机转过1~9圈;配合上下键改变转动方向,按向上键后正向转1~9圈,向下键则反向转1~9圈;左键固定正转90度,右键固定反转90;Esc键终止转动。通过这个程序,我们也可以进一步体会到如何用按键来控制程序完成复杂的功能,以及控制和执行模块之间如何协调工作,而你的编程水平也可以在这样的实践练习中得到锻炼和提升。

#include

sbitKEY_IN_1=P2^4;

sbitKEY_IN_2=P2^5;

sbitKEY_IN_3=P2^6;

sbitKEY_IN_4=P2^7;

sbitKEY_OUT_1=P2^3;

sbitKEY_OUT_2=P2^2;

sbitKEY_OUT_3=P2^1;

sbitKEY_OUT_4=P2^0;

unsignedcharcodeKeyCodeMap[4][4]={//矩阵按键编号到标准键盘键码的映射表

{0x31,0x32,0x33,0x26},//数字键1、数字键2、数字键3、向上键

{0x34,0x35,0x36,0x25},//数字键4、数字键5、数字键6、向左键

{0x37,0x38,0x39,0x28},//数字键7、数字键8、数字键9、向下键

{0x30,0x1B,0x0D,0x27}//数字键0、ESC键、回车键、向右键

};

unsignedcharKeySta[4][4]={//全部矩阵按键的当前状态

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}

};

signedlongbeats=0;//电机转动节拍总数

voidKeyDriver();

voidmain(){

EA=1;//使能总中断

TMOD=0x01;//设置T0为模式1

TH0=0xFC;//为T0赋初值0xFC67,定时1ms

TL0=0x67;

ET0=1;//使能T0中断

TR0=1;//启动T0

while(1){

KeyDriver();//调用按键驱动函数

}

}

voidStartMotor(signedlongangle){

//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误

EA=0;

beats=(angle*4076)/360;//实测为4076拍转动一圈

EA=1;

}

voidStopMotor(){

EA=0;

beats=0;

EA=1;

}

voidKeyAction(unsignedcharkeycode){

staticbitdirMotor=0;//电机转动方向

//控制电机转动1-9圈

if((keycode>=0x30)&&(keycode<=0x39)){

if(dirMotor==0){

StartMotor(360*(keycode-0x30));

}else{

StartMotor(-360*(keycode-0x30));

}

}elseif(keycode==0x26){//向上键,控制转动方向为正转

dirMotor=0;

}elseif(keycode==0x28){//向下键,控制转动方向为反转

dirMotor=1;

}elseif(keycode==0x25){//向左键,固定正转90度

StartMotor(90);

}elseif(keycode==0x27){//向右键,固定反转90度

StartMotor(-90);

}elseif(keycode==0x1B){//Esc键,停止转动

StopMotor();

}

}

voidKeyDriver(){

unsignedchari,j;

staticunsignedcharbackup[4][4]={//按键值备份,保存前一次的值

{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}

};

for(i=0;i<4;i++){//循环检测4*4的矩阵按键

for(j=0;j<4;j++){

if(backup[i][j]!=KeySta[i][j]){//检测按键动作

if(backup[i][j]!=0){//按键按下时执行动作

KeyAction(KeyCodeMap[i][j]);//调用按键动作函数

}

backup[i][j]=KeySta[i][j];//刷新前一次的备份值

}

}

}

}

voidKeyScan(){

unsignedchari;

staticunsignedcharkeyout=0;//矩阵按键扫描输出索引

staticunsignedcharkeybuf[4][4]={//矩阵按键扫描缓冲区

{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},

{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF}

};

//将一行的4个按键值移入缓冲区

keybuf[keyout][0]=(keybuf[keyout][0]<<1)|KEY_IN_1;

keybuf[keyout][1]=(keybuf[keyout][1]<<1)|KEY_IN_2;

keybuf[keyout][2]=(keybuf[keyout][2]<<1)|KEY_IN_3;

keybuf[keyout][3]=(keybuf[keyout][3]<<1)|KEY_IN_4;

//消抖后更新按键状态

for(i=0;i<4;i++){//每行4个按键,所以循环4次

if((keybuf[keyout][i]&0x0F)==0x00){

//连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下

KeySta[keyout][i]=0;

}elseif((keybuf[keyout][i]&0x0F)==0x0F){

//连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起

KeySta[keyout][i]=1;

}

}

//执行下一次的扫描输出

keyout++;//输出索引递增

keyout=keyout&0x03;//索引值加到4即归零

//根据索引,释放当前输出引脚,拉低下次的输出引脚

switch(keyout){

case0:KEY_OUT_4=1;KEY_OUT_1=0;break;

case1:KEY_OUT_1=1;KEY_OUT_2=0;break;

case2:KEY_OUT_2=1;KEY_OUT_3=0;break;

case3:KEY_OUT_3=1;KEY_OUT_4=0;break;

default:break;

}

}

voidTurnMotor(){

unsignedchartmp;//临时变量

staticunsignedcharindex=0;//节拍输出索引

unsignedcharcodeBeatCode[8]={//步进电机节拍对应的IO控制代码

0xE,0xC,0xD,0x9,0xB,0x3,0x7,0x6

};

if(beats!=0){//节拍数不为0则产生一个驱动节拍

if(beats>0){//节拍数大于0时正转

index++;//正转时节拍输出索引递增

index=index&0x07;//用&操作实现到8归零

beats--;//正转时节拍计数递减

}else{//节拍数小于0时反转

index--;//反转时节拍输出索引递减

index=index&0x07;//用&操作同样可以实现到-1时归7

beats++;//反转时节拍计数递增

}

tmp=P1;//用tmp把P1口当前值暂存

tmp=tmp&0xF0;//用&操作清零低4位

tmp=tmp|BeatCode[index];//用|操作把节拍代码写到低4位

P1=tmp;//把低4位的节拍代码和高4位的原值送回P1

}else{//节拍数为0则关闭电机所有的相

P1=P1|0x0F;

}

}

voidInterruptTimer0()interrupt1{

staticbitp=0;

TH0=0xFC;//重新加载初值

TL0=0x67;

KeyScan();//执行按键扫描

//用一个静态bit变量实现二分频,即2ms定时,用于控制电机

p=~p;

if(p==1){

TurnMotor();

}

}这个程序是第8章和本章知识的一个综合——用按键控制步进电机转动。程序中有这么几点值得注意,我们分述如下:

针对电机要完成正转和反转两个不同的操作,我们并没有使用正转启动函数和反转启动函数这么两个函数来完成,也没有在启动函数定义的时候增加一个形式参数来指明其方向。我们这里的启动函数voidStartMotor(signedlongangle)与单向正转时的启动函数唯一的区别就是把形式参数angle的类型从unsignedlong改为了signedlong,我们用有符号数固有的正负特性来区分正转与反转,正数表示正转angle度,负数就表示反转angle度,这样处理是不是很简洁又很明了呢?而你对有符号数和无符号数的区别用法是不是也更有体会了?

针对终止电机转动的操作,我们定义了一个单独的StopMotor函数来完成,尽管这个函数非常简单,尽管它也只在Esc按键分支内被调用了,但我们仍然把它单独提出来作为了一个函数。而这种做法就是基于这样一条编程原则:尽可能用单独的函数来完成硬件的某种操作,当一个硬件包含多个操作时,把这些操作函数组织在一起,形成一个对上层的统一接口。这样的层次化处理,会使得整个程序条理清晰,既有利于程序的调试维护,又有利于功能的扩充。

中断函数中要处理按键扫描和电机驱动两件事情,而为了避免中断函数过于复杂,我们就又分出了按键扫描和电机驱动两个函数(这也同样符合上述2的编程原则),而中断函数的逻辑就变得简洁而清晰了。这里还有个矛盾,就是按键扫描我们选择的定时时间是1ms,而本章之前的实例中电机节拍持续时间都是2ms;很显然,用1ms的定时可以定出2ms的间隔,而用2ms的定时却得不到准确的1ms间隔;所以我们的做法就是,定时器依然定时1ms,然后用一个bit变量做标志,每1ms改变一次它的值,而我们只选择值为1的时候执行一次动作,这样就是2ms的间隔了;如果我要3ms、4ms„„呢,把bit改为char或int型,然后对它们递增,判断到哪个值该归零,就可以了。这就是在硬件定时器的基础上实现准确的软件定时,其实类似的操作我们在讲数码管的时候也用过了,回想一下吧。

标签: 单片机
打赏

免责声明:
本站部份内容系网友自发上传与转载,不代表本网赞同其观点;
如涉及内容、版权等问题,请在30日内联系,我们将在第一时间删除内容!

购物指南

支付方式

商家合作

关于我们

微信扫一扫

(c)2008-2018 DESTOON B2B SYSTEM All Rights Reserved
免责声明:以上信息由相关企业或个人自行免费发布,其真实性、准确性及合法性未证实。请谨慎采用,风险自负。本网对此不承担任何法律责任。

在线咨询

在线咨询:

QQ交流群

微信公众号