赞
踩
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的这八个引脚初始化为高电平,作为熄灭初始化)
(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 */ }
追踪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。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。