STM32——通用定时器TIM3实现一路可调PWM波的输出(频率不变10KHZ,占空比可调)

    科技2022-07-10  120

    小编又回顾了一下野火的高级定时器的视频,然后就想实现一个串口控制实现可调PWM波的实现,整了两天终于搞明白咋回事了,我太菜了,下面不多说直接上实验 工程是在野火的源文件的基础上修改的,不要喷昂 工程里的User文件包括以下内容: 其中的GeneralTim文件夹包含:bsp_GeneralTim.c和bsp_GeneralTim.h Usart文件夹包含:bsp_usart.c和bsp_usart.h

    好了,直接上代码: main.c

    // TIM—通用定时器(TIM3)-1路(通道3)可调PWM输出应用 #include "stm32f10x.h" #include "bsp_GeneralTim.h" #include "bsp_usart.h" uint16_t GENERAL_TIM_Period=9;//主函数中的周期的全局变量,周期初始化给个值9(TIM内部寄存器函数输出比较寄存器值是5) uint16_t CCR_Valeur_Fix=5; //给GENERAL_TIM_Mode_Config中的形参CCR_Valeur_Fix设置一个固定值 static void Show_Message(void); /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { //定义变量,获取串口发来的数据 char ch; /* 定时器初始化 ,先给一个固定的脉冲*/ GENERAL_TIM_GPIO_Config(); GENERAL_TIM_Mode_Config(GENERAL_TIM_Period,CCR_Valeur_Fix); /*串口初始化*/ USART_Config(); Show_Message(); while(1) { ch=getchar(); switch(ch){ case '0': CCR_Valeur_Fix--; //占空比变小****** printf("\r\n 减速 \n"); break; case '1': CCR_Valeur_Fix++; //占空比变大***** printf("\r\n 加速了 \n"); break; } if(((GENERAL_TIM_Period >= CCR_Valeur_Fix)&&(CCR_Valeur_Fix>=0))==1) { //控制占空比在0-1之间 ***** GENERAL_TIM_Mode_Config(GENERAL_TIM_Period,CCR_Valeur_Fix); }else{printf("\r\n 请改变控制方式 \n");} } } static void Show_Message(void) { printf("\r\n 这是一个通过串口通信指令控制A5电平的实验 \n"); printf("使用 USART 参数为:%d 8-N-1 \n",DEBUG_USART_BAUDRATE); printf("开发板接到指令后A5电平,指令对应如下:\n"); printf(" 0 ------ 加速 \n"); printf(" 1 ------ 减速 \n"); } /*********************************************END OF FILE**********************/

    总结:(周期常量GENERAL_TIM_Period)和CRR(占空比:输出比较寄存器CCR_Valeur_Fix)的大小比较,因为占空比是不会大于1的,所以加了f语句,if语句用于限制占空比在0-1的范围内,实质上就是限制ARR始终大于或等于CCR并且CCR大于或等于0控制操作才有效。

    bsp_usart.h

    #ifndef __USART_H #define __USART_H #include "stm32f10x.h" #include <stdio.h> /** * 串口宏定义,不同的串口挂载的总线不一样,移植时需要修改这几个宏 */ #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200 // USART GPIO 引脚宏定义 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 #define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler void USART_Config(void); void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch); void Usart_SendString( USART_TypeDef * pUSARTx, char *str); void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch); #endif /* __USART_H */

    bsp_usart.c

    #include "bsp_usart.h" /** * @brief USART GPIO 配置,工作参数配置 * @param 无 * @retval 无 */ void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 打开串口GPIO的时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE); // 打开串口外设的时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); // 配置串口的工作参数 // 配置波特率 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置 针数据字长 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置停止位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置校验位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置硬件流控制 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置工作模式,收发一起 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 完成串口的初始化配置 USART_Init(DEBUG_USARTx, &USART_InitStructure); // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); } /***************** 发送一个字符 **********************/ void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch) { /* 发送一个字节数据到USART */ USART_SendData(pUSARTx,ch); /* 等待发送数据寄存器为空 */ while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } /***************** 发送字符串 **********************/ void Usart_SendString( USART_TypeDef * pUSARTx, char *str) { unsigned int k=0; do { Usart_SendByte( pUSARTx, *(str + k) ); k++; } while(*(str + k)!='\0'); /* 等待发送完成 */ while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET) {} } /***************** 发送一个16位数 **********************/ void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch) { uint8_t temp_h, temp_l; /* 取出高八位 */ temp_h = (ch&0XFF00)>>8; /* 取出低八位 */ temp_l = ch&0XFF; /* 发送高八位 */ USART_SendData(pUSARTx,temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); /* 发送低八位 */ USART_SendData(pUSARTx,temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } ///重定向c库函数printf到串口,重定向后可使用printf函数 int fputc(int ch, FILE *f) { /* 发送一个字节数据到串口 */ USART_SendData(DEBUG_USARTx, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); return (ch); } ///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 int fgetc(FILE *f) { /* 等待串口输入数据 */ while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(DEBUG_USARTx); }

    整个bsp_usart.c和bsp_usart.h文件我是直接移植野火的程序来的,初始化了usart1串口,因为没有涉及中断服务程序,所以.c文件里也没有移植什么优先级配置函数

    bsp_GeneralTim.h

    #ifndef __BSP_GENERALTIME_H #define __BSP_GENERALTIME_H #include "stm32f10x.h" /************通用定时器TIM参数定义,只限TIM2、3、4、5************/ // 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意 // 我们这里默认使用TIM3 #define GENERAL_TIM TIM3 #define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd #define GENERAL_TIM_CLK RCC_APB1Periph_TIM3 #define GENERAL_TIM_Prescaler 720 //72M/720=10KHz频率 // TIM3 输出比较通道3(B0) #define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB #define GENERAL_TIM_CH3_PORT GPIOB #define GENERAL_TIM_CH3_PIN GPIO_Pin_0 /**************************函数声明********************************/ //下面的这两个函数即可实现通用定时器TIM3的通道3的PWM波可调 //初始化TIM3的GPIO外设 void GENERAL_TIM_GPIO_Config(void); //TIM3通用定时器的内部寄存器设置(这个参数很重要,通过ARR周期不变,改变CCR高电平时间的方式实现PWM波的可调) void GENERAL_TIM_Mode_Config(uint16_t GENERAL_TIM_Period,uint16_t CCR_Valeur_Fix); #endif /* __BSP_GENERALTIME_H */

    bsp_GeneralTim.c

    #include "bsp_GeneralTim.h" void GENERAL_TIM_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 输出比较通道3 GPIO 初始化 RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH3_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure); } ///* // * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有 // * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可, // * 另外三个成员是通用定时器和高级定时器才有. // *----------------------------------------------------------------------------- // *typedef struct // *{ TIM_Prescaler 都有 // * TIM_CounterMode TIMx,x[6,7]没有,其他都有 // * TIM_Period 都有 // * TIM_ClockDivision TIMx,x[6,7]没有,其他都有 // * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有 // *}TIM_TimeBaseInitTypeDef; // *----------------------------------------------------------------------------- // */ /* ---------------- PWM信号 周期和占空比的计算--------------- */ // ARR :自动重装载寄存器的值 // CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1) // PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M // 占空比P=CCR/(ARR+1) void GENERAL_TIM_Mode_Config(uint16_t GENERAL_TIM_Period,uint16_t CCR_Valeur_Fix) { // 开启定时器时钟,即内部时钟CK_INT=72M GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE); /*--------------------时基结构体初始化-------------------------*/ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断 TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period; // 驱动CNT计数器的时钟 = Fck_int/(psc+1) TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler; // 时钟分频因子 ,配置死区时间时需要用到 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 计数器计数模式,设置为向上计数 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 重复计数器的值,没用到不用管 TIM_TimeBaseStructure.TIM_RepetitionCounter=0; // 初始化定时器 TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure); /*--------------------输出比较结构体初始化-------------------*/ // CCR占空比配置,通过参数配置一个固定值 uint16_t CCR3_Val = CCR_Valeur_Fix; TIM_OCInitTypeDef TIM_OCInitStructure; // 配置为PWM模式1 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 输出使能 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出通道电平极性配置 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出比较通道 3 TIM_OCInitStructure.TIM_Pulse = CCR3_Val; TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure); TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable); // 使能计数器 TIM_Cmd(GENERAL_TIM, ENABLE); } /*********************************************END OF FILE**********************/

    bsp_GeneralTim.c和bsp_GeneralTim.h文件我是在野火的通用寄存器输出四路不同PWM波的原码上修改的;要修改的步骤如下: 1、bsp_GeneralTim.h文件中仅保留“TIM3 输出比较通道3”的通道宏定义,并删除了周期的宏定义GENERAL_TIM_Period 2、bsp_GeneralTim.c文件中将原本的无参数函数static void GENERAL_TIM_Mode_Config(void),改变为有参数函数void GENERAL_TIM_Mode_Config(uint16_t GENERAL_TIM_Period,uint16_t CCR_Valeur_Fix),前一个参数定周期长度,后一个参数定高电平长度。其次不使用void GENERAL_TIM_Init(void);因为要实现PWM的可调的话,对应GPIO和定时器的初始化必须分开。通过在主函数whlie不断循环查询串口发来的信号并利用switch判断调整变量uint16_t CCR_Valeur_Fix的值,再调用void GENERAL_TIM_Mode_Config(uint16_t GENERAL_TIM_Period,uint16_t CCR_Valeur_Fix)使得引脚的PWM输出发生改变。

    实验图 1、程序刚下载后,PWM输出的情况

    2、串口发送几次‘0’信号的情况 3、串口发送几次‘1’的情况 4、CCR的值小于0或CCR好ARR的比值大于1的情况

    最后敲黑板

    记住这几个寄存器很重要 计数器 CNT 自动重载寄存器 ARR CCR输出比较寄存器 *****CNT从 0开始计数,当 CNT<CCR的 值时,OCxREF 为有效的高电平,于此同时,比较中断寄存器 CCxIF 置位。当 CCR=<CNT<=ARR 时,OCxREF 为无效的低电平。然后 CNT 又从 0 开始计数并生成计数 器上溢事件,以此循环往复。

    重点:这个实验的重点在于bsp_GeneralTim.c中的

    void GENERAL_TIM_Mode_Config(uint16_t GENERAL_TIM_Period,uint16_t CCR_Valeur_Fix){} 函数;在这个实验中我们让参数uint16_t GENERAL_TIM_Period不变,在main.c中给了个常量,将参数uint16_t CCR_Valeur_Fix作为变量,这样我们就可以得到一个频率不变(本实验为10KHz),而占空比改变的PWM波。

    如果我们使得uint16_t CCR_Valeur_Fix作为常量,而uint16_t GENERAL_TIM_Period作为变量,则可以实现得到一个频率变化的可调PWM波。有兴趣的同学可以试试。

    希望对大家的学习有帮助,嘻嘻。

    Processed: 0.018, SQL: 8