00.新建工程(CubeMX)



1.Debug配置
点击魔术棒,debug改为cmsis,setting里设置reset and run。
2.文件目录配置
新建code文件,存放自己的代码;然后在keil内新建code文件夹


在魔术棒内添加code文件路径路径


在code内新建文件,注意:新建文件时不要忘记选择保存路径在code内!!!

新建headfile.h文件,添加标准头文件
#include "stm32g4xx.h"
#include <stdio.h>
#include <string.h>
#include <stdint.h>
01.LED

原理图
锁存器当PD2为高电平时数据才会开启传送,PC8-PC15置低电平的时候LED亮
第一步先把PD2置为高电平
第二步把LED置为低电平
第三步把PD2置为低电平 一定要置为低电平
IO配置
PC8~PC15以及PD2都配置为高电平,让LED上电的时候为高电平,默认熄灭;
注意!!!
PD2开始必须置为高电平才能写入!!!,因为接入锁存器锁定状态;
写入结束PD2必须置为低电平!!!
void led_on(void)
{
// PD2开始必须置为高电平才能写入,因为接入锁存器锁定状态
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);
// 写入结束PD2必须置为低电平!!!
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
02.按键
按键需要配置为上拉输入,没有按下时输入高电平,按下时输入低电平;
uint8_t key1, key2, key3, key4;
uint8_t key1_state = 0, key2_state = 0, key3_state = 0, key4_state = 0;
void key_scan(void)
{
key1 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key2 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key3 = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key4 = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
if (key1 == 0)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == 0);
key1_state = !key1_state;
led_on(1, key1_state);
led_on(2, key1_state);
}
if (key2 == 0)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == 0);
key2_state = !key2_state;
led_on(3, key2_state);
led_on(4, key2_state);
}
if (key3 == 0)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == 0);
key3_state = !key3_state;
led_on(5, key3_state);
led_on(6, key3_state);
}
if (key4 == 0)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0);
key4_state = !key4_state;
led_on(7, key4_state);
led_on(8, key4_state);
}
}
按键长短按函数
// 按键长按短按
uint8_t key1_state, key2_state, key3_state, key4_state;
uint8_t key1_last_state = 1, key2_last_state = 1, key3_last_state = 1, key4_last_state = 1;
int16_t count = 0;
uint8_t led_mode;
uint8_t lcd_highshow = 0;
void key_scan(void)
{
key1_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
key2_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
key3_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
key4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
// 按下瞬间
if (key1_state == 0 && key1_last_state == 1) // 按键按下
{
TIM3->CNT = 0;
}
// 一直按
else if (key1_state == 0 && key1_last_state == 0)
{
if (TIM3->CNT >= 10000)
{
count+=1;
HAL_Delay(200);
}
}
else if (key1_state == 1 && key1_last_state == 0) // 松开
{
if (TIM3->CNT < 10000)
{
count-=1;
}
}
if(key2_state == 0 && key2_last_state == 1) // 按键B2按下
{
TIM3->CNT = 0;
}
else if(key2_state == 0 && key2_last_state == 0) // 按键B2一直按着
{
if(TIM3->CNT >= 10000) //按键B2长按1s
{
count+=1;
HAL_Delay(200);
}
}
else if(key2_state == 1 && key2_last_state == 0) // 按键B2松开
{
if(TIM3->CNT < 10000) //按键B2短按
{
count-=1;
}
}
// 按下瞬间
if (key3_state == 0 && key3_last_state == 1) // 按键按下
{
TIM3->CNT = 0;
}
// 一直按
else if (key3_state == 0 && key3_last_state == 0)
{
if (TIM3->CNT >= 10000)
{
count+=1;
HAL_Delay(200);
}
}
else if (key3_state == 1 && key3_last_state == 0) // 松开
{
if (TIM3->CNT < 10000)
{
count-=1;
}
}
// 按下瞬间
if (key4_state == 0 && key4_last_state == 1) // 按键按下
{
TIM3->CNT = 0;
}
// 一直按
else if (key4_state == 0 && key4_last_state == 0)
{
if (TIM3->CNT >= 10000)
{
lcd_highshow+=1;
HAL_Delay(200);
}
}
else if (key4_state == 1 && key4_last_state == 0) // 松开
{
if (TIM3->CNT < 10000)
{
count-=1;
}
}
key1_last_state = key1_state;
key2_last_state = key2_state;
key3_last_state = key3_state;
key4_last_state = key4_state;
}
03.LCD
解决LCD与LED引脚冲突问题
首先在LCD_Init();前加上HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
在使用的LCD显示函数前加上uint16_t temp = GPIOx->ODR,如在LCD_Init();里和LCD_Clear(Black);,最后读取原状态,如:
void LCD_Init(void)
{
uint16_t temp = GPIOC->ODR;
LCD_CtrlLinesConfig();
dummy = LCD_ReadReg(0);
if(dummy == 0x8230)
{
REG_8230_Init();
}
else
{
REG_932X_Init();
}
dummy = LCD_ReadReg(0);
GPIOC->ODR = temp;
}
void LCD_Clear(u16 Color)
{
uint16_t temp = GPIOC->ODR;
u32 index = 0;
LCD_SetCursor(0x00, 0x0000);
LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
for(index = 0; index < 76800; index++)
{
LCD_WriteRAM(Color);
}
GPIOC->ODR = temp;
}
void LCD_DisplayStringLine(u8 Line, u8 *ptr)
{
uint16_t temp = GPIOC->ODR;
u32 i = 0;
u16 refcolumn = 319;//319;
while ((*ptr != 0) && (i < 20)) // 20
{
LCD_DisplayChar(Line, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
}
GPIOC->ODR = temp;
}
LCD高亮显示
需要哪行高亮就先使用LCD_SetBackColor();设置为高亮颜色,最后设置为初始化颜色;
char text[20];
void lcd_show()
{
sprintf(text, " TEST ");
LCD_DisplayStringLine(Line0, (uint8_t *)text);
lcd_highshow %= 3;
switch (lcd_highshow)
{
case 0:
LCD_SetBackColor(Yellow);
sprintf(text, " CNT: %d ", count);
LCD_DisplayStringLine(Line1, (uint8_t *)text);
LCD_SetBackColor(Black);
sprintf(text, " hello1 ");
LCD_DisplayStringLine(Line2, (uint8_t *)text);
sprintf(text, " hello2 ");
LCD_DisplayStringLine(Line3, (uint8_t *)text);
break;
case 1:
sprintf(text, " CNT: %d ", count);
LCD_DisplayStringLine(Line1, (uint8_t *)text);
LCD_SetBackColor(Yellow);
sprintf(text, " hello1 ");
LCD_DisplayStringLine(Line2, (uint8_t *)text);
LCD_SetBackColor(Black);
sprintf(text, " hello2 ");
LCD_DisplayStringLine(Line3, (uint8_t *)text);
break;
case 2:
sprintf(text, " CNT: %d ", count);
LCD_DisplayStringLine(Line1, (uint8_t *)text);
sprintf(text, " hello1 ");
LCD_DisplayStringLine(Line2, (uint8_t *)text);
LCD_SetBackColor(Yellow);
sprintf(text, " hello2 ");
LCD_DisplayStringLine(Line3, (uint8_t *)text);
LCD_SetBackColor(Black);
break;
}
}
04.PWM
PWM输出
计算公式

输出1000HZ,则PSC=800-1,ARR=100-1;80000000/(800*100)=1000;
首先使用HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);使能TIM的pwm输出功能

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
TIM2->CCR2 = 50;
// CCRx:x为通道号
输入捕获
在波形上升沿/下降沿产生一个中断
第一个上升沿TIMx->CNT = 0;下一个上升沿时uint16_t capture value = TIMx->CNT;即可算出周期T


PSC设置为80-1,能获得更大的计数;
初始化使能HAL_TIM_IC_Start_IT(&htimx, TIM_CHANNEL_x);
使用自定义测量引脚测量输出的PWM频率(PA7测量PA1)
配置PA7为输入捕获,PSC=80-1,使能中断;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM17)
{
capture_value = TIM17->CCR1;
//capture_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); //在捕获到上升沿时,会将CNT赋值给CCR ,可写成capture_value = TIM17->CCR1;
TIM17->CNT = 0;
fre = 80000000/(80*capture_value);
}
}
使用原理图指定引脚测量555定时器输出PWM频率
XL555(555定时器)可输出一个频率可调的PWM波频率可通过R39、R40等电位器调整;可以配置PA15或者PB4进行输入捕获测量

LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim16, TIM_CHANNEL_1);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM16) //频率输出1 R39
{
//在捕获到上升沿时,会将CNT赋值给CCR
capture_value1 = TIM16->CCR1;
//capture_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
TIM16->CNT = 0;
fre1 = 80000000/(80*capture_value1);
}
if(htim->Instance == TIM2) //频率输出2 R40
{
//在捕获到上升沿时,会将CNT赋值给CCR
capture_value2 = TIM2->CCR1;
//capture_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
TIM2->CNT = 0;
fre2 = 80000000/(80*capture_value2);
}
}
05.ADC测量

PB15、PB12引脚配置为ADC输入模式,测量旋转R37、R38电位器变化的电压值


double get_vol(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Start(hadc);// 一定不能忘记ADC打开函数!!!!!!!!!!
uint32_t adc_value = HAL_ADC_GetValue(hadc);
return 3.3*adc_value/4096;
}
06.串口发送与接收

使能中断

串口发送
char text01[20];
sprintf(text01, "Lan Qiao Bei\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)text01, sizeof(text01), 50);// 50为Timeout时间
HAL_Delay(1000);
串口接收
g4的Rx先接收电脑发过来的数据,存放到rec_data中,然后HAL_UART_Transmit传给电脑,再使用HAL_UART_Receive_IT开启下一次接收
HAL_UART_Receive_IT(&huart1, &rec_data, 1);
uint8_t rec_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit(huart, &rec_data, 1, 50);
HAL_UART_Receive_IT(huart, &rec_data, 1);
}
}
利用定时器进行串口不定长数据接收
串口接收:每次进入中断只能接收1个字节的数据;
考虑错误情况:
1.每次接收一个字节就要判断该字节是否符合要求(非常复杂)
2.接收完所有字符,判断字符串是否符合要求
解决方法:利用定时器,处理串口接收
串口波特率 = 9600 bit/s 就是1s可以传输9600bit
串口传输一次数据包含起始位,数据位,结束位,一共10bit
10 * 1/9600 = 0.00104 秒 = 1.04 毫秒
核心思想:PSC=8000-1,8000/80000000=1/10000,即100us计数器加1,计数器数到15,1500us,1.5ms,当定时器大于1.5ms没接收到数据时判断数据接收完成。
HAL_UART_Receive_IT(&huart1, &rec_data, 1);
HAL_TIM_Base_Start(&htim4);
char send_buff[20];
uint8_t rec_data, count;
uint8_t rec_flag;
uint8_t rec_buff[20];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
//HAL_UART_Transmit(huart, &rec_data, 1, 50);
TIM4->CNT = 0;
rec_flag = 1;
rec_buff[count] = rec_data;
count++;
// 准备下一次接收
HAL_UART_Receive_IT(huart, &rec_data, 1);
}
}
void uart_data_rec()
{
if(rec_flag)
{
if(TIM4->CNT > 15) //数据接收完成
{
//处理数据
if(rec_buff[0] == 'l' && rec_buff[1] == 'a' && rec_buff[2] == 'n') //lan
{
sprintf(send_buff, "lan\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
}
else if(rec_buff[0] == 'q' && rec_buff[1] == 'i' && rec_buff[2] == 'a' && rec_buff[3] == 'o') //qiao
{
sprintf(send_buff, "qiao\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
}
else if(rec_buff[0] == 'b' && rec_buff[1] == 'e' && rec_buff[2] == 'i') //bei
{
sprintf(send_buff, "bei\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
}
else
{
sprintf(send_buff, "error!\r\n");
HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
}
rec_flag = 0;
for(int i=0; i<count; i++)
rec_buff[i] = 0;
count = 0;
}
}
}
07.eeprom读写
EEPROM:非易失性存储器(掉电后数据不丢失)
记住两点:
1.主机给从机发信息,从机接到信息后要给主机回应
2.从机给主机发信息,主机接到信息后也要给从机回应
0XA0(写)/0XA1(读)


软件I2C:要将PB6、PB7设为OUTPUT;
GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_6;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStructure.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
void eeprom_write(uint8_t addr, uint8_t dat)
{
I2CStart();
I2CSendByte(0xa0); // 芯片地址
I2CWaitAck();
I2CSendByte(addr); // 片内地址
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
HAL_Delay(20);
}
uint8_t eeprom_read(uint8_t addr)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
uint8_t dat = I2CReceiveByte();
I2CSendNotAck(); //主机不应答 从机不再发数据
I2CStop();
return dat;
}
08.rtc实时时钟
基础配置


显示时间
char text[20];
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint8_t led_mode;
void lcd_show()
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sprintf(text, " test ");
LCD_DisplayStringLine(Line0, (uint8_t *)text);
sprintf(text, " %2d:%2d:%2d ", sTime.Hours, sTime.Minutes, sTime.Seconds);
LCD_DisplayStringLine(Line1, (uint8_t *)text);
sprintf(text, " %d-%d-%d-%d ", sDate.Year, sDate.Month, sDate.Date, sDate.WeekDay);
LCD_DisplayStringLine(Line2, (uint8_t *)text);
}
闹钟中断
char text[20];
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
uint8_t led_mode;
void lcd_show()
{
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sprintf(text, " test ");
LCD_DisplayStringLine(Line0, (uint8_t *)text);
sprintf(text, " %2d:%2d:%2d ", sTime.Hours, sTime.Minutes, sTime.Seconds);
LCD_DisplayStringLine(Line1, (uint8_t *)text);
sprintf(text, " %d-%d-%d-%d ", sDate.Year, sDate.Month, sDate.Date, sDate.WeekDay);
LCD_DisplayStringLine(Line2, (uint8_t *)text);
led_show(1, led_mode);
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
led_mode = 1;
}

壁纸选的很有品味
包的,我的审美再别说😄