目录
第一部分 客观试题(15 分)
不定项选择(1.5 分/题)
第二部分 程序设计试题(85 分)
2.1 STM32CubeMX初始化配置
2.1.1 配置GPIO
2.1.2 配置ADC
2.1.3 配置RCC
2.1.4 配置定时器TIM
2.1.5 配置ADC1、ADC2
2.1.6 配置定时器2
2.1.7 配置定时器3
2.1.8 可以选择配置操作系统
2.1.9 时钟树设置
2.2 代码详解
2.2.1 LCD task
2.2.2 KEYtask
2.2.3 PWM信号输出控制
总耗时:4.5h
第十四届蓝桥杯嵌入式
第一部分 客观试题(15 分)
不定项选择(1.5 分/题)
01. 下列电路中属于时序逻辑电路的是( ABC)。
A. 计数器 B. 分频器 C. D 触发器 D. 编码器
02. 一个 8 位二进制减法计数器,初始状态为 0000 0000,经过 300 个输入脉冲后,计 数器的状态为(B )。
A. 0010 1100 B. 1101 0011 C. 0010 0011 D. 1101 0100
03. 晶体管的穿透电流 ICEO能够体现( A)。
A. 晶体管的温度稳定性 B. 晶体管允许通过最大电流极限参数
C. 晶体管放大能力 D. 晶体管的频率特性
04. STM32 系列微控制器,程序可以在哪些区域上运行(AB )。
A. ROM B. RAM C. 寄存器 D. E2PROM
05. 一个 8 位的 DAC 转换器,供电电压为 3.3V,参考电压 2.4V,其 1LSB 产生的输出 电压增量是(D )V。
A. 0.0129 B. 0.0047 C. 0.0064 D. 0.0094
06. 下列门电路中,输出端可以直接相连实现线与的是(AC )。
A.OC 门 B.TTL 或非门 C.OD 门 D.CMOS 与非门
07. 在 STM32 系列微控制器中,中断优先级可配置的是(BC )。
A. RCC B. NMI C. HardFault D. Systick
08. 工作在线性区域的运算放大器应处于什么状态( A)。
A. 负反馈 B. 正反馈 C. 开环 D. 振荡
09. 同步电路和异步电路的区别是(D )。
A. 电路中是否包含缓冲器 B. 电路中是否包含触发器
C. 电路中是否存在时钟信号 D. 电路中是否存在统一的时钟信号
10. 下列关于关键字 inline 的描述,正确的是(ABD )。
A. 降低栈内存的消耗。 B. 可以提高代码的运行效率。
C. 可以提高微控制器访问内部寄存器的速度。
D. 程序中大量使用,会增大代码编译后的可执行文件的大小
第二部分 程序设计试题(85 分)
2.1 STM32CubeMX初始化配置
选择STM32G431RBTx系列芯片;
2.1.1 配置GPIO
2.1.2 配置ADC
2.1.3 配置RCC
2.1.4 配置定时器TIM
2.1.5 配置ADC1、ADC2
一般是默认设置就可以了,开启全局中断,打开DMA通道1、DMA通道2;
2.1.6 配置定时器2
将PA1引脚配置定时器PWM模式1,使PA1输出PWM脉冲信号让PA7捕获。
2.1.7 配置定时器3
将PA7引脚配置定时器为输入捕获模式,用来捕捉PWM信号。
2.1.8 可以选择配置操作系统
1.添加FreeRTOS定时器
PWM信号定时器,PWM输出高速模式,PWM输出低速模式
2.添加任务,提前设置好任务的内存块
堆栈空间的大小。定义句柄
3.建立消息队列
按键功能复杂可以使用队列简化复杂度进行调度, PWM信号分高频和低频模式也可以使用队列进行调度。
4.其他设置
使能固件库pack
使能定义任务函数
2.1.9 时钟树设置
2.2 代码详解
定义全局变量Generator_Mode用来选择LCD界面3个模式的显示,Generator_FreqMode用来选择PWM高频和低频模式。Generator_KeyLock用来标志按键是否锁定,如果锁定则不能使用,Generator_DutyLock用来标志占空比锁。Generator_FreqShifting用来选择PWM高低模式,Generator_ShiftNum记录频率切换次数,多于10次则转1。Generator_ShiftNum、Generator_K参数界面的R和K。MaxSpeed_HighFreq、MaxSpeed_LowFreq记录高低频率下的最大速度。
低频输出频率是4k,高频输出频率是8k,可以用const静态关键字进行固定。
// 屏幕显示模式(0为数据,1为参数,2为统计)和频率模式(0低1高)
uint8_t Generator_Mode = 0, Generator_FreqMode = 0;
// 按键锁定标志位(低高频切换过程置高),占空比锁定标志位(数据界面长按B4置高,再短按B4置低)
uint8_t Generator_KeyLock = 0, Generator_DutyLock = 0;
// 正在切换的频率模式(0低1高)
uint8_t Generator_FreqShifting = 0;
// 记录的频率切换次数
uint32_t Generator_ShiftNum = 0;
// 参数界面的R和K
int8_t Generator_R = 1, Generator_K = 1;
// 记录的高/低频率下的最大速度
float MaxSpeed_HighFreq = 0, MaxSpeed_LowFreq = 0;
// PA1输出的占空比,PA7采样计算得到的速度
float Generator_PWMDuty = 0, Generator_Speed = 0;
// PA1输出的频率
uint16_t Generator_Freq = 4000;
//PA1高、低频模式下的频率(因为是固定的,所以声明为const)
const uint16_t Generator_HFreq = 8000, Generator_LFreq = 4000;
2.2.1 LCD task
全局变量已经解释过了,按照任务需求进行LCD界面选择显示,
LCD_Printf:va_list关键字作用是宏定义一个para变量,va_start(para, format)执行para = (va_list)&format + _INTSIZEOF(format),para指向参数format之后的那个参数的地址,即para指向第一个可变参数在堆栈的地址。vsnprintf函数用来向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度。va_end(para)用来清空para。LCD_DisplayStringLine函数是官方给的lcd.c文件中的函数用来定位行,直接调用就行。
void LCD_Printf(u8 line, const char *format, ...)
{
char buf[22] = {0};
va_list para;
va_start(para, format);
int len = vsnprintf(buf, sizeof(buf), format, para);
va_end(para);
LCD_DisplayStringLine(line, (unsigned char *)buf);
}
osMessageQueueGet接收按键队列的消息,osOK枚举确认接收到了队列消息,
void LCD_Handler(void *argument)
{
/* USER CODE BEGIN LCD_Handler */
int8_t Generator_TempR = 1, Generator_TempK = 1;//R,K两个参数的暂存
uint8_t Modified = 0;//0为修改R,1为修改K
uint32_t online_cnt = 0;
/* Infinite loop */
for (;;)
{
LED_Ctrl(0, (Generator_Mode == 0) ? 1 : 0);//如果在数据模式下才点亮LD1
LED_Ctrl(2, Generator_DutyLock);//如果占空比锁定则点亮LD3
switch (Generator_Mode)//根据显示模式切换LCD显示
{
case 0:
LCD_Printf(Line1, " DATA ");
LCD_Printf(Line3, " M=%c ", ((Generator_FreqMode) ? 'H' : 'L'));
LCD_Printf(Line4, " P=%.0f%% ", Generator_PWMDuty * 100.0f);
LCD_Printf(Line5, " V=%.1f ", Generator_Speed);
break;
case 1:
LCD_Printf(Line1, " PARA ");
LCD_Printf(Line3, " R=%d ", Generator_TempR);
LCD_Printf(Line4, " K=%d ", Generator_TempK);
break;
case 2:
LCD_Printf(Line1, " RECD ");
LCD_Printf(Line3, " N=%d ", Generator_ShiftNum);
LCD_Printf(Line4, " MH=%.1f ", MaxSpeed_HighFreq);
LCD_Printf(Line5, " ML=%.1f ", MaxSpeed_LowFreq);
break;
default:
break;
}
uint8_t key = 0xff;//暂存变量,接收按键队列传出的按键信息
if (osMessageQueueGet(Key_QueueHandle, &key, 0, 0) == osOK)//按键队列有最新按键信息
{
switch (key)
{
case 0://B1短按
LCD_Clear(Black);
Generator_Mode++;//刷屏,换模式
if (Generator_Mode == 1)//换模式后如果是参数模式,也即按下按键后进入参数模式
{
Generator_TempR = Generator_R, Generator_TempK = Generator_K;//暂存变量更新为现在设置的值。
Modified = 0;//进入模式默认先修改R,所以Modified不管之前是什么值都改为0
}
if (Generator_Mode > 2)//超过2则返回数据模式
Generator_Mode = 0;
if (Generator_Mode == 2)//换模式后如果是统计模式,也即刚刚是从参数模式退出来的
{
Generator_R = Generator_TempR, Generator_K = Generator_TempK;//实际参数用暂存值更新。
}
break;
case 1://B2短按
if (!Generator_KeyLock && Generator_Mode == 0)//此时按键未锁定且处于数据模式下
{
osMessageQueuePut(PWM_Signal_QueueHandle, &key, 0, 0);//启动频率切换进程
}
else if (Generator_Mode == 1)//此时在参数模式下
{
Modified = (Modified) ? 0 : 1;//Modified在0,1之间切换
}
break;
case 2://B3短按
if (Generator_Mode == 1)//此时在参数模式下
{
if (Modified)//0为修改R,1为修改K
Generator_TempK++;
else
Generator_TempR++;
if (Generator_TempK > 10)//参数越界判定
Generator_TempK = 1;
if (Generator_TempR > 10)
Generator_TempR = 1;
}
break;
case 3://B4短按
if (Generator_Mode == 1)
{
if (Modified)//0为修改R,1为修改K
Generator_TempK--;
else
Generator_TempR--;
if (Generator_TempK < 1)//参数越界判定
Generator_TempK = 10;
if (Generator_TempR < 1)
Generator_TempR = 10;
}
else if (Generator_Mode == 0)//此时在数据模式下
{
Generator_DutyLock = 0;//短按是解除占空比锁
}
break;
case 4:
/* code */
break;
case 5:
/* code */
break;
case 6:
/* code */
break;
case 7://B4触发长按条件
if (Generator_Mode == 0)//此时在数据模式下
{
Generator_DutyLock = 1;//长按是占空比锁上锁
}
break;
default:
break;
}
}
osDelayUntil((online_cnt + 1) * 100);//刷新用时控制在100ms左右
online_cnt++;
}
/* USER CODE END LCD_Handler */
}
2.2.2 KEYtask
将按键按下次数存入数组中,返回到按键扫描函数。
uint8_t Key_IsUp(uint8_t pos)
{
return Key_Up[pos];
}
uint8_t Key_IsLongPress(uint8_t pos)
{
return Key_LongPress[pos];
}
遍历按键gpio是否按下,按下则往Key_Reg数组中存入1,方便接下来取反,判断按键按下次数,并将按键按下次数存入Key_Cnt数组中。
uint8_t Key_Reg[4], Key_Constant[4], Key_Up[4], Key_LongPress[4], Key_LongTemp[4];
uint16_t Key_Cnt[4], Key_Delay = 78;
void Key_Scan(void)
{
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
Key_Reg[0] = 1;
}
else
{
Key_Reg[0] = 0;
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
Key_Reg[1] = 1;
}
else
{
Key_Reg[1] = 0;
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET)
{
Key_Reg[2] = 1;
}
else
{
Key_Reg[2] = 0;
}
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
Key_Reg[3] = 1;
}
else
{
Key_Reg[3] = 0;
}
for (int i = 0; i < 4; i++)
{
Key_Up[i] = ~Key_Reg[i] & (Key_Reg[i] ^ Key_Constant[i]);
Key_Constant[i] = Key_Reg[i];
if (Key_Cnt[i] >= Key_Delay)
{
if (Key_Up[i])
{
Key_LongPress[i]=1;
// Key_LongPress[i] = Key_Up[i] & (Key_Up[i] ^ Key_LongTemp[i]);
Key_Up[i] = 0;
}
// Key_LongPress[i] = Key_Reg[i] & (Key_Reg[i] ^ Key_LongTemp[i]);
Key_LongTemp[i] = Key_Reg[i];
}
else
{
Key_LongPress[i] = 0;
Key_LongTemp[i] = 0;
}
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
Key_Cnt[0]++;
}
else
{
Key_Cnt[0] = 0;
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
Key_Cnt[1]++;
}
else
{
Key_Cnt[1] = 0;
}
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET)
{
Key_Cnt[2]++;
}
else
{
Key_Cnt[2] = 0;
}
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
Key_Cnt[3]++;
}
else
{
Key_Cnt[3] = 0;
}
}
这里用for循环用来判断按键4是否长按,如果i>3则触发长按,
void Key_Handler(void *argument)
{
/* USER CODE BEGIN Key_Handler */
uint32_t online_cnt = 0;
/* Infinite loop */
for (;;)
{
Key_Scan();//扫描按键
for (uint8_t i = 0; i < 4; i++)
{
if (Key_IsUp(i))
{
osMessageQueuePut(Key_QueueHandle, &i, 0, 0);//触发短按条件,B1-4对应数字0-3
}
if (Key_IsLongPress(i))
{
uint8_t j = i + 4;
osMessageQueuePut(Key_QueueHandle, &j, 0, 0);//触发长按条件,B1-4对应数字4-7
}
}
osDelayUntil((online_cnt + 1) * 25);//扫描周期粗略为25ms
online_cnt++;
}
/* USER CODE END Key_Handler */
}
2.2.3 PWM信号输出控制
由题目知道,电压处于0-3V之间,占空比同时随着电压变换。(只展示部分核心代码)
void PWM_Handler(void *argument)
{
/* USER CODE BEGIN PWM_Handler */
uint32_t online_cnt = 0;
/* Infinite loop */
for (;;)
{
if (Generator_DutyLock == 0)//未启用占空比锁
{//根据题目实时修改占空比(介于10-85%之间)
if (R1_Vol < 1.0f)
{
Generator_PWMDuty = 0.1f;
}
else if (R1_Vol >= 3.0f)
{
Generator_PWMDuty = 0.85f;
}
else
{
Generator_PWMDuty = -0.275f + R1_Vol * 0.375f;
}
}//上锁则保持原有占空比不变
Timer_PWMSetDuty(Generator_PWMDuty);//控制占空比
Timer_PWMSetFreq(Generator_Freq);//控制频率
Generator_Speed = (IC_Freq * 2 * 3.14f * Generator_R) / 100 / Generator_K;//根据PA7输入频率,计算得到“速度”。
switch (Generator_FreqMode)//0为低频率模式,1为高频率模式
{
case 0:
if (Generator_Speed > MaxSpeed_LowFreq && !osTimerIsRunning(Lspeed_UpdateTimerHandle))
{//发现现有速度高于记录的低频率最高速度,且低频率最高速度更新定时器未启动
osTimerStart(Lspeed_UpdateTimerHandle, 2000U);//启动更新定时器,准备2s后更新低频率最高速度
}
else if (Generator_Speed <= MaxSpeed_LowFreq && osTimerIsRunning(Lspeed_UpdateTimerHandle))
{//发现现有速度已经低于记录的低频率最高速度,且低频率最高速度更新定时器已经启动
osTimerStop(Lspeed_UpdateTimerHandle);//不必再更新低频率最高速度,停止更新定时器
}
break;
case 1://高频率部分道理同上
if (Generator_Speed > MaxSpeed_HighFreq && !osTimerIsRunning(Hspeed_UpdateTimerHandle))
{
osTimerStart(Hspeed_UpdateTimerHandle, 2000U);
}
else if (Generator_Speed <= MaxSpeed_HighFreq && osTimerIsRunning(Hspeed_UpdateTimerHandle))
{
osTimerStop(Hspeed_UpdateTimerHandle);
}
break;
default:
break;
}
uint8_t info = 0;
if (osMessageQueueGet(PWM_Signal_QueueHandle, &info, 0, 0) == osOK)//收到LCD控制任务下发的频率切换指令
{
Generator_KeyLock = 1;//锁定B2按键
Generator_FreqShifting = Generator_FreqShifting ? 0 : 1;//此时高频则改为低频(1->0),此时低频则改为高频(0->1)
osTimerStart(PWM_Signal_TimerHandle, 100U);//启动频率修改定时器(0.1s刷新一次)
}
osDelayUntil((online_cnt + 1) * 10);//整个PWM控制任务大概执行一次,延时10ms
online_cnt++;
}
/* USER CODE END PWM_Handler */
}
void PWM_Signal_CB(void *argument)
{
/* USER CODE BEGIN PWM_Signal_CB */
//频率修改定时器回调函数(0.1s触发一次)
if (Generator_FreqShifting)//切换目标是高频还是低频
{
Generator_Freq += (Generator_HFreq - Generator_LFreq) / 50;//单个周期(0.1s)增加80HZ,5s后正好为8KHZ
}
else
{
Generator_Freq -= (Generator_HFreq - Generator_LFreq) / 50;//单个周期(0.1s)减少80HZ,5s后正好为4KHZ
}
LED_Toggle(1);//LD1以0.1s为周期闪烁
if (Generator_FreqShifting && Generator_Freq >= Generator_HFreq)
{//低频切高频时PWM频率增加后已经超过了8KHZ,也即切换5s之后
Generator_FreqMode = Generator_FreqShifting;//现在的频率模式更新为高频
Generator_ShiftNum++;//PWM切换次数加一
Generator_Freq = Generator_HFreq;//现在的频率和最高频率对齐
Generator_KeyLock = 0;//按键B2解锁
LED_Ctrl(1, 0);//熄灭LD1
osTimerStop(PWM_Signal_TimerHandle);//完成任务,停止定时器
}
if (!Generator_FreqShifting && Generator_Freq <= Generator_LFreq)
{//高频切低频时PWM频率减少后已经低于4KHZ,也即切换5s之后
Generator_FreqMode = Generator_FreqShifting;//现在的频率模式更新为低频
Generator_ShiftNum++;//PWM切换次数加一
Generator_Freq = Generator_LFreq;//现在的频率和最低频率对齐
Generator_KeyLock = 0;//按键B2解锁
LED_Ctrl(1, 0);//熄灭LD1
osTimerStop(PWM_Signal_TimerHandle);//完成任务,停止定时器
}
//注意该定时器为周期性定时器(osTimerPeriodic),完成切换后需要手动终止定时器
/* USER CODE END PWM_Signal_CB */
}