wiringx
简介
wiringX
是一个开源的 GPIO 控制库,旨在为不同的嵌入式平台提供通用且统一的 GPIO 控制接口。它基于 WiringPi 库进行了改进和扩展,并支持多种嵌入式平台,对Milk-V Duo
也进行了适配。使用wiringX
,开发者可以使用相同的代码来控制不同平台上的 GPIO 引脚,简化了跨平台开发的工作,使得开发嵌入式应用程序更加方便和灵活。
本文将分为如下4个部分介绍如何使用 wiringX 在 Duo 上开发应用:
- wiringX 的 APIs
- 基本使用方法代码示范
- 基于 wiringX 的应用程序编译环境配置
- 一些使用 wiringX 实现的 Demo 和项目介绍
如果您对 wiringX 的使用方法已经非常熟悉,可以直接参考我们的样例代码: duo-examples
注意,Duo 系列的很多引脚功能是复用的,在使用wiringX
来控制 Duo/Duo256M/DuoS 各引脚 的功能时,要先确认一下引脚当前的状态是不是自己需要的功能, 如果不是,可以用duo-pinmux
命令来切换为所需功能。
具体方法请参考: 引脚复用。
Duo/Duo256M wiringX 引脚序号
Duo 和 Duo256M 的 wiringX 引脚序号, 与引脚名序号是一致的,蓝色 LED 控制引脚不在引出的 40PIN 物理引脚上,其 wiringX 的序号是25
。
wiringX | PIN NAME | PIN# | PIN# | PIN NAME | wiringX |
---|---|---|---|---|---|
0 | GP0 | 1 | 40 | VBUS | |
1 | GP1 | 2 | 39 | VSYS | |
GND | 3 | 38 | GND | ||
2 | GP2 | 4 | 37 | 3V3_EN | |
3 | GP3 | 5 | 36 | 3V3(OUT) | |
4 | GP4 | 6 | 35 | ||
5 | GP5 | 7 | 34 | ||
GND | 8 | 33 | GND | ||
6 | GP6 | 9 | 32 | GP27 | 27 |
7 | GP7 | 10 | 31 | GP26 | 26 |
8 | GP8 | 11 | 30 | RUN | |
9 | GP9 | 12 | 29 | GP22 | 22 |
GND | 13 | 28 | GND | ||
10 | GP10 | 14 | 27 | GP21 | 21 |
11 | GP11 | 15 | 26 | GP20 | 20 |
12 | GP12 | 16 | 25 | GP19 | 19 |
13 | GP13 | 17 | 24 | GP18 | 18 |
GND | 18 | 23 | GND | ||
14 | GP14 | 19 | 22 | GP17 | 17 |
15 | GP15 | 20 | 21 | GP16 | 16 |
25 | GP25 | LED |
DuoS wiringX 引脚序号
DuoS 的 wiringX 引脚序号, 与物理引脚序号是一致的,蓝色 LED 控制引脚不在引出的 40PIN 物理引脚上,其 wiringX 的序号是 0
。
排针 J3
排针 J3
上的 GPIO 使用 3.3V 逻辑电平。
wiringX | PIN NAME | PIN# | PIN# | PIN NAME | wiringX |
---|---|---|---|---|---|
3V3 | 1 | 2 | VSYS(5V) | ||
3 | B20 | 3 | 4 | VSYS(5V) | |
5 | B21 | 5 | 6 | GND | |
7 | B18 | 7 | 8 | A16 | 8 |
GND* | 9 | 10 | A17 | 10 | |
11 | B11 | 11 | 12 | B19 | 12 |
13 | B12 | 13 | 14 | GND | |
15 | B22 | 15 | 16 | A20 | 16 |
3V3 | 17 | 18 | A19 | 18 | |
19 | B13 | 19 | 20 | GND | |
21 | B14 | 21 | 22 | A18 | 22 |
23 | B15 | 23 | 24 | B16 | 24 |
GND | 25 | 26 | A28 | 26 |
GND*:引脚 9 在 DuoS V1.1 版本硬件中是一个低电平的 GPIO,在 V1.2 及更高版本硬件中为 GND。
排针 J4
排针 J4
上的 GPIO 使用 1.8V 逻辑电平。
该排针上 的大部分引脚都有其专用功能,如 MIPI DSI 信号,触摸屏信号以及音频信号,如非特殊需求,不建议使用该排针上的引脚做为 GPIO 使用。
wiringX | PIN NAME | PIN# | PIN# | PIN NAME | wiringX |
---|---|---|---|---|---|
VSYS(5V) | 52 | 51 | AUDIO_OUT_R | ||
50 | B1 | 50 | 49 | AUDIO_OUT_L | |
48 | B2 | 48 | 47 | AUDIO_IN_R | |
46 | B3 | 46 | 45 | AUDIO_IN_L | |
44 | E2 | 44 | 43 | 3V3 | |
42 | E1 | 42 | 41 | C18 | 41 |
40 | E0 | 40 | 39 | C19 | 39 |
GND | 38 | 37 | GND | ||
36 | C20 | 36 | 35 | C16 | 35 |
34 | C21 | 34 | 33 | C17 | 33 |
GND | 32 | 31 | GND | ||
30 | C14 | 30 | 29 | C12 | 29 |
28 | C15 | 28 | 27 | C13 | 27 |
一、代码示范
GPIO 使用示例
下面是一个操作 GPIO 的例子,将 Duo 的20
引脚间隔1秒循环拉高再拉低,物理20
引脚的 wiringX 序号是15
。
#include <stdio.h>
#include <unistd.h>
#include <wiringx.h>
int main() {
int DUO_GPIO = 15;
// Duo: milkv_duo
// Duo256M: milkv_duo256m
// DuoS: milkv_duos
if(wiringXSetup("milkv_duo", NULL) == -1) {
wiringXGC();
return -1;
}
if(wiringXValidGPIO(DUO_GPIO) != 0) {
printf("Invalid GPIO %d\n", DUO_GPIO);
}
pinMode(DUO_GPIO, PINMODE_OUTPUT);
while(1) {
printf("Duo GPIO (wiringX) %d: High\n", DUO_GPIO);
digitalWrite(DUO_GPIO, HIGH);
sleep(1);
printf("Duo GPIO (wiringX) %d: Low\n", DUO_GPIO);
digitalWrite(DUO_GPIO, LOW);
sleep(1);
}
return 0;
}
编译后放到 Duo 中运行,可以用万用表或者示波器测量20
引脚的状态是否符合预期。
也可以使用板上的 LED 引脚来验证,通过观察 LED 亮灭来直观地判断程序是否正确执行,LED 引脚的 wiringX 序号为25
,把上面代码中的15
引脚改为25
即可,需要注意的是默认固件开机后通过脚本控制 LED 闪烁了,要将其禁用,方法请参考下面的 blink 例子说明。
I2C 使用示例
以下是一个 I2C 的示例:
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <wiringx.h>
#define I2C_DEV "/dev/i2c-1"
#define I2C_ADDR 0x04
int main(void)
{
int fd_i2c;
int data = 0;
// Duo: milkv_duo
// Duo256M: milkv_duo256m
// DuoS: milkv_duos
if(wiringXSetup("milkv_duo", NULL) == -1) {
wiringXGC();
return -1;
}
if ((fd_i2c = wiringXI2CSetup(I2C_DEV, I2C_ADDR)) <0) {
printf("I2C Setup failed: %d\n", fd_i2c);
wiringXGC();
return -1;
}
// TODO
}
SPI 使用示例
以下是一个SPI的示例:
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <wiringx.h>
int main(void)
{
int fd_spi;
// Duo: milkv_duo
// Duo256M: milkv_duo256m
// DuoS: milkv_duos
if(wiringXSetup("milkv_duo", NULL) == -1) {
wiringXGC();
return -1;
}
if ((fd_spi = wiringXSPISetup(0, 500000)) <0) {
printf("SPI Setup failed: %d\n", fd_spi);
wiringXGC();
return -1;
}
// TODO
}
UART 使用示例
以下是一个 UART 的示例,使用引脚 4/5 上的 UART4:
#include <stdio.h>
#include <unistd.h>
#include <wiringx.h>
int main() {
struct wiringXSerial_t wiringXSerial = {115200, 8, 'n', 1, 'n'};
char buf[1024];
int str_len = 0;
int i;
int fd;
// Duo: milkv_duo
// Duo256M: milkv_duo256m
// DuoS: milkv_duos
if(wiringXSetup("milkv_duo", NULL) == -1) {
wiringXGC();
return -1;
}
if ((fd = wiringXSerialOpen("/dev/ttyS4", wiringXSerial)) < 0) {
printf("Open serial device failed: %d\n", fd);
wiringXGC();
return -1;
}
wiringXSerialPuts(fd, "Duo Serial Test\n");
while(1)
{
str_len = wiringXSerialDataAvail(fd);
if (str_len > 0) {
i = 0;
while (str_len--)
{
buf[i++] = wiringXSerialGetChar(fd);
}
printf("Duo UART receive: %s\n", buf);
}
}
wiringXSerialClose(fd);
return 0;
}
测试方法:
电脑上的 USB 转串口线的 RX 接 Duo 的4脚(UART4_TX),串口线的 TX 接 Duo 的5脚(UART4_RX),串口线的 GND 接 Duo 的 GND,电脑中使用串口调试助手配置好相应的 COM 口和参数。
上述程序编译后生成的可执行程序命名为uart_test
,通过ssh上传到 Duo 中运行,可以看到电脑上的串口工具中收到了Duo Serial Test
字符串,串口工具中发送一个字符串Hello World
,Duo 的终端上也会收到对应的字符串,说明串口收发都正常。
二、开发环境配置
准备开发环境
使用本地的 Ubuntu 系统,推荐 Ubuntu 20.04 LTS
(也可以使用虚拟机中的Ubuntu系统、Windows 中 WSL 安装的 Ubuntu、基于 Docker 的 Ubuntu 系统)。
-
安装编译依赖的工具
sudo apt-get install wget git make
-
获取 Examples 源码
git clone https://github.com/milkv-duo/duo-examples.git
-
加载编译环境
cd duo-examples
source envsetup.sh第一次加载会自动下载所需 的 SDK 包,大小为180M左右,下载完会自动解压到
duo-examples
下,解压后的目录名为duo-sdk
,下次加载时检测到已存在该目录,就不会再次下载了。注: 如果因为网络原因无法完成SDK包的下载,请通过其他途径获取到
duo-sdk.tar.gz
包,手动解压到duo-examples
目录下,重新source envsetup.sh
。 -
编译测试
以
hello-world
为例,进入该例子目录直接执行make
即可:cd hello-world
make编译成功后将生成的
helloworld
可执行程序通过网口或者USB网络等方式传送到 Duo 设备中,比如默认固件支持的 USB-NCM 方式,Duo 的 IP 为192.168.42.1
,用户名是root
,密码是milkv
。scp helloworld [email protected]:/root/
发送成功后,在 ssh 或者串口登陆的终端中运行
./helloworld
,会打印Hello, World!
[root@milkv]~# ./helloworld
Hello, World!至此,我们的编译开发环境就可以正常使用了
如何创建自己的工程
根据需要,拷贝现有的例子,稍加修改即可。比如需要操作某个 GPIO,可以参考blink
例子,LED闪烁就是通过控制 GPIO 电平高低实现的,平台初始化和控制 GPIO 的方法,可参考blink.c
中的代码。
- 新建自己的工程目录
my-project
- 复制
blink
例子中的blink.c
和Makefile
文件到my-project
目录 - 将
blink.c
重命名为自己所需名字如gpio_test.c
- 修改
Makefile
中的TARGET=blink
为TARGET=gpio_test
- 修改
gpio_test.c
,实现自己的代码逻辑 - 执行
make
命令编译 - 将生成的
gpio_test
可执行程序发送到Duo中运行
注意:
- 新建工程目录不是必须要放到 duo-examples 目录下的,可以根据自己的习惯放到其他位置,执行 make 编译命令之前,加载过 duo-examples 目录下的编译环境就可以了(
source /PATH/TO/duo-examples/envsetup.sh
)。 - 在加载过编译环境(
envsetup.sh
)的终端里,不要编译其他平台如 ARM 或 X86 的 Makefile 工程,如需编译其他平台项目,需要新开终端。
三、Demo和项目说明
hello-world
源码:https://github.com/milkv-duo/duo-examples/tree/main/hello-world
一个简单的例子,不操作 Duo 外设,仅打印输出"Hello, World!",用来验证开发环境。
blink
源码:https://github.com/milkv-duo/duo-examples/tree/main/blink
一个让 Duo 板载 LED 闪烁的例子,操作 GPIO 使用的是wiringX
的库,blink.c
代码中包含了wiringX
中的平台初始化以及操作 GPIO 的方法。
注意: 当前Duo的默认固件上电后 LED 会自动闪烁,这个是通过开机脚本实现的,在测试该 blink 例子的时候,需要将 LED 闪烁的脚本禁用,在 Duo 的终端中执行:
mv /mnt/system/blink.sh /mnt/system/blink.sh_backup && sync
也就是将 LED 闪烁脚本改名,重启 Duo 后,LED 就不闪了
测试完我们C语言实现的 blink 程序后,如果需要恢复 LED 闪烁脚本,再将其名字改回来,重启即可:
mv /mnt/system/blink.sh_backup /mnt/system/blink.sh && sync
PWM
源码:https://github.com/milkv-duo/duo-examples/tree/main/pwm
通过手动输入引脚号和占空比来设置引脚上的 PWM 信号。运行程序后请按提示输入 [引脚号]:[占空比] 以设置对应引脚上的 PWM 信号,比如 3:500 。
ADC
adcRead 读取电压值
源码:https://github.com/milkv-duo/duo-examples/tree/main/adc
读取 ADC 的测量值,分为 shell 脚本和C语言两个版本,启动后根据输出提示选择要读取的 ADC,选择后会循环打印 ADC 测量到的电压值。
I2C
I2C 代码目录:https://github.com/milkv-duo/duo-examples/tree/main/i2c
BMP280 温度气压传感器
源码:https://github.com/milkv-duo/duo-examples/tree/main/i2c/bmp280_i2c
通过 I2C 接口连接温度气压传感器 BMP280,读取当前温度和气压值。
VL53L0X ToF 测距传感器
源码:https://github.com/milkv-duo/duo-examples/tree/main/i2c/vl53l0x_i2c
通过 I2C 接口使用 TOF 测距传感器 VL53L0X 模块,读取测量到的距离。
SSD1306 显示屏
源码:https://github.com/milkv-duo/duo-examples/tree/main/i2c/ssd1306_i2c
通过 I2C 接口在 SSD1306 OLED 显示屏上显示字符串。
ADXL345 三轴加速度传感器
源码:https://github.com/milkv-duo/duo-examples/blob/main/i2c/adxl345_i2c
通过 I2C 接口读取 ADXL345 获得的加速度数据,每 1s 读取一次,并将结果打印在屏幕上。
LCM1602 显示屏
源码:https://github.com/milkv-duo/duo-examples/blob/main/i2c/lcm1602_i2c
通过 I2C 接口在 1602 LCD 屏幕上显示字符串。
LCM2004 显示屏
源码:https://github.com/milkv-duo/duo-examples/blob/main/i2c/lcm2004_i2c
通过 I2C 接口在 2004 LCD 屏幕上显示字符串。
TCS34725 颜色传感器
源码:https://github.com/milkv-duo/duo-examples/blob/main/i2c/tcs34725_i2c
通过 I2C 接口读取 TCS34725 颜色传感器,并将获得的数据输出。
SPI
SPI 代码目录:https://github.com/milkv-duo/duo-examples/tree/main/spi
MAX6675 热电偶温度传感器
源码:https://github.com/milkv-duo/duo-examples/tree/main/spi/max6675_spi
通过 SPI 接口连接 K 型热电偶测量模块 MAX6675,测量当前传感器上的温度。
RC522 RFID读写模块
源码:https://github.com/milkv-duo/duo-examples/tree/main/spi/rc522_spi
通过 SPI 接口连接 RC522 RFID 读写模块,读取卡片 ID 和类型并输出到屏幕。
四、编译 wiringX
Duo 固件中已经包含编译好的 wiringX 库(/usr/lib/libwiringx.so),可以直接使用。如果你需要通过编译 wiringX 的源码来生成该库,可以按如下方法编译。
我们这里在 Ubuntu 主机或其他 Linux 发行版上进行编译。
注:Duo 的 wiringX 代码有部分尚未合入上游 wiringX 仓库中,在实际使用中请优先使用 Duo 固件中的 wiringX 库。
下载 wiringX 源码
git clone https://github.com/wiringX/wiringX.git
修改 CMakeLists.txt
进入代码目录:
cd wiringX
wiringX 项目使用 cmake 方式来编译,需要通过 vi
或其他编辑器修改 CMakeLists.txt,来添加交叉编译工具链以及编译参数:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8909393..6918181 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,11 @@ set(CMAKE_EXE_LINKER_FLAGS " -Wl,-rpath=/usr/local/lib/,-rpath=/usr/lib/,-rpath=
set(CMAKE_SHARED_LINKER_FLAGS " -Wl,-rpath=/usr/local/lib/,-rpath=/usr/lib/,-rpath=/lib/")
set(CMAKE_MODULE_LINKER_FLAGS " -Wl,-rpath=/usr/local/lib/,-rpath=/usr/lib/,-rpath=/lib/")
+set(CMAKE_C_COMPILER "${CMAKE_CURRENT_SOURCE_DIR}/host-tools/gcc/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc")
+set(CMAKE_CXX_COMPILER "${CMAKE_CURRENT_SOURCE_DIR}/host-tools/gcc/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-g++")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64")
+
# Start uninstaller generator
function(WRITE_UNINSTALL_TARGET_SCRIPT)
# Create uninstall target template file, if it doesn't exist...
其中有两个变量需要注意一下,根据自己的文件路径来配置:
- CMAKE_C_COMPILER:交叉编译工具链中 gcc 的路径
- CMAKE_CXX_COMPILER:交叉编译工具链中 g++ 的路径
交叉编译工具链的下载链接:host-tools.tar.gz。可以通过 wget 命令下载后解压:
wget https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz
tar -xf host-tools.tar.gz
如果你曾经编译过 duo-buildroot-sdk,其根目录下的 host-tools
目录就是交叉工具链的目录,没必要重新下载,可以直接修改 CMAKE_CURRENT_SOURCE_DIR
字段指定到该目录即可。或者创建个软链接指向该目录。