[應用] 在 ESP32 Audio 開發板的 VoIP 範例

開發板介紹

        安信可公司(Ai-Thinker)基於 ESP32 晶片推出一塊具有語音功能的 ESP32 Audio Kit 開發板,簡稱 ESP32-A1S,它與原廠的 ESP32 Lyrat 開發板都使用同一套 ESP-ADF SDK 軟體,但是開發板的電路有些差異,底層的驅動程式需要替換才能順利開發。

        首先,最大的差異在 ESP32-A1S 語音編碼晶片採用 AC101,不過晶片被封裝在 ESP32 模組上,所以我們在開發板上面看不到這顆晶片,然而 ESP32 Lyrat 板採用 ES8388 晶片,因此在 ESP-ADF 中語音編碼的驅動程式需要更替。另外,其他不一樣的地方都在 GPIO 接腳的連接上,整理後,如下表所示,我們需要調整 ADF 底層的 HAL 和 Board 的配置,SDK 才能正常使用。

表一:ESP32-Lyrat 與 Audio Kit 開發板的差異

        第一步,下載 ESP-ADF。在 VS Code 開發環境下[1],按 F1 後,選取 "Install ESP-ADF",VS Code 會自動從 GitHub 下載,並安裝 ADF 開發套件。如果是使用 ESP32-Lyrat 板子,可以直接跳到第四步,創建範例開始開發了。使用 ESP32-A1S 板子前,需要調整一下底層代碼,接續前面動作,我們從 GitHub 下載 A1S 的驅動程式,ESP32-A1S-AudioKit 程式碼,其目錄結構與 ADF 一樣,方便我們相互比對兩者差異。

        第二步,到 \esp-adf\components\audio_hal\driver 目錄下,將下載 A1S 裡的 AC101 驅動程式放進來。接著,要將 ac101 目錄和 ac101.c 加到 CMakeLists.txt 文件,同樣也要加到 component.mk 文件裡,這樣編譯過程就會把 AC101 驅動程式加進來編譯了。

        第三步,我們再到 \esp-adf\components\audio_board\lyrat_v4_3 目錄下(因為我們沒想要在 menuconfig 裡新增 A1S 配置,打算借用 lyrat 原配置),將 board.c、board.h、board_def.h、board_pins_config.c 更換成 A1S 的,其中 board_def.h 裡面是 GPIO 腳位的對應,請比對上面圖表的對應,修改成正確的 IO 編號。在 INPUT_KEY 結構中定義按鈕的型態,分 BUTTON TOUCH,我們在未來使用一些範例時要改為 BUTTON (對於 A1S 而言),因為可能之後使用 VoIP 範例時會遇上問題。 

        第四步,我們要驗證上面更換的動作無誤,從 ADF 裡創建一個範例來驗證驅動程式能運作。按 F1 後,選取 "Show Examples Projects",找 play_mp3_control 這個範例來試試看。編譯並燒錄後,我們從耳機孔能聽到音樂的話,代表驅動程式更換成功。開發板上的 Key2~Key6 按鈕對應的功能分別為 Mode、Set、Play、Vol+、Vol-,我們也可以檢驗按鈕功能是否正常,代表 GPIO 腳位對應正確了。

範例 VoIP

        在 ESP-ADF 提供了 VoIP 範例程式碼,按 F1 後,選取 "Show Examples Projects",找到 VoIP 這個範例後並創建目錄。VS Code 載入這個範例後,我們先打開 menuconfig 配置,如下圖所示,設定欲連線 WiFi AP 的名稱與密碼,Audio_HAL 選定 "Lyrate 4.3",前段我們說過借用這個配置, 底層的驅動程式已經被更換了。

        接下來,SIP URI 設定開發板的 SIP 帳號將會註冊到哪一個服務器上,註冊成哪個分機與註冊的密碼。這裡的SIP URI 格式為  transport://user:password@serverIP:port,transport 指的是 SIP 連線方式是 UDP 或 TCP,user:password 指的是註冊的分機號和密碼,serverIP 指的是欲註冊的服務器 IP 位址,port 指的是服務器上監聽 SIP 的端口。我在這邊設定 udp://1002:1234@192.168.1.205:5060,代表 SIP 通訊協定採用 UDP 連線方式,監聽端口為 5060,註冊到服務器 192.168.1.205 上面的分機 1002,註冊密碼為 1234。

圖一:menuconfig 配置

        當我們設定 menuconfig 配置後,可以首次編譯,看看是否成功。如果成功,燒錄 .bin 到板子上。另外,第一次載入 VoIP 這個範例時,還要燒錄一個音檔(audio-esp.bin)到板子上,這個音檔的內容主要是鈴聲、服務器連接的提示音....等,VoIP 應用程式會撥放這個音檔裡的內容,至於燒錄的指令,請參考 README.md。

        在 VoIP 這個應用,開發板上的 Key1~Key6 按鈕對應的功能分別為 MuteMode、Set、Play、Vol+ 和 Vol-,不過 Mute 按鍵的功能似乎在 A1S 板並不成功。Mode 鍵是掛斷或取消,Play 鍵是接聽或呼叫,Vol+ 和 Vol- 就是音量鍵。圖二說明 Play 按鍵可當接聽或撥號,目前程式碼設定的撥號分機號為 101,這個部分可以自行修改調整。

圖二:Play 按鍵的功能代碼

        最後,我們需要準備一個 SIP PBX 服務器和另一個手機 SIP app,這樣才能完成 VoIP 的系統測試。服務器可以選擇安裝 FreeSwitch 或 Asterisk (這裡我安裝的是 FreeSwitch 1.8.7),在手機安裝一個 Linphone APP,並註冊到服務器上。從 Linphone 撥打 1002,ESP-A1S 板子會聽到鈴聲,按下 Key4 (Play) 接聽,VoIP 通訊雙向就連通了。



參考資料

留言

gwansyan說…
SIP PBX 服務器 是安裝在筆電嗎?
gwansyan說…
編繹後產生的錯,請指導

implicit declaration of function 'INPUT_KEY_DEFAULT_INFO'; did you mean 'ETH_PHY_DEFAULT_CONFIG'? [-Werror=implicit-function-declaration]

'INPUT_KEY_NUM' undeclared (first use in this function)

passing argument 3 of 'input_key_service_add_key' makes integer from pointer without a cast [-Wint-conversion]
漢亞科技說…
It's Key_Pad ..... 'INPUT_KEY_NUM' is defined in "board_def.h" under \esp\esp-adf\components\audio_board\
漢亞科技說…
SIP PBX 服務器 是安裝在筆電嗎? Yes, softswitch..... such as FreeSwitch.
匿名說…
in vscode monitoring show
++++++++++++++++++++++++++++++++++++++++++++++++++
REGISTER sip:604@192.168.43.202:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.43.96:15849;branch=z9hG4bK-773438890;rport
From: ;tag=-1505150429
To:
Contact:
Max-Forwards: 70
Call-ID: 696A5897BE7245B4A0080AB89470A451884FF94558ED
CSeq: 7 REGISTER
Expires: 3600
User-Agent: ESP32 SIP/2.0
Content-Length: 0
Allow: INVITE, ACK, CANCEL, BYE, UPDATE, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE
Supported: replaces, norefersub, extended-refer, timer, X-cisco-serviceuri
Allow-Events: presence, kpml


I (43977) SIP: [1970-01-01/00:00:21]=======================>>
I (43997) SIP: [1970-01-01/00:00:21]<<=====READ 0591 bytes==
I (43997) SIP:

SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.43.96:15849;branch=z9hG4bK-773438890;rport=15849
From: ;tag=-1505150429
To: ;tag=9Kr5SpvFUt6Zp
Call-ID: 696A5897BE7245B4A0080AB89470A451884FF94558ED
CSeq: 7 REGISTER
User-Agent: FreeSWITCH
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, path, replaces
WWW-Authenticate: Digest realm="192.168.43.202", nonce="8cbf35bf-61b8-4557-a086-dd49f2926a1e", algorithm=MD5, qop="auth"
Content-Length: 0


I (44047) SIP: [1970-01-01/00:00:21]<<======================
I (44047) SIP: Required authentication
I (44057) SIP: [1970-01-01/00:00:21]=======WRITE 0836 bytes>>
I (44057) SIP:

REGISTER sip:604@192.168.43.202:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.43.96:15849;branch=z9hG4bK-216679480;rport
From: ;tag=-747012202
To:
Contact:
Max-Forwards: 70
Call-ID: 696A5897BE7245B4A0080AB89470A451884FF94558ED
CSeq: 8 REGISTER
Expires: 3600
User-Agent: ESP32 SIP/2.0
Content-Length: 0
Allow: INVITE, ACK, CANCEL, BYE, UPDATE, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE
Supported: replaces, norefersub, extended-refer, timer, X-cisco-serviceuri
Allow-Events: presence, kpml
Authorization: Digest username="604", realm="192.168.43.202", nonce="8cbf35bf-61b8-4557-a086-dd49f2926a1e", uri="sip:192.168.43.202:5060", response="5bd697163c9f6d63490021e2c0a37280", algorithm=MD5, nc=00000001, cnonce="72229f753174d8a3", qop="auth"


I (44137) SIP: [1970-01-01/00:00:21]=======================>>
I (44197) SIP: [1970-01-01/00:00:21]<<=====READ 0465 bytes==
I (44197) SIP:

SIP/2.0 403 Forbidden
Via: SIP/2.0/UDP 192.168.43.96:15849;branch=z9hG4bK-216679480;rport=15849
From: ;tag=-747012202
To: ;tag=aXHyUHDKr3vjj
Call-ID: 696A5897BE7245B4A0080AB89470A451884FF94558ED
CSeq: 8 REGISTER
User-Agent: FreeSWITCH
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, path, replaces
Content-Length: 0


I (44237) SIP: [1970-01-01/00:00:21]<<======================
E (44247) SIP: Error register device
W (44247) SIP: CHANGE STATE FROM 1, TO 0, :func: sip_register:1682
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

in freswitch :
sofia_reg.c:1806 SIP auth failure (REGISTER) on sofia profile 'internal' for [604@192.168.43.202] from ip 192.168.43.96
漢亞科技說…
Authorization: Digest username="604" <---- did you create ext 604 in freeswitch?
In freeswitch, default ext 1000 ~ 1009, password is 1234. You study freeswitch config in vars.xml, take a look at the "directory" folder.....
匿名說…
yes, registering 604,
other using softphone 600 and 601 work weel using microsip and zoiper,
or problem in ESP-adf voip example?
匿名說…
hi bro, how to solved! i install vscode after select esp-idf 3.3 get it :
[Error: non zero exit code 2 usage: idf.py [-h] [-p PORT] [-b BAUD] [-C PROJECT_DIR] [-B BUILD_DIR] [-G {Ninja,MinGW Makefiles}] [-n] [-v] [-D DEFINE_CACHE_ENTRY [DEFINE_CACHE_ENTRY ...]] [--no-ccache] {all,build,clean,fullclean,reconfigure,menuconfig,defconfig,confserver,size,size-components,size-files,bootloader,bootloader-clean,bootloader-flash,app,app-flash,efuse_common_table,efuse_custom_table,show_efuse_table,partition_table,partition_table-flash,flash,erase_flash,monitor,erase_otadata,read_otadata} [{all,build,clean,fullclean,reconfigure,menuconfig,defconfig,confserver,size,size-components,size-files,bootloader,bootloader-clean,bootloader-flash,app,app-flash,efuse_common_table,efuse_custom_table,show_efuse_table,partition_table,partition_table-flash,flash,erase_flash,monitor,erase_otadata,read_otadata} ...] idf.py: error: argument actions: invalid choice: 'set-target' (choose from 'all', 'build', 'clean', 'fullclean...
漢亞科技說…
Sorry, I have no idea. You may contact ESP-idf/adf support.
匿名說…
Work well now. but just microphone not work.
i am using esp32 v2.3 (e8388 audio chip)
我用 ESP_ADF 的 voip 例子烧掉了它。 SIP连接可以拨打电话,拨打电话时耳机有声音。 但 ESP 板上的麦克风不工作。

whats is wrong? mic not work but headphone sounding voice
gwansyan說…
編繹後產生的錯,請指導

implicit declaration of function 'INPUT_KEY_DEFAULT_INFO'; did you mean 'ETH_PHY_DEFAULT_CONFIG'? [-Werror=implicit-function-declaration]

'INPUT_KEY_NUM' undeclared (first use in this function)

passing argument 3 of 'input_key_service_add_key' makes integer from pointer without a cast [-Wint-conversion]


------------
在下面資料夾,按鍵定義如下,但編譯結果還是如上所述。



D:\Espressif\.espressif\esp-adf\components\audio_board\lyrat_v4_3

/*
* @Author: your name
* @Date: 2020-01-26 10:13:06
* @LastEditTime : 2020-01-28 19:24:42
* @LastEditors : Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \esp-adf\components\audio_board\aithinker\board_def.h
*/
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2019
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

#ifndef _AUDIO_BOARD_DEFINITION_H_
#define _AUDIO_BOARD_DEFINITION_H_

/* SD card related */
#define SD_CARD_INTR_GPIO GPIO_NUM_34
#define SD_CARD_INTR_SEL GPIO_SEL_34
#define SD_CARD_OPEN_FILE_NUM_MAX 5

#define HEADPHONE_DETECT GPIO_NUM_5
#define PA_ENABLE_GPIO GPIO_NUM_21

#define GREEN_LED_GPIO GPIO_NUM_22
#define BLUE_LED_GPIO GPIO_NUM_19

#define BUTTON_REC_ID GPIO_NUM_36
#define BUTTON_MODE_ID GPIO_NUM_13

/* Touch pad related */
#define TOUCH_SEL_SET GPIO_SEL_19
#define TOUCH_SEL_PLAY GPIO_SEL_23
#define TOUCH_SEL_VOLUP GPIO_SEL_18
#define TOUCH_SEL_VOLDWN GPIO_SEL_5

#define TOUCH_SET GPIO_NUM_19
#define TOUCH_PLAY GPIO_NUM_23
#define TOUCH_VOLUP GPIO_NUM_18
#define TOUCH_VOLDWN GPIO_NUM_5

extern audio_hal_func_t AUDIO_CODEC_AC101_CODEC_HANDLE;

#define AUDIO_CODEC_DEFAULT_CONFIG() { \
.adc_input = AUDIO_HAL_ADC_INPUT_LINE1, \
.dac_output = AUDIO_HAL_DAC_OUTPUT_ALL, \
.codec_mode = AUDIO_HAL_CODEC_MODE_BOTH, \
.i2s_iface = { \
.mode = AUDIO_HAL_MODE_SLAVE, \
.fmt = AUDIO_HAL_I2S_NORMAL, \
.samples = AUDIO_HAL_48K_SAMPLES, \
.bits = AUDIO_HAL_BIT_LENGTH_16BITS, \
}, \
};

#endif
漢亞科技說…
將原本在 lyrat_v4_3 board_def.h 的定義搬到 aithinker 裡面,先讓編譯能夠通過。

#define INPUT_KEY_NUM 6
#define INPUT_KEY_DEFAULT_INFO() { \
{ \
.type = PERIPH_ID_BUTTON, \
.user_id = INPUT_KEY_USER_ID_REC, \
.act_id = BUTTON_REC_ID, \
}, \
gwansyan說…
將原本在 lyrat_v4_3 board_def.h 的定義搬到 aithinker 裡面,先讓編譯能夠通過。

#define INPUT_KEY_NUM 6
#define INPUT_KEY_DEFAULT_INFO() { \
{ \
.type = PERIPH_ID_BUTTON, \
.user_id = INPUT_KEY_USER_ID_REC, \
.act_id = BUTTON_REC_ID, \
}, \

編繹後產生的錯,請指導

../main/voip_app.c: In function 'app_main':
../main/voip_app.c:413:58: error: 'fINPUT_KEY_NUM' undeclared (first use in this function); did you mean 'INPUT_KEY_NUM'?
input_key_service_add_key(input_ser, input_key_info, fINPUT_KEY_NUM);
^~~~~~~~~~~~~~
INPUT_KEY_NUM
../main/voip_app.c:413:58: note: each undeclared identifier is reported only once
for each function it appears in
ninja: build stopped: subcommand failed.
終端機處理序 "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command ninja " 已終止。結束代碼: 1。

在下面資料夾,按鍵定義如下。

D:\Espressif\.espressif\esp-adf\components\audio_board\lyrat_v4_3

* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

#ifndef _AUDIO_BOARD_DEFINITION_H_
#define _AUDIO_BOARD_DEFINITION_H_

/* SD card related */
#define SD_CARD_INTR_GPIO GPIO_NUM_34
#define SD_CARD_INTR_SEL GPIO_SEL_34
#define SD_CARD_OPEN_FILE_NUM_MAX 5

#define HEADPHONE_DETECT GPIO_NUM_5
#define PA_ENABLE_GPIO GPIO_NUM_21

#define GREEN_LED_GPIO GPIO_NUM_22
#define BLUE_LED_GPIO GPIO_NUM_19

#define BUTTON_REC_ID GPIO_NUM_36
#define BUTTON_MODE_ID GPIO_NUM_13

/* Touch pad related */
#define TOUCH_SEL_SET GPIO_SEL_19
#define TOUCH_SEL_PLAY GPIO_SEL_23
#define TOUCH_SEL_VOLUP GPIO_SEL_18
#define TOUCH_SEL_VOLDWN GPIO_SEL_5

#define TOUCH_SET GPIO_NUM_19
#define TOUCH_PLAY GPIO_NUM_23
#define TOUCH_VOLUP GPIO_NUM_18
#define TOUCH_VOLDWN GPIO_NUM_5

#define INPUT_KEY_NUM 6
#define INPUT_KEY_DEFAULT_INFO() { \
{ \
.type = PERIPH_ID_BUTTON, \
.user_id = INPUT_KEY_USER_ID_REC, \
.act_id = BUTTON_REC_ID, \
}, \
};
extern audio_hal_func_t AUDIO_CODEC_AC101_CODEC_HANDLE;

#define AUDIO_CODEC_DEFAULT_CONFIG() { \
.adc_input = AUDIO_HAL_ADC_INPUT_LINE1, \
.dac_output = AUDIO_HAL_DAC_OUTPUT_ALL, \
.codec_mode = AUDIO_HAL_CODEC_MODE_BOTH, \
.i2s_iface = { \
.mode = AUDIO_HAL_MODE_SLAVE, \
.fmt = AUDIO_HAL_I2S_NORMAL, \
.samples = AUDIO_HAL_48K_SAMPLES, \
.bits = AUDIO_HAL_BIT_LENGTH_16BITS, \
}, \
};

#endif
漢亞科技說…
前一個留言,因為篇幅過多,我省略的部分定義內容。
找一找原本在 lyrat_v4_3 board_def.h 的定義,然後搬到 aithinker 裡面,讓編譯能夠通過
gwansyan說…
你好,
1。修改board_def.h內的按鍵定義後,編譯成功,謝謝。
2。透過UART燒錄.bin韌體,com埠是內定com3嗎?
3。第一次使用voip項目,必須燒錄音檔,但終端機出現以下訊息"[Errno 2] No such file or directory",請指導:
PS D:\adf-voip\voip> python $ADF_PATH/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x210000 ./tools/audio-esp.bin

D:\Espressif\.espressif\tools\idf-python\3.8.7\python.exe: can't open file '/esp-idf/components/esptool_py/esptool/esptool.py': [Errno 2] No such file or directory
漢亞科技說…
COM port 不是內定 com3,要看你接到電腦上哪個USB孔上,com數字會不同的。
第一次使用必須燒錄音檔(就是鈴聲),請參考影片,細節需要看一看 README.md。
gwansyan說…
你好:
目前無法燒錄音檔(就是鈴聲),以下訊息顯示沒有此檔案或資料夾。我查詢資料夾是存在的,請指導,謝謝

D:\Espressif\.espressif\tools\idf-python\3.8.7\python.exe: can't open file '/esp-idf/components/esptool_py/esptool/esptool.py': [Errno 2] No such file or directory
JOJU說…
Hello, when I try to build play_mp3_control code after doing all steps above , An error occurred after compilation, please guide


error: 'GPIO_SEL_23' undeclared (first use in this function); did you mean 'GPIO_NUM_23'?
55 | #define TOUCH_SEL_PLAY GPIO_SEL_23
| ^~~~~~~~~~~

error: 'GPIO_SEL_18' undeclared (first use in this function); did you mean 'GPIO_NUM_18'?
56 | #define TOUCH_SEL_VOLUP GPIO_SEL_18

error: 'I2S_NUM_MAX' undeclared (first use in this
function); did you mean 'I2C_NUM_MAX'?
93 | if (i2s_num >= I2S_NUM_MAX)
| ^~~~~~~~~~~
| I2C_NUM_MAX

error: 'PERIPHS_IO_MUX_GPIO0_U' undeclared (first
use in this function)
107 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);

error: 'FUNC_GPIO0_CLK_OUT1' undeclared (first use in this function)
107 | PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);


error: 'PIN_CTRL' undeclared (first use in this function)
108 | WRITE_PERI_REG(PIN_CTRL, 0xFFF0);
| ^~~~~~~~
| ^~~~~~~~~~~~~~~~~~~

此網誌的熱門文章

[筆記] ESP32 在 VS Code 開發環境的編譯與除錯

[筆記] Raspberry Pi 樹莓派的軟體開發

[應用] STM32 DFU (Device Firmware Upgrade)

[筆記] Visual Studio 遠端偵錯的設定步驟

[模組] 無線資料傳輸:nRF905 和 nRF24L01 (以 Arduino 為控制器)

[應用] 藍芽 BLE client/server 架構:BLE remote controller