蓝桥杯嵌入式的学习总结
一. 前言
嵌入式竞赛实训平台(CT117E-M4) 是北京国信长天科技有限公司设计,生产的一款 “ 蓝桥杯全国软件与信息技术专业人才大赛-嵌入式设计与开发科目 “ 专用竞赛平台,平台以STM32G431RBT6为主控芯片,预留扩展板接口,可为用户提供丰富的实验场景。
以下内容都是小编自己在学习中所总结摘抄下来内容以及对嵌入式竞赛实训平台的相关理解,如有不到位之处,可尽情指出。如果全部完全理解掌握,拿个省二完全绰绰有余。
二. 学习CT117E-M4开发板
1. 首先需要了解开发板资源,可以通过查看CT117E-M4的产品手册获取相对应的知识。另外在开始学习前,需要准备好相对应的软件环境,包括嵌入式集成开发环境(Keil Realview MDK) 以及STM32CubeMx的相关环境配置。值得注意的是如果使用keil 5 集成开发环境,就得使用集成开发环境自带的Pack installer安装STM32G4系列器件包。这些资源我都会放在我主页的资源当中,如果有需要可以取走。
2. 在蓝桥云课上的右上角的搜索处输入蓝桥杯大赛历届真题即可查找到历届真题。
3. LED是由PC8~PC15这8个GPIO口控制,在低电平时候点亮,所以我们在初始化时就可以在CubeMX中让它们都置高。如下所示:
如上图所示,就实现了将PC8~PC15所有的GPIO口都置为高,因此刚开始所以LED灯都会灭。
4. GPIO端口总共有八种模式,输入模式有四种:上拉输入模式,下拉输入模式,浮空输入,模拟输入。输出模式有:推挽输出,开漏输出,复用推挽,复用开漏。
5. 这里简单介绍下这八种模式的概念:
1)上拉输入模式:上拉开关闭合,下拉开关断开,肖特基触发器打开。当IO引脚没有外部输入时,GPIO引脚会默认输入一个高电平,可以通过读取输入数据寄存器来读取到此时的IO电平。
2)下拉输入模式则跟上拉输入模式相反。
3)浮空输入模式:上拉开关和下拉开关均断开,肖特基触发器打开。此时,如果外部的IO引脚什么都不接(即悬空状态),此时GPIO引脚的电平将是一个不确定的状态。它将完全由外部的输入电平来确定。
4)模拟输入模式:肖特基触发器关闭,数据不再经过触发器模块。并且内部上下拉全部断开。该模式一般是给芯片内部的ADC外设来使用的。在该模式下,可以知道MCU将无法通过读取输入数据寄存器获得IO引脚的电平变化状态。
5)推挽输出模式:可输出高低电平,驱动能力强。
6)开漏输出模式:只能输出低电平,需要外部上拉电阻才能输出高电平。
7)复用推挽输出模式:用于特殊功能如UART、SPI等,由其他外设控制输出。
8)复用开漏输出模式:同样用于特殊功能,由其他外设控制输出。
6. 代码书写的位置:
1)private includes:用户头文件的引用。
2)private typedef:用户自定义数据类型。
3)private macro:用户宏函数定义。
4)private variables:用户常量和变量定义。
5)private function prototypes:用户函数说明。
6)private usercode:功能实现位置。
7)Initialize all configured peripherals:各种初始化位置。
7. 大家在安装完Keil集成开发环境后,需要选择CMSIS-DAP Debugger调试器,然后点击setting就能进入下面这个界面,然后再在Flash选项卡下,勾选Reset and Run。
三. 开发板各个外设的配置及使用
1. 首先从LED灯开始,由于上面已经讲过LED的CubeMx配置,所以我们直接来看下由CubeMx生成的keil 5工程代码中LED相关代码。
#include"led.h"
void LED_Disp(uchar dsLED)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET); //hign pin
HAL_GPIO_WritePin(GPIOC,dsLED<<8,GPIO_PIN_RESET); //some low
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET); //open Latch
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); //close Latch
}
其中,HAL_GPIO_WritePin这个就是HAL库中的一个内置函数,用来设置引脚电平。
下面我们再来看下led.h的代码,我们在每创建一个.c和.h文件后都要保存到相应工程目录的一个文件夹中,这里可以统一命名为bsp。
#ifndef _LED_H_
#define _LED_H_
#include "main.h"
void LED_Disp(uchar dsLED);
#endif
其中bsp文件夹就是存放的我们各种.c和.h文件,MDK-ARM中就有我们的keil5工程,下面那个test.ioc就是我们相应的CubeMx配置。在有了文件夹后,别忘了在keil5工程左侧目录中添加这个文件夹哦。最后一步就是让这个包含所有内容的总文件夹成为我们keil5工程的环境变量,点击keil5工程上方一个类似魔法棒的东西,然后选择其中的Include Paths如下所示:
2. 关于LCD的相关代码在官方给出的资料当中就有,我们可以直接拿过来使用。这里小编后面也会整理在我主页的资料当中。
3. 按键控制,通过查询产品手册,可以知道按键主要由PB1,PB2,PB0,PA0控制。
相关按键代码示例如下:
#include "interrupt.h"
#include "main.h"
struct keys key[4]={0,0,0,0};
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM1)
{
key[0].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].key_sta=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].key_sta=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(int i=0;i<4;i++)
{
switch(key[i].judge_sta)
{
case 0:
{
if(key[i].key_sta==0)
{
key[i].judge_sta=1;
key[i].key_time=0;
}
}
break;
case 1:
{
if(key[i].key_sta==0)
{
key[i].judge_sta=2;
}
else
{
key[i].judge_sta=0;
}
}
break;
case 2:
{
if(key[i].key_sta==1)
{
key[i].judge_sta=0;
if(key[i].key_time<70)
{
key[i].single_flag=1;
}
}
else
{
key[i].key_time++;
if(key[i].key_time>70)
{
key[i].long_flag=1;
}
}
}
break;
}
}
}
}
这里包含了按键的点击,长按的判断,并且使用到了定时器作为中断,用来间隔很短的时间来查询按键是否被按下。
下面我们来看下相关的CubeMx配置。
这里我们使用的是定时器TIM1,需要设置Clock Source为Internal Clock内部时钟源。并且在下面设置好分频系数(Prescaler)以及重装载值(Counter Period)。这里为什么设置成80-1,主要是根据频率和时间的关系来确定的。我们知道频率的倒数就是所间隔的时间,例如50Hz就是0.02s。因为在我们的产品手册中说明了频率为80MHz,也就是80000000,我们用这个数除以分频系数,再除以重装载值就是我们最后的定时器间隔时间。这里80000000除以80得1000000,再除以10000,得100,也就是100Hz,0.01s,10ms。之所以减一是因为这里从0开始。
为了方便,这里我把我们今天所讲到的内容所涉及到的GPIO配置直接给出来。
3. PWM输入捕获
与PA6和PA7有关,选择TIM16_CH1和TIM17_CH1。
double ccrl_val1a=0,ccrl_val2a=0;
uint ccrl_val1b=0,ccrl_val2b=0;
uint frq1=0,frq2=0;
float duty1=0,duty2=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)
{
ccrl_val1a=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
ccrl_val1b=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
__HAL_TIM_SetCounter(htim,0);
frq1=(80000000/80)/ccrl_val1a;
duty1=(ccrl_val1b/ccrl_val1a)*100;
HAL_TIM_IC_Start(htim,TIM_CHANNEL_1);
HAL_TIM_IC_Start(htim,TIM_CHANNEL_2);
}
}
if(htim->Instance==TIM3)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)
{
ccrl_val2a=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
ccrl_val2b=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
__HAL_TIM_SetCounter(htim,0);
frq2=(80000000/80)/ccrl_val2a;
duty2=(ccrl_val2b/ccrl_val2a)*100;
HAL_TIM_IC_Start(htim,TIM_CHANNEL_1);
HAL_TIM_IC_Start(htim,TIM_CHANNEL_2);
}
}
}
4. ADC数模转化
#include "badc.h"
double getADC(ADC_HandleTypeDef *pin)
{
double abc;
HAL_ADC_Start(pin);
abc = HAL_ADC_GetValue(pin);
return (abc*3.3)/4096.0;
}
5. IIC通信
相关硬件配置如下:
由此可以得出我们的IIC通信跟PB6和PB7有关。
关于IIC通信的相关代码,我们也可以直接使用官方所给的资料当中的代码,我们只需要在此基础上再添加两个代码即可,也就是IIC通信的读取和写入。如下所示:
unsigned char eeprom_read(unsigned char addr)
{
unsigned char dat;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
dat=I2CReceiveByte();
I2CSendAck();
I2CStop();
return dat;
}
void eeprom_write(unsigned addr,unsigned char dat)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
}
以上便是我们今天的全部内容,如还有些许疑问,可以直接私信我或者在评论区底下留言。另外所需的资源数据包以及相关的完整源代码文件可在我主页中进行查找,或者找我拿取。