往期文章目录
蓝桥杯备战(1)——第六届蓝桥杯嵌入式设计省赛赛题
备战蓝桥杯(2)——第七届蓝桥杯嵌入式省赛赛题实战
前言
寒假的时候写了第七届和第六届的蓝桥杯省赛的程序设计题,本来也想接着做下去的,但是过年了玩上头了,第八届的题就搁置了一个寒假,直到开学(2月28号)才动工。第八届的题目有难度,比前两届的难度大很多,我做了四个晚上,讲解9的多小时才完成,不过这次练习也让我收获颇丰。
本次实验仍然采用CT117E平台完成,用CUBEMX和Keil5编写和调试的程序。
一、第八届蓝桥杯嵌入式省赛赛题展示
第八届蓝桥杯程序设计题的题目如下:
题目不长,但是当我读完题目以后我就觉得这个题目好难,思路都没有理得很清楚就直接上手做了。
二、程序流程图
根据题目所要求的功能,我同样绘制了一个程序流程图作为编程参考,这个是在我写完所有程序并且调试完成有画的。
三、配置所需外设
我的CUBEMX的配置图如下,在设计中前前后后改了很多次,思路也一直有在改变。根据题目要求,设置了定时器模块用来计时与产生PWM波,配置了LED等输出引脚和楼层按键引脚(根据原理图配置成上拉输入模式)。
四、程序编写
这次的程序会比较大段,给出的程序只是部分程序,我的项目工程下载链接会在文末给出,有需要的可以自行下载。
先给出主函数和一些函数,变量的定义。
uint8_t level = 1;
uint8_t tar_level[4]; //目标楼层,长度为4的数组
uint8_t now_level = 1; //当前楼层
uint8_t up_down[3]; //判断出目标楼层的上下行方向并且放在这个数组里面
uint8_t go_up[3]; //存储上行的数组
uint8_t go_down[3]; //存储下行的数组
uint8_t str[20]; //LCD的显示字符串
uint8_t str1[20]; //LCD的显示字符串
uint8_t cnt = 0;
uint8_t up_cnt = 0;
uint8_t down_cnt = 0;
int flag_up = 0; //上行标志位
int flag_down = 0; //下行标志位
int push_flag = 0; //按键操作完成标志位
int arrive_tar = 0; //到达目标楼层标志位
extern int time_cnt; //定时器的计数值
extern int sec_6; //6秒计时标志位
extern int sec_4; //4秒计时标志位
extern int sec_2; //2秒计时标志位
extern int sec_1; //1秒计时标志位
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void led_start(void);//初始化LED灯和引脚
void floor_led(int floor);//到达指定楼层亮灯函数
void floor_push(void);//按键判断函数
void led_up(void);//上行流水等函数
void led_down(void);//下行流水灯函数
void up_down_jug(void);//上行下行判断函数
void floor_add_dec(void);//楼层增减函数
void floor_rank(void);//将上行楼层和下行楼层分开来
void go_stop(void);//判断电梯是否到达目标楼层函数
void num_flashing(void);//楼层数字LCD闪烁函数
void door_open(void);//开门控制函数
void door_close(void);//关门控制函数
void EL_up(void);//升降机上行控制函数
void EL_down(void);//升降机下行控制函数
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
int main(void)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RTC_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_TIM15_Init();
/* USER CODE BEGIN 2 */
HAL_RTC_Init(&hrtc);
LCD_Init();
LCD_Clear(White);
LCD_SetTextColor(Black);
LCD_DisplayStringLine(Line2," Level");
LCD_DisplayStringLine(Line4," 1");
led_start();
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_RTC_GetDate(&hrtc,&D,FORMAT_BIN);//获取RTC
HAL_RTC_GetTime(&hrtc,&T,FORMAT_BIN);
sprintf((char *)str," %d:%d:%d ",T.Hours,T.Minutes,T.Seconds);
sprintf((char *)str1," %d",now_level);
LCD_DisplayStringLine(Line4,str1);
LCD_DisplayStringLine(Line7,str);
floor_led(now_level);
floor_push();
floor_rank();
up_down_jug();
floor_add_dec();
go_stop();
HAL_Delay(5);
}
/* USER CODE END 3 */
}
1、电梯按键功能
电梯的按键总共有四个,分别对应1至4楼,按下楼梯以后电梯就会到对应的楼层。同时按照题目要求,按键按下一秒后电梯就要开始运行了,并且按下与当前楼层相同的楼层按键是无效的。根据这些要求我写了如下代码来完成这些要求。这块的程序编写比较简单,只是需要一些逻辑分析,不断调试就可以写出来。
void floor_push(void)
{
time_cnt = 0;
sec_1 = 0;
if(push_flag == 0)
{
arrive_tar = 0;
HAL_TIM_Base_Start_IT(&htim3);
while(1)
{
HAL_RTC_GetDate(&hrtc,&D,FORMAT_BIN);
HAL_RTC_GetTime(&hrtc,&T,FORMAT_BIN);
sprintf((char *)str," %d:%d:%d ",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line7,str);
if(HAL_GPIO_ReadPin(F1_GPIO_Port,F1_Pin) == 0 && now_level !=1)//按下一楼按键并且当前不是1楼
{
HAL_Delay(300);
if(HAL_GPIO_ReadPin(F1_GPIO_Port,F1_Pin) == 0)
{
tar_level[cnt] = 1;
up_down[cnt]=2; //幅值为1时表示这个要上楼,幅值为2表示要下楼
cnt++;
sec_1 = 0;
time_cnt = 0;
}
}
if(HAL_GPIO_ReadPin(F2_GPIO_Port,F2_Pin) == 0)
{
HAL_Delay(300);
if(HAL_GPIO_ReadPin(F2_GPIO_Port,F2_Pin) == 0 && now_level !=2)//按下一楼按键并且当前不是2楼
{
tar_level[cnt] = 2;
if(now_level<2)
{
up_down[cnt]=1; //幅值为1时表示这个要上楼,幅值为2表示要下楼
}
if(now_level>2)
{
up_down[cnt]=2; //幅值为1时表示这个要上楼,幅值为2表示要下楼
}
cnt++;
sec_1 = 0;
time_cnt = 0;
}
}
if(HAL_GPIO_ReadPin(F3_GPIO_Port,F3_Pin) == 0)
{
HAL_Delay(300);
if(HAL_GPIO_ReadPin(F3_GPIO_Port,F3_Pin) == 0 && now_level !=3)//按下一楼按键并且当前不是3楼
{
tar_level[cnt] = 3;
if(now_level<3)
{
up_down[cnt]=1; //幅值为1时表示这个要上楼,幅值为2表示要下楼
}
if(now_level>3)
{
up_down[cnt]=2; //幅值为1时表示这个要上楼,幅值为2表示要下楼
}
cnt++;
sec_1 = 0;
time_cnt = 0;
}
}
if(HAL_GPIO_ReadPin(F4_GPIO_Port,F4_Pin) == 0)
{
HAL_Delay(300);
if(HAL_GPIO_ReadPin(F4_GPIO_Port,F4_Pin) == 0 && now_level !=4)//按下一楼按键并且当前不是4楼
{
tar_level[cnt] = 4;
up_down[cnt]=1; //幅值为1时表示这个要上楼,幅值为2表示要下楼
cnt++;
sec_1 = 0;
time_cnt = 0;
}
}
if(sec_1 == 1 && strlen((char *)tar_level) != 0) //按下按键后一秒内没有按下其他按键就说明按键操作已完成
{
HAL_TIM_Base_Stop_IT(&htim3);
sec_1 = 0;
time_cnt = 0;
push_flag = 1;
cnt = 0;
break;
}
}
}
}
2、电梯上下行控制
这部分是我花时间最多也是我认为最困难的一部分。这一块的代码其实就是电梯调度算法,奈何我的算法学得太差,只能靠分析逻辑关系一点点码出来。我也上网了解了一下电梯调度算法,优快云上有很多大佬写得很好,有兴趣的同学可以取了解了解。
我编写这块代码的思路时,先判断目标篇楼层有哪些是上去的哪些是下去的,然后根据先上后下原则先跑上后跑下并产生升降机的PWM和上下行的流水灯,每6秒楼层数加1或减1同时判断是否到达目标楼层,若到达就执行开门关门的命令,都做完以后就整个LCD数字闪烁。 代码如下:
void floor_add_dec(void)
{
time_cnt = 0;
sec_6 = 0;
if(push_flag == 1)
{
HAL_TIM_Base_Start_IT(&htim3);
while(1)
{
HAL_RTC_GetDate(&hrtc,&D,FORMAT_BIN);
HAL_RTC_GetTime(&hrtc,&T,FORMAT_BIN);
sprintf((char *)str," %d:%d:%d ",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line7,str);
if(flag_up == 1 && flag_down == 0)//若电梯上行,控制升降机
{
EL_up();
led_up();
}
if(flag_up == 0 && flag_down == 1)//若电梯下行,控制升降机
{
EL_down();
led_down();
}
if(sec_6 == 1 && (strlen((char *)go_up)>0 || strlen((char *)go_down)>0))//6秒到,楼层加一并且判断是否到达目标楼层
{
HAL_TIM_Base_Stop_IT(&htim3);//关闭定时器
sec_6 = 0;
time_cnt = 0;
if(flag_up == 1 && flag_down == 0 && arrive_tar == 0)//若是上行就加一
{
now_level++;
if(now_level == go_up[up_cnt] )//判断是否到达目标楼层
{
up_cnt++;
arrive_tar = 1;
HAL_TIM_PWM_Stop(&htim4,TIM_CHANNEL_1);
door_open();
door_close();
}
}
else if(flag_up == 0 && flag_down == 1 && arrive_tar == 0)//若是下行就减一
{
now_level--;
if(now_level == go_down[down_cnt] )//判断是否到达目标楼层
{
down_cnt++;
arrive_tar = 1;
HAL_TIM_PWM_Stop(&htim4,TIM_CHANNEL_1);
}
}
break;
}
}
}
}
/*----------------------------------------*/
void floor_rank(void)
{
int t,i;
int q = 0,p = 0;
t = strlen((char *)up_down);//读取楼层个数
for(i = 0;i<t;i++)
{
if(up_down[i] == 0x02)//判断下行
{
go_down[q] = tar_level[i];
q++;
}
else if(up_down[i] == 1)//判断上行
{
go_up[p] = tar_level[i];
p++;
}
}
p = 0;
q = 0;
memset(up_down,0,3);
memset(tar_level,0,3);
}
/*----------------------------------------*/
void up_down_jug(void)
{
if(strlen((char *)go_up) > 0)//判断并设置上行标志位
{
flag_up = 1;
flag_down = 0;
}
else if(strlen((char *)go_up)==0 && strlen((char *)go_down) > 0)//判断并设置下行标志位,同时符合先上后下规则
{
flag_down = 1;
flag_up = 0;
}
else
{
flag_down = 0;
flag_up = 0;
}
}
/*----------------------------------------*/
void go_stop(void)
{
if(arrive_tar == 1)//到达楼层
{
if(up_cnt < strlen((char *)go_up) )//如果电梯还有楼层没有跑完,将到达标志位清零,电梯继续运行
{
arrive_tar = 0;
}
else if(up_cnt == strlen((char *)go_up) && up_cnt != 0)//所有上行楼层都跑完了,将上行标志位清零。
{
memset(go_up,0,3);
up_cnt = 0;
flag_up = 0;
if(strlen((char *)go_down) == 0)
{
push_flag = 0;
}
HAL_GPIO_WritePin(EL_UP_GPIO_Port,EL_UP_Pin,GPIO_PIN_RESET);
num_flashing();
door_open();
door_close();
}
if(down_cnt < strlen((char *)go_down) )//如果电梯还有楼层没有跑完,将到达标志位清零,电梯继续运行
{
arrive_tar = 0;
}
else if(down_cnt == strlen((char *)go_down) && down_cnt !=0 )//所有下行楼层都跑完了,将下行标志位清零。
{
memset(go_down,0,3);
num_flashing();
push_flag = 0;
down_cnt = 0;
flag_down = 0;
HAL_GPIO_WritePin(EL_DOWN_GPIO_Port,EL_DOWN_Pin,GPIO_PIN_RESET);
door_open();
door_close();
}
}
}
其实里面的好呢多工作都可以放到定时器中断里面完成,但是我不太喜欢写代码跳来跳去的,就都拿出来在主函数里面做完了。
3、RTC功能
这个是最好做的了,不多说了,只要在CUBE上配置号就可以用了,这里我就不贴代码了。
还有一些函数向开关门控制,升降控制,LED控制等函数,比较基础,由于篇幅限制我就不贴出来了,有需要的可以通过下方的链接下载参考。
总结
写了这么久的一道题,我也做了一个小总结。
- HAL_Delay()绝对不能写在任何中断里面,(虽然这个可以通过调整中断优先级解决),但是一但出现问题困难就会不知所措。
- 语句
int p,q = 0;
表示q幅值0,p并没有被幅值,这个地方是个小细节(基础没打好),要注意。 - **【重点】**复杂的题目只要好好地理清逻辑关系,就可以做出来,千万不能放弃!!!!多调试,总会发现问题所在。
工程文件下载链接
下载链接:第八届蓝桥杯嵌入式省赛编程赛题工程文件