# Internet echo server application on MT7687 with Linkit SDK
###### tags: `Linkit SDK`, `MT7687`, `Toolchain`, `Internet echo server`, `arm`, `Cortex-M4`, `MCU`
為了量測移動、轉動物體的相關數值,如果還要從待測物連接通訊線到外界設備,是件不切實際的事。因此使用無線通訊,將量測數值傳回就是不可或缺的技術。
在尋找合適的晶片/開發板過程中,有找到例如 [ESP32](https://www.espressif.com/en/products/socs/esp32/overview)/[8266](https://www.espressif.com/en/products/socs/esp8266/overview) 等相關資源。但考量到它是中國晶片,便不選擇它。剛好之前運氣好,抽到一張 [Linkit 7697](https://labs.mediatek.com/en/platform/linkit-7697) 的開發板,搭載的晶片是臺灣在地公司聯發科 MediaTek 的 [MT7697](https://labs.mediatek.com/en/chipset/MT7697) 控制器,是屬 Cortex-M4 MCU。寫個 Echo server 放到 Linkit 7697 上,來做網路通訊 PoC 好像不錯。
## 準備 Toolchain
工欲善其事,必先利其器!
### Linkit SDK 環境
相較於其他晶片商,MediaTek 的商業模式是較封閉的。不像許多晶片商,可以在**公開**的網路找到**免費**且**詳細**的**技術資訊**。如果要詳細資訊/支援,就必須要有商業往來(**錢**)。所以如果要在 Linkit 上開發一些應用,無法從零開始,必須使用 MediaTek 打包好的 [Linkit SDK](https://docs.labs.mediatek.com/resource/mt7687-mt7697/en) 環境,再往上發展。也可以從官網上看到,Linkit SDK 是採用 FreeRTOS 當作 kernel。
![img](https://docs.labs.mediatek.com/resource/mt7687-mt7697/files/en/5799961/9208928/1/1473319599246/sdk_architecture_for_MT7697.png)
可以從官網下載 https://docs.labs.mediatek.com/resource/mt7687-mt7697/en/downloads
SDK for Public Users -> SDK for GCC/IAR (因為我是在 Archlinux 上用 GCC)
又因為我的電腦作業系統是 Archlinux **64 bits**
```
$ uname -a
Linux starnight 5.6.13-arch1-1 #1 SMP PREEMPT Thu, 14 May 2020 06:52:53 +0000 x86_64 GNU/Linux
```
但 linkit sdk v4.6.1 裡附的編譯器是 **32 bits** 程式,無法在我的電腦上直接執行。
```
$ file tools/gcc/gcc-arm-none-eabi/bin/arm-none-eabi-gcc
tools/gcc/gcc-arm-none-eabi/bin/arm-none-eabi-gcc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
```
曾經有人說過:「這年頭有誰的電腦是 32 bits!?」無奈 ... 所以我在[社群上有反應這問題](https://www.facebook.com/groups/1641811569367039/permalink/2552673764947477/),希望 MediaTek 可以盡快更新 SDK。而目前暫時的解決方法,就是先額外裝支援 32 bits 的 libc library `pacman -S lib32-glibc`。
### Flash tool
Linkit SDK 有 MT76x7 FlashTool,用來把編譯好的程式是載入 Linkit 7697。但根據 [LinkIt 7697 不用能使用官方的 MT76X7 FLASH TOOL的問題](https://yafaretcs.blogspot.com/2018/12/linkit-7697-mt76x7-flash-tool.html)和想用單純指令/不用 GUI 的需求,所以我改用文章中提到的 [mt76x7-uploader](https://github.com/MediaTek-Labs/mt76x7-uploader)。mt76x7-uploader 的 [README.md](https://github.com/MediaTek-Labs/mt76x7-uploader/blob/master/README.md) 有詳述其運作機制。尷尬的是,它是用 **Python 2.7** 寫出來的程式(截至2020/05/23,其 master branch 仍是 Python 2.7),但 [Python 2.7 已在 2020/1/1 EOL](https://www.python.org/doc/sunset-python-2/)。為了確保之後可以持續有工具可以用,只好自己想辦法改程式,把 mt76x7-uploader 轉到 Python 3。
在 trace code 之後,發現:
* 主要的程式是 [upload.py](https://github.com/MediaTek-Labs/mt76x7-uploader/blob/v1.4/upload.py),預設用 Python 2.7 執行
* 需要第三方 Python libraries:serial、xmodem 和 pyprind。MediaTek 已整理這些 libraries 放到 mt76x7-uploader [路徑](https://github.com/MediaTek-Labs/mt76x7-uploader/tree/v1.4)下。
從上述的追蹤/分析結果,需要從 Python 2 porting 到 Python 3 的部份只有 upload.py 和說明文件(Windows 不在我的考量範圍)。所以我發了個 [pull request (PR)](https://github.com/MediaTek-Labs/mt76x7-uploader/pull/2),且該 PR 已被 merge 到 mt76x7-uploader 的 [python-3 branch](https://github.com/MediaTek-Labs/mt76x7-uploader/tree/python-3)。
終於,準備好 toolchain 包含 Linkit SDK 和 flash tool,接下來可以做一些簡單的測試。
為方便說明,我分別將下載的 Linkit SDK 接壓縮放到家目錄下;同樣的,mt76x7-uploader 也放到**家目錄**下。
## Serial Echo Application
在 Linkit SDK v4.6.1 裡,有個 uart_loopback_data_dma 的範例,其實就是個 serial echo application,適合當做 Hello World 起手式。
### 看程式 Trace
他的路徑在`project/linkit7697_hdk/hal_examples/uart_loopback_data_dma`,進到該資料夾,看一下有什麼。
```
$ cd ~/linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma
$ ls *
readme.txt
EWARM:
flash.icf startup_mt7687.s uart_loopback_data_dma.ewd uart_loopback_data_dma.ewp uart_loopback_data_dma.eww
GCC:
feature.mk Makefile mt7687_flash.ld mt7687_hdk.cmm startup_mt7687.s syscalls.c
inc:
flash_map.h hal_feature_config.h
MDK-ARM:
flash.sct startup_mt7687.s uart_loopback_data_dma.uvoptx uart_loopback_data_dma.uvprojx
src:
main.c system_mt7687.c
```
* Source files 在 `src`
* Header files 在 `inc`
* Makefile 在 `GCC`
### 編譯 Compile
於是進到 `GCC` 資料夾,編譯範例程式:
```
$ cd GCC
$ make
rm -f ...linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma/GCC/build/*.log
Build... cos_api.o
Build... cos_api.o PASS
...
Build... verno.o
Build... verno.o PASS
Linking...
Done
text data bss dec hex filename
9016 772 1420 11208 2bc8 ...linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma/GCC/build/uart_loopback_data_dma.elf
copy_firmware.sh....
BOARD=linkit7697_hdk
bin filename is uart_loopback_data_dma.bin
...linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma/GCC/build/mt7697_bootloader.bin doesn't exist. copy default bootloader done.
cp ...linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma/GCC/mt7687_hdk.cmm to ...linkitsdk/project/linkit7697_hdk/hal_examples/uart_loopback_data_dma/GCC/build/
```
編譯出來的 binary file 是 `uart_loopback_data_dma.bin`,在 `build` 資料夾下。
### 燒錄 Flash
Linkit 7697 接上電腦後,它在我的系統上會註冊一個 serial port `/dev/ttyUSB0`。將 binary file 載入 Linkit 7697:
```
$ ls -l /dev/ttyUSB0
crw-rw---- 1 root uucp 188, 0 5月 24 11:05 /dev/ttyUSB0
$ python3 ~/mt76x7-uploader/upload.py -c /dev/ttyUSB0 -p mt7697 -n ~/mt76x7-uploader/da97.bin -f build/uart_loopback_data_dma.bin
Start uploading the download agent
0% 100%
[############################# ] | ETA: 00:00:00DA uploaded, start uploading the user bin
0% 100%
[########## ] | ETA: 00:00:00
Bin file uploaded. The board reboots now.
```
### 測試 Test
接下來可以用 serial terminal 透過 `/dev/ttyUSB0`,Baud Rate 是 115200 和 Linkit 7697 通訊。因為我習慣使用 `picocom`,所以我就用 `picocom`:
```
$ sudo picocom -b 115200 /dev/ttyUSB0
picocom v3.1
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : 115200
parity is : none
databits are : 8
stopbits are : 1
escape is : C-a
local echo is : no
noinit is : no
noreset is : no
hangup is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv -E
imap is :
omap is :
emap is : crcrlf,delbs,
logfile is : none
initstring : none
exit_after is : not set
exit is : no
Type [C-a] [C-h] to see available commands
Terminal ready
Please input data to this UART port and watch it's output:
Hello World!!!
```
按下 Linkit 7697 上的 **RST 鍵**。它會重開機,接著會透過 `/dev/ttyUSB0` print 出字串:"Please input data to this UART port and watch it's output:",我輸入 "Hello World!!!",Linkit 7697 也真的 echo print 出 "Hello World!!!" => **Serial Echo Application** 測試成功!
## Internet Echo Server Application
在 Linkit 7697 上成功測試 **Serial Echo Application** 後,可以進一步測試 TCP 通訊。
剛好可以修改既有的範例程式 **lwip_socket** `project/linkit7697_hdk/apps/lwip_socket`。
### 修改程式
為了簡化、專注在 TCP echo server 的部份,我修改了 **lwip_socket** 範例,修改內容如:https://gist.github.com/starnight/9cd376bcd8895127ec3ee6217fcc7d3c#file-lwip-socket-diff
所以可以直接 `patch` 該 diff 快速修改:
```
$ cd ~/Desktop/linkitsdk/project/linkit7697_hdk/apps/lwip_socket/
[zack@starnight lwip_socket]$ patch -p1 < /tmp/lwip-socket.diff
patching file src/main.c
```
* 再依據所在環境,於 `src/main.c` 設定 WIFI SSID、密碼
* 於 `src/main.c` 設定 Server 想要 listen 的 port,範例程式是 port 6500
```
#define WIFI_SSID ("<WIFI SSID>")
#define WIFI_PASSWORD ("<WIFI Password>")
#define SOCK_TCP_SRV_PORT 6500
```
### 編譯 Compile
```
$ cd GCC
$ make
../../../../../middleware/MTK/wifi_service/combo/module.mk:21: WIFI_LIB_FOLDER=wifi_supp
Prebuilt WIFI_LIB_FOLDER=wifi_supp
../../../../../middleware/MTK/wifi_service/combo/module.mk:21: WIFI_LIB_FOLDER=wifi_supp
Prebuilt WIFI_LIB_FOLDER=wifi_supp
rm -f ...project/linkit7697_hdk/apps/lwip_socket/GCC/build/*.log
Build... cos_api.o
Build... cos_api.o PASS
...
Build... verno.o
Build... verno.o PASS
Linking...
Done
text data bss dec hex filename
338428 1060 233042 572530 8bc72 ...project/linkit7697_hdk/apps/lwip_socket/GCC/build/lwip_socket.elf
Generate Assembly from elf:
copy_firmware.sh....
BOARD=linkit7697_hdk
bin filename is lwip_socket.bin
...project/linkit7697_hdk/apps/lwip_socket/GCC/build/mt7697_bootloader.bin doesn't exist. copy default bootloader done.
cp ...project/linkit7697_hdk/apps/lwip_socket/GCC/mt7687_hdk.cmm to ...project/linkit7697_hdk/apps/lwip_socket/GCC/build/
```
編譯出來的 binary file 是 `lwip_socket.bin`,在 `build` 資料夾下。
### 燒錄 Flash
```
$ python3 ~/mt76x7-uploader/upload.py -c /dev/ttyUSB0 -p mt7697 -n ~/mt76x7-uploader/da97.bin -f build/lwip_socket.bin
Start uploading the download agent
0% 100%
[############################# ] | ETA: 00:00:00DA uploaded, start uploading the user bin
0% 100%
[############################# ] | ETA: 00:00:00
Bin file uploaded. The board reboots now.
```
### 測試 Test
一樣使用 `picocom` 透過 serial console 看 Linkit 7697 執行時的資訊。按下 Linkit 7697 上的 **RST 鍵**。它會重開機:
```
$ sudo picocom -b 115200 /dev/ttyUSB0
picocom v3.1
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : 115200
parity is : none
databits are : 8
stopbits are : 1
escape is : C-a
local echo is : no
noinit is : no
noreset is : no
hangup is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv -E
imap is :
omap is :
emap is : crcrlf,delbs,
logfile is : none
initstring : none
exit_after is : not set
exit is : no
Type [C-a] [C-h] to see available commands
Terminal ready
[T: 30 M: lwip_socket_example C: info F: main L: 196]: start to create task.
[T: 244 M: wifi C: error F: wifi_wlan_evt_handler L: 296]: Supplicant is not ready to receive event from interface(=0) yet.
[T: 245 M: inband C: warning F: inband_queue_evt_handler L: 791]: u2PacketType(0xe000), ucEID(0x30), ucSeqNum(0x0) not handled!
[T: 1237 M: minisupp C: error F: wpa_supplicant_entry L: 414]: ========= Supplicant Ready =======
[T: 2290 M: inband C: warning F: inband_queue_evt_handler L: 791]: u2PacketType(0xe000), ucEID(0x76), ucSeqNum(0x0) not handled!
[T: 2386 M: common C: info F: wifi_station_port_secure_event_handler L: 114]: wifi connected
[T: 3387 M: common C: info F: ip_ready_callback L: 75]: ************************
[T: 3387 M: common C: info F: ip_ready_callback L: 76]: DHCP got IP:192.168.1.123
[T: 3387 M: common C: info F: ip_ready_callback L: 77]: ************************
[T: 3387 M: lwip_socket_example C: info F: user_entry L: 161]: Begin to create socket_sample_task
[T: 3387 M: lwip_socket_example C: info F: user_entry L: 172]: Finish to create socket_sample_task
[T: 3387 M: lwip_socket_example C: info F: tcp_server_test L: 81]: tcp_server_test starts
[T: 6762 M: lwip_socket_example C: info F: tcp_server_test L: 116]: TCP server waiting for data...
```
可以看到:
1. 連上所在環境的 Wifi
2. 分配到的 IP 是:192.168.1.123
3. TCP echo server 啟動了!等待 client 中 ...
開啟另一個 terminal,使用 `telnet` 當作 client,連到 TCP echo server,並傳送一些字串:
```
$ telnet 192.168.1.123 6500
Trying 192.168.1.123...
Connected to 192.168.1.123.
Escape character is '^]'.
Hello Linkit 7697! LWIP!
Hello Linkit 7697! LWIP!
^]
telnet>
Connection closed.
```
![Client Side](https://hackmd.io/_uploads/rJ1kqud8n.png)
1. 成功連到位於 192.168.1.123:6500 的 TCP echo server。
2. 傳送出 "Hello Linkit 7697! LWIP!" 字串,TCP echo server 也正確回傳 "Hello Linkit 7697! LWIP!" 字串。
```
[T: 29664 M: lwip_socket_example C: info F: tcp_server_test L: 123]: TCP
server received data:Hello Linkit 7697! LWIP!
[T: 36716 M: lwip_socket_example C: info F: tcp_server_test L: 133]: TCP server s close:ret = 0
[T: 36716 M: lwip_socket_example C: info F: tcp_server_test L: 135]: TCP server test completed
Terminating...
Thanks for using picocom
```
![Server Side](https://hackmd.io/_uploads/SyAe9du83.png)
從 TCP echo server 也可以看到和上述相同的紀錄。=> **Internet Echo Server Application** 測試成功!