蓝桥杯嵌入式组第六届省赛题目深度解析:STM32G431RBT6源码实现详解
文章目录
前言:STM32G431RBT6实现嵌入式组第六届题目解析+源码,本文默认读者具备基础的stm32知识。文章末尾有第六届题目。
1.题目解析
1.1 分而治之,藕断丝连
我们如果要开发一个多外设协同的项目,开始应该做一个大致的流程图,将各个模块进行大致分析,理清模块间的关系,这样我们在写代码的过程中不至于毫无头绪。蓝桥杯给我们出的题目条理就很清晰,我们可以按照题目进行梳理。
函数将模块分而治之,变量使模块间藕断丝连。
1.2 模块化思维导图
下图根据题目梳理。我们可以将各个模块封装成一个函数例如rtc_process(),adc_process()…。封装成函数之后,各个模块间的条理清晰了,不至于代码多了,写代码的人都看不懂自己写的是什么东西了。
1.3 模块解析
当代码写多了之后会发现,使用到一个模块,常常也就那么几个作用,所以最重要的还是思维逻辑,如何做到多模块间的限制与配合,这才是最重要的。
1.3.1 RTC模块
这里的目的是,获取时间使用到hal库中的两个函数。
这两个函数必须的同时调用,而且GetTime()在GetDate()之前。
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0},
HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);
1.3.2 ADC模块
采集可调电位器电压。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
adc_vtg_val = HAL_ADC_GetValue(hadc);
}
1.3.3 IIC模块
完成eeprom中数据的读写。开发板的PB6和PB7设置为开漏输出,使用软件模拟实现单字节数据的读写。用到i2c_hal.c, i2c_hal.h。
具体实现看第二部分源码。
/* 软件模拟实现at24c02单字节写入 */
void at24c02_write(uint8_t addr, uint8_t data){
...
}
/* 软件模拟实现at24c02单字节读取 */
uint8_t at24c02_read(uint8_t addr){
...
}
/* i2c向eeprom写入data */
void i2c_write()
{
...
}
...
1.3.4 UART模块
UART实现数据接受,响应,和定时数据上报。
具体实现看第二部分源码。
//中断触发回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
...
}
//定时上报数据
void tim_inform_process()
{
...
}
1.3.5 LCD模块
将涉及到的数据显示到lcd屏幕上,用到lcd.c, lcd.h, fonts.h。
我这里使用判断语句显得有些杂乱无章,可以使用状态机改改。
具体实现看第二部分源码。
void lcd_process()
{
...
}
1.3.6 LED模块
0.2秒闪烁,使用tim2通用定时器完成0.2s的延时。
注意lcd屏幕也会使用到led涉及到的引脚,要注意PD2控制锁存器使能和失能的使用。
if(key_state.bits.B1 == 1 || (adc_vtg_val*3.3/4096)>i2c_val.num*3.3)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
GPIOC->ODR = 0xff00;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}else{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
GPIOC->ODR = 0xff00 ^ (led_state_flag << 8); //使用异或操作只改变PCB电平状态,其他保持高电平。
led_state_flag ^= 1;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
1.3.7 TIM模块
170MHz的频率,预分频值填写16,重装载寄存器填写1999999实现0.2ms时基。
产生0.2s的时基,定时使能adc, uart中断。
具体实现看第二部分源码。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
...
}
1.3.8 KEY模块
我使用的这种方法不是很好,为了实现按一次,按键状态改变一次(计数值加1),在多状态按键里面使用了while()循环等待按键回到高电平状态,造成不必要的cpu资源浪费。如果有更好的办法,请在评论区分享一下。
配置按键对应gpio口为上拉输入模式。
具体实现看第二部分源码。
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET); //while等待按键回到高电平状态
void key_process()
{
...
}
2.源码
我所有的实现都在main.c文件中。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "rtc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "lcd.h"
#include "i2c_hal.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
char lcd_vtg_str[30] = {0}, lcd_k_str[30] = {0},lcd_led_str[30] = {0},
lcd_rtc_str[30] = {0}, uart_rec[5] = {0}, uart_inform[32] = {0}; //lcd显示
RTC_DateTypeDef date = {0}; //rtc日期
RTC_TimeTypeDef time = {0}, //rtc时间
cmp_time = {0};
uint32_t adc_vtg_val = 0; //adc_vtg_val:adc采集接收
/*
led_state_flag:控制led0.2s闪烁
tim_blink_flag:控制上报时间设置时间位值闪烁
lcd_clear_flag:lcd页面转换清屏
i2c_rouse_flag:i2c写入唤醒
*/
uint8_t led_state_flag = 0, tim_blink_flag = 0, lcd_clear_flag = 0, i2c_rouse_flag = 0,
infrom_tim_flag = 1;
/*
按键状态联合体
Bi:按键Bi
*/
typedef union{
uint8_t keys;
struct{
uint8_t B1:1;
uint8_t B2:2;
uint8_t B3:4;
uint8_t B4:1;
}bits;
} keys_t;
keys_t key_state = {0};
/*
eeprom float写入联合体
*/
typedef union{
float num;
uint8_t bytes[4];
}float_t;
float_t analyze_val = {0}, i2c_val = {0.1};
void lcd_process();
void rtc_process();
void key_process();
void at24c02_write(uint8_t addr, uint8_t data);
uint8_t at24c02_read(uint8_t addr);
void i2c_write(float_t *data);
void i2c_read(float_t *data);
void tim_inform_process();
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear(Black);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_RTC_Init();
MX_TIM2_Init();
MX_ADC2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t ret;
HAL_TIM_Base_Start_IT(&htim2);
HAL_ADC_Start_IT(&hadc2);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t*)uart_rec, 5);
i2c_read(&analyze_val);
if(analyze_val.num < 0.1 || analyze_val.num > 0.9)
{
i2c_write(&i2c_val);
} else{
i2c_read(&i2c_val);
}
for(int i=0; i<4; i++)
{
analyze_val.bytes[i] = i2c_val.bytes[i];
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
rtc_process(); //获取rtc时间
key_process(); //按键状态监听
lcd_process(); //lcd显示控制
if(i2c_rouse_flag ==1)
{
i2c_write(&analyze_val); //i2c通信向eeprom写入数据
i2c_read(&i2c_val);
i2c_rouse_flag = 0;
}
//定时上报
tim_inform_process();
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV6;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* 获取rtc时间 */
void rtc_process()
{
HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);
}
/* lcd显示 */
void lcd_process()
{
if(key_state.bits.B2 == 0) //界面1
{
if(lcd_clear_flag == 0){ //条件清屏
lcd_clear_flag = 1;
LCD_Clear(Black);
}
sprintf(lcd_vtg_str, " V:%.2fV", adc_vtg_val*3.3/4096);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_vtg_str);
sprintf(lcd_k_str, " k%.1f", i2c_val.num);
LCD_DisplayStringLine(Line4, (uint8_t*)lcd_k_str);
if(key_state.bits.B1 == 0){
sprintf(lcd_led_str, " LED:ON ");
}else{
sprintf(lcd_led_str, " LED:OFF");
}
LCD_DisplayStringLine(Line5, (uint8_t*)lcd_led_str);
sprintf(lcd_rtc_str, " T:%2d-%2d-%2d", time.Hours, time.Minutes, time.Seconds);
LCD_DisplayStringLine(Line6, (uint8_t*)lcd_rtc_str);
}
else //界面2
{
if(lcd_clear_flag == 1){ //条件清屏
lcd_clear_flag = 0;
LCD_Clear(Black);
}
LCD_DisplayStringLine(Line2, " Setting");
if(key_state.bits.B3 == 0){
sprintf(lcd_rtc_str, " %2d-%2d-%2d", cmp_time.Hours, cmp_time.Minutes, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
else if(key_state.bits.B3 == 1){ //Hours位闪烁 && 设置Hours位值
if(tim_blink_flag == 1){
sprintf(lcd_rtc_str, " -%2d-%2d", cmp_time.Minutes, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
else{
sprintf(lcd_rtc_str, " %2d-%2d-%2d", cmp_time.Hours, cmp_time.Minutes, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
if(key_state.bits.B4 == 1){
key_state.bits.B4 = 0;
cmp_time.Hours ++;
if(cmp_time.Hours == 24) cmp_time.Hours = 0;
}
}
else if(key_state.bits.B3 == 2){ //Minutes位闪烁 && 设置Minutes位值
if(tim_blink_flag == 1){
sprintf(lcd_rtc_str, " %2d- -%2d", cmp_time.Hours, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
else{
sprintf(lcd_rtc_str, " %2d-%2d-%2d", cmp_time.Hours, cmp_time.Minutes, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
if(key_state.bits.B4 == 1){
key_state.bits.B4 = 0;
cmp_time.Minutes ++;
if(cmp_time.Minutes == 60) cmp_time.Minutes = 0;
}
}
else if(key_state.bits.B3 == 3){ //Seconds位闪烁 && 设置Seconds位值
if(tim_blink_flag == 1){
sprintf(lcd_rtc_str, " %2d-%2d- ", cmp_time.Hours, cmp_time.Minutes);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
else{
sprintf(lcd_rtc_str, " %2d-%2d-%2d", cmp_time.Hours, cmp_time.Minutes, cmp_time.Seconds);
LCD_DisplayStringLine(Line3, (uint8_t*)lcd_rtc_str);
}
if(key_state.bits.B4 == 1){
key_state.bits.B4 = 0;
cmp_time.Seconds ++;
if(cmp_time.Seconds == 60) cmp_time.Seconds = 0;
}
}
if(key_state.bits.B2 == 2){
key_state.bits.B2 = 0; //按下B2返回界面1
infrom_tim_flag = 1;
}
}
}
/* 按键状态获取 */
void key_process()
{
static uint32_t key_red = 0;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) //B1
{
if(HAL_GetTick() - key_red > 20) { //消抖
key_red = HAL_GetTick();
key_state.bits.B1 = 1;
}
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET) //B2
{
if(HAL_GetTick() - key_red > 20) {
key_red = HAL_GetTick();
key_state.bits.B2 += 1;
if(key_state.bits.B3 == 3) key_state.bits.B3 = 0;
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET); //保证按一次加一次
}
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET) //B3
{
if(HAL_GetTick() - key_red > 20) {
key_state.bits.B3 += 1;
if(key_state.bits.B3 == 4) key_state.bits.B3 = 0;
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET);
}
}
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) //B4
{
if(HAL_GetTick() - key_red > 20) {
key_red = HAL_GetTick();
key_state.bits.B4 = 1;
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
}
}
void tim_inform_process()
{
if(time.Hours == cmp_time.Hours && time.Minutes == cmp_time.Minutes && time.Seconds == cmp_time.Seconds)
{
if(infrom_tim_flag == 1){
infrom_tim_flag = 0;
sprintf(uart_inform, "%.2f+%.1f+%2d%2d%2d\n", adc_vtg_val*3.3/4096, i2c_val.num, cmp_time.Hours,
cmp_time.Minutes, cmp_time.Seconds);
HAL_UART_Transmit_IT(&huart1, (uint8_t*)uart_inform, strlen(uart_inform));
}
}
}
/* at24c02单字节写入 */
void at24c02_write(uint8_t addr, uint8_t data){
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
}
/* at24c02单字节读取 */
uint8_t at24c02_read(uint8_t addr){
uint8_t read_data=0;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
read_data = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return read_data;
}
/* 读取eeprom数据 */
void i2c_read(float_t *data)
{
uint8_t ret = 0;
for(int i=0;i<4;i++)
{
ret = at24c02_read(i+1);
data->bytes[i] = ret;
HAL_Delay(3);
}
}
/* i2c向eeprom写入data */
void i2c_write(float_t *data)
{
for(int i=0;i<4;i++)
{
at24c02_write(i+1, data->bytes[i]);
HAL_Delay(3); //rom的缺点必须带延时,读写比较慢
}
}
/* 定时器0.2s时基中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t temp = 0;
//led报警闪烁控制
if(key_state.bits.B1 == 1 || (adc_vtg_val*3.3/4096)>i2c_val.num*3.3)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
GPIOC->ODR = 0xff00;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}else{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
GPIOC->ODR = 0xff00 ^ (led_state_flag << 8);
led_state_flag ^= 1;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
HAL_ADC_Start_IT(&hadc2);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t*)uart_rec, 5);
//设置上报时间闪烁0.6s
temp++;
if(temp ==3){
tim_blink_flag ^=1;
temp = 0;
}
}
/* adc采集中断回调函数 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
adc_vtg_val = HAL_ADC_GetValue(hadc);
}
/* uart通信中断回调函数 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
HAL_UART_Transmit_IT(huart, "ok\n", 3);
sscanf(uart_rec, "k%f\n", &analyze_val.num); //解析浮点k数值
i2c_rouse_flag = 1;
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
3.第六届题目
作者::눈_눈: