这是第一次做嵌入式相关的东西,使用了STM32F401VE芯片,由于没有板子,只能用Proteus进行仿真,在实现过程中遇到了很多困难。由于这是嵌入式课程大作业的内容,源码和proteus工程等课程结束再放上来。因为是边学习边写的,所以一些功能的实现可能比较低效、繁琐。
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 | MCU | STMF32F401VE |
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记录电梯当前是在执行上升序列还是下降序列。
图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 定时器中断处理
??通过定时器中断允许电梯调度算法并设置输出,即使得电梯状态变化有一定时间间隔也使得多部电梯可以“并行”工作。由于采用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 停止状态顺序图
代码如下:
void TIM3_IRQHandler(void)
{
int i;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
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)
{
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)
{
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)
{
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;
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
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)定义为所连元器件,这样便于操作。
|