PWM works

This commit is contained in:
Robin Müller 2025-04-02 16:26:39 +02:00
parent d16927376a
commit 1ce930f7a8
Signed by: muellerr
GPG Key ID: A649FB78196E3849
4 changed files with 226 additions and 25 deletions

View File

@ -0,0 +1,151 @@
//! PWM example which uses a PWM pin routed through EMIO.
//!
//! On the Zedboard, the PWM waveform output will be on the W12 pin of PMOD JB1. The Zedboard
//! reference FPGA design must be flashed onto the Zedboard for this to work.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::{digital::StatefulOutputPin, pwm::SetDutyCycle};
use embedded_io::Write;
use fugit::RateExtU32;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{MioPins, PinState},
gtc::Gtc,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000::PsPeripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let dp = PsPeripherals::take().unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
unsafe {
gic.enable_interrupts();
}
let mio_pins = MioPins::new(dp.gpio);
// Set up global timer counter and embassy time driver.
let gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Unwrap is okay, the address is definitely valid.
let ttc_0 = zynq7000_hal::ttc::Ttc::new(dp.ttc_0).unwrap();
let mut pwm =
zynq7000_hal::ttc::Pwm::new_with_cpu_clk(ttc_0.ch0, clocks.arm_clocks(), 1000.Hz())
.unwrap();
pwm.set_duty_cycle_percent(50).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe { zynq7000_hal::log::init_unsafe_single_core(uart, log::LevelFilter::Trace, false) };
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut led = mio_pins.mio7.into_output(PinState::Low);
let mut current_duty = 0;
loop {
led.toggle().unwrap();
pwm.set_duty_cycle_percent(current_duty).unwrap();
info!("Setting duty cycle to {}%", current_duty);
current_duty += 5;
if current_duty > 100 {
current_duty = 0;
}
ticker.next().await;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {:?}", info);
loop {}
}

View File

@ -70,6 +70,6 @@ set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
set_property PACKAGE_PIN AA11 [get_ports UART_txd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
# TTC1 Wave Out
set_property PACKAGE_PIN W12 [get_ports {TTC1_WAVEOUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {TTC1_WAVEOUT}]
# TTC0 Wave Out
set_property PACKAGE_PIN W12 [get_ports {TTC0_WAVEOUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {TTC0_WAVEOUT}]

View File

@ -1,8 +1,10 @@
//! Triple-timer counter (TTC) high-level driver.
//!
//! This module also contains support for PWM and output waveform generation.
use core::convert::Infallible;
use arbitrary_int::{u3, u4};
use arbitrary_int::{Number, u3, u4};
use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
@ -87,6 +89,8 @@ pub struct Ttc {
impl Ttc {
/// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
/// processing system TTC peripheral.
///
/// Returns [None] if the passed peripheral block does not have a valid PS TTC address.
pub fn new(ps_ttc: impl PsTtc) -> Option<Self> {
ps_ttc.id()?;
let regs = ps_ttc.reg_block();
@ -116,6 +120,14 @@ impl TtcChannel {
&mut self.regs
}
#[inline]
pub fn read_counter(&self) -> u16 {
self.regs
.read_current_counter(self.id as usize)
.unwrap()
.count()
}
pub fn id(&self) -> ChannelId {
self.id
}
@ -137,16 +149,28 @@ pub enum TtcConstructionError {
FrequencyIsZero(#[from] FrequencyIsZeroError),
}
pub fn calculate_prescaler_reg_and_interval_ticks(mut ref_clk: Hertz, freq: Hertz) -> (u4, u16) {
pub fn calculate_prescaler_reg_and_interval_ticks(
mut ref_clk: Hertz,
freq: Hertz,
) -> (Option<u4>, u16) {
// TODO: Can this be optimized?
let mut prescaler_reg = 0;
let mut prescaler_reg: Option<u4> = None;
let mut tick_val = ref_clk / freq;
while tick_val > u16::MAX as u32 {
ref_clk /= 2;
prescaler_reg += 1;
if let Some(prescaler_reg) = prescaler_reg {
// TODO: Better error handling for this case? Can this even happen?
if prescaler_reg.value() == u4::MAX.value() {
break;
} else {
prescaler_reg.checked_add(u4::new(1));
}
} else {
prescaler_reg = Some(u4::new(0));
}
tick_val = ref_clk / freq;
}
(u4::new(prescaler_reg), tick_val as u16)
(prescaler_reg, tick_val as u16)
}
pub struct Pwm {
@ -155,8 +179,9 @@ pub struct Pwm {
}
impl Pwm {
/// Create a new PWM instance which uses the CPU 1x clock as the clock source.
pub fn new_with_mio_wave_out(
/// Create a new PWM instance which uses the CPU 1x clock as the clock source and also uses
/// a MIO output pin for the waveform output.
pub fn new_with_cpu_clk_and_mio_waveout(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
@ -165,16 +190,30 @@ impl Pwm {
if wave_out.mux_conf() != TTC_MUX_CONF {
return Err(InvalidTtcPinConfigError(wave_out.mux_conf()).into());
}
Ok(Self::new_with_cpu_clk(channel, arm_clocks, freq)?)
}
/// Create a new PWM instance which uses the CPU 1x clock as the clock source.
pub fn new_with_cpu_clk(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
Self::new_generic(channel, arm_clocks.cpu_1x_clk(), freq)
}
/// Create a new PWM instance based on a reference clock source.
pub fn new_generic(
channel: TtcChannel,
ref_clk: Hertz,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
if freq.raw() == 0 {
return Err(FrequencyIsZeroError.into());
return Err(FrequencyIsZeroError);
}
let (prescaler_reg, tick_val) =
calculate_prescaler_reg_and_interval_ticks(arm_clocks.cpu_1x_clk(), freq);
let (prescaler_reg, tick_val) = calculate_prescaler_reg_and_interval_ticks(ref_clk, freq);
let id = channel.id() as usize;
let mut pwm = Self {
channel,
ref_clk: arm_clocks.cpu_1x_clk(),
};
let mut pwm = Self { channel, ref_clk };
pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(pwm)
}
@ -193,6 +232,11 @@ impl Pwm {
Ok(())
}
#[inline]
pub fn ttc_channel_mut(&mut self) -> &mut TtcChannel {
&mut self.channel
}
#[inline]
pub fn max_duty_cycle(&self) -> u16 {
self.channel
@ -229,7 +273,7 @@ impl Pwm {
.unwrap();
}
fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: u4, tick_val: u16) {
fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: Option<u4>, tick_val: u16) {
// Disable the counter first.
self.channel
.regs
@ -244,8 +288,8 @@ impl Pwm {
zynq7000::ttc::ClockControl::builder()
.with_ext_clk_edge(false)
.with_clk_src(zynq7000::ttc::ClockSource::Pclk)
.with_prescaler(prescaler_reg)
.with_prescale_enable(prescaler_reg.value() > 0)
.with_prescaler(prescaler_reg.unwrap_or(u4::new(0)))
.with_prescale_enable(prescaler_reg.is_some())
.build(),
)
.unwrap();
@ -266,10 +310,10 @@ impl Pwm {
.write_cnt_ctrl(
id,
zynq7000::ttc::CounterControl::builder()
.with_wave_polarity(zynq7000::ttc::WavePolarity::HighToLowOnMatch1)
.with_wave_polarity(zynq7000::ttc::WavePolarity::LowToHighOnMatch1)
.with_wave_enable_n(zynq7000::ttc::WaveEnable::Enable)
.with_reset(true)
.with_match_enable(false)
.with_match_enable(true)
.with_decrementing(false)
.with_mode(zynq7000::ttc::Mode::Interval)
.with_disable(false)

View File

@ -1,12 +1,14 @@
//! Triple-timer counter (TTC) register module.
use arbitrary_int::u4;
pub const TTC_0_BASE_ADDR: usize = 0xF800_1004;
pub const TTC_1_BASE_ADDR: usize = 0xF800_2004;
pub const TTC_0_BASE_ADDR: usize = 0xF800_1000;
pub const TTC_1_BASE_ADDR: usize = 0xF800_2000;
#[derive(Debug, Default)]
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum ClockSource {
/// PS internal bus clock.
#[default]
Pclk = 0b0,
External = 0b1,
}
@ -25,13 +27,14 @@ pub struct ClockControl {
prescale_enable: bool,
}
#[derive(Debug)]
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum Mode {
Overflow = 0b0,
Interval = 0b1,
}
#[derive(Default)]
#[derive(Debug, Default)]
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum WavePolarity {
/// The waveform output goes from high to low on a match 0 interrupt and returns high on
@ -43,6 +46,7 @@ pub enum WavePolarity {
LowToHighOnMatch1 = 0b1,
}
#[derive(Debug)]
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum WaveEnable {
Enable = 0b0,
@ -158,6 +162,8 @@ pub struct Ttc {
event_reg: [EventCount; 3],
}
static_assertions::const_assert_eq!(core::mem::size_of::<Ttc>(), 0x84);
impl Ttc {
/// Create a new TTC MMIO instance for TTC0 at address [TTC_0_BASE_ADDR].
///