Skip to content

Overview

UART is the most commonly used serial communication interface, used to talk to computers, sensors, GPS modules and more. The Ai-WB2 module has 2 UARTs (UART0 and UART1) built in: UART0 is the system log port (blog_info and other logs always output via UART0), while UART1 can be freely used as a communication serial port. This tutorial implements UART1 receiving data and echoing it back as-is (loopback).

In plain words: UART (serial) is like a "telephone line" between the board and the computer — both sides speak at the same speed, word by word (send one byte, listen one byte). The "loopback" in this tutorial makes the board a repeater: whatever the computer sends, it sends back as-is, like shouting into a valley and hearing the echo.

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/peripherals/uart; the code can be found directly in your local SDK.

🎯Page GoalImplement a serial loopback on UART1 (TX GPIO16 / RX GPIO7): receive data from the computer and send it back as-is; learn UART init, receive and send APIs.
🧰Prerequisites① Ai-WB2 development board, USB-TTL serial module, jumper wires ② Environment set up per [SDK Installation](../sdk/sdk_intro).
🔗RelatedLog output: [System Control - Log System](../system/blog_log); serial wiring and the difference from the log port: see this page's notes.

Hardware Wiring

Wire per the official example (see SDK applications/peripherals/uart/README.md). The communication port is UART1: TX GPIO16, RX GPIO7, cross-connected to the USB-TTL module:

Ai-WB2 Pin USB-TTL Module
IO16 (UART1 TX) RXD
IO7 (UART1 RX) TXD
GND GND

💡 Serial TX/RX must be cross-connected (TX to the other side’s RX, like speaking into the microphone of a phone); and be sure to share ground (GND to GND, otherwise there’s no voltage reference and communication garbles). The USB-TTL module’s VCC can take 3V3 power, or the board can be powered separately via Type-C.

📌 Note the difference: the log you see with the board’s USB port connected directly to the computer is UART0 (the log port, TX GPIO4 / RX GPIO3, always outputs system logs); this tutorial’s UART1 needs a USB-TTL module to bring out.

Enter the Example Project

Open a terminal and enter the official uart example project directory:

cd ~/Ai-Thinker-WB2/applications/peripherals/uart

Note: cd is the “change directory” command; this enters the uart example project. All subsequent make build and make flash commands must run inside this directory first.

Write the Code

Open uart/main.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/peripherals/uart/uart/main.c).

Code highlights:

Code Purpose
uart_id = 1, tx_pin = 16, rx_pin = 7 The communication port UART1 (TX16/RX7), baud rate (transfer speed; both ends must match) 115200; wrong pin or baud means no data
uart_id = 0, tx_pin = 4, rx_pin = 3 The system log port UART0; blog_info always outputs here — don’t connect communication data to it
mode = HOSAL_UART_MODE_POLL Poll mode: hosal_uart_receive waits for data; without it it blocks and doesn’t continue
hosal_uart_receive(&uart_dev_log, data, sizeof(data)) Receives data from the computer, returns the actual byte count; no echo if nothing was received (returns 0)
hosal_uart_send(&uart_dev_log, data, ret) Sends the received data back as-is (loopback); without it the computer sees no echo
Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (a program file) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/uart.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board’s chip. After p= comes the serial device (change it to your computer’s actual port — check with ls /dev/ttyUSB*); b= is the flash baud rate (transfer speed).

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.

Run and Verify

After flashing, the board automatically restarts:

  1. Open two serial tools: the direct USB port (UART0, baud rate 921600, for system logs) and the USB-TTL-connected UART1 (baud rate 115200, for the communication test).
  2. The UART0 log port prints Uart Demo Start.
  3. Send any characters (e.g. hello) in the USB-TTL serial tool; UART1 echoes them as-is, while the UART0 log port prints Get data.
[UART0 Log Port]
Uart Demo Start
Get data
Get data

[UART1 Communication Port]
hello      ← sent
hello      ← echoed

💡 The example baud rates are 115200 (UART1) and 9600 (uart_dev_echo, not actually used in this example); both ends’ baud rates (the serial transfer speed; both ends must match) must agree, otherwise you get garbled text.

Seeing hello echoed back as hello after sending from the USB-TTL and Get data on the log port means success; if there’s no echo after sending, check the “FAQ & Troubleshooting” section at the end.


API Summary for This Tutorial

hosal_uart_init(uart)

Configures the UART controller per the uart_id, pins, baud rate, etc. in the dev struct (initializing the UART1 communication port and UART0 log port in this tutorial).

Parameters:

  • uart: hosal_uart_dev_t struct pointer, required. Key fields: config.uart_id (UART number, values: 0/1/2; communication port uses 1 here), config.tx_pin/config.rx_pin (TX/RX pins, TX16/RX7 here), config.baud_rate (baud rate, values: 115200/921600 etc.), config.data_width (values: HOSAL_DATA_WIDTH_8BIT), config.parity (values: HOSAL_NO_PARITY), config.stop_bits (values: HOSAL_STOP_BITS_1), config.mode (values: HOSAL_UART_MODE_POLL poll / HOSAL_UART_MODE_INT interrupt send/receive)

Return: 0 on success; negative error code on failure

hosal_uart_send(uart, txbuf, size)

Sends buffer data out via the serial port (echoing received data in this tutorial).

Parameters:

  • uart: hosal_uart_dev_t struct pointer
  • txbuf: pointer to the buffer to send, required
  • size: byte count to send, values: 1~256

Return: the actual number of bytes sent (≤ size) on success; negative error code on failure

hosal_uart_receive(uart, data, expect_size)

Reads data from the serial port into the buffer (blocks in poll mode).

Parameters:

  • uart: hosal_uart_dev_t struct pointer
  • data: receive buffer pointer, required
  • expect_size: expected byte count

Return: the actual number of bytes received (may be less than expect_size) on success; negative error code on failure

hosal_uart_finalize(uart)

Stops the serial port and frees the occupied pins (call before no longer using it).

Parameters:

  • uart: hosal_uart_dev_t struct pointer

Return: 0 on success; negative error code on failure

blog_info(fmt, ...)

Outputs an INFO-level log (always via UART0, filtered by level).

Parameters:

  • fmt: format string, same usage as printf, required
  • ...: variadic args matching fmt placeholders, optional

Return: none

xTaskCreate(task, name, stack, param, prio, handle)

Creates a task and adds it to the ready queue; the scheduler runs it according to priority.

Parameters:

  • task: pointer to the task entry function, shaped void task(void *arg), required
  • name: task name string (for debugging), e.g. "uart_task"
  • stack: task stack size (in words), values: any size within memory limits, e.g. 2048 (too small a stack overflows easily and crashes)
  • param: pointer to the argument passed to the entry function; pass NULL if none
  • prio: task priority, values: 0 (lowest)~19 (highest, SDK config), e.g. 16
  • handle: output pointer for the task handle; pass NULL if not needed

Return: pdPASS on success; pdFAIL on failure (e.g. out of memory)


Full Code

Below is the complete uart/main.c source, identical to the official example (applications/peripherals/uart/uart/main.c):

📜 Click to expand the full uart/main.c code
c
/*
 * @Author: xuhongv@yeah.net
 * @Date: 2022-10-03 15:02:19
 * @LastEditors: xuhongv@yeah.net xuhongv@yeah.net
 * @LastEditTime: 2022-10-20 17:42:45
 * @FilePath: \bl_iot_sdk_for_aithinker\applications\get-started\helloworld\helloworld\main.c
 * @Description: Uart
 */
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <blog.h>
#include "bl_sys.h"
#include <stdio.h>
#include <cli.h>
#include <hosal_uart.h>
#include <blog.h>
#include <hosal_uart.h>

void TaskUart(void *param)
{

    uint8_t data[32];
    int ret;

    hosal_uart_dev_t uart_dev_echo = {
        .config = {
            .uart_id = 0,
            .tx_pin = 4, // TXD GPIO
            .rx_pin = 3, // RXD GPIO
            .cts_pin = 255,
            .rts_pin = 255,
            .baud_rate = 9600,
            .data_width = HOSAL_DATA_WIDTH_8BIT,
            .parity = HOSAL_NO_PARITY,
            .stop_bits = HOSAL_STOP_BITS_1,
            .mode = HOSAL_UART_MODE_POLL,
        },
    };

    hosal_uart_dev_t uart_dev_log = {
        .config = {
            .uart_id = 1,
            .tx_pin = 16, // TXD GPIO
            .rx_pin = 7,  // RXD GPIO
            .cts_pin = 255,
            .rts_pin = 255,
            .baud_rate = 115200,
            .data_width = HOSAL_DATA_WIDTH_8BIT,
            .parity = HOSAL_NO_PARITY,
            .stop_bits = HOSAL_STOP_BITS_1,
            .mode = HOSAL_UART_MODE_POLL,
        },
    };

    /* Uart init device */
    hosal_uart_init(&uart_dev_log);
    /* Uart init device */
    hosal_uart_init(&uart_dev_echo);
    blog_info("Uart Demo Start");
    while (1)
    {
        /* Uart receive poll */
        ret = hosal_uart_receive(&uart_dev_log, data, sizeof(data));
        if (ret > 0)
        {
            /* Uart send poll */
            hosal_uart_send(&uart_dev_log, data, ret);
            blog_info("Get data ");
        }
    }
}

/**
 * @brief main
 *
 */
void main(void)
{

    xTaskCreate(TaskUart, "TaskUart", 1024, NULL, 15, NULL);
}

FAQ & Troubleshooting

⚠️ No echo after sending from the USB-TTL
Cause: TX/RX not cross-connected, no common ground, or baud rate mismatch
Fix: confirm IO16 → USB-TTL RXD, IO7 → USB-TTL TXD, and GND must be connected; communication port baud 115200 and identical on both ends

⚠️ Garbled text received
Cause: baud rate mismatch, or the USB-TTL module's levels are incompatible (3.3V vs 5V)
Fix: confirm both ends use the same baud rate (115200); use a 3.3V-level USB-TTL module, not an old 5V-powered module wired directly

⚠️ Mixing up the log port and the communication port
Cause: the log you see with the board's USB directly to the computer is UART0, not UART1 communication data
Fix: remember the two serial ports: UART0 = log (TX4/RX3), UART1 = communication (TX16/RX7); blog_info output always goes through UART0

⚠️ The log port stops printing after a system restart
Cause: per the official README, some configs disable log output after restart
Fix: to keep printing logs after restart, set CONFIG_SYS_REBOOT_LOG_DISENABLE:=1 in proj_config.mk

⚠️ Flashing keeps waiting, the progress bar doesn't move
Cause: download mode wasn't entered, or the cable only charges and can't transfer data
Fix: press and hold EN to enter download mode when prompted; try a Type-C cable that can transfer data and retry

⚠️ Serial device not found or permission denied
Cause: /dev/ttyUSB0 doesn't exist or permissions are insufficient on Linux; USB-to-serial driver not installed on Windows
Fix: on Linux confirm the device with ls /dev/ttyUSB*, for permissions run sudo usermod -aG dialout $USER then log in again; on Windows install the driver in Device Manager and confirm the COM number

Self-Check

Sending characters from the USB-TTL gets them echoed back as-is, and the log port prints Get data — UART communication is verified.

Released under the MIT License. Build Time 2026-09-11 14:52:23