蓝桥杯 嵌入式组 题解 + 个人笔记(还在更新ing)
一、写在前面
蓝桥杯嵌入式组,比赛用国信长天STM32G431开发板(2021新版),搭配CubeMX和Keil开发,推荐用HAL库orLL库开发。
本文是我在赛前总结的一些点,供参考。
这里是一些我做过的题目,均使用LL库开发:
二、题解的代码
第11届 嵌入式组第一题(ADC+计时的题):
https://pan.baidu.com/s/1XkTFiivHrST4MSxt9mH_TA?pwd=bn7z
bn7z
第11届 嵌入式组第二题(TIM+ADC+占空比控制的题):
https://pan.baidu.com/s/18pt1qQZY6KN4Voo9XhVlCA?pwd=129w
129w
三、问答
Q:HAL库还是LL库?
A:因为LL库代码运行效率更高,LL的操作函数也更多,比如LL的GPIO操作就比GPIO的方式多一些。LL未来必然取代HAL库,所以蓝桥杯比赛中我选择的是LL库,也是未来以后的学习。
Q:蓝桥杯的基础知识在哪里看?
A:建议顺手买一本官方的《嵌入式设计与开发实训指导书》(也可以找找电子资料),跟着指导书走一遍。这个比赛要适当背一些内容,如如何配置引脚,LL的常用函数,如何初始化LCD,如何写USART接收中断等等。
四、一些常见BUG
Q:ADC读取没有数值
A: 1.确定下有没有校准ADC
2.确定下有没有使能ADC
3.如果1、2都检查过,再试下把CubeMX里时钟树的HCLK从170Mhz改成85Mhz,然后重新编译打开。
五、一些复习内容和Tips:
1.Keil 添加代码自动补全:
Edit->Configura->Text Compleme 勾选Enter/Tab as ..... ,然后点击ok,不要点x,这样才算配置成功。

2.变量命名
main.c程序中的变量前缀加uc (user code),头文件和子程序里的变量前缀加us(user subcode),以区分不同的变量,让变量匹配和补全的时候更明确,方便自己找。
3.Systick中断
Systick每1ms中断一次。
用LL库时,需要在main函数里加入LL_SYSTICK_EnableIT(),才能启动Systick中断。
在main里定义ucMs , ucSec,ucMin,用于记录时间。在xxx_it.c的Systick_Handler()中断更新它们的数值。
//1.使能定时中断
LL_SYSTICK_EnableIT();
//2.这函数在 xxxx_it.c里
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
/* USER CODE BEGIN SysTick_IRQn 1 */
ucTms++;
if(ucMs==1000){
ucTms=0;
ucSec++;
}
if(ucSec==60){
ucSec=0;
ucMin++;
}
/* USER CODE END SysTick_IRQn 1 */
}
5.LCD显示
用LCD时,需要将LCD的初始化代码放在GPIO的初始化之后,不然无法成功初始化。
MX_GPIO_Init();
...
LCD_Init();
LCD_Clear(Black);
...
在LCD某一行显示数据的方式是:
LCD_SetTextColor("White");//设置字体颜色
LCD_SetBackColor("Black");//设置背景颜色
uint8_t uclcd[21];
sprintf((Char*)uclcd," ");//你要显示的内容,要#include "stdio.h"
LCD_DisplayStringLine(Line1,ucLcd);//选择行Line1~Line19
6.USART
串口通信 一般是主动发送+被动接收,所以发送时直接发,接收数据前开启接收中断。
发送时,一般提前封装一个发送函数,用于调用发送长串数据。另外还可以重定义fputc,就可以使用print向发送数据。
接收时,要提前打开接收中断,LL_USART_EnableIT_RXNE(USART1); ,并在xxx_it.c的USART1_Handler()中添加接收代码。
各个代码如下:
//1.设置发送和接收的数组,记录数据长度
uint8_t ucRcv[20],ucTx[20];//接收存储数组
uint8_t ucNum; //接收的个数
//2.接收中断
//开启接收中断
LL_USART_EnableIT_RXNE(USART1);//在main里写,使能接收中断
//在xx_it.c的USART1_IRQHandler()中添加
if(LL_USART_IsActiveFlag_RXNE(USART1)==1)
ucRcv[ucNum++] = LL_USART_ReceiveData8(USART1); //存储的数据会存在数组ucRcv中
//3.发送数据
//在usart.c里写,之后调用该function实现发送
void USART1_Transmit(uint8_t *pData,uint8_t size){
uint8_t i;
for(i=0;i<size;i++){
while(LL_USART_IsActiveFlag_TXE(USART1)==0){} //判断发送寄存器是空的
LL_USART_TransmitData8(USART1,*pData++); //发送8bit数据
}
}
//print重定向,之后可以用print来发送数据
int fputc(int ch,FILE *f){
USART1_Transmit((uint8_t*)&ch,1);
return ch;
}
I2C
I2C的底层代码在i2c.c和i2c.h中写好了,添加到路径即可。
我们要做的是写24C02以及MCP407(编程可调电阻)的读写函数。在i2c.h和i2c.c中添加以下代码:
//在i2c.h中声明这两个函数
void MEM_Read(unsigned char* p,unsigned char Addr,unsigned char num);
void MEM_Write(unsigned char* p,unsigned char Addr,unsigned char num);
void MCP_Write(usigned char val);
//在i2c.c中写函数内容
//MEM读数据
void MEM_Read(unsigned char* p,unsigned char Addr,unsigned char num){
//在读取24C02前,要先向24C02发送命令和地址,收到回复后才可以开始读取24C02的数据
I2C_Start();
I2C_SendByte(0xa0);//24C02的写地址
I2C_WatiAck();//等待Acknowledge信号
I2C_SendByte(Addr);//Addr是要读取第几个Byte(1Byte=8bit)
I2C_WaitAck();
I2C_Start();
I2C_SendByte(0xa1);//24C02的读地址
I2C_WaitAck();
while(num--){
*p++=I2C_ReceiveByte();
if(num)
I2C_SendAck(); //如果要继续接收就发送一个应答
else
I2C_SendNotAck();//结束接收前,要发送一个非应答
}
I2C_Stop();
}
//MEM写数据
void MEM_Write(unsigned char* p,unsigned char Addr,unsigned char num){
I2C_Start();
I2C_SendByte(0xa0);
I2C_WaitAck();
I2C_SendByte(Addr);
I2C_WateAck();
while(num--){
I2C_SendByte(*p++);
I2C_WaitAck();
}
I2C_Stop();
LL_Delay(500); //指导书上写的 这段要加一个延迟500ms,不知道是什么原因;
}
//MCP可变电阻器写数据
void MCP_Write(usigned char val){
I2C_Start();
I2C_SendByte(0x5e);
I2C_WaitAck();
I2C_SendByte(val);
I2C_WaitAck();
I2C_Stop();
}
ADC
ADC1的IN5接R38,IN11接MCP407;ADC2的IN15接R37
ADC在使用前要先校准,等待校准完成后,使能ADC。开始ADC转换时,要等待转换完成,才可以读取ADC数据。具体代码如下:
//ADC1校准代码 ,ADC2校准代码一样
LL_ADC_StartCalibration(ADC1,LL_ADC_SINGLE_ENDED);
while(LL_ADC_IsCalibrationOnGoin(ADC1));
LL_ADC_Enable(ADC1);
//ADC1读取两个通道
LL_ADC_REG_StartConversion(ADC1);
while(LL_ADC_IsActiveFlga_EOC(ADC1)==0);
data[1] = LL_ADC_REG_ReadConversionData12(ADC1);
LL_ADC_REG_StartConversion(ADC1);
while(LL_ADC_IsActiveFlga_EOC(ADC1)==0);
data[2] = LL_ADC_REG_ReadConversionData12(ADC1);
TIM定时器
1、TIM1本次用于输出,使用的CH1N通道(注意不是CH1),对应PA7,如下图,

图 PA7在J3排针上
在配置TIM1的CH1N时要勾选Automatic Output State的“Enable”:

图 勾选Automatic Output State
2、TIM2本次用于输入,使用的是CH1和CH2,两个通道默认使用的是PA5,要重新选择为PA15,对应板子上的555输出:

图 PA15对应着U8的555输出
在配置TM2的CH1和CH2时,CH2要选择 Polarity Selection :“Falling Edge”

图 CH2选择Falling Edge
3、TIM3本次用于输出,使用的是CH1通道,对应着PA6,也在J3排针上,就在PA7上方。
4、使用之前,要使能TIM。
//建议:把这两行代码放在MX_TIM1_Init()里
LL_TIM_CC_EnableChannel(TIM1,LL_TIM_CHANNEL_CH1N);//使能TIM1的CH1N
LL_TIM_EnableCounter(TIM1) ;//允许TIM1
//...TIM2,TIM3也是类似的,用了什么通道、什么定时器,就写哪个的初始化,然后放到对应的定时器函数里
5.常用的TIM函数
LL_TIM_SetAutoReload(TIM1,value); //设置TIM1计数的值
LL_TIM_OC_SetCompareCH1(TIM1,value);//设置TIM1的通道1的比较值
LL_TIM_IC_GetCaptureCH1(TIM2)+1;//获取TIM2的通道1的捕获数值,1000000/捕获的值=频率
LL_TIM_IC_GetCaptureCH2(TIM2)+1;//获取TIM2的通道2的捕获数值,捕获的值等于周期(us)
更多推荐
所有评论(0)