Introduction
wiringX is a library that allows developers to control the GPIO of various platforms with generic and uniform functions. By using wiringX, the same code will run on all platforms supported by wiringX, natively.
This article will be divided into the following four parts to introduce how to develop applications on Duo using wiringX:
- wiringX APIs
- Basic usage code demonstration
- Configuration of the application compilation environment based on wiringX
- Introduction to some demos and projects implemented using wiringX
If you are already familiar with the usage of wiringX, you can directly refer to our sample code: duo-examples
Please note that many pin functions of the Duo series are multiplexed. When using wiringX
to control the functions of each pin, it is important to confirm the current state of the pin to ensure it matches the desired functionality. If it doesn't, you can use the duo-pinmux
command to switch it to the desired function.
Please refer to the detailed usage instructions for more information:pinmux.
Duo/Duo256M wiringX numbers
The wiringX pin numbers of Duo and Duo256M are consistent with the pin name numbers. However, the blue LED control pin is not available on the 40-pin physical pinout, and its wiringX pin number is 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 numbers
The wiringX pin numbers of DuoS are consistent with the physical pin numbers. The blue LED control pin is not on the 40PIN physical pins, and its wiringX number is 0
.
Header J3
GPIO on Header J3
use 3.3V logic levels.
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*: Pin 9 is a low-level GPIO in the V1.1 version of the hardware, and is GND in the V1.2 version and later.
Header J4
GPIO on Header J4
use 1.8V logic levels.
Most of the pins on this header have dedicated functions, such as MIPI DSI signals, Touch Screen signals, and audio signals. If there is no special requirement, it is not recommended to use the pins on this header as 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 |
1. Code example
GPIO Usage Example
Here is an example of working with GPIO. It will toggle pin 20 on Duo every 1 second, pulling it high and then low. The physical pin number for pin 20 is 15
in the WiringX numbering system.
#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;
}
After compiling and running it on Duo, you can use a multimeter or an oscilloscope to measure the state of pin 20 and verify if it matches the expected behavior.
You can also use the onboard LED pin to verify the behavior. By observing the on/off state of the LED, you can intuitively determine if the program is executing correctly. The WiringX pin number for the LED is 25
. Simply modify the code mentioned above by replacing pin 15
with pin 25
. However, please note that the default firmware has a script to control LED blinking on startup, which needs to be disabled. You can refer to the example explanation for blink below for instructions on how to disable it.
I2C Usage Example
Here is an example of I2C communication:
#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 Usage Example
Here is an example of SPI communication:
#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 Usage Example
Here is an example of UART communication using UART4 on pins 4/5:
#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;
}
Testing Method:
The RX of the USB to serial converter cable is connected to pin 4 (UART4_TX) of Duo, the TX of the serial cable is connected to pin 5 (UART4_RX) of Duo, and the GND of the serial cable is connected to the GND of Duo. On the computer, configure the COM port and parameters using a serial debugging tool.
The compiled executable of the above program is named uart_test
. After uploading it to Duo via SSH and running it, you should be able to see the string Duo Serial Test
received in the serial tool on your computer. If you send the string Hello World
from the serial tool, you will also see the corresponding string received on the Duo's terminal. This confirms that the serial communication is functioning correctly.
2. Development Environment Setup
Build environment on Ubuntu20.04
You can also use Ubuntu installed in a virtual machine, Ubuntu installed via WSL on Windows, or Ubuntu-based systems using Docker.
-
Install the tools that compile dependencies
sudo apt-get install wget git make
-
Get example source code
git clone https://github.com/milkv-duo/duo-examples.git
-
Prepare compilation environment
cd duo-examples
source envsetup.shThe first time you source it, the required SDK package will be automatically downloaded, which is approximately 180MB in size. Once downloaded, it will be automatically extracted to the
duo-examples
directory with the nameduo-sdk
. When source it next time, if the directory already exists, it will not be downloaded again. -
Compile testing
Take hello-world as an example, enter the hello-world directory and execute make:
cd hello-world
makeAfter the compilation is successful, send the generated
helloworld
executable program to the Duo device through the network port or the USB network. For example, the USB-NCM method supported by the default firmware, Duo’s IP is 192.168.42.1, the user name isroot
, and the password ismilkv
.scp -O helloworld [email protected]:/root/
After sending successfully, run
./helloworld
in the terminal logged in via ssh or serial port, and it will printHello, World!
[root@milkv]~# ./helloworld
Hello, World!At this point, our compilation and development environment is ready for use.
How to create your own project
You can simply copy existing examples and make necessary modifications. For instance, if you need to manipulate a GPIO, you can refer to the blink
example. LED blinking is achieved by controlling the GPIO's voltage level. The current SDK utilizes the WiringX library for GPIO operations, which has been adapted specifically for Duo. You can find the platform initialization and GPIO control methods in the blink.c
code for reference.
- Create your own project directory called
my-project
. - Copy the
blink.c
andMakefile
files from theblink
example to themy-project
directory. - Rename
blink.c
to your desired name, such asgpio_test.c
. - Modify the
Makefile
by changingTARGET=blink
toTARGET=gpio_test
. - Modify
gpio_test.c
to implement your own code logic. - Execute the
make
command to compile. - Send the generated
gpio_test
executable program to Duo for execution.
Note:
- Creating a new project directory is not mandatory to be placed within the
duo-examples
directory. You can choose any location based on your preference. Before executing themake
compilation command, it is sufficient to load the compilation environment from theduo-examples
directory (source /PATH/TO/duo-examples/envsetup.sh). - Within the terminal where the compilation environment (envsetup.sh) is loaded, avoid compiling Makefile projects for other platforms such as ARM or X86. If you need to compile projects for other platforms, open a new terminal.
3. Explanation of each example
hello-world
Source code: https://github.com/milkv-duo/duo-examples/tree/main/hello-world
A simple example that doesn't interact with Duo peripherals, only prints the output "Hello, World!" to verify the development environment.
blink
Source code: https://github.com/milkv-duo/duo-examples/tree/main/blink
This example demonstrates how to control an LED connected to a GPIO pin. It uses the WiringX library to toggle the GPIO pin's voltage level, resulting in the LED blinking.
The blink.c
code includes platform initialization and GPIO manipulation methods from the WiringX library.
To test the blink
example, which involves LED blinking, you need to disable the script responsible for the automatic LED blinking on the default firmware of Duo. In the Duo terminal, execute the following command:
mv /mnt/system/blink.sh /mnt/system/blink.sh_backup && sync
This command renames the LED blinking script. After restarting Duo, the LED will no longer blink.
Once you have finished testing the blink
program implemented in C, if you want to restore the LED blinking script, you can rename it back using the following command and then restart Duo:
mv /mnt/system/blink.sh_backup /mnt/system/blink.sh && sync
PWM
Source code:https://github.com/milkv-duo/duo-examples/tree/main/pwm
Set the PWM signal on a pin by manually entering the pin number and duty cycle. After running the program, please follow the prompts to enter the [pin number]:[duty] cycle to set the PWM signal on the corresponding pin, such as 3:500.
ADC
adcRead reads the voltage value
Source code: https://github.com/milkv-duo/duo-examples/tree/main/adc
Reading the measured value of the ADC is divided into two versions: shell script and C language. After starting, select the ADC to be read according to the output prompts. After selection, the voltage value measured by the ADC will be printed in a loop.
I2C
I2C source code directory: https://github.com/milkv-duo/duo-examples/tree/main/i2c
BMP280 Temperature Barometer Sensor
Source code: https://github.com/milkv-duo/duo-examples/tree/main/i2c/bmp280_i2c
This example code shows how to interface the Milk-V Duo with the popular BMP280 temperature and air pressure sensor manufactured by Bosch.
VL53l0X Time of Flight distance sensor
Source code: https://github.com/milkv-duo/duo-examples/tree/main/i2c/vl53l0x_i2c
Use the TOF distance sensor VL53L0X module via the I2C interface to read the measured distance.
SSD1306 OLEDs
Source code: https://github.com/milkv-duo/duo-examples/tree/main/i2c/ssd1306_i2c
Display strings on SSD1306 OLED display via I2C interface.
ADXL345 Three-axis acceleration sensor
Source code: https://github.com/milkv-duo/duo-examples/blob/main/i2c/adxl345_i2c
Read the acceleration data obtained by ADXL345 through the I2C interface, read it every 1 second, and print the result on the screen.