当前位置:网站首页>潘多拉 IOT 开发板学习(HAL 库)—— 实验9 PWM输出实验(学习笔记)

潘多拉 IOT 开发板学习(HAL 库)—— 实验9 PWM输出实验(学习笔记)

2022-07-06 03:40:00 小辉_Super

本文代码参考正点原子例程

实验功能

例程源码:(main.c)

本实验实现的功能:用 PWM 波控制电机转动,通过控制 PWM 的占空比,从而控制电机的转速。

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "pwm.h"

/********************************************************************************* ___ _ _____ _____ _ _ _____ _____ _ __ / _ \ | | |_ _|| ___|| \ | ||_ _|| ___|| | / / / /_\ \| | | | | |__ | \| | | | | |__ | |/ / | _ || | | | | __| | . ` | | | | __| | \ | | | || |_____| |_ | |___ | |\ | | | | |___ | |\ \ \_| |_/\_____/\___/ \____/ \_| \_/ \_/ \____/ \_| \_/ * ****************************************************************************** * 正点原子 Pandora STM32L475 IoT开发板 实验9 * PWM输出实验 HAL库版本 * 技术支持:www.openedv.com * 淘宝店铺:http://openedv.taobao.com * 关注微信公众平台微信号:"正点原子",免费获取STM32资料。 * 广州市星翼电子科技有限公司 * 作者:正点原子 @ALIENTEK * ******************************************************************************/

int main(void)
{
    
	u8 time = 0;
    u8 speed = 1;				//速度控制 0:加速 1:减速
    u8 dir = 1;					//正反转标志 0:电机正转 1:电机反转
    u16 pwmval = 500;			//默认值

    HAL_Init();
    SystemClock_Config();		//初始化系统时钟为80M
    delay_init(80); 			//初始化延时函数 80M系统时钟
    uart_init(115200);			//初始化串口,波特率为115200

    LED_Init();					//初始化LED

    PWM_Init(1000 - 1, 80 - 1);	//TIM2时钟频率 80M/80=1M 计数频率1M/1000=1KHZ 默认占空比为50%

    while(1)
    {
    
        if(speed)	pwmval += 5;
        else		pwmval -= 5;

        if(pwmval >= 1000) 	speed = 0;
        if(pwmval <= 500)
        {
    
            speed = 1;
            dir = dir ^ 0x01;	//速度变为最小时改变电机方向
        }

        if(dir)
        {
    
            TIM_SetTIM2Compare1(pwmval);
            TIM_SetTIM2Compare2(0);
        }
        else
        {
    
            TIM_SetTIM2Compare1(0);
            TIM_SetTIM2Compare2(pwmval);
        }

		time++;
		if( time%20 == 0 )
			LED_B_TogglePin;

        delay_ms(15);
    }
}

代码剖析

HAL_Init()

HAL_Init() 定义如下:(具体实现的功能见注释)

HAL_StatusTypeDef HAL_Init(void)
{
    
  HAL_StatusTypeDef  status = HAL_OK;

  /* 配置 Flash 预取,指令缓存,数据缓存 */
  /* 默认配置为:预存取关闭 指令缓存和数据缓存开启 */     
#if (INSTRUCTION_CACHE_ENABLE == 0) // Flash开启预存取配置,能加速CPU代码的执行
   __HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE == 0)
   __HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */ 

#if (PREFETCH_ENABLE != 0)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置 NVIC 优先级分组

  /* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
  if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) //初始化滴答定时器,时钟节拍设置为 1ms
  {
    
    status = HAL_ERROR;
  }
  else
  {
    
    /* Init the low level hardware */
    HAL_MspInit(); // 低速的外设初始化,比如 GPIO、中断等的设置(使用 STM32CubeMx 生成代码时会将低速外设初始
                   // 代码当这类函数里,其他情况下可以忽略这个函数
  }

  /* Return function status */
  return status;
}

HAL_InitTick()
滴答定时器时钟节拍初始化函数

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    
  HAL_StatusTypeDef  status = HAL_OK;

  /*Configure the SysTick to have interrupt in 1ms time basis*/
  if (HAL_SYSTICK_Config(SystemCoreClock/1000UL) != 0U) // 系统时钟/1000,中断周期为 1ms
  {
    
    status = HAL_ERROR;
  }
  else
  {
    
    /*Configure the SysTick IRQ priority */
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0); // 将滴答定时器的中断优先级设置为最高
  }

  /* Return function status */
  return status;
}

SystemClock_Config()

SystemClock_Config()函数定义如下:(具体实现的功能见注释,仅供参考)

void SystemClock_Config(void)
{
    
    HAL_StatusTypeDef	ret = HAL_OK;

    RCC_OscInitTypeDef RCC_OscInitStruct; // 定义振荡器初始化结构体变量
    RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义时钟初始化结构体变量

    __HAL_RCC_PWR_CLK_ENABLE(); // 使能电源控制时钟

    /*Initializes the CPU, AHB and APB busses clocks*/
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 将 HSE(外部高速时钟)作为时钟源
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;  // 开启 HSE
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启 PLL(锁相环)
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 将 HSE 作为 PLL 的时钟源
    RCC_OscInitStruct.PLL.PLLM = 1; // PLL-VCO 输入时钟分频系数,1 表示 2 分频(8 / 2 = 4M,本开发板外部晶振频率为 8MHz)
    RCC_OscInitStruct.PLL.PLLN = 20; // PLL-VCO 输出时钟倍频系数,4 * 20 = 80M,即输出时钟频率为 80MHz
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI 时钟的分频系数
    RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC1, RNG 和 USB 的时钟分频系数
    RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 主系统时钟的分频系数

    ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); //初始化时钟配置

    if(ret != HAL_OK)	while(1);

    /*Initializes the CPU, AHB and APB busses clocks*/
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                  | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 将所有时钟同时进行配置
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 将 PLL 作为系统时钟源
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 不分频
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 不分频
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 不分频


    ret	= HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 配置时钟初始结构体变量,
    //使用 Flash 延迟4,等待状态(延迟)的数量需要根据CPU时钟(HCLK)的频率和内部电压范围来选择,具体怎么
    //选需要参考芯片手册

    if(ret != HAL_OK)	while(1);

    /*Configure the main internal regulator output voltage*/
    ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); //内部寄存器输出电压配置
    // 下面是 HAL_PWREx_ControlVoltageScaling() 函数说明的部分内容:
    //PWR_REGULATOR_VOLTAGE_SCALE1 Regulator voltage output range 1 mode, typical output voltage
    // at 1.2 V, system frequency up to 80 MHz.

    if(ret != HAL_OK)	while(1);
}

delay_init()

滴答定时器已经在 HAL_Init() 中进行了初始化,下面这个函数实际上就是给 fac_us 赋了一个值(目前暂不涉及操作系统,其他代码暂时不去研究)。

static u32 fac_us = 0;							//us延时倍乘数
/** * @brief 初始化延迟函数,SYSTICK的时钟固定为AHB时钟 * * @param SYSCLK 系统时钟频率 * * @return void */
void delay_init(u8 SYSCLK)
{
    
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
    u32 reload;
#endif
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
    fac_us = SYSCLK;						//不论是否使用OS,fac_us都需要使用

#if SYSTEM_SUPPORT_OS //如果需要支持OS.
    reload = SYSCLK;					  //每秒钟的计数次数 单位为K
    reload *= 1000000 / delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间
    //reload为24位寄存器,最大值:16777216,在80M下,约209.7ms左右
    fac_ms = 1000 / delay_ostickspersec;		//代表OS可以延时的最少单位
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
    SysTick->LOAD = reload; 					//每1/OS_TICKS_PER_SEC秒中断一次
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}

LED_Init()

/** * @brief LED IO初始化函数 * * @param void * * @return void */
void LED_Init(void)
{
    
	/* LED-B PE9 LED-G PE8 LED-R PE7 */
    GPIO_InitTypeDef GPIO_InitStruct;

    __HAL_RCC_GPIOE_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);
}

PWM_Init()

本实验使用 TIM2 来输出 PWM,下面是 TIM2 的初始化函数。TIM2 的时钟频率为 80MHz,main() 函数中分频系数为 80 - 1,80M / 80 = 1MHz,对应的计时周期就是 1us;重载值为 1000 - 1,那么 PWM 的周期就是 1ms。

/** * @brief TIM2 PWM输出初始化函数 * * @param arr 自动重装值 * @param psc 时钟预分频数 * * @return void */
void PWM_Init(u16 arr, u16 psc)
{
    
    TIM2_Handler.Instance = TIM2;          				//定时器2
    TIM2_Handler.Init.Prescaler = psc;     				//定时器分频
    TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数模式
    TIM2_Handler.Init.Period = arr;        				//自动重装载值
    TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&TIM2_Handler);       				//初始化PWM

    TIM2_CHnHandler.OCMode = TIM_OCMODE_PWM1; 			//模式选择PWM1
    TIM2_CHnHandler.Pulse = arr / 2;       				//设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
    TIM2_CHnHandler.OCPolarity = TIM_OCPOLARITY_HIGH; 	//输出比较极性为低

    HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CHnHandler, TIM_CHANNEL_1); //配置TIM2通道1
    HAL_TIM_PWM_Start(&TIM2_Handler, TIM_CHANNEL_1); 	//开启PWM通道1


    HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CHnHandler, TIM_CHANNEL_2); //配置TIM2通道2
    HAL_TIM_PWM_Start(&TIM2_Handler, TIM_CHANNEL_2); 	//开启PWM通道2
}

HAL_TIM_Base_MspInit()

上面的 HAL_TIM_PWM_Init() 函数会调用定时器(PWM)的底层驱动初始化函数,该函数中配置了 PWM 通道对应的 GPIO。

/** * @brief 定时器底层驱动,时钟使能,引脚配置,此函数会被HAL_TIM_PWM_Init()调用 * * @param htim 定时器句柄 * * @return void */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM2_CLK_ENABLE();				//使能定时器2
    __HAL_RCC_GPIOA_CLK_ENABLE();				//开启GPIOA时钟

    GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1; //PA0.1
    GPIO_Initure.Mode = GPIO_MODE_AF_PP;  		//复用推挽输出
    GPIO_Initure.Pull = GPIO_PULLUP;        	//上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;   	//高速
    GPIO_Initure.Alternate = GPIO_AF1_TIM2;		//PA1复用为TIM2_CH1.CH2
    HAL_GPIO_Init(GPIOA, &GPIO_Initure);
}

TIM_SetTIM2Compare1()

TIM2 通道 1 占空比设置函数,直接向 CCR1 寄存器填值。

/** * @brief 设置TIM2通道1的占空比 * * @param compare 比较值 * * @return void */
void TIM_SetTIM2Compare1(u32 compare)
{
    
    TIM2->CCR1 = compare;
}

通道 2 的占空比设置原理和通道 1 相同。

/** * @brief 设置TIM2通道2的占空比 * * @param compare 比较值 * * @return void */
void TIM_SetTIM2Compare2(u32 compare)
{
    
    TIM2->CCR2 = compare;
}

HAL 库中也自带了占空比配置函数,同样是写 CCR 寄存器。

/** * @brief Set the TIM Capture Compare Register value on runtime without calling another time ConfigChannel function. * @param __HANDLE__ TIM handle. * @param __CHANNEL__ TIM Channels to be configured. * This parameter can be one of the following values: * @arg TIM_CHANNEL_1: TIM Channel 1 selected * @arg TIM_CHANNEL_2: TIM Channel 2 selected * @arg TIM_CHANNEL_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @arg TIM_CHANNEL_5: TIM Channel 5 selected * @arg TIM_CHANNEL_6: TIM Channel 6 selected * @param __COMPARE__ specifies the Capture Compare register new value. * @retval None */
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \ (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_4) ? ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)) :\ ((__CHANNEL__) == TIM_CHANNEL_5) ? ((__HANDLE__)->Instance->CCR5 = (__COMPARE__)) :\ ((__HANDLE__)->Instance->CCR6 = (__COMPARE__)))

LED 操作函数

LED 的控制函数是宏函数,分别用到了 HAL_GPIO_WritePin()HAL_GPIO_TogglePin() 两个库函数。

//RGB接口定义
#define LED_R(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_RESET))
#define LED_R_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_7) //LED_R电平翻转

#define LED_G(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET))
#define LED_G_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_8) //LED_G电平翻转

#define LED_B(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET))
#define LED_B_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_9) //LED_B电平翻转

delay_ms()

delay_ms() 里运行的是 delay_us()delay_us()通过滴答定时器实现延时。上面的 delay_init() 已经将 fac_us 设置为了 80,滴答定时器计数 80 次需要用 10-6 秒(系统时钟为 80MHz),即 1us。

/** * @brief 延时毫秒(ms)函数 * * @param nms 需要延时多少毫秒 * * @return void */
void delay_ms(u16 nms)
{
    
    u32 i;

    for(i = 0; i < nms; i++) delay_us(1000);
}

/** * @brief 延时微秒(us)函数 * * @remark nus:0~190887435(最大值即2^32/[email protected]_us=22.5) * * @param nus 需要延时多少微秒 * * @return void */
void delay_us(u32 nus)
{
    
    u32 ticks;
    u32 told, tnow, tcnt = 0;
    u32 reload = SysTick->LOAD;				//LOAD的值
    ticks = nus * fac_us; 					//需要的节拍数
    told = SysTick->VAL;        			//刚进入时的计数器值

    while(1)
    {
    
        tnow = SysTick->VAL;

        if(tnow != told)
        {
    
            if(tnow < told)tcnt += told - tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
            else tcnt += reload - tnow + told;
			
            told = tnow;
            if(tcnt >= ticks)break;			//时间超过/等于要延迟的时间,则退出.
        }
    }
}
原网站

版权声明
本文为[小辉_Super]所创,转载请带上原文链接,感谢
https://xiaohuisuper.blog.csdn.net/article/details/125612943