PWM works
This commit is contained in:
parent
d16927376a
commit
1ce930f7a8
151
examples/embassy/src/bin/pwm.rs
Normal file
151
examples/embassy/src/bin/pwm.rs
Normal 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 {}
|
||||||
|
}
|
@ -70,6 +70,6 @@ set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
|
|||||||
set_property PACKAGE_PIN AA11 [get_ports UART_txd]
|
set_property PACKAGE_PIN AA11 [get_ports UART_txd]
|
||||||
set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
|
set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
|
||||||
|
|
||||||
# TTC1 Wave Out
|
# TTC0 Wave Out
|
||||||
set_property PACKAGE_PIN W12 [get_ports {TTC1_WAVEOUT}]
|
set_property PACKAGE_PIN W12 [get_ports {TTC0_WAVEOUT}]
|
||||||
set_property IOSTANDARD LVCMOS33 [get_ports {TTC1_WAVEOUT}]
|
set_property IOSTANDARD LVCMOS33 [get_ports {TTC0_WAVEOUT}]
|
@ -1,8 +1,10 @@
|
|||||||
//! Triple-timer counter (TTC) high-level driver.
|
//! Triple-timer counter (TTC) high-level driver.
|
||||||
|
//!
|
||||||
|
//! This module also contains support for PWM and output waveform generation.
|
||||||
|
|
||||||
use core::convert::Infallible;
|
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};
|
use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
|
||||||
|
|
||||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||||
@ -87,6 +89,8 @@ pub struct Ttc {
|
|||||||
impl Ttc {
|
impl Ttc {
|
||||||
/// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
|
/// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
|
||||||
/// processing system TTC peripheral.
|
/// 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> {
|
pub fn new(ps_ttc: impl PsTtc) -> Option<Self> {
|
||||||
ps_ttc.id()?;
|
ps_ttc.id()?;
|
||||||
let regs = ps_ttc.reg_block();
|
let regs = ps_ttc.reg_block();
|
||||||
@ -116,6 +120,14 @@ impl TtcChannel {
|
|||||||
&mut self.regs
|
&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 {
|
pub fn id(&self) -> ChannelId {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
@ -137,16 +149,28 @@ pub enum TtcConstructionError {
|
|||||||
FrequencyIsZero(#[from] FrequencyIsZeroError),
|
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?
|
// TODO: Can this be optimized?
|
||||||
let mut prescaler_reg = 0;
|
let mut prescaler_reg: Option<u4> = None;
|
||||||
let mut tick_val = ref_clk / freq;
|
let mut tick_val = ref_clk / freq;
|
||||||
while tick_val > u16::MAX as u32 {
|
while tick_val > u16::MAX as u32 {
|
||||||
ref_clk /= 2;
|
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;
|
tick_val = ref_clk / freq;
|
||||||
}
|
}
|
||||||
(u4::new(prescaler_reg), tick_val as u16)
|
(prescaler_reg, tick_val as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pwm {
|
pub struct Pwm {
|
||||||
@ -155,8 +179,9 @@ pub struct Pwm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Pwm {
|
impl Pwm {
|
||||||
/// Create a new PWM instance which uses the CPU 1x clock as the clock source.
|
/// Create a new PWM instance which uses the CPU 1x clock as the clock source and also uses
|
||||||
pub fn new_with_mio_wave_out(
|
/// a MIO output pin for the waveform output.
|
||||||
|
pub fn new_with_cpu_clk_and_mio_waveout(
|
||||||
channel: TtcChannel,
|
channel: TtcChannel,
|
||||||
arm_clocks: &ArmClocks,
|
arm_clocks: &ArmClocks,
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
@ -165,16 +190,30 @@ impl Pwm {
|
|||||||
if wave_out.mux_conf() != TTC_MUX_CONF {
|
if wave_out.mux_conf() != TTC_MUX_CONF {
|
||||||
return Err(InvalidTtcPinConfigError(wave_out.mux_conf()).into());
|
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 {
|
if freq.raw() == 0 {
|
||||||
return Err(FrequencyIsZeroError.into());
|
return Err(FrequencyIsZeroError);
|
||||||
}
|
}
|
||||||
let (prescaler_reg, tick_val) =
|
let (prescaler_reg, tick_val) = calculate_prescaler_reg_and_interval_ticks(ref_clk, freq);
|
||||||
calculate_prescaler_reg_and_interval_ticks(arm_clocks.cpu_1x_clk(), freq);
|
|
||||||
let id = channel.id() as usize;
|
let id = channel.id() as usize;
|
||||||
let mut pwm = Self {
|
let mut pwm = Self { channel, ref_clk };
|
||||||
channel,
|
|
||||||
ref_clk: arm_clocks.cpu_1x_clk(),
|
|
||||||
};
|
|
||||||
pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
|
pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
|
||||||
Ok(pwm)
|
Ok(pwm)
|
||||||
}
|
}
|
||||||
@ -193,6 +232,11 @@ impl Pwm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ttc_channel_mut(&mut self) -> &mut TtcChannel {
|
||||||
|
&mut self.channel
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn max_duty_cycle(&self) -> u16 {
|
pub fn max_duty_cycle(&self) -> u16 {
|
||||||
self.channel
|
self.channel
|
||||||
@ -229,7 +273,7 @@ impl Pwm {
|
|||||||
.unwrap();
|
.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.
|
// Disable the counter first.
|
||||||
self.channel
|
self.channel
|
||||||
.regs
|
.regs
|
||||||
@ -244,8 +288,8 @@ impl Pwm {
|
|||||||
zynq7000::ttc::ClockControl::builder()
|
zynq7000::ttc::ClockControl::builder()
|
||||||
.with_ext_clk_edge(false)
|
.with_ext_clk_edge(false)
|
||||||
.with_clk_src(zynq7000::ttc::ClockSource::Pclk)
|
.with_clk_src(zynq7000::ttc::ClockSource::Pclk)
|
||||||
.with_prescaler(prescaler_reg)
|
.with_prescaler(prescaler_reg.unwrap_or(u4::new(0)))
|
||||||
.with_prescale_enable(prescaler_reg.value() > 0)
|
.with_prescale_enable(prescaler_reg.is_some())
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -266,10 +310,10 @@ impl Pwm {
|
|||||||
.write_cnt_ctrl(
|
.write_cnt_ctrl(
|
||||||
id,
|
id,
|
||||||
zynq7000::ttc::CounterControl::builder()
|
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_wave_enable_n(zynq7000::ttc::WaveEnable::Enable)
|
||||||
.with_reset(true)
|
.with_reset(true)
|
||||||
.with_match_enable(false)
|
.with_match_enable(true)
|
||||||
.with_decrementing(false)
|
.with_decrementing(false)
|
||||||
.with_mode(zynq7000::ttc::Mode::Interval)
|
.with_mode(zynq7000::ttc::Mode::Interval)
|
||||||
.with_disable(false)
|
.with_disable(false)
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
//! Triple-timer counter (TTC) register module.
|
//! Triple-timer counter (TTC) register module.
|
||||||
use arbitrary_int::u4;
|
use arbitrary_int::u4;
|
||||||
|
|
||||||
pub const TTC_0_BASE_ADDR: usize = 0xF800_1004;
|
pub const TTC_0_BASE_ADDR: usize = 0xF800_1000;
|
||||||
pub const TTC_1_BASE_ADDR: usize = 0xF800_2004;
|
pub const TTC_1_BASE_ADDR: usize = 0xF800_2000;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
pub enum ClockSource {
|
pub enum ClockSource {
|
||||||
/// PS internal bus clock.
|
/// PS internal bus clock.
|
||||||
|
#[default]
|
||||||
Pclk = 0b0,
|
Pclk = 0b0,
|
||||||
External = 0b1,
|
External = 0b1,
|
||||||
}
|
}
|
||||||
@ -25,13 +27,14 @@ pub struct ClockControl {
|
|||||||
prescale_enable: bool,
|
prescale_enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Overflow = 0b0,
|
Overflow = 0b0,
|
||||||
Interval = 0b1,
|
Interval = 0b1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
pub enum WavePolarity {
|
pub enum WavePolarity {
|
||||||
/// The waveform output goes from high to low on a match 0 interrupt and returns high on
|
/// 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,
|
LowToHighOnMatch1 = 0b1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
#[bitbybit::bitenum(u1, exhaustive = true)]
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
pub enum WaveEnable {
|
pub enum WaveEnable {
|
||||||
Enable = 0b0,
|
Enable = 0b0,
|
||||||
@ -158,6 +162,8 @@ pub struct Ttc {
|
|||||||
event_reg: [EventCount; 3],
|
event_reg: [EventCount; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Ttc>(), 0x84);
|
||||||
|
|
||||||
impl Ttc {
|
impl Ttc {
|
||||||
/// Create a new TTC MMIO instance for TTC0 at address [TTC_0_BASE_ADDR].
|
/// Create a new TTC MMIO instance for TTC0 at address [TTC_0_BASE_ADDR].
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user