小編又回顧了一下野火的高階定時器的視訊,然後就想實現一個串列埠控制實現可調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波。有興趣的同學可以試試。
希望對大家的學習有幫助,嘻嘻。