一、赛题分析
第八届蓝桥杯嵌入式省赛,博主觉得是在前几届中最难的一届。绕来绕去的,前前后后写了三个版本,第一个版本main.c里就写了400多行代码,而且功能不是很完整。第三版终于把代码浓缩成了300多行,功能完全实现(理论没错,可能有些小错误),在这里分享第三版的程序设计方法和思路。
本届赛题设计到模块有LCD、LED、PWM双路输出、KEY、RTC。其中LED、KEY、RTC均可在往期博客中找到相应代码初始化。附上上一届的链接,如果上一届没有则可在上上届找到:
蓝桥杯嵌入式第七届省赛——“模拟液位检测告警系统”旧板标准库_对愁眠后霜满天的博客-CSDN博客
二、程序设计
1、PA6-7的双路PWM输出程序设计
直接把PWM输出写出函数void TIM3_PWM_OCToggle(uint8_t PA6,uint8_t PA7)方便控制。只需要在函数中写入占空比即输出占空比,写入0则输出低电平。
#ifndef _PWM_H
#define _PWM_H
#include "stm32f10x.h"
void TIM3_PWM_OCToggle(uint8_t PA6,uint8_t PA7);
extern uint16_t TIM3_CCR1_Val;
extern uint16_t TIM3_CCR2_Val;
extern float TIM3_CH1_duty, TIM3_CH2_duty;
#endif
/*********************************************
PA1--TIM2--CH2 PA2--TIM2--CH3 PA3--TIM2--CH4
PA6--TIM3--CH1 PA7--TIM3--CH2
*********************************************/
#include "pwm.h"
#include "stm32f10x_tim.h"
#include "misc.h"
uint16_t TIM3_CCR1_Val = 1000;//PA6输出1000HZ
uint16_t TIM3_CCR2_Val = 500;//PA7输出2000HZ
uint16_t TIM3_capture = 0;
_Bool TIM3_CH1_flag=0, TIM3_CH2_flag=0;
float TIM3_CH1_duty=0.3, TIM3_CH2_duty=0.7;
void TIM3_PWM_OCToggle(uint8_t PA6,uint8_t PA7)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
uint16_t PrescalerValue = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
if(PA6 || PA7)
{
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
PrescalerValue = (uint16_t) (SystemCoreClock /1000000) - 1;
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel2 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
}
if(PA6 == 0)
{
TIM3_CH1_duty = 0;
TIM_OC1PolarityConfig(TIM3,TIM_OCPolarity_Low);
TIM_ForcedOC1Config(TIM3,TIM_ForcedAction_Active);
}else
{
TIM3_CH1_duty = PA6 * 0.01;
TIM_OCInitStructure.TIM_Pulse = TIM3_CCR1_Val;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);
TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
}
if(PA7 == 0)
{
TIM3_CH2_duty = 0;
TIM_OC2PolarityConfig(TIM3,TIM_OCPolarity_Low);
TIM_ForcedOC2Config(TIM3,TIM_ForcedAction_Active);
}else
{
TIM3_CH2_duty = PA7 * 0.01;
/* Output Compare Toggle Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_Pulse = TIM3_CCR2_Val;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);
TIM_ITConfig(TIM3,TIM_IT_CC2, ENABLE);
}
TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
TIM3_capture = TIM_GetCapture1(TIM3);
if(TIM3_CH1_flag == 1)
{
TIM_SetCompare1(TIM3, TIM3_capture + (u16)(TIM3_CCR1_Val*TIM3_CH1_duty));
TIM3_CH1_flag = 0;
}else
{
TIM_SetCompare1(TIM3, TIM3_capture + (u16)(TIM3_CCR1_Val*(1-TIM3_CH1_duty)));
TIM3_CH1_flag = 1;
}
}
if(TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
TIM3_capture = TIM_GetCapture2(TIM3);
if(TIM3_CH2_flag == 1)
{
TIM_SetCompare2(TIM3, TIM3_capture + (u16)(TIM3_CCR2_Val*TIM3_CH2_duty));
TIM3_CH2_flag = 0;
}else
{
TIM_SetCompare2(TIM3, TIM3_capture + (u16)(TIM3_CCR2_Val*(1-TIM3_CH2_duty)));
TIM3_CH2_flag = 1;
}
}
}
2、main.c设计
主函数存在的各个关键变量,状态标志位作用讲解处:
枚举STAT表示电梯所在的状态:STOP表示开门停留,OFF表示门在关闭中,ON表示门在打开中,UP表示在向上运行,DOWN表示向下运行。
LED[4]包括LED5到LED8,用于方便流水灯的操作。
state表示现在的状态,初始幅值STOP,表示电梯门在开门停留中。
key_lock为1表示按键锁死。key_lock_flag为1时表示有按键按下,key_lock_cnt开始计时,到1S时按键锁死。
ld[4]在程序中表示LED1到LED4的亮灭标志位。当按键按下相应位置置1,到达相应楼层之后只需要把相应楼层数减1处的值幅值为0即可灭掉到达相应楼层对应的LED灯。
arrive_flag初始化为1,上电表示到达。Input_target[4]保存输入目标楼层信息。
enum STAT{STOP,OFF,ON,UP,DOWN};
unsigned LED[4]={LED5,LED6,LED7,LED8};
u8 state = STOP;
u8 key_flag = 0,key_lock = 0,key_lock_flag = 0;
u8 now_floor = 1,Input_target[4]={0},target = 0;
u8 ld[4]={0},led_up = 0,led_down = 4,led_flag = 1;
u8 arrive_flag = 1,lcd_flag = 0,shine_flag = 0;
u8 arrive_pre = 0;
u8 temp[4],min[4];
u8 string[20];
u16 key_lock_cnt = 0;
u16 ON_cnt = 0,OFF_cnt = 0,STOP_cnt = 0;
key_in函数,当key_lock_cnt == 1000时key_lock == 1时按键锁死在这里体现。每次按键按下key_lock_cnt 清0就可以在最后一次按下按键后1s锁死按键。target_floor()--目标楼层读取函数,由高至低,先上后下。按键锁死之后读取,当target_floor()返回值为0时表示此时以到达最终楼层,arrive_flag置1,进行相应的开门操作。
void key_in(void)
{
if(key_lock == 0) key_flag = key_scan();
else if(key_lock == 1)
{
key_flag = 0;
target = target_floor();
}
if(key_flag == 1&&now_floor != 1)
{
Input_target[0] = 1;
ld[0]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 2&&now_floor != 2)
{
Input_target[1] = 2;
ld[1]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 3&&now_floor != 3)
{
Input_target[2] = 3;
ld[2]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 4&&now_floor != 4)
{
Input_target[3] = 4;
ld[3]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
}
target_floor()函数,在此处用到的i,j,k,m等用到局部变量,因为程序每执行一次他们都会被清0.在程序中temp[0]永远都是大于当前楼层的最小楼层,比如现在按下2、3、4按键它会依次返回2、3、4,做到顺序上升。当temp[0]==0时表示当前没有比当前楼层大的楼层输入,则返回min[0],min[0]同理是小于当前楼层的最大值。做到先上后下,顺序运行。
u8 target_floor(void)
{
u8 i,j = 0,k = 0,m;
for(i=0;i<4;i++)//返回大于当前楼层的追小值
{
if(now_floor<Input_target[i])
{
temp[j] = Input_target[i];
j++;
}
if(now_floor>Input_target[i]&&Input_target[i]!=0)
{
min[k] = Input_target[i];
k++;
}
}
for(m=0;m<k;m++)//返回小于当前楼层的最大值
{
if(min[0]<min[m])min[0]=min[m];
}
if(temp[0] == 0) return min[0];
else return temp[0];
}
LED_content(),LED灯的控制程序。在TIM4,1ms定时器中,初始化led_flag为1,led_flag取反时间为位移led_up或led_down的1/2即可实现向上或向下的流水灯。
void LED_content(void)
{
LED_Control(LED1,ld[0]);
LED_Control(LED2,ld[1]);
LED_Control(LED3,ld[2]);
LED_Control(LED4,ld[3]);
if(state == UP) LED_Control(LED[led_up],led_flag);//向上流水灯
else if(state == DOWN) LED_Control(LED[led_down-1],led_flag);//向下流水灯
else if(state != UP||state != DOWN) LED_Control(LED5|LED6|LED7|LED8,0);
}
全部代码如下,本届赛题就像一个状态机一样,由一个状态的完成触发下一个状态。这个程序有一个小bug,arrive_flag初始化为1,则上电即判断到达,屏幕上的1会闪两下,然后开门停住。
#include "stm32f10x.h"
#include "lcd.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "rtc.h"
#include "timer.h"
#include "pwm.h"
#include "stdio.h"
#include "string.h"
enum STAT{STOP,OFF,ON,UP,DOWN};
unsigned LED[4]={LED5,LED6,LED7,LED8};
u8 state = STOP;
u8 key_flag = 0,key_lock = 0,key_lock_flag = 0;
u8 now_floor = 1,Input_target[4]={0},target = 0;
u8 ld[4]={0},led_up = 0,led_down = 4,led_flag = 1;
u8 arrive_flag = 1,lcd_flag = 0,shine_flag = 0;
u8 arrive_pre = 0;
u8 temp[4],min[4];
u8 string[20];
u16 key_lock_cnt = 0;
u16 ON_cnt = 0,OFF_cnt = 0,STOP_cnt = 0;
void key_in(void);
u8 target_floor(void);
void show_lcd(void);
void LED_content(void);
void state_judgement(void);
void PWM_STATE(void);
void GPIO_INIT(void);
void gpio_state(void);
//Main Body
int main(void)
{
STM3210B_LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
SysTick_Config(SystemCoreClock/1000);
TIM4_Init();
LED_Init();
KEY_Init();
GPIO_INIT();
TIM3_PWM_OCToggle(0,0);
RTC_Init(12,50,55);
while(1)
{
key_in();
show_lcd();
LED_content();
PWM_STATE();
state_judgement();
gpio_state();
}
}
void key_in(void)
{
if(key_lock == 0) key_flag = key_scan();
else if(key_lock == 1)
{
key_flag = 0;
target = target_floor();
}
if(key_flag == 1&&now_floor != 1)
{
Input_target[0] = 1;
ld[0]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 2&&now_floor != 2)
{
Input_target[1] = 2;
ld[1]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 3&&now_floor != 3)
{
Input_target[2] = 3;
ld[2]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
if(key_flag == 4&&now_floor != 4)
{
Input_target[3] = 4;
ld[3]=1;
key_lock_flag = 1;key_lock_cnt = 0;
}
}
u8 target_floor(void)
{
u8 i,j = 0,k = 0,m;
for(i=0;i<4;i++)//返回大于当前楼层的追小值
{
if(now_floor<Input_target[i])
{
temp[j] = Input_target[i];
j++;
}
if(now_floor>Input_target[i]&&Input_target[i]!=0)
{
min[k] = Input_target[i];
k++;
}
}
for(m=0;m<k;m++)//返回小于当前楼层的最大值
{
if(min[0]<min[m])min[0]=min[m];
}
if(temp[0] == 0) return min[0];
else return temp[0];
}
void show_lcd(void)
{
LCD_DisplayStringLine(Line1,(u8*)" Now Floor ");
memset(string,0,sizeof(string));
sprintf((char*)string," %d ",now_floor);
if(lcd_flag == 0) LCD_DisplayStringLine(Line4,string);
else LCD_DisplayStringLine(Line4,(u8*)" ");
update_time();
LCD_DisplayStringLine(Line7,time_str);
}
void LED_content(void)
{
LED_Control(LED1,ld[0]);
LED_Control(LED2,ld[1]);
LED_Control(LED3,ld[2]);
LED_Control(LED4,ld[3]);
if(state == UP) LED_Control(LED[led_up],led_flag);//向上流水灯
else if(state == DOWN) LED_Control(LED[led_down-1],led_flag);//向下流水灯
else if(state != UP||state != DOWN) LED_Control(LED5|LED6|LED7|LED8,0);
}
void state_judgement(void)
{
if(arrive_flag == 1&&arrive_pre == 0)
{
state = ON;
ON_cnt = 2000;
STOP_cnt = 2000;
OFF_cnt = 2000;
arrive_pre = 1;
}
}
void PWM_STATE(void)
{
static u8 state_pre = 0;
if(state_pre != state&&key_lock == 1)
{
if(state == OFF) TIM3_PWM_OCToggle(0,50);
else if(state == UP) TIM3_PWM_OCToggle(80,0);
else if(state == DOWN) TIM3_PWM_OCToggle(60,0);
else if(state == ON) TIM3_PWM_OCToggle(0,60);
else TIM3_PWM_OCToggle(0,0);
state_pre = state;
}
}
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void gpio_state(void)
{
if(state == UP)GPIOA->ODR |= GPIO_Pin_4;
if(state == DOWN)GPIOA->ODR &= ~GPIO_Pin_4;
if(state == ON)GPIOA->ODR |= GPIO_Pin_5;
if(state == OFF)GPIOA->ODR &= ~GPIO_Pin_5;
if(state == STOP)
{
GPIOA->ODR &= ~GPIO_Pin_5;
GPIOA->ODR &= ~GPIO_Pin_4;
}
}
void TIM4_IRQHandler(void)
{
static u16 elevator_running = 0,led_cnt = 0,lcd_cnt = 1000;
if(TIM_GetITStatus(TIM4,TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
if(key_lock_flag == 1&&++key_lock_cnt>=1000)
{
key_lock_flag = 0;
key_lock = 1;
key_lock_cnt = 0;
}
if(now_floor<target&&arrive_flag == 0)
{
state = UP;
if(++led_cnt%100 == 0)
{
led_flag=!led_flag;
if(led_cnt%200 == 0) led_up = (led_up + 1) % 4;
}
if(++elevator_running>=6000)
{
elevator_running = 0;
now_floor++;
if(now_floor == target)
{
arrive_flag = 1;
Input_target[now_floor-1] = 0;
ld[now_floor-1]=0;
temp[0]=0;
led_flag = 1;
if(Input_target[0]==0&&Input_target[1]==0&&Input_target[2]==0&&Input_target[3]==0)
{
key_lock = 0;
target = 0;
}
}
}
}
if(now_floor>target&&now_floor>1&&target!=0&&arrive_flag == 0)
{
state = DOWN;
if(++led_cnt%100 == 0)
{
led_flag=!led_flag;
if(led_cnt%200 == 0)
{
led_down--;
if(led_down == 0) led_down = 4;
}
}
if(++elevator_running>=6000)
{
elevator_running = 0;
now_floor--;
if(now_floor == target)
{
arrive_flag = 1;
Input_target[now_floor-1] = 0;
ld[now_floor-1]=0;
min[0]=0;
led_flag = 1;
if(Input_target[0]==0&&Input_target[1]==0&&Input_target[2]==0&&Input_target[3]==0)
{
key_lock = 0;
target = 0;
}
}
}
}
if(arrive_flag == 1)
{
if(shine_flag == 0)
{
if(--lcd_cnt%250==0)
{
lcd_flag =! lcd_flag;
if(lcd_cnt == 0)
{
lcd_cnt = 1000;
shine_flag = 1;
}
}
}
if(state == ON)
{
ON_cnt--;
if(ON_cnt == 0) state = STOP;
}
if(state == STOP&&target!=0)
{
STOP_cnt--;
if(STOP_cnt == 0) state = OFF;
}
if(state == OFF)
{
OFF_cnt--;
if(OFF_cnt == 0)
{
arrive_flag = 0;
shine_flag = 0;
arrive_pre = 0;
}
}
}
}
}
三、附上工程
链接:https://pan.baidu.com/s/1Tsy7qyELnBX7Qz8xox-s2A
提取码:m1s5
有不对的地方,请多指教...