【备战蓝桥杯】蓝桥杯十四届省赛真题

1. 准备工作
(1)安装STM32CubeMX
Step 1 :官网下载地址:https://www.st.com/en/development-tools/stm32cubemx.html
在这里插入图片描述在这里插入图片描述

Step2 : 点击"Get Software" --> “STM32CubeMX-Win”(根据系统选择对应的) --> “Get latest” --> “Accept” --> 个人信息随意填一下
(2) 安装STM32G431芯片包
在上一篇【备战蓝桥杯】中有写如何下载对应的芯片包,下载好后是一个‘.pack’的文件,双击自动安装即可。再次打开keil5,会提示设备已经发生改变(devices has been modified类似的),确定即可更新完成。
(3) 将比赛提供的lcd驱动代码复制到自己的工程里
‘Src’里的‘lcd.c’、‘Inc’里的‘lcd.h’‘fonts.h’。
(4) 查找往年真题
到蓝桥杯官网,搜索框中‘真题’,第一个就是。
↓↓↓↓↓↓↓↓↓↓↓↓ 下面直接对照【第十四届省赛真题】来一步步实现功能 ↓↓↓↓↓↓↓↓↓↓↓↓

2. 配置32CubeMX
共包含以下几个部分:Pinout & Configuration, Clock Configuration, Project Manager。在这里插入图片描述
(1)引脚、时钟、定时器、ADC等配置

  • 按键
    题目中要求,B1, B2, B3, B4 四个按键要被使用到。通过查询《CT117E-M4产品手册》原理图可知,Bx(x=1 … 4) 对应着 PB0, PB1, PB2, PA0。所以:
    设置按键
    左键单击这四个引脚,选择‘GPIO_Input’,设置为输入模式。

  • 定时器
    题目中,通过PA1引脚输出PWM信号。PWM波有两个参数:频率和占空比。我们想调节一个周期内高电平、低电平的占比,这就需要用到定时器来计时了。
    在这里插入图片描述
    注意了,‘CHxN’这类字样的不可选。随后来到左侧下拉栏,选择‘Timers’–‘TIM2’
    在这里插入图片描述
    按照上图,将通道2选为PWM生成,然后设置预分频系数和重装载值。系统时钟是80MHz,我们想生成 xHz 的信号,需要使得80 000 000 / (预分频系数+1) / 自动重装载值 = Frequency,如图的系数设置最终计算得信号频率是4KHz,也就是题目要求的低频信号,如果要生成高频信号(8KHz),后续在代码中将重装载值修改为125即可。

  • ADC
    题目中3.1,3)要求“通过ADC功能检测R37上的模拟输出电压”,电位器R37拧一圈其实就是通过调节电阻的大小来改变分压。观察原理图,PB15.
    在这里插入图片描述
    将PB15勾选为ADC2_IN15,左侧把IN15 Single-ended勾一下,其他不用管。
    在这里插入图片描述
    另外,题目中要求PA7捕获输入脉冲,所以也将PA7引脚勾选为TIM2_CH2,同时左侧选择该通道为‘Input Capture direct mode’。
    在这里插入图片描述

  • 其他
    最后剩下的就是LED了,它们对应了PC8,…15引脚,将他们勾选为‘Output’输出类型,就不放图演示了。
    注意,PD2也要配置为输出模式。(将LED的这八个引脚初始化为高电平,作为熄灭初始化)
    原理图LED-PD2

在这里插入图片描述
(2) 配置时钟树
在这里插入图片描述
按照如图修改,可以看到最右侧的系统时钟频率是80MHz。
(3) 工程管理设置
最后一步,如何更改第2步的路径,在上一篇中有讲到。
在这里插入图片描述
最后,可以点击‘Generate Code’啦~

3. 书写逻辑代码
(1) 再啰嗦两句
在这里插入图片描述
‘GENERATE CODE’之后的代码如上图,我们写代码的时候一定要在某个模块的BEGIN和END之间来写,这样才能保证再次修改32Cube MX配置时自己的代码不会被覆盖掉。
在这里插入图片描述
上图是自动补全功能的开启(一般默认开启)。

在这里插入图片描述
在参考别人代码时,经常不懂某个函数的意义,可以如上图一样转到定义查看注释。

(2)整体逻辑的思路
首先,为了实现整体的功能,我们需要在main.h里的while(1){}里面书写全部逻辑,已达到轮询的目的。
任务整体分为四个部分:按键模块 keyFunction()、LED模块 LEDwork()、LCD显示模块 LCD_Display()、数据处理模块 dataProcess()。
在这里插入图片描述
下面分别实现每个模块。
(3)按键模块

/*******************************************************************************
* Function Name  : keyFunction
* Description    : 管理按键.
* Input          : None
* Output         : None
* Return         : None
* Source location : myFunction.c
*******************************************************************************/
void keyFunction(void)
{
	KeyScan();
	if(KeyFalling == Key_B4)
	{
		KeyTick = HAL_GetTick(); // 记录B4按下的时刻
	}
	
	switch(KeyRising)
	{
		case Key_B1:
			mod++;
			// 界面从‘数据界面’切换到‘参数界面’
			if(mod == 1) RKcount = 0; // 每次从数据界面进入参数界面,默认调整R参数
			// 有两种情况:(1)界面由‘参数’到‘统计’;(2)界面由‘统计’到‘数据’
			if(mod != 1)
			{
				for(int i = 0; i <= 1; i++)
				{
					if(RKtemp[i] != RKvalue[i]) RKvalue[i] = RKtemp[i]; // 如果符合情况(1),则更新R, K
				}
			}
			// 形成环路,回到数据界面
			if(mod == 3) mod = 0;
			break;
			
			case Key_B2:
				// 数据界面
				if(mod == 0 && LED2Flag == 0 && sysCount[0] >= 5000)
				{
					sysCount[0] = 0;
					LED2Flag = 1;
				}
				// 参数界面
				if(mod == 1)
				{
					RKcount ^= 1; // 取反,切换R、K参数
				}
				break;
				
			case Key_B3:
				// 参数界面
				if(mod == 1)
				{
					RKtemp[RKcount]++; // 改变的是中间值RKtemp,因为要等B1按下,参数才会更新,所以要把值丢给RKtemp暂时保存一下
					if(RKtemp[RKcount] > 10) RKtemp[RKcount] = 1;
				}
				break;
				
			case Key_B4:
				// 数据界面
				if(mod == 0)
				{
					if(HAL_GetTick() - KeyTick > 2000) // B4按下到松开超过2s,lock
					{
						lock = 1;
					}
					else // 姑且认为,只要按住B4的时间小于2s就算‘短按键’
					{
						lock = 0;
					}
				}
			
				// 参数界面
				if(mod == 1)
				{
					RKtemp[RKcount]--; 
					if(RKtemp[RKcount] < 1) RKtemp[RKcount] = 10;
				}
				break;
				
			default:
				break;			
	}
	
	// 5s之后更新频率模式标志
	if(LED2Flag && sysCount[0] >= 5000)
	{
		modeFreqCount ^= 1;
		modeFreqSwitchCount++;
		LED2Flag = 0;
	}
}

以上是按键模块,模块中出现的变量均在myFunction.c 的头部定义为了全局变量,**详细代码需查看工程原文件。**在函数的头注释中,特意加了一行名为‘Source location’,以说明此段代码的位置。

#include "stm32g4xx_hal.h"                 
#include "key.h"
#include "stm32g4xx.h"                  // Device header
#include "gpio.h"

/* 定义初始变量 */
uint8_t KeyOldState = 0;
uint8_t KeyFalling = 0;
uint8_t KeyRising = 0;


/*******************************************************************************
* Function Name  : KeyScan
* Description    : 扫描按键
* Input          : None
* Output         : KeyFalling 表示某个按键按下;KeyRising 表示某个按键松开
* Return         : None
* Source location : key.c
*******************************************************************************/

void KeyScan(void)
{
	uint8_t state = getKeysState();
	uint8_t key_temp = 0xFF ^ (0xF0 | state); // 异或操作,对state按位取反
	
	/* 通过逻辑运算实现消抖 */
	// 按下状态
	KeyFalling = key_temp & (key_temp ^ KeyOldState); // 某一位由0变为1,表示对应的按键按下
	// 松开状态
	KeyRising = ~key_temp & (key_temp ^ KeyOldState); 
	// 保存本次按键的值
	KeyOldState = key_temp;
}

/*******************************************************************************
* Description    : getKeysState()的宏定义
* Source location : key.h
*******************************************************************************/
// 四位按键状态,高位到地位分别表示按键B1, B2, B3, B4
#define getKeysState()    (	HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) << 0 | HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) << 1 |    \
						    HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) << 2 | HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) << 3      ) 

(4) 数据处理模块

/*******************************************************************************
* Function Name  : getADC
* Description    : 将adc采集到的输入模拟信号转为数字信号.
* Input          : adc
* Output         : 电压值
* Return         : 电压值
* Source location : myFunction.c
*******************************************************************************/
double getADC(ADC_HandleTypeDef *adc)
{
	HAL_ADC_Start(adc);
	valueADC = HAL_ADC_GetValue(adc);
	return (valueADC * 3.3 / 4096); // 4096 = 2^12
	//return valueADC;
}

/*******************************************************************************
* Function Name  : convert_frequencyTOv
* Description    : 频率f转换为速度值v.
* Input          : 整型变量freq
* Output         : 浮点数v
* Return         : v
*******************************************************************************/
float convert_frequencyTOv(int freq)
{
	extern uint8_t R, K; 
	return (freq * 2 * 3.14 * RKvalue[0]) / (100 * RKvalue[1]);
}

/*******************************************************************************
* Function Name  : getDuty
* Description    : 将电压值转化为占空比
* Input          : 电压值
* Output         : 占空比
* Return         : 占空比
*******************************************************************************/
char getDuty(double voltage)
{
	
	if(voltage >= 0 && voltage < 1) return 10;
	else if(voltage >= 3) return 85;
	else return (char)(75 / 2 * voltage + 10 - 75 / 2); // 电压-占空比 线性转换
}

/*******************************************************************************
* Function Name  : dataProcess
* Description    : 数据处理逻辑函数.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
static void dataProcess(void)
{
	/* ADC获取R37调节的占空比 */
	if(lock == 0)
	{
		adcV = getADC(&hadc2); // 获取电压值
		duty = getDuty(adcV);
		if(dutyTemp != duty && LED2Flag == 0) // 设置PA1输出的占空比
		{
			__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, PA1freq[modeFreqCount]* (duty / 100)); // 
			dutyTemp = duty;
		}
	}
	// 速度v
	if(fTemp != f)
	{
		v = convert_frequencyTOv(f);
		fTemp = f;
		sysCount[2] = 0;
	}
	else if(f == fTemp && sysCount[2] >= 2000) // 速度保持超过2s,考虑更新vmax
	{
		if(modeFreqCount == 0)
		{
			if(v > vLowFreq) vLowFreq = v;
		}
		if(modeFreqCount == 1)
		{
			if(v > vHighFreq) vHighFreq = v;
		}
	}
}

(5) LED模块

/*******************************************************************************
* Function Name  : LEDwork
* Description    : LED工作函数.
* Input          : None
* Output         : None
* Return         : None
* Source location : myFunction.c
*******************************************************************************/
void LEDwork(void)
{
	// 数据界面,LED1亮
	if(mod == 0)
	{ 
		LED_On(LED1);
	}
	else
	{
		LED_On(LEDall);
	}
	
	//切换期间,LED2闪烁,间隔0.1s
	if(LED2Flag && sysCount[1] >= 100)
	{
		switchLED(LED2);
		sysCount[1] = 0;
	}
	else if(!LED2Flag)
	{
		singleLED(LED_2, 0);
	}
	
	// 锁定模式下,LED3亮
	if(lock == 1)
	{
		singleLED(LED_3, 1);
	}
	else
	{
		singleLED(LED_3, 0);
	}
}

/*******************************************************************************
* Function Name  : LED_On
* Description    : 控制所有LED的状态
* Input          : LEDx x=1..8(宏定义:LED1 = 0x01)
* Output         : None
* Return         : None
* Source location : led.c
*******************************************************************************/
void LED_On(uint16_t dsLED)
{
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_All, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC, (dsLED << 8), GPIO_PIN_RESET); // 低电平点亮
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); // 打开锁存器,打开的这一瞬间数据就传过去了
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); // 关闭锁存器,防止误写
}
/*******************************************************************************
* Function Name  : switchLED
* Description    : 翻转LED的电平
* Input          : LEDx x=1..8(宏定义:LED1 = 0x01)
* Output         : None
* Return         : None
* Source location : led.c
*******************************************************************************/
void switchLED(uint16_t dsLED)
{
	HAL_GPIO_TogglePin(GPIOC, (dsLED << 8));
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); 
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/*******************************************************************************
* Function Name  : singleLED
* Description    : 改变单个LED的状态
* Input          : 枚举类型选择LED
										LEDx, x : 1..8
* Input          : LED状态
										1 : ON ; 0 : OFF
* Output         : None
* Return         : None
* Source location : led.c
*******************************************************************************/

void singleLED(enum LEDLOCATION LEDlocation,char LEDSTATE)
{
	HAL_GPIO_WritePin(GPIOC, LEDlocation, (LEDSTATE == 1 ? GPIO_PIN_RESET : GPIO_PIN_SET));
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); 
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

/* Source location : led.h */
enum LEDLOCATION	{ // 枚举
										LED_1 = GPIO_PIN_8,
										LED_2 = GPIO_PIN_9,
										LED_3 = GPIO_PIN_10,
										LED_4 = GPIO_PIN_11,
										LED_5 = GPIO_PIN_12, 
										LED_6 = GPIO_PIN_13,
										LED_7 = GPIO_PIN_14,
										LED_8 = GPIO_PIN_15};

(6) LCD显示模块

/*******************************************************************************
* Function Name  : LCD_Display
* Description    : 控制LCD屏幕显示.
* Input          : None
* Output         : None
* Return         : None
* Source location : myFunction,c
*******************************************************************************/
static void LCD_Display(void)
{

	char temp[20];
	if(mod == 0) // 数据界面
	{
		LCD_DisplayStringLine(Line1, (u8*)"        DATA        ");
		sprintf(temp, "        M = %c       ", modeFreq[modeFreqCount]); 
		LCD_DisplayStringLine(Line3, (u8*)temp);
		sprintf(temp, "        P = %d%%       ", duty); 
		LCD_DisplayStringLine(Line4, (u8*)temp);
		sprintf(temp, "        V = %.1f       ", v); // v保留1位小数
		LCD_DisplayStringLine(Line5, (u8*)temp);
	}
	else if(mod == 1) // 参数界面
	{
		LCD_DisplayStringLine(Line1, (u8*)"        PARA        ");
		sprintf(temp, "        R = %d       ", RKvalue[0]); 
		LCD_DisplayStringLine(Line3, (u8*)temp);
		sprintf(temp, "        K = %d       ", RKvalue[1]); 
		LCD_DisplayStringLine(Line4, (u8*)temp);
		LCD_ClearLine(Line5);
	}
	else if(mod == 2) // 统计界面
	{
		LCD_DisplayStringLine(Line1, (u8*)"        RECD        ");
		sprintf(temp, "        N = %d       ", modeFreqSwitchCount); 
		LCD_DisplayStringLine(Line3, (u8*)temp);
		sprintf(temp, "        MH = %.1f       ", vHighFreq); 
		LCD_DisplayStringLine(Line4, (u8*)temp);
		sprintf(temp, "        ML = %.1f       ", vLowFreq); 
		LCD_DisplayStringLine(Line5, (u8*)temp);
	}
}

(7)PA7输入捕获(timer3定时器)

/* 定时器回调函数,无需外部调用 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM3) // 条件判断。确保这是与定时器TIM3相关的事件。
	{
		cclValue = __HAL_TIM_GET_COUNTER(&htim3); // 统计两次输入捕获事件之间的计数值差
		__HAL_TIM_SetCounter(&htim3, 0); // 计数器清零
		f = (80000000 / 80) / cclValue; // 80MHz,80为预分频系数. 计算输入频率f
		HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2); // 重新启动TIM3-通道2的输入捕获
	}
}

在这里插入图片描述
(8)PA1输出PWM(timer2定时器)
不需要我们去开启、关闭定时器,只需要设置PWM波的占空比。见dataProcess().
(9) sysCount的定时实现
sysCount里有3个时间间隔,LED2的0.1s闪烁、低高频模式切换的5s判断、速度保持2s后的统计,这三个计数值都需要定时器。
这里是使用了滴答定时器systick,1ms触发一次中断。

/**
  * @brief This function handles System tick timer.
  * @source location stm32g4xx_it.c
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
	// time continue 5s
	if(LED2Flag && (sysCount[0] < 5000) )
	{
		if( sysCount[0]%200 == 0) // 步进值200
		{
			if(modeFreqCount == 0) // 低频模式
				PA1freq[modeFreqCount] -= 5;
			else 
				PA1freq[modeFreqCount] += 5;
			__HAL_TIM_SetAutoreload(&htim2,PA1freq[modeFreqCount]);
			HAL_TIM_GenerateEvent(&htim2, TIM_EVENTSOURCE_UPDATE);
		}
		sysCount[0]++;
	}
	// time continue 0.1s
	if(sysCount[1] < 100)
		sysCount[1]++;
	
	// time continue 2s
	if(sysCount[2] < 2000)
		sysCount[2]++;

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

至于为什么是1ms触发一次中断?

  • 首先,在时钟树中配置了systick的频率为80MHz;其次,我们需要知道systick的重装载值,也就是下面的SysTick_Type ->LOAD。在HAL库中,会默认生成1ms触发中断。

在这里插入图片描述
追踪HAL库代码。文件位置“stm32g4xx_hal.c”.

/**
  * @brief This function configures the source of the time base:
  *        The time source is configured to have 1ms time base with a dedicated
  *        Tick interrupt priority.
  * @note This function is called  automatically at the beginning of program after
  *       reset by HAL_Init() or at any time when clock is reconfigured  by HAL_RCC_ClockConfig().
  * @note In the default implementation, SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals.
  *       Care must be taken if HAL_Delay() is called from a peripheral ISR process,
  *       The SysTick interrupt must have higher priority (numerically lower)
  *       than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
  *       The function is declared as __weak  to be overwritten  in case of other
  *       implementation  in user file.
  * @param TickPriority: Tick interrupt priority.
  * @retval HAL status
  */
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  HAL_StatusTypeDef  status = HAL_OK;

  if (uwTickFreq != 0U)
  {
    /* Configure the SysTick to have interrupt in 1ms time basis*/
    if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) == 0U)
    {
      /* Configure the SysTick IRQ priority */
      if (TickPriority < (1UL << __NVIC_PRIO_BITS))
      {
        HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
        uwTickPrio = TickPriority;
      }
      else
      {
        status = HAL_ERROR;
      }
    }
    else
    {
      status = HAL_ERROR;
    }
  }
  else
  {
    status = HAL_ERROR;
  }

  /* Return function status */
  return status;
}

如果想手动调整中断触发间隔,需要修改的是uwTickFreq,stm32g4xx_hal.c中默认设置为1KHz。
在这里插入图片描述

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值