本文由網友投稿。
作者:陳顯達
原文標題:【單晶片入門】(四)應用層軟體開發的單晶片學習之路-----ESP32 開發板 PWM 控制馬達以及中斷的使用
原文連結:https://www.cnblogs.com/1996-Chinese-Chen/p/16846218.html
引言
各位大佬,晚上好啊,在上一篇部落格中,我們講了什麼是 UART 串列通訊,以及使用 USB 轉 TTL 使得單晶片可以和 C# 上位機做一個串列通訊,接下來,為大家帶來 PWM 的概念原理,以及實際案例,使用 PWM 對馬達進行速度調製,因為本課程的最後是做一個紅外線遙控的智慧小車,所以是需要馬達四個,驅動四個,輪胎四個,所以 PWM 對於最後的成果也是極為重要,並且在實際開發中,PWM 也是比較常用的調速方式。
概念
PWM 全稱 Pulse width modulation,中文翻譯為脈波寬度調變,其基本原理為控制方式就是對逆變電路開關元件的通斷進行控制,使輸出端得到一系列幅值相等但寬度不一致的脈波,用這些脈波來代替正弦波或所需要的波形。也就是在輸出波形的半個週期中產生多個脈波,使各脈波的等值電壓為正弦波形,所獲得的輸出平滑且低次諧波少。按一定的規則對各脈波的寬度進行調製,既可改變逆變電路輸出電壓的大小,也可改變輸出頻率。
可能上面對於原理的解釋過於官方,大家可能看不懂,通俗易懂的來說,就是透過對電子元件的電路進行高低電位進行控制,在一段時間內,高低電位在輸出會形成一段波動,這個波動可以稱為 PWM 波形,而我們需要使用程式碼去控制 PWM 的輸出波形,高電位在這一段波動中,通電時間即高電位時間是佔了總時間多少,同時在這一段 PWM 波形中,高低電位來回切換的頻率又是多少,形成了這麼一段波形,這就引入了兩個概念,工作週期 (Duty Ratio) 和頻率,工作週期代表著,高電位通電總時和總時的一個佔比 (這段波形中,高低電位的總共佔用時間),而頻率則是高低電位在這段波形中,來回切換的一個頻率。
如下圖,下方在 Arduino 序列埠繪圖器中,展示了一段鋸齒波形,看下方的 GIF 我們可以看到對應的馬達運動也是有快到慢的一個運動狀態。

程式碼解析
void setup() {
Serial.begin(9600);
ledcSetup(0, 5000, 8);
ledcAttachPin(12, 0);
}
// the loop function runs over and over again forever
void loop() {
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(0, dutyCycle);
delay(7);
Serial.println(dutyCycle);
}
}
在 Arduino 中我們可以使用 LEDC 來實現對 PWM 的控制,而在純 C 樂鑫的開發板中,是可以使用 MCPWM 進行控制,但是由於 Arduino 在此處不能使用 MCPWM,則就有了 LEDC 作為替代品,ESP32 帶有一個 16 通道的一個 LED PWM 控制器,對應使用的是樂鑫的 LED PWM 控制,ESP32 LED PWM,分為 8 路高速通道和 8 路低速通道,然後我們使用不同的頻率,和工作週期來實現控制馬達轉速的控制。
在上面的程式碼中,我們先設定了 ledc 的通道為 0,頻率為 5000,第八個低速 LED 控制器,即程式碼為 ledcSetup(0, 5000, 8);然後需要將通道和引腳進行管理使用 ledcAttachPin(12, 0);將引腳 12 和第 0 個通道關聯起來,在 loop 程式碼中,可以看到,我們寫入的最大工作週期為 255,而 0-255 總數為 256,那是因為,工作週期是和通道有關係的,上文提到,LED 的 PWM 控制器一共有 16 個,此處我們使用 8,而 256 則為 2 的 8 次方的值,所以工作週期最大為 256,如果取值為 10,工作週期的最大值則為 1024-1;ledcwrite(0,dutyCycle);則是將工作週期寫入對應的通道,便完成了 PWM 對馬達進行調速設定。

Arduino 針對 ESP32 樂鑫 PWM 的封裝,目前已知的有 LEDC,不需要安裝,預設就可以使用,而其他的也有對於 PWM 的封裝,個人測試了一兩個倒也沒有這個好用,後續各位朋友也可以繼續探索其他好用的 PWM 庫進行開發。
中斷
在上面講完 PWM 之後,我們再來講一下中斷,以及中斷的一個實際案例。中斷,顧名思義,是在程式執行期間,遇到某一個事件的時候,將暫停手頭上的工作先去執行某一件事情,這個事情則是我們中斷當下工作,去執行的事情,這個動作,稱之為中斷。雖然在程式碼中,可以註冊一個背景任務(在純 C 中),進行不停的 while,但是這樣在效能上還是無法發揮單晶片的功效,所以這種場景下我們便需要使用中斷,來實現我們某種功能,例如使用按鈕,來判斷是否需要打開 LED,或者是其他的行為。
在 Arduino 中,我們可以使用 attachInterrupt 函式來進行對引腳增加中斷以及使用 detachInterrupt 來移除中斷,
attachInterrupt 函式需要三個參數,第一個為中斷需要使用的引腳 pin,第二個為中斷觸發的函式,第三個為中斷的類型,對於 ESP32 的中斷,在 Arduino 中,其方法名前面必須加一個 IRAM_ATTR 標記其為中斷函式,第一個函式中的 digitalPinToInterrupt 為將 27 和中斷進行一個綁定,同時還有其他方法,但是官方均不推薦,
在下方的程式碼中,我們定義了一個 change 的函式用來處理 ESP32 27 引腳的中斷,用 27 引腳的電位控制 LED 引腳 2 的電位,以此來控制是否點亮 LED 燈,先設定引腳 2 為輸出模式,27 引腳為上拉輸入模式,可以理解為上拉電阻的一般都需要用到這種模式,然後我們將引腳 27 和中斷進行關聯,設定中斷函式為 change,模式為 CHANGE。然後在 LOOP 函式中,我們給引腳 2 寫入 state 的值,當進入 change 中斷函式中,會將 state 取反,然後進入 loop 寫入值。以此實現控制 LED 的顯示和不顯示,在這裡,提醒一下,由於在單晶片中,中斷以及計時器都是非阻塞模式,而 Serial.println 函式是阻塞寫入緩衝區,會導致中斷函式會不斷的輸出錯誤,錯誤:Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).
這是因為 println 函式阻塞導致計時器無法繼續執行,所以如果非要使用此函式,可以嘗試設定中間變數,然後在 loop 函式中判斷是否改變值,然後進行輸出資訊到序列埠。
可以在下方 GIF 看到,我們使用按鈕進行控制 LED 的顯示和不顯示。
volatile byte state = LOW;
void IRAM_ATTR change()
{
state=!state;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(2, OUTPUT);
pinMode(27, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(27), change, CHANGE);
}
void loop() {
digitalWrite(2, state);// put your main code here, to run repeatedly:
}
可以看到第二個方法是傳入一個 interrupt 的中斷編號,但是 ESP32 上面的中斷編號,不在官方資料中,所以我們只有需要第一個方法來進行引腳和中斷函式的關聯,當然了可能最後一個也是可以,只是此處我沒有嘗試,感興趣的可以進行嘗試,
在 mode 中,Arduino 是支援五種模式,第一種為 LOW,,看翻譯我們知道,這個是在電位處於低電位時會觸發中斷函式,
第二個 CHANGE 是不管是高到低,還是低到高,都會觸發
第三種是引腳在由低電位到高電位時觸發,而不是已經到了高電位觸發,
第四種是下降,當電位由高到低時會觸發中斷函式,
第五種是電位處於高電位時會觸發中斷函式。


結語
今天講了 PWM 還有中斷的使用,可能一次性講的有點多,有點難以消化,有什麼不懂的可以及時問我,以及後面我更新的時間會稍微慢一點,防止講的過快,一時不明白,後面還會有對於 IIC,SPI 的一個案例講解,在這些講完後,我會開始準備最終極的目標,做一個智慧小車,其中會需要的配件,這兩天我會總結好發到群裡,以及購買連結。有感興趣的同學可以加 QQ 群,一起學習,一起討論,博主也是一個剛開始玩單晶片的學徒,後面也會研究 stm32 系列單晶片,歡迎大家加入討論,學習。
