STM32G431RBT6+CubeMX+Keil
这个选项配置应该是选择下载调试方法;用这款单片机开发板的话,就应该是这个样子配置,但是原先配置的是ST-link的,这个方法应该就是用ST-Link的下载器下载程序的,
创建工程相关事宜

这个选项配置应该是选择下载调试方法;用这款单片机开发板的话,就应该是这个样子配置,但是原先配置的是ST-link的,这个方法应该就是用ST-Link的下载器下载程序的,
RCC时钟

记住按回车保存时钟树的数据(上图) ,不然这个模板复制之后时钟树的数据并不会保存下来
出现头文件未找到的问题
1:首先检查创建的时候是否放在的指定的文件夹,(这里我用的是江科大的创建文件的方法)
2:再在魔法棒->C/C++里面看看是否把文件夹的路径添加进来
3:还有就是看看三个箱子选项;看看是否文件夹里面有对应的.c和.h文件
4:上面三个都没有问题的话,就去魔法棒里面的c/c++,把对应的文件夹都删了,在创建一个新的,再到主界面双击这个文件夹,再把.c和.h文件添加回来;不要创建,要添加进来,应该就是说要在外面创建一个zzuser专属文件夹,然后在里面添加进来;
LED
原理图

注意点:
1:74HC573锁存器,只要高电平使能这个锁存器就可以了,
2:但是在只点亮一个的时候,发现八个灯都点亮了,而且每次都是其他灯随机点亮的,推测是其他的引脚未设置数入模式,未设置的时候就应该是默认浮空输入,就会出现随机亮灯的情况,我的解决方法是,在MX里面给每个LED灯的引脚都设置为上拉输入,这个就不会出现随机亮灯的情况。
闪烁灯

点亮1位灯的封装函数
void LED_Display_Bit(uint8_t LED, uint8_t state) //LED参数为LED位号, state参数为LED状态,1亮0灭
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
if(state)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8<<(LED-1), GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8<<(LED-1), GPIO_PIN_SET);
}
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
键盘Key

简单阻塞按键
#include "stm32f1xx_hal.h" // Device header
uint8_t KeyNum;
void Key_GetNum(void)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == 0)
{
HAL_Delay(20);
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == 0);
KeyNum = 4;
}
}
非阻塞式按键(只能检测单击 )
1:利用系统时间戳消抖,
2:我的理解:
在第一次按键按下后,last_state会被置为1,因为这个循环的时间发生的很快,第二次很快就检测到按键的现状态为1了,
因为没有按键按下,所以按键现状态为1;
然后判断if(now_state != Key[i].last_state) //判断现状态与旧状态是否相同,肯定是不一样的,因为上一个状态是按键按下,
接着就会更新旧状态为1,之后的消抖过程也会发生,稳定状态也会随之更新为1,为下一次按键按下做准备,
但是到了最后一步,检测按键的现状态是否为0(就是说按键是否按下),按下才会输出按键值
#define Key_delay_time 20
typedef struct{
GPIO_TypeDef * Port; //GPIOX
uint16_t pin; //引脚号
uint8_t last_state; //按键的旧状态
uint8_t stable_state; //按键消抖后的稳定状态
uint32_t last_time; //旧的时间戳的值,用来按键消抖
}Key_habdle;
Key_habdle Key[4]=
{
{GPIOB, GPIO_PIN_0, 1, 1, 0}, //Key1
{GPIOB, GPIO_PIN_1, 1, 1, 0}, //Key2
{GPIOB, GPIO_PIN_2, 1, 1, 0}, //Key3
{GPIOA, GPIO_PIN_0, 1, 1, 0} //Key4
};
uint8_t Get_KeyNum(void)
{
uint8_t Keynum = 0; //按键的键码值
uint8_t now_state = 0; //按键的现状态
uint32_t now_time = HAL_GetTick(); //获取现在的时间戳
for(uint8_t i=0; i<4; i++) //循环扫描按键的状态
{
now_state = HAL_GPIO_ReadPin(Key[i].Port, Key[i].pin); //获取当前的按键状态
if(now_state != Key[i].last_state) //判断现状态与旧状态是否相同
{
Key[i].last_time = now_time; //记录下现在的时间,为之后的消抖做准备
Key[i].last_state = now_state; //
}
if(now_time - Key[i].last_time > Key_delay_time) //按键的消抖
{
if(Key[i].stable_state != now_state) //判断现状态与稳定状态是否相同
{
Key[i].stable_state = now_state; //更新稳定状态
if(now_state == 0) //这步最关键,检测按键是否按下,按下才能输出按键键码值
{
Keynum = i+1;
}
}
}
}
return Keynum;
}
/*
在第一次按键按下后,last_state会被置为1,因为这个循环的时间发生的很快,第二次很快就检测到按键的现状态为1了,
因为没有按键按下,所以按键现状态为1;
然后判断if(now_state != Key[i].last_state) //判断现状态与旧状态是否相同,肯定是不一样的,因为上一个状态是按键按下,
接着就会更新旧状态为1,之后的消抖过程也会发生,稳定状态也会随之更新为1,为下一次按键按下做准备,
但是到了最后一步,检测按键的现状态是否为0(就是说按键是否按下),按下才会输出按键值
*/
非阻塞式按键(只能检测单击 )(江科大版本)
#include "stm32g4xx.h" // Device header
#define Key_Delay_Time 20
uint8_t Num;
uint8_t Key_GetNum(void)
{
uint8_t Temp = 0;
if(Num)
{
Temp = Num;
Num = 0;
}
return Temp;
}
uint8_t Key_Getstate(void)
{
uint8_t state = 0;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) //Key1
{
state = 1;
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET) //Key2
{
state = 2;
}
else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET) //Key3
{
state = 3;
}
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) //Key4
{
state = 4;
}
return state;
}
void Key_Tick(void)
{
static uint8_t new_state = 0;
static uint8_t old_state = 0;
static uint8_t count = 0;
count++;
if(count>=Key_Delay_Time)
{
count = 0;
old_state = new_state;
new_state = Key_Getstate();
if((old_state == 0)&&(new_state == 1))
{
Num = 1;
}
else if((old_state == 0)&&(new_state == 2))
{
Num = 2;
}
else if((old_state == 0)&&(new_state == 3))
{
Num = 3;
}
else if((old_state == 0)&&(new_state == 4))
{
Num = 4;
}
}
}
易错点
1:代码状态错误
void LED_Pro(void)
{
static uint8_t temp = 0;
Key_GetNum();
if(KeyNum == 4)
{
KeyNum = 0;
LED_Dispaly(~temp);//状态切换逻辑缺失:按下按键时仅仅显示 ~temp,但未更新 temp 的值,导致每次显示的可能是随机值或无变化。
}
}
状态切换逻辑缺失:按下按键时仅仅显示 ~temp,但未更新 temp 的值,导致每次显示的可能是随机值或无变化。
下面是正确的方法;

//
2.LED状态

这个状态用完之后要快速的清零,如果是全局变量的话,还要用完之后就清零,防止状态一直不更新清零,导致后面的定时器里面的检测状态不起效,所以一般就是用局部变量的,如果要全局变量的就是要用完之后要清零;
//
LCD屏幕

注意点:
1:引脚使用了PA8,PB5,PB8,PB9,PC0~PC15;
基本的初始化和显示函数

解决LCD与LED的引脚冲突问题
1:首先就是在操作LED的时候,在给LED的引脚赋值的时候,先使能573寄存器,在操作完LED的引脚之后呢,在失能573锁存器:代码示例如下;
void LED_Display_Bit(uint8_t LED, uint8_t state) //参数LED为LED位号,state为状态,0亮1灭
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);//使能573锁存器
if(!state)
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8<<(LED-1), GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8<<(LED-1), GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);//失能573锁存器
}
2:但是有时候的按键还是会与LCD冲突;这个时候直接在用到的LCD函数里面加一个下面这个操作,先把输出端的状态存起来,在LCD操作完PC端口后,再把之前存的状态值复原给输出端,这个样子无论LCD怎么改变PC口,PC口都会复原成改变之前的样子,

有LCD的地方都要

只需要在出现LCD函数的头尾加上这两话就可以了
封装LCD函数(可变参数)
c语言可变参数

简单求和函数

LCD封装函数
#include "stm32g4xx.h" // Device header
#include "lcd.h"
#include "stdio.h"
void zz_LCD_Init(void) //LCD初始化
{
LCD_Init();
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LCD_Clear(Black);
}
#include <stdarg.h> // 包含可变参数支持头文件
void zz_LCD_DisplayStr(uint8_t LineX, const char *format, ...)
{
char buffer[64]; // 足够大的缓冲区存储格式化后的字符串
// 设置可变参数列表
va_list args;
va_start(args, format);
// 将格式化字符串和参数写入缓冲区
vsnprintf(buffer, sizeof(buffer), format, args);
// 结束可变参数使用
va_end(args);
// 在指定行显示格式化后的字符串
LCD_DisplayStringLine(LineX, (uint8_t *)buffer);
}


定时器
系统滴答定时器
系统滴答定时器实现流水灯,这个定时器是1毫秒就进一次中断,

设置PSC预分频器和ARR自动重装载值

开启NVIC

设置好上面的参数,文件就会自己产生一个定时器文件,
注意点:

在上面这个文件夹下会有一个使能中断的函数

再这个文件夹下就会生成一个中断服务函数
这个中断函数好像不能移到其他的地方,但是我感觉只要把原来的那个中断函数给注释掉,就可以把这个中断函数挪位置,
最好不用这个上面的函数,最好用封装出来的回调函数

上图,上面这个函数是开启定时器的,下面的这个函数是开启中断的,如果需要开启中断的话,就只需要用一下下面的这个函数就可以了
中断回调函数
简介

**HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)**

**HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)**

**HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)**

**HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)**

**HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)**
**HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim)**

用法:

注意点
PWM
PWM输出比较-输出频率
cubeMX配置


注意:这个设置占空比的,应该来说设置为0,也没有问题,但是在设置PC8为呼吸灯的时候就不能设置为0,我觉得应该是设置为0的时候就直接没有频率,全都是第低电平哪里来的频率,一般不做要求的话,ARR设置为100-1;上面的占空比设置为50;
Keil代码
首先就是PWM输出频率的话,就要开启这个函数;
上图的函数是设置PWM使能的,但是说你如果要使用中断的话,还需要设置PWM的中断,应该这个中断函数是带着IT的,

然后如果需要改变频率的就可以用操作寄存器的方法;上图,但是我们只需要改变PSC就可以改变频率,所以我们进入这个函数里面:

操作PSC寄存器就可以直接的改变分频值 ;
下面是一些其他的方法,

其中的两个函数的参数,小小的看一下,具体不会的话可以Ai;
还有一点就是如果用的是蓝桥杯的板子,记得使能573锁存器
PWM输入捕获测量频率


上图:
1:基准时间t0是一个频率跳变的时间,
2:capture_value是每两个上升沿之间的时间;单位是毫秒;
3:然后就能得到周期T为基准时间t0乘以capture_value;
4:频率就为周期的倒数;带入就得到上面的公式,fsys是系统频率80MHz,psc就是分频系数


下面是各个参数的解释:


在设置完上面的设置后,最后记得开启NVIC中断

有一个小细节:死活找不到Tim.h文件的话,点击编译之后就会出来了
#include "stm32g4xx.h" // Device header
uint32_t Freq;
uint32_t count = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
count = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
Freq = 1000000/count;
count = 0;
}
}
}
但是感觉还是有问题,最高7万多,最低只有1万多,但是刚复位的时候,就只有700多,之后就没有最低700多了
经过验证是正确的
这个函数HAL_TIM_ReadCapturedValue(),在Stm32g4xx.hal.tim.h文件夹里面
555定时器-双通道测量频率

这个555定时器输出的频率测量起来还跟上面的PWM输入捕获测量频率不一样,但是他们都是测量频率,为什么会不一样,在csdn上面别人的MX配置方法好像跟又是一样的;
测量单个555定时器的时候好像又是一样
cubeMX配置
下面是PA15的

再勾选上NVIC。
下面的是PB4的;

上面的cube和代码还是有问题,
ADC测量
简介

读取一路ADC值
cubeMX配置

cubeMX只需要简单得使能一下ADC
Keil代码编写
ADC读取函数

显示函数

注意点
1:extern修饰的变量只能是全局变量

USART串口
简介:
原理图

代码:
发送
cubeMX配置

单单是发送的话,就用不到NVIC中断;
keil代码
void zz_UART_Send_Pro(void)
{
char UART_Text[20];
sprintf(UART_Text, "Hello World\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)UART_Text, sizeof(UART_Text), 50);
HAL_Delay(1000);
}
发送与接收:
cubaMX配置
接收与发送相比,接收用到了串口中断;
所以要多配置一下NVIC


Keil代码
#include "stm32g4xx.h" // Device header
#include "stdio.h"
#include "usart.h"
uint8_t UART_Recieve_data;
void zz_UART_Init(void) //这个初始化函数放在while大循环的外面
{
HAL_UART_Receive_IT(&huart1, &UART_Recieve_data, 1);
}
void zz_UART_Send_Pro(void) //发送的函数
{
char UART_Text[20];
sprintf(UART_Text, "Hello World\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)UART_Text, sizeof(UART_Text), 50);
HAL_Delay(1000);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //接收中断回调函数
{
if(huart-> Instance == USART1)
{
HAL_UART_Transmit(&huart1, &UART_Recieve_data, 1, 50); //
HAL_UART_Receive_IT(&huart1, &UART_Recieve_data, 1); //
}
}
我的理解是:
HAL_UART_Receive_IT(&huart1, &UART_Recieve_data, 1);这个函数是接收的中断使能函数,在初始化的时候调用一下,这个时候就已经准备好接收了,创建了一个地址,这个地址就已经准备好接收数据了,相当于这个函数调用一次,这个地址就能接收一次数据,当串口接收到一个数据的时候,会直接把数据发送到这个地址内,这个时候程序就会到了串口回调函数里面的,在把数据回显,然后就到了中断回调函数里面的HAL_UART_Receive_IT(&huart1, &UART_Recieve_data, 1);,再一次准备好接收;
利用定时器进行不定长数据的接收


注意点:
1:中断回调函数的使用

要谨慎使用这个函数,最好直接用她的回调函数;
2:串口数值传输的形式
一般我们在传输给单片机的一个‘1’,其形式是按照ASCII码表来传输的,1对应的码值是0x49;
在LCD上显示的话,直接显示就会显示一个49;这个时候有两个方法:
a:直接转换为字符显示

b:从ASCII码减去一个字符零,也就是48,然后进行显示;
这个有个前提:要保证这个字符值>'0';

日志:
EEPROM
简介:

RTC实时时钟
简介
代码
设置与读取时间
cubeMX配置


keil代码

易错点
1:
注意定义时间和日期的结构体是不一样的,获取时间和日期的函数也是不一样的
更多推荐




所有评论(0)