第八届省赛蓝桥杯嵌入式真题
含有取字模软件
百度云:https://pan.baidu.com/s/1B0Qh6uN628fHGwJFAwxlnA?pwd=6666
提取码:6666
目录
1.赛题
2.分析及配置
浏览一遍。看用到了什么直接配置即可。
按键,PWM,LCD,LED,RTC,PA4,PA5
然后按顺序写代码就好,代码不会一次写成,写到下一个的时候可能要更改上一个的不过没事,改一下就好。
2.1 RCC配置
2.2 SYS配置
2.3 PWM的配置
PA6的PWM选择TIM16_CH1,PA7的PWM选择TIM17_CH1。也可以选择TIM3的CH1和CH2使用双通道。
PA6的PWM是1KHz
频率=时钟源/分频/计数值
1KHz=80M/分频/100 得出分频为800
因为是PWM所以我们这里计数值为100,这样占空比好调,改变占空比是直接填几十就是占空比为百分之多少
2.4 LCD及LED的配置
用到的引脚全部设置为out_put即可
因为PC8-PC15是LED所以默认灭。
2.5 RTC的配置
只是简单的时钟功能。直接使能剩下的默认即可。
2.6 PA4和PA5的配置
设置为输出即可用
2.7 按键的配置
定时器10ms检测一次
2.8 生成代码
起名字选择Keil
勾上这个
然后生成代码按顺序写代码即可用。
3.代码的编写及解释
分析:最难的部分就是电机部分的转换,直接用switch编写就行。
3.1 定义变量
/*当*/
u8 Dang[] = {0x00,0x00,0x00,0x00,0x10,0x60,0xC0,0xC0,
0x80,0x00,0xF8,0x00,0x00,0x00,0x00,0xF0,
0x00,0x00,0x00,0x00,0xFC,0x00,0x00,0x00,
0x00,0x00,0x08,0x18,0x18,0x18,0x18,0x18,
0x98,0x58,0xFF,0x00,0x00,0x00,0x00,0xFF,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x06,0x06,0x03,0x01,
0x00,0x00,0x3F,0x18,0x18,0x18,0x18,0x1F,
0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x00};
/*前*/
u8 Qian[] = {0x00,0x80,0x00,0x00,0x00,0xFE,0x00,0x00,
0xF0,0x10,0x10,0x10,0xF0,0x10,0x10,0x10,
0xF0,0x10,0x10,0x10,0x10,0x90,0x10,0x00,
0x00,0x00,0x81,0x83,0x42,0xFF,0x00,0x00,
0x47,0xC4,0x44,0x44,0x47,0x44,0x44,0x44,
0x47,0x44,0xC4,0x04,0x84,0x07,0x02,0x00,
0x00,0x00,0x01,0x00,0x10,0x3F,0x00,0x04,
0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,
0x0C,0x0C,0x0C,0x0C,0x0C,0x07,0x02,0x00};
/*平*/
u8 Ping[] = {0x00,0x00,0x00,0xF8,0x00,0x00,0x20,0xC0,
0x80,0x80,0x00,0xFE,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,
0x18,0x98,0x58,0xFF,0x18,0x18,0x18,0x18,
0x18,0x18,0x18,0x18,0x18,0x18,0x08,0x00,
0x00,0x00,0x08,0x1F,0x00,0x04,0x06,0x02,
0x01,0x00,0x10,0x3F,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
/*台*/
u8 Tai[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,
0x20,0x18,0xF8,0x10,0x00,0xC0,0x40,0x40,
0x40,0x40,0x40,0x40,0xC0,0x40,0x00,0x00,
0x00,0x08,0x1C,0x06,0x02,0x01,0x00,0x00,
0x00,0xF0,0x0F,0x00,0x00,0xFF,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,
0x0C,0x1F,0x18,0x10,0x00,0x07,0x02,0x02,
0x02,0x02,0x02,0x02,0x03,0x02,0x02,0x00};
extern uc16 ASCII_Table[];
typedef struct
{
unsigned char Number_Blink;//数字闪烁
unsigned char Platform;//平台
unsigned char LED_Flag;//灯亮的标志
unsigned char Motor_Flag;//电击运行标志位
unsigned int Motor_Time;//计算按键中间间隔的秒
bool Time_Flag;//按键按下开始计时标志位
unsigned char Open_Time;//开门时间计数
bool OpenOver;//开门结束
unsigned char Close_Time;//开门时间计数
bool CloseOver;//关门结束
unsigned int Up_Time;//上升时间计数
bool UpOver;//上升完成
unsigned int Down_Time;//下降时间计数
bool DownOver;//下降完成
unsigned char Motor_Up_Down;
unsigned char Wait_Time;//停留时间计数
bool WaitOver;//停留完成
unsigned char State;
unsigned int Flow_Time;
bool Flow_Flag;
}DATA_TypeDef;
DATA_TypeDef Data;
unsigned char Platform;
typedef struct
{
unsigned int Value;//当前按键的值
bool Flag;//按键按下标志位
unsigned char State;//按键判断的步骤
}Key_TypeDef;
Key_TypeDef B[4];
bool Key_Press;//有按键按下标志位
//时间的显示
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
//判断秒是否相等,相等就显示
RTC_TimeTypeDef sTime_Temp;
3.2 LCD的移植
找到它点进去看到Inc和Src
把Src里的lcd.c放到自己工程的Src中
把Inc里的fonts.h和lcd.h放到自己工程Inc中
用记事本打开main.c
复制到自己的main.c的对应位置即可。
双击添加自己工程的lcd.c
3.3 LCD界面编写显示
这里有中文的显示所以我们要改写一些官方给的程序。
3.3.1 中文的显示
打开lcd.c找到void LCD_DrawMonoPict(uc32 *Pict)复制粘贴改写
阴码代表不亮的地方是0,相当我们程序中的背景。
官方历程中是行列式、低位在前的打点所以我们按照官方历程取模。
取模软件给的字模是显示完八个点之后换行显示所以我们要在对应的程序中添加换行
void LCD_DrawMonoPict1(u8 Xpos, u16 Ypos,unsigned int BackColor,unsigned int TextColor,u8 *Pict)
{
u32 index = 0, i = 0;
u8 Xpos_Temp;
Xpos_Temp = Xpos;
//319时是左上角,我们的汉字24*24个像素点
//所以319 - 24相当于右移一个汉字的
Ypos = 319 - 24*Ypos;
LCD_SetCursor(Xpos, Ypos);
LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
//一共有24列所以要换2次行
//0-24换行,24-48换行,48-72一共三列
for(index = 0; index < 73; index++)
{
//换行
if(index==24||index==48)
{
//Xpos回到起点
Xpos = Xpos_Temp;
//Ypos右移八位
Ypos = Ypos - 8;
}
//打八个点
for(i = 0; i < 8; i++)
{
//为0就显示背景
if((Pict[index] & (1 << i)) == 0x00)
{
LCD_WriteRAM(BackColor);
}
else//不为0就显示字
{
LCD_WriteRAM(TextColor);
}
}
//写入坐标
LCD_SetCursor(Xpos++, Ypos);
LCD_WriteRAM_Prepare();/* Prepare to write GRAM */
}
}
3.3.2 显示平台数字
找到void LCD_DrawChar(u8 Xpos, u16 Ypos, uc16 *c)复制改一下
由这里可以知道uc16 *c传入的是一个数组这个
所以我们这里也这样写
当ASCII_Table[16 * 24]时显示的是0所以我们用Number+16就可以显示对应的数字
void LCD_DrawChar1(u8 Xpos, u16 Ypos,unsigned char Number,unsigned int BackColor,unsigned int TextColor)
{
u32 index = 0, i = 0;
u8 Xaddress = 0;
uc16 *c;
Ypos = 319-16*Ypos;
c = &ASCII_Table[(Number+16) * 24];
Xaddress = Xpos;
LCD_SetCursor(Xaddress, Ypos);
for(index = 0; index < 24; index++)
{
LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
for(i = 0; i < 16; i++)
{
if((c[index] & (1 << i)) == 0x00)
{
LCD_WriteRAM(BackColor);
}
else
{
LCD_WriteRAM(TextColor);
}
}
Xaddress++;
LCD_SetCursor(Xaddress, Ypos);
}
}
void Show(void)
{
LCD_DrawMonoPict1(Line1,4,Black,White,Dang);
LCD_DrawMonoPict1(Line1,5,Black,White,Qian);
LCD_DrawMonoPict1(Line1,6,Black,White,Ping);
LCD_DrawMonoPict1(Line1,7,Black,White,Tai);
//当平台数改变时就闪烁
if(Platform != Data.Platform)
{
//Data.Number_Blink<20 和 40 =<Data.Number_Blink < 60时灭
if((Data.Number_Blink<20)||((Data.Number_Blink>=40)&&(Data.Number_Blink<60)))
LCD_DrawChar1(Line3,9,Data.Platform,Black,Black);
//20 =<Data.Number_Blink < 40时灭60 =<Data.Number_Blink < 80
else if(((Data.Number_Blink>=20)&&(Data.Number_Blink<40))||((Data.Number_Blink>=60)&&(Data.Number_Blink<80)))
LCD_DrawChar1(Line3,9,Data.Platform,Black,White);
else
{
//闪烁完毕清0和让他们相等
Platform = Data.Platform;
Data.Number_Blink = 0;
}
}
//当秒有变化时就显示时间
if(sTime.Seconds!=sTime_Temp.Seconds)
{
char arr[30];
sTime_Temp.Seconds = sTime.Seconds;
sprintf(arr," %d: %d: %d ",sTime.Hours,sTime.Minutes,sTime.Seconds);
LCD_DisplayStringLine(Line5,(u8 *)arr);
}
}
我们用了if(Platform != Data.Platform)和if(sTime.Seconds!=sTime_Temp.Seconds)
这个可以让程序不必一直执行if里面的不必要的程序。
Data.Number_Blink我们放在了定时器中断中。
当平台数有变化时就执行加操作。
3.4 按键的编写
Data.Motor_Flag==0代表电机未工作此时按下按键有效其他无效。按键只进行标志位的改变中间不夹杂其他的操作。
void Key_Handle(void)
{
if(Key_Press)
{
Key_Press = 0;
if(B[0].Flag)
{
B[0].Flag = 0;
if((Data.Platform!=1)&&(Data.Motor_Flag==0))
Data.LED_Flag |= 0x01;
}
if(B[1].Flag)
{
B[1].Flag = 0;
if(Data.Platform!=2&&(Data.Motor_Flag==0))
Data.LED_Flag |= 0x02;
}
if(B[2].Flag)
{
B[2].Flag = 0;
if(Data.Platform!=3&&(Data.Motor_Flag==0))
Data.LED_Flag |= 0x04;
}
if(B[3].Flag)
{
B[3].Flag = 0;
if(Data.Platform!=4&&(Data.Motor_Flag==0))
Data.LED_Flag |= 0x08;
}
}
}
定时器中断定时读取按键引脚以及各种计数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM4)
{
if(Data.Flow_Flag)
{
Data.Flow_Time++;
}
if(Data.Wait_Time>0)
{
Data.Wait_Time++;
if(Data.Wait_Time>200)
{
Data.Wait_Time = 0;
Data.WaitOver = 1;
}
}
if(Data.Open_Time>0)
{
Data.Open_Time++;
if(Data.Open_Time>200)
{
Data.Open_Time = 0;
Data.OpenOver = 1;
}
}
if(Platform != Data.Platform)//平台数字变化时闪烁
Data.Number_Blink++;
if(Data.Up_Time>0)
{
Data.Up_Time++;
if(Data.Up_Time>=600)
{
Data.Up_Time = 0;
Data.UpOver = 1;
}
}
if(Data.Down_Time>0)
{
Data.Down_Time++;
if(Data.Down_Time>=600)
{
Data.Down_Time = 0;
Data.DownOver = 1;
}
}
if(Data.Close_Time>0)//关闭开始计数
{
Data.Close_Time++;
if(Data.Close_Time>=200)//到两秒停止计数
{
Data.Close_Time = 0;
Data.CloseOver = 1;
}
}
if(Data.Time_Flag)
Data.Motor_Time++;
if(Data.Motor_Time>100)//10ms*100=1000ms=1s
{
Data.Time_Flag = 0;
Data.Motor_Time = 0;
Data.Motor_Flag = 1;
}
B[0].Value = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
B[1].Value = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
B[2].Value = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
B[3].Value = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(int i=0;i<4;i++)
{
switch(B[i].State)
{
case 0: if(B[i].Value == 0)
B[i].State = 1;break;
case 1: if(B[i].Value == 0)
{
B[i].State = 2;
B[i].Flag = 1;
Key_Press = 1;
}
else
B[i].State = 0;break;
case 2: if(B[i].Value == 1)
{
B[i].State = 0;
Data.Time_Flag = 1;
Data.Motor_Time = 0;
}break;
default:break;
}
}
}
}
3.5 PWM电机的编写
因为设计多个过程所以我们用switch来写每一个过程,每一个过程更改标志位和判断即可。
电机的步骤,关门->运行->开门->有就电梯就等待,无电梯就结束。
我们先进行编号设置一下那些电梯是上操作,这样不用进行排序直接按顺序判断即可。
首先就是判断有没有灯亮,有就进行流程没有就直接退出。
void Motor_Operation(void)
{
if(Data.Motor_Flag == 1)
{
Data.Motor_Flag = 2;
switch(Data.Platform)
{
//当在平台1时0x0E = 0000 1110;1代表上升0代表下降
//相当于在1时都是上升
case 1:Data.Motor_Up_Down = 0x0E;break;
//当在平台2时0x0C = 0000 1100;
//3,4上升
case 2:Data.Motor_Up_Down = 0x0C;break;
//4上升
case 3:Data.Motor_Up_Down = 0x08;break;
//没有上升
case 4:Data.Motor_Up_Down = 0x00;break;
}
}
if(Data.Motor_Flag == 2)
{
//电机进行的步骤
switch(Data.State)
{
case 0: //判断是否有灯亮,有就关门进入下一步
if(Data.LED_Flag)
{
//查看是否关门,没关门就关门
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_5))
{
//开启关门时间
Data.Close_Time = 1;
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,50);
//设置为下一个步骤
Data.State = 1;
}
else
//已经关闭也进行下一个步骤
Data.State = 1;
}
//没有就退出电机运行
else
{
Data.Motor_Flag = 0;
Data.State = 0;
}
break;
case 1: //等待电机门关
if(Data.CloseOver)//关闭结束
{
//关门之后开门标志位置零
Data.OpenOver = 0;
//关闭完毕后把门的灯关闭
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);
//关闭完成开始上升
if(Data.LED_Flag&Data.Motor_Up_Down)
{
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,80);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//上升计数开始
Data.Up_Time = 1;
}
else
{
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,60);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
//上升计数开始
Data.Down_Time = 1;
}
Data.State = 2;
//上升时,开始流水
Data.Flow_Flag = 1;
}
break;
case 2: if(Data.UpOver||Data.DownOver)//上升结束
{
Data.Flow_Flag = 0;
Data.Flow_Time = 0;
LED();
Data.UpOver = 0;
Data.DownOver = 0;
if(Data.Motor_Up_Down&0x0f)
{
//如果平台2是上升
if(Data.Motor_Up_Down&0x02)
{
//平台2灯亮
if(Data.LED_Flag&0x02)
{
//关灯平台2的灯
Data.LED_Flag &= 0x0D;
//更新平台
Data.Platform = 2;
}
//如果灯没亮就清除上升
Data.Motor_Up_Down &= 0x0D;
}
//平台2已经上升完毕
else if(((Data.Motor_Up_Down&0x02)==0)&&
//如果平台3是上升就进入
(Data.Motor_Up_Down&0x04))
{
//平台3的灯亮
if(Data.LED_Flag&0x04)
{
//关灯
Data.LED_Flag &= 0x0B;
//更新平台
Data.Platform = 3;
}
//如果灯没亮就清除上升
Data.Motor_Up_Down &= 0x0B;
}
//如果平台2没有上升
else if(((Data.Motor_Up_Down&0x02)==0)&&
//平台3没有上升
((Data.Motor_Up_Down&0x04)==0)&&
//平台4为上升
(Data.Motor_Up_Down&0x08))
{
//平台4的灯亮
if(Data.LED_Flag&0x08)
{
//关灯
Data.LED_Flag &= 0x07;
//更新平台
Data.Platform = 4;
}
Data.Motor_Up_Down = 0x00;
}
}
else
{
//看平台3
if(Data.LED_Flag&0x04)
{
//关灯
Data.LED_Flag &= 0x0B;
//更新平台
Data.Platform = 3;
}
//如果平台3没亮,且平台2亮
else if(((Data.LED_Flag&0x04)==0)&&
(Data.LED_Flag&0x02))
{
//关灯
Data.LED_Flag &= 0x0D;
//更新平台
Data.Platform = 2;
}
//如果平台2,3都灭且平台1亮
else if( ((Data.LED_Flag&0x04)==0)&&
((Data.LED_Flag&0x02)==0)&&
(Data.LED_Flag&0x01))
{
//关灯
Data.LED_Flag = 0x00;
//更新平台
Data.Platform = 1;
}
}
//上升完毕开始开门
Data.Open_Time = 1;
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,60);
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,0);
Data.State = 3;
}
else
LED();
break;
case 3: if(Data.OpenOver)//开门结束
{
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
Data.CloseOver = 0;
//如果还有上升电梯就等待
if(Data.LED_Flag)
{
Data.Wait_Time = 1;
Data.State = 4;
}
else//如果没有上升电梯
{
//电机停止运转
Data.State = 0;
Data.Motor_Flag = 0;
Data.Motor_Up_Down = 0x00;
}
}
break;
case 4: if(Data.WaitOver)
{
Data.State = 0;
Data.WaitOver = 0;
}
break;
}
}
}
一些时间的计数在定时器中断里
按键按下的时候清除按键时间计数
3.5 LED编写
判断标志位进行亮灯即可
void LED(void)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
if( ((Data.Flow_Time<20)&&(Data.Down_Time>0))||
((Data.Flow_Time>=60)&&(Data.Flow_Time<80)&&(Data.Up_Time>0)))
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15,GPIO_PIN_RESET);
if( ((Data.Flow_Time<20)&&(Data.Up_Time>0))||
((Data.Flow_Time>=60)&&(Data.Flow_Time<80)&&(Data.Down_Time>0)))
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_12,GPIO_PIN_RESET);
if( ((Data.Flow_Time>=20)&&(Data.Flow_Time<40)&&(Data.Down_Time>0))||
((Data.Flow_Time>=40)&&(Data.Flow_Time<60)&&(Data.Up_Time>0)))
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);
if( ((Data.Flow_Time>=20)&&(Data.Flow_Time<40)&&(Data.Up_Time>0))||
((Data.Flow_Time>=40)&&(Data.Flow_Time<60)&&(Data.Down_Time>0)))
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
if(Data.Flow_Time>=80)
Data.Flow_Time = 0;
if(Data.LED_Flag&0x01)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
if(Data.LED_Flag&0x02)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_9,GPIO_PIN_RESET);
if(Data.LED_Flag&0x04)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_10,GPIO_PIN_RESET);
if(Data.LED_Flag&0x08)
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_11,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
3.6 RTC
RTC很简单配置完直接取时间即可
3.7 初始化
void Init(void)
{
//开启定时器中断
HAL_TIM_Base_Start_IT(&htim4);
//初始化LCD
LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
//设置平台初始化
Data.Platform = 1;
//平台中间变量初始化,Data.Platform = Platform时不闪烁
Platform = 1;
//在LCD中显示平台
LCD_DrawChar1(Line3,9,Data.Platform,Black,White);
//设置RTC显示的时间
Data.Motor_Time = 0;
Data.Time_Flag = 0;
sTime.Hours = 0x12;
sTime.Minutes = 0x50;
sTime.Seconds = 0x55;
HAL_RTC_SetTime(&hrtc,&sTime,RTC_FORMAT_BCD);
//默认门打开
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);
//开启PWM
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim16,TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,60);
//默认灯都关闭
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
3.8 while主循环
HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&sDate,RTC_FORMAT_BIN);
Show();
Key_Handle();
LED();
Motor_Operation();
4.结果展示
5.结尾
这个逻辑性还是比较强的涉及到各种转换,可以用switch及标志位解决,中文显示也是也需要修改。在写的过程中我们没有用排序,直接使用判断标志位来实行电梯是上还是下。先判断上在判断下,每一个case写上需要的标志位,以及需要改变的标志位即可。能力有限,代码还有很大的提升空间如有错误还请指正,谢谢。