本文针对于具有一定HAL库开发基础的人复习,如有侵权,我表示抱歉,请联系作者更改或删除,谢谢。本资料大部分来源于蚂蚁科技资料,已购买!!!!
Day一、LED
LED与LCD会发生冲突,在初始化LCD之后需要关闭LED。单独编写操作LED的函数。
LCD_Init();//LCD 初始化
LED_Control(0x00);//关闭LED控制
根据此原理图,我们控制LED时需要打开锁存器后才能控制LED,即先打开锁存器(拉高PD2电平),再关闭锁存器 (拉低PD2电平),Q端的数据就可以稳定输出。
设置关于LED 的IO口全为低电平输出模式
1.编写单独的LED控制函数:
#include "led.h"
void LED_Control(u8 led_ctrl)
{
//先熄灭所有LED灯 //HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15,GPIO_PIN_SET); //让PC8~PC15输出高电平,熄灭LED
HAL_GPIO_WritePin(GPIOC, 0xff00, GPIO_PIN_SET); //让PC8~PC15输出高电平,熄灭LED
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); //打开锁存器
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); //关闭锁存器
//根据led_ctrl来点亮对应的LED
HAL_GPIO_WritePin(GPIOC, led_ctrl << 8, GPIO_PIN_RESET);//根据led_ctrl输出低电平,点亮LED
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); //打开锁存器
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); //关闭锁存器
}
使用示例:我的板子是0x55为 0101 0101 0是咩,1时亮,请注意你的板子引脚设置
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
LED_Control(0xff);//全开
HAL_Delay(500); //延时500毫秒
LED_Control(0x00);//全关
HAL_Delay(500); //延时500毫秒
}
另一种方法更为高效,采用滴答定时器的方式,编写单独的LED函数,在主循环里调用该函数任务即可。
// LED执行程序
__IO uint32_t ledTick = 0;
u8 led_ctrl = 0xff;
void LED_Process(void)
{
if(uwTick - ledTick < 100) return ; //不阻塞状态延时100ms
ledTick = uwTick;
LED_Control(led_ctrl);
led_ctrl = ~led_ctrl;
}
要采用不阻塞状态延时,需要创建一个函数,本质上是满足技术到达设定值时才可运行下面的条件任务,否则直接return退出函数,继续执行其他任务命令
Day二、按键控制
根据原理图,按键按下,IO读到低电平,松开按键,IO读到高电平。
按键需要消抖,一般为10ms延时消抖。
设置按键引脚为 浮空输入模式,
代码如下:
1.编写KEY驱动函数
#include "key.h"
#define KB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define KB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define KB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define KB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
#define KEYPORT KB1 | (KB2<<1) | (KB3<<2) | (KB4<<3) | 0xf0
u8 Trg; // 全局变量,单次触发
u8 Cont; // 全局变量,长按
void Key_Read(void)
{
u8 ReadData = (KEYPORT)^0xff; // 1 (异或,不同为1)
Trg = ReadData & (ReadData ^ Cont); // 2
Cont = ReadData; // 3
}
2.编写KEY任务函数
// 按键执行程序
__IO uint32_t keyTick = 0;
u16 key1_val = 0;
u16 cnt_key_time = 0;
void Key_Process(void)
{
if(uwTick - keyTick < 10) return ;
keyTick = uwTick; //不阻塞状态 延时10ms
Key_Read();
//短按操作
if(Trg & 0x01) //B1
{
LED_Control(0x01);//第1个LED显示
key1_val ++;
}
if(Trg & 0x02) //B2
{
LED_Control(0x02);//第2个LED显示
}
if(Trg & 0x04) //B3
{
LED_Control(0x04);//第3个LED显示
}
if(Trg & 0x08) //B4
{
LED_Control(0x08);//第4个LED显示
}
//下面为处理长按操作:
if(Cont & 0x01)
{
cnt_key_time++;
if(cnt_key_time == 100) //连续按下1S
{
cnt_key_time =0;
LED_Control(0xff);//全开
}
}
}
Day三、ADC
STM32G431内部集成2个12位ADC(ADC1、ADC2)
根据G431的原理图,我们需要对PB15和PB12进行电压采集,
可以max中设置PB15为ADC2 IN15 ,PB12为ADC1 IN11,均采用(single—ended)单端模式
简单的 开启功能,采集值,转换值
// ADC
u16 adc1_val,adc2_val;
float volt_r37,volt_r38;
void ADC_collect_process(void)
{
HAL_ADC_Start(&hadc1); //启动ADC1的功能
adc1_val = HAL_ADC_GetValue(&hadc1);//采集ADC1的值
volt_r38 = adc1_val/4095.0f*3.3f; //数据转换成实际的电压值
HAL_ADC_Start(&hadc2); //启动ADC2的功能
adc2_val = HAL_ADC_GetValue(&hadc2);//采集ADC2的值
volt_r37 = adc2_val/4095.0f*3.3f; //数据转换成实际的电压值
}
Day四、LCD液晶屏
LCD液晶屏的分辨率为320*240 (一行可显示20个字符,显示10行)
比赛提供的HAL_LCD例程,相关的IO初始化已经初始化完成。
在使用例程时:
单纯的初始化需要注意:
LCD_Init(); //初始化LCD
LED_Control(0x00); //关闭LED
LCD_Clear(Blue); //清屏,背景为蓝色
LCD_SetBackColor(Blue); //设置背景为蓝色
LCD_SetTextColor(White);//设置文本颜色为白色
LCD_Process(); //LCD 任务函数
// LCD执行程序
void LCD_Process(void)
{
u8 display_buf[20];
//【问题】长数据对短数据覆盖问题
sprintf((char *)display_buf, "%d", 4000);
LCD_DisplayStringLine(Line0, display_buf);
sprintf((char *)display_buf, "%d", 10);
LCD_DisplayStringLine(Line0, display_buf);
//--> 解决方案:加空格,针对字符串
LCD_DisplayStringLine(Line2, "hello");
LCD_DisplayStringLine(Line2, "hi ");
//--> 解决方案:格式化输出,针对数据
sprintf((char *) display_buf, "%5d", 4000); //显示5位,默认右对齐
LCD_DisplayStringLine(Line3, display_buf);
sprintf((char *) display_buf, "%5d", 10);
LCD_DisplayStringLine(Line3, display_buf);
//格式化输出例子
sprintf((char *) display_buf, "%-5d", 10); //左对齐
LCD_DisplayStringLine(Line4, display_buf);
sprintf((char *) display_buf, "%05d", 500); //前面补0
LCD_DisplayStringLine(Line5, display_buf);
sprintf((char *) display_buf, "%5.3f", 3.1415926); //显示小数,总长是5位数(小数点算1位),小数点后是2位
LCD_DisplayStringLine(Line6, display_buf);
sprintf((char *) display_buf, "%x", 15); //%x显示16进制,%o显示8进制
LCD_DisplayStringLine(Line7, display_buf);
sprintf((char *) display_buf, "%c", 'a'); //%s字符串,%c字符
LCD_DisplayStringLine(Line8, display_buf);
sprintf((char *) display_buf, "%d %% ", 10); //输出百分号:%
LCD_DisplayStringLine(Line9, display_buf);
}
使用sprintf函数时,需要引用外部头文件#include "stdio.h",包括串口也会用到此头文件,在数据转换时,要避免数据格式错误,长数据对短数据的覆盖,应在后面加入空格或格式化!!!
Day五;IIC协议的理解与应用
蓝桥杯开发板用到IIC协议的外设有
AT240C2 EEPROM存储器
MCP4107 AD 芯片 (数字电位器)
IIC 有两条线
SCL:时钟线
SDA:数据线
在使用时需要对两条线进行上拉处理
SCL为高时,SDA必须保持稳定!
故 SCL为低时,SDA上的电平才可以变化!
写数据时:SCL为低,改变SDA
读数据时,SCL为高,读取IO电平
一、IIC协议的使用,EEPROM
1、注意事项
EEPROM的器件地址:1010 000(R/W)
写 0xA0代表单片机向AT24C02写数据
读 0xA1代表单片机向AT24C02读数据
写入周期 5ms
2、代码编写
首先引用官方文件包的I2C.C和.H文件包,因为引脚在文件包中已经配置好,所以我们只需要配置好引用的代码顺序就可以正常使用。
比赛中需要自己编写EEPROM的读写函数,只需要看数据手册的时序进行编写即可,很轻松。
写函数:
//比赛中,需要自己编写EEPROM的读写函数
//写24C02
void EEPROM_Write(u8 add, u8 dat)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
HAL_Delay(5);
}
读函数:
//读24C02
u8 EEPROM_Read(u8 add)
{
u8 dat;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(add);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
dat = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return(dat);
}
然后在头文件中进行函数声明即可。
该代码为显示单片机启动次数
//EEPROM
I2CInit();//引用初始化函数
startup_times = EEPROM_Read(0x20);//读取该地址的数据
EEPROM_Write(0x20, ++startup_times);//对该地址写入数据
LCD_Process(); //LCD显示
// EEPROM
u8 val_24c02 = 0;
u8 startup_times = 0;
// LCD执行程序
void LCD_Process(void)
{
u8 display_buf[20];
sprintf((char *)display_buf, "%3d", startup_times);//显示当前重启次数
LCD_DisplayStringLine(Line0, display_buf);
}
二、数字电位器(MCP4017)
1.注意事项
可通过PB14对电位器输出 的电压进行采样,
MCP4017的器件地址:0101 111(R/W)
写 0x5e 代表单片机向电位器写数据
读 0x5f 代表单片机向电位器读数据
协议格式请于数据手册5.4进行查找
读取到的电压值数据计算:参考电压*(设定电阻/(设定电阻+10k)),设定电阻为电位器设定值,
实际阻值:0~100k,
参数值:0~0x7f
设定阻值计算: 参数值*(100k/127)
就算参数值设置为0,它本身还有0.1k欧姆的阻值
2.代码编写
写 函数:
//写MCP4017
void MCP4017_Write(u8 val)
{
I2CStart();
I2CSendByte(0x5E);
I2CWaitAck();
I2CSendByte(val);
I2CWaitAck();
I2CStop();
}
读 函数:比赛中可用可不用
//读MCP4017
u8 MCP4017_Read(void)
{
u8 val;
I2CStart();
I2CSendByte(0x5F);
I2CWaitAck();
val = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return val;
}
使用示例:
//MCP4017
MCP4017_Write(0x70);//写数据
val_mcp = MCP4017_Read();//读取数据
3.在使用双通道ADC
需在cubemax设定PB14为ADC1 通道5
配置ADC的RANK和采样速度;
并开启轮询采集方式,设定为2 rank,同时也要注意轮询的顺序,和采样时间,可以改为640个时钟周期,保证电压的准确性;
代码:
// ADC执行程序
u16 adc1_val, adc2_val;
float volt_r37, volt_r38, volt_mcp;
void ADC_Process(void)
{
//RANK1 - CH5
HAL_ADC_Start(&hadc1);
volt_mcp = HAL_ADC_GetValue(&hadc1) / 4096.0f * 3.3f;
//RANK2 - CH11
HAL_ADC_Start(&hadc1);
adc1_val = HAL_ADC_GetValue(&hadc1);
volt_r38 = adc1_val / 4096.0f * 3.3f;
//ADC2的采集
HAL_ADC_Start(&hadc2);
adc2_val = HAL_ADC_GetValue(&hadc2);
volt_r37 = adc2_val / 4096.0f * 3.3f;
}
Day六、DAC
官方原理图
对应的引脚为
ADC1 PA4 -> OU1
PA5->OUT2
均设置输出到外设。
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);
}