2026年2月22日 星期日

ESP32 Hosted NG - RV1106 SPI Porting

原本 ESP-Hosted-NG 是給 Respberry Pi 的一個 SPI/SDI 通訊的網卡/Bluethooth 方案, 其實 NG 系列的 library 也可以通用到其他的 linux 系統 (但是有機率會失敗),這裡測試移植這個方案到 RV1106 的 buildroot 系統。

系統環境設定

需要先準備這些設定,才開始進行:

  1. 需要先有 rv1106 linux 的編譯專案,要自行下載然後配置,測試能不能 ./build.sh 直接編譯整個 linux image 出來
  2. 需要下載 esp32_hosted_ng,進去後到 /esp/esp_driver/ 做 ./setup -f 他會自動下載 esp-idf,完成之後,要做 source ./esp-idf/export.sh 套用到編譯環境。 (注意 esp32_hosted_ng 一定要用 tag 上的版本,不要直接用 master,本文是使用 (git checkout -f release/ng-1.0.4.0.0) 注意要先 checkout 過去 


注意事項

  1. ESP32 的功耗比較大,建議測試時要外接電源,然後先做 SPI 通訊線路
  2. 這個 esp32_hosted_ng 方案比 SPI 多出三條 esp32 output -> rv1106 input 的線 (data_ready, reset, handshake)


接線方式

接線是對 RV1106 到 ESP32 之間的接線,首先要觀察 ESP32 文件上面的寫法,這些腳位對應到開發板上面的這些腳位:



ESP32HostedNG ESP32 RV1106
IO15 GPIO15/HSPI_CS0 SPI0_CS1_M0/GPIO1_D2_d
IO14 GPIO14/HSPI_CLK SPI0_CLK_M0/GPIO1_C1_d
IO12 GPIO12/HSPI_Q (MISO) SPI0_MISO_M0/GPIO1_C3_d
IO13 GPIO13/HSPI_ID (MOSI) SPI0_MOSI_M0/GPIO1_C2_d
IO2 GPIO2/HSPI_WP0 (DATAREADY) GPIO1_C7_d
IO4 GPIO4/HSPI_HD (HANDSHAKE) GPIO1_C6_d
EN EN/RESET GPIO1_C5_d


注意上面的 handshake, dataready 腳位,這是自己另外定義的腳位,這是由 host 的程式控制的,所以翻到專案上面可以看到 host/spi/esp_spi.h 有寫著 HANDSHAKE 腳位數字,但是不可以直接對應手冊上的數字,這是不對的。


按照官方的寫法, pin 數字是: pin = bank * 32 + (group * 8 + X), (GROUP: A=0, B=1, C=2, D=3)
DATAREADY -> GPIO1_C7_d 就是 1 * 32 + (2 * 8 + 7) = 55
HANDSHAKE-> GPIO1_C6_d 就是 1 * 32 + (2 * 8 + 6) = 54
RESET -> GPIO1_C5_d 就是 1 * 32 + (2 * 8 + 5) = 53

所以稍後會在 esp_spi.h 上面做一些調整。



Handshake/dataready/reset 線的原理 

這三條線是一定要接入的,因為主機端載入整個 esp32 的時候,就會先對這個 esp32 重啟 (reset),然後等待 esp32 傳送 handshake, dataready 訊號後開始同步。


改 RV1106 的 DTS 設備樹設定

1. 移除衝突 SPI

rv1106-luckfox-pico-pro-max-ipc.dtsi

// SPI
&spi0 {
pinctrl-0 = <&spi0m0_clk &spi0m0_miso &spi0m0_mosi &spi0m0_cs0>;
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
status = "disabled"; // 暫時先關閉
compatible = "rockchip,spidev";
spi-max-frequency = <50000000>;
reg = <0>;
};

fbtft@0 {
status = "disabled"; // 暫時先關閉
compatible = "sitronix,st7789v";
reg = <0>;
spi-max-frequency = <20000000>;
fps = <30>;
buswidth = <8>;
debug = <0x7>;
led-gpios = <&gpio2 RK_PB0 GPIO_ACTIVE_HIGH>;//BL
dc-gpios = <&gpio2 RK_PB1 GPIO_ACTIVE_HIGH>;//DC
reset-gpios = <&gpio1 RK_PC3 GPIO_ACTIVE_LOW>;//RES
};
};


2. 加入 SPI 腳位定義

rv1106-pinctrl.dtsi

&pinctrl {
    ...
    esp32_pins {
        /* SPI */
        esp32_spi_pins: esp32-spi-pins {
            rockchip,pins =
<1 RK_PC1 4 &pcfg_pull_none>, /* SPI0_CLK - GPIO1_C1 */
<1 RK_PC3 6 &pcfg_pull_none>, /* SPI0_MISO - GPIO1_C3 */
<1 RK_PC2 6 &pcfg_pull_none>, /* SPI0_MOSI - GPIO1_C2 */
<1 RK_PC0 4 &pcfg_pull_none>;

/* 因為衝突而換 cs0 */
/* <1 RK_PD2 4 &pcfg_pull_none>; /* SPI0_CS1   GPIO1_D2 */

/* 因為衝突而換 cs1 */
/* <1 RK_PC0 4 &pcfg_pull_none>; /* SPI0_CS0   GPIO1_C0 */
        };


        /* 控制腳:全部設 GPIO function */
        esp32_ctrl_pins: esp32-ctrl-pins {
            rockchip,pins =
                <1 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>,   /* RESET */
                <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>, /* HANDSHAKE */
                <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_none>; /* DATAREADY */
        };
    };
}

3. 加入 SPI 功能

rv1106g-luckfox-pico-pro-max.dts

/********** SPI **********/
&spi0 {
    // ESP32
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&esp32_spi_pins &esp32_ctrl_pins>;

    // num-cs = <2>; // 將支援數量改為 2 (如果要用 CS1 而不是 CS0,這個要打開)
};


設定 esp32 firmware 編譯環境

  1. 首先在 esp_driver 下做 source esp-idf/export.sh
  2. 在 network_adapter 下輸入指令 idf.py menuconfig,進去 Example Configuration 把 Transport layer 改 SPI
  3. 輸入指令 idf.py build
  4. 輸入指令 idf.py set-target esp32
  5. 輸入指令 idf.py build
  6. build 出來的東西會在 network_adapter 的 build 目錄下。

設定 SPI 額外的接腳 - 在 host

首先是在 ./host/spi/esp_spi.h 這文件裡面,直接修改參數:

#define HANDSHAKE_PIN           54
#define SPI_IRQ                 gpio_to_irq(HANDSHAKE_PIN)
#define SPI_DATA_READY_PIN      55
#define SPI_DATA_READY_IRQ      gpio_to_irq(SPI_DATA_READY_PIN)
#define SPI_BUF_SIZE            1600

把剛才的 pin 腳定位重改。


設定 SPI Mode - 在 network_adapter (esp32 firmware) 和 host 端

基於 SPI Mode 2 的不穩定性比較高,所以換成 SPI Mode 3,同時要修改 ./host/spi/esp_spi.c 上的

uint8_t g_spi_mode = SPI_MODE_3;

這個模式也需要同步改到 esp32,在 ./network_adapter 裡面的 spi_slave_api.c 中,也要改:

uint8_t g_spi_mode = SPI_MODE_3;


設定 host .ko 的 Makefile 交叉編譯環境

  • 先做 RV1106 buildroot 本身的設定
    • ./build.sh kernelconfig,然後打開 cfg80211, BT_HCIBLUECARD, wifi, insmod, rmmod 這些功能
    • ./build.sh buildrootconfig ,打開 iw, BR2_PACKAGE_BLUEZ5_UTILS, BR2_PACKAGE_WPA_SUPPLICANT
    • 到 sysdrv/cfg/package.mk 把 CONFIG_SYSDRV_ENABLE_WIFI=y 打開
    • ./build.sh (編譯全部, 或是 rootfs, kernel 這兩個)
  • 複製 kernel 目錄過去 host 編譯的路徑 (以防裡面內容被修改)
cp -r ./luckfox-pico/sysdrv/source/objs_kernel [path_to]/esp_hosted_ng/kernel-build

  • 設定 esp32_hosted_ng/host 的 Makefile
# Toolchain and kernel
CROSS_COMPILE := arm-rockchip830-linux-uclibcgnueabihf-
KERNEL := /home/hpcslag/esp-idf/esp-hosted/esp_hosted_ng/kernel-build
ARCH := arm

target=spi
  • 是否要打開 AP 模式支援 (成為 wifi hotspot,要打開 iw list 的支援才會出現 AP 模式)
# Toolchain and kernel
CROSS_COMPILE := arm-rockchip830-linux-uclibcgnueabihf-
KERNEL := /home/hpcslag/esp-idf/esp-hosted/esp_hosted_ng/kernel-build
ARCH := arm

target=spi
CONFIG_AP_SUPPORT=y

改 main.c 加入忽略用文件當驅動載入的 flag:

在 #includes 之後直接加入這行:

MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);


全部編譯

  1. 在 host 下面直接 make (如果出現 hci 編譯錯誤相關訊息,可以考慮是否要把 bluethooth 功能關掉,只需要建立 esp_bt_stub.c,然後在 makefile 下面的 module_objects += 前面把 esp_bt.o 改成 esp_bt_stub.o
  2. 在 network_adapter 下面直接 idf.py build
  3. 在 rv1106 linux 目錄下 ./build kernel

esp_bt_stub.o
#include <linux/kernel.h>

int esp_init_bt(void)
{
    pr_info("esp_hosted: BT disabled (stub)\n");
    return 0;
}

void esp_deinit_bt(void)
{
    pr_info("esp_hosted: BT disabled (stub)\n");
}

void esp_hci_update_rx_counter(int len)
{
    (void)len;
    // no-op
}


燒錄 rv1106

只需要燒錄 kernel 就好了,但是如果前面有把 iw, bluethooth, wifi tools 燒進去,要整個重新燒錄。


燒錄 esp32 firmware


燒錄 esp32 可以使用 esp32 flash download tool ,燒錄時需要對準不同的 partition 記憶體位置燒錄,具體的記憶體位置可以看 ./build/flasher_args.json ,裡面寫的 offset 就是對準記憶體位置


燒錄時,按著 boot 後按一下 reset,就會進入燒錄模式,就可以開始燒錄。

如果要 debug 訊息,可以另外接上 UART debugger USB,把 TX 對到 ESP32 的 GPIO1 (RXD 0), GPIO2 (TXD 0) ,但是打開 serial port 的時候會占用,燒錄時要關閉其他的占用。

建議打開 debug 訊息,因為要確認 esp32 上面燒的是不是 SPI mode:

: *********************************************************************
I (570) stats:                 ESP-Hosted Firmware version :: NG-1.0.5.0.7                        
I (578) stats:                 Transport used :: SDIO only                     
I (585) stats: *********************************************************************

向這個訊息就是錯誤的,要是 SPI Only


移動 host file 到機器上準備啟動

在 host 編譯完成後會得到 esp32_spi.ko ,直接複製到 rv1106 系統,然後使用指令執行:

insmod esp32_spi.ko resetpin=53 clockspeed=10 

這裡是指定了 resetpin 位置,然後 clockspeed 是 MHz,這個數字剛好不會產生問題,其他數字要自己測試。


如果要熱移除模組,可以直接輸入

rmmod -f esp32_spi.ko

除錯時可以用這個指令打印結果:

 dmesg


看報後面出現版本那種訊息時,直接 ip addr 就可以看到 wlan0 的介面啟動了。


除錯

接入過程中,發現各種問題導致失敗,這裡會描述不同的錯誤發生的原因。


版本問題

一起動時發現會一直有 offset drop 的問題



黃色: MOSI,綠色: CLK

綠色為 CS, 黃色為 CLK

從示波器看,啟動的 offset 是沒有差異,於是懷疑是發送指令的問題:

到 esp_spi.c 的 esp_spi_work 裡面加入
esp_info("esp_spi_work... (txready: %d, rxpending: %d)\n",trans_ready,rx_pending);

然後在這行 ret 後面打印內容:
ret = spi_sync_transfer(spi_context.esp_spi_dev, &trans, 1);
esp_info("spi rx ret=%d len=%d first16=%*ph\n", ret, SPI_BUF_SIZE, 16, rx_buf);

結果發現在 03 00 00 00 1f 這裡給的事件 id 不是 1 (1 是主機發出的 BOOT 的訊號)


於是就先把整個版本切回一樣的,重新編譯 host, esp32 ,燒錄之後就成功了。

其他類似的問題是 clock 速度,現在是手動調整的,目前我使用 10MHz 是可以用的,但是再快就不行,esp32 firmware 上面寫的上限是 20MHz。 (這個速度也可能受到線的影響,尤其是杜邦線,甚至長度也不可以超過 10M)



Reference:

[1] https://wiki.luckfox.com/Luckfox-Pico-Pro-Max/GPIO/
[2] https://github.com/espressif/esp-hosted/issues/387
[3] https://github.com/espressif/esp-hosted/issues/489
[4] https://www.samkwort.com/luckfox_esp_hosted
[5] https://github.com/espressif/esp-hosted/blob/master/esp_hosted_ng/docs/spi_protocol.md
[6] https://github.com/espressif/esp-hosted/blob/master/esp_hosted_ng/docs/setup.md#11-host-software-setup



沒有留言:

張貼留言

© Mac Taylor, 歡迎自由轉貼。
Background Email Pattern by Toby Elliott
Since 2014