本文由網友投稿。
作者:陳顯達
原文標題:【單片機入門】(四)應用層軟體開發的單片機學習之路-----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 系列單片機,歡迎大家加入討論,學習。
