PY32F0xx HAL Documentation

Welcome to the PY32F0xx Hardware Abstraction Layer (HAL) documentation. This library provides a Rust-based hardware abstraction layer for the Puya Semiconductor PY32F0xx family of microcontrollers.

Crates.io Crates.io docs.rs

What is PY32F0xx?

The PY32F0xx family consists of low-cost ARM Cortex-M0+ based microcontrollers that offer an excellent alternative to STM32F0xx series. These MCUs are manufactured by Puya Semiconductor and provide:

  • Low Cost: Extremely affordable microcontrollers
  • ARM Cortex-M0+: Industry-standard 32-bit processor
  • Rich Peripherals: USART, SPI, I2C, ADC, Timers, and more
  • Small Packages: Available in compact packages like DFN8
  • Wide Voltage Range: Typically 1.7V to 5.5V operation

Supported Devices

This HAL supports the following PY32F0xx device families:

  • PY32F030 (16KB, 32KB, 48KB, 64KB Flash variants)
  • PY32F003 (16KB, 32KB, 64KB Flash variants)
  • PY32F002A (20KB Flash)
  • PY32F002B (20KB Flash)

Key Features

  • Type-safe GPIO with compile-time pin configuration
  • Serial Communication (USART) with working examples
  • ADC Support for analog measurements
  • PWM and Timers for precise timing control
  • SPI and I2C for device communication
  • Real-time Clock (RTC) support
  • DMA Support (on supported devices)
  • Embedded HAL Compatibility for ecosystem integration

Working Examples

This documentation includes comprehensive examples that have been tested and verified on real hardware:

Verified Serial Examples

  • Serial Echo - Basic USART2 communication
  • Serial ADC - Advanced serial + ADC with command interface

Both examples work reliably on PY32F003I DFN8 package at 9600 bps.

What's in This Documentation?

This book is organized into several sections to help you get started and become productive with PY32F0xx development:

Getting Help

If you encounter issues or have questions:

  1. Check the Troubleshooting section
  2. Look for similar examples in the Examples section
  3. Review the Peripheral Drivers documentation
  4. Open an issue on GitHub

Contributing

We welcome contributions! Whether you're fixing bugs, adding examples, improving documentation, or testing on new devices, your help is appreciated. See our Contributing Guidelines to get started.


Ready to begin? Start with the Quick Start Guide!

Quick Start Guide

This guide will get you up and running with PY32F0xx development using Rust in just a few minutes.

Prerequisites

Before you begin, ensure you have the following installed:

  • Rust toolchain (1.70 or later)
  • Python 3.7+ for flashing tools
  • Git for version control
  • A PY32F0xx development board or chip

1. Clone the Repository

git clone https://github.com/UNIT-Electronics-MX/py32f0xx-hal.git
cd py32f0xx-hal

2. Quick Setup

Run the setup script to configure your development environment:

./scripts/setup.sh

This script automatically:

  • Adds the thumbv6m-none-eabi Rust target
  • Creates a Python virtual environment with PyOCD
  • Verifies all required tools are available

Manual Setup (Alternative)

If you prefer to set up manually:

# Add Rust target for ARM Cortex-M0+
rustup target add thumbv6m-none-eabi

# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\\Scripts\\activate
pip install pyocd

# Or use the Makefile
make setup-venv

3. Build Your First Example

Let's build the classic "blinky" LED example using the simplified commands:

# Simple way - build blinky (default MCU: PY32F003x4)
make blinky

# Or specify MCU type explicitly
make EXAMPLE=blinky MCU_TYPE=PY32F003x4

New Simplified Commands:

  • make blinky - Build blinky example
  • make serial_echo - Build serial example
  • make pwm - Build PWM example
  • make adc_values - Build ADC example

Available MCU types:

  • PY32F003x4 - PY32F003 with 16KB Flash (default)
  • PY32F003x6 - PY32F003 with 32KB Flash
  • PY32F003x8 - PY32F003 with 64KB Flash
  • PY32F030x4 - PY32F030 with 16KB Flash
  • PY32F030x6 - PY32F030 with 32KB Flash
  • PY32F030x7 - PY32F030 with 48KB Flash
  • PY32F030x8 - PY32F030 with 64KB Flash
  • PY32F002Ax5 - PY32F002A with 20KB Flash
  • PY32F002Bx5 - PY32F002B with 20KB Flash

4. Flash to Your Device

Connect your PY32F0xx board via SWD and flash the firmware:

# Super simple - build and flash blinky
make flash-blinky

# Or with explicit MCU type
make flash EXAMPLE=blinky MCU_TYPE=PY32F003x4

# Alternative syntax
make example=blinky flash

5. Verify It Works

If everything is set up correctly, you should see:

  • The LED on your board blinking
  • No compilation errors
  • Successful flashing messages

Quick Reference Commands

Get help anytime with:

make help          # Show quick command reference
make full-help     # Show complete help with all options

Most Used Commands:

make blinky        # Build blinky
make flash-blinky  # Build and flash blinky
make serial_echo   # Build serial example
make flash-serial_echo # Build and flash serial
make clean         # Clean build files

Next Steps

Now that you have a working setup, explore more examples with the simplified commands:

# Try the serial echo example
make flash-serial_echo

# Or the ADC example  
make flash EXAMPLE=serial_adc

# PWM example
make flash-pwm

# List all available examples
make list-examples

Common Issues

Compilation Errors

If you get target-related errors:

rustup target add thumbv6m-none-eabi

Flashing Errors

If PyOCD can't find your device:

  1. Check SWD connections
  2. Verify device is powered
  3. Try: pyocd list to see connected devices

Permission Issues (Linux)

Add yourself to the dialout group:

sudo usermod -a -G dialout $USER
# Log out and back in

What's Next?

Need Help?

Hardware Setup

This guide covers the hardware setup requirements for PY32F0xx development, including wiring, programmer connections, and common pin configurations.

Development Hardware

Minimum Requirements

  1. PY32F0xx Microcontroller - Any supported variant
  2. SWD Programmer - Multiprotocol Programmer, ST-Link v2, J-Link, or compatible
  3. Power Supply - 3.3V (or 1.7V-5.5V depending on variant)
  4. Breadboard/PCB - For prototyping connections
  • Development Board with built-in SWD programmer
  • Oscilloscope/Logic Analyzer for debugging
  • Multimeter for voltage verification
  • LED and resistor for basic output testing

SWD Programming Connections

All PY32F0xx devices support Serial Wire Debug (SWD) for programming and debugging.

Standard SWD Pinout

SWD SignalPY32F0xx PinDescription
SWDIOPA13Data line
SWDCKPA14Clock line
NRSTNRSTReset (optional)
GNDGNDGround
VCCVCCPower (3.3V)
Multiprotocol      PY32F0xx
-------------      --------
SWDIO        <--> PA13
SWDCK        <--> PA14  
GND          <--> GND
3V3          <--> VCC
RST          <--> NRST (optional)

Package-Specific Pin Configurations

DFN8 Package (PY32F003I)

The DFN8 is a compact 8-pin package commonly used for space-constrained applications.

            DFN8 Pinout (Top View):
               VCC  1 ┌─────┐ 8  PB5/LED
               PA0  2 │     │ 7  PA14-SWDCK/PB6
               PA1  3 │     │ 6  PA13-SWDIO/PA10
               PA2  4 └─────┘ 5  PB0/PF2-NRST

Verified DFN8 Configuration

This configuration is tested and working on PY32F003I DFN8:

PinFunctionDescription
PA0USART2 TX (AF9)Serial transmit
PA1USART2 RX (AF9)Serial receive
PA2ADC Channel 2Analog input
PB5GPIO OutputDebug LED
PA13SWDIOProgramming
PA14SWDCKProgramming

Power Supply Considerations

Voltage Requirements

  • PY32F002: 1.7V - 5.5V
  • PY32F003: 1.7V - 5.5V
  • PY32F030: 1.7V - 5.5V

Power Connections

Power Rails:
VCC/VDD  --> Main power supply (3.3V recommended)
VDDA     --> Analog power (connect to VCC)
VSS/GND  --> Ground (0V)
VSSA     --> Analog ground (connect to GND)

Decoupling

Always include decoupling capacitors:

  • 100nF ceramic close to each VCC pin
  • 10μF tantalum/electrolytic for bulk decoupling
  • 100nF ceramic between VDDA and VSSA

Clock Configuration

Internal Oscillators

PY32F0xx devices have built-in oscillators:

  • HSI: 8MHz internal RC oscillator (default)
  • LSI: ~40kHz internal low-speed oscillator

External Crystals (Optional)

For applications requiring precise timing:

  • HSE: 4-32MHz external crystal
  • LSE: 32.768kHz external crystal for RTC
HSE Crystal Connections:
OSC_IN   --> Crystal + 12-22pF capacitor to GND
OSC_OUT  --> Crystal + 12-22pF capacitor to GND

Common Test Circuits

Basic LED Test

PB5 --> 330Ω resistor --> LED --> GND

Serial Communication Test

PA0 (TX) --> RX of USB-to-Serial adapter
PA1 (RX) <-- TX of USB-to-Serial adapter
GND      --> GND of USB-to-Serial adapter

ADC Input Test

PA2 --> 10kΩ potentiometer --> GND
        (center tap to PA2)

Troubleshooting Hardware Issues

Programming Issues

  1. Can't connect to device

    • Check SWD wiring (SWDIO, SWDCK, GND)
    • Verify power supply (3.3V)
    • Try different SWD frequency
  2. Device not responding

    • Check NRST connection
    • Verify crystal oscillator (if using HSE)
    • Try holding NRST low during connection

Runtime Issues

  1. LED not blinking

    • Check LED polarity and current-limiting resistor
    • Verify GPIO configuration in code
    • Measure voltage on GPIO pin
  2. Serial not working

    • Check baud rate (9600 default)
    • Verify TX/RX wiring (not crossed)
    • Test with known-good USB-to-Serial adapter
  3. ADC readings incorrect

    • Check VDDA connection
    • Verify input voltage range (0-VDDA)
    • Check reference voltage configuration

Safety Guidelines

  • Never exceed maximum voltage ratings
  • Use current-limiting resistors with LEDs
  • Double-check power supply polarity
  • Avoid ESD damage - use anti-static precautions
  • Keep connections short for high-frequency signals

Next Steps

Once your hardware is set up:

  1. Follow the Development Environment guide
  2. Try the First Program tutorial
  3. Explore Working Examples

Development Environment Setup

This guide covers setting up your development environment for PY32F0xx embedded development using Rust.

Prerequisites

Required Software

  1. Rust Toolchain (1.70.0 or later)
  2. Python 3.7+ (flashing tool)
  3. Git (for version control)
  4. Text Editor/IDE (VS Code recommended)
  • OpenOCD (alternative to PyOCD)
  • GDB (for debugging)
  • Logic Analyzer/Oscilloscope (for hardware debugging)

Installing Rust

Linux/macOS

# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Restart shell or source environment
source ~/.cargo/env

# Verify installation
rustc --version
cargo --version

Windows

  1. Download and run rustup-init.exe
  2. Follow the installation wizard
  3. Restart command prompt
  4. Verify with rustc --version

Add ARM Target

# Add Cortex-M0+ target (required for PY32F0xx)
rustup target add thumbv6m-none-eabi

# Verify target is installed
rustup target list | grep thumbv6m-none-eabi

Installing PyOCD

PyOCD is the recommended flashing and debugging tool for PY32F0xx devices.

# Install PyOCD system-wide
pip install pyocd

# Or create virtual environment (recommended)
python3 -m venv py32-env
source py32-env/bin/activate  # Linux/macOS
# py32-env\\Scripts\\activate.bat  # Windows
pip install pyocd

Verify Installation

# Check PyOCD version
pyocd --version

# List supported devices (should include py32f0xx)
pyocd list --targets | grep py32

# Test SWD connection (with device connected)
pyocd list

Setting Up the Project

Clone Repository

git clone https://github.com/UNIT-Electronics-MX/py32f0xx-hal.git
cd py32f0xx-hal

Quick Setup Script

# Run the provided setup script
./scripts/setup.sh

This script will:

  • Verify Rust installation
  • Add ARM target if missing
  • Create Python virtual environment
  • Install PyOCD
  • Verify all tools

Manual Setup

If you prefer manual setup:

# Ensure ARM target is available
rustup target add thumbv6m-none-eabi

# Create and activate Python environment
python3 -m venv venv
source venv/bin/activate

# Install PyOCD
pip install pyocd

# Verify build works
make build EXAMPLE=blinky MCU_TYPE=py32f003xx4

IDE Configuration

Install recommended extensions:

# Install VS Code extensions
code --install-extension rust-lang.rust-analyzer
code --install-extension ms-vscode.cpptools
code --install-extension marus25.cortex-debug

Recommended VS Code Settings (.vscode/settings.json):

{
    \"rust-analyzer.cargo.target\": \"thumbv6m-none-eabi\",
    \"rust-analyzer.checkOnSave.allTargets\": false,
    \"rust-analyzer.cargo.allFeatures\": false,
    \"rust-analyzer.cargo.features\": [\"py32f003xx4\"]
}

Launch Configuration (.vscode/launch.json):

{
    \"version\": \"0.2.0\",
    \"configurations\": [
        {
            \"name\": \"Debug PY32F0xx\",
            \"type\": \"cortex-debug\",
            \"request\": \"launch\",
            \"servertype\": \"pyocd\",
            \"cwd\": \"${workspaceRoot}\",
            \"executable\": \"target/thumbv6m-none-eabi/debug/examples/blinky\",
            \"device\": \"py32f003xx4\",
            \"svdFile\": \"tools/Misc/SVD/py32f003xx.svd\"
        }
    ]
}

Other IDEs

CLion/IntelliJ IDEA:

  • Install Rust plugin
  • Configure Rust toolchain
  • Set target to thumbv6m-none-eabi

Vim/Neovim:

  • Install rust-analyzer LSP
  • Configure for embedded development

Hardware Setup

Programmer Connection

Connect your SWD programmer (ST-Link v2, J-Link, etc.):

Programmer    PY32F0xx
----------    --------
SWDIO      ←→ PA13
SWDCK      ←→ PA14  
GND        ←→ GND
3V3        ←→ VCC
RST        ←→ NRST (optional)

Test Hardware Connection

# Activate Python environment if using one
source venv/bin/activate

# Check if device is detected
pyocd list

# Should show something like:
# 0 => ST-Link v2 [STM32F103C8T6]

Building Your First Project

Test Build

# Build the blinky example
make build EXAMPLE=blinky MCU_TYPE=py32f003xx4

# Should complete without errors

Flash and Test

# Simple way - flash blinky (uses PY32F003x4 by default)
make flash-blinky

# Or explicit way
make flash EXAMPLE=blinky MCU_TYPE=PY32F003x4

# LED should start blinking

Development Workflow

Typical Development Cycle

  1. Edit Code - Modify examples or create new ones
  2. Build - make build EXAMPLE=your_example MCU_TYPE=py32f003xx4
  3. Flash - make flash EXAMPLE=your_example MCU_TYPE=py32f003xx4
  4. Test - Verify functionality on hardware
  5. Debug - Use GDB/PyOCD for troubleshooting

Project Structure

py32f0xx-hal/
├── .vscode/             # VS Code configuration
│   ├── settings.json    # Rust Analyzer settings
│   ├── launch.json      # Debug configuration
│   ├── tasks.json       # Build tasks
│   └── extensions.json  # Recommended extensions
├── src/                 # HAL source code
├── examples/            # Example applications
├── docs/                # This documentation
├── tools/               # Development tools
├── scripts/             # Build and setup scripts
├── Cargo.toml           # Rust dependencies
├── memory.x             # Memory layout
└── Makefile            # Build automation

Creating New Examples

# Copy existing example
cp examples/blinky.rs examples/my_project.rs

# Edit the new file
# Build and test
make build EXAMPLE=my_project MCU_TYPE=py32f003xx4
make flash EXAMPLE=my_project MCU_TYPE=py32f003xx4

Debugging Setup

GDB with PyOCD

Start PyOCD GDB server:

# Terminal 1 - Start GDB server
pyocd gdbserver --target py32f003xx4

# Terminal 2 - Connect GDB
arm-none-eabi-gdb target/thumbv6m-none-eabi/debug/examples/blinky
(gdb) target remote localhost:3333
(gdb) load
(gdb) break main
(gdb) continue

VS Code Debugging

  1. Set breakpoints in code
  2. Press F5 (Start Debugging)
  3. VS Code will build, flash, and start debug session
  4. Step through code, inspect variables

Environment Variables

Useful environment variables for development:

# In your ~/.bashrc or ~/.zshrc
export PY32_TARGET=py32f003xx4
export PY32_PROGRAMMER=pyocd

# Use in commands
make build MCU_TYPE=${PY32_TARGET}

Troubleshooting

Common Issues

"thumbv6m-none-eabi" target not found:

rustup target add thumbv6m-none-eabi

PyOCD not found:

# Ensure Python environment is activated
source venv/bin/activate
pip install pyocd

Permission denied on Linux:

# Add user to dialout group
sudo usermod -a -G dialout $USER
# Log out and back in

Build failures:

# Clean and rebuild
make clean
make build EXAMPLE=blinky MCU_TYPE=py32f003xx4

Getting Help

  1. Check Troubleshooting section
  2. Review Hardware Setup
  3. Open issue on GitHub with:
    • OS and versions
    • Complete error messages
    • Steps to reproduce

Next Steps

Once your environment is set up:

  1. Try Your First Program tutorial
  2. Explore Examples to learn the HAL
  3. Read Hardware Setup for wiring details
  4. Check Peripheral Documentation for advanced usage

VS Code Configuration

The project includes pre-configured VS Code settings for optimal Rust development:

Install these VS Code extensions for the best development experience:

# Core Rust development
code --install-extension rust-lang.rust-analyzer
code --install-extension ms-vscode.cpptools

# Debugging support
code --install-extension marus25.cortex-debug

Settings Configuration

The project includes .vscode/settings.json with optimized settings:

{
    "rust-analyzer.cargo.target": "thumbv6m-none-eabi",
    "rust-analyzer.checkOnSave.allTargets": false,
    "rust-analyzer.cargo.allFeatures": false,
    "rust-analyzer.cargo.features": ["py32f003xx4"]
}

This configuration:

  • Sets the correct target for embedded development
  • Optimizes analysis for faster performance
  • Uses the right features for your MCU

Debug Configuration

The project includes .vscode/launch.json for debugging:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug PY32F0xx",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "pyocd",
            "cwd": "${workspaceRoot}",
            "executable": "target/thumbv6m-none-eabi/debug/examples/blinky",
            "device": "py32f003x4",
            "svdFile": "tools/Misc/SVD/py32f003xx.svd",
            "configFiles": ["tools/Misc/pyocd.yaml"],
            "targetId": "py32f003x4",
            "runToEntryPoint": "main",
            "showDevDebugOutput": "raw"
        }
    ]
}

Using VS Code Debugging

  1. Build debug version: make debug-blinky
  2. Set breakpoints in your code
  3. Press F5 to start debugging
  4. Use debug controls: F10 (step over), F11 (step into)

For complete debugging guide, see VS Code Debugging.

For detailed VS Code configuration, see VS Code Configuration.

Performance Tips

  • Use release builds for final deployment: cargo build --release
  • Enable LTO for smaller binaries in Cargo.toml
  • Profile memory usage with cargo bloat
  • Optimize for size with opt-level = \"s\" in Cargo.toml

Your development environment is now ready for PY32F0xx embedded development!

Your First Program

This tutorial will guide you through creating and running your first PY32F0xx program using the HAL.

Goal

By the end of this tutorial, you'll have:

  • Created a simple LED blinking program
  • Built and flashed it to your PY32F0xx device
  • Understood the basic structure of a PY32F0xx Rust program

Prerequisites

Step 1: Understanding the Basic Structure

Every PY32F0xx Rust program follows this basic pattern:

#![no_main]     // We don't use Rust's standard main
#![no_std]      // No standard library (embedded environment)

use panic_halt as _;  // Panic handler for embedded

use py32f0xx_hal as hal;  // Import HAL
use crate::hal::{
    pac,           // Peripheral Access Crate
    prelude::*,    // Common traits and imports
    rcc::HSIFreq,  // Clock configuration
};

use cortex_m_rt::entry;  // Entry point macro

#[entry]  // This is our \"main\" function
fn main() -> ! {
    // Hardware setup code goes here
    
    loop {
        // Main program loop
    }
}

Step 2: Create Your First Program

Let's create a simple LED blinky program. Create examples/my_first_program.rs:

#![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() -> ! {
    // Take ownership of device peripherals
    let mut p = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();

    // Configure the system clock
    let rcc = p.RCC
        .configure()
        .hsi(HSIFreq::Freq24mhz)  // Use 24MHz internal oscillator
        .sysclk(24.MHz())         // Set system clock to 24MHz
        .freeze(&mut p.FLASH);    // Apply clock configuration

    // Initialize GPIO port B
    let gpiob = p.GPIOB.split();

    // Configure PB5 as push-pull output (LED pin)
    let mut led = gpiob.pb5.into_push_pull_output();

    // Create a delay timer using the system timer
    let mut delay = Delay::new(cp.SYST, &rcc.clocks);

    // Main loop - blink the LED
    loop {
        led.set_high();           // Turn LED on
        delay.delay_ms(500_u16);  // Wait 500ms
        led.set_low();            // Turn LED off
        delay.delay_ms(500_u16);  // Wait 500ms
    }
}

Step 3: Build Your Program

# Build your program
make build EXAMPLE=my_first_program MCU_TYPE=py32f003xx4

# If successful, you should see:
# Compiling my_first_program v0.1.0
# Finished release [optimized] target(s)

Step 4: Flash to Device

# Connect your PY32F0xx device via SWD
# Then flash the program
make flash EXAMPLE=my_first_program MCU_TYPE=py32f003xx4

# You should see PyOCD output:
# 0001735:INFO:board:Target type is py32f003xx4
# 0001736:INFO:flash_loader:Erasing chip...
# 0001750:INFO:flash_loader:Programming...
# 0001755:INFO:flash_loader:Programming completed

Step 5: Verify It Works

After successful flashing:

  1. LED should start blinking - On for 500ms, off for 500ms
  2. If using PB5 - Connect LED with 330Ω resistor to GND
  3. No errors in terminal - Clean flash output indicates success

Understanding the Code

Memory Management

#![allow(unused)]
#![no_main]  // No standard main function
#![no_std]   // No heap allocation, stack-based only

fn main() {
use panic_halt as _;  // Simple panic handler - halts on panic
}

Hardware Abstraction

#![allow(unused)]
fn main() {
let mut p = pac::Peripherals::take().unwrap();
// pac::Peripherals gives access to all hardware peripherals
// take() ensures only one instance exists (singleton pattern)
}

Clock Configuration

#![allow(unused)]
fn main() {
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)  // Internal 24MHz oscillator
    .sysclk(24.MHz())         // System clock frequency
    .freeze(&mut p.FLASH);    // Lock in configuration
}

Why 24MHz? This is a proven, stable configuration that works reliably across PY32F0xx devices.

GPIO Setup

#![allow(unused)]
fn main() {
let gpiob = p.GPIOB.split();
// split() converts raw peripheral to HAL-managed GPIO port

let mut led = gpiob.pb5.into_push_pull_output();
// Configure specific pin as output
// push_pull = can source and sink current
}

Timing

#![allow(unused)]
fn main() {
let mut delay = Delay::new(cp.SYST, &rcc.clocks);
// Uses ARM Cortex-M SysTick timer for delays
// Calibrated to the system clock frequency
}

Customizing Your Program

#![allow(unused)]
fn main() {
// Faster blinking
led.set_high();
delay.delay_ms(100_u16);  // 100ms on
led.set_low();
delay.delay_ms(100_u16);  // 100ms off

// Slower blinking  
led.set_high();
delay.delay_ms(1000_u16);  // 1 second on
led.set_low();
delay.delay_ms(1000_u16);  // 1 second off
}

Use Different LED Pin

#![allow(unused)]
fn main() {
// For PY32F003 DFN8 package - use PA2 instead
let gpioa = p.GPIOA.split();
let mut led = gpioa.pa2.into_push_pull_output();
}

Add 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 {
    led1.set_high();
    led2.set_low();
    delay.delay_ms(250_u16);
    
    led1.set_low();  
    led2.set_high();
    delay.delay_ms(250_u16);
}
}

Common Issues and Solutions

LED Not Blinking

  1. Check wiring:

    PB5 → [330Ω resistor] → LED anode
    LED cathode → GND
    
  2. Verify pin assignment:

    • PY32F003I DFN8: Use PB5 (pin 1)
    • Check your device pinout
  3. Check power supply:

    • Should be stable 3.3V
    • Measure with multimeter

Build Errors

"error: target 'thumbv6m-none-eabi' not found"

rustup target add thumbv6m-none-eabi

"feature 'py32f003xx4' not found"

# Use correct feature for your device:
make build EXAMPLE=my_first_program MCU_TYPE=py32f030xx4  # for PY32F030

Flash Errors

"No devices found"

# Check SWD connections
pyocd list  # Should show your programmer

# Verify wiring:
# SWDIO ↔ PA13
# SWDCK ↔ PA14  
# GND ↔ GND

"Flash failed"

# Try different SWD frequency
pyocd flash --frequency 1000000 target/thumbv6m-none-eabi/release/examples/my_first_program

# Or try mass erase first
pyocd erase --chip --target py32f003xx4

Next Steps

Congratulations! You've successfully created, built, and flashed your first PY32F0xx program. Here's what to explore next:

Learn More Peripherals

Explore Examples

# Try the serial echo example
make flash EXAMPLE=serial_echo MCU_TYPE=py32f003xx4

# Or the ADC example
make flash EXAMPLE=serial_adc MCU_TYPE=py32f003xx4

Build Real Projects

  • Temperature monitor with serial output
  • PWM motor controller
  • Data logger with external sensors
  • Simple IoT device with serial interface

Advanced Topics

You're now ready to start building more complex PY32F0xx applications!

PY32F003 Device Guide

The PY32F003 is a low-cost ARM Cortex-M0+ microcontroller particularly well-suited for cost-sensitive applications. This guide covers device-specific features and configurations.

Device Overview

PY32F003 Family Variants

DeviceFlashRAMPackage OptionsKey Features
PY32F003x416KB2KBDFN8, TSSOP20Basic peripherals
PY32F003x632KB4KBTSSOP20, QFN32More memory
PY32F003x864KB8KBTSSOP20, QFN32Maximum memory

Key Specifications

  • Core: ARM Cortex-M0+ @ up to 24MHz
  • Voltage: 1.7V to 5.5V operation
  • Temperature: -40°C to +85°C (industrial)
  • Power: Very low power consumption
  • Packages: DFN8, TSSOP20, QFN32

Peripheral Availability

PY32F003 Peripheral Map

PeripheralF003x4F003x6F003x8Notes
GPIO6-27 pins15-27 pins15-27 pinsPackage dependent
USARTUSART1,2USART1,2USART1,22 channels
SPISPI1SPI1SPI11 channel
I2CI2C1I2C1I2C11 channel
ADC1x12-bit1x12-bit1x12-bitUp to 8 channels
TimersTIM1,3,14,16,17TIM1,3,14,16,17TIM1,3,14,16,17Advanced + basic
RTCYesYesYes32.768kHz
IWDGYesYesYesIndependent watchdog
WWDGYesYesYesWindow watchdog
CRCYesYesYesHardware CRC

Package-Specific Information

DFN8 Package (PY32F003I)

Ultra-compact 2x3mm package - ideal for space-constrained designs.

            DFN8 Pinout (Top View):
               VCC  1 ┌─────┐ 8  PB5/LED
               PA0  2 │     │ 7  PA14-SWDCK/PB6
               PA1  3 │     │ 6  PA13-SWDIO/PA10
               PA2  4 └─────┘ 5  PB0/PF2-NRST

Available Pins:

  • Power: VCC, VSS
  • Programming: PA13 (SWDIO), PA14 (SWDCK), NRST
  • GPIO: PA0, PA1, PA2, PB5
  • USART2: PA0 (TX), PA1 (RX)
  • ADC: PA2 (Channel 2)

Recommended Usage:

#![allow(unused)]
fn main() {
// Verified working configuration
let tx = gpioa.pa0.into_alternate_af9();  // USART2_TX
let rx = gpioa.pa1.into_alternate_af9();  // USART2_RX
let adc_pin = gpioa.pa2.into_analog();    // ADC input
let led = gpiob.pb5.into_push_pull_output(); // Debug LED
}

Additional Features:

  • More GPIO pins available
  • Separate analog power (VDDA/VSSA)
  • More peripheral pin options
  • Better power supply decoupling

Memory Layout

Flash Memory Organization

PY32F003 Flash Layout:
┌─────────────────┐ 0x0800FFFF (64KB variant)
│   User Flash    │ 
│   (Application) │
├─────────────────┤ 0x08007FFF (32KB variant)  
│                 │
├─────────────────┤ 0x08003FFF (16KB variant)
│   System Flash  │ 0x08003000
│   (Bootloader)  │
└─────────────────┘ 0x08000000

RAM Memory Map

PY32F003 RAM Layout:
┌─────────────────┐ 0x20001FFF (8KB variant)
│      SRAM       │
│   (User Data)   │ 
├─────────────────┤ 0x20000FFF (4KB variant)
│                 │
├─────────────────┤ 0x200007FF (2KB variant)
│                 │
└─────────────────┘ 0x20000000

Clock Configuration

Clock Sources

Internal Clocks:

  • HSI: 8MHz internal RC oscillator (can be scaled to 24MHz)
  • LSI: ~40kHz low-speed internal oscillator

External Clocks (optional):

  • HSE: 4-32MHz external crystal/oscillator
  • LSE: 32.768kHz external crystal (for RTC)
#![allow(unused)]
fn main() {
// Standard 24MHz configuration (proven stable)
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)  // Use internal 24MHz
    .sysclk(24.MHz())         // System clock = 24MHz
    .freeze(&mut p.FLASH);

// Alternative: Use external crystal
let rcc = p.RCC
    .configure()
    .hse(8.MHz())            // 8MHz external crystal
    .sysclk(24.MHz())        // PLL to 24MHz
    .freeze(&mut p.FLASH);
}

Power Management

Supply Requirements

  • VDD/VCC: 1.7V to 5.5V (main supply)
  • VDDA: Analog supply (same as VDD in most cases)
  • VSS/VSSA: Ground (0V)

Power Consumption (Typical @ 3.3V)

ModeCurrentDescription
Run @ 24MHz~3mAFull speed operation
Run @ 8MHz~1.5mAReduced speed
Sleep~0.8mACPU stopped, peripherals active
Stop~2µAMost peripherals stopped
Standby~1µAMinimal power, RTC active

Low Power Programming

#![allow(unused)]
fn main() {
use py32f0xx_hal::pwr::{PowerMode, Pwr};

// Enter sleep mode
cortex_m::asm::wfi(); // Wait for interrupt

// Enter stop mode
let pwr = Pwr::new(p.PWR);
pwr.stop_mode();

// Configure wake-up sources
pwr.enable_wakeup_pin(WakeupPin::PA0);
}

Development Tips

DFN8-Specific Considerations

  1. Limited pins - Plan pin usage carefully
  2. Single-layer PCB friendly - Bottom pad can be difficult
  3. Hand soldering - Requires fine-pitch soldering skills
  4. Debug access - PA13 on bottom pad may need special connector

Pin Multiplexing Strategy

#![allow(unused)]
fn main() {
// Example: Maximize utility of limited DFN8 pins
// PA0: USART2_TX (communication)
// PA1: USART2_RX (communication) 
// PA2: ADC input (sensing)
// PB5: GPIO output (LED/control)
// PA13: SWDIO (programming - bottom pad)
// PA14: SWDCK (programming)
}

PCB Design Recommendations

DFN8 Package:

  • Thermal pad connection to ground plane recommended
  • Decoupling: 100nF close to VCC pin
  • Crystal placement: Keep HSE crystal close if used
  • Debug connector: Consider test points for PA13/PA14

Power Supply:

  • Linear regulator for 3.3V from 5V if needed
  • Battery operation possible with low-power modes
  • Brown-out detection available in software

Common Applications

Sensor Nodes

#![allow(unused)]
fn main() {
// Temperature sensor with serial output
let temp_sensor = adc.convert(&adc_pin);
serial.write_str(&format!("Temp: {}°C\r\n", temp_to_celsius(temp_sensor)));
}

Simple Controllers

#![allow(unused)]
fn main() {
// Button-controlled LED
if button.is_high() {
    led.set_high();
} else {
    led.set_low();  
}
}

Data Loggers

#![allow(unused)]
fn main() {
// Log sensor data periodically
rtc.set_alarm(60); // Every minute
loop {
    cortex_m::asm::wfi(); // Sleep until alarm
    let data = read_sensors();
    log_data(data);
}
}

Debugging and Development

SWD Programming

  • SWDIO: PA13 (bottom pad on DFN8)
  • SWDCK: PA14
  • NRST: Hardware reset (optional)
  • VCC/GND: Power for programmer

Serial Debugging

#![allow(unused)]
fn main() {
// Use USART2 for debug output
let mut debug_serial = Serial::usart2(
    p.USART2, 
    (tx, rx), 
    115200.bps(), 
    rcc.clocks
);

// Debug output
writeln!(debug_serial, "Debug: value = {}", value).ok();
}

Migration Notes

From Other STM32F0xx

  • Pin compatibility: Check alternate functions
  • Clock setup: May need adjustment
  • Peripheral differences: Some features may be missing

Between PY32F003 Variants

  • Pin count differences: DFN8 vs TSSOP20 vs QFN32
  • Memory sizes: Check flash/RAM requirements
  • Package-specific features: Some pins only on larger packages

Next Steps

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:

  1. Configures the system clock to 24MHz
  2. Sets up a GPIO pin as output (typically PB5)
  3. Blinks an LED on and off every 500ms
  4. 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 peripherals
  • cortex_m::Peripherals provides access to ARM Cortex-M core peripherals
  • take() 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 pins
  • into_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

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:

  1. LED polarity - Long leg (anode) to resistor, short leg (cathode) to GND
  2. Resistor value - Use 330Ω to 1kΩ
  3. Connections - Ensure solid connections
  4. Power supply - Verify 3.3V on VCC

Check Software:

  1. Correct pin - Verify PB5 for DFN8 package
  2. Successful flash - Look for "Programming completed" message
  3. 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);
    }
}
}
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();
        }
    });
}

Next Steps

Once you have blinky working:

  1. Try Your First Program to understand the code better
  2. Explore GPIO Documentation for advanced pin control
  3. Add Serial Communication for debugging output
  4. 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!

Serial Communication Examples

This section covers serial communication examples that have been tested and verified on PY32F0xx hardware.

Overview

The PY32F0xx HAL includes two comprehensive serial communication examples:

  1. Serial Echo - Basic USART communication
  2. Serial ADC - Advanced serial + ADC integration

Both examples are confirmed working on PY32F003I DFN8 package at 9600 bps.

Key Features

Verified Configuration

  • USART2: PA0=TX(AF9), PA1=RX(AF9)
  • Baud Rate: 9600 bps
  • Clock: 24MHz HSI (reliable configuration)
  • Debug LED: PB5 for visual feedback

Hardware Tested

  • Device: PY32F003I DFN8 package
  • Programmer: PyOCD with ST-Link compatible
  • Serial: USB-to-Serial adapters (multiple tested)

Quick Start

Build and Flash

# Serial echo example
make flash EXAMPLE=serial_echo MCU_TYPE=py32f003xx4

# Serial ADC example
make flash EXAMPLE=serial_adc MCU_TYPE=py32f003xx4

Connect Serial Terminal

# Using screen
screen /dev/ttyACM0 9600

# Using minicom  
minicom -D /dev/ttyACM0 -b 9600

Pin Configuration

PY32F003I DFN8 Package

            DFN8 Pinout (Top View):
               VCC  1 ┌─────┐ 8  PB5/LED
               PA0  2 │     │ 7  PA14-SWDCK/PB6
               PA1  3 │     │ 6  PA13-SWDIO/PA10
               PA2  4 └─────┘ 5  PB0/PF2-NRST

Connections

PinFunctionConnection
PA0USART2 TX (AF9)Serial adapter RX
PA1USART2 RX (AF9)Serial adapter TX
PA2ADC Channel 2Analog input (0-3.3V)
PB5GPIO OutputLED + 330Ω resistor
PA13SWDIOSWD programmer
PA14SWDCKSWD programmer

Technical Implementation

Clock Configuration

Both examples use the same reliable clock setup:

#![allow(unused)]
fn main() {
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)  // 24MHz HSI
    .sysclk(24.MHz())         // System clock
    .freeze(&mut p.FLASH);
}

USART Setup

The key to success is using AF9 alternate function:

#![allow(unused)]
fn main() {
let tx = gpioa.pa0.into_alternate_af9(); // TX
let rx = gpioa.pa1.into_alternate_af9(); // RX

let serial = Serial::usart2(
    p.USART2,
    (tx, rx),
    9600.bps(),
    clocks,
);
}

Debug LED

Visual feedback with PB5:

#![allow(unused)]
fn main() {
let mut led = gpiob.pb5.into_push_pull_output();
led.set_high().ok(); // LED on for activity
}

Example Comparison

FeatureSerial EchoSerial ADC
ComplexityBasicAdvanced
USART✅ Echo functionality✅ Command interface
ADC❌ Not used✅ PA2 analog input
Commands❌ Simple echo✅ Interactive (r/s/q/h)
Streaming❌ No✅ Continuous ADC
Best forLearning basicsReal applications

Testing Tools

Python Test Script

You can quickly verify serial communication using a Python script, for example, test_serial.py:

import serial
import time

# Connect to device
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)

# Test echo
ser.write(b'Hello PY32F0xx!\n')
response = ser.read(50)
print(f"Response: {response}")

# For ADC example
ser.write(b'h')  # Get help
ser.write(b'r')  # Read ADC
ser.write(b's')  # Start streaming
time.sleep(5)
ser.write(b'q')  # Stop streaming

Manual Testing

# Connect to device
screen /dev/ttyACM0 9600

# For serial_echo.rs:
# Type any characters, they should echo back

# For serial_adc.rs:
# Type 'h' for help
# Type 'r' to read ADC once  
# Type 's' to start streaming
# Type 'q' to stop streaming

Troubleshooting

No Serial Output

  1. Check connections:

    • PA0 (TX) → Serial adapter RX
    • PA1 (RX) → Serial adapter TX
    • GND → GND
  2. Verify settings:

    • Baud rate: 9600
    • Data bits: 8
    • Parity: None
    • Stop bits: 1
  3. Test adapter:

    • Try different USB-to-Serial adapter
    • Check adapter drivers

LED Not Working

  1. Check PB5 connection
  2. Verify LED polarity
  3. Use appropriate current-limiting resistor (330Ω)

ADC Issues

  1. Input range: Ensure 0-3.3V on PA2
  2. Reference: ADC uses VDD as reference
  3. Test with known voltage (e.g., 1.5V battery)

Next Steps

Once you have serial communication working:

  1. Explore USART Peripheral documentation
  2. Learn ADC Configuration details
  3. Try Custom Applications using serial
  4. Check Troubleshooting for advanced issues

Serial Echo Example

The serial_echo.rs example demonstrates basic USART2 serial communication on the PY32F0xx microcontroller. This is the simplest serial example and a great starting point for learning.

What It Does

This example:

  1. Configures USART2 on PA0 (TX) and PA1 (RX) at 9600 bps
  2. Sets up a debug LED on PB5 for visual feedback
  3. Echoes back any characters received over serial
  4. Provides a simple interactive serial interface

Hardware Requirements

PY32F003I DFN8 Package

  • PA0: USART2 TX (AF9) → Connect to serial adapter RX
  • PA1: USART2 RX (AF9) → Connect to serial adapter TX
  • PB5: Debug LED → Connect LED + 330Ω resistor to GND
  • GND: Ground → Connect to serial adapter GND

Code Walkthrough

Clock Configuration

The example uses the same reliable clock setup as the working blinky example:

#![allow(unused)]
fn main() {
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)  // Set HSI to 24MHz 
    .sysclk(24.MHz())         // Set system clock to 24MHz
    .freeze(&mut p.FLASH);
}

GPIO Setup

Configure pins for USART2 with AF9 alternate function:

#![allow(unused)]
fn main() {
// Split GPIO ports
let gpioa = p.GPIOA.split();
let gpiob = p.GPIOB.split();

// Configure USART2 pins with AF9
let tx = gpioa.pa0.into_alternate_af9(); // PA0 as TX
let rx = gpioa.pa1.into_alternate_af9(); // PA1 as RX

// Debug LED
let mut debug_pin = gpiob.pb5.into_push_pull_output();
}

Serial Interface

Create the USART2 serial interface:

#![allow(unused)]
fn main() {
let mut serial = p.USART2.serial((tx, rx), 9_600.bps(), &rcc.clocks);

// Send startup message
serial.write_str("=== USART2 PA0/PA1 AF9 WORKING - 9600 bps ===\\r\\n").ok();
}

Main Echo Loop

The main loop continuously reads and echoes characters:

#![allow(unused)]
fn main() {
loop {
    // Try to read a character
    if let Ok(byte) = serial.read() {
        // Toggle debug LED on activity
        debug_pin.toggle();
        
        // Echo character back (with handling for special characters)
        match byte {
            b'\\r' => {
                serial.write(b'\\r').ok();
                serial.write(b'\\n').ok();
            },
            _ => {
                serial.write(byte).ok();
            }
        }
    }
}
}

Building and Flashing

Using Make

# Build the example
make build EXAMPLE=serial_echo MCU_TYPE=py32f003xx4

```bash
# Simple way
make flash-serial_echo

# Traditional way  
make flash EXAMPLE=serial_echo MCU_TYPE=PY32F003x4

Using Cargo Directly

# Build for PY32F003
cargo build --release --example serial_echo --features py32f003xx4

# Flash with PyOCD
pyocd flash target/thumbv6m-none-eabi/release/examples/serial_echo --target py32f003xx4

Testing the Example

1. Connect Hardware

Wire your USB-to-Serial adapter:

  • Adapter RXPA0 (PY32F0xx TX)
  • Adapter TXPA1 (PY32F0xx RX)
  • Adapter GNDPY32F0xx GND

2. Open Serial Terminal

# Using screen
screen /dev/ttyACM0 9600

# Using minicom
minicom -D /dev/ttyACM0 -b 9600

# Using PuTTY (Windows)
# Set COM port, 9600 baud, 8-N-1

3. Expected Output

Upon connecting, you should see:

=== USART2 PA0/PA1 AF9 WORKING - 9600 bps ===
PA0: TX (AF9) - Register configured
PA1: RX (AF9) - Register configured
PB5: Debug LED - Activity indicator
Ready to echo characters...
Type any character:

4. Test Functionality

  • Type any character → Should echo back immediately
  • PB5 LED → Should toggle with each character
  • Enter key → Should produce proper line endings

Expected Behavior

InputOutputLED
HHToggle
HelloHelloToggle for each char
EnterNew lineToggle
Any ASCIISame characterToggle

Troubleshooting

No Serial Output

  1. Check wiring:

    PY32F0xx    Adapter
    PA0 (TX) → RX
    PA1 (RX) ← TX
    GND      → GND
    
  2. Verify serial settings: 9600-8-N-1

  3. Test serial adapter with loopback (TX→RX)

LED Not Working

  1. Check PB5 connection
  2. Verify LED polarity (anode to PB5, cathode via resistor to GND)
  3. Use 330Ω current-limiting resistor

Characters Corrupted

  1. Check baud rate (must be 9600)
  2. Verify clock configuration (24MHz HSI)
  3. Test different serial adapter

No Response

  1. Verify device is flashed and running
  2. Check SWD connections for programming
  3. Try reset (cycle power or use NRST)

Code Customization

Change Baud Rate

#![allow(unused)]
fn main() {
// Change from 9600 to 115200
let mut serial = p.USART2.serial((tx, rx), 115_200.bps(), &rcc.clocks);
}

Add More Functionality

#![allow(unused)]
fn main() {
// Process specific commands
match byte {
    b'h' | b'H' => {
        serial.write_str("Help: Type any character to echo\\r\\n").ok();
    },
    b'\\r' => {
        serial.write_str("\\r\\n").ok();
    },
    _ => {
        serial.write(byte).ok();
    }
}
}

Use Different Pins

#![allow(unused)]
fn main() {
// Use different USART pins (check datasheet for AF mappings)
let tx = gpioa.pa9.into_alternate_af1();  // USART1 TX
let rx = gpioa.pa10.into_alternate_af1(); // USART1 RX
let mut serial = p.USART1.serial((tx, rx), 9_600.bps(), &rcc.clocks);
}

Next Steps

Once you have the echo example working:

  1. Try Serial ADC Example for more advanced functionality
  2. Learn about USART Peripheral details
  3. Explore GPIO Configuration for other pins
  4. Build custom applications using serial communication

Serial ADC Example

USART2 Configuration

I2C Slave Communication

The i2c_slave_demo.rs example demonstrates I2C slave functionality on the PY32F0xx microcontroller with serial debug output. This example shows how to configure the microcontroller as an I2C slave device that can receive data from an I2C master.

Overview

This example configures:

  • I2C Slave: Address 0x50, 100kHz communication
  • GPIO Pins: PA10 (SDA), PB6 (SCL) with AF6 alternate function
  • Serial Debug: USART2 on PA0 (TX), PA1 (RX) at 9600 bps
  • Status LED: PB5 for visual feedback

Hardware Setup

Pin Configuration

  • PA10: I2C SDA (Serial Data) - AF6, Open Drain + Pull-up
  • PB6: I2C SCL (Serial Clock) - AF6, Open Drain + Pull-up
  • PA0: USART2 TX (Debug output) - AF9
  • PA1: USART2 RX (Debug input) - AF9
  • PB5: Status LED (Push-pull output)

External Connections

Connect external I2C pull-up resistors (4.7kΩ recommended) between:

  • SDA (PA10) and VCC
  • SCL (PB6) and VCC

Code Structure

Initialization

#![allow(unused)]
fn main() {
// Configure I2C pins with AF6
let _sda = gpioa.pa10.into_alternate_af6();   
let _scl = gpiob.pb6.into_alternate_af6();    

// GPIO configuration: Open Drain + Pull-up
unsafe {
    let gpioa = &(*pac::GPIOA::ptr());
    let gpiob = &(*pac::GPIOB::ptr());
    
    // PA10 (SDA): Open Drain + Pull-up
    gpioa.otyper.modify(|_, w| w.ot10().set_bit());     
    gpioa.pupdr.modify(|_, w| w.pupd10().pull_up());    
    
    // PB6 (SCL): Open Drain + Pull-up  
    gpiob.otyper.modify(|_, w| w.ot6().set_bit());      
    gpiob.pupdr.modify(|_, w| w.pupd6().pull_up());     
}
}

I2C Slave Configuration

#![allow(unused)]
fn main() {
// Configure I2C SLAVE - Address 0x50
let slave_addr = 0x50_u8;
unsafe {
    let i2c = &(*pac::I2C::ptr());
    
    i2c.cr1.modify(|_, w| w.pe().clear_bit());
    i2c.cr2.write(|w| w.freq().bits(24_u8));    
    i2c.oar1.write(|w| w.add().bits(slave_addr)); 
    i2c.cr1.write(|w| w.ack().set_bit().pe().set_bit());
}
}

Main Loop - Event Handling

#![allow(unused)]
fn main() {
loop {
    unsafe {
        let i2c = &(*pac::I2C::ptr());
        let sr1 = i2c.sr1.read();
        
        // Address matched - start of transaction
        if sr1.addr().bit_is_set() {
            contact_count += 1;
            writeln!(serial, "=== I2C Transaction #{} ===\r", contact_count).unwrap();
            
            // Clear ADDR flag (required for ACK)
            let _sr2_clear = i2c.sr2.read();
            i2c.cr1.modify(|_, w| w.ack().set_bit());
        }
        
        // Data received
        if sr1.rxne().bit_is_set() {
            let data = i2c.dr.read().dr().bits();
            byte_count += 1;
            
            writeln!(serial, "Data #{}: 0x{:02X} ({})", byte_count, data, data).unwrap();
            
            // Show ASCII if printable
            if data >= 32 && data <= 126 {
                writeln!(serial, "ASCII: '{}'\r", data as char).unwrap();
            }
        }
        
        // End of transaction
        if sr1.stopf().bit_is_set() {
            writeln!(serial, "Transaction complete\r\n").unwrap();
            
            // Clear STOP flag
            i2c.cr1.modify(|r, w| w.bits(r.bits()));
            i2c.cr1.modify(|_, w| w.ack().set_bit());
        }
    }
}
}

Building and Running

Build the Example

make build EXAMPLE=i2c_slave_demo

Flash to Microcontroller

make flash EXAMPLE=i2c_slave_demo

Monitor Serial Output

make monitor
# or
picocom /dev/ttyACM0 -b 9600

Expected Output

When an I2C master communicates with the slave, you'll see output like:

=== I2C SLAVE DEMO ===
Configuration: PA10=SDA, PB6=SCL
I2C SLAVE configured at address 0x50
Waiting for I2C communication...

=== I2C Transaction #1 ===
Data #1: 0x42 (66)
ASCII: 'B'
Transaction complete

=== I2C Transaction #2 ===
Data #2: 0x48 (72)
ASCII: 'H'
Data #3: 0x65 (101)
ASCII: 'e'
Data #4: 0x6C (108)
ASCII: 'l'
Data #5: 0x6C (108)
ASCII: 'l'
Data #6: 0x6F (111)
ASCII: 'o'
Transaction complete

Testing with ESP32-H2

You can test the I2C slave using an ESP32-H2 as master with MicroPython:

from machine import I2C, Pin

# Configure I2C master
i2c = I2C(0, scl=Pin(22), sda=Pin(12), freq=100000)

# Scan for devices
devices = i2c.scan()
print(f"I2C devices found: {[hex(d) for d in devices]}")

# Send data to slave at address 0x50
if 0x50 in devices:
    # Send single byte
    i2c.writeto(0x50, b'A')
    
    # Send multiple bytes
    i2c.writeto(0x50, b'Hello')
    
    # Send custom data
    data = bytearray([0x01, 0x02, 0x03, 0xFF])
    i2c.writeto(0x50, data)

Key Features

  • Slave Address: Configurable (default 0x50)
  • Data Reception: Handles single and multi-byte transactions
  • Serial Debug: Detailed transaction logging with hex, decimal, and ASCII display
  • Transaction Counting: Tracks number of I2C communications
  • ACK Generation: Proper acknowledgment handling for reliable communication
  • Status Indication: Visual feedback through LED

Troubleshooting

No I2C Communication

  • Check pull-up resistors on SDA and SCL lines
  • Verify correct pin connections (PA10=SDA, PB6=SCL)
  • Ensure master and slave use same frequency (100kHz)
  • Check slave address matches in master code (0x50)

Incomplete Data Reception

  • Monitor ACK flag handling in main loop
  • Verify STOP condition detection
  • Check for proper flag clearing sequence

Serial Output Issues

  • Confirm USART2 connections (PA0=TX, PA1=RX)
  • Verify baud rate settings (9600 bps)
  • Check terminal configuration
  • serial_echo.rs - Basic serial communication
  • blinky.rs - Basic GPIO control
  • pwm.rs - PWM signal generation

Technical Details

  • MCU: PY32F003x4 series
  • I2C Speed: 100kHz (Standard mode)
  • Clock: 24MHz HSI
  • Addressing: 7-bit addressing mode
  • Communication: Receive-only slave implementation

ADC Examples

PWM Examples

Timer Examples

GPIO

USART/Serial Communication

The PY32F0xx family provides flexible USART (Universal Synchronous/Asynchronous Receiver/Transmitter) peripherals for serial communication.

Available USART Peripherals

DeviceUSART1USART2Notes
PY32F002ASingle USART
PY32F002BSingle USART
PY32F003Dual USART
PY32F030Dual USART

Pin Configurations

USART1 Pin Mappings

PinAFFunctionPackage Availability
PA1AF1USART1_RXAll packages
PA2AF1USART1_TXAll packages
PA9AF1USART1_TXTSSOP20+ only
PA10AF1USART1_RXTSSOP20+ only

USART2 Pin Mappings

PinAFFunctionPackage Availability
PA0AF9USART2_TXAll packages
PA1AF9USART2_RXAll packages
PA2AF4USART2_TXAll packages (alternative)
PA14AF1USART2_TXAll packages (alternative)

Basic USART Setup

Simple Configuration

#![allow(unused)]
fn main() {
use py32f0xx_hal::{
    pac,
    prelude::*,
    rcc::HSIFreq,
    serial::Serial,
};

// Get peripherals
let mut p = pac::Peripherals::take().unwrap();

// Configure clock
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)
    .sysclk(24.MHz())
    .freeze(&mut p.FLASH);

// Setup GPIO
let gpioa = p.GPIOA.split();

// Configure pins for USART2
let tx = gpioa.pa0.into_alternate_af9();
let rx = gpioa.pa1.into_alternate_af9();

// Create serial interface
let mut serial = Serial::usart2(
    p.USART2,
    (tx, rx),
    9600.bps(),
    rcc.clocks,
);
}

Advanced Configuration

#![allow(unused)]
fn main() {
use py32f0xx_hal::serial::{Config, Parity, StopBits};

// Custom serial configuration
let config = Config {
    baudrate: 115_200.bps(),
    parity: Parity::ParityNone,
    stopbits: StopBits::STOP1,
    // Additional config options...
};

let mut serial = Serial::usart1(
    p.USART1,
    (tx, rx),
    config,
    rcc.clocks,
);
}

Supported Baud Rates

The USART peripheral supports a wide range of baud rates, limited by the system clock:

Common Baud Rates (24MHz System Clock)

Baud RateTypical Use CaseError Rate
2400Low-speed sensors< 0.1%
4800Legacy devices< 0.1%
9600General purpose< 0.1%
19200Faster communication< 0.1%
38400High-speed sensors< 0.1%
57600Fast data transfer< 0.2%
115200Maximum practical< 0.5%

Calculating Baud Rate

#![allow(unused)]
fn main() {
// Baud rate = Clock / (16 * USARTDIV)
// For 24MHz clock and 9600 baud:
// 9600 = 24,000,000 / (16 * USARTDIV)
// USARTDIV = 156.25 ≈ 156

let serial = Serial::usart2(
    p.USART2,
    (tx, rx),
    9600.bps(),  // HAL calculates USARTDIV automatically
    rcc.clocks,
);
}

Reading and Writing Data

Basic I/O Operations

#![allow(unused)]
fn main() {
use embedded_hal_02::serial::{Read, Write};

// Write single character
serial.write(b'A').ok();

// Write string
use core::fmt::Write;
serial.write_str(\"Hello World!\\r\\n\").ok();

// Read single character
if let Ok(byte) = serial.read() {
    // Process received byte
    println!(\"Received: {}\", byte as char);
}
}

Buffered Operations

#![allow(unused)]
fn main() {
// Write multiple bytes
let message = b\"Hello PY32F0xx!\";
for &byte in message {
    serial.write(byte).ok();
}

// Read with timeout handling
use cortex_m::interrupt;

let mut buffer = [0u8; 64];
let mut index = 0;

loop {
    match serial.read() {
        Ok(byte) => {
            buffer[index] = byte;
            index += 1;
            
            if byte == b'\\n' || index >= buffer.len() {
                // Process complete message
                break;
            }
        },
        Err(nb::Error::WouldBlock) => {
            // No data available, continue
        },
        Err(nb::Error::Other(_)) => {
            // Handle error
            break;
        }
    }
}
}

Interrupt-Driven Communication

#![allow(unused)]
fn main() {
use py32f0xx_hal::{
    pac::interrupt,
    serial::{Event, Serial},
};

// Enable RX interrupt
serial.listen(Event::Rxne);

// In interrupt handler
#[interrupt]
fn USART2() {
    // Handle received data
    if let Ok(byte) = SERIAL.read() {
        // Process byte
    }
}
}

Error Handling

Common Errors

#![allow(unused)]
fn main() {
use py32f0xx_hal::serial::Error;

match serial.read() {
    Ok(byte) => {
        // Process byte
    },
    Err(nb::Error::WouldBlock) => {
        // No data available
    },
    Err(nb::Error::Other(Error::Overrun)) => {
        // Data overrun - clear error
        serial.clear_overrun_error();
    },
    Err(nb::Error::Other(Error::Noise)) => {
        // Noise detected on line
    },
    Err(nb::Error::Other(Error::Framing)) => {
        // Framing error - incorrect stop bit
    },
    Err(nb::Error::Other(Error::Parity)) => {
        // Parity error
    },
}
}

Error Recovery

#![allow(unused)]
fn main() {
// Clear all errors
fn clear_serial_errors(serial: &mut Serial<USART2>) {
    // Read status register to clear flags
    let _ = serial.clear_overrun_error();
    let _ = serial.clear_noise_error();
    let _ = serial.clear_framing_error();
    let _ = serial.clear_parity_error();
}
}

DMA Integration

For high-throughput applications, USART can be used with DMA:

#![allow(unused)]
fn main() {
use py32f0xx_hal::dma::{dma1, Transfer, W, R};

// Setup DMA for TX
let tx_channel = dma1.ch2;
let tx_transfer = Transfer::init_memory_to_peripheral(
    tx_channel,
    serial.tx(),
    tx_buffer,
    None,
);

// Start DMA transfer
tx_transfer.start(|serial_tx| {
    serial_tx.enable_dma_tx();
});

// Setup DMA for RX
let rx_channel = dma1.ch3;
let rx_transfer = Transfer::init_peripheral_to_memory(
    rx_channel,
    serial.rx(),
    rx_buffer,
    None,
);
}

Flow Control

Software Flow Control (XON/XOFF)

#![allow(unused)]
fn main() {
const XON: u8 = 0x11;   // Resume transmission
const XOFF: u8 = 0x13;  // Pause transmission

// Send flow control characters
serial.write(XOFF).ok(); // Pause sender
serial.write(XON).ok();  // Resume sender

// Handle received flow control
match serial.read() {
    Ok(XON) => {
        // Resume sending
        tx_enabled = true;
    },
    Ok(XOFF) => {
        // Pause sending
        tx_enabled = false;
    },
    Ok(byte) => {
        // Normal data
    },
    _ => {}
}
}

Hardware Flow Control (RTS/CTS)

#![allow(unused)]
fn main() {
// Configure RTS/CTS pins (if available on package)
let rts = gpioa.pa12.into_alternate_af1(); // USART1_RTS
let cts = gpioa.pa11.into_alternate_af1(); // USART1_CTS

// Enable hardware flow control
let mut serial = Serial::usart1(
    p.USART1,
    (tx, rx),
    config.rts(rts).cts(cts),
    rcc.clocks,
);
}

Power Management

Low Power Operation

#![allow(unused)]
fn main() {
// Disable USART when not needed
serial.disable();

// Re-enable when needed
serial.enable();

// Use lower baud rates for better power efficiency
let serial = Serial::usart2(
    p.USART2,
    (tx, rx),
    2400.bps(),  // Lower baud = lower power
    rcc.clocks,
);
}

Wake-up from Stop Mode

#![allow(unused)]
fn main() {
// Configure USART for wake-up
serial.enable_wakeup();
serial.set_wakeup_method(WakeupMethod::StartBit);

// Enter stop mode
cortex_m::asm::wfi();

// USART activity will wake the MCU
}

Practical Examples

Command Interface

#![allow(unused)]
fn main() {
struct CommandProcessor {
    buffer: [u8; 64],
    index: usize,
}

impl CommandProcessor {
    fn process_byte(&mut self, byte: u8, serial: &mut Serial<USART2>) {
        match byte {
            b'\\r' | b'\\n' => {
                // Process complete command
                let command = &self.buffer[..self.index];
                self.handle_command(command, serial);
                self.index = 0;
            },
            b => {
                if self.index < self.buffer.len() {
                    self.buffer[self.index] = b;
                    self.index += 1;
                }
            }
        }
    }
    
    fn handle_command(&self, cmd: &[u8], serial: &mut Serial<USART2>) {
        match cmd {
            b\"help\" => {
                serial.write_str(\"Available commands:\\r\\n\").ok();
                serial.write_str(\"  help - Show this help\\r\\n\").ok();
                serial.write_str(\"  status - Show status\\r\\n\").ok();
            },
            b\"status\" => {
                serial.write_str(\"System OK\\r\\n\").ok();
            },
            _ => {
                serial.write_str(\"Unknown command\\r\\n\").ok();
            }
        }
    }
}
}

Data Logging

#![allow(unused)]
fn main() {
use heapless::Vec;

struct DataLogger {
    buffer: Vec<u8, 256>,
}

impl DataLogger {
    fn log_measurement(&mut self, value: f32, serial: &mut Serial<USART2>) {
        // Format measurement
        use core::fmt::Write;
        let mut formatted = heapless::String::<32>::new();
        write!(formatted, \"{:.2},{}\\r\\n\", value, timestamp()).ok();
        
        // Send over serial
        serial.write_str(&formatted).ok();
        
        // Store in buffer if needed
        self.buffer.extend_from_slice(formatted.as_bytes()).ok();
    }
}
}

Debugging Serial Issues

Signal Verification

#![allow(unused)]
fn main() {
// Toggle TX pin to verify GPIO configuration
let mut tx_pin = gpioa.pa0.into_push_pull_output();

loop {
    tx_pin.set_high();
    delay.delay_ms(500);
    tx_pin.set_low(); 
    delay.delay_ms(500);
}
}

Loopback Testing

#![allow(unused)]
fn main() {
// Connect TX to RX externally for loopback test
let test_data = b\"ABCDEF123456\";

for &byte in test_data {
    serial.write(byte).ok();
    
    // Should receive same byte back
    if let Ok(received) = serial.read() {
        if received != byte {
            // Loopback failed
            panic!(\"Loopback test failed\");
        }
    }
}
}

Best Practices

  1. Always use appropriate baud rates for your application
  2. Handle errors gracefully - serial lines can be noisy
  3. Use DMA for high-throughput applications
  4. Implement flow control for reliable communication
  5. Test with oscilloscope for timing verification
  6. Use proven pin configurations from working examples
  7. Add timeout handling to prevent hanging
  8. Clear errors promptly to maintain communication

ADC

SPI

I2C

Timers

RTC

DMA

Multi-Protocol Programmer

Multi-Protocol Programmer

Resources

Resource Link
Wiki Development Boards Wiki
Documentation unit_multiprotocol_programmer_platform
Getting Started Initial Setup
Schematic & PCB Hardware Files
Firmware & SDK SDK & Firmware
Main Repository GitHub Repo

Firmware Required

This programmer requires specific firmware depending on the protocol:

  • AVR: USBasp & UPDI
  • ARM: CMSIS-DAP (SWD/JTAG)
  • CPLD: USB-Blaster (JTAG)

Load the correct .bin before use. Without it, the device won't function properly.

Overview

The Multi-Protocol Programmer is a USB tool based on the CH552 microcontroller. It supports flashing and debugging of:

  • AVR microcontrollers (ATmega, ATtiny, AVR-DA)
  • ARM Cortex-M devices (STM32, nRF52, SAM, etc.)
  • Intel/Altera MAX II CPLDs (EPM240, EPM570, etc.)

Features

  • USB Full-Speed (CDC/HID)
  • Voltage selector: 3.3V / 5V
  • SWD / JTAG / UPDI / USBasp support
  • Works with popular tools (avrdude, OpenOCD, Quartus, etc.)

PY32F0xx Configuration

The Multi-Protocol Programmer is fully compatible with PY32F0xx microcontrollers when using the CMSIS-DAP firmware.

Testing Status & Recommendations

Tested Devices ✅

  • PY32F003x4 - Fully verified with this programmer
  • PY32F003x8 - Fully verified with this programmer

For the following devices that are supported in code but need hardware verification:

  • PY32F030 series (all variants)
  • PY32F002A series
  • PY32F002B series

This programmer is the recommended solution for testing these untested device variants.

Setup for PY32F0xx

  1. Flash CMSIS-DAP Firmware:

    python3 tools/chprog.py firmware/cmsis_dap.bin
    
  2. Connect to PY32F0xx:

    Programmer    PY32F0xx
    ----------    --------
    SWDIO      ←→ PA13
    SWDCK      ←→ PA14  
    GND        ←→ GND
    VTG        ←→ VCC (3.3V/5V)
    RST        ←→ NRST (optional)
    
  3. Set Correct Voltage:

    • Use 3.3V for most PY32F0xx devices
    • Check your specific device voltage requirements

Using with PyOCD

The programmer works seamlessly with PyOCD for PY32F0xx development:

# List connected devices
pyocd list

# Flash PY32F003
pyocd flash firmware.bin --target py32f003xx4

# Start GDB server
pyocd gdbserver --target py32f003xx4

# Interactive debugging
pyocd commander --target py32f003xx4

Using with OpenOCD

OpenOCD can also be used with the CMSIS-DAP firmware:

# Flash firmware
openocd -f interface/cmsis-dap.cfg -f target/py32f0xx.cfg -c "program firmware.bin verify reset exit"

# Start GDB server
openocd -f interface/cmsis-dap.cfg -f target/py32f0xx.cfg

Makefile Integration

Update your project Makefile to use the Multi-Protocol Programmer:

# Use CMSIS-DAP interface
PROGRAMMER = cmsis-dap
PROGRAMMER_ARGS = --target py32f003xx4

flash: $(BUILD_DIR)/$(EXAMPLE).bin
	pyocd flash $(BUILD_DIR)/$(EXAMPLE).bin $(PROGRAMMER_ARGS)

debug: $(BUILD_DIR)/$(EXAMPLE).elf
	pyocd gdbserver $(PROGRAMMER_ARGS) &
	arm-none-eabi-gdb $(BUILD_DIR)/$(EXAMPLE).elf

Supported Protocols

FirmwareProtocolsTarget DevicesInterfaceTools
AVRUSBasp, UPDIATmega, ATtinyCDC/HIDavrdude, Arduino IDE
CMSIS-DAPSWD, JTAGSTM32, nRF52, PY32F0xxHID+CDCOpenOCD, PyOCD, Keil
CPLDUSB-BlasterEPM240, EPM570, MAX IIHIDQuartus Prime

Flashing Firmware

  1. Enter Bootloader Mode:

    • Hold BOOT, plug USB, release.
  2. Flash Firmware:

    python3 tools/chprog.py firmware/firmware_name.bin
    

    Or use WCHISPTool on Windows.

Install Requirements

# Linux (Debian/Ubuntu)
sudo apt install build-essential sdcc python3-pip git
pip3 install pyusb pyocd

# Add user to dialout group for device access
sudo usermod -a -G dialout $USER
# Log out and back in for changes to take effect

For Windows: Download SDCC, Python 3, and Git.

Troubleshooting

Common Issues

  • Device not recognized? ➤ Check firmware & USB drivers (use Zadig on Windows for CMSIS-DAP)

  • Programming error? ➤ Verify voltage level (3.3V/5V), connections & cable quality

  • Slow upload? ➤ Reduce SWD/JTAG frequency or use shorter cables

PY32F0xx Specific

  • PyOCD can't find device?

    # Check if programmer is detected
    pyocd list
    
    # Try different target specification
    pyocd flash firmware.bin --target py32f030xx4
    
  • SWD connection issues?

    • Verify SWDIO/SWDCK connections
    • Ensure stable power supply
    • Try lower SWD frequency: --frequency 1000000
  • Flashing fails?

    # Try mass erase first
    pyocd erase --chip --target py32f003xx4
    
    # Then flash
    pyocd flash firmware.bin --target py32f003xx4
    

Testing Contributions

Help expand device support! If you test py32f0xx-hal with untested devices using this programmer:

  1. Test procedure:

    • Flash basic examples (blinky, serial_echo)
    • Verify peripheral functionality
    • Document any issues or successes
  2. Report results:

    • Open GitHub issue with test results
    • Include device model, programmer setup, and example outcomes
    • Help us update the compatibility matrix

Firmware Selection Guide

For PY32F0xx development, use the CMSIS-DAP firmware:

Target FamilyRecommended FirmwareInterfaceTools
PY32F0xxCMSIS-DAPSWDPyOCD, OpenOCD
STM32F0xxCMSIS-DAPSWDPyOCD, OpenOCD
AVRUSBasp/UPDIISP/UPDIavrdude
MAX II CPLDUSB-BlasterJTAGQuartus

Performance Notes

SWD Frequency Settings

For reliable PY32F0xx programming:

  • High-speed: --frequency 10000000 (10MHz) - for short cables
  • Standard: --frequency 1000000 (1MHz) - recommended default
  • Low-speed: --frequency 400000 (400kHz) - for problematic connections

Cable Quality

  • Use short cables (< 15cm) for high-speed SWD
  • Twisted pair for SWDIO/SWDCK reduces noise
  • Good ground connection essential for reliable operation

License

  • Hardware: CC BY-SA 4.0
  • Firmware & Software: MIT License
  • Third-party components: see individual LICENSE files

OpenOCD Support

While PyOCD is the recommended and default programming interface for PY32F0xx development, OpenOCD is also supported for users who prefer it or have specific workflow requirements.

Files Location

OpenOCD configuration files are located in the tools/openocd/ directory:

  • openocd.cfg - OpenOCD configuration file for PY32F0xx microcontrollers
  • openocd_program.sh - Shell script for programming with OpenOCD
  • README.md - Documentation for OpenOCD setup

When to Use OpenOCD

Consider using OpenOCD if you:

  • Have existing OpenOCD-based workflows
  • Need specific OpenOCD features
  • Are integrating with tools that expect OpenOCD
  • Want to use OpenOCD-specific debugging features

Installation

Linux (Ubuntu/Debian)

sudo apt-get install openocd

macOS

brew install openocd

Windows

Download from the OpenOCD releases page.

Usage

Using the Configuration Files

  1. Navigate to the OpenOCD directory:

    cd tools/openocd/
    
  2. Run OpenOCD with the provided configuration:

    openocd -f openocd.cfg
    
  3. In another terminal, use GDB to load your program:

    arm-none-eabi-gdb target/thumbv6m-none-eabi/release/examples/blinky
    (gdb) target remote localhost:3333
    (gdb) load
    (gdb) continue
    

Using the Programming Script

The openocd_program.sh script provides a convenient way to program your device:

./tools/openocd/openocd_program.sh path/to/your/firmware.elf

Default PyOCD Workflow

For most users, we recommend sticking with the default PyOCD workflow:

# Install PyOCD (done automatically with make setup-venv)
pip install pyocd

# Use the simplified Makefile commands
make blinky           # Build example
make flash-blinky     # Build and flash

Troubleshooting

Connection Issues

If you're having connection problems with OpenOCD:

  1. Check your debugger connection
  2. Verify the correct configuration file is being used
  3. Make sure no other debugging session is active
  4. Try resetting your development board

Permission Issues (Linux)

You may need to add udev rules for your debugger:

sudo usermod -a -G dialout $USER
sudo udevadm control --reload-rules
sudo udevadm trigger

Log out and log back in for changes to take effect.

Configuration Details

The provided openocd.cfg is configured specifically for PY32F0xx microcontrollers. If you need to modify it for your specific setup, refer to the OpenOCD documentation for:

  • Interface configuration (ST-Link, J-Link, etc.)
  • Target-specific settings
  • Memory map customization

Switching Back to PyOCD

To switch back to the default PyOCD workflow:

make clean
make flash-blinky  # Uses PyOCD by default

The Makefile uses PyOCD by default, so no configuration changes are needed.

VS Code Configuration

This page details the VS Code configuration included in the project for optimal PY32F0xx development.

Required Extensions

Install these extensions for full functionality:

Core Development

code --install-extension rust-lang.rust-analyzer
code --install-extension ms-vscode.cpptools

Debugging

code --install-extension marus25.cortex-debug

Settings Configuration

The project includes .vscode/settings.json with optimized settings for embedded Rust development:

{
    "rust-analyzer.cargo.target": "thumbv6m-none-eabi",
    "rust-analyzer.checkOnSave.allTargets": false,
    "rust-analyzer.cargo.allFeatures": false,
    "rust-analyzer.cargo.features": ["py32f003xx4"],
    "rust-analyzer.check.allTargets": false,
    "files.associations": {
        "*.svd": "xml"
    },
    "cortex-debug.variableUseNaturalFormat": true,
    "cortex-debug.showRTOS": true
}

Settings Explanation

  • rust-analyzer.cargo.target: Sets the target architecture for analysis
  • checkOnSave.allTargets: Disables checking all targets (improves performance)
  • cargo.allFeatures: Disables all features analysis for better performance
  • cargo.features: Specifies default MCU features (PY32F003x4)
  • files.associations: Associates SVD files with XML syntax highlighting
  • cortex-debug.*: Optimizes debugging display options

Launch Configuration

The debugging configuration is in .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug PY32F0xx",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "pyocd",
            "cwd": "${workspaceRoot}",
            "executable": "target/thumbv6m-none-eabi/debug/examples/blinky",
            "device": "py32f003x4",
            "svdFile": "tools/Misc/SVD/py32f003xx.svd",
            "configFiles": ["tools/Misc/pyocd.yaml"],
            "targetId": "py32f003x4",
            "runToEntryPoint": "main",
            "showDevDebugOutput": "raw"
        }
    ]
}

Configuration Explanation

  • servertype: "pyocd": Uses PyOCD as debug server
  • device: "py32f003x4": Specifies target device
  • svdFile: Points to SVD file for peripheral register view
  • configFiles: Uses project PyOCD configuration
  • runToEntryPoint: Stops at main function
  • showDevDebugOutput: Shows detailed debug output

Different MCU Types

To use different MCU variants, update both files:

For PY32F003x6:

// .vscode/settings.json
"rust-analyzer.cargo.features": ["py32f003xx6"]

// .vscode/launch.json  
"device": "py32f003x6"

For PY32F030x6:

// .vscode/settings.json
"rust-analyzer.cargo.features": ["py32f030xx6"]

// .vscode/launch.json
"device": "py32f030x6"

Multiple Debug Configurations

You can add multiple configurations for different examples:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Blinky",
            "type": "cortex-debug",
            "executable": "target/thumbv6m-none-eabi/debug/examples/blinky",
            // ... other settings
        },
        {
            "name": "Debug Serial Echo", 
            "type": "cortex-debug",
            "executable": "target/thumbv6m-none-eabi/debug/examples/serial_echo",
            // ... other settings
        }
    ]
}

Workspace Recommendations

The project includes .vscode/extensions.json with recommended extensions:

{
    "recommendations": [
        "rust-lang.rust-analyzer",
        "ms-vscode.cpptools", 
        "marus25.cortex-debug"
    ]
}

Tasks Configuration

You can also add .vscode/tasks.json for build tasks:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build Blinky",
            "type": "shell",
            "command": "make",
            "args": ["blinky"],
            "group": "build",
            "problemMatcher": "$rustc"
        },
        {
            "label": "Flash Blinky",
            "type": "shell", 
            "command": "make",
            "args": ["flash-blinky"],
            "group": "build",
            "dependsOn": "Build Blinky"
        },
        {
            "label": "Debug Build Blinky",
            "type": "shell",
            "command": "make", 
            "args": ["debug-blinky"],
            "group": "build"
        }
    ]
}

Troubleshooting

Rust Analyzer Issues

If Rust Analyzer is slow or not working:

  1. Restart Rust Analyzer: Ctrl+Shift+P → "rust-analyzer: Restart server"
  2. Clear cache: Delete target/ directory and rebuild
  3. Check target: Ensure thumbv6m-none-eabi is installed

Debug Issues

If debugging doesn't work:

  1. Check extensions: Ensure Cortex-Debug is installed
  2. Build debug: Run make debug-blinky first
  3. Check hardware: Verify SWD connection
  4. PyOCD test: Run make -f rust.mk debug to test connection

Performance Issues

If VS Code is slow:

  1. Disable unused extensions in workspace
  2. Use specific features: Don't enable all MCU features
  3. Exclude build dirs: Add to .gitignore and VS Code exclude

Integration with Make Commands

The VS Code configuration works seamlessly with Make commands:

# Build for analysis
make blinky

# Build for debugging  
make debug-blinky

# Flash and test
make flash-blinky

Advanced Configuration

Custom Keybindings

Add to your keybindings.json:

[
    {
        "key": "f6",
        "command": "workbench.action.tasks.runTask",
        "args": "Build Blinky"
    },
    {
        "key": "f7", 
        "command": "workbench.action.tasks.runTask",
        "args": "Flash Blinky"
    }
]

Integrated Terminal

Set default shell for embedded development:

{
    "terminal.integrated.defaultProfile.linux": "bash",
    "terminal.integrated.cwd": "${workspaceFolder}"
}

This configuration provides a complete, optimized VS Code experience for PY32F0xx development!

VS Code Debugging Setup

This guide shows how to set up debugging for PY32F0xx development in VS Code using the Cortex-Debug extension.

Prerequisites

  1. Cortex-Debug Extension - Install from VS Code marketplace:

    code --install-extension marus25.cortex-debug
    
  2. PyOCD installed and configured - Should be done automatically by setup script:

    ./scripts/setup.sh
    

Launch Configuration

The project includes a pre-configured launch.json for debugging:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug PY32F0xx",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "pyocd",
            "cwd": "${workspaceRoot}",
            "executable": "target/thumbv6m-none-eabi/debug/examples/blinky",
            "device": "py32f003x4",
            "svdFile": "tools/Misc/SVD/py32f003xx.svd",
            "configFiles": [
                "tools/Misc/pyocd.yaml"
            ],
            "targetId": "py32f003x4",
            "runToEntryPoint": "main",
            "showDevDebugOutput": "raw"
        }
    ]
}

How to Debug

1. Build Debug Version

First, build your example in debug mode:

# Simple way - build debug version of blinky
make debug-blinky

# Build debug version of serial_echo
make debug-serial_echo

# Or for specific MCU (traditional way)
make debug-build EXAMPLE=blinky MCU_TYPE=PY32F003x4

2. Start Debugging

  1. Set Breakpoints - Click in the gutter next to line numbers
  2. Press F5 or go to Run > Start Debugging
  3. Select "Debug PY32F0xx" configuration

3. Debug Controls

  • F5 - Continue
  • F10 - Step Over
  • F11 - Step Into
  • Shift+F11 - Step Out
  • Ctrl+Shift+F5 - Restart
  • Shift+F5 - Stop

Debugging Different Examples

To debug different examples, update the executable path in launch.json:

"executable": "target/thumbv6m-none-eabi/debug/examples/YOUR_EXAMPLE"

Available examples:

  • blinky
  • serial_echo
  • serial_adc
  • adc_values
  • pwm
  • And more...

Multiple Configurations

You can add multiple debug configurations for different examples:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Blinky",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "pyocd",
            "executable": "target/thumbv6m-none-eabi/debug/examples/blinky",
            "device": "py32f003x4",
            "svdFile": "tools/Misc/SVD/py32f003xx.svd",
            "configFiles": ["tools/Misc/pyocd.yaml"]
        },
        {
            "name": "Debug Serial Echo",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "pyocd",
            "executable": "target/thumbv6m-none-eabi/debug/examples/serial_echo",
            "device": "py32f003x4",
            "svdFile": "tools/Misc/SVD/py32f003xx.svd",
            "configFiles": ["tools/Misc/pyocd.yaml"]
        }
    ]
}

Different MCU Types

For different MCU variants, update the device field:

  • PY32F003x4: "device": "py32f003x4"
  • PY32F003x6: "device": "py32f003x6"
  • PY32F003x8: "device": "py32f003x8"
  • PY32F030x6: "device": "py32f030x6"
  • PY32F030x8: "device": "py32f030x8"

Troubleshooting

"Could not connect to target" Error

  1. Check hardware connection:

    • SWD pins connected correctly
    • Device powered
    • Debugger connected
  2. Verify PyOCD can see device:

    cd tools/Misc
    ../../venv/bin/pyocd list
    
  3. Check device matches configuration:

    ../../venv/bin/pyocd info --config pyocd.yaml
    

"Executable not found" Error

Make sure you've built the debug version:

# Build debug version of your example
make debug-blinky
make debug-serial_echo
make debug-pwm

# Or specific example
make debug-build EXAMPLE=your_example

Permission Issues (Linux)

Add user to dialout group:

sudo usermod -a -G dialout $USER
# Log out and back in

SVD File Issues

If peripheral registers don't show properly, verify the SVD file path:

  • File should be at: tools/Misc/SVD/py32f003xx.svd
  • Check that the SVD file matches your MCU type

Debug Output

The configuration includes "showDevDebugOutput": "raw" which shows:

  • PyOCD connection messages
  • Memory read/write operations
  • Reset and halt operations

This helps diagnose connection issues.

Advanced Features

Memory View

  • View > Command PaletteCortex-Debug: View Memory
  • Enter memory address (e.g., 0x20000000 for RAM)

Peripheral Registers

  • Available in Debug sidebar when debugging
  • Shows all MCU peripherals with current values
  • Requires correct SVD file

Disassembly View

  • View > Command PaletteCortex-Debug: View Disassembly
  • Shows assembly code with source correlation

Tips

  1. Use debug build - Optimized builds are harder to debug
  2. Set meaningful breakpoints - Avoid breakpoints in tight loops
  3. Check variables - Hover over variables to see values
  4. Use watch expressions - Add variables to watch panel
  5. Single-step carefully - Some operations might affect timing-sensitive code

Serial Communication Troubleshooting

This guide helps you diagnose and fix common serial communication issues with PY32F0xx microcontrollers.

Quick Diagnostic Checklist

Before diving into detailed troubleshooting, check these common issues:

  • Wiring: TX↔RX, RX↔TX, GND↔GND
  • Baud Rate: 9600 bps on both sides
  • Power: 3.3V to microcontroller
  • Programming: Device successfully flashed
  • Serial Adapter: Working and recognized by system

Common Problems and Solutions

1. No Serial Output at All

Symptoms

  • No characters appear in serial terminal
  • LED may or may not blink (depending on example)
  • Terminal connects but shows no data

Diagnostic Steps

Check Hardware Connections

Verify wiring:
PY32F0xx     Serial Adapter
PA0 (TX)  →  RX
PA1 (RX)  ←  TX  
GND       →  GND

Verify Serial Settings

  • Baud rate: 9600
  • Data bits: 8
  • Parity: None
  • Stop bits: 1
  • Flow control: None

Test Serial Adapter

# Test adapter loopback (connect TX to RX on adapter)
echo \"test\" > /dev/ttyACM0
cat /dev/ttyACM0  # Should see \"test\"

Solutions

  1. Fix Wiring Issues

    • Double-check TX/RX connections (they cross over)
    • Ensure good connections (no loose wires)
    • Verify GND connection
  2. Correct Pin Configuration

    #![allow(unused)]
    fn main() {
    // Ensure you're using the right alternate function
    let tx = gpioa.pa0.into_alternate_af9(); // USART2 TX
    let rx = gpioa.pa1.into_alternate_af9(); // USART2 RX
    }
  3. Try Different Baud Rate

    #![allow(unused)]
    fn main() {
    // Try lower baud rate
    let mut serial = p.USART2.serial((tx, rx), 9_600.bps(), &rcc.clocks);
    }

2. Garbled/Corrupted Characters

Symptoms

  • Characters appear but are wrong/corrupted
  • Random symbols instead of expected text
  • Intermittent correct characters

Causes and Solutions

Baud Rate Mismatch

# Ensure both sides use same baud rate
# PY32F0xx side (in code):
let mut serial = p.USART2.serial((tx, rx), 9_600.bps(), &rcc.clocks);

# Terminal side:
screen /dev/ttyACM0 9600  # Must match!

Clock Configuration Issues

#![allow(unused)]
fn main() {
// Use proven 24MHz HSI configuration
let rcc = p.RCC
    .configure()
    .hsi(HSIFreq::Freq24mhz)
    .sysclk(24.MHz())
    .freeze(&mut p.FLASH);
}

Power Supply Problems

  • Ensure stable 3.3V power supply
  • Check for voltage drops under load
  • Add decoupling capacitors (100nF)

3. Serial Works But Intermittently

Symptoms

  • Sometimes works, sometimes doesn't
  • Works after reset but fails later
  • Occasional correct characters

Solutions

  1. Check Power Stability

    # Measure supply voltage with multimeter
    # Should be stable 3.3V ± 0.1V
    
  2. Verify Clock Stability

    #![allow(unused)]
    fn main() {
    // Use internal HSI (more stable than HSE)
    let rcc = p.RCC
        .configure()  
        .hsi(HSIFreq::Freq24mhz)  // Internal oscillator
        .sysclk(24.MHz())
        .freeze(&mut p.FLASH);
    }
  3. Add Error Handling

    #![allow(unused)]
    fn main() {
    // Handle serial errors gracefully
    match serial.read() {
        Ok(byte) => {
            // Process byte
            serial.write(byte).ok();
        },
        Err(_) => {
            // Handle error - maybe reset USART
        }
    }
    }

4. Device Not Responding

Symptoms

  • Serial terminal connects but no startup message
  • No response to any input
  • LED may still blink

Diagnostic Steps

  1. Verify Programming

    # Reflash the device
    make flash EXAMPLE=serial_echo MCU_TYPE=py32f003xx4
    
    # Check flash was successful (no errors in output)
    
  2. Test with Different Example

    # Flash and test blinky first
    make flash EXAMPLE=blinky MCU_TYPE=py32f003xx4
    # LED should blink - confirms basic functionality
    
  3. Check Reset

    # Try hardware reset
    # Briefly pull NRST low or cycle power
    

Solutions

  1. Reflash Firmware
  2. Check SWD Programming Connections
  3. Verify Device is Running (check LED blinks)

5. Wrong USART Configuration

Common USART/Pin Combinations

For PY32F003I DFN8:

#![allow(unused)]
fn main() {
// Working configuration (tested):
// USART2 with AF9
let tx = gpioa.pa0.into_alternate_af9(); // Pin 3 → TX
let rx = gpioa.pa1.into_alternate_af9(); // Pin 6 → RX
let mut serial = p.USART2.serial((tx, rx), 9_600.bps(), &rcc.clocks);
}

Alternative USART1 configuration:

#![allow(unused)]
fn main() {
// USART1 with AF1 (different pins)
let tx = gpioa.pa9.into_alternate_af1();  
let rx = gpioa.pa10.into_alternate_af1();
let mut serial = p.USART1.serial((tx, rx), 9_600.bps(), &rcc.clocks);
}

Advanced Diagnostics

Logic Analyzer Testing

If you have a logic analyzer:

  1. Capture TX Line

    • Set trigger on PA0 (TX)
    • Look for correct 9600 baud timing
    • Verify start bit, data bits, stop bit
  2. Check Clock Signals

    • Verify system clock is 24MHz
    • Check USART clock enabling

Oscilloscope Testing

  1. Measure TX Signal

    • Should be 3.3V idle (high)
    • Should go to 0V for start bits
    • Timing: ~104µs per bit at 9600 baud
  2. Check Power Rails

    • Stable 3.3V on VDD
    • No significant ripple or noise

Testing Tools and Scripts

Python Test Script

#!/usr/bin/env python3
import serial
import time
import sys

def test_serial(port='/dev/ttyACM0', baud=9600):
    try:
        ser = serial.Serial(port, baud, timeout=1)
        print(f\"Connected to {port} at {baud} baud\")
        
        # Test echo
        test_msg = \"Hello PY32F0xx!\"
        ser.write(test_msg.encode())
        time.sleep(0.1)
        
        response = ser.read(len(test_msg))
        print(f\"Sent: '{test_msg}'\")
        print(f\"Received: '{response.decode()}'\")
        
        if response.decode() == test_msg:
            print(\"✅ Echo test PASSED\")
        else:
            print(\"❌ Echo test FAILED\")
            
        ser.close()
        
    except Exception as e:
        print(f\"Error: {e}\")

if __name__ == \"__main__\":
    port = sys.argv[1] if len(sys.argv) > 1 else '/dev/ttyACM0'
    test_serial(port)

Manual Terminal Testing

# Test with different terminals
screen /dev/ttyACM0 9600
# or
minicom -D /dev/ttyACM0 -b 9600  
# or
picocom -b 9600 /dev/ttyACM0

Device-Specific Issues

PY32F003I DFN8

Pin Limitations:

  • Only 8 pins available
  • PA0/PA1 are best choice for USART2
  • PB5 available for debug LED

Known Working Configuration:

#![allow(unused)]
fn main() {
// Verified working on real hardware
let tx = gpioa.pa0.into_alternate_af9();
let rx = gpioa.pa1.into_alternate_af9(); 
let mut serial = p.USART2.serial((tx, rx), 9_600.bps(), &rcc.clocks);
}

PY32F030 TSSOP20

More Pin Options:

  • Multiple USART pin combinations available
  • Can use USART1 or USART2
  • More GPIO pins for debugging

Getting Help

If none of these solutions work:

  1. Check Examples Documentation for working code
  2. Review Hardware Setup guide
  3. Compare with Working Configuration
  4. Open GitHub Issue with:
    • Hardware details (device, package, programmer)
    • Code that's not working
    • Error messages
    • What you've already tried

Prevention Tips

To avoid serial communication issues:

  1. Start with working examples before modifying
  2. Use proven clock configurations (24MHz HSI)
  3. Verify hardware before coding (power, connections)
  4. Test serial adapter separately before connecting MCU
  5. Use consistent naming for TX/RX pins to avoid confusion
  6. Add error handling in production code
  7. Document your pin choices for future reference

GPIO Configuration

Debug LED Issues

Build Issues

Verification Guide

Pin Mapping

Memory Map

Clock Tree

Peripheral Support Matrix

How to Contribute

We welcome contributions to the PY32F0xx HAL project! Whether you're fixing bugs, adding features, improving documentation, or testing on new hardware, your help is appreciated.

Ways to Contribute

Code Contributions

  • Bug fixes - Fix issues you encounter
  • New features - Add support for new peripherals or devices
  • Examples - Create examples for common use cases
  • Optimizations - Improve performance or reduce code size

Documentation

  • Fix typos - Correct spelling and grammar errors
  • Add examples - Document working code patterns
  • Improve clarity - Make instructions easier to follow
  • Translate content - Help with internationalization

Testing and Validation

  • Test on new hardware - Verify HAL works on different devices
  • Report bugs - Document issues you encounter
  • Validate examples - Confirm examples work as expected
  • Performance testing - Benchmark and optimize code

Getting Started

1. Set Up Development Environment

Follow our Development Environment Guide to set up your tools.

2. Fork and Clone

# Fork the repository on GitHub
# Then clone your fork
git clone https://github.com/YOUR_USERNAME/py32f0xx-hal.git
cd py32f0xx-hal

# Add upstream remote
git remote add upstream https://github.com/UNIT-Electronics-MX/py32f0xx-hal.git

3. Create a Feature Branch

# Create and switch to a new branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/issue-description

Development Guidelines

Code Style

  • Follow Rust conventions - Use cargo fmt and cargo clippy
  • Document public APIs - Add doc comments to public functions
  • Include examples - Show how to use new features
  • Test thoroughly - Verify code works on real hardware

Commit Messages

Use clear, descriptive commit messages:

type: brief description

Optional longer description explaining the change.

Fixes #123

Types:

  • feat: - New features
  • fix: - Bug fixes
  • docs: - Documentation changes
  • style: - Code formatting
  • refactor: - Code restructuring
  • test: - Test additions or changes
  • chore: - Maintenance tasks

Testing

  • Test on real hardware - Ensure changes work on actual devices
  • Include test cases - Add tests for new functionality
  • Verify examples - Make sure all examples still compile and work
  • Check documentation - Verify docs are accurate and complete

Submitting Changes

1. Prepare Your Changes

# Format code
cargo fmt

# Run clippy for linting
cargo clippy --all-targets --all-features

# Build all examples
make build-all

# Test documentation
cd docs && mdbook test

2. Commit and Push

# Stage changes
git add .

# Commit with descriptive message
git commit -m \"feat: add USART interrupt support\"

# Push to your fork
git push origin feature/your-feature-name

3. Create Pull Request

  1. Go to GitHub and create a pull request
  2. Describe your changes clearly in the PR description
  3. Reference issues if your PR fixes them
  4. Include testing details - what hardware you tested on

Pull Request Template

## Description
Brief description of what this PR does.

## Changes
- List of changes made
- New features added
- Bugs fixed

## Testing
- [ ] Tested on PY32F003I DFN8
- [ ] Tested on PY32F030 TSSOP20
- [ ] All examples compile
- [ ] Documentation updated

## Related Issues
Fixes #123

Areas Needing Help

High Priority

  • Device support expansion - Test on more PY32F0xx variants
  • Peripheral implementations - Complete I2C, SPI, DMA drivers
  • Example collection - More real-world examples
  • Documentation improvements - Clearer getting-started guides

Medium Priority

  • Performance optimizations - Reduce code size and improve speed
  • Power management - Low-power mode support
  • Bootloader support - Custom bootloader implementations
  • Testing framework - Automated hardware-in-loop testing

Good First Issues

  • Fix documentation typos - Easy way to get started
  • Add code comments - Improve code readability
  • Create simple examples - Basic peripheral usage
  • Update README files - Keep information current

Development Best Practices

Hardware Testing

When testing changes:

  1. Test on multiple devices if possible
  2. Use different packages (DFN8, TSSOP20, etc.)
  3. Verify with oscilloscope for timing-critical changes
  4. Document test setup in PR description

Code Review Process

  1. Self-review first - Check your own code carefully
  2. Address feedback promptly - Respond to review comments
  3. Update tests - Modify tests if needed
  4. Squash commits - Clean up commit history if requested

Documentation Standards

  • Include working examples for new features
  • Update CHANGELOG.md for significant changes
  • Add troubleshooting info for complex features
  • Keep style consistent with existing docs

Community Guidelines

Be Respectful

  • Welcome newcomers - Everyone was a beginner once
  • Give constructive feedback - Focus on the code, not the person
  • Be patient - Not everyone has the same experience level

Be Collaborative

  • Share knowledge - Help others learn
  • Ask questions - Don't hesitate to seek clarification
  • Offer help - Assist with testing, documentation, or code review

Getting Help

Need help with contributing?

Communication Channels

  • GitHub Issues - For bugs and feature requests
  • GitHub Discussions - For questions and general discussion
  • PR Comments - For code-specific questions

Resources

Recognition

Contributors are recognized in:

  • CHANGELOG.md - Credits for significant changes
  • README.md - Hall of fame for major contributors
  • GitHub Contributors - Automatic recognition

By contributing, you agree that your contributions will be licensed under the same license as the project (MIT/Apache 2.0).


Thank you for contributing to PY32F0xx HAL! Every contribution, no matter how small, helps make embedded Rust development better for everyone.

Development Guidelines

Testing

Changelog

All notable changes to the PY32F0xx HAL project are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • Complete mdBook documentation system
  • Comprehensive serial communication examples
  • English translation of all documentation
  • Enhanced README with working examples
  • Detailed troubleshooting guides
  • Device-specific configuration guides

Changed

  • Improved project structure and organization
  • Enhanced example code with better comments
  • Standardized clock configuration across examples

Fixed

  • USART2 configuration for DFN8 package
  • Serial communication reliability issues
  • Documentation inconsistencies

[0.4.0] - Previous Release

Added

  • Initial serial communication support
  • Basic peripheral drivers (GPIO, USART, ADC, SPI, Timers)
  • Support for PY32F002A, PY32F002B, PY32F003, PY32F030 families
  • Examples for common use cases
  • DMA support for F030/F003 series

Changed

  • Updated to embedded-hal 1.0
  • Improved error handling
  • Enhanced GPIO pin configuration

Fixed

  • Clock configuration issues
  • Peripheral initialization problems

[0.3.0] - Earlier Release

Added

  • Multi-device support
  • Enhanced GPIO functionality
  • Timer and PWM support
  • RTC peripheral driver

Changed

  • Refactored peripheral access patterns
  • Improved documentation

Fixed

  • Various peripheral configuration issues

[0.2.0] - Early Release

Added

  • Basic HAL structure
  • GPIO support
  • Initial documentation

Changed

  • Project organization

Fixed

  • Initial bug fixes

[0.1.0] - Initial Release

Added

  • Initial PY32F0xx HAL implementation
  • Basic peripheral support
  • Project foundation