PWM點燈

来源:https://www.cnblogs.com/Lavender-edgar/archive/2023/07/19/17567069.html
-Advertisement-
Play Games

[TOC] # PWM脈衝寬調點燈 ## 前言 對於燈等來說有很多種方法,前面介紹了一些基礎的點燈方法,比如直接點燈,按鍵控制點燈,按鍵中斷點燈,但都是比較簡單的一些方法也很基礎,要問我有沒有什麼高級點的點燈方法,答案是有的,在這我要介紹一種高級點燈的方法就是使用PWM進行點燈。 ## 1.什麼是P ...


目錄

PWM脈衝寬調點燈

前言

對於燈等來說有很多種方法,前面介紹了一些基礎的點燈方法,比如直接點燈,按鍵控制點燈,按鍵中斷點燈,但都是比較簡單的一些方法也很基礎,要問我有沒有什麼高級點的點燈方法,答案是有的,在這我要介紹一種高級點燈的方法就是使用PWM進行點燈。

1.什麼是PWM

PWM是脈衝寬度調製,簡稱脈衝寬調。它利用微處理器的數字輸出來對模擬電路進行控制的一種非常有效的技術。

PWM一般用在測量、通訊、功率控制雨轉換,電動機控制、調光、開關電源,但是在這我們只研究點燈,點燈才是重中之重。

2.PWM的實現

PWM是基於定時器來進行實現的,但並不是所有的定時器都能實現PWM的,比如說基本定時器就沒有辦法實現PWM,所以下麵的PWM是用通用定時器和高級定時器來進行實現的。

其實PWM的實現是非常的簡單,其實和定時器一樣,定時器到0或者是到設置的最大值就會觸發中斷,PWM也是,只不過它並不是到達最大值,也不觸發中斷,它是到達你設置的那個比較值後就進行翻轉電平,例如我的PWM的自動重載值為2000,比較值為1000

img

當計數到1000的時候PWM就會進行一次電平的翻轉,比如你一開始設置的電平是高電平,那等到到比較值的時候就會變成低電平。

3.PWM實現步驟(通用定時器)

前面說了,PWM和定時器一樣,所以開啟PWM的方式前面和打開定時器的方式一樣,但是有一點不同的是PWM要使用到所對應的埠上,所以需要加上一般就是設置埠:

  • 打開定時器時鐘

  • 配置埠

  • 配置TIM定時器

  • 使能TIM外設

按照上面的步驟完成後就得開始配置PWM了,配置的方法如下:

  • 配置PWM
  • 使能預裝寄存器

這樣就可以讓PWM進行使用了,現在來一個一個的介紹打開的步驟。

3.1 打開定時器的時鐘

打開時鐘這個是必須要的內容,而在打開時鐘的時候需要註意你使用的定時器,如果你使用的是通用定時器,一般都是打開APB1的時鐘,而高級定時器一般是APB2,這個需要考慮一下。

如果我這是使用的TIM3的定時器,那麼打開的時鐘代碼如下:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

如果這裡使用TIM1,代碼如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

3.2 配置埠

配置埠的步驟和之前配置埠的步驟一致,只不過需要註意一下這個埠的模式需要使用GPIO_Mode_AF_PP復用推輓輸出的方式,因為有些埠是使用重映射到這個埠的,此時這個埠是屬於復用功能輸出,所以這裡需要使用到復用的方式。

那什麼時候需要使用到重映射呢?這裡需要知道一下,我們現在使用的PWM是PWM的輸出模式,所以需要知道開啟這個PWM定時器的輸出埠。

下麵的表說明瞭通用定時器和高級定時器通道輸出所對應的埠:

TIM2定時器 未重映射 部分重映射
TIM2_CH1 PA0 PA15
TIM2_CH2 PA1 PB3
TIM2_CH3 PA2 PB10
TIM2_CH4 PA3 PB11
TIM3定時器 未重映射 部分重映射 重映射
TIM3_CH1 PA6 PB4 PC6
TIM3_CH2 PA7 PB5 PC7
TIM3_CH3 PB0 PB0 PC8
TIM3_CH4 PB1 PB1 PC9
TIM4定時器 未重映射 重映射
TIM4_CH1 PB6 PD12
TIM4_CH2 PB7 PD13
TIM4_CH3 PB8 PD14
TIM4_CH4 PB9 PD15
TIM5定時器 未重映射 重映射
TIM5_CH4 PA3 LSI內部時鐘連至TIM5_CH4的輸入作為校準使用
TIM1定時器 未重映射 部分重映射 重映射
TIM1_ETR PA12 PA12 PE7
TIM1_CH1 PA8 PA8 PE9
TIM1_CH2 PA9 PA9 PE11
TIM1_CH3 PA10 PA10 PE13
TIM1_CH4 PA11 PA11 PE14

上面只介紹了部分定時器輸出的重映射,更多的大家可以翻閱一下手冊,上面的只是一個簡單的參考。

知道了定時器輸出引腳後我們需要選擇定時器然後配置定時器所對應的輸出引腳。

這裡我使用的是PA6這個引腳,所以使用的是TIM3這個定時器,配置的代碼如下:

GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 打開GPIOA的時鐘
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 設置復用推輓輸出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

3.3 設置定時器

設置完埠後就可以開始設置定時器了,設置定時器的方法和之前打開基本定時器的方法一樣。

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CountterMode_Up;  // 設置定時器計數模式為上拉
TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1;  // 自動重裝值為20000
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; // 預分繫數,72MHz/7200
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;   // 時鐘分割
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);

3.4 設置PWM

前面設置完TIM3定時器了,現在就是想要配置一下PWM了。

TIM_OCInitTypeDef TIM_OCInitStruct = {0};
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;   // 打開PWM通道1  因為這裡使用的是TIM3的通道一作為PWM
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_ENABLE; // PWM輸出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCNPolarity_Low;  // 一開始輸出的電平,如果為低,那到達比較值後就為高。
TIM_OC1Init(TIM3, &TIM_OCInitStruct);  // 讓TIM3的通道1和設置的PWM進行綁定
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_ENABLE);   //使能TIM3在CCR1上的預重裝寄存器
TIM_SetCompare1(TIM3, 10000 - 1);    // 設置比較值
TIM_Cmd(TIM3, ENABLE);    // 使能TIM3外設

以上就是配置好PWM了,有幾個註意點,就是選擇的PWM的通道數,比如說我這要讓它打開通道二,那在設置的時候需要加上:

TIM_OC2Init(TIM3, &TIM_OCInitStruct);  // 讓TIM3的通道1和設置的PWM進行綁定
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_ENABLE);   //使能TIM3在CCR1上的預重裝寄存器
TIM_SetCompare2(TIM3, 10000 - 1);    // 設置比較值

這樣也設置了通道2的,當然也可以讓PWM通道2的重裝值為其它的值,那就當設置完通道一後再設置一下PWM,再把設置好的PWM設置通道二。

3.5 完整代碼

下麵展示一下完整的代碼,大家可以拿下來直接使用,但是要註意這個是用TIM3,並且沒有重定向的。

void MX_PWMInit(void){
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
	TIM_OCInitTypeDef TIM_OCInitStruct = {0};
	
	// 開啟時鐘
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 配置埠
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 配置通用定時器
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	// 配置PWM
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;    // 打開通道1
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_Low;
	TIM_OC1Init(TIM3, &TIM_OCInitStruct);
	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
	TIM_SetCompare1(TIM3, 10000 - 1);
	TIM_Cmd(TIM3, ENABLE);
}

4.PWM實現步驟(高級定時器)

其實高級定時器的實現步驟和上面一樣,只不過就是想要添加一條語句,為什麼不直接在通用定時器上寫呢?我也不知道,寫這個我都是隨性的。

當你設置完PWM後,想要添加下麵的語句:

TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主輸出使能

因為這裡使用的是高級定時器TIM1,包括TIM8都需要添加這一句話,因為這一句話是讓MOE主輸出使能的,如果沒有這一句話,就算你設置好PWM,這個定時器也沒有辦法進行輸出,也就是讓TIM1或者TIM8為關閉狀態。

完整代碼如下,你可以直接拿去使用,但是要註意我這個打開的是TIM1定時器的PWM,而且是通道一,輸出的埠是PA8:

void MX_PWMInit(void){
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct = {0};
	TIM_OCInitTypeDef TIM_OCInitStruct = {0};
	
	// 開啟時鐘
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 配置GPIO模式和IO口
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// TIM1通用定時器初始化
	// 因為要操作的PA8這個引腳的LED燈,但是只有高級定時器TIM1才可以使用PWM進行控制這個埠
	// 所以需要使用打開高級定時器的方法來進行打開
	TIM_TimeBaseStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;    // 向上計數
	TIM_TimeBaseStruct.TIM_Period = 20000 - 1;
	TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);
	
	// PWM初始化
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;      // 設置模式為PWM模式
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;     // 設置比較輸出使能
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;        // 設置極性為高
	TIM_OC1Init(TIM1, &TIM_OCInitStruct);        

	TIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主輸出使能
	
	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在CCR1上的預裝載寄存器
	
	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的預裝載寄存器
    
    TIM_SetCompare1(TIM1, 10000 - 1);
	
	TIM_Cmd(TIM1, ENABLE); 
	
}

5.開始點燈

如果你上面的步驟都跟上了,那麼現在恭喜你你可以點燈了,直接調用上面的初始化函數就可以直接開啟點燈了,效果就是亮滅亮滅的。

主函數:

#include "stm32f10x.h"
#include "pwm.h"

int main(void){
	MX_PWMInit();
   	while(1)
	{	   
	} 
}

這樣就可以了,你可以用TIM_SetCompare1(TIMx, value;函數去修改一下每次的比較值,可以讓它亮得久或者亮得短。

你也可以通過修改TIM_OCInitStruct.TIM_OCPolarity的值來改變一開始的電平。

6.PWM呼吸燈

點燈很容易,但是還是感覺比較低級,那麼在這我給大家介紹一下用PWM來點燈的高級點的內容----“PWM呼吸燈”,不知道的可以看完這個教程拿去跑一下就知道了。

呼吸燈其實就是改變每一次PWM的比較值,在感官上感覺就是LED像呼吸一樣。

這裡需要添加一下滴答定時器來延遲,所以需要添加一下下麵的代碼:

void delay_us(unsigned int time){
	unsigned int temp;
	SysTick->LOAD = 9 * time;
	SysTick->CTRL = 0x01;
	SysTick->VAL = 0;
	do{
		temp = SysTick->CTRL;
	}while((temp&0x01)&&(!(temp&(1<<16))));
	SysTick->VAL = 0;
	SysTick->CTRL = 0x00;
}

void delay_ms(unsigned int time){
	unsigned int temp;
	SysTick->LOAD = 9000 * time;
	SysTick->CTRL = 0x01;
	SysTick->VAL = 0;
	do{
		temp = SysTick->CTRL;
	}while((temp&0x01)&&(!(temp&(1<<16))));
	SysTick->VAL = 0;
	SysTick->CTRL = 0x00;
}

這個是之前說過的滴答定時器配置,我就懶得寫註釋了。

然後PWM需要將分頻變成0,因為使用滴答定時器來延遲,所以不需要分頻了(你分頻會出問題,不要分),然後預裝值改為900,其它都不變。

在主函數中的代碼就如下:

int main(void){
	unsigned char dir = 1;    // 方向
	unsigned int Duty = 0;    // 占空比
	MX_PWMInit();
   	while(1)
	{ 
		delay_ms(10);
		if (dir == 1){
			Duty++;
			if (Duty > 300){
				dir = 0;
			}
		}
		else{
			Duty--;
			if (Duty == 0){
				dir = 1;
			}
		}	
		TIM_SetCompare1(TIM1, Duty);
	} 
}

上面的是你一開始的電平為:TIM_OCPolarity_Low

如果你一開始的電平為:TIM_OCPolarity_Hight,那麼需要使用下麵的代碼:

int main(void){
	unsigned char dir = 0;    // 方向
	unsigned int Duty = 0;    // 占空比
	MX_PWMInit();
   	while(1)
	{ 
		delay_ms(10);
		if (dir == 0){
			Duty++;
			if (Duty > 300){
				dir = 1;
			}
		}
		else{
			Duty--;
			if (Duty == 0){
				dir = 0;
			}
		}	
		TIM_SetCompare1(TIM1, Duty);
	} 
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在編程方面,從來都是實踐出真知,書讀百遍其義自見,所以實戰是最好的提升自己編程能力的方式。 前一段時間,寫了一些實戰系列文章,如: ASP.NET MVC開發學生信息管理系統 Vue+Antdv+Asp.net WebApi開發學生信息管理系統 WPF+Prism+MAH+Asp.net Web A ...
  • 這篇文章主要介紹了介面的概念、定義和實現,以及顯式/隱式實現介面的場景。文章還對介面和抽象類進行了比較,指出它們的區別。同時,文章提供了詳細的代碼示例,方便讀者理解和實踐。 ...
  • ## 引言 眾所周知,使用線程可以極大的提高應用程式的效率和響應性,提高用戶體驗,但是不可以無節制的使用線程,為什麼呢? ## 線程的開銷 線程的開銷實際上是非常大的,我們從空間開銷和時間開銷上分別討論。 ### 線程的空間開銷 線程的空間開銷來自這四個部分: 1. 線程內核對象(Thread Ke ...
  • 在一些文檔處理中,我們需要對PDF蓋上公司的印章操作,本篇隨筆介紹利用Spire.Pdf實現PDF添加印章的操作,如全章和騎縫章的處理。 ...
  • 模型配置可以通過Fluent API和註解的方式 FluentAPI步驟 新建Products 和Category類 新建Products類 Products public class Product { public int Id { get; set; } public string Name ...
  • ## 一:背景 ### 1. 講故事 如果要分析 Linux上的 .NET程式 CPU 爆高,按以往的個性我肯定是抓個 dump 下來做事後分析,這種分析模式雖然不重但也不輕,還需要一定的底層知識,那有沒有傻瓜式的 CPU 爆高分析方式呢? 相信有很多朋友知道 **B站713事件**,最終就是用 p ...
  • > 註:本文隸屬於《理解ASP.NET Core》系列文章,請查看置頂博客或[點擊此處查看全文目錄](https://www.cnblogs.com/xiaoxiaotank/p/15185288.html) # 概述 在微服務化的架構設計中,網關扮演著重要的看門人角色,它所提供的功能之一就是**限 ...
  • ## 引言 上文[編碼技巧 同步鎖對象的選定](url)中,提到了在C#中,讓線程同步有兩種方式: - 鎖(lock、Monitor等) - 信號量(EventWaitHandle、Semaphore、Mutex) 加鎖是最常用的線程同步的方法,就不再討論,本篇主要討論使用信號量同步線程。 ## W ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...