第十二届蓝桥杯嵌入式国赛(赛后总结)

前言

笔者今年是第一次参加蓝桥杯,赛道为嵌入式设计,目前取得了国二的成绩,虽说不是最好,但从中学到了许多,收获了许多。今年我所使用的是STM32G431, 用的HAL库,这也是我第一次学习使用HAL库。参加本次比赛前,我已经学习了快一年的STM32,但注重在项目开发方面,参加此比赛是为了进一步巩固自己的32基础,同时参加比赛也认识了一些优秀的大佬,在此分享我个人的一些经验。

对于此次比赛总结:
对于省赛,我花了大大概两周的时间,三天左右学习了HAL库,之后将省赛所用的模块单独用HAL写了一遍,然后就去做往年省赛题,我做了四套题,感觉省赛难度不大,无非是堆函数,这些函数只要你能理解自个能敲出来,那都是没问题的,重要的考点在于你解决问题的逻辑,这个很难短时间突击,只能靠平时的积累。而且不要想象省赛那么难,其实我第一次看到省赛题时感觉也有点小挑战,但省一并不需要完完全全实现,只要实现70%就可了,全部实现是一定能获奖的。

对于国赛,期间各种比赛交错,计算机设计大赛、互联网+等等,还有学校的一些乱七八糟的事物,耽误了很多,国赛只准备了一周时间,和国赛一样,把可能考的模块单独写一遍,再做往年题,但我只做了两套。国赛期间,因为我的捕获函数开启了HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_2),而不是HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);,两个字母之差,这个问题我找了快两个小时,他们的寄存器都有数据,但一个能进回调函数,一个不能进,因为这里耽误,导致最后一个功能没有完成,失之锅一。这里说下,国一的水平基本上是全部功能实现的水平。对于客观题只能靠平时积累和往年题。

对于其比赛大家一定要懂得是,这个比赛是一个类似于算法题的考试,单位时间内尽全力解题。而且比赛成绩的判定是看你的.axf文件,也就是看现象看结果,所以实现过程不要过度追求完美、复杂,能用就行,不出问题就行。我准备的单一功能实现代码就是追求能用就行,确保到比赛时我不会写错,能够以最短的时间完成符合的现象。就比如串口接收,我平时做项目会用DMA,但比赛我不会用,因为用这个会增加我的复杂度,进而错误率也会增大,所以在准备能简单就简单。

在我看来,蓝桥杯嵌入式适用于初学者学习STM32、已有经验者巩固基础、往年单片机组晋升的同学来参加,但不要把所有的精力都放在这一个比赛上,要结合自身当前情况,将比赛融入自身的学习路线中,比赛是为自个服务的,而不能为了比赛而比赛。

以下是我国赛准备期间单一功能的实现代码,大家可以收藏看看,这些代码可能不是最优解,但在我看来是最快最简单最粗暴解,很容易在比赛中套用。有些不懂可以私聊哈,相互学习!

串口通信

重写printf函数

int fputc(int ch, FILE *f)
{
    
    
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
	return(ch);
}

中断接收字符串函数

使用前一定开启中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);

void USART1_IRQHandler(void)
{
    
    
  static u8 index = 0;
	if(__HAL_UART_GET_FLAG( &huart1, UART_FLAG_RXNE ) != RESET)
	{
    
    		
    ch=( uint16_t)READ_REG(huart1.Instance->RDR);
		rxbuff[index] = ch;
		
		if(rxbuff[index] == '\n')
	    {
    
    
			rxbuff[index] = '\0';//将\n字符清零
			rxbuff[index-1] = '\0';//将\r字符清零
			index = 0;//数组下标放到最前面
			strcpy(temp,rxbuff);  //拷贝函数,将rxbuff送给temp
			memset(rxbuff,0,sizeof(rxbuff));    //将rxbuff清0
			return ;//接收结束直接返回
		}
		index++;
	}
}

IIC EPROM

Cube设置

将PB6、PB7模式调节为Output Push Pull,No pull-up and no pull-down,速度High

读取函数

uint8_t M24C02_Read(unsigned char address)
{
    
    
	unsigned char val;
	
	I2CStart();			//IIC开始
	I2CSendByte(0xa0); 	//测试状态
	I2CWaitAck();				//等待相应
	
	I2CSendByte(address);	//发送要读取的地址
	I2CWaitAck();					//等待相应
	
	I2CStart();
	I2CSendByte(0xa1);		//告诉24C02要读数据
	I2CWaitAck();					//等待相应
	val = I2CReceiveByte();	//返回要读取的值
	I2CWaitAck();					//等待相应
	I2CStop();						//停止
	
	return val;
}

发送函数

void M24C02_Write(unsigned char address, uint16_t info)
{
    
    
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	
	I2CSendByte(address);
	I2CWaitAck();
	
	I2CSendByte(info);
	I2CWaitAck();
	I2CStop();
}

ADC

获取ADC数字(通用)

uint16_t Get_ADC(uint32_t ch)   //eg:ADC_CHANNEL_17
{
    
    
ADC_ChannelConfTypeDef sConfig = {
    
    0};
  sConfig.Channel = ch;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    
    
    Error_Handler();
  }
	HAL_ADC_Start(&hadc2);
	HAL_ADC_PollForConversion(&hadc2,10);  //很重要!!
	return (uint16_t)HAL_ADC_GetValue(&hadc2);
}

简易

uint16_t getADC(void)
{
    
    
	uint16_t adc = 0;
	HAL_ADC_Start(&hadc2);
	adc = HAL_ADC_GetValue(&hadc2);
	return adc;
	
}

数字滤波

u16 Get_Adc_Average(u32 ch,u8 times)
{
    
    
	u32 temp_val=0;
	u8 t;
	for(t=0;t<times;t++)
	{
    
    
		temp_val+=Get_ADC(ch);
		HAL_Delay(5);
	}
	return temp_val/times;
} 

内部定时器

配置

  • Clock Source选择Internal Clock.
  • Counter Period 设为 999

实现

//定时1ms
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    
    
	static uint16_t TIM4_Cnt1 = 0;
	static uint16_t SEG_Cnt1 = 0;
	static uint16_t Print_Cnt1 = 0;
	static uint16_t LED_Cnt1 = 0;
	
	if(htim->Instance == TIM4){
    
    
		TIM4_Cnt1++;
		
		if(TIM4_Cnt1 >= 100){
    
    
			TIM4_Cnt1 = 0;
			Time++;
		}	
	}
}

注意事项: 一定要初始化

HAL_TIM_Base_Start_IT(&htim4);

按键

中断触发

void EXTI0_IRQHandler(void)
{
    
    
	if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET){
    
    
		Key1_Flag = !Key1_Flag;
		Key2_Flag = 0;
		HAL_Delay(4);
		__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
	}
}

纯软件实现

//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{
    
    
    static u8 key_up=1;     //按键松开标志
    if(mode==1)key_up=1;    //支持连按
    if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
    {
    
    
        delay_ms(10);
        key_up=0;
        if(KEY0==0)       return KEY0_PRES;
        else if(KEY1==0)  return KEY1_PRES;
        else if(KEY2==0)  return KEY2_PRES;
        else if(WK_UP==1) return WKUP_PRES;          
    }else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
    return 0;   //无按键按下
}

简易实现

uint8_t KeyScan(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
    
    
	if( HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == RESET){
    
    
		while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == RESET);
		return 2;
	}
	else
		return 1;
}

判断长按短按

简单那来说就是套两层软件按键检测,按键第一次time = 0,开始计数。

void KeyScan(){
    
    
	static u8 key_up=1;     //按键松开标志
	static u8 key_ups=1; 
	if(key_ups && (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET
								|| HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == RESET )){
    
    
		Time = 0;	
		key_ups = 0;
		N_Flag = 1;
	}else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == SET
								&& HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == SET){
    
    
		key_ups = 1;
	}
		if(Time >= 8) mode = 1;
		else mode = 0;			
		if(Time > 5000) Time = 0;
	if(mode == 1) key_up = 1;
	if(key_up && (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET
								|| HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == RESET)){
    
    
		key_up = 0;
	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == RESET){
    
    
		}
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == RESET){
    
    
		    }
	}
	else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == SET
			&& HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == SET){
    
    
		key_up = 1;
	}
	}

PWM捕获(求频率或占空比)

规范操作

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    
    
	if(htim -> Instance == TIM3)
	{
    
    
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
		{
    
    
			if(CaptureNumber == 0)
			{
    
    
				/* Get the Input Capture value */
				IC3ReadValue1 = TIM3->CCR2;
				CaptureNumber = 1;
				__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);
			}
			else if(CaptureNumber == 1)
			{
    
    
				/* Get the Input Capture value */
				IC3ReadValue2 = TIM3->CCR2;
				CaptureNumber = 2;
				__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);
				if (IC3ReadValue2 > IC3ReadValue1)
				{
    
    
					Capture_High = (IC3ReadValue2 - IC3ReadValue1); 
				}
				else
				{
    
    
					Capture_High = ((0xFFFF - IC3ReadValue1) + IC3ReadValue2); 
				}
				IC3ReadValue1 = IC3ReadValue2;
			}
			else if(CaptureNumber == 2)
			{
    
    
				/* Get the Input Capture value */
				IC3ReadValue2 = TIM3->CCR2;
				CaptureNumber = 0;
				if (IC3ReadValue2 > IC3ReadValue1)
				{
    
    
					Capture_Low = (IC3ReadValue2 - IC3ReadValue1); 
				}
				else
				{
    
    
					Capture_Low = ((0xFFFF - IC3ReadValue1) + IC3ReadValue2); 
				}
				/* Frequency computation */ 
				TIM3Freq = (uint32_t) 1000000 / (Capture_Low + Capture_High);
				TIM3Duty = Capture_High * 1.0 / (Capture_Low + Capture_High);
			}
		}
	}
}

记得使能

	HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);

简易(只能求频率)

uint32_t  cc1_value_2 = 0;  									// TIMx_CCR1 的值
uint32_t  RP2 = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    
    

    cc1_value_2 = __HAL_TIM_GET_COUNTER(&htim3);
    __HAL_TIM_SetCounter(&htim3, 0);
    RP2 = 1000000 / cc1_value_2;

    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}

外设部分


DS18b20

double GetTemp()
{
    
    
	u16 read = (ds18b20_read() & 0x07FF);
	return (read/16.);
}

数码管使用

原理图

在这里插入图片描述

数码管是共阴极的,所以当输入为1时,数码管亮。
74HC595是一个8位串行输入、并行输出的位移缓存器RCLK引脚:当RCLK到上升沿时,移位寄存器进入存储寄存器,也就是负责将数据传到数码管。
SCK 引脚到上升沿时:数据寄存器移位
SER 引脚是串行数据输入端
简单概括就是 SER负责写数据(0或1)然后写一位sck存储,然后sck移位,数据写完RCLK负责把数据传到数码管进行显示。

功能代码

显示函数

void SEG_DisplayValue(u8 Bit1, u8 Bit2, u8 Bit3)
{
    
    
	u8 i = 0;
	u8 code_tmp = 0;
	RCLK_L;
	
	code_tmp = Seg7[Bit3];
	for(i = 0; i < 8; i++){
    
    
		SCK_L;
		if(code_tmp & 0x80){
    
    
			SER_H;
		}
		else{
    
    
			SER_L;
		}	
		code_tmp = code_tmp << 1;
		SCK_L;
		SCK_H;
	}
	
	code_tmp = Seg7[Bit2];
	for(i = 0; i < 8; i++){
    
    
		SCK_L;
		if(code_tmp & 0x80){
    
    
			SER_H;
		}
		else{
    
    
			SER_L;
		}	
		code_tmp = code_tmp << 1;
		SCK_L;
		SCK_H;
	}
	
	code_tmp = Seg7[Bit1];
	for(i = 0; i < 8; i++){
    
    
		SCK_L;
		if(code_tmp & 0x80){
    
    
			SER_H;
		}
		else{
    
    
			SER_L;
		}	
		code_tmp = code_tmp << 1;
		SCK_L;
		SCK_H;
	}
	
	RCLK_L;
	RCLK_H;
	
	
}

宏定义和参数部分

#define RCLK_PIN		GPIO_PIN_2
#define SER_PIN			GPIO_PIN_1
#define SCK_PIN			GPIO_PIN_3

#define RCLK_H			HAL_GPIO_WritePin(GPIOA, RCLK_PIN, GPIO_PIN_SET)
#define RCLK_L			HAL_GPIO_WritePin(GPIOA, RCLK_PIN, GPIO_PIN_RESET)

#define SER_H			HAL_GPIO_WritePin(GPIOA, SER_PIN, GPIO_PIN_SET)
#define SER_L			HAL_GPIO_WritePin(GPIOA, SER_PIN, GPIO_PIN_RESET)

#define SCK_H			HAL_GPIO_WritePin(GPIOA, SCK_PIN, GPIO_PIN_SET)
#define SCK_L			HAL_GPIO_WritePin(GPIOA, SCK_PIN, GPIO_PIN_RESET)


uc8 Seg7[17] = {
    
     0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f,
							0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 0x00};

光敏

当作滑动变阻器即可

 tmp = getADC();
        snprintf((char *)str, sizeof(str), " R-P:%.2fK  ", tmp / (4096. - tmp) * 10);

PWM输出

调整输出占空比

__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1, 1000);

调整输出频率

__HAL_TIM_SET_AUTORELOAD(&htim2,999);

停止和开始

HAL_TIM_PWM_Stop(&htim2,TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);

三轴传感器

 ptr = Lis302DL_Output();
        x = ptr[0] * 18. / 1000;
        y = ptr[1] * 18. / 1000;
        z = ptr[2] * 18. / 1000;

猜你喜欢

转载自blog.csdn.net/qq_45628620/article/details/117753766