一:前言
本届题目虽然题目字数少,但是设计到的逻辑关系比较复杂,目的是实现电梯的逻辑,博主也是写了一天才将此程序完善,比较的难想
二:题目分析
1.读题理解题意大概以及设计的模块
本届题目首先需要用到按键,led,lcd,还有额外的pa45输出和pa67的pwm输出还需要rtc实时时钟的功能,实现功能需要开关门,电梯上升下降,以及控制楼层的排序,无关按键顺序,先升后降
2.进行模块化实现具体功能布局
首先我们肯定要实现的按键检测函数,lcd显示函数,其次我们需要进行模块化的是电梯的上升下降,以及门的开关。
三:进行cubomx的配置
1:首先进行rcc配置将High Speed Clock设置成如图所示
2:进入Clock Configuration进行如图所示配置
3:进行rtc时钟配置
4:进行pin口的配置
PA4,5 & PC8-15配置成GPIO输出模式,PA0 & PB0-3进行GPIO输入模式并且设置上拉电阻,PA6 & PA7进行定时器1617通道1的设置进行pwm输出。
5:进行定时器设置
博主选用TIM2作为按键检测定时器,TIM3作为led流水灯定时器
TIM2定时时间设定为10毫秒,根据公式Tout = ((arr+1)*(psc+1))/Tclk(us) ,Tclk =80,则(arr + 1) * (psc + 1) = 800000,TIM3同理
TIM3定时时间为100ms,也就是流水灯是100ms的速度进行流水
记得使能TIM2和TIM3的NVIC
6:进行PWM配置
首先我们需要对TIM16,17的定时器通道选为PWM Generation CH1
进行对psc和arr的设置,TIM16需要输出1khz ,TIM17需要输出2khz根据公式频率f = (arr + 1)* (psc + 1) / 80 (Hz) ,得出TIM16(arr + 1)* (psc + 1) = 80000,TIM17(arr + 1)* (psc + 1) = 40000,并且arr最好等于100最后设置占空比好设置
7:进行RTC设置
首先选中这两个选项
其次进行对General里的值进行设置
然后对初始化的时分秒,年月日周进行设置
8.进行生成设置
在SYS选项中debug上选中Serial Wire选项
后进入Project Manager选项的Code Generator中进行图下选项选中
最后设置路径以及ide然后点击进行配置完成
四:进行代码编写
1:进行变量的初步定义及宏定义
#define LD_WRITE(a,x) HAL_GPIO_WritePin(GPIOC, a , x)//led灯写宏定义
#define LD_ALL_CLOSE HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,1)//led灯全部关闭
#define LD_CHOOSE(x) HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, x);//led灯开关
#define DOOR_OPEN HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,1);//电梯门开
#define DOOR_CLOSE HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,0);//电梯门关
#define ELE_UP HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,1);//电梯上升
#define ELE_DOWN HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,0);//电梯下降
#define LD_WATER_CLOSE HAL_GPIO_WritePin(GPIOC,0xff00,1);//流水灯用的全部关闭,LD_ALL_CLOSE
一样
/*按键宏定义*/
#define BUTTON_B1 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
#define BUTTON_B2 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
#define BUTTON_B3 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
#define BUTTON_B4 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
#define LD_WATER_CLOSE HAL_GPIO_WritePin(GPIOC,0xff00,1);
struct lcd{
char line1[20];
char line2[20];
char line3[20];
char line4[20];
};
struct key{
uint8_t count;//消抖变量
uint8_t keynum;//最终判断变量,1为按下
uint8_t state;//此时按键的状态
};
uint8_t platform_num = 1;//目前实际楼层数目
uint16_t time = 0;//计时变量,单位时间1ms
uint16_t led_value = 0x0000;//led选中pin口的值
uint8_t platform_index = 0;//实际楼层在选中数组中的位置
char choose[4] = {0};//选中数组
uint8_t platform_state = 0;//电梯进行的状态0为待机1为运行
uint8_t frist = 0;//上电状态为开门,首次运行需要关门,此变量作用为识别是否第一次
uint8_t led_waterstate = 0;//led流水灯状态变量
uint16_t led_time = 0;//led流水的计时时间变量
unsigned int led_up = 0x0800;//流水灯向上流水时gpio选中值
unsigned int led_down = 0x10000;//流水灯向上流水时gpio选中值
2: 进行检测按键的配置
在中断回调函数中void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
if (htim -> Instance == TIM2){
key_structure[0].state = BUTTON_B1;
key_structure[1].state = BUTTON_B2;
key_structure[2].state = BUTTON_B3;
key_structure[3].state = BUTTON_B4;
for (uint8_t i = 0; i < 4; ++i){
switch (key_structure[i].count){
case 0:{
if (!key_structure[i].state)
key_structure[i].count++;
break;
}
case 1:{
if (!key_structure[i].state)
key_structure[i].count++;
break;
}
case 2:{
if (key_structure[i].state){
key_structure[i].keynum = 1;
key_structure[i].count = 0;
}
}
}
}
}
3:lcd显示函数
void lcd_interface()
{
get_rtcvalue();
sprintf (lcd_structure.line1," Now Plantform ");
sprintf (lcd_structure.line2," %d ",platform_num);
sprintf (lcd_structure.line3," %02d : %02d : %02d "
,my_time.Hours, my_time.Minutes, my_time.Seconds);
LCD_DisplayStringLine(Line2, (unsigned char *)lcd_structure.line1);
LCD_DisplayStringLine(Line4, (unsigned char *)lcd_structure.line2);
LCD_DisplayStringLine(Line7, (unsigned char *)lcd_structure.line3);
}
/*获取rtc函数*/
void get_rtcvalue()
{
HAL_RTC_GetTime(&hrtc, &my_time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &my_data, RTC_FORMAT_BIN);
}
4:按键选中楼层函数函数
/*按键选择楼层函数*/
void key_handle()
{
uint8_t count = 0;//是否重复变量
choose[0] = platform_num;//将实际楼层存入
/*进行按键扫描*/
for (uint8_t i = 0; i < 4; ++i){
/*如果按键摁下,且在待机状态,且不在实际楼层时*/
if (key_structure[i].keynum && !platform_state && i + 1 != platform_num){
/*在1秒时间内再次摁下则时间清零*/
if (time < 1000){
HAL_TIM_Base_Start_IT(&htim3);//开启计时
/*防止重复*/
for (uint8_t j = 0; j < 4; ++j){
if (i + 1 == choose[j]){
key_structure[i].keynum = 0;
count = 1;
break;
}
}
/*若不重复,则进行数据储存并且点亮灯*/
if (!count){
choose[key_index] = i + 1;
key_index++;
time = 0;
switch(i + 1){
case 1:{
led_value |= 0x0100;
break;
}
case 2:{
led_value |= 0x0200;
break;
}
case 3:{
led_value |= 0x0400;
break;
}
case 4:{
led_value |= 0x0800;
break;
}
}
LD_CHOOSE(1);
LD_ALL_CLOSE;
LD_WRITE(led_value,0);
LD_CHOOSE(0);
time = 0;
key_structure[i].keynum = 0;
}
count = 0;//重复变量清零
}
}
}
}
5:led选中函数
void led_close(uint8_t value)
{
LD_CHOOSE(1);
LD_ALL_CLOSE;
LD_WRITE(led_value,0);
switch (value){
case 1:{
led_closevalue |= 0x0100;
break;
}
case 2:{
led_closevalue |= 0x0200;
break;
}
case 3:{
led_closevalue |= 0x0400;
break;
}
case 4:{
led_closevalue |= 0x0800;
break;
}
}
LD_WRITE(led_closevalue,1);
LD_CHOOSE(0);
}
6:选中楼层数据处理函数
/*进行选中楼层按键处理*/
void platform_handle()
{
unsigned char sort_value = 0;//排序变量
key_handle();//按键检测
/*如果超过一秒没有摁下其他按键则开始运行*/
if (time > 1000){
platform_state = 1;//开始运行标志位
HAL_TIM_Base_Stop_IT(&htim3);//停止计时
time = 0;//时间清零
}
/*进行选中数组排序,在开始运行前*/
if (choose[1] && platform_state){
for (uint8_t i = 0; i < 4; ++i){
for (uint8_t j = i; j < 4; ++j){
if (choose[i] > choose[j]){
sort_value = choose[i];
choose[i] = choose[j];
choose[j] = sort_value;
}
}
}
/*找到当前楼层在数组的位置并赋值*/
for (uint8_t i = 0; i < 4; ++i){
if (choose[i] == platform_num){
platform_index = i;
break;
}
}
}
}
7:电梯上升下降以及电梯门的开关函数
/*电梯门控制函数,0关1开*/
void door_control(uint8_t choose)
{
HAL_TIM_Base_Start_IT(&htim3);//开启定时器计时
if (choose){
/*进行开门操作*/
while (time < 4000){
lcd_interface();//持续显示
DOOR_OPEN;
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);//开启pwm
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,60);//设置占空比为60%
}
HAL_TIM_PWM_Stop(&htim17,TIM_CHANNEL_1);//关闭pwm
HAL_TIM_Base_Stop_IT(&htim3);//关闭定时器
time = 0;
}
else{
/*进行关门操作*/
while (time < 4000){
lcd_interface();//持续显示
DOOR_CLOSE;
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);//开启pwm
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,50);//设置占空比为50%
}
HAL_TIM_PWM_Stop(&htim17,TIM_CHANNEL_1);//关闭pwm
HAL_TIM_Base_Stop_IT(&htim3);//关闭定时器
time = 0;
}
}
/*电梯运行电机控制函数,0下1上*/
void elevator_con(uint8_t choose)
{
HAL_TIM_Base_Start_IT(&htim3);//开启定时器
if (choose){
/*进行上升操作*/
while (time < 6000){
led_waterstate = 1;//开启流水灯按照灯号降序
lcd_interface();//持续显示
ELE_UP;
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);//开启PWM
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,80);//设置占空比为80%
}
}
else{
/*进行下降操作*/
while (time < 6000){
led_waterstate = 2;//开启下降流水灯按照灯号升序
lcd_interface();//持续显示
ELE_DOWN;
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);//开启pwm
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,60);//设置占空比为60%
}
}
led_waterstate = 0;//停止流水灯
HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_1);//PWM停止
HAL_TIM_Base_Stop_IT(&htim3);//停止计时
time = 0;//时间清零
8:进行具体电梯操作函数
逻辑讲解:数组一共四位,首先我们要将当前楼层存入,后进行选择,当选择结束,对数组进行升序排序后在找到当前楼层所在数组的位置,在此位置前的为低于当前楼层的选中楼层,在此位置后的为高于当前楼层额选中楼层,则进行下面的while循环,先上升while后下降while如果当前楼层位置在数组的末端则视为当前为最高层,若当前位置处于首端或前位为零则时最低端,当然也可以直接简单粗暴的让他进行对1和4的判断,注意的是进行while循环时需要将显示函数加入,最后进行上升和下降的操作,如果当前到达的楼层是选中楼层则进行一次开关门操作,以此循环。
/*进行上升下降操作*/
void start_handle()
{
uint8_t index = platform_index;//进行赋值初始楼层进行判断
HAL_TIM_Base_Stop_IT(&htim2);//关闭按键扫描
/*进行上升操作,当当前楼层在最后一位时则不需要进行上升操作*/
while (index < 3 && choose[index + 1]){
index++;
/*上升开始,直到实际楼层与选中楼层相符*/
while (platform_num != choose[index] && index < 4 && choose[index]){
lcd_interface();
elevator_con(1);
platform_num++;
}
led_close(choose[index]);//进行到达楼层led灯的关闭
door_control(1);//进行开门
HAL_TIM_Base_Start_IT(&htim3);//开启定时器
/*进行等待两秒*/
while (time < 2000)
lcd_interface();
time = 0;
door_control(0);//进行关门
HAL_TIM_Base_Stop_IT(&htim3);//停止计时
}
index = platform_index;//开始下降之前index需要复位到初始楼层
/*进行下降操作,当初始楼层不为1,且前一位不是零的情况下进行操作*/
while (index > 0 && choose[index] != 1 && choose[index - 1] != 0){
index--;
/*下降开始,直到实际楼层与选中楼层相符*/
while (platform_num != choose[index] && index > 0){
lcd_interface();
elevator_con(0);
platform_num--;
}
led_close(choose[index]);//进行到达楼层led灯的关闭
door_control(1);//进行开门
HAL_TIM_Base_Start_IT(&htim3);//开启定时器
/*进行等待两秒*/
while (time < 2000)
lcd_interface();
time = 0;
door_control(0);//进行关门
HAL_TIM_Base_Stop_IT(&htim3);//停止计时
}
/*将选中数组清零*/
for (uint8_t i = 0; i < 4; i++)
choose[i] = 0;
platform_state = 0;//恢复待机状态
choose[0] = platform_num;//进行当前楼层赋值
key_index = 1;//按键赋值数组复位
HAL_TIM_Base_Start_IT(&htim2);//开始按键扫描
led_value = 0x0000;//led选中值清零
led_closevalue = 0x0000;//led关闭选中值清零
led_waterstate = 0;//关闭led流水状态
/*led全部关闭*/
LD_CHOOSE(1);
LD_ALL_CLOSE;
LD_CHOOSE(0);
}
9:主函数编写
因为上电状态下是开门状态,则第一次进行操作需要先关门。
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RTC_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM17_Init();
MX_TIM16_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
HAL_TIM_Base_Start_IT(&htim2);
HAL_RTC_Init(&hrtc);
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LCD_Clear(Black);
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,1);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,1);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,0);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
lcd_interface();
platform_handle();
if (platform_state){
if (!frist){
frist = 1;
door_control(0);
}
start_handle();
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
五 :总结
本人水平有限,如果有哪里出错或者有更好的解法可以与我私信或在评论区里进行讨论,不喜勿喷。