Files
zynq7000-rs/firmware/examples/zedboard/src/bin/oled.rs
T
muellerr d30273aa0c
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
bump aarch32 deps
2026-05-08 12:53:31 +02:00

298 lines
9.9 KiB
Rust

#![no_std]
#![no_main]
use aarch32_cpu::asm::nop;
use core::panic::PanicInfo;
use dummy_pin::DummyPin;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_time::{Delay, Duration, Ticker};
use embedded_graphics::{Drawable as _, geometry::Point};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs as _;
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use embedded_io::Write;
use log::{error, info};
use ssd1306::{Ssd1306, prelude::*};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode, clocks, gic, gpio, gtc, spi,
time::Hertz,
uart::{self, TxAsync},
};
use embedded_graphics::image::Image;
use tinybmp::Bmp;
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
/// Entry point which calls the embassy main method.
#[zynq7000_rt::entry]
fn entry_point() -> ! {
main();
}
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
// the CPU 1x clock which is around 110 MHz.
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
assert!(
clocks.io_clocks().spi_clk().to_raw()
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
"SPI reference clock must be larger than CPU 1x clock"
);
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio_for_uart_1(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
uart.flush().unwrap();
// Safety: We are not multi-threaded yet.
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace)
.expect("Failed to initialize async logger");
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
info!(
"SPI reference clock speed: {:?}",
clocks.io_clocks().spi_clk()
);
spawner.spawn(logger_task(uart, log_reader).unwrap());
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
let mut reset_pin =
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
let mut oled_vdd_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
let mut oled_vbat_switch =
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
Delay.delay_ms(100).await;
let spi = spi::Spi::new_for_emio(
periphs.spi_0,
spi::Config::calculate_for_io_clock(
Hertz::MHz(8),
clocks.io_clocks(),
embedded_hal::spi::MODE_0,
spi::SlaveSelectConfig::AutoCsManualStart,
),
)
.expect("Failed to initialize SPI");
let exclusive_device = ExclusiveDevice::new(spi, DummyPin::new_high(), NoDelay)
.expect("Failed to create exclusive SPI device");
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
let mut ssd1306 = Ssd1306::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
oled_vdd_switch.set_low();
oled_vbat_switch.set_low();
Delay.delay_ms(100).await;
ssd1306.reset(&mut reset_pin, &mut embassy_time::Delay {});
let mut display = ssd1306.into_buffered_graphics_mode();
display.init().unwrap();
// Include the BMP file data.
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
// Parse the BMP file.
let bmp_rust = Bmp::from_slice(rust_logo_data).unwrap();
let bmp_ferris = Bmp::from_slice(ferris_data).unwrap();
// Draw the image with the top left corner at (10, 20) by wrapping it in
// an embedded-graphics `Image`.
Image::new(&bmp_rust, Point::new(0, 0))
.draw(&mut display)
.unwrap();
Image::new(&bmp_ferris, Point::new(32, 0))
.draw(&mut display)
.unwrap();
display.flush().unwrap();
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
loop {
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
.draw(&mut display)
.unwrap();
display.flush().unwrap();
ferris.step();
ticker.next().await;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Right,
Left,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FerrisMovement {
pub pos: u16,
pub dir: Direction,
pub left_threshold: u16,
pub right_threshold: u16,
}
impl FerrisMovement {
/// Creates a new movement controller.
/// Assumes: left_threshold <= right_threshold and pos is within that range.
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
Self {
pos,
dir,
left_threshold,
right_threshold,
}
}
/// Move one tick and "bounce" between thresholds.
pub fn step(&mut self) {
match self.dir {
Direction::Right => {
if self.pos >= self.right_threshold {
self.dir = Direction::Left; // flip at right boundary
} else {
self.pos += 1;
}
}
Direction::Left => {
if self.pos <= self.left_threshold {
self.dir = Direction::Right; // flip at left boundary
} else {
self.pos -= 1;
}
}
}
}
}
#[embassy_executor::task]
pub async fn logger_task(
uart: uart::Uart,
reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
) -> ! {
let (tx, _) = uart.split();
let mut tx_async = TxAsync::new(tx);
let mut log_buf: [u8; 2048] = [0; 2048];
loop {
let read_bytes = reader.read(&mut log_buf).await;
if read_bytes > 0 {
tx_async.write(&log_buf[0..read_bytes]).unwrap().await;
}
}
}
#[embassy_executor::task]
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
let mut ticker = Ticker::every(Duration::from_millis(200));
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await;
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(spi_interrupt) => match spi_interrupt {
gic::SpiInterrupt::Uart1 => {
zynq7000_hal::uart::tx_async::on_interrupt_tx(uart::UartId::Uart1);
}
_ => {
log::warn!("Unhandled SPI interrupt: {:?}", spi_interrupt);
}
},
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}