这篇博客介绍下我是如何使用CubeMX生成基础环境并调用HAL库来产生硬件PWM的。
103C8T6的通用定时器,1个定时器可以产生4个通道的PWM波,每个通道对应了不同的Pin脚。我这次用的是PB5(TIM3_CHANNEL2)。
分两种办法:
1.自己配置相应的寄存器,配置好之后,类似51单片机那样使能输出,则相应的Pin脚就会输出对应的PWM波。
2.使用HAL库,HAL库提供了丰富的接口,调用对应的函数,并且传入正确的参数,那么也能使相应的Pin脚产生目标波形。
介绍下第二种方法:
首先CubeMx配置好基础环境:
定时器初值这个部分:
补上上一篇博客的定时器定时时间计算公式:
(ARR+1)*(PSC+1)/Tclk=time
也就是说假如我要定时10ms作为PWM的总周期,并且一个周期分为100次计数,那么TIM3的定时时间就应该是(100-1+1)*(2400-1+1)/2400 0000 (24M时钟)=0.01s,即10ms的总周期。
设置完总周期之后,我可以得知PWM的频率为100Hz,1个周期分为了100份。这时候开始设置下面的这个PWM设置。PWM模式 1和2 区别就是定时器当前值跟占空比设置的值做对比,如果定时器当前值更大则输出PWM/或者不输出。
上面截图上的注释中:模式1应该修改为模式2
模式1: 在向上计数的模式中,定时器值<设置的占空比值 时 ,PWM输出。在向下计数模式中,定时器当前值<=设置的占空比值 时,PWM输出。
模式2跟模式1是相反的。
完成了上面的CubeMX设置后,生成代码。进入到调用步骤:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
可以在主函数里面看到MX_TIM3_Init();
这个就是CubeMX生成的定时器3的初始化函数。
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2); 这个函数功能就是让定时器3通道2的PWM开始输出。至于这个函数的内容我这里不考究了。因为用HAL库就是要学会调用库,至于库为什么能实现,那是底层层面的内容。根据前面弄51的经验,这个库函数的最终就是根据我们传入的参,判断哪些寄存器的哪些位作一些改变。根本上来说还是操作寄存器,只不过用HAL库开发就是省略了我们自己去操作这些寄存器的麻烦步骤。
再来看看MX_TIM3_Init();函数内容:
void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 2400-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 100-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
重点看:
htim3.Instance = TIM3;
htim3.Init.Prescaler = 2400-1;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 100-1;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
这里对应了我刚刚CubeMX设置的TIM3的一些配置,比如我设置的周期、计数模式、时钟分频以及是否自动重载。
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
这里对应的是默认的那两条设置。
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
这里的意思就是 PWM模式1、占空比为0、有效电平为高电平、非快速模式。
还有个地方想说一下,当在CubeMX中设置了PB5作为TIM3_CHANNEL2 PWM输出之后,在GPIO_Init(); 函数里面找不到关于PB5的设置,我是通过搜索才搜索到配置PB5的地方。
代码如下:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_baseHandle->Instance==TIM2)
{
/* USER CODE BEGIN TIM2_MspInit 0 */
/* USER CODE END TIM2_MspInit 0 */
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
/* USER CODE BEGIN TIM2_MspInit 1 */
/* USER CODE END TIM2_MspInit 1 */
}
else if(tim_baseHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* TIM3 clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**TIM3 GPIO Configuration
PB5 ------> TIM3_CH2
*/
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_AFIO_REMAP_TIM3_PARTIAL();
/* USER CODE BEGIN TIM3_MspInit 1 */
/* USER CODE END TIM3_MspInit 1 */
}
}
在MX_TIM3_Init(void);函数中 有一句htim3.Instance = TIM3;
在上面的函数中:
else if(tim_baseHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* TIM3 clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/**TIM3 GPIO Configuration
PB5 ------> TIM3_CH2
*/
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_AFIO_REMAP_TIM3_PARTIAL();
/* USER CODE BEGIN TIM3_MspInit 1 */
/* USER CODE END TIM3_MspInit 1 */
}
正好与PB5匹配,于是在这里面找到了配置PB5的代码。
GPIO_MODE_AF_PP 这个是设置PB5的输出模式为复用推挽输出模式。
后面的就不多赘述了。
到这为止,PB5已经可以输出我想要的PWM了。为了做一个呼吸灯的效果,我又把TIM2开了一个10MS的定时器中断,在中断里面我执行了如下代码:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_6);
if(flag==0)
{
pwm++;
if(pwm>100)
{
pwm=100;
flag=1;
}
}
else
{
pwm--;
if(pwm<1)
{
pwm=0;
flag=0;
}
}
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,pwm);
}
}
这样就通过硬件PWM做出了一个呼吸灯的效果。