PWM
首先,让我们先了解一下PWM的概念与基本原理。
PWM(Pulse width modulation),即脉冲宽度调制,其基本原理是控制方式对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。即在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率。
例如,把正弦半波波形分成N等份,就可把正弦半波看成由N个彼此相连的脉冲所组成的波形。这些脉冲宽度相等,都等于 π/n ,但幅值不等,且脉冲顶部不是水平直线,而是曲线,各脉冲的幅值按正弦规律变化。如果把上述脉冲序列用同样数量的等幅而不等宽的矩形脉冲序列代替,使矩形脉冲的中点和相应正弦等分的中点重合,且使矩形脉冲和相应正弦部分面积(即冲量)相等,就得到一组脉冲序列,这就是PWM波形。可以看出,各脉冲宽度是按正弦规律变化的。根据冲量相等效果相同的原理,PWM波形和正弦半波是等效的。对于正弦的负半周,也可以用同样的方法得到PWM波形。
在PWM波形中,各脉冲的幅值是相等的,要改变等效输出正弦波的幅值时,只要按同一比例系数改变各脉冲的宽度即可,因此在交-直-交变频器中,PWM逆变电路输出的脉冲电压就是直流侧电压的幅值。
PWM中重要的参数是周期与占空比。
占空比 = 高电平持续时间 / PWM时钟周期。
电机驱动模块L298N
L298N是双H桥电机驱动芯片,可以驱动两个直流电机或一个步进电机,能实现电机的正反转以及调速。
OUT1、OUT2可接直流电机,OUT3、OUT4也可接直流电机。
使用+12V电源输入来驱动模块,GND接电源负极,注意此GND要与单片机的GND相连共地,+5V输入至单片机。
ENA、ENB为使能端口,其中ENA使能左边电机,ENB使能右边电机,(默认高电平使能)IN1~IN4为控制端口,IN1、IN2控制左边电机,IN3、IN4控制右边电机。
当IN1 = 0(低电平),IN2 = 1(高电平)时,直流电机正转,反之则反转;IN3、IN4同理。
我们可使用单片机输入一个高电平给控制端口以控制电机转向,原本使能端口为高电平时,电机可一直均速转动,我们要拆掉使能端口的跳帽,用单片机对应引脚接使能端口,从而可以使用PWM调节电机转速。
基本原理
定时器的应用场景为定时器中断、PWM输出、输入捕获。
STM32分为三种定时器:①高级定时器(TIM1、TIM8):用于PWM电机控制;②通用定时器(TIM2~TIM5、TIM12~TIM17):用于定时计数、PWM输出、输入捕获以及输出比较;③基本定时器(TIM6、TIM7):用于驱动DAC。
本次实验采用的是通用定时器TIM4。
每个通用定时器完全独立,各拥有4个独立通道(TIMx_CH1~4)。
使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
定时器结构分为时基单元、计数器模式、时钟选择与捕获/比较通道。
定时器相关寄存器为 当前值寄存器CNT、预分频寄存器TIMx_PSC、自动重装载寄存器TIMx_ARR、控制寄存器与DMA中断使能寄存器TIMx_DIER等。
下图为通用定时器框图。
PWM波的占空比由CCRx决定,周期由ARR决定。
在向上计数模式下,使用PWM2模式,则CNT > CCRx时为有效电平,极性设置为HIGH,则有效电平为高电平。当CCRx由大变小时,CNT > CCRx的部分逐渐变大,得到的输出脉冲宽度也逐渐变宽,由此可得,输出电压也逐渐变大,LED灯由暗逐渐变亮,呼吸灯由此而来!电机转速也会由慢逐渐变快,如果直接设定CCRx为固定值,那么电机的转速也会固定,其大小可由占空比计算得出,只需改变捕获比较值CCRx就可以实现电机的调速啦!
PWM输出配置步骤
① 使能定时器4和相关IO口时钟。
② 初始化IO口为复用功能输出。
③ 把PB7用作定时器的PWM输出引脚,要重映射配置,所以需要开启AFIO时钟,同时设置重映射。
④ 初始化定时器:ARR,PSC等。
⑤ 初始化输出比较参数。
⑥ 使能预装载寄存器。
⑦ 使能定时器。
⑧ 不断改变比较值CCRx,达到不同的占空比效果。
硬件连接
用电烙铁焊接电线与直流减速电机的引脚,从而引出两端引脚,接入L298N电机驱动模块的OUT3、OUT4;将12V的电池电源正负极分别连接至L298N的+12V与GND端口;用跳线连接单片机的GND与L298N的GND,实现共地;用跳线连接单片机的+5V与L298N的+5V,向单片机供电,TTL高电平5V。
通用定时器TIM4的通道TIM4_CH1、TIM4_CH2可以分别作为PB6、PB7的引脚复用,而PB6、PB7已与LED1、LED2用跳线连接。用跳线连接单片机的PB7至L298N模块的使能端口ENB(因为我使用的是右边的电机,故需用到ENB、IN3 / IN4);由单片机输入一个高电平至L298N的IN3或IN4,我点亮了LED3,而LED3对应的是GPIO的PH4,故我将PH4与IN3或IN4连接,即可供给5V的TTL高电平给控制端口。
MDK调试——代码及注释说明
“main.c”
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
char i;
void SystemClock_Config(void);
int main(void)
{
uint16_t i; //定义无符号的16位整数,占2字节
HAL_Init(); //重置所有外围设备,初始化闪存接口和Systick
SystemClock_Config(); //配置系统时钟
/*初始化所有配置的外围设备*/
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM4_Init(); //初始化通用定时器4
printf("Waveshare STM32H7 PWM test\r\n");
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1); //使能定时器4通道1
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2); //使能定时器4通道2
while (1)
{
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET); //LED3 ON
i=i+3;
if(i>1000){
i = 0;
}
TIM4->CCR1 = i;
TIM4->CCR2 = 1000-i;
HAL_Delay(5);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0); //Supply configuration update enable
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); //Configure the main internal regulator output voltage
while ((PWR->D3CR & (PWR_D3CR_VOSRDY)) != PWR_D3CR_VOSRDY)
{
}
/* 初始化CPU、AHB和APB总线时钟 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 400;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_1;
RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
RCC_OscInitStruct.PLL.PLLFRACN = 0;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
|RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInitStruct.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
}
“tim.c” 定时器4初始化函数
void MX_TIM4_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 200-1; //预分频装载值
htim4.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
htim4.Init.Period = 1000; //周期,即ARR的值
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM2; //PWM2模式,CNT > CCRx为有效电平
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; //极性,有效电平为高电平
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim4);
}
“gpio.c” GPIO初始化函数
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED3_GPIO_Port, &GPIO_InitStruct);
}
“usart.c” 串口初始化函数
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.Prescaler = UART_PRESCALER_DIV1;
huart1.Init.FIFOMode = UART_FIFOMODE_DISABLE;
huart1.Init.TXFIFOThreshold = UART_TXFIFO_THRESHOLD_1_8;
huart1.Init.RXFIFOThreshold = UART_RXFIFO_THRESHOLD_1_8;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
注意在相关头文件中定义相关函数或结构体。
#define xxx