First Peripherals¶
These tutorials pick up right after Get Started. You should already have the SiFli-SDK toolchain installed, have flashed hello_world at least once, and know how to open the serial console. Each tutorial is intentionally small: one concept, one peripheral, one clear success condition.
The examples assume an SF32LB52-DevKit-LCD-class board such as sf32lb52-lcd_n16r8. If you are using another board, replace the board name in the build command and confirm the pin names, peripheral instances, and storage layout from that board's support files.
Use this series to build reliable habits: verify one board-level behavior, state a clear success condition, and preserve the working baseline before moving to the next peripheral. It is deliberately practical for developers who are new to the SF32 SDK, even when they already know embedded C.
Before You Start¶
Use the same basic loop for every tutorial:
Keep a serial terminal open. Most beginner mistakes are easier to solve when you can see rt_kprintf() output and the FinSH msh> prompt.
| Item | Check |
|---|---|
| Board name | Matches one of the supported board configurations. |
| Serial port | Opens cleanly and shows boot output. |
| Flash tool | Can reprogram the board repeatedly without changing cables. |
| Schematic or board files | Available before choosing GPIO, ADC, PWM, or RTC resources. |
| One change at a time | Build, flash, and verify after each small edit. |
Blink an LED with GPIO¶
Goal: control a GPIO pin from firmware, the embedded equivalent of a first successful build.
Time: 15 minutes
Start from: example/hal/gpio or example/rt_device/gpio
SF32's RT-Thread-based SDK exposes GPIO through the standard RT-Thread PIN device API. A minimal blink loop looks like this:
#include <rtthread.h>
#include <rtdevice.h>
#define LED_PIN GET_PIN(A, 0) /* replace with your board's LED pin */
int main(void)
{
rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(LED_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
}
- Copy the closest GPIO example into your own project directory, or edit it in place while learning.
- Find the LED pin in the schematic or board support files. Do not guess the port and pin numbering.
- Build and flash with the same board name used for
hello_world. - Confirm the LED blinks at the expected rate.
- Change the delay to 100 ms, rebuild, and confirm the visible blink rate changes.
Success criteria: the LED toggles repeatedly, the serial console still shows normal boot output, and changing the delay changes the blink rate.
If it does not work: check the active polarity, whether the LED is connected through a transistor, whether the pin is already used by another function, and whether your board name matches the hardware on your desk.
What you learned: the PIN device abstraction, board-specific pin definitions, and the basic edit/build/flash loop.
Read a Button with GPIO Input¶
Goal: read a digital input and react to a button press.
Time: 20 minutes
Start from: example/rt_device/gpio
Reading a button uses the same PIN API in input mode. Start with a simple read to confirm the wiring and logic level:
rt_pin_mode(BUTTON_PIN, PIN_MODE_INPUT_PULLUP);
if (rt_pin_read(BUTTON_PIN) == PIN_LOW)
{
/* button pressed, active-low */
}
For anything beyond a quick test, attach an interrupt instead of polling in a tight loop. It is more responsive and much better for power:
rt_pin_attach_irq(BUTTON_PIN, PIN_IRQ_MODE_FALLING, button_isr, RT_NULL);
rt_pin_irq_enable(BUTTON_PIN, PIN_IRQ_ENABLE);
- Identify an available button or input pin on your board.
- Start with a polled read and print the raw value while pressing and releasing the button.
- Confirm whether the button is active-high or active-low.
- Switch to an interrupt handler once the basic read works.
- Toggle the LED from Tutorial 1 each time the button is pressed.
- Add a simple debounce strategy, either by ignoring events for a short interval or by validating the level after a short delay.
Success criteria: one physical press causes one intended firmware action, with no repeated triggers from bounce.
If it does not work: verify pull-up or pull-down configuration, button polarity, interrupt edge selection, and whether the pin is shared with boot mode, debug, or another peripheral.
What you learned: digital input configuration, active-high vs. active-low logic, interrupt-driven input, and why polling is a power smell. See Low-Power Overview for the system-level reason.
Use the Shell and Print Debug Output¶
Goal: get comfortable with the FinSH console and rt_kprintf() before debugging gets harder.
Time: 15 minutes
Start from: example/system/finsh
RT-Thread's FinSH shell gives you an interactive msh> prompt over the same UART used for boot logs. Build these habits early:
- Use
rt_kprintf()for lightweight debug logging instead of raw UART writes. - Use built-in shell commands such as
list_thread,list_device, and memory/status commands to inspect the running system. -
Keep logs short and meaningful; excessive logging can change timing and power behavior.
-
Flash any of the previous examples and reopen your serial terminal.
- Press Enter to get the
msh>prompt. - Run
list_threadandlist_device. - Add
rt_kprintf()calls to the button handler from Tutorial 2. - Confirm the logs appear when you press the button.
Success criteria: you can inspect threads and devices from the shell and correlate your own log messages with physical events.
If it does not work: check serial baud rate, port selection, whether the shell component is enabled, and whether another tool is holding the serial port open.
What you learned: how to inspect a running system without a debugger attached, and how to use low-friction logging without turning every problem into a blind firmware guess.
Read an Analog Sensor with ADC¶
Goal: sample an analog voltage from a potentiometer, simple sensor, or battery-sense input.
Time: 20 minutes
Start from: example/hal/adc or example/rt_device/adc
rt_adc_device_t adc = (rt_adc_device_t)rt_device_find("adc1");
rt_adc_enable(adc, channel);
rt_uint32_t raw = rt_adc_read(adc, channel);
- Identify an ADC-capable pin and channel on your board.
- Confirm the input voltage range and whether there is a divider or analog front-end circuit.
- Open the ADC device, enable the channel, and read a raw value.
- Convert the raw reading to a voltage using the board's reference voltage and ADC resolution.
- Print the result over the shell from Tutorial 3.
- Move the input slowly and confirm the raw value changes smoothly.
Success criteria: raw ADC readings change in the expected direction and convert to a plausible voltage.
If it does not work: check pin muxing, channel number, reference voltage, input range, whether the input is floating, and whether the sensor needs power or settling time before sampling.
What you learned: the rt_device_find() pattern used across RT-Thread device drivers. You will reuse this pattern for I2C, SPI, PWM, storage, and custom drivers.
Drive a PWM Output¶
Goal: generate a PWM signal to dim an LED or drive a simple buzzer.
Time: 20 minutes
Start from: example/rt_device/pwm
struct rt_device_pwm *pwm = (struct rt_device_pwm *)rt_device_find("pwm1");
rt_pwm_set(pwm, channel, period_ns, pulse_ns);
rt_pwm_enable(pwm, channel);
- Pick a PWM-capable channel and confirm the pin mux.
- Choose a period that matches the peripheral: high enough to avoid visible LED flicker, or in the audible range for a buzzer.
- Sweep the duty cycle to fade an LED up and down.
- Try a few fixed tones on a buzzer by changing the period.
- Disable the PWM and confirm the output returns to a safe idle state.
Success criteria: changing duty cycle changes brightness or changing period changes tone, and disabling PWM leaves the output in the expected state.
If it does not work: check whether the pin is routed to the PWM instance, whether the channel number is correct, whether period and pulse are in nanoseconds, and whether the external load needs a driver transistor.
What you learned: period/duty-cycle configuration and the same device-driver pattern from Tutorial 4 applied to a different peripheral class.
Keep Time with the RTC¶
Goal: read and set wall-clock time using the on-chip RTC.
Time: 15 minutes
Start from: example/rt_device/rtc
time_t now = time(RT_NULL);
struct tm *tm_now = localtime(&now);
rt_kprintf("%04d-%02d-%02d %02d:%02d:%02d
",
tm_now->tm_year + 1900, tm_now->tm_mon + 1, tm_now->tm_mday,
tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);
- Set the RTC time once at startup or through a shell command.
- Print the current time periodically.
- Power-cycle the board and confirm whether time is retained.
- If time is not retained, check the board's backup power path before changing firmware.
Success criteria: time increments normally while powered, and retention behavior matches the board hardware.
If it does not work: check RTC component enablement, clock source, backup supply, and whether the board resets RTC state during boot.
What you learned: the standard C time API on top of RT-Thread's RTC device, plus an early example of how firmware behavior depends on hardware design.
Mini Project: Button-Controlled Sensor Logger¶
After finishing the six short tutorials, combine them into one small project:
- Blink the LED at boot.
- Use the button to start or stop sampling.
- Read one ADC value every second while sampling is active.
- Print timestamped readings through FinSH.
- Use PWM brightness or buzzer tone to indicate whether sampling is active.
Pass criterion: the project runs for several minutes without repeated button triggers, runaway logging, or unexpected resets.
First-Peripheral Bring-Up Checklist¶
- Board name, serial port, and flash flow are known-good.
- GPIO output works on a real LED or measured pin.
- GPIO input works with confirmed polarity and debounce.
- FinSH shell is usable, and
list_thread/list_devicework. - ADC readings are plausible and converted to engineering units.
- PWM period/duty settings produce the expected physical output.
- RTC behavior is understood, including retention limitations.
Where to Go Next¶
Once these feel comfortable, move on to Connected Subsystems. The next track combines peripherals with middleware: I2C sensors, LVGL, BLE, audio playback, storage, and deliberate coexistence testing.
Auto-generated content
This page was compiled/drafted without an existing source document. Verify technical claims against SiFli's official documentation before relying on them.