STM32F4单电梯调度系统(扫描算法)

这是第一次做嵌入式相关的东西,使用了STM32F401VE芯片,由于没有板子,只能用Proteus进行仿真,在实现过程中遇到了很多困难。

  • 修改了定时器中断内容,把调度算法放到主函数中,定时器中断只是简单修改标志位(因为中断不能做延时长的处理)
  • 项目资源链接:https://github.com/xs1317/STM32-Elevator.git
    KELL工程下:FWLIB存放了相关的库函数,HADWARE为相关硬件资源配置,USER为程序入口,OBJ中为生成的目标代码

1 系统概述

1.1 输入

  • 厢内按钮:请求到达每一层的按钮,共计5个
  • 厢外按钮:楼层呼梯按钮,第一层只有上升,第五层只有下降,其余每一层都有两个按钮,共计8个。
  • 紧急按钮:按下后电梯停止,维持状态不变。

1.2 输出

  • 电梯运行状态:用四个LED灯显示,LED0,LED1,LED2,LED3,每个灯亮分别表示停止、上升、下降、紧急状态。
  • 电梯当前楼层:用一个七段数码管显示电梯当前楼层。
  • 蜂鸣器:紧急状态下,蜂鸣器响。

1.3 调度算法

  本系统电梯调度采用改进的扫描算法。我们将各个楼层的请求称为呼梯请求(分上升和下降),将厢内请求称为到达请求。常规的扫描算法是指让电梯在最底层和最高层间往复运动,“捎带”地相应各种请求:在上升时相应各层上升呼梯请求和到达请求,在下降时相应各层下降呼梯请求和到达请求。这个算法比较直观、简单,但存在很大的问题,会造成能源的浪费和不必要的时间浪费。比如,电梯在一楼时,有人在三楼呼梯准备去一楼,此时电梯必须运行到最高层再下降到一楼,非常不合理。
  针对上述问题,做如下改进:实时记录当前所有请求的最高楼层MAX和最低楼层MIN,电梯在MAX和MIN之间往复运动,而不是在一层和最高层往复运动。这样对于一个给定的请求序列,可以使电梯运动距离最小(对于实时输入则不一定能使运动距离最小)。
  具体的算法流程和实现见第三节。

1.4 开发环境

编程语言C
编译环境Keil μVision V5
仿真环境Proteus 8.11 Professional
MCUSTMF32F401VE

2 硬件设计及说明

  总体原理图如图2.1所示。主要有LED、蜂鸣器、按键输入、七段数码管、MCU、复位电路四个部分。下面介绍各部分电路以及相关IO口的设置。

图2.1 硬件原理图

2.1 按键输入

  如图2.1右侧所示,厢外呼梯请求1U-4U,2D-5D,厢内到达请求L1-L5,连接至MCU的PD0-PD12引脚。这些引脚设置为普通输入模式,输出频率100MHz,并采用上拉输入(无信号和低电平都置为高位),这样所有请求按钮都是低电平有效。
  每个请求按钮再通过与门连接到PE0作为中断输入(图中标号EX1),按钮EX2作为紧急按钮连接到PE1,PE0与PE1也设置为普通输入、100MHz速度并设置为上拉输入。
  EX1和EX2条线分别连接到中断线1和中断线2,设置为下降沿触发,优先级分组设置为2。EX1抢断优先级为0x01,响应优先级为0x00;EX2抢断优先级0x00,响应优先级0x00。两个中断均设置为下降沿触发。

2.2 LED灯

  如图2.1左上角,LED0,LED1,LED2,LED3通过510Ω的电阻与一个4V的电源相连,然后接到引脚PA9、PA10、PA15、PA7上。四个引脚均设置为推挽输出(驱动元器件),并设置为上拉,使得无信号时LED不亮。
在Proteus仿真时,注意电阻不能太大,并适当减小LED的额定电压。

2.3 蜂鸣器

  如图2.1左下角,蜂鸣器不能直接连接到IO口,需要通过放大电路将输出电流放大。因为直接驱动蜂鸣器需要几十毫安的电流而STM32F401VE芯片单个IO口最多25mA,所以经过三极管扩流后再驱动蜂鸣器。蜂鸣器连接到PA8,设置为下拉,推挽输出。

2.4 7段数码管

  七段数码管采用了Proteus封装好的器件,如图2.1左侧中间所示,该器件将七段译码器与七段数码管封装到了一起,只需要输入4位BCD码即可显示对应数字。四位引脚从左到右代表为BCD码的高位到低位,连接到PA11~PA14线,同样也置为下拉输出。

3软件设计

3.1总体设计

  本系统通过外部中断实现按钮的输入处理,通过定时器让电梯每隔一段时间进行调度(模拟运行时延),所以主程序需要做一些初始化工作:IO接口,定时器以及初始状态设置,然后运行调度算法(由定时器控制运行时间间隔)。系统的程序框图如图3.1所示,中断处理为独立模块,不受主程序调度。各个中断应该有以下优先级:紧急中断>按键输入中断>定时器中断,这样使得紧急中断可以再任何时候暂停系统,调度算法可以及时响应实时输入。
  为了协调各个中断处理,在status.h文件中定义了几个全局变量共中断处理使用:UP[6],记录上升请求;DOWN[6],记录下降请求;MAX、MIN记录当前所有请求中的最高楼层和最低楼层;STATUS记录电梯状态,0、1、2分别表示电梯停止、上升、下降;L记录电梯当前楼层;know记录电梯当前是在执行上升序列还是下降序列;T,当T为1时,主程序执行调度算法并将T置为0,定时器中断负责将T置为1.

图3.1 系统模块图
在这里插入图片描述

## 3.2 按键输入中断处理   如第2节介绍,所有请求按钮均通过与门连接到一个中断输入,该中断处理的作用就是依次扫描所有的按钮并通过设置全局变量表示请求。对于呼梯请求和到达请求有不同的处理过程。处理流程如图3.2所示。

(1)呼梯请求:记呼梯楼层为i,若为上升呼梯则置UP[i]=1,若为下降呼梯则置DOWN[i]=1,并更新MAX和MIN。
(2)到达请求:对于达到请求i,与电梯的具体状态有关。如i>L,显然应在上升序列到达i,置UP[i]=1;若i<L,应在下降序列到达i,置DOWN[i]=1;若L==i,则电梯应再维持一段时间停止,若know=1在执行上升过程则置UP[i]=1,若know=2在执行下降过程则置DOWN[i]=1。

图3.2 请求按钮输入中断流程图

在这里插入图片描述

3.3 紧急中断处理

  紧急中断的处理比较简单 ,需注意的是为实现暂停和启动功能,定义一个静态变量stop,当stop==0时暂停系统中断并置stop==1,当stop==1时启动系统中断并置stop=0。具体流程如图3.3所示。

图3.3 紧急中断流程图

在这里插入图片描述

3.4 定时器中断处理

定时器中断只需要置变量T为1即可。

3.5主函数

  主函数运行电梯状态算法,即使得电梯状态变化有一定时间间隔。由于采用Proteus仿真对软件设计进行验证,所以定时器设置为每70ms触发一次,这样仿真时状态变化稍快一点。定时器中断抢断优先级为0x02,响应优先级为0x00,可被按键输入和紧急按钮抢断。根据当前运行在状态和请求情况进行状态变化,设置状态变量并对状态进行输出,状态转换图如图3.4所示。由于要响应实时的输入,状态转移条件较为复杂。由实际电梯运行情况可以知道,电梯若要转变运行方向必须先停在某一层,所以主要的调度体现在停止状态实现,下面详细介绍三个状态的流程。
  在设计算法之前,必须明确时钟触发的逻辑。由于中断处理过程没有时延操作,处理时间忽略,所以状态变化的时间间隔体现为两次时钟中断的时间差。也就是说,时钟中断触发后,状态立即改变,在下次时钟中断发生前维持改变后的状态。所以在每次设置STATUS改变状态的同时,要对LED、BEEP和七段数码管进行设置,使得下次时钟中断发生前输出变化后的状态,而不是进入对应状态再设置。

图3.4 系统状态转移图
在这里插入图片描述

(1)上升状态:使得当前楼层L加1,使数码管显示上升后的楼层;若UP[L]1或LMAX,说明该楼层有上升呼梯请求或到达请求,应当设置LED并进入停止状态。流程图如图3.5所示。

图3.5 上升状态流程图
在这里插入图片描述

(2) 下降状态:使得当前楼层L减1,使数码管显示下降后的楼层;若DOWN[L]1或LMIN,说明该楼层有下降呼梯请求或到达请求,应当设置LED并进入停止状态。流程图略。
(3)停止状态:主要的调度执行模块,根据全局状态变量进行状态转换、变量设置、输出设置。

  • 当前电梯执行上升扫描过程know==1:
    • UP[L]==1:说明在到达L层后,在电梯运行之前,又有到L层的请求,应维持停止状态。
    • UP[L]==0:L层的请求已被相应,考虑接下来的动作:
      • L<MAX:未达到请求的最高层,设置LED并转入上升状态。
        • 若L==MIN:则更新MIN,这是一种特殊情况,在后面进行说明。
      • L==MAX&&MAX>MIN:达到了请求的最高楼层,且MAX>MIN,还有低楼层请求,应当更新MAX,设置LED,转入下降状态并设置know=2。注意:该系统在上升扫描过程中,L不可能大于MAX,所以不需讨论。
      • L==MAX&&MAX==MIN:所有请求响应完毕,维持停止状态。
  • 电梯执行下降扫描过程know=2的过程与know=1过程类似,不再赘述。
    具体的执行流程图如图3.6所示。
    图3.6 停止状态顺序图

在这里插入图片描述

代码如下:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "status.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"
#include "timer.h"
//状态变量初始化
int UP[6]={0,0,0,0,0,0};
int DOWN[6]={0,0,0,0,0,0};
int MAX = 1;
int MIN = 5;
int STATUS = 0;
int L=1;
int know=1;
int T=0;


int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设定中断优先级组
    delay_init(168);		  //设置时延
	LED_Init();		        //初始化LED
	BEEP_Init();
	KEY_Init();
	EXTIX_Init(); 
	TIM3_Int_Init(900-1,8400-1);	//初始化定时器
	LED0 = 0;
	LED1 = 1;
	LED2 = 1;
	LED3 = 1;
	BEEP = 0;
  while(1)
  {
		 	int i;
		if(T==1) //设定时间间隔达到
		{
			
			switch(STATUS)
			{
				case 0:                      //停止状态
					
					if(know==1)                  
					{
						if(UP[L]==0)           //检查当前楼层是否有请求
						{
							if(L<MAX)
							{
								if(L==MIN)
								{
									for(i=1;i<=5;i++)
									{
										if(UP[i]==1 || DOWN[i]==1)
											MIN = i;
									}
								}
								STATUS = 1;
								LED0 = 1;
								LED1 = 0;
							}
							else if(L==MAX && MAX>MIN)        //到达最高层,维持或者下降
							{
								//更新MAX
								for(i=5;i>=1;i--)
								{
									if(UP[i]==1 || DOWN[i]==1)
										MAX = i;
								}
								LED0=1;LED2=0;
								STATUS = 2;
								know = 2;
							}
						}
						else
						{
							UP[L]=0;
						}
					}
					
					if(know==2)
					{
						if(DOWN[L]==0)
						{
							if(L>MIN)
							{
								if(L==MAX)
								{
									for(i=5;i>=1;i--)
									{
										if(UP[i]==1 || DOWN[i]==1)
											MAX = i;
									}
								}
								STATUS = 2;
								LED0=1;
								LED2=0;
							}
							else if(L==MIN && MIN<MAX)
							{
								//¸üÐÂMIN
								for(i=1;i<=5;i++)
								{
									if(UP[i]==1 || DOWN[i]==1)
										MIN = i;
								}
								LED0=1;LED1=0;
								STATUS=1;
								know=1;
							}
						}
						else
						{
							DOWN[L]=1;
						}
					}
					break;			
					
				case 1:                       //上升状态
						L += 1;
						setSEG(L);
						if(UP[L]==1 || L==MAX)               //该楼层有请求或到达MAX
						{
							UP[L]=0;
							LED0=0;LED1=1;
							STATUS = 0;
						}
					break;
					
				case 2:                        //下降状态
						L-=1;
						setSEG(L);
						if(DOWN[L]==1 || L==MIN)
						{
							DOWN[L]=0;
							LED0=0;LED2=1;
							STATUS = 0;
						}
					break;
			}
		T=0;
		}
	}
}

4 一些问题

  • 电梯达到MAX停止时,发出更高处的到达请求或上升呼梯请求,应当继续执行上升过程而不是转至下降过程,该步实现需要使按键输入外部中断优先级大于定时器中断;电梯连续到达某一层后,若有该层的到达请求,应当一直维持停止状态(模拟持续开门)。
  • 电梯在上升过程可能会到达MIN,比如电梯在初始状态时连续按下L2,L3。此时MIN=2,MAX=3,UP[2]==1,UP[3]==1。在刚开始测试时我发现,电梯会在2层和3层之间往复运动。这是因为电梯状态know=1,执行上升序列只更新MAX,到达2层时置UP[2]=0,且不更新MIN,到达MAX时置UP[3]==0,此时更新MAX但UP数组已全部为0,MAX维持3不变,此后由于UP数组全为0,MAX和MIN不会更新,MAX=3,MIN=2会在两层楼往复运动。只要添加一个判断,上升过程中如果L==MIN则更新MIN;下降过程中,若果L==MAX则更新MAX,即可。
  • Keil选择输出文件格式为elf,这样proteus仿真时可以看到对应的源码信息(调试->CM4->Source Code),可以添加断点,便于调试。
  • 由于未设置串口,打印功能不能展现,可以通点亮一个特殊的LED来判断程序是否执行进入了某一个条件分支或中断处理函数。
  • Proteus可以不连线,通过给线路进行标号实现连接,这样电路图会更加简洁明了。注意选择由仿真模型的元器件
  • 各个源文件要访问几个共有的全局变量,需要在status.h头文件中声明(不能定义)形式为:extern int i;再在所有需要使用的文件引入该源文件(注意用宏定义防止多次引入),并在任意一个源文件中定义即可。如果只是在头文件定义为静态变量,各个源文件使用时会视作独立的变量处理。
  • 七段数码管直接用寄存器操作进行赋值即可,每次更新前先将数码管清空,注意要保持其他IO接口不变,在本系统中将GPIOA->BSRRH(BSRR高16位,置1对应IO口重置,置0对应IO口不变)置为0x7800即可(PA11-PA14重置)。赋值时直接对GPIOA->BSRRL(BSRR低16位,置1则对应IO口输出高电平,置0对应IO口不变)操作,只需要将PA11-PA14设置为对应数字的BCD码即可:GPIOA->BSRRL=num<<11。
  • 对于中断关闭功能,通过操作EXTI的IMR寄存器操作外部中断,通过TIM_Config库函数操作定时器中断。EXTI->IMR &= ~(EXTI_Linex),外部中断线x关闭;EXTI->IMR|=EXTI_Linex,外部中断线x开启;TIM_ITConfig(TIMx,TIM_IT_Upadate,ENABLE/DISABLE),定时器x中断开启/关闭。
  • 实验室提供的正点原子源程序汇总,可以直接用!运算符对某个IO口取反再赋值,比如:BEEP=!BEEP,将蜂鸣器取反,但我在测试时发现这种方式在我的开发环境下不可行,所以取反需要先获得IO口输出,判断后再赋值。可以直接采用位带操作对IO口赋值,如置PA10为1,只需要PAout(10)=1即可,该函数会操作寄存器位将其赋值。使用时可以将Pxout(n)定义为所连元器件,这样便于操作。
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值