STM32——通用定時器TIM3實現一路可調PWM波的輸出(頻率不變10KHZ,佔空比可調)

2020-10-05 12:00:24

小編又回顧了一下野火的高階定時器的視訊,然後就想實現一個串列埠控制實現可調PWM波的實現,整了兩天終於搞明白咋回事了,我太菜了,下面不多說直接上實驗
工程是在野火的原始檔的基礎上修改的,不要噴昂
工程裡的User檔案包括以下內容:
圖1
其中的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輸出的情況

圖1
2、串列埠傳送幾次‘0’訊號的情況
圖23、串列埠傳送幾次‘1’的情況
圖3
4、CCR的值小於0或CCR好ARR的比值大於1的情況
圖4
圖5

最後敲黑板

記住這幾個暫存器很重要
計數器 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波。有興趣的同學可以試試。

希望對大家的學習有幫助,嘻嘻。