【国六总结】蓝桥杯复习资料(含外设代码)

瞎叨叨

喜报:十三届国赛300块买了个3XL抹布!
这次属实是被套路了,确实可以不用拓展版,但就是调试麻烦。
今年是最后一年参加蓝桥杯了,走出考场速板子挂小黄鱼,出去搓了顿好的。
这个资料实际上是省赛之前自己写了复习用的,好巧不巧今年没考拓展版上的外设,如果你是来找拓展版外设的那可以Ctrl+W换下一篇了。

客观题

好像官方是有本书,上面有客观题,不清楚。 这东西基本靠平时积累,不可能完全考前突击。下面是总结的十二届之前的部分客观题。

STM32相关

0.部分题目答案可以直接在Cube或者手册里找;
1.RS232通信最少需要3根线;
2.STM32提供的是3级流水线,哈佛结构;
3.STM32不支持双字数据类型;
4.STM32内部ADC的工作原理是逐次逼近
5.8080接口不属于串行通信;
6.RS232通信中,逻辑1电平电压 -5V~-15V
7.实现A/D转换的方法有
逐次逼近法
计数法双积分法
8.嵌入式系统中按照总线所传送的信息类型,可以分为数据总线地址总线控制总线等;
9.能够以菊花链方式连接的接口是SPIJTAG
10.使用STM32开发USB外设应使用HSE时钟源;
11.Cortex-M3中可以使用小端格式访问代码;
12.某存储芯片存储容量为8KB,数据线8根,地址线13根(8KB = 8 * 1024Byte = 2 ^ 13);
13.STM32F103RBT6每个DMA通道具有3事件标志;
14.一个功能简单但需要频繁调用的函数,比较适用内联函数
15.STM32片内FLASH一次可以写入16位;
16.RS485最少需要2根线进行通信;
17.高性能不是一般嵌入式系统设计的主要目标;
18.ARM处理器有7种工作模式;
19.为了用二进制数表示十进制数,常使用 BCD 编码;
20.Unicode 编码可以用于表示汉字;
21.ASCII一共128个字符;
22.STM32 微控制器 USART1 的波特率通过PCLK2提供;
23.中断优先级用4位编辑(0x00~0xFF -> 0000 ~ 1111);
24.Cortex M3 系列处理器支持 Thumb-II 指令集
25.Cortex M3 可以支持对单一的比特位进行读写操作的操作
26.在 STM32 处理器中一个 DMA 请求,至少占用2个周期的 CPU 访问系统总线时间;
27.STM32 处理器 APB2 上的 IO 引脚最大翻转速度为18MHz
28.USB通信速率 大于 RS232;
29.Cortex-M3处理器中的寄存器R14代表链接寄存器,R15代表程序计数寄存器;
30.NAND FLASH存储器和 NOR FLASH存储器的区别是NAND FLASH 擦除单元较小
31.Corte-M3有2个堆栈,同一时间只能调用一个;
32.微控制器掉电后重新上电NRST引脚上的低电平微控制器看门狗定时器计数终止将复位寄存器的特定位清零可以触发 STM32 微控制器复位;
33.外设分为内核外设片上外设,内核外设有SystickNVICSCBMPU
34.

大部分数电模电

1.数字时序逻辑电路输出与电路原状态当前输入有关;
2.能够实现线与的是OC门
3.温度升高时,二极管反向饱和电流将增大
4.设计一个8421BCD码计数器至少需要4个触发器;
5.高阻抗信号源和低阻抗负载间适合接入共集电路进行阻抗匹配;
6.将三角波转换为矩形波需使用施密特触发器
7.将矩形波输入到积分电路可以得到三角波
8.场效应管导通电阻与 V G S V_{GS} VGS温度 有关;
9.N 个触发器构成的计数器中,有效状态最多有2^N个;
10.欲提高电压比较器的抗干扰能力,应选用滞回比较器
11. 两个电压放大倍数相同(电路相同,且采用同一种晶体管)的 A 和 B 电路,对同一个信号源的电压进行放大,在负载开路的条件下,测得 A 电路的输出电压较小,不考虑仪表的测量误差,这说明 A 电路输入电阻小
12. RLC 串联电路的谐振频率为=1000,当频率为 800Hz 的正弦电压源激励时该电路呈容性
13. 信号不全都可以用一个确定的时间函数来描述;
14. 由 5 个 D 触发器构成的环形计数器,其计数长度为5
15. 数字电路中,三极管相当于一个开关,通常工作在饱和截止状态;
16. 两个逻辑函数恒等,则它们必然具有唯一的真值表
17. 不具有压电效应的滤波器是LC滤波器RC滤波器
18. 施密特触发器常用于对脉冲波形的整形
19. 运算放大器差模增益高输入阻抗高失调较小输出阻抗低
20. 单个运算放大器和若干个电阻无法构成振荡器乘法器
21. 分析运算放大器的依据是 U N ≈ U P U_N≈U_P UNUP I N ≈ I P I_N≈I_P INIP
22. 三态门的输出状态包括:高电平低电平高阻态
23.

三极管

工作参数计算

I C M I_{CM} ICM:集电极最大允许电流;
P C M P_{CM} PCM:集电极最大允许功率, P C = I C ∗ U C E P_{C}=I_C*U_{CE} PC=ICUCE
U ( B R ) C E O U_{(BR)CEO} U(BR)CEO:反向击穿电压, U C E > U ( B R ) C E O U_{CE}>U_{(BR)CEO} UCE>U(BR)CEO会导致 I C I_C IC急剧增大,三极管烧毁;
例题:
例题
题目给了四组参数,没有任何一组 U C E U_{CE} UCE过大,但B选项 I C I_C IC过大,先选上;然后开始计算剩下各组 P C P_{C} PC,C选项120mW大于最大功率,故答案选BC。

工作状态判断

对于NPN型三极管
放大区: U C > U B > U E U_C>U_B>U_E UC>UB>UE,发射结正偏,集电结反偏;
截止区: U B < U C , U B < U E U_BUB<UC,UB<UE,发射结和集电结反偏;
饱和区: U C E < U B E U_{CE}UCE<UBE,发射结和集电结正偏;
三极管用作开关管时,通常工作在饱和区截止区
例题:
【国六总结】蓝桥杯复习资料(含外设代码)_第1张图片
A选项明显符合放大区要求,B选项在截止区,C选项在饱和区,D选项注意负号,实际也是在放大区,答案选AD。

运算放大器

首先是虚短和虚断的概念。简单说,虚短就是运放工作在线性区时,两输入端可被视为等电位(实际并不能物理短路两输入端);虚断是由于理想运放输入电阻无穷大,所以几乎没有电流流入运放输入端,可以被视作断路。

最常规的例1

【国六总结】蓝桥杯复习资料(含外设代码)_第2张图片
根据虚短,反相输入端电流全部流过80K反馈电阻,可以对红色箭头所指节点应用KCL, 0.1 V 10 K + 0.2 V 10 K = U O 80 K \frac{0.1V}{10K} + \frac{0.2V}{10K} = \frac{U_{O}}{80K} 10K0.1V+10K0.2V=80KUO,最后算出 U O = 2.4 V U_O = 2.4V UO=2.4V,反向输入端加上负号,故答案是-2.4V。

挖了大坑的例2

有些题目会强调单电源供电,此时结果就不符合上面的公式了。例题:
【国六总结】蓝桥杯复习资料(含外设代码)_第3张图片
正常思路,前面是同向电压跟随器,后面是反向放大器,算出来-4V,选C。但这个题强调了是DC 12V单电源供电,所以不选C,选D。Multisim仿真结果是1.36V,具体咋算我也不大清楚(祈祷别出个这玩意儿给你算吧)。

还能这样玩的例3

带RC电路的题目,需要计算上限/下限频率。例题:
【国六总结】蓝桥杯复习资料(含外设代码)_第4张图片
RC电路截止频率 f = 1 / ( 2 π R C ) f=1/(2{\pi}RC) f=1/(2πRC),其中电容C的单位为法拉F。对于这个题,先换算电容单位,10uF=10^-6F=0.00001F,带入公式计算,约为159.155Hz,按题目要求取整,答案为159Hz。放大倍数 = - 10K / 100 = -100,答案为-100倍。

我看不懂但大受震撼的例4

还有一种差分减法器,如题:
【国六总结】蓝桥杯复习资料(含外设代码)_第5张图片
这个就只有问百度了,真考到听天由命
【国六总结】蓝桥杯复习资料(含外设代码)_第6张图片

十三届第一场省赛的例5

写这部分的的时候是2022年5月8日,离第二场省赛好像还有6天。因为看不懂一个多月前写的虚短虚断是啥玩意儿,于是把第一场省赛的题拿来分析一遍。
【国六总结】蓝桥杯复习资料(含外设代码)_第7张图片
先说仿真结果,3V。
然后再来分析,如下图:
【国六总结】蓝桥杯复习资料(含外设代码)_第8张图片
前一个部分,典型的电压跟随器,输出电压(蓝色节点)为+1V。后半部分,由运放虚短可知,绿色节点电压与正向端输入电压相等,应为+2V;由运放虚断,可提出题目下面的那个手绘模型,接下来对绿色节点用KCL就解出来了:流出电流1mA,流入也应为1mA,红色节点电压应为绿色电压+I*R=+3V,分析完毕。

外设代码

HAL库还是LL库?

个人认为,评测系统中的一个指标是代码大小。在用HAL库狠凹第一套模拟题之后,各项功能已经是1:1复刻官方参考答案了,但仍只有89.2分(官方LL库答案98.2分),据此猜测代码大小也是评分指标之一,当然也只是猜测。之后我也尝试用LL库编写程序,但奈何水平不够,各个功能整合起来之后稳定性不如HAL库,以后有机会一定好好学学LL库。

FPU

相传在MDK的options for target的target选项卡下可以直接设置,但还是感觉用宏定义更安心一点,反正也费不了一分钟。在模拟测评中开不开对分数没有影响。

//MDK->Options for Target->C/C++->Define
__FPU_USED=1U,__TARGET_FPU_VFP,ARM_MATH_CM4

LED

LED部分最简单的操作方法就是操作ODR寄存器,有两种方法,在模拟评测系统上分数是一样的,但方法二更直观一些,临场不容易写错+易分析,故优先采用方法2。寄存器内容参考RM0440参考手册(英文手册实际上也没那么难看懂,至少GPIO部分不会有很多很多专业词+生僻词)。

//方法一:按位运算得到ODR的值
uint8_t code = 0x00;	//注意两个方法初始code恰好相反

void LED_SetStatus(uint8_t status, uint8_t serial)
{
	if(status == LED_ON)
	{
		code = code | (0x01 << (serial - 1));
		GPIOC->ODR = ~(code << 8);
	}
	else if(status == LED_OFF)
	{
		uint8_t temp = ~code;
		temp = temp | (0x01 << (serial -1));
		code = ~temp;
		GPIOC->ODR = (temp << 8);
	}
	else
	{
		code = 0x00;
		GPIOC->ODR = (0xFF << 8);
	}
	
	HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_RESET);
}

//方法二:操作BSRR控制ODR
void LED_SetStatus(uint8_t serial, uint8_t status)
{
	if(status == LED_ON)
	{
		GPIOC->ODR = code << 8;
		GPIOC->BSRR = 0x01 << (23 + serial);	//Set BRx
		code = GPIOC->ODR >> 8;
	}
	else if(status == LED_OFF)
	{
		GPIOC->ODR = code << 8;
		GPIOC->BSRR = 0x01 << (7 + serial);	//Set BSx
		code = GPIOC->ODR >> 8;
	}
	else
	{
		code = 0xFF;
		GPIOC->ODR = (code << 8);
	}
	
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}

按键

实现方案很多,还有用定时中断检测按键的(这个试了试效果不是很稳定,就弃了,也可能是我写的姿势不对)。除了写的这个方法,还可以用读IDR实现。经实验,分数也是没差别。长短按?省赛没写,国赛也不是用自己电脑,就简单说个思路吧:用1ms(1KHz)的定时器中断数数,判断是长按还是短按。

uint8_t KEY_CheckStatus(void)
{
	keyPressed = 0;
	
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
	{
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET)
		{
			keyPressed = 1;
		}
	}
	if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)
	{
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET)
		{
			keyPressed = 2;
		}
	}
	if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)
	{
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET)
		{
			keyPressed = 3;
		}
	}
	if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET)
	{
		HAL_Delay(10);
		if(HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET)
		{
			keyPressed = 4;
		}
	}
	
	if(keyPressed != keyPrevious)	//检测与上次按键是否相同
	{
		keyPrevious = keyPressed;		//不同则代表是第一次检测到,更新上次键值
	}
	else
	{
		keyPressed = 0;		//相同则代表之前已经检测过了,按下键值返回0
	}
	
	return keyPressed;
}

UART

一步登顶,直接DMA+空闲中断,省得纠结。

void UART_Init(void)
{
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
	HAL_UART_Receive_DMA(&huart1, rxBuf, sizeof(rxBuf));
}

void UART_IRQHandler(UART_HandleTypeDef *huart)	//记得添加到it文件里
{
	if(huart->Instance == USART1)	//串口1中断,如果是扔对应IRQHandler里的话这个if实际上没必要
	{
		if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)	//串口1空闲
		{
			HAL_UART_DMAStop(&huart1);
			uint8_t lenMsgReceived = sizeof(rxBuf) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
			memcpy(rxDest, rxBuf, lenMsgReceived);	//copy数据,不在中断里处理数据
			memset(rxBuf, 0x00, sizeof(rxBuf));
			rxFlag = 1;
			
			__HAL_UART_CLEAR_IDLEFLAG(&huart1);
			HAL_UART_Receive_DMA(&huart1, rxBuf, sizeof(rxBuf));
		}
	}
}

PWM

PWM生成

PWM,预分频器(PSC) = timer频率 - 1,计数周期(CP)= 1,000,000 / 目标频率 - 1。如时钟频率170MHz,要输出100Hz的PWM波,PSC定170-1,CP定1,000,000 / 100 = 10,000 - 1。如果是设置一个100ms的定时器,则采用PSC 1700-1 & CP 10000-1的组合,(因为大多定时器CP是16位的,写不到100000)。

void PWM_Init(void)
{
	__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, 500);
	__HAL_TIM_SetCompare(&htim16, TIM_CHANNEL_1, 1000);
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim16, TIM_CHANNEL_1);
	//定时器中断用HAL_TIM_Base_Start_IT()
}

void PWM_SetDuty(float duty)
{
	if(pwmDuty != pwmDutyPrevious)
	{
		pwmDutyPrevious = pwmDuty;
		uint16_t tmpDuty = (uint16_t)((pwmPeriod + 1) * pwmDuty);
		__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_2, tmpDuty);
	}
}

void PWM_SetFreq(uint16_t frq)
{
	if(frq != pwmFreqPrevious)
	{
		pwmFreqPrevious = frq;
		pwmDutyPrevious = 0.0f;		//强制刷新占空比
		pwmPeriod = (uint16_t)(1000000 / frq);
		PWM_SetDuty(pwmDuty);
		__HAL_TIM_SetAutoreload(&htim3, pwmPeriod - 1);
	}
}

频率测量

没大用过,直接上流程:
1.目标通道,选择Input Capture direct mode(直接捕获)
2.PSC=timer频率 - 1,计数周期直接拉满
3.向上计数,关闭自动重装载(这个应该是默认的)
4.开中断!开中断!!开中断!!!
频率测量原理:MCU捕获到方波上升沿产生中断,在中断回调函数中读取计数值,频率 = 1,000,000 / 计数值,然后清零计数器重新计数。

void TIM_Init(void)
{
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{

	cc1_value_2 = __HAL_TIM_GET_COUNTER(&htim2);
	__HAL_TIM_SetCounter(&htim2,0);
	frqCapture = 1000000/cc1_value_2;
	
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}

占空比测量

占空比测量实现的思路:使用一路定时器通道,配置为捕获上升沿,在捕获到上升沿触发中断后,记录当前计数值,然后改为捕获下降沿;捕获到下降沿触发中断后,即可计算出高电平时间,然后改为捕获上升沿。为求出占空比,还需得到输入信号周期,记录下两次上升沿间隔时间即可。

if(htim->Instance == TIM2)
{
	if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
	{
		if(timDutyTestStep == 0)	//捕获上升沿
		{
			timDutyValue[2] = __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1);
			if(timDutyValue[2] < timDutyValue[0])
			{
				timCapWidth = (0xFFFF - timDutyValue[2]) + timDutyValue[0];	//0xFFFF为16位寄存器上限值,需注意CP具体位数
			}
			else
			{
				timCapWidth = timDutyValue[2] - timDutyValue[0];
			}
			timCapFreq = 1000000 / timCapWidth;	//计算捕获信号频率
			timDutyValue[0] = timDutyValue[2];
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);	//设定为捕获下降沿
			timDutyTestStep = 1;
		}
		else
		{
			timDutyValue[1] = __HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_1);
			if(timDutyValue[0] > timDutyValue[1])	//向上计数,说明定时器溢出
			{
				timDutyHigh = (0xFFFF - timDutyValue[0]) + timDutyValue[1];	//0xFFFF为16位寄存器上限值,需注意CP具体位数
			}
			else
			{
				timDutyHigh = timDutyValue[1] - timDutyValue[0];
			}
			__HAL_TIM_SET_CAPTUREPOLARITY(&htim2,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);	//设定为捕获上升沿
			timDutyPct = (float)timDutyHigh / (float)timCapWidth;
			timDutyTestStep = 0;
		}
	}
}

这东西国赛之前应该不会考,两个方波测
反转了,国赛没用拓展板

ADC

板子提供了两路模拟输出,分别接到PB12(ADC1_IN11)、PB15(ADC2_IN15)。因为不是同一个ADC,所以不需要开非连续转换模式。国赛题目暂时没深究,可能会用到同一个ADC的多个通道,这个到时候注意一下。

HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);	//自动校准ADC2,加在ADC初始化后

void ADC_ReadValue(void)
{
	HAL_ADC_Start(&hadc2);
	if(HAL_ADC_PollForConversion(&hadc2, ADC_CHANNEL_15) == HAL_OK)
	{
		voltRead = (float)HAL_ADC_GetValue(&hadc2) / 4095.0f * 3.3f; 
	}
}

时间紧也没测试,但只要开了连续转换,多叠几个if应该就行。13国赛好像只有新板子需要连续转换,旧版似乎可以直接用两个ADC。

I2C

看眼原理图,再看眼Cube,好的,只能用软件I2C,不过资源包里给底层代码了,所以问题不大。
解决了底层驱动问题,现在就该考虑时序问题了。查找数据手册:
【国六总结】蓝桥杯复习资料(含外设代码)_第9张图片
写入操作顺序:发START–>发设备写地址(0xA0)–>等待响应(ACK)–>发送存储单元地址–>等待响应–>数据内容–>等待响应–>发STOP。需要注意的是,地址的二进制数最后一位必须是0(R/W位,0为写)。
【国六总结】蓝桥杯复习资料(含外设代码)_第10张图片
读取时应选用随机读取,操作顺序:发START–>发设备写地址(0xA0)–>等待响应–>发送目标存储单元地址–>等待响应–>发START–>发设备读地址(0xA1)–>等待响应–>接收内容–>发STOP
把稳起见可以加一个和校验。

RTC(这个没写)

RTC新板没给例程,模拟题没见过,大概不考吧。

RES可编程电阻(这个也没写)

据说这个是新板子才有的模块,今年还能用旧版,所以判断不会考。真要考了就照着手册写。

DAC

直接选择Connected to external pin only,剩下配置都不用改。
【国六总结】蓝桥杯复习资料(含外设代码)_第11张图片
代码也很简单

void Dac1_Set_Vol(float vol)
{
	uint16_t temp;
	
	temp = (4096*vol/3.3f);
	
	HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1,DAC_ALIGN_12B_R,temp);
}

总结

总之就是这样,蓝桥杯生涯画句号啦。因为国赛没用自己电脑,比赛的实验室20级在做课设,加上写的也不好,就懒得去拷了(LCD翻转是真离谱)。附上13届省赛代码,基本没啥问题,EEPROM上电校验可以再改进改进。

省赛代码:https://download.csdn.net/download/qq_26942797/85683923

你可能感兴趣的:(笔记,蓝桥杯,stm32)