Contributed by bzhou830, curated by Ai-Thinker
This article first introduces the concept of the LHAL library, then uses the GPIO peripheral of the Xiao An Pai, and finally uses a Raspberry Pi Pico as a logic analyzer to sample and verify the GPIO output.
1. LHAL Library Peripherals
Bouffalo's LHAL library provides a unified wrapper for peripheral drivers. The table below is excerpted from the SDK documentation and lists the BL616 chip peripherals used by the Xiao An Pai that are supported in the LHAL library. Here, √ means supported, × means not supported, and - means the chip does not have that peripheral.
| parameter | BL616/BL618 |
|---|---|
| ADC | √ |
| CAM | × |
| CKS | √ |
| DAC | √ |
| DMA | √ |
| EFUSE | √ |
| EMAC | √ |
| FLASH | √ |
| GPIO | √ |
| I2C | √ |
| IR | √ |
| MJPEG | √ |
| PWM_v1 | - |
| PWM_v2 | √ |
| RTC | √ |
| SEC_AES | √ |
| SEC_SHA | √ |
| SEC_TRNG | √ |
| SEC_PKA | √ |
| SPI | √ |
| TIMER | √ |
| UART | √ |
| USB_v1 | - |
| USB_v2 | √ |
| WDG | √ |
All the peripherals in the table above are configured through the same struct:
struct bflb_device_s {
const char *name; // peripheral name
uint32_t reg_base; // peripheral register base address
uint8_t irq_num; // peripheral interrupt number
uint8_t idx; // peripheral id, e.g. UART0、UART1
uint8_t sub_idx; // peripheral sub id, e.g. DMA0_CH0、DMA0_CH1
uint8_t dev_type; // peripheral type
void *user_data; // user data
};Alternatively, the peripheral object provides two methods:
Operation Steps
Getting the device by peripheral name
struct bflb_device_s *bflb_device_get_by_name(const char *name)
Getting the device by peripheral type and ID
struct bflb_device_s *bflb_device_get_by_id(uint8_t type, uint8_t idx)
The implementation of these two functions can be found in aithinker_Ai-M6X_SDK\drivers\lhal\config\bl616\device_table.c, as shown in the figure below:

2. GPIO Configuration
With the basic understanding above, it's clear that to use the GPIO peripheral we first need to get the GPIO device, and then initialize it with the device we got. The init function provided in the SDK is defined as follows:
void bflb_gpio_init(struct bflb_device_s *dev, uint8_t pin, uint32_t cfgset);- The first parameter is the GPIO device obtained via get;
- The second parameter is the port number corresponding to the hardware;
- The third parameter sets this pin to a specific mode and function (including gpio mode, gpio function, gpio pupd, gpio smt, gpio drive). These settings can be combined with bitwise OR. The following are the setting parameters for each field, excerpted from the SDK.
// gpio mode
#define GPIO_INPUT (0 << GPIO_MODE_SHIFT) /* Input Enable */
#define GPIO_OUTPUT (1 << GPIO_MODE_SHIFT) /* Output Enable */
#define GPIO_ANALOG (2 << GPIO_MODE_SHIFT) /* Analog Enable */
#define GPIO_ALTERNATE (3 << GPIO_MODE_SHIFT) /* Alternate Enable */
// gpio pupd
#define GPIO_FLOAT (0 << GPIO_PUPD_SHIFT) /* No pull-up, pull-down */
#define GPIO_PULLUP (1 << GPIO_PUPD_SHIFT) /* Pull-up */
#define GPIO_PULLDOWN (2 << GPIO_PUPD_SHIFT) /* Pull-down */
// gpio smt
#define GPIO_SMT_DIS (0 << GPIO_SMT_SHIFT)
#define GPIO_SMT_EN (1 << GPIO_SMT_SHIFT)
// gpio drive
#define GPIO_DRV_0 (0 << GPIO_DRV_SHIFT)
#define GPIO_DRV_1 (1 << GPIO_DRV_SHIFT)
#define GPIO_DRV_2 (2 << GPIO_DRV_SHIFT)
#define GPIO_DRV_3 (3 << GPIO_DRV_SHIFT)
// gpio init trig mode
#define GPIO_INT_TRIG_MODE_SYNC_FALLING_EDGE 0
#define GPIO_INT_TRIG_MODE_SYNC_RISING_EDGE 1
#define GPIO_INT_TRIG_MODE_SYNC_LOW_LEVEL 2
#define GPIO_INT_TRIG_MODE_SYNC_HIGH_LEVEL 3
#define GPIO_INT_TRIG_MODE_SYNC_FALLING_RISING_EDGE 4
#define GPIO_INT_TRIG_MODE_ASYNC_FALLING_EDGE 8
#define GPIO_INT_TRIG_MODE_ASYNC_RISING_EDGE 9
#define GPIO_INT_TRIG_MODE_ASYNC_LOW_LEVEL 10
#define GPIO_INT_TRIG_MODE_ASYNC_HIGH_LEVEL 11
// gpio uart function
#define GPIO_UART_FUNC_UART0_RTS 0
#define GPIO_UART_FUNC_UART0_CTS 1
#define GPIO_UART_FUNC_UART0_TX 2
#define GPIO_UART_FUNC_UART0_RX 3
#define GPIO_UART_FUNC_UART1_RTS 4
#define GPIO_UART_FUNC_UART1_CTS 5
#define GPIO_UART_FUNC_UART1_TX 6
#define GPIO_UART_FUNC_UART1_RX 7Let's also take a look at a few functions related to initialization:
void bflb_gpio_deinit(struct bflb_device_s *dev, uint8_t pin); //de-initialize, back to the state before init. io set to floating
void bflb_gpio_set(struct bflb_device_s *dev, uint8_t pin); //set high level
void bflb_gpio_reset(struct bflb_device_s *dev, uint8_t pin); //set low level
bool bflb_gpio_read(struct bflb_device_s *dev, uint8_t pin); //read io level3. GPIO Configuration Example
Now let's pick an IO and configure it to alternately output high and low levels. Then use a logic analyzer to verify that our program works.
Open the Xiao An Pai schematic and see whether IO0 is brought out.

From the figure above, IO0 corresponds to the network labeled I2C_SCL_TOP in the schematic. Searching directly in the schematic file, we find that connector J9 connects to I2C_SCL_TOP.

Where is that on the board? Let's open the Xiao An Pai PCB file; for easier viewing, remove the copper pour first. Now we can see where I2C_SCL_TOP is on the board.

Luckily, this interface can be connected. So we'll configure IO0 directly.
#include "bflb_gpio.h"
#include "bflb_mtimer.h"
#include "board.h"
struct bflb_device_s *gpio;
#define gpio_test GPIO_PIN_0
int main(void)
{
board_init();
// get gpio device
gpio = bflb_device_get_by_name("gpio");
// configure io 0 as pull-up output
bflb_gpio_init(gpio, gpio_test, GPIO_OUTPUT | GPIO_PULLUP);
while (1) {
bflb_gpio_set(gpio, gpio_test); // set high
bflb_mtimer_delay_ms(100); // delay 100ms
bflb_gpio_reset(gpio, gpio_test); // set low
bflb_mtimer_delay_ms(100);
}
}Compile and flash the code above to the Xiao An Pai. Next, use the logic analyzer made with an RP2040 to measure the IO0 output.
4. Verifying with a Logic Analyzer
Flash the logic analyzer firmware into the Raspberry Pi Pico and plug it into the computer. Configure PulseView (this process won't be detailed here).
Connect the Pico's GND and GP2 to the Xiao An Pai's GND and I2C_SCL_TOP respectively.

Run the logic analyzer and capture the following waveform:

From the waveform, the level changes are exactly 100ms apart, fully consistent with our program.
So now we understand the GPIO feature.
Have questions?
For other questions, please visit the unified discussion area: Ai-Thinker Discussions

