一、写在前面

        蓝桥杯嵌入式组,比赛用国信长天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)

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐