西电微机系统课程设计——步进电机开环控制系统设计
一、课程设计目的
1.掌握微机系统总线与各芯片管脚连接方法,提高接口扩展硬件电路的连接能力。
2.加深对 A/D 和并行接口芯片的工作方式和编程方法的理解。
3.搞懂步进电机的工作原理及控制方式,掌握开环控制系统的设计思路和实现方法。
二、课程设计的内容
手动调节电位器旋钮(0V~5V),通过 ADC0809 模拟输入水库水位 0 米~50 米,CPU收到水位信号后,根据水位高度控制步进电机(水闸)进行调节。
三、系统功能与设计要求
基本功能要求
手动调节电位器旋钮,步进电机根据水位实时调节水闸。设水闸全部打开需要逆时针旋转 10 圈 (10 x 360°)度。随着上游进入水库的水流量变化,水库水位不断变化(手动调节电位器旋钮),每到一定高度,步进电机顺时针(关)或逆时针(开)旋转一定的角度调节水闸开启程度,从而控制水库水位在 10~50 米之间。调节精度控制在±5%,调节规律如下:
步进电机采用四相八步控制,开关顺序如图:
步进电机不使用时要关闭,不可以停止在一个相位上时间过长,会导致发热;
发挥部分
1.增加速度调节功能。水位在 10~40 米期间,步进电机中速转动,水位低于 10 (水位过低)或高于 40 米(水位过高)时,步进电机高速转动。
2.增加实时水位显示。用数码管 DLED 高两位显示当前水位(00~50 米)。
3.增加水闸开启程度显示。用数码管 DLED 低两位实时显示水闸开启程度(00~10圈)。
四、操作步骤
- 首先根据题目要求选择合适的器件并参考示例代码了解各器件的操作方法:
器件名称 | 功能 |
---|---|
电位器 | 获取0~5V的模拟量 |
ADC0809 | 模拟量转换为数字量 |
步进电机 | 模拟水闸转动 |
数码管 | 显示必要参数 |
LED | 根据水位亮不同的灯 |
74HC273N | 锁存器,锁存控制LED的信号 |
系统总线 | 控制其他器件 |
8255 | 将系统总线的命令传输到步进电机和数码管 |
- 阅读硬件接口手册,粗略连接各器件,并加部分的流程控制,画出大致的流程控制图:
- 对于一些实验要求的分析:
- 由于水位可能下降也可能上升,所以步进电机要记录历史状态,根据上一次水位信息来判断正转和反转;
- 步进电机的速度控制通过延时不同时间来实现,延时函数可以粗略地使用空循环;
- 数码管水位信息应该随步进电机旋转动态变化,所以应该放入步进电机旋转的函数中不停得对数码管进行刷新以实现;
- 步进电机的每次旋转停下之后的相位应该被记录,下一次旋转应该在上一次停下的相位进行继续驱动,否则会出现旋转开始时倒转的现象;
- 步进电机正转就是根据控制图由上到下循环
out
给步进电机的控制端口,同样反转是由下到上循环out
给步进电机的控制端口;由于步进角我们并不清楚,所以采用测验来大致确定步进角,测验结果为步进电机运行40步为旋转一圈,在代码实现中,可以通过acircle
这个全局变量来修改旋转一圈的步数;
五、硬件连线图
六、代码实现
/*
步进电机开环控制系统设计:
电位器功能描述:
调节电位器旋钮(0~5V)作为水位;
动态获取模拟量并转换为数字量显示在数码管上;
4s左右时间不旋转电位器则停止采样,进入控制水闸功能模块;
8位数码管功能描述:
数码管低2位显示水位(m);
数码管高2位显示水闸开启程度,用百分比表示;
数码管4(中间位置)显示旋转速度,1表示中速,2表示高速;
步进电机功能描述:
[0,10)m和[40,50]m水位时,高速旋转,每转一步60ms延时;
[10,40)m时,中速旋转,每转一步160ms延时;
可以根据历史水闸开启状态来关闸或开闸(即逆时针旋转和顺时针旋转);
LED管功能描述:
[0,10)m水位时,黄灯闪烁;
[10,50)m水位时,绿灯常亮;
50m水位时,红灯闪烁;
*/
#define u8 unsigned char
#define u16 unsigned int
/* 接口地址定义 */
#define IO8255_PC 0x272 //8255 C口地址;
#define IO8255_Con 0x273 //8255 控制字地址;
#define addr0809 0x250 //0809 地址;
#define IO273 0x230 //74LS273地址;
/* 库函数定义 */
extern void outportb( unsigned int, char); //写I/O;
extern char inportb( unsigned int ); //读I/O;
extern void cInitKeyDisplay(); //对键盘、数码管管理器初始化;
//将pBuffer指向的8字节缓冲区内容显示于F5区数码管上;
extern void cDisplay8(u8* pBuffer);
u8 buffer[8]; //用于显示数码管缓冲区,8个字节;
u8 SpeedNo; //选择哪一级速度;
u8 StepDelay; //转动一步后,延时常数;
u16 StepCount; //转动步数;
//传给步进电机的值(四相八步);
u8 StepControl[8]={0x11, 0x99, 0x88, 0xcc, 0x44, 0x66, 0x22, 0x33};
u8 WaterHight = 0xff; //水位;
u8 WaterHight1 = 0xff; //WaterHight1的副本;
u16 adResult = 0; //电位器读取的值;
u16 lastResult = 0xff; //电位器上次读取的值;
u8 Steptemp = 0; //步进电机的历史状态(上次旋转之后位于哪步);
u8 hight = 0; // 水闸开启程度为hight%;
u8 acircle = 40; //转1圈要步进电机需要走的总步数;
u8 adtemp;
u8 count = 0;
u8 i = 0;
/* 刷新数码管,并延时 */
void delayBuffer()
{
u16 count = 10;
while (count--)
{
cDisplay8(buffer); //刷新数码管1次;
}
}
/* 延时函数 */
void delay(u16 ms)
{
u16 i;
while(ms--)
{
i = 100;
do
{;}while(--i);
}
}
/* 读AD0809数据 */
u8 ad0809()
{
u8 i = 100;
outportb(addr0809, 0x0);
while (i--)
{;} //延时,等待AD转换完成;
return inportb(addr0809);
}
/* 接收到电位器旋转角度变化,更新数码管 */
void display_data(u16 adResult)
{
buffer[5] = 0x10;
buffer[1] = adResult / 51; //255/51 (16进制的1 = 1/51V) ,十位;
adResult = (adResult % 51) * 10;
buffer[0] = adResult / 51; //个位;
buffer[2] = 0x10;
buffer[3] = 0x10; //0x10用于消隐,即数码管不显示;
}
/* 初始化8255 */
void Init8255()
{
outportb(IO8255_Con,0x80); //写8255控制字,无条件的数据传输方式,A、B、C口都单向输出;
outportb(IO8255_PC, 0xff); //0FFH->8255 PC,写C口;
}
/* 刷新LED灯:
当水位为[10,50)时,绿灯亮;
当水位为[0,10)时黄灯闪烁;
当水位为50时,红灯闪烁
*/
void Testled(){
/* 此处用WaterHight的副本WaterHight1作为判断而不用WaterHight的原因是:
WaterHight在电压器不变而水闸已经运动结束的情况下会被置为0xff,
0xff并不能表示水位信息,即LED无法刷新;
WaterHight1作为WaterHight被置为0xff之前的副本,可以一直记录当前水位用于LED刷新;
*/
if(WaterHight1>100){
WaterHight1 = 0xff; //水位不正常,不显示LED;
}
else if(WaterHight1 >= 50){
outportw(IO273, 0xef); //红灯;
delay(20);
outportw(IO273, 0xff);
}
else if(WaterHight1 >= 10){
outportw(IO273, 0xbf); //绿灯;
}
else{
outportw(IO273, 0xdf); //黄灯;
delay(20);
outportw(IO273, 0xff);
}
}
/* 实现步进电机旋转的函数,参数:需要转动的圈数 */
void StepCircle(u8 circleN){
u8 rate = 1;
u8 htemp = 0;
if(circleN >= 8){rate = 0;} //需要转10圈,即水位到了40以上,需要快速转动;
if(circleN == 0){rate = 0;} //需要转0圈,即水位到了10以下,需要快速转动;
/* 此处要解决步进电机的正转和反转问题;
buffer[7]中存储的水闸的开启程度,而circleN代表假设水闸开启程度为0时,需要转多少圈;
buffer[7]可以视作水闸已经转了多少圈;
circleN减buffer[7]即代表还需要转多少圈;
结果为负数代表需要关闸,步进电机逆时针转;
结果为正数代表需要开闸,步进电机顺时针转;
*/
if(circleN <= buffer[7]){
//关闸,逆时针转;
circleN = buffer[7] - circleN;
while(circleN){
u8 control = 0;
for(i = acircle; i > 0; i--){
control = (Steptemp+i)%8; //读取历史状态,根据历史状态旋转步进电机;
delay(rate*100); //速度控制;
buffer[4] = 2 - rate; //速度显示;
outportb(IO8255_PC, StepControl[control]); //步机旋转;
if(i%(acircle/10) == 0){
//转1圈水闸变化10%,一圈要旋转acircle次,acircle/10次水闸开启1%;
hight--;
buffer[7]=hight/10;
buffer[6]=hight%10;
delayBuffer(); //刷新显示水闸开启程度;
}
htemp = i;
Testled();
delay(40); //前面最少delay(20),即最小总延时为delay(60);最大总延时为delay(160);
}
circleN--; //需要转的圈数减一;
}
}
else{
//开闸,顺时针转;
circleN = circleN - buffer[7];
while(circleN){
u8 control = 0;
for(i = 0; i < acircle; i++){
control = (Steptemp+i)%8;
delay(rate*100);
buffer[4] = 2 - rate;
outportb(IO8255_PC, StepControl[control]);
if(i%(acircle/10) == 0){
hight++;
buffer[7]=hight/10;
buffer[6]=hight%10;
delayBuffer();
}
htemp = i;
Testled();
delay(40);
}
circleN--;
}
}
Steptemp = htemp;
}
void main()
{
Init8255(); //8255初始化;
cInitKeyDisplay(); //键盘,数码管初始化;
while (1)
{
count = 0;
adtemp = 0;
buffer[4] = 0; //步进电机停止转动,速度为0,显示出来;
//采样时长为4s,如果4s内电压值不变化,则采样结束;
while(1){
adResult = ad0809(); //获取电压值;
display_data(adResult); //刷新数码管;
if(adResult == adtemp){ //adResult不等于adtemp时,说明电压旋钮在旋转;
count++ ;
}
else{
adtemp = adResult;
}
if(count == 4){
break; //四次采样都相等时,认为电压不再改变,退出采样循环;
}
delayBuffer(); //电压旋钮旋转时数码管刷新水位,显示到数码管;
Testled();
delay(1000); //延时,保证采样过程不会太快;
}
if (lastResult != adResult) //与历史版本比较,只有电压变化时才会重新设置水位;
{
lastResult = adResult; //记录新电压值的副本作为历史版本;
WaterHight = buffer[1]*10 + buffer[0]; //更新水位;
if(WaterHight1 == WaterHight){
WaterHight = 0xff;
}
else{
WaterHight1 = WaterHight; //保存水位的副本作为历史版本;
}
}
delayBuffer();
Testled();
//根据水位来调整水闸,一旦调整水闸完成,WaterHight将暂时失效(即如果电压值不变,则不能再用于控制水闸);
if(WaterHight>100){
WaterHight = 0xff;
}
else if(WaterHight>=50){
StepCircle(10); //水位等于50时,水闸需要开启到100%,转10圈;
WaterHight = 0xff;
}
else if(WaterHight>=40){
StepCircle(8);
WaterHight = 0xff;
}
else if(WaterHight>=30){
StepCircle(6);
WaterHight = 0xff;
}
else if(WaterHight>=20){
StepCircle(4);
WaterHight = 0xff;
}
else if(WaterHight>=10){
StepCircle(2);
WaterHight = 0xff;
}
else{
StepCircle(0);
WaterHight = 0xff;
}
}
}
七、心得体会
通过本次课设,我了解了CPU如何控制各器件实现一个简单的系统;
在实验中也有一些不足:可能是由于各个分支的延时不同,也可能是对四相八步理解不到位,步进电机旋转的速度并不是恒定的;此外我们没有使用8259中断来使输入和步进电机的旋转互不干扰,而是采用了不停地定时检测来确保在旋转按钮时步进电机不会旋转,所以反应速度比较慢,延时比较长;
对于操作系统学习的OS通过读写文件控制硬件,以及微机原理学习的各种芯片功能使用等,都通过本次实验得到了更深刻的理解;
踩坑点:
星研软件编译链接完要点全速运行(一个小感叹号)才能在板子上运行;
步进电机的示例代码有误,中断处理函数TIMERO
中if(--StartStepDelay)
应该改成--StartStepDelay; if(StartStepDelay)
(老师说的,没验证),如果不改就会有一个警告,即使没错误只要有警告就无法在板子上下载运行;