STM32CubeMX新建工程
1、在RCC中的HSE选择第三个选项Crystal/Ceramic Resonator,并在时钟树选择页面填写外部时钟为24MHz,在HCLK(主时钟频率)填写为80MHz。
2、在Project Manager页面中选择工程路径、填写名称,Toolchain/IDE选择MDK-ARM 在Code Generator页面选择Copy only the necessary library files(仅复制所需要的库文件)以及Generate peripheral initialization as a pair of '.c.h' files per peripheral(将初始化代码分为独立的C文件和H文件)
3、生成工程并打开,在魔术棒选项的C/C++中的Optimization中选择Level 0(-O0)(方便Debug),并在Debug页面中选择CMSIS-DAP Debugger(选择使用DAP烧录方式),在Settings-Flash Download中勾选Reset and Run(烧录后自动复位)
TIM初始化
1、在Timers页面中选择对应定时器,高级及以上的定时器因在Clock Source选项中选择时钟为Internal Clock
2、在Parameter Settings中配置预分频寄存器PSC、自动重装载寄存器ARR和控制寄存器CCR,并选择auto-reload preload选择Enable开启自动重载
**计算公式: 频率: Freq = CK_PSC/(PSC + 1)/ (ARR+1)***
3、在NVIC Settings选项卡中勾选开启中断
PWM输出
1、在STM33CubeMX中选择对应引脚配置为TIMn_CHn
2、在Timers-TIMn选项卡中选择Channeln(对应通道)为PWM Generation CHn模式,并在Clock Source选项中选择Internal Clock
3、设置预分频寄存器PSC、自动重装载寄存器ARR和控制寄存器CCR
计算公式: PWM频率: Freq = CK_PSC/(PSC + 1)/ (ARR+1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Rseo = 1 / (ARR + 1)
STM32G431的内部主时钟正常为80MHz,若要产生一个频率为1KHz,占空比为50%,分辨率为1%的PWM波形
对应频率 1000 = 80 * 10^6 / (PSC + 1) / (ARR + 1)
占空比 50% = CCR / (ARR + 1)
分辨率 1% = 1 / (ARR + 1)
则ARR = 99, CCR = 50, PSC = 799
PWM输出相关函数说明
-
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
功能描述 使能定时器的对应通道,产生PWM 入口参数1 *htim:TIM句柄的地址,一般为&htim_n 入口参数2 Channel:对应通道,为TIM_CHANNEL_n 注意事项 函数需由用户调用才能开启PWM输出 -
#define __HAL_TIM_SetCompare(TIM_HandleTypeDef *htim, uint32_t Channel, __COMPARE__)
函数说明 设置定时器对应通道的比较值 入口参数1 *htim:TIM句柄的地址,一般为&htim_n 入口参数2 Channel:对应通道,为TIM_CHANNEL_n 入口参数3 COMPARE:CCR的值,注意占空比为CCR/(ARR+1) 注意事项 该函数由用户调用才可更改占空比
定时器计数达到比较值后,会触发中断或产生某种信号,一般用于修改PWM输出的占空比 -
#define __HAL_TIM_SetAutoreload(TIM_HandleTypeDef *htim, __AUTORELOAD__)
函数说明 设置定时器预分频系数 入口参数11 *htim:TIM句柄的地址,一般为&htim_n 入口参数2 AUTORELOAD:设置预分配系数的值 注意事项 调用该函数后需要重新设置自动重载值
USART初始化
1、在STM32CubeMX中选择对应引脚配置为串口TX和RX
2、在USARTn选项卡中选择模式为Asynchronous(异步串口通信模式)
3、在USART1-Parameter Setting选项卡中的Baud Rate(通信波特率)填写9600或115200
4、在UASRTn-NVIC Settings中勾选Enabled开启串口中断
USART相关函数说明
-
void USART1_IRQHandler(void)
串口中断处理函数,由STM32CubeMX自动生成,在stm32g4xx_it.c文件中,为中断响应后的步骤,可以在此函数中编写中断处理逻辑,会自动清除中断标志位
功能描述 |
---|
-
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
功能描述 回调函数,用于处理所有串口的接受中断,用户在该函数内编写实际的任务处理 入口参数1 huart:串口句柄的地址 返回值 无 注意事项 1. 函数由串口中断通用处理函数HAL——UART——IRQHandler调用,完成所有串口的发送中断任务处理
2、函数内部需要根据串口句柄的实例来判断是哪一个串口产生的中断
3、函数由用户根据具体的处理任务编写 -
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef huart, uint8_t *pData, uint16_t Size);
功能描述 | 在中断方式下发送一定数量的数据 | |
---|---|---|
入口参数1 | huart | 串口句柄的地址,一般为*hurat n |
入口参数2 | pData | 待发送数据的首地址,一般为字符串名 |
入口参数3 | Size | 待发数据的个数 |
返回值 | HAL状态值 | |
注意事项 | 函数将使能串口发送中断;该函数由用户调用 |
-
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能描述 | 在中断方式下接收一定数量的数据 | |
---|---|---|
入口参数1 | huart | 串口句柄的地址,一般为&huart n |
入口参数2 | pData | 只想数据缓冲区的指针 |
入口参数3 | Size | 待接收数据的个数 |
返回值 | HAL状态值 | |
注意事项 | 函数将使能串口接收中断; |
UART串口DMA初始化
1、在USART已经配置完成的情况下,在USART n-DAM Settings选项卡中点击Add,在DMA Pequest选项中选择USARTn_RX并添加USARTn_TX
-
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
功能描述 以DMA方式发送一定量的数据 入口参数1 huart:UART句柄,通常为&huart n 入口参数2 pData:指向数据缓冲区的指针 入口参数3 Size:要发送的数据元素的数量 返回值 HAL状态值 注意事项 该函数为使用DMA方式进行串口发送数据;
该函数由用户调用 -
HAL_StatuTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t pData, uint16_t Size)
功能描述 在DMA模式下接收一定量的数据 入口参数1 huart:UART句柄 入口参数2 pData:指向数据缓冲区的指针 入口参数3 Size:要接收的数据元素的数量 返回值 HAL状态值 注意事项 该函数为使用DMA方式进行串口接收数据;
该函数由用户调用; -
#define _HAL_UART_ENABLE_IT(_HANDLE_, _INTERRUPT_)
功能描述 使能指定的UART中断 入口参数1 HANDLE:指定UART句柄 入口参数2 INTERRUPT:指定要启用的UART中断源 返回值 无 注意事项 该函数由用户调用 -
#define _HAL_UART_GET_FLAG(__HANDLE_, _FLAG_)
功能描述 检查指定的UART标志是否设置 入口参数1 HANDLE:指定UART句柄 入口参数2 FLAG:指定要检查的标志 返回值 FLAG的新状态(TRUE或FALSE) 注意事项 该函数由用户调用 -
#define _HAL_UART_CLEAR_IDLEFLAG(_HANDLE_)
功能描述 清除UART IDLE挂起标志 入口参数1 HANDLE:指定UART句柄 返回值 无 注意事项 该函数由用户调用
ADC初始化
1、在STM32CubeMX中选择对应引脚配置为ADC模式(ADCn_IN15),并在ADC中打开对应通道(IN15 Single-ended)
2、在代码中使用HAL_ADC_Start(&ADCn)函数进行ADC使能
ADC相关函数说明
-
HAL_ADC_PollForConversion(&hadc n, Timeout)
功能描述:等待ADC转换完成 Timeout:等待时间(ms)
-
uint32_t HAL_ADC_GetValue(&hadc n)
功能描述:获取ADC按设定规则转换的结果
-
HAL_ADC_Stop(&hadc n)
功能描述:停止ADC转换
一般采用打开、获取、关闭的步骤获取ADC的值
uint16_t Get_ADC1_Value(void)
{
uint16_t ADC_Value = 0;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 5);
ADC_Value = HAL_ADC_GetValue(&hadc1);
return ADC_Value;`
}
DAC初始化
1、在STM32CubeMX中选择对应引脚配置为DAC模式,并在DAC中选择对应通道模式为Connected to external pin only
void HAL_DAC_SetValue(hdac n, DAC_CHANNEL_n, DAC_ALIGN_12B_R, temp)
void DAC1_Set_Vol(float vol) /* 设置DAC1的通道1为对应电压值 */
{
uint16_t temp;
temp = (4096 * vol + 3.3f);
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN12B_R, temp);
}
PWM输入捕获
1、在对应引脚口选择TIMn_CHn
2、在Timers-TIMn页面中的对应Channeln选择Input Capture direct mode(输入捕获)
3、在Parameter Settings中配置PSC为79, Counter Period为0xFFFF
PWM输入捕获相关函数说明
-
HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
函数说明 开启PWM输入捕获 入口参数1 *htim:TIM句柄的地址,一般为&htim_n 入口参数2 Channel:对应通道,为TIM_CHANNEL_n 注意事项 该函数有用户调用才可开启输入捕获 -
__weak void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
函数说明 输入捕获中断回调函数 入口参数1 *htim:TIM句柄的地址,一般为&htim_n 注意事项 该函数为弱定义,使用可以重新定义该函数
可以使用htim->Instance == TIM3的方式判断是哪个定时器产生的中断
//输入捕获PWM的频率和占空比
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim17)
{
if(tim17_state == 0)//第一个上升沿产生,开始计时
{
__HAL_TIM_SetCounter(&htim17, 0);//把CNT清零
TIM17->CCER|=0x02; //下降沿中断,就是要把CC1P置为1
tim17_state = 1;
}
else if(tim17_state == 1)//第一个下降沿产生,获取T1的值,就是高电平的时长
{
Tim17_cnt1 = __HAL_TIM_GetCounter(&htim17);//获取CNT的值
TIM17->CCER &=~0x02;
tim17_state = 2;
}
else if (tim17_state == 2)//第二个上升沿产生,获取T2的值,就是整个周期
{
Tim17_cnt2 = __HAL_TIM_GetCounter(&htim17);//获取CNT的值
PA7_Freq = 1000000 / Tim17_cnt2; // Tim17_cnt2;周期的倒数就是频率
PA7_Duty = Tim17_cnt1 * 100.0f / Tim17_cnt2;
tim17_state = 0;
}
HAL_TIM_IC_Start_IT(&htim17, TIM_CHANNEL_1);
}
}
常用模块
长按、短按、双击按键检测
typedef struct
{
uint8_t State;
uint8_t Who;
int8_t Up_Flag;
uint32_t Down_Time;
uint32_t Last_Time;
uint8_t Clicks;
}Key_State;
Key_State Key;
//该函数需要在滴答定时器中定时调用
void Key_S(void)
{
//Key.State是状态值,分为按键悬空(0)、按键按下状态(1)、按键释放状态(2)
if (Key.State == 0)
{
if (Key.Up_Flag == -1) //Key.Up为-1是按键预释放标志位,表示不是第一次按下
{
if (uwTick - Key.Last_Time >= 500)//如果释放时间超过500ms则触发按键释放标志位
{
Key.Up_Flag = 1;
}
}
if (Key1 || Key2 || Key3 || Key4)
{
Key.State = 1;
}
}
else if (Key.State == 1)
{
if (Key1)
{
Key.Who = 1;
}
else if (Key2)
{
Key.Who = 2;
}
else if (Key3)
{
Key.Who = 3;
}
else if (Key4)
{
Key.Who = 4;
Key.Down_Time += 10; //长按时长检测
}
else
{
Key.State = 2;
}
}
else if (Key.State == 2)
{
if (Key.Up_Flag == -1)
{
Key.Clicks++; //按键按下次数计数
Key.Down_Time = 0; //多次按键屏蔽长按时长
}
Key.Up_Flag = -1; //置预释放按键标志位
Key.State = 0; //状态转移至悬空等待
Key.Last_Time = uwTick;
if (Key.Clicks == 0) //单次按键不会触发计数值,需手动置为1
Key.Clicks = 1;
}
}
//该函数需要在主循环中轮询调用
void Key_Scan(void)
{
if (Key.Up_Flag == 1)
{
sprintf(str, "Key%d Time:%d :%d ", Key.Who, Key.Down_Time, Key.Clicks);
LCD_DisplayStringLine(Line0, (unsigned char *)str);
Key.Down_Time = 0;
Key.Up_Flag = 0;
Key.Clicks = 1;
}
}
串口不定长数据分析
typedef struct
{
char Buffer[2];
char Data[25];
uint8_t Cnt;
uint32_t LastTime;
uint8_t Flag;
}Rx_Typedef;
Rx_Typedef Rx;
char str[UART_SIZE], Tx[UART_SIZE];
/* 将串口接收到的数据后的处理写在Rx_Task函数中 */
void Rx_Task(void)
{
sprintf(str, "Rx:%s ", Rx.Data);
LCD_DisplayStringLine(Line1, (unsigned char *)str);
HAL_UART_Transmit(&huart1, (unsigned char *)str, strlen(str), 0xFFFF);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == huart1.Instance)
{
Rx.Data[Rx.Cnt++] = Rx.Buffer[0];
Rx.LastTime = uwTick;
}
HAL_UART_Receive_IT(&huart1, (unsigned char *)Rx.Buffer, 1);
}
//该函数需要在主函数轮询调用,然后判断Rx_Stuct.Data[0]是否不为'\0'
void Rx_Scan(void)
{
if (uwTick - Rx.LastTime < 50)
return;
if (Rx.Data[0] != '\0')
{
Rx_Task();
}
memset(Rx.Data, '\0', sizeof(Rx.Data));
Rx.LastTime = uwTick;
Rx.Cnt = 0;
}
LCD与LED冲突解决办法
//在LCD_DisplayStringLine函数的开始记录GPIOC的ODR寄存器并上锁,并在函数结尾恢复ODR寄存器的值并解锁
void LCD_DisplayStringLine(u8 Line, u8 *ptr)
{
uint32_t led = GPIOC->ODR;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
u32 i = 0;
u16 refcolumn = 319;//319;
while ((*ptr != 0) && (i < 20)) // 20
{
LCD_DisplayChar(Line, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
}
GPIOC->ODR = led;
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
}
PWM设置固定频率与占空比
void Set_PWM(TIM_HandleTypeDef htim, uint32_t Channel, uint16_t Target_Freq, uint16_t Target_duty)
{
//如果设定占空比为0或100%,则重新初始化GPIO,输出为低电平或高电平
if(Target_duty == 0 || Target_duty == 100)
{
HAL_TIM_PWM_Stop(&htim, Channel);
if(Target_duty == 0)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
else if(Target_duty == 100)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
PA1_Init_Flag = 1;
GPIO_Reload_Init(Set_PIN);
return ;
}
//如果PA1为推挽初始化状态,则重新初始化PWM输出
if(PA1_Init_Flag == 1)
{
PA1_Init_Flag = 0;
MX_TIM2_Init();
HAL_TIM_PWM_Start(&htim, Channel);
}
uint32_t Basic_Freq = 80000000; //主频
uint16_t Prescale = 80-1; //预分频系数
float Freq = 0, Duty = 0;
//计算PWM频率,所对应的自动重装载值 ---> ARR = 主频 / (预分频+1) / 预期PWM频率(Hz) -
Freq = Basic_Freq * 1.0 / (Prescale + 1) / (Target_Freq * 1.0f) - 1;
//计算PWM占空比,所对应比较寄存器的值 ---> CCR = 预期占空比 * (自动重装载值+1)
//占空比则由捕获/比较寄存器(TIMx_CRx)寄存器决定。占空比:duty = Pluse / (ARR+
Duty = (Freq * 1.0) / 100.0 * (Target_duty * 1.0f);
// //配置PSC预分频值
// __HAL_TIM_SET_PRESCALER(&htim, Prescale);
//配置PWM频率 ARR
__HAL_TIM_SetAutoreload(&htim, (uint16_t)Freq);
//配置PWM占空比
__HAL_TIM_SetCompare(&htim, Channel, (uint16_t)Duty);
}
//GPIO推挽输出初始化
void GPIO_Reload_Init(int IO)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = (uint16_t)1 << IO;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}