Blinky LED Example
The classic "Hello World" of embedded programming - blinking an LED. This example demonstrates basic GPIO output control and timing.
What It Does
This example:
- Configures the system clock to 24MHz
- Sets up a GPIO pin as output (typically PB5)
- Blinks an LED on and off every 500ms
- Runs indefinitely
Hardware Requirements
PY32F003I DFN8 Package
- PB5 (Pin 1): LED output
- GND (Pin 2): Ground connection
- VCC (Pin 8): 3.3V power
LED Circuit
PB5 → [330Ω resistor] → LED Anode
LED Cathode → GND
Component Values:
- LED: Standard 3mm or 5mm LED (any color)
- Resistor: 330Ω (current limiting)
- Current: ~7mA @ 3.3V
Code Example
#![no_main] #![no_std] use panic_halt as _; use py32f0xx_hal as hal; use crate::hal::{ pac, prelude::*, rcc::HSIFreq, timer::delay::Delay, }; use cortex_m_rt::entry; #[entry] fn main() -> ! { // Get hardware peripherals let mut p = pac::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap(); // Configure 24MHz system clock let rcc = p.RCC .configure() .hsi(HSIFreq::Freq24mhz) .sysclk(24.MHz()) .freeze(&mut p.FLASH); // Setup GPIO let gpiob = p.GPIOB.split(); let mut led = gpiob.pb5.into_push_pull_output(); // Create delay timer let mut delay = Delay::new(cp.SYST, &rcc.clocks); // Blink forever loop { led.set_high(); delay.delay_ms(500_u16); led.set_low(); delay.delay_ms(500_u16); } }
Building and Running
Using Make
# Build the example
# Simple way
make blinky # Build only
make flash-blinky # Build and flash
# Traditional way
make build EXAMPLE=blinky MCU_TYPE=PY32F003x4
make flash EXAMPLE=blinky MCU_TYPE=PY32F003x4
Using Cargo
# Build for PY32F003
cargo build --release --example blinky --features py32f003xx4
# Flash with PyOCD
pyocd flash target/thumbv6m-none-eabi/release/examples/blinky --target py32f003xx4
Expected Behavior
After flashing successfully:
- LED should turn ON for 500ms
- LED should turn OFF for 500ms
- Pattern repeats indefinitely
- Total cycle time: 1 second
Code Walkthrough
System Initialization
#![allow(unused)] fn main() { let mut p = pac::Peripherals::take().unwrap(); let cp = cortex_m::Peripherals::take().unwrap(); }
pac::Peripherals
provides access to all microcontroller peripheralscortex_m::Peripherals
provides access to ARM Cortex-M core peripheralstake()
ensures singleton access (only one instance)
Clock Configuration
#![allow(unused)] fn main() { let rcc = p.RCC .configure() .hsi(HSIFreq::Freq24mhz) // Internal 24MHz oscillator .sysclk(24.MHz()) // System clock = 24MHz .freeze(&mut p.FLASH); // Apply and lock configuration }
Why 24MHz?
- Stable and reliable frequency
- Good balance between performance and power
- Well-tested configuration
- Compatible with common baud rates
GPIO Setup
#![allow(unused)] fn main() { let gpiob = p.GPIOB.split(); let mut led = gpiob.pb5.into_push_pull_output(); }
split()
converts raw GPIO peripheral to HAL-managed pinsinto_push_pull_output()
configures pin as:- Output direction (not input)
- Push-pull mode (can drive high and low)
- Default low (LED starts off)
Timing
#![allow(unused)] fn main() { let mut delay = Delay::new(cp.SYST, &rcc.clocks); }
- Uses ARM SysTick timer for precise delays
- Automatically calibrated to system clock frequency
delay_ms()
provides millisecond-accurate delays
Main Loop
#![allow(unused)] fn main() { loop { led.set_high(); // LED ON (3.3V output) delay.delay_ms(500_u16); // Wait 500ms led.set_low(); // LED OFF (0V output) delay.delay_ms(500_u16); // Wait 500ms } }
Customizations
Change Blink Pattern
Fast Blink:
#![allow(unused)] fn main() { led.set_high(); delay.delay_ms(100_u16); led.set_low(); delay.delay_ms(100_u16); }
Slow Blink:
#![allow(unused)] fn main() { led.set_high(); delay.delay_ms(2000_u16); // 2 seconds led.set_low(); delay.delay_ms(2000_u16); }
Heartbeat Pattern:
#![allow(unused)] fn main() { // Quick double-blink, then pause for _ in 0..2 { led.set_high(); delay.delay_ms(100_u16); led.set_low(); delay.delay_ms(100_u16); } delay.delay_ms(800_u16); // Long pause }
Different LED Pins
Use PA2 (DFN8 Pin 7):
#![allow(unused)] fn main() { let gpioa = p.GPIOA.split(); let mut led = gpioa.pa2.into_push_pull_output(); }
Multiple LEDs:
#![allow(unused)] fn main() { let mut led1 = gpiob.pb5.into_push_pull_output(); let mut led2 = gpioa.pa2.into_push_pull_output(); loop { // Alternating blink led1.set_high(); led2.set_low(); delay.delay_ms(250_u16); led1.set_low(); led2.set_high(); delay.delay_ms(250_u16); } }
Toggle Method
#![allow(unused)] fn main() { // More efficient toggling loop { led.toggle(); delay.delay_ms(500_u16); } }
Troubleshooting
LED Not Blinking
Check Hardware:
- LED polarity - Long leg (anode) to resistor, short leg (cathode) to GND
- Resistor value - Use 330Ω to 1kΩ
- Connections - Ensure solid connections
- Power supply - Verify 3.3V on VCC
Check Software:
- Correct pin - Verify PB5 for DFN8 package
- Successful flash - Look for "Programming completed" message
- Device running - Try different delay values
LED Always On/Off
Always On:
#![allow(unused)] fn main() { // Check if set_low() is being called led.set_low(); // Should turn LED off led.set_high(); // Should turn LED on }
Always Off:
- Check LED polarity (try reversing)
- Verify power supply voltage
- Test with multimeter on PB5 pin
Build Errors
Missing target:
rustup target add thumbv6m-none-eabi
Wrong feature:
# Use correct device feature
cargo build --example blinky --features py32f030xx4 # For PY32F030
cargo build --example blinky --features py32f003xx4 # For PY32F003
Flash Errors
Device not found:
# Check SWD connections
pyocd list
# Should show your programmer
Programming failed:
# Try erasing first
pyocd erase --chip --target py32f003xx4
make flash EXAMPLE=blinky MCU_TYPE=py32f003xx4
Advanced Variations
PWM Breathing LED
#![allow(unused)] fn main() { use py32f0xx_hal::pwm::*; // Setup PWM on timer let pwm = p.TIM1.pwm( gpiob.pb5.into_alternate_af2(), 1.kHz(), &rcc.clocks ); let mut pwm_ch = pwm.split(); // Breathing effect loop { // Fade in for duty in 0..100 { pwm_ch.set_duty_cycle_percent(duty); delay.delay_ms(10_u16); } // Fade out for duty in (0..100).rev() { pwm_ch.set_duty_cycle_percent(duty); delay.delay_ms(10_u16); } } }
Interrupt-Driven Blink
use py32f0xx_hal::timer::{Event, Timer}; use cortex_m::interrupt::Mutex; use core::cell::RefCell; // Global LED reference static LED: Mutex<RefCell<Option<gpio::gpiob::PB5<gpio::Output<gpio::PushPull>>>>> = Mutex::new(RefCell::new(None)); #[entry] fn main() -> ! { // Setup timer interrupt let mut timer = Timer::tim1(p.TIM1, 1.Hz(), &rcc.clocks); timer.listen(Event::TimeOut); // Store LED globally cortex_m::interrupt::free(|cs| { LED.borrow(cs).replace(Some(led)); }); // Enable timer interrupt unsafe { cortex_m::peripheral::NVIC::unmask(pac::Interrupt::TIM1_UP_TIM16) }; loop { cortex_m::asm::wfi(); // Sleep until interrupt } } #[interrupt] fn TIM1_UP_TIM16() { // Toggle LED in interrupt cortex_m::interrupt::free(|cs| { if let Some(ref mut led) = LED.borrow(cs).borrow_mut().as_mut() { led.toggle(); } }); }
Related Examples
- Serial Echo - Add serial communication to LED control
- PWM Examples - Generate PWM signals for LED brightness control
- Timer Examples - Advanced timing and interrupts
Next Steps
Once you have blinky working:
- Try Your First Program to understand the code better
- Explore GPIO Documentation for advanced pin control
- Add Serial Communication for debugging output
- Learn PWM Control for variable brightness
The humble blinky example is the foundation for all embedded development - once you master GPIO control, you can interface with any digital device!