1.整体框架
在HAL库底层和MAIN应用层之间建立一个BSP中间层,HAL库底层由官方提供,BSP里用于各种外设硬件的初始化和基本功能函数编写,MAIN应用层编写程序的整个控制流程。
2.BSP模块
使用STM32CubeMX初始化时钟和外设
变量别名定义:
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
#define __IO volatile
__IO的作用(两根下划线) : 为了不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改
(1)系统时钟
a.首先用STM32CubeMX在相应的位置建立一个Source_Project文件工程,配置RCC系统时钟,选择外部高速时钟:Crystall / Ceramic Resonator (晶振)
b.配置系统时钟来源和分频系数
选择高速外部时钟设置为24Mhz,然后经过 (/3) --> (x20) -->(/2),然后选择PLLCLK时钟源,最终得到SYSCLK,时钟频率80Mhz
(2)Key 和 LED
Key原理图:
由于按键连接了一个上拉电阻,所以默认高电平,当按下按键时,电路闭合,IO口电平从高电平到低电平,检测IO口电平的变化就是判断按键是否按下的依据。GPIO设置为浮空输入模式(默认输入值不确定,读取值唯一),并且不带上下拉
STM32CubeMX配置:
按键源码(必背要考!!!)
// 按键扫描
uint8_t Key_Scan(void)
{
uint8_t val=0;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
val = 1;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
val = 2;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==0)
val = 3;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
val = 4;
return val;
}
// 按键处理函数
// 在main中声明的全局变量
__IO uint32_t uwTick_Key_Set_Point = 0; //控制key_Pro执行速度
//按键变量
uint8_t ucKey_Val, unKey_Down, ucKey_Up, ucKey_Old;
//按键扫描子函数
void key_proc(void)
{
if(uwTick - key_uwTick<50)return ;
key_uwTick = uwTick;
key_val = Key_Scan();
key_down = key_val & (key_old ^ key_val);
key_up = ~key_val & (key_old ^ key_val);
key_old = key_val;
if(key_down ==1)
{
ucled = 1<<5; // 0000 1000
}
if(key_down == 2)
{
ucled = 0;
}
}
判断按键的按下或者释放状态,只需要判断key_val的值,为1,则按键1被按下。
LED原理图:
LED的一端接入高电平,所以只要给另外一端(PC9-15)引脚输出低电平LED即可点亮,但是,LED灯的控制引脚与驱动LCD的引脚有重叠部分,所以在使用LCD的时候会影响LED的使用情况,所以LED需要使用锁存器
锁存器就是把当前的状态锁存起来,使输入的数据在接口电路的输出端保持一段时间锁存后状态不再发生变化,直到解除锁存
在控制LED的时候,只需在输出对应电平后,给锁存器的PD2管脚一个下降沿脉冲即可把对应的电平锁存到锁存器的输出端(Q1~Q8),从而控制LED,代码如下:
// 根据ucled的值控制led的亮灭
HAL_GPIO_WritePin(GPIOC,ucled<<8,GPIO_PIN_RESET);//输出相应的电平
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);// 开启锁存,输出电平
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);// 关闭锁存
STM32CubeMX配置
上电默认LED灯全灭(PC8~PC12为高电平)、锁存器不通(PD2为低电平)、全部为推挽输出、全部无上下拉、全部为低速
LED源码(必背)
// 在main中声明变量
__IO uint32_t uwTick_Led_Set_Point = 0; //控制Led_Pro执行速度
//LED变量
uint8_t ucLed;
//LED显示函数
void LED_Disp(uint8_t ucLed)
{
//**将所有的灯熄灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
//根据ucLed的数值点亮相应的灯
HAL_GPIO_WritePin(GPIOC, ucLed<<8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
//LED处理函数
//***LED扫描子函数
void Led_Proc(void)
{
if((uwTick - uwTick_Led_Set_Point)<200) return;//减速函数
uwTick_Led_Set_Point = uwTick;
LED_Disp(ucLed);//显示,也可以在别的地方调用
.......
}
第0位翻转:ucLed ^= 0x01
第一位翻转:ucLed ^= 0x20
第二位翻转:ucLed ^= 0x40
某一位翻转:ucLed ^= (1<< x) 将第x位翻转(0-7)
某一位清0: ucLed &= ~(1<<3) 第3位清0(0-7)
某一位置1: ucLed | = (1<< 3)
(3)LCD
移植官方给的LCD测试例程:BSP层移植相应的三个文件lcd.h,lcd.c,fonts.h
在初始化时一定要先进行清屏设置,字体的颜色和背景色
// LCD初始化时,要清屏,设置背景色和文本色!!!!
LCD_Init();
LCD_Clear(White);
LCD_SetTextColor(Red);
LCD_SetBackColor(White);
液晶屏大小为320*240,若按行来显示的,显示屏共有Lin0~Lin9,每行可以显示20个字符
若传入数字则是以像素为单位
LCD一共分为10行,20列,所以一个字符的大小为24*16,可以看到lcd.h宏定义里面每一行之间的差值就是24,那么每一列之间的差值就是16,但表示一列的时是反着来的,320表示第一列,320-16表示第二列,那么320-(16 * i)表示第i列
//在Line4,第5列显示字符'A'
LCD_DisplayChar(Line4,(320 - (16 * 5)),'A');
若用字符显示函数来显示数据,需要每一位分开,并且加上字符'0'(48)。
格式化字符串: 将格式化的数据写入字符串buffer中
int sprintf(char *buffer, const char *format, [argument]…)
参数:
buffer:是char类型的指针,指向写入的字符串指针;
format:格式化字符串,即在程序中想要的格式;
argument:可选参数,可以为任意类型的数据;
函数返回值:buffer指向的字符串的长度;
LCD源码
__IO uint32_t lcd_uwTick = 0; //控制Lcd_Pro执行速度
//LCD变量
uint8_t display[21]; //最多显示21个字符
// LCD初始化时,要清屏,设置背景色和文本色!!!!
LCD_Init();
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LCD_Clear(Black);
//LCD处理函数
void lcd_proc(void)
{
if(uwTick - lcd_uwTick<200)return ;
lcd_uwTick = uwTick;
sprintf((char *)display,"tets %d",1);
LCD_DisplayStringLine(Line1,display);
}
(4)UART
STM32G4串口资源:一般使用UART1即可
USART1: PA9-->USART1_TX PA10-->USART1_RX
USART2: PA2-->USART2_TX PA3-->USART2_RX
USART3: PC10-->USART3_TX PC11-->USART3_RX
串口配置时要配置串口的引脚,串口参数,NVIC(优先级),时钟选择
编写UART的接收与发送函数
串口发送数据:HAL_UART_Transmit()
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
//huart:串口句柄
//pData : 发送数据的地址
//Size :发送数据的大小
//Timeout :超时时间
串口通信一般最简单的有两个中断:串口接收中断、串口发送中断。实际上应用中常用串口接收中断,这样可以在监测到有外部设备对本机通过串口发送数据时就可以马上进入中断服务函数,在服务函数中接收数据
串口接收数据和开启接收中断:HAL_UART_Receive_IT()
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
//huart:串口句柄
//pData : 接收数据缓冲区地址
//Size :发送数据的大小
UART代码
__IO uint32_t uart_uwTick = 0;//控制Usart_Proc的执行速度
uint8_t rx_buf,rx_data[20],rx_index=0,tx_data[20];
// rx_buf用于一位一位的接收字符,rx_dex接收字符的索引,rx_data接收的字符内容
// tx_data串口发送字符
//在while前要开启接收中断,在串口初始化后加上下面这行代码
//HAL_UART_Receive_IT(&huart1, (uint8_t *)(&rx_buf), 1);
//串口发送处理函数
//若收到指令,先对指令进行长度判断,在对内容进行比较,
//不管收到的字符是否对错,在函数退出前都要将数组索引和内容清理,为一下一次做准备。
void Usart_Proc(void)
{
if((uwTick - uart_uwTick )<300) return;//每300ms执行一次
uart_uwTick = uwTick;
if(rx_index==6)// 假设指令为6位(\n会被当作成2位) ,粗略的判断
{
if(rx_data[0]=='L'...)// 精确判断,每一位处理
{
//收到正确指令执行的命令
}
else // 指令错误
{
//发送error,并且每一次都进行清0
rx_index=0;
memset(rx_data,0,strlen((char*)rx_data));
}
}
else if(rx_index==0) // 没有发送字符,不回应
{
}
else // 数据长度不一样,错误
{
//发送error,并且每一次都进行清0
rx_index=0;
memset(rx_data,0,strlen((char*)rx_data));
}
// 接收完一帧数据,为下一次做准备,上面的清零其实到最后来清零也可以
rx_index=0;
memset(rx_data,0,strlen((char*)rx_data));
}
//串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) // 若是串口1
{
rx_data[rx_index++]= rx_buf; // 处理收到的字符串
HAL_UART_Receive_IT(&huart1, &rx_buf, 1);
}
}
void *memset(void *s, int c, unsigned long n);
将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。memset() 的作用是在一段内存块中填充某个给定的值。memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化
int strcmp(const char *string1,const char *string2)
字符串中每个字符的ascii码值大小,但也可以用来判断两个字符串是否相等。
如果相同则返回0,如果不同,前者大于后者则返回1,否则返回-1。
在串口中\n,会被当作成两位字节。
对串口处理的数据,转换为int后,需要减去48('0')
(5)IIC(24C02,MCP4017)
移植官方底层IIC代码,I2CWaitAck()函数需要修改
将SDA_Output_Mode()移动到最后一行
24C02芯片地址:1010 000(R/W) R: 1 W: 0
读:1010 0001 (0xa1) 写: 1010 0000(0xa0)
连续写时序:
void write_24c02(uint8_t *buf,uint8_t addr,uint8_t num)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
while(num--)
{
I2CSendByte(*buf++);
I2CWaitAck();
}
I2CStop();
}
随机地址读时序:
void read_24c02(uint8_t *buf,uint8_t addr,uint8_t num)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
while(num--)
{
*buf++ = I2CReceiveByte();
if(num)
I2CSendAck();
else
I2CSendNotAck();
}
I2CStop();
}
MCP4017器件地址:0101 111(R/W) R: 1 W : 0
读地址:0101 1111(0x5f) 写地址:0101 1110(0x5e)
写字节时序:
MCP4017一行中的' 0 ' 表示应答
void write_mcp(uint8_t val)
{
I2CStart();
I2CSendByte(0x5e);
I2CWaitAck();
I2CSendByte(val);
I2CWaitAck();
I2CStop();
}
读字节时序:
uint8_t read_mcp(void)
{
uint8_t val=0;
I2CStart();
I2CSendByte(0x5f);
I2CWaitAck();
val = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return val;
}
写进去的是一个数字(0-127),读出来也是一个数字
电阻:R = 787.402 * read_resistor() 欧
电压:3.3*(R/(R+10k)) (假设外接的电压为3.3)
注:在使用IIC协议时,一定要在主函数里面初始化IIC(I2CInit())
(6)ADC
STM32G4内部集成2个有最高12位ADC(ADC1、ADC2),它们是逐次逼近型模数转换器,ADC 的输入电压范围设定在: 0~3.3v,因为 ADC是 12 位的,则12 位满量程对应的就是 3.3V, 12 位满量程对应的数字值是: 2^12=4096,数值0 对应的就是 0V。 如果转换后的数值为 X , X 对应的模拟电压为 Y,则:Y / 3.3 = X / 4096 , => Y = (3.3 * X ) / 2^12
原理图:
滑动变阻器的动触点通过连接至 STM32 芯片的 ADC 通道引脚。当我们旋转滑动变阻器(R37,R38)调节旋钮时,其动触点电压也会随之改变,电压变化范围为 0~3.3V,亦是开发板默
认的 ADC 电压采集范围
ADC1通道11(对应引脚PB12)单次转换模式
STM32CubeMX配置:
参数配置只需要设置异步时钟2分频和rank中采样时间,其他采用默认初始设置即可。
读取ADC采集的数据:
// 读取ADC采集的数据
uint16_t getADC1(void)
{
uint16_t adc = 0;
HAL_ADCEx_Calibration_Start (&hadc1,adc); //开启adc前添加校准
HAL_ADC_Start(&hadc1); // 开启ADC通道
adc = HAL_ADC_GetValue(&hadc1);
return adc;
}
电压换算:( (float)getADC1() ) / 4096) * 3.3
ADC算术平均数字滤波:
uint16_t getADC(uint8_t times)
{
uint32_t adc_sum=0;
uint8_t i =0;
for(i=0;i<times;i++)
{
adc_sum += getadc();
HAL_Delay(5);
}
return adc_sum / times;
}
(7)基本定时器(TIM6/7) 定时
当基本定时器累加的时钟脉冲数超过预定值时,能触发中断 , 定时100ms产生一次中断
STMCubeMX配置:
定时器的参数主要是设置预分频系数和ARR自动重装载值,自动重转载预装载是用来控制ARR寄存器的影子寄存器,若使能,影子寄存器有效,只有在事件更新时才把 TIMx_ARR 值赋给影子寄存器。如果不使能,修改 TIMx_ARR 值马上有效,这里使能/不使能都可以,因为我们不用去改变ARR的值
定时时间 = 1 / {80 Mhz / (psc + 1)} * (arr + 1) s
定时器6 定时代码
//初始化定时器后,记得一定要开始定时器中断,和UART一样的
HAL_TIM_Base_Start_IT(&htim6); // 开启中断
// 100mS更新一次
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
/* 程序编写*/
......
}
}
(8)输入捕获(PWM频率和占空比) PWM 输入捕获模式
STM32G4上有两个信号发生器(PA15和PB4),通过调节R39, R40可以产生不同频率和占空比的PWM
测量占空比和频率使用 PWM 输入捕获模式,该模式是输入捕获的特例,需要使用通道 1 和通道 2,所以需要占用两个捕获寄存器
定时器从模式选择复位Reset模式:使用内部时钟作为时钟源,TI1/2外部有效信号复位计数器。CH1触发输入上升沿有效时,先将计算器的值移动到CCR比较寄存器中,然后计数器复位到默认值0(注意事件发生的顺序)
当PWM信号由TI1进入,配置TI1FP1为触发信号,上升沿捕获,当上升沿的时候,IC1捕获,先将计算器的值移动到CCR1中,然后计数器CNT清零;到了下降沿的时候,IC2捕获,此时计数器CNT的值被锁存到捕获寄存器CCR2中;到了下一个上升沿的时候,IC1捕获,计数器CNT的值被锁存到捕获寄存器CCR1中,然后又恢复到0。 其中CCR2测量的是脉宽,CCR1测量的是周期,所以占空比 = CCR2 / CCR1
STM32CubeMX配置:通道1设置为上升沿捕获,通道2设置为下降沿捕获,定时器频率1Mhz
输入捕获源码
//*pwm输入捕获相关变量
uint16_t PWM_T_Count;
uint16_t PWM_D_Count;
float PWM_Duty;
//*输入捕获PWM启动
HAL_TIM_Base_Start(&htim2); /* 启动定时器 */
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1); /* 启动定时器通道输入捕获并开启中断 */
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);
// 输入捕获PWM中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM3) // 定时器判断
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) // 通道1
{
PWM1_T_Count = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1)+1; // f = 1e6/PWM1_T_Count
PWM1_Duty = (float)PWM1_D_Count / PWM1_T_Count; // 占空比
}
else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) // 通道2
{
PWM1_D_Count = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2)+1;
}
}
if(htim->Instance == TIM2)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
PWM2_T_Count = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1)+1; // f = 1e6/PWM2_T_Count 1e6由设置预装载值而来
PWM2_Duty = (float)PWM2_D_Count / PWM1_T_Count ; // 占空比
}
else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) // 通道2
{
PWM2_D_Count = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2)+1;
}
}
}
普通输入捕获:定时器通道2
采用定时器2的通道2作为信号的输入,必须将通道2设置为直接捕获模式并且上升沿触发,通道1间接捕获模式并且下降沿触发。注意:使能自动预载值。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
{
period_val= HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2)+1; // arr值
duty_val = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1)+1; // compare值
if(period_val)
{
__HAL_TIM_SET_COUNTER(&htim2,0); // 清0,为下一次捕获做准备
freq = 1000000/period_val; // 频率
duty = duty_val/period_val*100; // 占空比
}
}
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
}
这里period_val,duty_val,freq,duty都建议采用float,以减小误差。
(9)输出比较模式(方波) 电平翻转模式
输出通道有8种模式,这里采用的是比较输出模式(2),即电平翻转模式。当匹配时,引脚状态翻转。假设CCR寄存器里面的值设定为100 (注意这个不是控制波形的频率,只是一个初始值,波形频率的设置在中断里面设置),那么计数器从0开始计数,每隔100次,电平将会翻转一次,并且通过中断设置下次比较值,改变波形的频率( __HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_1,(__HAL_TIM_GetCounter(htim)+500)); //这里最好初始脉冲值设置为500,1Khz),这样就通过设定CCR寄存器里面的值就可以输出不同频率的方波
系统时钟经过分频得到1M作为计数时钟,计一个数需要t=1e-6s, 波的频率 1/[2*( 100*t)] =5khz,__HAL_TIM_SET_COMPARE(htim,TIM_CHANNEL_1,(__HAL_TIM_GetCounter(htim)+100));
STM32CubeMX配置: 设置系统时钟为1Mhz, 频率设置:Pulse脉冲值,一定要使能auto-reload preload,因为每一次中断里面要更改比较寄存器CCR的值
//*输出方波PA2引脚
HAL_TIM_OC_Start_IT(&htim15,TIM_CHANNEL_1);
// 方波输出中断回调函数
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM15) // 定时器判断
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) // 通道1
{
// 每次中断计数器的值加100 Pulse = 100
__HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, (__HAL_TIM_GetCounter(htim)+100)); // 5khz
}
else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) // 通道2
{
// 每次中断计数器的值加500 Pulse = 500
__HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_2, (__HAL_TIM_GetCounter(htim)+500)); // 1khz
}
}
}
(10)输出比较模式(PWM) PWM模式
采用输出比较模式选择PWM模式,PWM(脉冲宽度调制)模式可以产生一个由TIMx_ARR寄存器确定频率(周期)、由TIMx_CCRx寄存器确定占空比的PWM信号
在递增计数模式下,CNT从 0 开始计数到ARR并生成计数器上溢事.,假设 ARR=999,CCR=300, CNT 从 0 开始计数,当 CNT<CCR 的值 时 , OCxREF为有效的 电平,比较中断寄存器 CCxIF置 位;当CCR=<CNT<=ARR 时,OCxREF 为无效的电平。然后 CNT 又从 0 开始计数并生成计数器上溢事件,以此循环往复,生成PWM,设置不同的CCR可以调节不同的占空比,设置ARR值可调节周期
若计数时钟为1M,则PWM的频率 = 1 / (1e-3) = 1000hz, 占空比为30%
STM32CubeMX配置:设置系统时钟为1Mhz,ARR:周期,Pulse(脉冲,设置的是CRR值,一个比较值):占空比 ,中断不用设置
PWM代码:
//*启动定时器3和定时器17通道输出,在while前加入
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); //PA6
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1); //PA7
(11)RTC实时时钟
RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A 和 ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志
它使用的时钟源有三种,分别为高速外部时钟的, 低速内部时钟 LSI 以及低速外部时钟 LSE,这里使用的高速外部时钟HSE32分频得到750khz 的rtc_ker_ck
rtc_ker_ck时钟在经过7位异步分频和15位同步分频得到了ck_spre(默认值1hz),ck_spre用于更新日历时间等信息
通过读取 RTC_TR 和 RTC_DR 来得到当前时间和日期信息
STM32CubeMX配置:时钟来源选择HSE然后经过32分频得到750khz,在经过125和6000分频得到1hz计数时钟
RTC代码:
//RTC变量
RTC_TimeTypeDef sTime = {0}; // 时间
RTC_DateTypeDef sDate = {0}; // 日期
//*RTC内容显示
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);//读取日期和时间必须同时使用
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sprintf((char *)Lcd_Display_String, "Time:%02d-%02d-%02d",(unsigned int)sTime.Hours,(unsigned int)sTime.Minutes,(unsigned int)sTime.Seconds);
LCD_DisplayStringLine(Line4, Lcd_Display_String);
注意:一般先GetTime(),再GetData();
暂停或运行RTC
__HAL_RCC_RTC_ENABLE();
__HAL_RCC_RTC_DISABLE();
3. 易忘总结
a.GPIO模式选择:
LED | 推挽输出模式(Output Push Pull),不带上下拉 |
KEY | 浮空输入模式(Input mode),不带上下拉 |
ADC | 模拟输入模式(Analog mode),不带上下拉 |
USART1_R/TX | 复用推挽输出模式(Alternate Function Push Pull) ,不带上下拉 |
输入捕获(PA15,PB4) | 复用推挽输出模式(Alternate Function Push Pull) ,不带上下拉 |
波形输出 | 复用推挽输出模式(Alternate Function Push Pull) ,不带上下拉,输出速率高 |