关于十三届蓝桥杯嵌入式省赛内容分享(基于HAL)

目录

题解思路:

详细内容:

一、系统初始化配置

  1.1 配置时钟:选择HSE外部晶振,配置主频80MHz

  1.2 芯片引脚配置:

  1.3 引脚输入、输出模式配置 

  1.4 TIM和USART配置

二、 代码编写

  2.1 按键扫描

  key.h/key.c

  2.2 Led初始化和修改

  led.h/led.c

  2.3 PWM频率和占空比修改

  2.4 串口修改密码

  2.5 main.c主要代码(不包含系统时钟配置等,这些Cube自动生成) 

main.c

三、 调试结果

四、 结语和注意事项


题解思路:

  1.   使用STM32 CubeMX配置时钟80MHz、GPIO(包括PC8~PC15(LED)、PD2(锁存器),  PA1(PWM), PA0、PB0~2(按键), PA9、A10(UART)
  2.   分析:按键响应时间t<0.1秒,同时要避免一次按下多次触发,则使用三行按键法短按即可;由于LED和LCD有共用引脚,要用锁存器即时先将LED引脚电平锁住。
  3.   考虑系统实时性问题,无需同步执行某些代码时可使用HAL_Delay(),若需要类同步执行则可使用系统滴答计时器(uwTick)或者启动定时器资源。

详细内容:

一、系统初始化配置

  1.1 配置时钟:选择HSE外部晶振,配置主频80MHz
时钟配置图

图 1-1  时钟树配置
​​​
 1.2 芯片引脚配置:

       根据下图自行配置,但实际工程中可以多复制几份工程文件,再进入不同的CubeMX的配置文 件(.ioc)配置生成相应的gpio.c,这里建议分别改为led.c\key.c\pwm.c等等方便随时移植的文件,文 件比较多时也可以新建文件夹bsp(板载)来区分外设和芯片内部的代码文件。


图 1-2  芯片引脚配置
 
1.3 引脚输入、输出模式配置 

      通过查阅竞赛板外设资料可知按键自带上拉模式,(PA0,PB0~2)配置为浮空输入即可,配 置后的生成的gpio.c/h改为key.c/h文件方便移植。


图 1-3-1  按键引脚配置
 

    LED为共阳极连接,芯片输出高电平时不导通,默认上电GPIO output level选择High;锁存器对应PD2,低电平锁存,高电平解锁,默认上电GPIO output level选择Low;输出只有高低电平之分,选择推挽输出即可,配置后生成的gpio.c/h改为led.c/h文件方便移植。


图 1-3-2  LED引脚配置
 
1.4 TIM和USART配置

      根据赛题要求,引脚A1为PWM输出端 ,点击引脚可知对应为TIM2的通道2,时钟源选择内部时钟源(系统时钟)即可,模式配置参考下图。


图 1-4-1 TIM模式配置
 

  上电默认PWM频率为1000Hz,方波即占空比为50%,预分频系数选择80-1,得到的时钟频率为80M/80=1MHz,自动重装载数为1000-1,即计1000个数,频率变为1M/1K=1KHz,根据占空比(50%)配置计数个数CCR=500,模式1为计数n<CCR为有效电平,n>CCR为无效电平,之后修改占空比也是修改CCR的值。配置完成会生成tim.c/h文件。


图 1-4-2  TIM生成PWM配置参数
 

  串口通信USART配置为异步模式Asynchronous,波特率配置为9600Bits/s,NVIC Settings中Enabled注意勾选,其余不用修改。配置完成会生成usart.c/h文件。


图 1-4-3 USART参数配置
 

二、 代码编写

   2.1 按键扫描

         采用三行按键法扫描,即将各个按键端口的状态组成一个字节数据,这里只有四个按键,采   用或运算将四个引脚状态放到字节数据低4位,详情见代码。

  key.h/key.c
#ifndef __KEY_H__
#define __KEY_H__

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

#define KEY1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KEY2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KEY3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KEY4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
/**       将每个按键的状态放到低四位,组合成新的字节数据   **/
#define KEYPORT  KEY1|(KEY2 << 1)|(KEY3 << 2)|(KEY4 <<3)|0xf0


/***      参数初始化    ***/


void KEY_Init(void);
void KEY_Input();
void key_scan();
/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */

#endif
#include "key.h"

/* USER CODE BEGIN 0 */
unsigned char ucTrg1=0;
unsigned char ucCont=0;
unsigned char ucRead=0;

/* USER CODE END 0 */

/* USER CODE BEGIN 1 */
uint8_t key[3]={0,0,0};//初始化输入值
uint8_t keyword[3];//确认密码值
uint8_t errorTime=0,OkKey=0;
/* USER CODE END 1 */

/**   CubeMX生成gpio.c中的初始化函数 进行重命名   **/
void KEY_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin : PA0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : PB0 PB1 PB2 */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/**   按下按键时输入密码值变化  **/
uint16_t keyClick=0;

void KEY_Input()
{
  uint8_t flag=0;
  key_scan();
	//延时20毫秒
  if(uwTick-keyClick<20)return;
  /**  判断  **/
	if(ucTrg1==0x01)
	{
	  key[0]++;
		if(key[0]==10)key[0]=0;
	}
	else if(ucTrg1==0x02)
	{
	  key[1]++;
		if(key[1]==10)key[1]=0;
	}
	else if(ucTrg1==0x04)
	{
	  key[2]++;
		if(key[2]==10)key[2]=0;
	}
	else if(ucTrg1==0x08)
	{
	  for(int i=0;i<3;i++)
		 {
			 keyword[i]=key[i];
			 flag++;
		 }
       //如果密码不正确,错误次数增加
		if(flag!=3)
		 errorTime++;
		OkKey=1;
	}
 
  keyClick=uwTick;
}

/**   按键扫描  **/
void key_scan()
{
   /**如果按键A0按下,KEYPORT就会变为ffff 0111,即0xf7异或后
   ucRead=0x08,在第一次按下时ucTrg1=ucRead=0x08,但持续按下时
   ucTrg1又会变为0x00,所以可以避免一次按下多次触发
   **/
   ucRead=(KEYPORT)^0xff;
   ucTrg1=ucRead&(ucRead^ucCont);
   ucCont=ucRead;
}
  2.2 Led初始化和修改

       由于LED和LCD界面在密码正确时同步变化,写在同个函数也行。需要注意LED得在循环内随时设置为灭和锁存状态,保证LCD修改时不会干扰LED。

  led.h/led.c
#ifndef __LED_H__
#define __LED_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

void LED_Init(void);
void LED_LCD_Control();
/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif
#endif /*__ GPIO_H__ */
#include "led.h"
#include "key.h"
#include "lcd.h"

#define LED1 GPIOC,GPIO_PIN_8
#define LED2 GPIOC,GPIO_PIN_9
#define LOCK GPIOD,GPIO_PIN_2
/* USER CODE BEGIN 0 */
extern int past_password[3];
extern uint8_t keyword[3];
extern uint8_t errorTime,OkKey;
uint32_t led1Tick=0,led2Tick1=0,led2Tick2=0;

extern void PWM_Change(uint32_t freq,float D);
extern void PWM_Display();

void LED_Init(void)
{
  
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
	
	
  
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
                          |GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_SET);
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);

  /*Configure GPIO pins : PC13 PC14 PC15 PC8
                           PC9 PC10 PC11 PC12 */
  GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
                          |GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  /*Configure GPIO pin : PD2 */
  GPIO_InitStruct.Pin = GPIO_PIN_2;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
	
  
}


/***  密码输入和确认后LED、LCD、PWM输出改变   ***/
uint8_t tempFlag2=1;

void LED_LCD_Control()
{
	
  uint8_t passFlag=0;
	
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
                          |GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_SET);
  HAL_GPIO_WritePin(LOCK,GPIO_PIN_SET);//解锁
  HAL_GPIO_WritePin(LOCK,GPIO_PIN_RESET);
	
	
  for(int i=0;i<3;i++)
   if(keyword[i]==past_password[i])
		 passFlag++;
	/***  5秒亮灯  ***/
	if(passFlag==3 && OkKey==1)
	{
		
		HAL_GPIO_WritePin(LED1,GPIO_PIN_RESET);
		HAL_GPIO_WritePin(LOCK,GPIO_PIN_SET);//解锁
		HAL_GPIO_WritePin(LOCK,GPIO_PIN_RESET);
        
        //PWM、LCD界面切换
	    PWM_Change(2000,0.1);
		PWM_Display();
		//这里的延时能让当前线程停止,按键无响应,保持输出2KHzPWM.
	    HAL_Delay(5000);
		
		OkKey=0;//清空确认位
		PWM_Change(1000,0.5);
		LCD_Clear(Black);
		
	}
	/***   密码三次以上错误  ***/
	if(errorTime>=3 && OkKey==1)
	{
	    HAL_GPIO_WritePin(LOCK,GPIO_PIN_SET);//解锁
		HAL_GPIO_WritePin(LED2,GPIO_PIN_RESET); //亮
		HAL_GPIO_WritePin(LOCK,GPIO_PIN_RESET);
		HAL_Delay(100);
		HAL_GPIO_WritePin(LOCK,GPIO_PIN_SET);//解锁
		HAL_GPIO_WritePin(LED2,GPIO_PIN_SET);   //灭
		HAL_GPIO_WritePin(LOCK,GPIO_PIN_RESET);
		//led2Tick1=uwTick;
       //确保第一次延时
		if(tempFlag2==1)
		{
			led2Tick2=uwTick;
			tempFlag2=0;
		}
		if(uwTick-led2Tick2<=5000)return;
		
	    led2Tick2=uwTick;
		errorTime=0;
		OkKey=0;
		tempFlag2=1;
	}	

}
  2.3 PWM频率和占空比修改

       初始化在生成后的tim.c中,修改占空比函数如下;

/**** 方波频率(HZ)和占空比修改  **/
void PWM_Change(u32 freq,float D)
{
 //设置重装载数值改变频率
  __HAL_TIM_SetAutoreload(&htim2,(int)(1000000.0/freq)-1);
 //设置CCR值修改占空比
  __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,(int)(D*(1000000.0/freq)));
 //更新定时器
    HAL_TIM_GenerateEvent(&htim2,TIM_EVENTSOURCE_UPDATE);

}
  2.4 串口修改密码

        由于只需要接收密码设定和判断是否输入了正确的密码,可使用C库中的int sscanf()函数将接收到的字符串转为整数数组元素,再来判定每个值是否相等,同时返回值等于6(xxx-xxx)则证明解析每个值正确,当然也可以直接利用字符串相等的函数int strcmp()(相等返回0),密码长度很长时建议使用后者,这里使用前者即可。接收串口输入函数可用HAL_UART_Receive(),想调试是否修改密码情况可以在LCD上显示,也可用串口发送返回到上位机。

/***  串口设定密码  ***/
int inputBool=0;
void setPassword()
{
   passFlag=0;
   HAL_UART_Receive(&huart1,(u8*)result,sizeof(result)-1,50);
   //now_password作为上位机输入的密码(修改密码的许可),setword为要设定的密码值
   inputBool=sscanf(result,"%1d%1d%1d-%1d%1d%1d",
	                 &now_password[0],&(now_password[1]),&(now_password[2]),&(setword[0]),&(setword[1]),&(setword[2])); 
   //判断是否解析成功(格式正确)
   if(inputBool==6)	
	  for(int i=0;i<3;i++)
		 if(now_password[i]==past_password[i])passFlag++;
		
	 //如果格式正确输入密码也正确,则显示成功修改1秒,这里只是作为调试
	 if(passFlag==3)
	 {
		 LCD_DisplayStringLine(Line8,(u8*)"Success");
		 for(int i=0;i<3;i++)
	      past_password[i]=setword[i];
		 HAL_Delay(1000);
		 LCD_ClearLine(Line8);
	 }
	
}

       发送数据(只作为调试),将past_password密码值传入data发送到上位机即可知道原有的密码past_password是否改为setword。

u32 usartTick=0;
void Usart_Send(u8* data)
{
	
  if(uwTick-usartTick<500)return;
  HAL_UART_Transmit(&huart1,data,strlen(data),50);
  usartTick=uwTick;

}
  2.5 main.c主要代码(不包含系统时钟配置等,这些Cube自动生成) 

        这里笔者忘记还有密码要初始值为“@”的要求,直接用LCD_DisplayChar即可,lcd.c/h和font.h竞赛时资料会发放。

main.c
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "lcd.h"
#include "led.h"
#include "key.h"
#include "stdio.h"
#include "string.h"
/*** 初始参数 ***/
unsigned char PSDString[3][20]={{0}},PWM_String[2][20]={{0}};
extern uint8_t key[3];
extern uint8_t keyword[3];
extern uint8_t errorTime;
char result[8];
/***  串口输入设定密码 、原密码、当前输入密码、是否匹配标志 ***/
int setword[3]={0,0,0},past_password[3]={1,2,3},now_password[3]={0,0,0};
u8 passFlag=0;
/***         ***/
void SystemClock_Config(void);
/***  自定义函数  ***/
void System_Init();
void setPassword();
void PSD_Display();
void PWM_Display();
void PWM_Change(u32 freq,float D);
/***/

int main(void)
{

  HAL_Init();
  
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  System_Init();
  /* USER CODE END SysInit */
  
  while (1)
  {
    /* USER CODE END WHILE */
	  PSD_Display();
		
    /* USER CODE BEGIN 3 */
  }
	
  /* USER CODE END 3 */
}
/***   系统初始化   **/
void System_Init()
{
  LCD_Init();
  KEY_Init();
  USART1_UART_Init();
  TIM2_Init();
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
	
  LCD_Clear(Black);
  LCD_SetBackColor(Black);
  LCD_SetTextColor(White);

  LED_Init();
	
}

/***  串口设定密码  ***/
int inputBool=0;
void setPassword()
{
   passFlag=0;
	 HAL_UART_Receive(&huart1,(u8*)result,sizeof(result)-1,50);
	
   inputBool=sscanf(result,"%1d%1d%1d-%1d%1d%1d",
	                 &now_password[0],&(now_password[1]),&(now_password[2]),&(setword[0]),&(setword[1]),&(setword[2])); 
   //判断是否解析成功(格式正确)
	 if(inputBool==6)	
	  for(int i=0;i<3;i++)
		 if(now_password[i]==past_password[i])passFlag++;
		
	 //如果格式正确输入密码也正确,则显示成功修改1秒
	 if(passFlag==3)
	 {
		 LCD_DisplayStringLine(Line8,(u8*)"Success");
		 for(int i=0;i<3;i++)
	   past_password[i]=setword[i];
		 HAL_Delay(1000);
		 LCD_ClearLine(Line8);
	 }
	
}
/*****    密码输入界面    ***/
void PSD_Display()
{
   LCD_DisplayStringLine(Line1,(u8*)"       PSD");
   setPassword();
   KEY_Input();
	 for(int i=0;i<3;i++)	
    sprintf((char*)PSDString[i],"    B%d:%d",i,key[i]);
	 
   LCD_DisplayStringLine(Line3,PSDString[0]);
   LCD_DisplayStringLine(Line4,PSDString[1]);
   LCD_DisplayStringLine(Line5,PSDString[2]);	
   LED_LCD_Control();		

}
/*****           */
/**** 方波参数显示界面   **/
void PWM_Display()
{
  LCD_Clear(Black);
  LCD_SetBackColor(Black);
  LCD_SetTextColor(White);
  
  LCD_DisplayStringLine(Line1,(u8*)"       STA");
  LCD_DisplayStringLine(Line3,(u8*)"    F:2000HZ");
  LCD_DisplayStringLine(Line4,(u8*)"    D:10%");

}
/*****/
/**** 方波频率(HZ)和占空比修改  **/
void PWM_Change(u32 freq,float D)
{
  __HAL_TIM_SetAutoreload(&htim2,(int)(1000000.0/freq)-1);
  __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,(int)(D*(1000000.0/freq)));
  HAL_TIM_GenerateEvent(&htim2,TIM_EVENTSOURCE_UPDATE);

}


 三、 调试结果


图  3-1  未确认开发板状态

       


图 3-2  密码正确后开发板状态
 

图 3-3  密码三次输入错误开发板状态
 

四、结语和注意事项

     笔者第一次写博客,内容和排版较为粗糙,旨在分享内容和记录学习过程,希望文章对现在和未来的小伙伴能够有所帮助。注意:在只有一颗MCU的单片机上没办法多线程并行(虽然可以进行并发操作),所以在使用HAL_Delay()的时候会影响系统实时性,这里就可以借用滴答计时器的uwTick作为毫秒精确计数的方法,if语句执行也就几十纳秒,使用多的寄存器暂时存储当前的数对系统实时性影响几乎不计,尽量使用这种方式可以节省TIM资源,同时也不会像Delay函数一样中断线程。实际建立工程中,要注意代码规范、工程文件的分类性、可移植性,数据类型也需要注意,像8位还是32位数据对空间分配还是有影响的,单片机的flash空间也是有限的,写入数据时要注意量,像LCD写入图片数据量也不能太大。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值