OLED example #74

Merged
muellerr merged 1 commits from oled-driver into main 2026-05-07 20:05:00 +02:00
22 changed files with 689 additions and 160 deletions
+4
View File
@@ -4,6 +4,10 @@ Zynq 7000 Bare-Metal Rust Support
This crate collection provides support to write bare-metal Rust applications for the AMD Zynq 7000
family of SoCs.
<p align="center">
<img src="./ferris-zedboard.jpeg" alt="Ferris on the Zedboard" width="400" />
</p>
# List of crates
This project contains the following crates:
Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

+5
View File
@@ -27,9 +27,14 @@ critical-section = "1"
static_cell = "2"
embedded-alloc = "0.7"
embedded-hal = "1"
embedded-hal-bus = "0.3"
embedded-hal-async = "1"
dummy-pin = "1"
fugit = "0.3"
embedded-graphics = "0.8"
log = "0.4"
ssd1306 = { version = "0.10" }
tinybmp = "0.7"
rand = { version = "0.10", default-features = false }
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
version="1.1"
width="0.16in" height="0.106667in"
viewBox="0 0 48 32">
<defs>
</defs>
<image id="raster0"
x="0"
y="0"
width="48"
height="32"
opacity="1.000000"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAgCAQAAAD+3TOXAAAHDmVYSWZJSSoACAAAAAAADgAAAAkA/gAEAAEAAAABAAAAAAEEAAEAAAAAAQAAAQEEAAEAAACqAAAAAgEDAAMAAACAAAAAAwEDAAEAAAAGAAAABgEDAAEAAAAGAAAAFQEDAAEAAAADAAAAAQIEAAEAAACGAAAAAgIEAAEAAACIBgAAAAAAAAgACAAIAP/Y/+AAEEpGSUYAAQEAAAEAAQAA/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAqgEAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+f6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr6/+CX/ACSHQv8At4/9KJK+QK+v/gl/ySHQv+3j/wBKJKAPnzxr8Jte8B6NDqmqXemzQS3C26rayOzBirNk7kUYwh7+lcHX0/8AtHf8k80//sKx/wDoqWvmCgAroPAn/JQ/DX/YVtf/AEatc/XQeBP+Sh+Gv+wra/8Ao1aAPqP4s+CtS8eeFbXS9LntIZ4r1LhmunZVKhHXA2qxzlx29a+VPFPhu88I+I7vQ7+SCS6tdm94GJQ7kVxgkA9GHavuevkD42/8le13/t3/APSeOgDz+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+g/hL8WdB0rw74f8Hz2mpNqD3BtxIkaGLdLOxU5Lg4+cZ49etfPlWLC+uNM1G2v7OTy7q1lSaF9oO11IKnB4OCB1oA+968j+J3xZ0HSofEXg+e01JtQeye3EiRoYt0sOVOS4OPnGePXrXjn/AAu34h/9DD/5JW//AMbrj9b1vUfEesT6tq1x9ovp9vmS7FTdtUKOFAA4AHAoAz6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9mZmF3EAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhhJREFUSMft1T1I1WEUBvDfVQn1hoHoDQlMqCSlaLAiCKMhbIpqKLCtTQKJoLWh76GgpQ+isKWMgoiiocgpKoKEoCHCoHJJ1GjRAvu4p8GPrt57Ta/W5HmW9/w553ne9z3nf14WbJ6tXMu/or7vPM76aoVavbbMH3W/NtXSwnU/hJd6hA5LPXZ0btQbJNQLYVBMQdqgcBc1agoVCD1uZ1FnYtAt313+O9WiPAIzw8UpeUnnbEZy1C1WpskzqzJCLvnozowF3nnkm4ax3OU+CJU4IQU3bFMuDGuaEDgyY/I/GN1vwhshUKbf3hJb7TOiy4CUbgmstER61vXqsxpvbZo4yXEpQZuQtt4LIdTjaQG7H0erPWOrDmFENc1C6NUlhEYcKJj+s3IpPyf8U6NHuTDxoQ8NDhUs8FqzSmfGvIdKxu+vzZAQ2rXnS45pvEkYViuE04oyS1TlldAs6WM+gcjrTcJJXcL27C6o0yvtis45CaQ9Fw7mbrRlHsyhf/7gZiZpccZ6SNKOeZi+a5R6kutPapEWwq95OMW+cdLMSo9IgGO+ueTLdNMvhw1M8u7JOz2HVQuh1G7XsnsqR3n7ddqpGKFp6g4SWRKw2LAKQ6DGOiusVaF1SuxVfd7q1pORW+mT0izWHLdQ53ABz1OxEMr/HhiSNs6SvkSjIu9zFyiXRCG2KzuvKGdgwn4VBQhU5eHLI7Jg/8d+A9OUD/TqXTt5AAAAAElFTkSuQmCCAA==" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -57,12 +57,17 @@ async fn main(spawner: Spawner) -> ! {
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// SPI reference clock must be larger than the CPU 1x clock.
let spi_ref_clk_div = spi::calculate_largest_allowed_spi_ref_clk_divisor(&clocks)
.unwrap()
.value()
- 1;
spi::configure_spi_ref_clk(&mut clocks, arbitrary_int::u6::new(spi_ref_clk_div as u8));
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().raw()
> (clocks.arm_clocks().cpu_1x_clk().raw() as f32 * 1.2) as u32,
"SPI reference clock must be larger than CPU 1x clock"
);
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
@@ -99,23 +104,21 @@ async fn main(spawner: Spawner) -> ! {
if DEBUG_SPI_CLK_CONFIG {
info!(
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}",
clocks.arm_clocks().cpu_1x_clk(),
clocks.io_clocks().ref_clk(),
clocks.io_clocks().spi_clk(),
spi_ref_clk_div
);
}
let mut spi = spi::Spi::new_one_hw_cs(
dp.spi_1,
clocks.io_clocks(),
spi::Config::new(
// 10 MHz maximum rating of the sensor.
zynq7000::spi::BaudDivSel::By64,
//l3gd20::MODE,
embedded_hal::spi::MODE_3,
spi::SlaveSelectConfig::AutoWithAutoStart,
spi::SlaveSelectConfig::AutoCsAutoStart,
),
(
gpio_pins.mio.mio12,
@@ -125,10 +128,13 @@ async fn main(spawner: Spawner) -> ! {
gpio_pins.mio.mio13,
)
.unwrap();
let sclk = Hertz::from_raw(
clocks.io_clocks().spi_clk().raw() / zynq7000::spi::BaudDivSel::By64.div_value() as u32,
);
let mod_id = spi.regs().read_mod_id();
assert_eq!(mod_id, spi::MODULE_ID);
assert!(spi.sclk() <= Hertz::from_raw(10_000_000));
let min_delay = (spi.sclk().raw() * 5) / 1_000_000_000;
assert!(sclk <= Hertz::from_raw(10_000_000));
let min_delay = (sclk.raw() * 5) / 1_000_000_000;
spi.inner().configure_delays(
DelayControl::builder()
.with_inter_word_cs_deassert(0)
+297
View File
@@ -0,0 +1,297 @@
#![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().raw()
> (clocks.arm_clocks().cpu_1x_clk().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 {}
}
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn on_interrupt(peripheral: SpiId) {
let enabled_irqs = spi.read_enabled_interrupts();
// Prevent spurious interrupts from messing with out logic here.
spi.disable_interrupts();
let interrupt_status = spi.read_isr();
let interrupt_status = spi.read_interrupt_status();
spi.clear_interrupts();
// IRQ is not related.
if !enabled_irqs.tx_below_threshold()
+137 -93
View File
@@ -19,9 +19,10 @@ use arbitrary_int::{prelude::*, u3, u4, u6};
use embedded_hal::delay::DelayNs;
pub use embedded_hal::spi::Mode;
use zynq7000::slcr::reset::DualRefAndClockResetSpiUart;
pub use zynq7000::spi::DelayControl;
use zynq7000::spi::{
BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus,
MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
BaudDivSel, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus, MmioRegisters,
SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
};
pub const FIFO_DEPTH: usize = 128;
@@ -352,13 +353,16 @@ impl ChipSelect {
/// Slave select configuration.
pub enum SlaveSelectConfig {
/// User must take care of controlling slave select lines as well as issuing a start command.
ManualWithManualStart = 0b11,
ManualAutoStart = 0b10,
ManualCsManualStart = 0b11,
/// Software controls the slave select, but the controller hardware automatically starts to
/// serialize data when there is data in the TxFIFO.
ManualCsAutoStart = 0b10,
/// Hardware slave select, but start needs to be issued manually.
AutoWithManualStart = 0b01,
/// Hardware slave select, auto serialiation if there is data in the TX FIFO.
AutoCsManualStart = 0b01,
/// Hardware slave select, auto serialiation if there is data in the TX FIFO. Might be
/// problematic for higher SPI speeds, where the processor can not fill the TX FIFO fast enough.
#[default]
AutoWithAutoStart = 0b00,
AutoCsAutoStart = 0b00,
}
#[derive(Debug, Copy, Clone)]
@@ -379,6 +383,31 @@ impl Config {
}
}
pub fn calculate_for_io_clock(
target_clock: Hertz,
io_clock: &IoClocks,
init_mode: Mode,
ss_config: SlaveSelectConfig,
) -> Self {
let divisor_raw = io_clock.spi_clk().raw().div_ceil(target_clock.raw());
let baud_div_sel = match divisor_raw {
0..=4 => BaudDivSel::By4,
5..=8 => BaudDivSel::By8,
9..=16 => BaudDivSel::By16,
17..=32 => BaudDivSel::By32,
33..=64 => BaudDivSel::By64,
65..=128 => BaudDivSel::By128,
129..=256 => BaudDivSel::By256,
_ => BaudDivSel::By256,
};
Self {
baud_div: baud_div_sel,
init_mode,
ss_config,
with_ext_decoding: false,
}
}
pub fn enable_external_decoding(&mut self) {
self.with_ext_decoding = true;
}
@@ -474,10 +503,10 @@ impl SpiLowLevel {
pub fn reconfigure(&mut self, config: Config) {
self.regs.write_enable(0);
let (man_ss, man_start) = match config.ss_config {
SlaveSelectConfig::ManualWithManualStart => (true, true),
SlaveSelectConfig::ManualAutoStart => (true, false),
SlaveSelectConfig::AutoWithManualStart => (false, true),
SlaveSelectConfig::AutoWithAutoStart => (false, false),
SlaveSelectConfig::ManualCsManualStart => (true, true),
SlaveSelectConfig::ManualCsAutoStart => (true, false),
SlaveSelectConfig::AutoCsManualStart => (false, true),
SlaveSelectConfig::AutoCsAutoStart => (false, false),
};
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode);
@@ -530,14 +559,11 @@ impl SpiLowLevel {
#[inline]
pub fn issue_manual_start(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_manual_start(true);
val
});
self.regs.modify_cr(|val| val.with_manual_start(true));
}
#[inline]
pub fn read_isr(&self) -> InterruptStatus {
pub fn read_interrupt_status(&self) -> InterruptStatus {
self.regs.read_interrupt_status()
}
@@ -569,6 +595,11 @@ impl SpiLowLevel {
Ok(())
}
#[inline]
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
self.regs.write_delay_control(delay_control);
}
/// This disables all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI master mode.
#[inline]
@@ -638,9 +669,7 @@ impl core::ops::DerefMut for SpiLowLevel {
/// Blocking Driver for the PS SPI peripheral in master mode.
pub struct Spi {
inner: SpiLowLevel,
sclk: Hertz,
config: Config,
outstanding_rx: bool,
}
#[derive(Debug, thiserror::Error)]
@@ -669,7 +698,6 @@ pub enum SpiConstructionError {
impl Spi {
pub fn new_no_hw_ss<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
) -> Result<Self, SpiConstructionError> {
@@ -687,17 +715,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_one_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pin: Ss,
@@ -717,17 +739,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pin, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_two_hw_cs<Sck: SckPin, Mosi: MosiPin, Miso: MisoPin, Ss0: SsPin, Ss1: SsPin>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1),
@@ -757,12 +773,7 @@ impl Spi {
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(
spi_id,
spi.reg_block(),
clocks,
config,
))
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_three_hw_cs<
@@ -774,7 +785,6 @@ impl Spi {
Ss2: SsPin,
>(
spi: impl PsSpi,
clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1, Ss2),
@@ -807,36 +817,40 @@ impl Spi {
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.2, SPI_MUX_CONF, Some(false));
Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
/// Constructor for usage with EMIO pins.
pub fn new_for_emio(spi: impl PsSpi, config: Config) -> Result<Self, InvalidPsSpiError> {
let spi_id = spi.id();
if spi_id.is_none() {
return Err(InvalidPsSpiError);
}
Ok(Self::new_generic_unchecked(
spi_id,
spi_id.unwrap(),
spi.reg_block(),
clocks,
config,
))
}
pub fn new_generic_unchecked(
id: SpiId,
regs: MmioRegisters<'static>,
clocks: &IoClocks,
config: Config,
) -> Self {
pub fn new_generic_unchecked(id: SpiId, regs: MmioRegisters<'static>, config: Config) -> Self {
let periph_sel = match id {
SpiId::Spi0 => crate::PeriphSelect::Spi0,
SpiId::Spi1 => crate::PeriphSelect::Spi1,
};
enable_amba_peripheral_clock(periph_sel);
let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32;
let mut spi = Self {
inner: SpiLowLevel { regs, id },
sclk,
config,
outstanding_rx: false,
};
spi.reset_and_reconfigure();
spi
}
pub fn write_delay_control(&mut self, delay_control: DelayControl) {
self.inner.write_delay_control(delay_control);
}
/// Re-configures the SPI peripheral with the initial configuration.
pub fn reconfigure(&mut self) {
self.inner.reconfigure(self.config);
@@ -852,19 +866,13 @@ impl Spi {
#[inline]
pub fn issue_manual_start_for_manual_cfg(&mut self) {
if self.config.ss_config == SlaveSelectConfig::AutoWithManualStart
|| self.config.ss_config == SlaveSelectConfig::ManualWithManualStart
if self.config.ss_config == SlaveSelectConfig::AutoCsManualStart
|| self.config.ss_config == SlaveSelectConfig::ManualCsManualStart
{
self.inner.issue_manual_start();
}
}
/// Retrieve SCLK clock frequency currently configured for this SPI.
#[inline]
pub const fn sclk(&self) -> Hertz {
self.sclk
}
/// Retrieve inner low-level helper.
#[inline]
pub const fn inner(&mut self) -> &mut SpiLowLevel {
@@ -876,7 +884,7 @@ impl Spi {
&mut self.inner.regs
}
fn initial_fifo_fill(&mut self, words: &[u8]) -> usize {
fn prefill_fifo(&mut self, words: &[u8]) -> usize {
let write_len = core::cmp::min(FIFO_DEPTH, words.len());
(0..write_len).for_each(|idx| {
self.inner.write_fifo_unchecked(words[idx]);
@@ -892,7 +900,7 @@ impl Spi {
self.inner.regs.write_rx_trig(1);
// Fill the FIFO with initial data.
let written = self.initial_fifo_fill(words);
let written = self.prefill_fifo(words);
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
@@ -900,7 +908,7 @@ impl Spi {
written
}
fn read(&mut self, words: &mut [u8]) {
pub fn read(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
@@ -935,33 +943,38 @@ impl Spi {
}
}
fn write(&mut self, words: &[u8]) {
pub fn write(&mut self, words: &[u8]) {
if words.is_empty() {
return;
}
let mut written = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
if words.len() > FIFO_DEPTH {
self.inner.regs.write_tx_trig(FIFO_DEPTH as u32 / 2);
}
while written < words.len() {
loop {
let status = self.regs().read_interrupt_status();
let rx_pending = read_idx < words.len();
let tx_pending = written < words.len();
// We empty the FIFO to prevent it filling up completely, as long as we have to write
// bytes
if status.rx_not_empty() {
if status.rx_not_empty() && rx_pending {
self.inner.read_fifo_unchecked();
read_idx += 1;
}
if !status.tx_full() {
if !status.tx_full() && tx_pending {
self.inner.write_fifo_unchecked(words[written]);
written += 1;
}
if !rx_pending && !tx_pending {
break;
}
}
// We exit once all bytes have been written, so some bytes to read might be outstanding.
// We use the FIFO trigger mechanism to determine when we can read all the remaining bytes.
self.regs().write_rx_trig((words.len() - read_idx) as u32);
self.outstanding_rx = true;
self.inner.regs.write_tx_trig(1);
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
if read.is_empty() {
return;
}
@@ -997,7 +1010,7 @@ impl Spi {
}
}
fn transfer_in_place(&mut self, words: &mut [u8]) {
pub fn transfer_in_place(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
@@ -1007,7 +1020,7 @@ impl Spi {
let mut writes_finished = write_idx == words.len();
let mut reads_finished = false;
while !writes_finished || !reads_finished {
let status = self.inner.read_isr();
let status = self.inner.read_interrupt_status();
if status.rx_not_empty() && !reads_finished {
words[read_idx] = self.inner.read_fifo_unchecked();
read_idx += 1;
@@ -1024,16 +1037,16 @@ impl Spi {
/// Blocking flush implementation.
fn flush(&mut self) {
if !self.outstanding_rx {
return;
}
let rx_trig = self.inner.read_rx_not_empty_threshold();
while !self.inner.read_isr().rx_not_empty() {}
(0..rx_trig).for_each(|_| {
self.inner.write_tx_trig(1);
let status = self.inner.read_interrupt_status();
while self.inner.read_interrupt_status().rx_not_empty() {
self.inner.read_fifo_unchecked();
});
self.inner.set_rx_fifo_trigger(1).unwrap();
self.outstanding_rx = false;
}
while status.tx_full() {
while self.inner.read_interrupt_status().rx_not_empty() {
self.inner.read_fifo_unchecked();
}
}
}
}
@@ -1145,7 +1158,7 @@ pub fn reset(id: SpiId) {
regs.reset_ctrl().write_spi(assert_reset);
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
// no idea what is meant with that.
for _ in 0..5 {
for _ in 0..10 {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl()
@@ -1159,22 +1172,32 @@ pub fn reset(id: SpiId) {
/// The Zynq7000 SPI peripheral has the following requirement for the SPI reference clock:
/// It must be larger than the CPU 1X clock. Therefore, the divisor used to calculate the reference
/// clock has a maximum value, which can be calculated with this function.
/// [configure_spi_ref_clk_with_divisor] can be used to configure the SPI reference clock with a
/// divisor.
///
/// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated
/// value.
/// *NOTE* - It is recommended to avoid the largest theoretical value which was proven to be
/// problematic for driving certain sensors and instead take a smaller value! Reduce the divisor
/// calculated by this function subtracting a small value to get a functioning SPI clock.
pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6> {
let slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
let div = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
clks.arm_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
clks.ddr_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
clks.io_clocks()
.ref_clk()
.raw()
.div_ceil(clks.arm_clocks().cpu_1x_clk().raw())
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks
.arm_clocks()
.ref_clk()
.raw()
.div_ceil(clks.arm_clocks().cpu_1x_clk().raw()),
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks
.ddr_clocks()
.ref_clk()
.raw()
.div_ceil(clks.arm_clocks().cpu_1x_clk().raw()),
};
if div > u6::MAX.value() as u32 {
return None;
@@ -1183,7 +1206,28 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option<u6
Some(u6::new(div as u8))
}
pub fn configure_spi_ref_clk(clks: &mut Clocks, divisor: u6) {
/// Configures the SPI reference clock.
///
/// It is strongly advised to take a clock value which is substantially higher than the CPU 1x
/// clock. It was proven that taking values which are only slightly larger than the CPU 1x
/// clock are problematic for driving ceratin devices.
pub fn configure_spi_ref_clock(clks: &mut Clocks, target_clock: Hertz) {
let slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
let ref_clk = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
clks.io_clocks().ref_clk().raw()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().raw(),
zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().raw(),
};
let div = ref_clk.div_ceil(target_clock.raw());
if div > u6::MAX.value() as u32 {
configure_spi_ref_clock_with_divisor(clks, u6::new(div as u8));
}
}
pub fn configure_spi_ref_clock_with_divisor(clks: &mut Clocks, divisor: u6) {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
slcr.modify(|regs| {
+1 -1
View File
@@ -6,7 +6,7 @@ pub use crate::{SpiClockPhase, SpiClockPolarity};
pub const SPI_0_BASE_ADDR: usize = 0xE000_6000;
pub const SPI_1_BASE_ADDR: usize = 0xE000_7000;
/// The SPI reference block will be divided by a divisor value.
/// The SPI reference clock will be divided by a divisor value.
#[bitbybit::bitenum(u3)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+15 -6
View File
@@ -1,5 +1,8 @@
all: check build check-fmt clippy docs-zynq
# Common paths (single source of truth)
INIT_SCRIPT := justfile_directory() / "scripts/zynq7000-init.py"
GDB_CMD := justfile_directory() / "firmware/gdb.gdb"
all: check build check-fmt clippy docs-zynq
check: (check-dir "firmware") (check-dir "host")
clean: (clean-dir "firmware") (clean-dir "host")
build: build-zynq (build-dir "host")
@@ -52,13 +55,19 @@ bootgen:
bootgen -arch zynq -image boot.bif -o boot.bin -w on
echo "Generated boot.bin at zynq-boot-image/staging"
# Internal helper to start GDB after running init.
# Pass init flags (if any) via `init_args`.
[no-cd]
run binary:
# Run the initialization script. It needs to be run inside the justfile directory.
python3 {{justfile_directory()}}/scripts/zynq7000-init.py
run_generic binary init_args="":
python3 {{INIT_SCRIPT}} {{init_args}}
gdb-multiarch -q -x {{GDB_CMD}} {{binary}} -tui
# Run the GDB debugger in GUI mode.
gdb-multiarch -q -x {{justfile_directory()}}/firmware/gdb.gdb {{binary}} -tui
# Public targets
[no-cd]
run binary: (run_generic binary)
[no-cd]
run-no-reset binary: (run_generic binary "-s")
flash-nor-zedboard boot_binary:
cd {{justfile_directory()}}/firmware/zedboard-qspi-flasher && cargo build --release
+21 -18
View File
@@ -33,10 +33,11 @@ proc ps7_clock_init_data_3_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_3_0 {} {
@@ -149,7 +150,7 @@ proc ps7_mio_init_data_3_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -188,14 +189,14 @@ proc ps7_mio_init_data_3_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007BC 0x00003FFF 0x00000200
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mask_write 0XF80007D0 0x00003FFF 0x00000280
mask_write 0XF80007D4 0x00003FFF 0x00000280
mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_3_0 {} {
@@ -268,10 +269,11 @@ proc ps7_clock_init_data_2_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_2_0 {} {
@@ -385,7 +387,7 @@ proc ps7_mio_init_data_2_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -424,14 +426,14 @@ proc ps7_mio_init_data_2_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007BC 0x00003FFF 0x00000200
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mask_write 0XF80007D0 0x00003FFF 0x00000280
mask_write 0XF80007D4 0x00003FFF 0x00000280
mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_2_0 {} {
@@ -504,10 +506,11 @@ proc ps7_clock_init_data_1_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_1_0 {} {
@@ -619,7 +622,7 @@ proc ps7_mio_init_data_1_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -658,14 +661,14 @@ proc ps7_mio_init_data_1_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007BC 0x00003FFF 0x00000200
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mask_write 0XF80007D0 0x00003FFF 0x00000280
mask_write 0XF80007D4 0x00003FFF 0x00000280
mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_1_0 {} {
+11 -7
View File
@@ -13,13 +13,15 @@ set bitstream ""
proc usage {} {
puts "Usage: xsct xsct-helper.tcl <init.tcl> \[-a|--app app.elf\] \[-b|--bit design.bit]"
puts "Options:"
puts " -a, --app Path to application ELF to download"
puts " -b, --bit Path to FPGA bitstream (.bit) to program"
puts " -h, --help Show this help"
puts " -a, --app Path to application ELF to download"
puts " -b, --bit Path to FPGA bitstream (.bit) to program"
puts " -s, --skip-reset Skip reset"
puts " -h, --help Show this help"
}
# Compact, robust parser
set expecting ""
set skip_reset 0
set endopts 0
foreach arg $argv {
# If previous option expects a value, take this arg
@@ -34,6 +36,7 @@ foreach arg $argv {
if {$arg eq "--"} { set endopts 1; continue }
if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 }
if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue }
if {$arg eq "-s" || $arg eq "--skip-reset"} { set skip_reset 1; continue }
if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue }
puts "error: unknown option: $arg"; usage; exit 1
}
@@ -100,10 +103,11 @@ target $apu_device_num
# Resetting the target involved problems when an image is stored on the flash.
# It has turned out that it is not essential to reset the system before loading
# the software components into the device.
puts "Reset target"
# TODO: Make the reset optional/configurable via input argument.
# Reset the target
rst
if {!$skip_reset} {
puts "Reset target"
# Reset the target
rst
}
# Check if bitstream is set and the file exists before programming FPGA
if {$bitstream eq ""} {
+8
View File
@@ -40,6 +40,12 @@ def main():
action="store_true",
help="No bitstream flashing for initialization with SDT.",
)
parser.add_argument(
"-s",
"--skip-reset",
action="store_true",
help="Skip device reset",
)
parser.add_argument("-a", "--app", dest="app", help="Path to the app to program")
default_ip = os.getenv("HW_SERVER_IP")
if not default_ip:
@@ -141,6 +147,8 @@ def main():
if args.app:
cmd_list.append("--app")
cmd_list.append(args.app)
if args.skip_reset:
cmd_list.append("-s")
# Join safely for shell execution
xsct_cmd = shlex.join(cmd_list)
+6
View File
@@ -39,6 +39,12 @@ vivado zedboard-rust.xpr
You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and
`Load Project`.
# Updating the project
If you add custom RTL code, you might have to edit the `zedboard-rust.tcl` project file and
add the source files there. This file was created using the `write_project_tcl` but was
optimized, so it might be easier to manually update the file.
# Generating the SDT folder from a hardware description
You can generate a hardware description by building the block design by using `Generate Bitstream`
+85 -19
View File
@@ -246,6 +246,10 @@ proc create_root_design { parentCell } {
set TTC0_WAVEOUT [ create_bd_port -dir O TTC0_WAVEOUT ]
set OLED_SCLK [ create_bd_port -dir O OLED_SCLK ]
set OLED_SDIN [ create_bd_port -dir O OLED_SDIN ]
set OLED_DC [ create_bd_port -dir O -from 0 -to 0 OLED_DC ]
set OLED_RESET [ create_bd_port -dir O -from 0 -to 0 OLED_RESET ]
set OLED_VDD [ create_bd_port -dir O -from 0 -to 0 OLED_VDD ]
set OLED_VBAT [ create_bd_port -dir O -from 0 -to 0 OLED_VBAT ]
# Create instance: processing_system7_0, and set properties
set processing_system7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_0 ]
@@ -283,25 +287,27 @@ proc create_root_design { parentCell } {
CONFIG.PCW_DDR_RAM_HIGHADDR {0x1FFFFFFF} \
CONFIG.PCW_ENET0_ENET0_IO {MIO 16 .. 27} \
CONFIG.PCW_ENET0_GRP_MDIO_ENABLE {1} \
CONFIG.PCW_ENET0_GRP_MDIO_IO {EMIO} \
CONFIG.PCW_ENET0_GRP_MDIO_IO {MIO 52 .. 53} \
CONFIG.PCW_ENET0_PERIPHERAL_CLKSRC {IO PLL} \
CONFIG.PCW_ENET0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_ENET0_PERIPHERAL_FREQMHZ {1000 Mbps} \
CONFIG.PCW_ENET0_RESET_ENABLE {0} \
CONFIG.PCW_ENET_RESET_ENABLE {1} \
CONFIG.PCW_ENET_RESET_SELECT {Share reset pin} \
CONFIG.PCW_EN_EMIO_ENET0 {0} \
CONFIG.PCW_EN_EMIO_GPIO {1} \
CONFIG.PCW_EN_EMIO_SPI0 {0} \
CONFIG.PCW_EN_EMIO_SPI1 {1} \
CONFIG.PCW_EN_EMIO_SPI0 {1} \
CONFIG.PCW_EN_EMIO_SPI1 {0} \
CONFIG.PCW_EN_EMIO_TTC0 {1} \
CONFIG.PCW_EN_EMIO_TTC1 {0} \
CONFIG.PCW_EN_EMIO_UART0 {1} \
CONFIG.PCW_EN_EMIO_WP_SDIO0 {1} \
CONFIG.PCW_EN_EMIO_WP_SDIO0 {0} \
CONFIG.PCW_EN_ENET0 {1} \
CONFIG.PCW_EN_GPIO {1} \
CONFIG.PCW_EN_QSPI {1} \
CONFIG.PCW_EN_SDIO0 {1} \
CONFIG.PCW_EN_SPI0 {0} \
CONFIG.PCW_EN_SPI1 {1} \
CONFIG.PCW_EN_SPI0 {1} \
CONFIG.PCW_EN_SPI1 {0} \
CONFIG.PCW_EN_TTC0 {1} \
CONFIG.PCW_EN_TTC1 {0} \
CONFIG.PCW_EN_UART0 {1} \
@@ -472,9 +478,9 @@ proc create_root_design { parentCell } {
CONFIG.PCW_MIO_9_PULLUP {enabled} \
CONFIG.PCW_MIO_9_SLEW {slow} \
CONFIG.PCW_MIO_TREE_PERIPHERALS {GPIO#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#GPIO#Quad SPI Flash#GPIO#GPIO#GPIO#GPIO#GPIO#GPIO#GPIO#Enet 0#Enet 0#Enet\
0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#SD 0#SD 0#SD 0#SD 0#SD 0#SD 0#USB Reset#SD 0#UART 1#UART 1#GPIO#GPIO#GPIO#GPIO}\
\
CONFIG.PCW_MIO_TREE_SIGNALS {gpio[0]#qspi0_ss_b#qspi0_io[0]#qspi0_io[1]#qspi0_io[2]#qspi0_io[3]/HOLD_B#qspi0_sclk#gpio[7]#qspi_fbclk#gpio[9]#gpio[10]#gpio[11]#gpio[12]#gpio[13]#gpio[14]#gpio[15]#tx_clk#txd[0]#txd[1]#txd[2]#txd[3]#tx_ctl#rx_clk#rxd[0]#rxd[1]#rxd[2]#rxd[3]#rx_ctl#data[4]#dir#stp#nxt#data[0]#data[1]#data[2]#data[3]#clk#data[5]#data[6]#data[7]#clk#cmd#data[0]#data[1]#data[2]#data[3]#reset#cd#tx#rx#gpio[50]#gpio[51]#gpio[52]#gpio[53]}\
0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#SD 0#SD 0#SD 0#SD 0#SD 0#SD 0#USB Reset#GPIO#UART 1#UART 1#GPIO#GPIO#Enet\
0#Enet 0} \
CONFIG.PCW_MIO_TREE_SIGNALS {gpio[0]#qspi0_ss_b#qspi0_io[0]#qspi0_io[1]#qspi0_io[2]#qspi0_io[3]/HOLD_B#qspi0_sclk#gpio[7]#qspi_fbclk#gpio[9]#gpio[10]#gpio[11]#gpio[12]#gpio[13]#gpio[14]#gpio[15]#tx_clk#txd[0]#txd[1]#txd[2]#txd[3]#tx_ctl#rx_clk#rxd[0]#rxd[1]#rxd[2]#rxd[3]#rx_ctl#data[4]#dir#stp#nxt#data[0]#data[1]#data[2]#data[3]#clk#data[5]#data[6]#data[7]#clk#cmd#data[0]#data[1]#data[2]#data[3]#reset#gpio[47]#tx#rx#gpio[50]#gpio[51]#mdc#mdio}\
\
CONFIG.PCW_PRESET_BANK1_VOLTAGE {LVCMOS 1.8V} \
CONFIG.PCW_QSPI_GRP_FBCLK_ENABLE {1} \
@@ -486,19 +492,17 @@ proc create_root_design { parentCell } {
CONFIG.PCW_QSPI_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_QSPI_PERIPHERAL_FREQMHZ {200} \
CONFIG.PCW_QSPI_QSPI_IO {MIO 1 .. 6} \
CONFIG.PCW_SD0_GRP_CD_ENABLE {1} \
CONFIG.PCW_SD0_GRP_CD_IO {MIO 47} \
CONFIG.PCW_SD0_GRP_CD_ENABLE {0} \
CONFIG.PCW_SD0_GRP_POW_ENABLE {0} \
CONFIG.PCW_SD0_GRP_WP_ENABLE {1} \
CONFIG.PCW_SD0_GRP_WP_IO {EMIO} \
CONFIG.PCW_SD0_GRP_WP_ENABLE {0} \
CONFIG.PCW_SD0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_SD0_SD0_IO {MIO 40 .. 45} \
CONFIG.PCW_SDIO_PERIPHERAL_FREQMHZ {50} \
CONFIG.PCW_SDIO_PERIPHERAL_VALID {1} \
CONFIG.PCW_SINGLE_QSPI_DATA_MODE {x4} \
CONFIG.PCW_SPI0_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_SPI1_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_SPI1_SPI1_IO {EMIO} \
CONFIG.PCW_SPI0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_SPI0_SPI0_IO {EMIO} \
CONFIG.PCW_SPI1_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_SPI_PERIPHERAL_FREQMHZ {166.666666} \
CONFIG.PCW_SPI_PERIPHERAL_VALID {1} \
CONFIG.PCW_TTC0_PERIPHERAL_ENABLE {1} \
@@ -654,6 +658,50 @@ proc create_root_design { parentCell } {
# Create instance: IRQ_F2P, and set properties
set IRQ_F2P [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconcat:1.0 IRQ_F2P ]
# Create instance: OLED_DC, and set properties
set OLED_DC [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_DC ]
set_property -dict [list \
CONFIG.DIN_FROM {11} \
CONFIG.DIN_TO {11} \
CONFIG.DIN_WIDTH {16} \
] $OLED_DC
# Create instance: OLED_RESET, and set properties
set OLED_RESET [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_RESET ]
set_property -dict [list \
CONFIG.DIN_FROM {12} \
CONFIG.DIN_TO {12} \
CONFIG.DIN_WIDTH {16} \
] $OLED_RESET
# Create instance: OLED_VDD, and set properties
set OLED_VDD [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_VDD ]
set_property -dict [list \
CONFIG.DIN_FROM {13} \
CONFIG.DIN_TO {13} \
CONFIG.DIN_WIDTH {16} \
] $OLED_VDD
# Create instance: OLED_VBAT, and set properties
set OLED_VBAT [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_VBAT ]
set_property -dict [list \
CONFIG.DIN_FROM {14} \
CONFIG.DIN_TO {14} \
CONFIG.DIN_WIDTH {16} \
] $OLED_VBAT
# Create instance: ilconstant_2, and set properties
set ilconstant_2 [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconstant:1.0 ilconstant_2 ]
# Create instance: ilconstant_3, and set properties
set ilconstant_3 [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconstant:1.0 ilconstant_3 ]
set_property CONFIG.CONST_VAL {0} $ilconstant_3
# Create interface connections
connect_bd_intf_net -intf_net processing_system7_0_DDR [get_bd_intf_ports DDR] [get_bd_intf_pins processing_system7_0/DDR]
connect_bd_intf_net -intf_net processing_system7_0_FIXED_IO [get_bd_intf_ports FIXED_IO] [get_bd_intf_pins processing_system7_0/FIXED_IO]
@@ -668,6 +716,12 @@ proc create_root_design { parentCell } {
[get_bd_pins EMIO_I/In3]
connect_bd_net -net EMIO_O_1_Dout [get_bd_pins EMIO_O_1/Dout] \
[get_bd_pins EMIO_I/In2]
connect_bd_net -net OLED_RESET_Dout [get_bd_pins OLED_RESET/Dout] \
[get_bd_ports OLED_RESET]
connect_bd_net -net OLED_VBAT_Dout [get_bd_pins OLED_VBAT/Dout] \
[get_bd_ports OLED_VBAT]
connect_bd_net -net OLED_VDD_Dout [get_bd_pins OLED_VDD/Dout] \
[get_bd_ports OLED_VDD]
connect_bd_net -net SWITCHES_1 [get_bd_ports SWITCHES] \
[get_bd_pins EMIO_I_0/In0]
connect_bd_net -net axi_uart16550_0_ip2intc_irpt [get_bd_pins axi_uart16550_0/ip2intc_irpt] \
@@ -686,10 +740,18 @@ proc create_root_design { parentCell } {
[get_bd_pins processing_system7_0/IRQ_F2P]
connect_bd_net -net ilconstant_0_dout [get_bd_pins ilconstant_0/dout] \
[get_bd_pins EMIO_I_0/In2]
connect_bd_net -net ilconstant_2_dout [get_bd_pins ilconstant_2/dout] \
[get_bd_pins processing_system7_0/SPI0_MISO_I] \
[get_bd_pins processing_system7_0/SPI0_SS_I]
connect_bd_net -net ilconstant_3_dout [get_bd_pins ilconstant_3/dout] \
[get_bd_pins processing_system7_0/SPI0_MOSI_I] \
[get_bd_pins processing_system7_0/SPI0_SCLK_I]
connect_bd_net -net ilslice_0_Dout [get_bd_pins UART_MUX/Dout] \
[get_bd_pins uart_mux_0/sel]
connect_bd_net -net ilslice_0_Dout1 [get_bd_pins LEDS/Dout] \
[get_bd_ports LEDS]
connect_bd_net -net ilslice_0_Dout2 [get_bd_pins OLED_DC/Dout] \
[get_bd_ports OLED_DC]
connect_bd_net -net processing_system7_0_FCLK_CLK0 [get_bd_pins processing_system7_0/FCLK_CLK0] \
[get_bd_pins processing_system7_0/M_AXI_GP0_ACLK] \
[get_bd_pins rst_ps7_0_100M/slowest_sync_clk] \
@@ -702,9 +764,9 @@ proc create_root_design { parentCell } {
connect_bd_net -net processing_system7_0_GPIO_O [get_bd_pins processing_system7_0/GPIO_O] \
[get_bd_pins EMIO_O_0/Din] \
[get_bd_pins EMIO_O_1/Din]
connect_bd_net -net processing_system7_0_SPI1_MOSI_O [get_bd_pins processing_system7_0/SPI1_MOSI_O] \
connect_bd_net -net processing_system7_0_SPI0_MOSI_O [get_bd_pins processing_system7_0/SPI0_MOSI_O] \
[get_bd_ports OLED_SDIN]
connect_bd_net -net processing_system7_0_SPI1_SCLK_O [get_bd_pins processing_system7_0/SPI1_SCLK_O] \
connect_bd_net -net processing_system7_0_SPI0_SCLK_O [get_bd_pins processing_system7_0/SPI0_SCLK_O] \
[get_bd_ports OLED_SCLK]
connect_bd_net -net processing_system7_0_TTC0_WAVE0_OUT [get_bd_pins processing_system7_0/TTC0_WAVE0_OUT] \
[get_bd_ports TTC0_WAVEOUT]
@@ -732,7 +794,11 @@ proc create_root_design { parentCell } {
connect_bd_net -net xlslice_1_Dout [get_bd_pins EMIO_O_0/Dout] \
[get_bd_pins EMIO_I/In0] \
[get_bd_pins UART_MUX/Din] \
[get_bd_pins LEDS/Din]
[get_bd_pins LEDS/Din] \
[get_bd_pins OLED_DC/Din] \
[get_bd_pins OLED_RESET/Din] \
[get_bd_pins OLED_VDD/Din] \
[get_bd_pins OLED_VBAT/Din]
# Create address segments
assign_bd_address -offset 0x43C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] -force
+36 -3
View File
@@ -72,11 +72,44 @@ set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
# OLED SPI
set_property PACKAGE_PIN AB12 [get_ports OLED_SCLK]
set_property IOSTANDARD LVCMOS33 [get_ports OLED_SCLK]
set_property PACKAGE_PIN AA12 [get_ports OLED_SDIN]
set_property IOSTANDARD LVCMOS33 [get_ports OLED_SDIN]
set_property PULLUP TRUE [get_ports OLED_SDIN]
set_property PACKAGE_PIN U9 [get_ports OLED_RESET]
set_property PULLUP TRUE [get_ports OLED_RESET]
set_property PACKAGE_PIN U10 [get_ports OLED_DC]
set_property PACKAGE_PIN U12 [get_ports OLED_VDD]
set_property PACKAGE_PIN U11 [get_ports OLED_VBAT]
# TTC0 Wave Out
set_property PACKAGE_PIN W12 [get_ports {TTC0_WAVEOUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {TTC0_WAVEOUT}]
# ----------------------------------------------------------------------------
# IOSTANDARD Constraints
#
# Note that these IOSTANDARD constraints are applied to all IOs currently
# assigned within an I/O bank. If these IOSTANDARD constraints are
# evaluated prior to other PACKAGE_PIN constraints being applied, then
# the IOSTANDARD specified will likely not be applied properly to those
# pins. Therefore, bank wide IOSTANDARD constraints should be placed
# within the XDC file in a location that is evaluated AFTER all
# PACKAGE_PIN constraints within the target bank have been evaluated.
#
# Un-comment one or more of the following IOSTANDARD constraints according to
# the bank pin assignments that are required within a design.
# ----------------------------------------------------------------------------
# Note that the bank voltage for IO Bank 33 is fixed to 3.3V on ZedBoard.
set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 33]];
# Set the bank voltage for IO Bank 34 to 1.8V by default.
# set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 34]];
# set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 34]];
set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 34]];
# Set the bank voltage for IO Bank 35 to 1.8V by default.
# set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 35]];
# set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 35]];
set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 35]];
# Note that the bank voltage for IO Bank 13 is fixed to 3.3V on ZedBoard.
set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 13]];