十二届蓝桥杯嵌入式省赛题目,以下给出每个功能到代码,具体功能描述可以参考省赛真题。下面只给出系统框图。

端口配置 stm32Cube

题目系统框图

根据功能描述,下面我们配置一下stm32Cube。
在这里插入图片描述
以上配置了,按键模块PB0、PB1、PB2、PA0,USART1串口通信,锁存器PD2(注意:千万要打开,因为LED和LCD的端口复用,锁存器用于保存led模块的电平,防止LCD复用篡改电平),还有PA7定时器功能用于输出固定频率和占空比的脉冲信号。

代码目录

在这里插入图片描述

注意以下代码,bsp_system.h文件用于保存所有的外部变量以及文件
bsp_system.h

#ifndef __BSP_SYSTEM_H
#define __BSP_SYSTEM_H

#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#include "string.h"
#include "stdio.h"

#define Data 0  // 数据界面
#define Para 1  // 参数界面


#include "key_app.h"
#include "lcd_app.h"
#include "led_app.h"
#include "usart_app.h"
#include "gpio_init_app.h"
#include "tim_init_app.h"
#include "system_init.h"

extern _Bool lcd_state;  //屏幕状态

extern u8 CNBR_CNT;    // 初始两种类型车为0辆
extern u8 VNBR_CNT;
extern u8 IDLE_CNT;    // 初始空闲车辆为8辆

extern float CNBR_Price;  // 两种车辆计费价格
extern float VNBR_Price;

extern u8 ctr_state;
extern u8 rec_buff[2];   // 串口接收缓冲区


#endif

系统初始化

上电时候,led需要保持关闭状态
system_init.c

#include "system_init.h"
#include "bsp_system.h"


/**
	@ 系统初始化
**/
void system_init(void)
{
	// 清除高八位
    GPIOC->ODR &= 0x00ff;
	// 将高八位拉高
	GPIOC->ODR |= ~(0x00<<8);
	// 锁存器 打开
	GPIOD->BSRR |= (0x01<<2);
	// 锁存器 关闭
	GPIOD->BRR |= (0x01<<2);
}

system_init.h

#ifndef __SYSTEM_INIT_H
#define __SYSTEM_INIT_H
void system_init(void);
#endif

LCD界面

lcd_app.c

#include "lcd_app.h"
#include "bsp_system.h"

#define Data 0  // 数据界面
#define Para 1  // 参数界面

_Bool lcd_state = 0;
u8 CNBR_CNT = 0;    // 初始两种类型车为0辆
u8 VNBR_CNT = 0;
u8 IDLE_CNT = 8;    // 初始空闲车辆为8辆

float CNBR_Price = 3.50;  // 两种车辆计费价格
float VNBR_Price = 2.00;

void LCD_Process(void)
{
    //u8 display_buff[15];
    if(lcd_state == Data)
    {
		u8 display_buff[15];
        sprintf((char *)display_buff,"       Data   ");
		
        LCD_DisplayStringLine(Line1,display_buff);

        sprintf((char *)display_buff,"   CNBR:%d   ",CNBR_CNT);
        LCD_DisplayStringLine(Line3,display_buff);

        sprintf((char *)display_buff,"   VNBR:%d   ",VNBR_CNT);
        LCD_DisplayStringLine(Line5,display_buff);

        sprintf((char *)display_buff,"   IDLE:%d   ",IDLE_CNT);
        LCD_DisplayStringLine(Line7,display_buff);

    }
    if(lcd_state == Para)
    {
		u8 display_buff[15];
        sprintf((char *)display_buff,"       Para   ");
        LCD_DisplayStringLine(Line1,display_buff);

        sprintf((char *)display_buff,"   CNBR:%-5.2f   ",CNBR_Price);
        LCD_DisplayStringLine(Line3,display_buff);

        sprintf((char *)display_buff,"   VNBR:%-5.2f   ",VNBR_Price);
        LCD_DisplayStringLine(Line5,display_buff);

        LCD_ClearLine(Line7);
    }

}

lcd_app.h

#ifndef _LCD_APP_H
#define _LCD_APP_H
void LCD_Process(void);

#endif

以下是实验现象
上述代码中定义了两个界面状态参数,Para和Data,当按键按下时候会自动切换界面,按键模块下面部分会给出。
在这里插入图片描述
在这里插入图片描述

按键模块

key_app.c
此模块会写出四个按键具体的功能,使用滴答定时器用于按键消抖
其中B4按键功能为使PA7端口输出频率为2000HZ占空比为20%的脉冲信号和低电平之间来回切换

#include "key_app.h"
#include "bsp_system.h"

#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define KEYPORT KB1 | (KB2<<1) | (KB3<<2) | (KB4<<3)| 0xf0

unsigned char Trg; // 单次触发
unsigned char Con; // 防止长按

void KEY_Read(void)
{
    unsigned char ReadData = (KEYPORT)^0xff;
    Trg = ReadData&(ReadData^Con);
    Con = ReadData;
}


u32 key_tick = 0;
u8 ctr_state = 0;

void KEY_Process(void)
{
	
	if(uwTick - key_tick < 20) return;
	
	key_tick = uwTick;
	
	KEY_Read();
	
	if(Trg & 0x01)   // B1键
	{
		lcd_state = !lcd_state;
		//lcd_state = Para;
	}
	
	if(Trg & 0x02)
	{
		if(lcd_state == Para)
		{
			CNBR_Price+=0.5f;
			VNBR_Price+=0.5f;
		}
	}
	if(Trg & 0x04)
	{
		if(lcd_state == Para)
		{
			if(CNBR_Price >=0.5f && VNBR_Price >= 0.5f)
			{
				CNBR_Price-=0.5f;
				VNBR_Price-=0.5f;
			}

		}
	}
	if(Trg & 0x08)
	{
		if(ctr_state == 0)
		{
			TIM_Init();
			ctr_state = 1;
			
		}else if(ctr_state == 1)
		{
			GPIO_Init();
			ctr_state = 0;
		}

	}
}

key_app.h

#ifndef __KEY_APP_H
#define __KEY_APP_H

#include "bsp_system.h"

void KEY_Read(void);
void KEY_Process(void);
#endif

切换PA7输出脉冲信号和低电平功能

gpio_init_app.c
初始化PA7端口为低电平,不过第一行需要停止PWM的输出,因为在主函数中使能了PWM输出,所以在按键切换时,首先就要把它停止,在初始化写入低电平。

#include "gpio_init_app.h"
#include "bsp_system.h"


void GPIO_Init(void)
{
    // 停止PA7的PWM信号输出
    HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);
	
	// 初始化GPIO端口  配置为输出模式
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
	
    GPIO_InitStruct.Pin =  GPIO_PIN_7;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

	HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	// 将PA7写入低电平
	HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET);
}

gpio_init_app.h

#ifndef __GPIO_INIT_APP_H
#define __GPIO_INIT_APP_H

void GPIO_Init(void);
#endif

tim_init_app.c
初始化定时器,配置为频率为2000HZ,占空比为20%的脉冲信号

#include "tim_init_app.h"
#include "bsp_system.h"

TIM_OC_InitTypeDef sConfigOC = {0};

void TIM_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
	
	GPIO_InitStruct.Pin = GPIO_PIN_7;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	
	HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	htim3.Instance = TIM3;
	htim3.Init.Prescaler = 80 - 1;
	htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
	htim3.Init.Period = 500-1;
	htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
	htim3.Init.AutoReloadPreload = TIM_AUTOMATICOUTPUT_ENABLE;
	
	HAL_TIM_PWM_Init(&htim3);
	sConfigOC.OCMode = TIM_OCMODE_PWM1;
	sConfigOC.Pulse = 100-1;
	sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
	sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
	HAL_TIM_PWM_ConfigChannel(&htim3,&sConfigOC,TIM_CHANNEL_2);
	
	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
	
}

tim_init_app.h

#ifndef __TIM_INIT_APP_H
#define __TIM_INIT_APP_H

void TIM_Init(void);
#endif

LED模块

led_app.c
这个功能没有什么太多,按照描述启动停止就行。

#include "led_app.h"

void LED_Ctr(u8 ctr)
{
	HAL_GPIO_WritePin(GPIOC,0xff00,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);	
	
	HAL_GPIO_WritePin(GPIOC,ctr<<8,GPIO_PIN_RESET);   //左移八位,因为led端口是高八位的
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}


u8 led_ctr = 0x00;

void LED_Process(void)
{
	if(IDLE_CNT != 0)
	{
		led_ctr|=0x01;  // led1点亮
	}else
	{
		led_ctr&=~0x01;
	}
	
	if(ctr_state == 1)
	{
		led_ctr|=0x02;	//led2点亮
	}else{
		led_ctr&=~0x02;
	}
	
	LED_Ctr(led_ctr);
}

led_app.h

#ifndef __LED_APP_H
#define __LED_APP_H

#include "bsp_system.h"

void LED_Ctr(u8 ctr);
void LED_Process(void);

#endif

USART串口功能模块

这个功能模块,我觉得是所以功能中最难的,以下是具体代码,注释也很详细,可以对照参考。
usart_app.c

#include "usart_app.h"

u8 rx_buf[23];
u8 rx_cnt = 0;
u32 uart_tick = 0;

u8 tem_code[5]; // 记录车辆编号
u8 car_out_pos = 0;
u8 car_IDLE_pos = 0;;

long fee_time_sec;  // 停留时间  秒
int fee_hour;

_Bool validata_command_format(u8* str);
u8 check_entry_leave(u8* str);
u8 check_IDLE_Pos(void); // 查找空闲位置

typedef struct{
	u8 now_Year;
	u8 now_Month;
	u8 now_Day;
	u8 now_Hour;
	u8 now_Minute;
	u8 now_Second;
}time_data;    // 记录时间结构体

typedef struct{
	u8 type[5];
	u8 code[5];
	u8 in_year;
	u8 in_month;
	u8 in_day;
	u8 in_hour;
	u8 in_minute;
	u8 in_second;
	u8 pos;
}Type_Car_INTO;// 车辆信息

Type_Car_INTO car_into[8];//8个位置
time_data store_time; // 时间结构体

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	uart_tick = uwTick;
	rx_buf[rx_cnt++] = rec_buff[0];    // 接收的数据存入字符串数组
	
	HAL_UART_Receive_IT(&huart1,rec_buff,1);
}

void UART_Process(void)
{ 
	if(rx_cnt == 0) return ;   // 如果缓冲区无数据就直接返回,有数据时候才回去解析
	
	if(uwTick - uart_tick < 50) return;
	
	uart_tick = uwTick;
	
	rx_buf[rx_cnt] = '\0';
	rx_cnt = 0;    // 索引归0,字符串数组暂时接收完成
	
	_Bool format_state = validata_command_format(rx_buf);
	
	if(format_state == 1) // 格式正确
	{
		tem_code[0] = rx_buf[5]; tem_code[1] = rx_buf[6]; tem_code[2] = rx_buf[7];tem_code[3] = rx_buf[8];
		car_out_pos = check_entry_leave(tem_code);
		printf("%d\r\n",car_out_pos);
		if(car_out_pos == 0) // 有空闲
		{
			printf("car in");
			// 查找空闲车位
			car_IDLE_pos = check_IDLE_Pos();
			if(car_IDLE_pos == 0)
			{
				// 位置已经满了
				printf("ERROR\r\n");
				return ;
			}
			car_into[car_IDLE_pos-1].type[0] = rx_buf[0]; car_into[car_IDLE_pos-1].type[1] = rx_buf[1]; car_into[car_IDLE_pos-1].type[2] = rx_buf[2]; car_into[car_IDLE_pos-1].type[3] = rx_buf[3];
			car_into[car_IDLE_pos-1].code[0] = tem_code[0];car_into[car_IDLE_pos-1].code[1] = tem_code[1];car_into[car_IDLE_pos-1].code[2] = tem_code[2];car_into[car_IDLE_pos-1].code[3] = tem_code[3];
			car_into[car_IDLE_pos-1].in_year = store_time.now_Year;
			car_into[car_IDLE_pos-1].in_month = store_time.now_Month;
			car_into[car_IDLE_pos-1].in_day = store_time.now_Day;
			car_into[car_IDLE_pos-1].in_hour = store_time.now_Hour;
			car_into[car_IDLE_pos-1].in_minute = store_time.now_Minute;
			car_into[car_IDLE_pos-1].in_second = store_time.now_Second;
			car_into[car_IDLE_pos-1].pos = 1;
			if(rx_buf[0] == 'C')
			{
				printf("car in");
				CNBR_CNT++;
				IDLE_CNT--;
			}
			if(rx_buf[0] == 'V')
			{
				VNBR_CNT++;
				IDLE_CNT--;
			}
			
		}else  // 离开
		{
			if(rx_buf[0] != car_into[car_out_pos-1].type[0])
			{
				printf("Error");
				return ;
			}
			printf("car_out");
			fee_time_sec = (store_time.now_Year - car_into[car_out_pos-1].in_year)*365*24*60*60+
							(store_time.now_Month - car_into[car_out_pos-1].in_month)*30*24*60*60+
							(store_time.now_Day - car_into[car_out_pos-1].in_day)*24*60*60 +
							(store_time.now_Hour - car_into[car_out_pos-1].in_hour)*60*60+
							(store_time.now_Minute - car_into[car_out_pos-1].in_minute)*60+
							(store_time.now_Second - car_into[car_out_pos-1].in_second);
			if(fee_time_sec < 0)
			{
				printf("Error\r\n");
				return ;
			}
			
			fee_hour = (fee_time_sec + 3599) / 3600;
			if(rx_buf[0] == 'C')
			{
				CNBR_CNT--;
				IDLE_CNT++;
				float CNBR_fee = fee_hour*CNBR_Price;
				
			}
			if(rx_buf[0] == 'V')
			{
				VNBR_CNT--;
				IDLE_CNT++;
				float CNBR_fee = fee_hour*VNBR_Price;
			}
			memset(&car_into[car_out_pos-1],'\0',sizeof(car_into[car_out_pos-1]));
		}
		
	}else{
		printf("Error\r\n");   //格式错误输出ERROR  然后返回掉
		return	;
	}
	
}

// CNBR:A392:200202120000
_Bool validata_command_format(u8* str)
{
	printf("%s\r\n",str);
	
	if(strlen((char *)str)!=22 || str[4] != ':' || str[9] != ':')
	{
		printf("Error1\r\n");
		return 0;
		
	}
	if(strncmp((char *)str,"CNBR",4)!=0 && strncmp((char *)str,"VNBR",4) !=0)
	{
		printf("Error\r\n");
		return 0;
	}
	for(int i = 10;i<=21;i++)
	{
		if(str[i] < '0' || str[i]>'9')
		{
			return 0;
		}
	}
	store_time.now_Year = (str[10] - '0') *10 + str[11] - '0';
	store_time.now_Month = (str[12] - '0') * 10 + str[13] - '0';
	store_time.now_Day = (str[14] - '0') * 10 + str[15] - '0';
	store_time.now_Hour = (str[16] - '0') * 10 + str[17] - '0';
	store_time.now_Minute = (str[18] - '0') * 10 + str[19] - '0';
	store_time.now_Second = (str[20] - '0') * 10 + str[21] - '0';
	
	printf("OK\r\n");
	return 1;
}

u8 check_entry_leave(u8* str)
{
	for(int i = 0;i<8;i++)
	{
		if(car_into[i].code[0] == str[0] && car_into[i].code[1] == str[1] && car_into[i].code[2] == str[2] &&car_into[i].code[3] == str[3])
		{
			return (i+1);
		}
	}
	return 0; // 找到空闲位置
}

u8 check_IDLE_Pos(void)
{
	for(int i = 0;i<8;i++)
	{
		if(car_into[i].pos == 0)
		{
			return (i+1);
		}
	}
	return 0;
}

usart_app.h

#ifndef __USART_APP_H
#define __USART_APP_H

#include "bsp_system.h"

_Bool validata_command_format(u8* str);
void UART_Process(void);

#endif

main函数

这里只给出,main中的代码,上面定义了几个变量,上述代码中都有提到,比如串口中断接收缓冲区,报错的化自行定义就可。

int main(void)
{


  HAL_Init();


  SystemClock_Config();


  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();

	
	LCD_Init();
	system_init();

	
	LCD_Clear(Black);
	LCD_SetBackColor(Black);
	LCD_SetTextColor(White);
	
	HAL_UART_Receive_IT(&huart1,rec_buff,1);
	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
	
	GPIO_INIT();  // 上电初始化  PA7输出低电平

  while (1)
  {
	  LCD_Process();
	  KEY_Process();
	  LED_Process();
	  UART_Process();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

完结

WhoIam
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐