diff --git a/examples/adc.rs b/examples/adc.rs new file mode 100644 index 0000000..573be97 --- /dev/null +++ b/examples/adc.rs @@ -0,0 +1,60 @@ +#![no_main] +#![no_std] + +use embedded_hal::digital::v2::*; +use msp430_rt::entry; +use msp430fr2x5x_hal::{ + adc::{AdcConfig, ClockDivider, Predivider, Resolution, SampleTime, SamplingRate}, + gpio::Batch, + pmm::Pmm, + watchdog::Wdt, +}; +use nb::block; +use panic_msp430 as _; + +// If pin 1.1 is between 1V and 2V, the LED on pin 1.0 should light up. +#[entry] +fn main() -> ! { + // Take peripherals and disable watchdog + let periph = msp430fr2355::Peripherals::take().unwrap(); + let _wdt = Wdt::constrain(periph.WDT_A); + + // Configure GPIO + let pmm = Pmm::new(periph.PMM); + let port1 = Batch::new(periph.P1).split(&pmm); + let mut led = port1.pin0.to_output(); + let mut adc_pin = port1.pin1.to_alternate3(); + + // ADC setup + let mut adc = AdcConfig::new( + ClockDivider::_1, + Predivider::_1, + Resolution::_8BIT, + SamplingRate::_50KSPS, + SampleTime::_4, + ) + .use_modclk() + .configure(periph.ADC); + + loop { + // Get ADC voltage, assuming the ADC reference voltage is 3300mV + // It's infallible besides nb::WouldBlock, so it's safe to unwrap after block!() + // If you want a raw count use adc.read() instead. + let reading_mv = block!( adc.read_voltage_mv(&mut adc_pin, 3300) ).unwrap(); + + // Turn on LED if voltage between 1000 and 2000mV + if (1000..=2000).contains(&reading_mv) { + led.set_high().ok(); + } else { + led.set_low().ok(); + } + } +} + +// The compiler will emit calls to the abort() compiler intrinsic if debug assertions are +// enabled (default for dev profile). MSP430 does not actually have meaningful abort() support +// so for now, we create our own in each application where debug assertions are present. +#[no_mangle] +extern "C" fn abort() -> ! { + panic!(); +} diff --git a/examples/blinky.rs b/examples/blinky.rs index 2cf9d3c..4941639 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -3,39 +3,40 @@ use embedded_hal::digital::v2::*; use msp430_rt::entry; -use msp430fr2x5x_hal::{gpio::Batch, pmm::Pmm, watchdog::Wdt}; +use msp430fr2x5x_hal::{ + clock::{ClockConfig, DcoclkFreqSel, MclkDiv, SmclkDiv}, + fram::Fram, + gpio::Batch, + hal::blocking::delay::DelayMs, + pmm::Pmm, + watchdog::Wdt, +}; use panic_msp430 as _; // Red onboard LED should blink at a steady period. -// Green onboard LED should go on when P2.3 button is pressed #[entry] fn main() -> ! { + // Take peripherals and disable watchdog let periph = msp430fr2355::Peripherals::take().unwrap(); let _wdt = Wdt::constrain(periph.WDT_A); + // Configure GPIO let pmm = Pmm::new(periph.PMM); - let p1 = Batch::new(periph.P1).split(&pmm); - let p2 = Batch::new(periph.P2) - .config_pin3(|p| p.pullup()) - .split(&pmm); - let p6 = Batch::new(periph.P6) - .config_pin6(|p| p.to_output()) - .split(&pmm); + let port1 = Batch::new(periph.P1).split(&pmm); + let mut p1_0 = port1.pin0.to_output(); - let mut p1_0 = p1.pin0.to_output(); - let p2_3 = p2.pin3; - let mut p6_6 = p6.pin6; + // Configure clocks to get accurate delay timing + let mut fram = Fram::new(periph.FRCTL); + let (_smclk, _aclk, mut delay) = ClockConfig::new(periph.CS) + .mclk_dcoclk(DcoclkFreqSel::_8MHz, MclkDiv::_1) + .smclk_on(SmclkDiv::_1) + .freeze(&mut fram); loop { + // `toggle()` returns a `Result` because of embedded_hal, but the result is always `Ok` with MSP430 GPIO. + // Rust complains about unused Results, so we 'use' the Result by calling .ok() p1_0.toggle().ok(); - - for _ in 0..5000 { - if p2_3.is_high().unwrap() { - p6_6.set_low().ok(); - } else { - p6_6.set_high().ok(); - } - } + delay.delay_ms(500); } } diff --git a/examples/capture.rs b/examples/capture.rs index cac31a9..86303cf 100644 --- a/examples/capture.rs +++ b/examples/capture.rs @@ -33,7 +33,7 @@ fn main() -> ! { .config_pin0(|p| p.to_output()) .split(&pmm); - let (smclk, aclk) = ClockConfig::new(periph.CS) + let (smclk, aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_1) .aclk_vloclk() diff --git a/examples/capture_intr.rs b/examples/capture_intr.rs index 9a3afb8..2857fb7 100644 --- a/examples/capture_intr.rs +++ b/examples/capture_intr.rs @@ -2,6 +2,8 @@ #![no_std] #![feature(abi_msp430_interrupt)] +// This example also demonstrates how to write panic-free code using panic_never. + use core::cell::UnsafeCell; use critical_section::with; use embedded_hal::digital::v2::ToggleableOutputPin; @@ -27,6 +29,7 @@ use panic_msp430 as _; #[cfg(not(debug_assertions))] use panic_never as _; +// We use UnsafeCell as a panic-free version of RefCell. If you aren't using `panic_never` then RefCell is more ergonomic. static CAPTURE: Mutex>>> = Mutex::new(UnsafeCell::new(None)); static VECTOR: Mutex>>> = @@ -38,38 +41,37 @@ static RED_LED: Mutex>>> = // so sometimes inputs are missed. #[entry] fn main() -> ! { - if let Some(periph) = msp430fr2355::Peripherals::take() { - let mut fram = Fram::new(periph.FRCTL); - Wdt::constrain(periph.WDT_A); - - let pmm = Pmm::new(periph.PMM); - let p1 = Batch::new(periph.P1) - .config_pin0(|p| p.to_output()) - .split(&pmm); - let red_led = p1.pin0; - - with(|cs| unsafe { *RED_LED.borrow(cs).get() = Some(red_led) }); - - let (_smclk, aclk) = ClockConfig::new(periph.CS) - .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) - .smclk_on(SmclkDiv::_1) - .aclk_vloclk() - .freeze(&mut fram); - - let captures = CaptureParts3::config(periph.TB0, TimerConfig::aclk(&aclk)) - .config_cap1_input_A(p1.pin6.to_alternate2()) - .config_cap1_trigger(CapTrigger::FallingEdge) - .commit(); - let mut capture = captures.cap1; - let vectors = captures.tbxiv; - - setup_capture(&mut capture); - with(|cs| { - unsafe { *CAPTURE.borrow(cs).get() = Some(capture) } - unsafe { *VECTOR.borrow(cs).get() = Some(vectors) } - }); - unsafe { enable() }; - } + let Some(periph) = msp430fr2355::Peripherals::take() else { loop {} }; + let mut fram = Fram::new(periph.FRCTL); + Wdt::constrain(periph.WDT_A); + + let pmm = Pmm::new(periph.PMM); + let p1 = Batch::new(periph.P1) + .config_pin0(|p| p.to_output()) + .split(&pmm); + let red_led = p1.pin0; + + with(|cs| unsafe { *RED_LED.borrow(cs).get() = Some(red_led) }); + + let (_smclk, aclk, _delay) = ClockConfig::new(periph.CS) + .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) + .smclk_on(SmclkDiv::_1) + .aclk_vloclk() + .freeze(&mut fram); + + let captures = CaptureParts3::config(periph.TB0, TimerConfig::aclk(&aclk)) + .config_cap1_input_A(p1.pin6.to_alternate2()) + .config_cap1_trigger(CapTrigger::FallingEdge) + .commit(); + let mut capture = captures.cap1; + let vectors = captures.tbxiv; + + setup_capture(&mut capture); + with(|cs| { + unsafe { *CAPTURE.borrow(cs).get() = Some(capture) } + unsafe { *VECTOR.borrow(cs).get() = Some(vectors) } + }); + unsafe { enable() }; loop {} } @@ -81,20 +83,15 @@ fn setup_capture, C>(capture: &mut Capture) { #[interrupt] fn TIMER0_B1() { with(|cs| { - if let Some(vector) = unsafe { &mut *VECTOR.borrow(cs).get() }.as_mut() { - if let Some(capture) = unsafe { &mut *CAPTURE.borrow(cs).get() }.as_mut() { - match vector.interrupt_vector() { - CaptureVector::Capture1(cap) => { - if cap.interrupt_capture(capture).is_ok() { - if let Some(led) = unsafe { &mut *RED_LED.borrow(cs).get() }.as_mut() { - led.toggle().void_unwrap(); - } - } - } - _ => {} - }; + let Some(vector) = unsafe { &mut *VECTOR.borrow(cs).get() }.as_mut() else { return; }; + let Some(capture) = unsafe { &mut *CAPTURE.borrow(cs).get() }.as_mut() else { return; }; + let Some(led) = unsafe { &mut *RED_LED.borrow(cs).get() }.as_mut() else { return; }; + + if let CaptureVector::Capture1(cap) = vector.interrupt_vector() { + if cap.interrupt_capture(capture).is_ok() { + led.toggle().void_unwrap(); } - } + }; }); } diff --git a/examples/clocks.rs b/examples/clocks.rs index 78c60d1..723d129 100644 --- a/examples/clocks.rs +++ b/examples/clocks.rs @@ -29,7 +29,7 @@ fn main() -> ! { .split(&pmm); let mut p1_0 = p1.pin0; - let (smclk, _aclk) = ClockConfig::new(periph.CS) + let (smclk, _aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_8MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_1) .aclk_vloclk() diff --git a/examples/echo.rs b/examples/echo.rs index 7f19ee9..24b9708 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -28,7 +28,7 @@ fn main() -> ! { let mut fram = Fram::new(periph.FRCTL); let _wdt = Wdt::constrain(periph.WDT_A); - let (_smclk, aclk) = ClockConfig::new(periph.CS) + let (_smclk, aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_2) .aclk_refoclk() diff --git a/examples/gpio.rs b/examples/gpio.rs new file mode 100644 index 0000000..498c8b5 --- /dev/null +++ b/examples/gpio.rs @@ -0,0 +1,41 @@ +#![no_main] +#![no_std] + +use embedded_hal::digital::v2::*; +use msp430_rt::entry; +use msp430fr2x5x_hal::{gpio::Batch, pmm::Pmm, watchdog::Wdt}; +use panic_msp430 as _; + +// Green onboard LED should go on when P2.3 button is pressed +#[entry] +fn main() -> ! { + let periph = msp430fr2355::Peripherals::take().unwrap(); + let _wdt = Wdt::constrain(periph.WDT_A); + + let pmm = Pmm::new(periph.PMM); + let p2 = Batch::new(periph.P2) + .config_pin3(|p| p.pullup()) + .split(&pmm); + let p6 = Batch::new(periph.P6) + .config_pin6(|p| p.to_output()) + .split(&pmm); + + let p2_3 = p2.pin3; + let mut p6_6 = p6.pin6; + + loop { + if p2_3.is_high().unwrap() { + p6_6.set_low().ok(); + } else { + p6_6.set_high().ok(); + } + } +} + +// The compiler will emit calls to the abort() compiler intrinsic if debug assertions are +// enabled (default for dev profile). MSP430 does not actually have meaningful abort() support +// so for now, we create our own in each application where debug assertions are present. +#[no_mangle] +extern "C" fn abort() -> ! { + panic!(); +} diff --git a/examples/gpio_interrupts.rs b/examples/gpio_interrupts.rs index 64168f5..5c910b4 100644 --- a/examples/gpio_interrupts.rs +++ b/examples/gpio_interrupts.rs @@ -28,7 +28,7 @@ static P2IV: Mutex>>> = Mutex::new(RefCell::new(None)); #[entry] fn main() -> ! { let periph = msp430fr2355::Peripherals::take().unwrap(); - let (_smclk, aclk) = ClockConfig::new(periph.CS) + let (_smclk, aclk, _delay) = ClockConfig::new(periph.CS) .mclk_refoclk(MclkDiv::_1) // 32 KHz SMCLK .smclk_on(SmclkDiv::_2) @@ -53,8 +53,8 @@ fn main() -> ! { let mut green_led = p6.pin6; let p2iv = p2.pxiv; - with(|cs| *RED_LED.borrow(cs).borrow_mut() = Some(red_led)); - with(|cs| *P2IV.borrow(cs).borrow_mut() = Some(p2iv)); + with(|cs| RED_LED.borrow_ref_mut(cs).replace(red_led)); + with(|cs| P2IV.borrow_ref_mut(cs).replace(p2iv)); wdt.set_aclk(&aclk) .enable_interrupts() @@ -74,25 +74,19 @@ fn main() -> ! { #[interrupt] fn PORT2() { with(|cs| { - RED_LED.borrow(cs).borrow_mut().as_mut().map(|red_led| { - match P2IV - .borrow(cs) - .borrow_mut() - .as_mut() - .unwrap() - .get_interrupt_vector() - { - GpioVector::Pin7Isr => red_led.toggle().ok(), - _ => panic!(), - } - }) + let Some(ref mut red_led) = *RED_LED.borrow_ref_mut(cs) else { return; }; + let Some(ref mut p2iv) = *P2IV.borrow_ref_mut(cs) else { return; }; + + if let GpioVector::Pin7Isr = p2iv.get_interrupt_vector() { + red_led.toggle().ok(); + } }); } #[interrupt] fn WDT() { with(|cs| { - RED_LED.borrow(cs).borrow_mut().as_mut().map(|red_led| { + RED_LED.borrow_ref_mut(cs).as_mut().map(|red_led| { red_led.toggle().ok(); }) }); diff --git a/examples/i2c.rs b/examples/i2c.rs new file mode 100644 index 0000000..204a982 --- /dev/null +++ b/examples/i2c.rs @@ -0,0 +1,61 @@ +#![no_main] +#![no_std] + +use embedded_hal::blocking::{i2c::{Read, Write, WriteRead}, delay::DelayMs}; +use msp430_rt::entry; +use msp430fr2x5x_hal::{ + clock::{ClockConfig, DcoclkFreqSel, MclkDiv, SmclkDiv}, fram::Fram, gpio::Batch, i2c::{GlitchFilter, I2CBusConfig}, pmm::Pmm, watchdog::Wdt +}; +use panic_msp430 as _; + +#[entry] +fn main() -> ! { + let periph = msp430fr2355::Peripherals::take().unwrap(); + + let mut fram = Fram::new(periph.FRCTL); + let _wdt = Wdt::constrain(periph.WDT_A); + + let pmm = Pmm::new(periph.PMM); + let p4 = Batch::new(periph.P4) + .split(&pmm); + let scl = p4.pin7.to_alternate1(); + let sda = p4.pin6.to_alternate1(); + + let (smclk, _aclk, mut delay) = ClockConfig::new(periph.CS) + .mclk_dcoclk(DcoclkFreqSel::_8MHz, MclkDiv::_1) + .smclk_on(SmclkDiv::_1) + .aclk_vloclk() + .freeze(&mut fram); + + let mut i2c = I2CBusConfig::new(periph.E_USCI_B1, GlitchFilter::Max50ns) + .use_smclk(&smclk, 80) // 8MHz / 10 = 100kHz + .configure(scl, sda); + + loop { + // Blocking read. Read 10 bytes (length of buffer) from address 0x12. + // Pass a u8 address for 7-bit addressing mode, pass a u16 for 10-bit addressing mode. + let mut buf = [0; 10]; + // You should handle errors here rather than unwrapping + i2c.read(0x12_u8, &mut buf).unwrap(); + + // Blocking write. Write one byte to address 0x12. + // You should handle errors here rather than unwrapping + i2c.write(0x12_u8, &[0b10101010]).unwrap(); + + // Blocking send + recieve. Write 10 bytes to 0x12, then read 20 bytes + let send_buf = [0b11001100; 10]; + let mut recv_buf = [0; 20]; + // You should handle errors here rather than unwrapping + i2c.write_read(0x12_u8, &send_buf, &mut recv_buf).unwrap(); + + delay.delay_ms(1000); + } +} + +// The compiler will emit calls to the abort() compiler intrinsic if debug assertions are +// enabled (default for dev profile). MSP430 does not actually have meaningful abort() support +// so for now, we create our own in each application where debug assertions are present. +#[no_mangle] +extern "C" fn abort() -> ! { + panic!(); +} diff --git a/examples/loopback.rs b/examples/loopback.rs index f3230d8..d294f2c 100644 --- a/examples/loopback.rs +++ b/examples/loopback.rs @@ -54,7 +54,7 @@ fn main() -> ! { let mut fram = Fram::new(periph.FRCTL); let _wdt = Wdt::constrain(periph.WDT_A); - let (smclk, _aclk) = ClockConfig::new(periph.CS) + let (smclk, _aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_4MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_2) .aclk_refoclk() diff --git a/examples/pwm.rs b/examples/pwm.rs index b799724..f39102a 100644 --- a/examples/pwm.rs +++ b/examples/pwm.rs @@ -24,7 +24,7 @@ fn main() -> ! { let pmm = Pmm::new(periph.PMM); let p6 = Batch::new(periph.P6).split(&pmm); - let (smclk, _aclk) = ClockConfig::new(periph.CS) + let (smclk, _aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_1) .aclk_vloclk() diff --git a/examples/rtc.rs b/examples/rtc.rs index f2f500f..5939133 100644 --- a/examples/rtc.rs +++ b/examples/rtc.rs @@ -33,7 +33,7 @@ fn main() -> ! { let mut led = p1.pin0; let mut button = p2.pin3; - let (_smclk, _aclk) = ClockConfig::new(periph.CS) + let (_smclk, _aclk, _delay) = ClockConfig::new(periph.CS) .mclk_refoclk(MclkDiv::_1) .smclk_on(SmclkDiv::_1) .aclk_vloclk() diff --git a/examples/spi.rs b/examples/spi.rs new file mode 100644 index 0000000..1b5f695 --- /dev/null +++ b/examples/spi.rs @@ -0,0 +1,66 @@ +#![no_main] +#![no_std] + +use embedded_hal::spi::FullDuplex; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::spi::MODE_0; +use embedded_hal::blocking::delay::DelayMs; +use msp430_rt::entry; +use msp430fr2x5x_hal::{ + clock::{ClockConfig, DcoclkFreqSel, MclkDiv, SmclkDiv}, fram::Fram, gpio::Batch, pmm::Pmm, spi::SpiBusConfig, watchdog::Wdt +}; +use nb::block; +use panic_msp430 as _; + +#[entry] +fn main() -> ! { + let periph = msp430fr2355::Peripherals::take().unwrap(); + + let mut fram = Fram::new(periph.FRCTL); + let _wdt = Wdt::constrain(periph.WDT_A); + + let pmm = Pmm::new(periph.PMM); + let p1 = Batch::new(periph.P1) + .split(&pmm); + let miso = p1.pin7.to_alternate1(); + let mosi = p1.pin6.to_alternate1(); + let sck = p1.pin5.to_alternate1(); + let cs = p1.pin4.to_alternate1(); + + let (smclk, _aclk, mut delay) = ClockConfig::new(periph.CS) + .mclk_dcoclk(DcoclkFreqSel::_8MHz, MclkDiv::_1) + .smclk_on(SmclkDiv::_1) + .aclk_vloclk() + .freeze(&mut fram); + + let mut spi = SpiBusConfig::new(periph.E_USCI_A0, MODE_0, true) + .use_smclk(&smclk, 16) // 8MHz / 16 = 500kHz + .configure_with_hardware_cs(miso, mosi, sck, cs); + + loop { + // Non-blocking send. Sending is infallible, besides `nb::WouldBlock` when the bus is busy. + // In this particular case we know the SPI bus isn't busy, so unwrapping is safe here. + spi.send(0b10101010).unwrap(); + + // Wait for the above send to complete. Wrap the non-blocking `.read()` with `block!()` to make it blocking. + // Note `.read()` only checks the recieve buffer, it does not send any SPI packets, + // so if you haven't previously used `.send()` there will be nothing to read. + // You should handle errors here rather than unwrapping + let _data = block!(spi.read()).unwrap(); + + // Multi-byte blocking send + recieve example + let mut send_recv_buf = [0b11001100; 10]; + // You should handle errors here rather than unwrapping + spi.transfer(&mut send_recv_buf).unwrap(); + + delay.delay_ms(1000); + } +} + +// The compiler will emit calls to the abort() compiler intrinsic if debug assertions are +// enabled (default for dev profile). MSP430 does not actually have meaningful abort() support +// so for now, we create our own in each application where debug assertions are present. +#[no_mangle] +extern "C" fn abort() -> ! { + panic!(); +} diff --git a/examples/timer.rs b/examples/timer.rs index 7726778..61511e7 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -30,7 +30,7 @@ fn main() -> ! { .split(&pmm); let mut p1_0 = p1.pin0; - let (_smclk, aclk) = ClockConfig::new(periph.CS) + let (_smclk, aclk, _delay) = ClockConfig::new(periph.CS) .mclk_dcoclk(DcoclkFreqSel::_1MHz, MclkDiv::_1) .smclk_on(SmclkDiv::_1) .aclk_vloclk() diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..ba64884 --- /dev/null +++ b/run.bat @@ -0,0 +1,14 @@ +@REM echo off +set arg1=%1 +@REM mspdebug --allow-fw-update tilib "prog %arg1%" +@REM mspdebug --allow-fw-update tilib "run" + +@REM mspdebug --allow-fw-update tilib +@REM prog %arg1% +@REM run + +echo prog %arg1% > temp_commands.txt +echo run >> temp_commands.txt + +mspdebug --allow-fw-update tilib < temp_commands.txt +del temp_commands.txt diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 0000000..39e6d5c --- /dev/null +++ b/src/adc.rs @@ -0,0 +1,477 @@ +//! Analog to Digital Converter (ADC) +//! +//! Begin configuration by calling `AdcConfig::new()` or `::default()`. Once fully configured an `Adc` will be returned. +//! +//! `Adc` can read from a channel by calling `.read()` (an implementation of the embedded_hal `OneShot` trait) to return an ADC count. +//! +//! The `.count_to_mv()` method is available to convert an ADC count to a voltage in millivolts, given a reference voltage. +//! +//! As a convenience, `.read_voltage_mv()` combines `.read()` and `.count_to_mv()`. +//! +//! Currently the only supported ADC voltage reference is `AVCC`, the operating voltage of the MSP430. +//! +//! The ADC may read from any of the following pins: +//! +//! P1.0 - P1.7 (channels 0 to 7), P5.0 - P5.3 (channels 8 to 11). +//! +//! ADC channels 12 to 15 are not associated with external pins, so in lieu of a pin use the `static`s below. +//! + +use crate::{clock::{Aclk, Smclk}, gpio::*}; +use core::convert::Infallible; +use embedded_hal::adc::{Channel, OneShot}; +use msp430fr2355::ADC; + +/// How many ADCCLK cycles the ADC's sample-and-hold stage will last for. +/// +/// Default: 8 cycles +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum SampleTime { + /// Sample for 4 ADCCLK cycles + _4 = 0b0000, + /// Sample for 8 ADCCLK cycles + #[default] + _8 = 0b0001, + /// Sample for 16 ADCCLK cycles + _16 = 0b0010, + /// Sample for 32 ADCCLK cycles + _32 = 0b0011, + /// Sample for 64 ADCCLK cycles + _64 = 0b0100, + /// Sample for 96 ADCCLK cycles + _96 = 0b0101, + /// Sample for 128 ADCCLK cycles + _128 = 0b0110, + /// Sample for 192 ADCCLK cycles + _192 = 0b0111, + /// Sample for 256 ADCCLK cycles + _256 = 0b1000, + /// Sample for 384 ADCCLK cycles + _384 = 0b1001, + /// Sample for 512 ADCCLK cycles + _512 = 0b1010, + /// Sample for 768 ADCCLK cycles + _768 = 0b1011, + /// Sample for 1024 ADCCLK cycles + _1024 = 0b1100, +} + +impl SampleTime { + #[inline(always)] + fn adcsht(self) -> u8 { + self as u8 + } +} + +/// How much the ADC input clock will be divided by after being divided by the predivider +/// +/// Default: Divide by 1 +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum ClockDivider { + /// Divide the input clock by 1 + #[default] + _1 = 0b000, + /// Divide the input clock by 2 + _2 = 0b001, + /// Divide the input clock by 3 + _3 = 0b010, + /// Divide the input clock by 4 + _4 = 0b011, + /// Divide the input clock by 5 + _5 = 0b100, + /// Divide the input clock by 6 + _6 = 0b101, + /// Divide the input clock by 7 + _7 = 0b110, + /// Divide the input clock by 8 + _8 = 0b111, +} + +impl ClockDivider { + #[inline(always)] + fn adcdiv(self) -> u8 { + self as u8 + } +} + +#[derive(Default, Copy, Clone, PartialEq, Eq)] +enum ClockSource { + /// Use MODCLK as the ADC input clock + #[default] + ModClk = 0b00, + /// Use ACLK as the ADC input clock + AClk = 0b01, + /// Use SMCLK as the ADC input clock + SmClk = 0b10, +} + +impl ClockSource { + #[inline(always)] + fn adcssel(self) -> u8 { + self as u8 + } +} + +/// How much the ADC input clock will be divided by prior to being divided by the ADC clock divider +/// +/// Default: Divide by 1 +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum Predivider { + /// Divide the input clock by 1 + #[default] + _1 = 0b00, + /// Divide the input clock by 4 + _4 = 0b01, + /// Divide the input clock by 64 + _64 = 0b10, +} + +impl Predivider { + #[inline(always)] + fn adcpdiv(self) -> u8 { + self as u8 + } +} + +/// The output resolution of the ADC conversion. Also determines how many ADCCLK cycles the conversion step takes. +/// +/// Default: 10-bit resolution +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum Resolution { + /// 8-bit ADC conversion result. The conversion step takes 10 ADCCLK cycles. + _8BIT = 0b00, + /// 10-bit ADC conversion result. The conversion step takes 12 ADCCLK cycles. + #[default] + _10BIT = 0b01, + /// 12-bit ADC conversion result. The conversion step takes 14 ADCCLK cycles. + _12BIT = 0b10, +} + +impl Resolution { + #[inline(always)] + fn adcres(self) -> u8 { + self as u8 + } +} + +/// Selects the drive capability of the ADC reference buffer, which can increase the maximum sampling speed at the cost of increased power draw. +/// +/// Default: 200ksps +#[derive(Default, Copy, Clone, PartialEq, Eq)] +pub enum SamplingRate { + /// Maximum of 50 ksps. Lower power usage. + _50KSPS, + /// Maximum of 200 ksps. Higher power usage. + #[default] + _200KSPS, +} + +impl SamplingRate { + #[inline(always)] + fn adcsr(self) -> bool { + match self { + SamplingRate::_200KSPS => false, + SamplingRate::_50KSPS => true, + } + } +} + +// Pins corresponding to an ADC channel. Pin types can have `::channel()` called on them to get their ADC channel index. +macro_rules! impl_adc_channel_pin { + ($port: ty, $pin: ty, $channel: literal ) => { + impl Channel for Pin<$port, $pin, Alternate3>> { + type ID = u8; + + fn channel() -> Self::ID { + $channel + } + } + }; +} + +impl_adc_channel_pin!(P1, Pin0, 0); +impl_adc_channel_pin!(P1, Pin1, 1); +impl_adc_channel_pin!(P1, Pin2, 2); +impl_adc_channel_pin!(P1, Pin3, 3); +impl_adc_channel_pin!(P1, Pin4, 4); +impl_adc_channel_pin!(P1, Pin5, 5); +impl_adc_channel_pin!(P1, Pin6, 6); +impl_adc_channel_pin!(P1, Pin7, 7); +impl_adc_channel_pin!(P5, Pin0, 8); +impl_adc_channel_pin!(P5, Pin1, 9); +impl_adc_channel_pin!(P5, Pin2, 10); +impl_adc_channel_pin!(P5, Pin3, 11); + +// A few ADC channels don't correspond to pins. +macro_rules! impl_adc_channel_extra { + ($type: ty, $channel: literal ) => { + impl Channel for $type { + type ID = u8; + + fn channel() -> Self::ID { + $channel + } + } + }; +} + +// We instead document the statics below. Users needn't deal with the structs themselves so it just adds noise to the docs. +#[doc(hidden)] +pub struct AdcTempSenseChannel; +impl_adc_channel_extra!(AdcTempSenseChannel, 12); +/// ADC channel 12, tied to the internal temperature sensor. Pass this to `adc.read()` to read this channel. +pub static ADC_CH12_TEMP_SENSE: AdcTempSenseChannel = AdcTempSenseChannel; + +#[doc(hidden)] +pub struct AdcVrefChannel; +impl_adc_channel_extra!(AdcVrefChannel, 13); +/// ADC channel 13, tied to the internal reference voltage. Pass this to `adc.read()` to read this channel. +pub static ADC_CH13_VREF: AdcVrefChannel = AdcVrefChannel; + +#[doc(hidden)] +pub struct AdcVssChannel; +impl_adc_channel_extra!(AdcVssChannel, 14); +/// ADC channel 14, tied to VSS. Pass this to `adc.read()` to read this channel. +pub static ADC_CH14_VSS: AdcVssChannel = AdcVssChannel; + +#[doc(hidden)] +pub struct AdcVccChannel; +impl_adc_channel_extra!(AdcVccChannel, 15); +/// ADC channel 15, tied to VCC. Pass this to `adc.read()` to read this channel. +pub static ADC_CH15_VCC: AdcVccChannel = AdcVccChannel; + +/// Typestate for an ADC configuration with no clock source selected +pub struct NoClockSet; +/// Typestate for an ADC configuration with a clock source selected +pub struct ClockSet(ClockSource); + +/// Configuration object for an ADC. +/// +/// Currently the only supported voltage reference is AVCC. +/// +/// The default configuration is based on the default register values: +/// - Predivider = 1 and clock divider = 1 +/// - 10-bit resolution +/// - 8 cycle sample time +/// - Max 200 ksps sample rate +#[derive(Clone, PartialEq, Eq)] +pub struct AdcConfig { + state: STATE, + /// How much the input clock is divided by, after the predivider. + pub clock_divider: ClockDivider, + /// How much the input clock is initially divided by, before the clock divider. + pub predivider: Predivider, + /// How many bits the conversion result is. Also defines the number of ADCCLK cycles required to do the conversion step. + pub resolution: Resolution, + /// Sets the maximum sampling rate of the ADC. Lower values use less power. + pub sampling_rate: SamplingRate, + /// Determines the number of ADCCLK cycles the sampling time takes. + pub sample_time: SampleTime, +} + +// Only implement Default for NoClockSet +impl Default for AdcConfig { + fn default() -> Self { + Self { + state: NoClockSet, + clock_divider: Default::default(), + predivider: Default::default(), + resolution: Default::default(), + sampling_rate: Default::default(), + sample_time: Default::default(), + } + } +} + +impl AdcConfig { + /// Creates an ADC configuration. A default implementation is also available through `::default()` + pub fn new( + clock_divider: ClockDivider, + predivider: Predivider, + resolution: Resolution, + sampling_rate: SamplingRate, + sample_time: SampleTime, + ) -> AdcConfig { + AdcConfig { + state: NoClockSet, + clock_divider, + predivider, + resolution, + sampling_rate, + sample_time, + } + } + /// Configure the ADC to use SMCLK + pub fn use_smclk(self, _smclk: &Smclk) -> AdcConfig{ + AdcConfig { + state: ClockSet(ClockSource::SmClk), + clock_divider: self.clock_divider, + predivider: self.predivider, + resolution: self.resolution, + sampling_rate: self.sampling_rate, + sample_time: self.sample_time, + } + } + /// Configure the ADC to use ACLK + pub fn use_aclk(self, _aclk: &Aclk) -> AdcConfig{ + AdcConfig { + state: ClockSet(ClockSource::AClk), + clock_divider: self.clock_divider, + predivider: self.predivider, + resolution: self.resolution, + sampling_rate: self.sampling_rate, + sample_time: self.sample_time, + } + } + /// Configure the ADC to use MODCLK + pub fn use_modclk(self) -> AdcConfig{ + AdcConfig { + state: ClockSet(ClockSource::ModClk), + clock_divider: self.clock_divider, + predivider: self.predivider, + resolution: self.resolution, + sampling_rate: self.sampling_rate, + sample_time: self.sample_time, + } + } +} +impl AdcConfig { + /// Applies this ADC configuration to hardware registers, and returns an ADC. + pub fn configure(self, mut adc_reg: ADC) -> Adc { + // Disable the ADC before we set the other bits. Some can only be set while the ADC is disabled. + disable_adc_reg(&mut adc_reg); + + let adcsht = self.sample_time.adcsht(); + adc_reg.adcctl0.write(|w| w.adcsht().bits(adcsht)); + + let adcssel = self.state.0.adcssel(); + let adcdiv = self.clock_divider.adcdiv(); + adc_reg.adcctl1.write(|w| {w + .adcssel().bits(adcssel) + .adcshp().adcshp_1() + .adcdiv().bits(adcdiv) + }); + + let adcpdiv = self.predivider.adcpdiv(); + let adcres = self.resolution.adcres(); + let adcsr = self.sampling_rate.adcsr(); + adc_reg.adcctl2.write(|w| { w + .adcpdiv().bits(adcpdiv) + .adcres().bits(adcres) + .adcsr().bit(adcsr) + }); + + Adc { + adc_reg, + is_waiting: false, + } + } +} + +/// Controls the onboard ADC. The `read()` method is available through the embedded_hal `OneShot` trait. +pub struct Adc { + adc_reg: ADC, + is_waiting: bool, +} + +impl Adc { + /// Whether the ADC is currently sampling or converting. + pub fn adc_is_busy(&self) -> bool { + self.adc_reg.adcctl1.read().adcbusy().bit_is_set() + } + + /// Gets the latest ADC conversion result. + pub fn adc_get_result(&self) -> u16 { + self.adc_reg.adcmem0.read().bits() + } + + /// Enables this ADC, ready to start conversions. + pub fn enable(&mut self) { + unsafe { + self.adc_reg.adcctl0.set_bits(|w| w.adcon().set_bit()); + } + } + + /// Disables this ADC to save power. + pub fn disable(&mut self) { + disable_adc_reg(&mut self.adc_reg); + } + + /// Selects which pin to sample. + fn set_pin(&mut self, _pin: &PIN) + where + PIN: Channel, + { + self.adc_reg + .adcmctl0 + .modify(|_, w| w.adcinch().bits(PIN::channel())); + } + + /// Starts an ADC conversion. + fn start_conversion(&mut self) { + unsafe { + self.adc_reg.adcctl0.set_bits(|w| w + .adcenc().set_bit() + .adcsc().set_bit()); + } + } + + /// Convert an ADC count to a voltage value in millivolts. + /// + /// `ref_voltage_mv` is the reference voltage of the ADC in millivolts. + pub fn count_to_mv(&self, count: u16, ref_voltage_mv: u16) -> u16 { + use crate::pac::adc::adcctl2::ADCRES_A; + let resolution = match self.adc_reg.adcctl2.read().adcres().variant() { + ADCRES_A::ADCRES_0 => 256, // 8-bit + ADCRES_A::ADCRES_1 => 1024, // 10-bit + ADCRES_A::ADCRES_2 => 4096, // 12-bit + ADCRES_A::ADCRES_3 => 4096, // Reserved, unreachable + }; + ((count as u32 * ref_voltage_mv as u32) / resolution) as u16 + } + + /// Begins a single ADC conversion if one isn't already underway, enabling the ADC in the process. + /// + /// If the result is ready it is returned as a voltage in millivolts based on `ref_voltage_mv`, otherwise returns `WouldBlock`. + /// + /// If you instead want a raw count you should use the `.read()` method from the `OneShot` trait implementation. + pub fn read_voltage_mv>(&mut self, pin: &mut PIN, ref_voltage_mv: u16) -> nb::Result { + self.read(pin).map(|count| self.count_to_mv(count, ref_voltage_mv)) + } +} + +fn disable_adc_reg(adc: &mut ADC) { + unsafe { + adc.adcctl0.clear_bits(|w| w + .adcon().clear_bit() + .adcenc().clear_bit()); + } +} + +impl OneShot for Adc +where + PIN: Channel, +{ + type Error = Infallible; // Only returns WouldBlock + + /// Begins a single ADC conversion if one isn't already underway, enabling the ADC in the process. + /// + /// If the result is ready it is returned as an ADC count, otherwise returns `WouldBlock` + fn read(&mut self, pin: &mut PIN) -> nb::Result { + if self.is_waiting { + if self.adc_is_busy() { + return Err(nb::Error::WouldBlock); + } else { + self.is_waiting = false; + return Ok(self.adc_get_result()); + } + } + self.disable(); + self.set_pin(pin); + self.enable(); + + self.start_conversion(); + self.is_waiting = true; + Err(nb::Error::WouldBlock) + } +} diff --git a/src/capture.rs b/src/capture.rs index b27b186..a7b94fe 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -31,10 +31,10 @@ pub enum CapTrigger { BothEdges, } -impl Into for CapTrigger { +impl From for Cm { #[inline] - fn into(self) -> Cm { - match self { + fn from(val: CapTrigger) -> Self { + match val { CapTrigger::RisingEdge => Cm::RisingEdge, CapTrigger::FallingEdge => Cm::FallingEdge, CapTrigger::BothEdges => Cm::BothEdges, @@ -206,9 +206,9 @@ impl CaptureConfig3 { pub fn commit(self) -> CaptureParts3 { let timer = self.timer; self.config.write_regs(&timer); - CCRn::::config_cap_mode(&timer, self.cap0.trigger.into(), self.cap0.select.into()); - CCRn::::config_cap_mode(&timer, self.cap1.trigger.into(), self.cap1.select.into()); - CCRn::::config_cap_mode(&timer, self.cap2.trigger.into(), self.cap2.select.into()); + CCRn::::config_cap_mode(&timer, self.cap0.trigger.into(), self.cap0.select); + CCRn::::config_cap_mode(&timer, self.cap1.trigger.into(), self.cap1.select); + CCRn::::config_cap_mode(&timer, self.cap2.trigger.into(), self.cap2.select); timer.continuous(); CaptureParts3 { @@ -313,13 +313,13 @@ impl CaptureConfig7 { pub fn commit(self) -> CaptureParts7 { let timer = self.timer; self.config.write_regs(&timer); - CCRn::::config_cap_mode(&timer, self.cap0.trigger.into(), self.cap0.select.into()); - CCRn::::config_cap_mode(&timer, self.cap1.trigger.into(), self.cap1.select.into()); - CCRn::::config_cap_mode(&timer, self.cap2.trigger.into(), self.cap2.select.into()); - CCRn::::config_cap_mode(&timer, self.cap3.trigger.into(), self.cap3.select.into()); - CCRn::::config_cap_mode(&timer, self.cap4.trigger.into(), self.cap4.select.into()); - CCRn::::config_cap_mode(&timer, self.cap5.trigger.into(), self.cap5.select.into()); - CCRn::::config_cap_mode(&timer, self.cap6.trigger.into(), self.cap6.select.into()); + CCRn::::config_cap_mode(&timer, self.cap0.trigger.into(), self.cap0.select); + CCRn::::config_cap_mode(&timer, self.cap1.trigger.into(), self.cap1.select); + CCRn::::config_cap_mode(&timer, self.cap2.trigger.into(), self.cap2.select); + CCRn::::config_cap_mode(&timer, self.cap3.trigger.into(), self.cap3.select); + CCRn::::config_cap_mode(&timer, self.cap4.trigger.into(), self.cap4.select); + CCRn::::config_cap_mode(&timer, self.cap5.trigger.into(), self.cap5.select); + CCRn::::config_cap_mode(&timer, self.cap6.trigger.into(), self.cap6.select); timer.continuous(); CaptureParts7 { diff --git a/src/clock.rs b/src/clock.rs index 90e7bb5..b7289fa 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -10,6 +10,7 @@ use core::arch::asm; +use crate::delay::Delay; use crate::fram::{Fram, WaitStates}; use msp430fr2355 as pac; use pac::cs::csctl1::DCORSEL_A; @@ -275,6 +276,13 @@ impl ClockConfig { // Run FLL configuration procedure from the user's guide if we are using DCO if let MclkSel::Dcoclk(target_freq) = self.mclk.0 { fll_off(); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); + self.periph.csctl3.write(|w| w.selref().refoclk()); self.periph.csctl0.write(|w| unsafe { w.bits(0) }); self.periph @@ -286,6 +294,10 @@ impl ClockConfig { ._1() }); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); + msp430::asm::nop(); msp430::asm::nop(); msp430::asm::nop(); msp430::asm::nop(); @@ -327,9 +339,10 @@ impl ClockConfig { } impl ClockConfig { - /// Apply clock configuration to hardware and return SMCLK and ACLK clock objects + /// Apply clock configuration to hardware and return SMCLK and ACLK clock objects. + /// Also returns delay provider #[inline] - pub fn freeze(self, fram: &mut Fram) -> (Smclk, Aclk) { + pub fn freeze(self, fram: &mut Fram) -> (Smclk, Aclk, Delay) { let mclk_freq = self.mclk.0.freq() >> (self.mclk_div as u32); unsafe { Self::configure_fram(fram, mclk_freq) }; self.configure_dco_fll(); @@ -337,19 +350,21 @@ impl ClockConfig { ( Smclk(mclk_freq >> (self.smclk.0 as u32)), Aclk(self.aclk_sel.freq()), + Delay::new(mclk_freq), ) } } impl ClockConfig { - /// Apply clock configuration to hardware and return ACLK clock object, as SMCLK is disabled + /// Apply clock configuration to hardware and return ACLK clock object, as SMCLK is disabled. + /// Also returns delay provider. #[inline] - pub fn freeze(self, fram: &mut Fram) -> Aclk { + pub fn freeze(self, fram: &mut Fram) -> (Aclk, Delay) { let mclk_freq = self.mclk.0.freq() >> (self.mclk_div as u32); self.configure_dco_fll(); unsafe { Self::configure_fram(fram, mclk_freq) }; self.configure_cs(); - Aclk(self.aclk_sel.freq()) + (Aclk(self.aclk_sel.freq()), Delay::new(mclk_freq)) } } diff --git a/src/delay.rs b/src/delay.rs new file mode 100644 index 0000000..6e5f273 --- /dev/null +++ b/src/delay.rs @@ -0,0 +1,31 @@ +//! Embedded hal delay implementation +use crate::hal::blocking::delay::DelayMs; +use msp430::asm; + +/// Delay provider struct +#[derive(Copy, Clone)] +pub struct Delay { + nops_per_ms: u16, +} + +impl Delay { + /// Create a new delay object + pub(crate) fn new(freq: u32) -> Self { + // ~21 nops needed per 2^20 Hz to delay 1 ms + let nops: u32 = 210 * (freq >> 20); + Delay { + nops_per_ms: (nops as u16), + } + } +} + +impl DelayMs for Delay { + #[inline] + fn delay_ms(&mut self, ms: u16) { + for _ in 0..ms { + for _ in 0..self.nops_per_ms { + asm::nop(); + } + } + } +} diff --git a/src/hw_traits/eusci.rs b/src/hw_traits/eusci.rs index 0798cf6..d9ce8b2 100644 --- a/src/hw_traits/eusci.rs +++ b/src/hw_traits/eusci.rs @@ -1,13 +1,99 @@ use super::Steal; +use embedded_hal::spi::{Mode, Phase, Polarity}; use msp430fr2355 as pac; +/// Defines macros for a register associated struct to make reading/writing to this struct's +/// register a lot less tedious. +/// +/// The $macro_rd and $macro_wr inputs are needed due to a limitation in Rust's macro parsing +/// where it isn't able to create new tokens. +macro_rules! reg_struct { + ( + $(#[$attr:meta])* + pub struct $struct_name : ident, $macro_rd : ident, $macro_wr : ident { + $(flags{ + $(pub $bool_name : ident : bool, $(#[$f_attr:meta])*)* + })? + $(enums{ + $(pub $val_name : ident : $val_type : ty : $size : ty, $(#[$e_attr:meta])*)* + })? + $(ints{ + $(pub $int_name : ident : $int_size : ty, $(#[$i_attr:meta])*)* + })? + } + ) => { + $(#[$attr:meta])* + #[derive(Copy, Clone)] + pub struct $struct_name { + $($(pub $bool_name : bool, $(#[$f_attr])*)*)? + $($(pub $val_name : $val_type , $(#[$e_attr])*)*)? + $($(pub $int_name : $int_size , $(#[$i_attr])*)*)? + } + + #[allow(unused_macros)] + macro_rules! $macro_wr { + ($reg : expr) => { |w| + w$($(.$bool_name().bit($reg.$bool_name))*)? + $($(.$val_name().bits($reg.$val_name as $size))*)? + $($(.$int_name().bits($reg.$int_name as $int_size))*)? + }; + } + }; +} + +#[derive(Copy, Clone)] pub enum Ucssel { - Uclk, - Aclk, - Smclk, + Uclk = 0, + Aclk = 1, + Smclk = 2, +} + +#[derive(Copy, Clone)] +pub enum Ucmode { + ThreePinSPI = 0, + FourPinSPI1 = 1, + FourPinSPI0 = 2, + I2CMode = 3, +} + +#[derive(Copy, Clone)] +pub enum Ucglit { + Max50ns = 0, + Max25ns = 1, + Max12_5ns = 2, + Max6_25ns = 3, } -pub struct UcxCtl0 { +/// Clock low timeout select +#[derive(Copy, Clone)] +pub enum Ucclto { + /// Disable clock low time-out counter + Ucclto00b = 0, + /// 135000 MODCLK cycles (approximately 28 ms) + Ucclto01b = 1, + /// 150000 MODCLK cycles (approximately 31 ms) + Ucclto10b = 2, + /// = 165000 MODCLK cycles (approximately 34 ms) + Ucclto11b = 3, +} + +/// Automatic STOP condition generation. In slave mode, only settings 00b and 01b +/// are available. +#[derive(Copy, Clone)] +pub enum Ucastp { + /// No automatic STOP generation. The STOP condition is generated after + /// the user sets the UCTXSTP bit. The value in UCBxTBCNT is a don't care. + Ucastp00b = 0, + /// UCBCNTIFG is set when the byte counter reaches the threshold defined in + /// UCBxTBCNT + Ucastp01b = 1, + /// A STOP condition is generated automatically after the byte counter value + /// reached UCBxTBCNT. UCBCNTIFG is set with the byte counter reaching the + /// threshold. + Ucastp10b = 2, +} + +pub struct UcaCtlw0 { pub ucpen: bool, pub ucpar: bool, pub ucmsb: bool, @@ -17,7 +103,116 @@ pub struct UcxCtl0 { pub ucrxeie: bool, } -pub trait EUsci: Steal { +reg_struct! { +pub struct UcbCtlw0, UcbCtlw0_rd, UcbCtlw0_wr { + flags{ + pub uca10: bool, + pub ucsla10: bool, + pub ucmm: bool, + pub ucmst: bool, + pub ucsync: bool, + pub uctxack: bool, + pub uctr: bool, + pub uctxnack: bool, + pub uctxstp: bool, + pub uctxstt: bool, + pub ucswrst: bool, + } + enums{ + pub ucmode: Ucmode : u8, + pub ucssel: Ucssel : u8, + } +} +} + +reg_struct! { +pub struct UcbCtlw1, UcbCtlw1_rd, UcbCtlw1_wr { + flags{ + pub ucetxint: bool, + pub ucstpnack: bool, + pub ucswack: bool, + } + enums{ + pub ucclto: Ucclto : u8, + pub ucastp: Ucastp : u8, + pub ucglit: Ucglit : u8, + } +} +} + +// in order to avoid 4 separate structs, I manually implemented the macro for these registers +pub struct UcbI2coa { + pub ucgcen: bool, + pub ucoaen: bool, + pub i2coa0: u16, +} + +reg_struct! { +pub struct UcbIe, UcbIe_rd, UcbIe_wr { + flags{ + pub ucbit9ie: bool, + pub uctxie3: bool, + pub ucrxie3: bool, + pub uctxie2: bool, + pub ucrxie2: bool, + pub uctxie1: bool, + pub ucrxie1: bool, + pub uccltoie: bool, + pub ucbcntie: bool, + pub ucnackie: bool, + pub ucalie: bool, + pub ucstpie: bool, + pub ucsttie: bool, + pub uctxie0: bool, + pub ucrxie0: bool, + } +} +} + +reg_struct! { +pub struct UcbIFG, UcbIFG_rd, UcbIFG_wr{ + flags{ + pub ucbit9ifg: bool, + pub uctxifg3: bool, + pub ucrxifg3: bool, + pub uctxifg2: bool, + pub ucrxifg2: bool, + pub uctxifg1: bool, + pub ucrxifg1: bool, + pub uccltoifg: bool, + pub ucbcntifg: bool, + pub ucnackifg: bool, + pub ucalifg: bool, + pub ucstpifg: bool, + pub ucsttifg: bool, + pub uctxifg0: bool, + pub ucrxifg0: bool, + } +} +} + +reg_struct! { +pub struct UcxSpiCtw0, UcxSpiCtw0_rd, UcxSpiCtw0_wr{ + flags{ + pub ucckph: bool, + pub ucckpl: bool, + pub ucmsb: bool, + pub uc7bit: bool, + pub ucmst: bool, + pub ucsync: bool, + pub ucstem: bool, + pub ucswrst: bool, + } + enums{ + pub ucmode: Ucmode : u8, + pub ucssel: Ucssel : u8, + } +} +} + +pub trait EUsciUart: Steal { + type Statw: UartUcxStatw; + fn ctl0_reset(&self); // only call while in reset state @@ -26,33 +221,126 @@ pub trait EUsci: Steal { // only call while in reset state fn loopback(&self, loopback: bool); - fn rx_rd(&self) -> u8; + fn txifg_rd(&self) -> bool; + fn rxifg_rd(&self) -> bool; + fn rx_rd(&self) -> u8; fn tx_wr(&self, val: u8); + fn iv_rd(&self) -> u16; + + // only call while in reset state + fn ctl0_settings(&self, reg: UcaCtlw0); + + fn mctlw_settings(&self, ucos16: bool, ucbrs: u8, ucbrf: u8); + + fn statw_rd(&self) -> ::Statw; + fn txie_set(&self); fn txie_clear(&self); fn rxie_set(&self); fn rxie_clear(&self); +} - fn txifg_rd(&self) -> bool; - fn rxifg_rd(&self) -> bool; +pub trait EUsciI2C: Steal { + type IfgOut: I2CUcbIfgOut; + + fn transmit_ack(&self); + fn transmit_nack(&self); + fn transmit_start(&self); + fn transmit_stop(&self); + + fn uctxstt_rd(&self) -> bool; + fn uctxstp_rd(&self) -> bool; + + fn set_ucsla10(&self, bit: bool); + fn set_uctr(&self, bit: bool); + + fn txifg0_rd(&self) -> bool; + fn rxifg0_rd(&self) -> bool; + + //Register read/write functions + + // Read or write to UCSWRST + fn ctw0_rd_rst(&self) -> bool; + fn ctw0_set_rst(&self); + fn ctw0_clear_rst(&self); + + // Modify only when UCSWRST = 1 + fn ctw0_wr(&self, reg: &UcbCtlw0); + + // Modify only when UCSWRST = 1 + fn ctw1_wr(&self, reg: &UcbCtlw1); + + // Modify only when UCSWRST = 1 + fn brw_rd(&self) -> u16; + fn brw_wr(&self, val: u16); + + // Modify only when UCSWRST = 1 + fn tbcnt_rd(&self) -> u16; + fn tbcnt_wr(&self, val: u16); + + fn ucrxbuf_rd(&self) -> u8; + fn uctxbuf_wr(&self, val: u8); + + // Modify only when UCSWRST = 1 + // the which parameter is used to select one of the 4 registers + fn i2coa_rd(&self, which: u8) -> UcbI2coa; + fn i2coa_wr(&self, which: u8, reg: &UcbI2coa); + + fn addrx_rd(&self) -> u16; + + // Modify only when UCSWRST = 1 + fn addmask_rd(&self) -> u16; + fn addmask_wr(&self, val: u16); + + fn i2csa_rd(&self) -> u16; + fn i2csa_wr(&self, val: u16); + fn ie_wr(&self, reg: &UcbIe); + + fn ifg_rd(&self) -> Self::IfgOut; + fn ifg_wr(&self, reg: &UcbIFG); fn iv_rd(&self) -> u16; } -pub trait EUsciUart: EUsci { - type Statw: UcaxStatw; +pub trait EusciSPI: Steal { + type Statw: SpiStatw; - // only call while in reset state - fn ctl0_settings(&self, reg: UcxCtl0); + fn ctw0_set_rst(&self); + fn ctw0_clear_rst(&self); - fn mctlw_settings(&self, ucos16: bool, ucbrs: u8, ucbrf: u8); + fn ctw0_wr(&self, reg: &UcxSpiCtw0); + + fn set_spi_mode(&self, mode: Mode); + + fn brw_wr(&self, val: u16); + + fn uclisten_set(&self); + fn uclisten_clear(&self); + + fn rxbuf_rd(&self) -> u8; + + fn txbuf_wr(&self, val: u8); + + fn set_transmit_interrupt(&self); + + fn clear_transmit_interrupt(&self); - fn statw_rd(&self) -> Self::Statw; + fn set_receive_interrupt(&self); + + fn clear_receive_interrupt(&self); + + fn transmit_flag(&self) -> bool; + + fn receive_flag(&self) -> bool; + + fn overrun_flag(&self) -> bool; + + fn iv_rd(&self) -> u16; } -pub trait UcaxStatw { +pub trait UartUcxStatw { fn ucfe(&self) -> bool; fn ucoe(&self) -> bool; fn ucpe(&self) -> bool; @@ -60,10 +348,34 @@ pub trait UcaxStatw { fn ucbusy(&self) -> bool; } -macro_rules! eusci_a_impl { - ($EUsci:ident, $eusci:ident, $ucaxctlw0:ident, $ucaxctlw1:ident, $ucaxbrw:ident, $ucaxmctlw:ident, - $ucaxstatw:ident, $ucaxrxbuf:ident, $ucaxtxbuf:ident, $ucaxie:ident, $ucaxifg:ident, - $ucaxiv:ident, $Statw:ty) => { +pub trait SpiStatw { + fn uclisten(&self) -> bool; + fn ucfe(&self) -> bool; + fn ucoe(&self) -> bool; + // fn ucbusy1(&self) -> bool; +} + +pub trait I2CUcbIfgOut { + /// Byte counter interrupt flag + fn ucbcntifg(&self) -> bool; + /// Not-acknowledge received interrupt flag + fn ucnackifg(&self) -> bool; + /// Arbitration lost interrupt flag + fn ucalifg(&self) -> bool; + /// STOP condition interrupt flag + fn ucstpifg(&self) -> bool; + /// START condition interrupt flag + fn ucsttifg(&self) -> bool; + /// eUSCI_B transmit interrupt flag 0. (UCBxTXBUF is empty) + fn uctxifg0(&self) -> bool; + /// eUSCI_B receive interrupt flag 0. (complete character present in UCBxRXBUF) + fn ucrxifg0(&self) -> bool; +} + +macro_rules! eusci_impl { + ($intr_vec:ident, $EUsci:ident, $eusci:ident, $ucxctlw0:ident, $ucxctlw1:ident, $ucxbrw:ident, + $ucxstatw:ident, $ucxrxbuf:ident, $ucxtxbuf:ident, $ucxie:ident, $ucxifg:ident, + $ucxiv:ident, $StatwSpi:ty) => { impl Steal for pac::$EUsci { #[inline(always)] unsafe fn steal() -> Self { @@ -71,74 +383,156 @@ macro_rules! eusci_a_impl { } } - impl EUsci for pac::$EUsci { + impl EusciSPI for pac::$EUsci { + type Statw = $StatwSpi; + #[inline(always)] - fn ctl0_reset(&self) { - self.$ucaxctlw0().write(|w| w.ucswrst().set_bit()); + fn ctw0_set_rst(&self) { + unsafe { self.$ucxctlw0().set_bits(|w| w.ucswrst().set_bit()) } } #[inline(always)] - fn brw_settings(&self, ucbr: u16) { - self.$ucaxbrw().write(|w| unsafe { w.bits(ucbr) }); + fn ctw0_clear_rst(&self) { + unsafe { self.$ucxctlw0().clear_bits(|w| w.ucswrst().clear_bit()) } } #[inline(always)] - fn loopback(&self, loopback: bool) { - self.$ucaxstatw().write(|w| w.uclisten().bit(loopback)); + fn ctw0_wr(&self, reg: &UcxSpiCtw0) { + self.$ucxctlw0().write(UcxSpiCtw0_wr! {reg}); } #[inline(always)] - fn rx_rd(&self) -> u8 { - self.$ucaxrxbuf().read().ucrxbuf().bits() + fn brw_wr(&self, val: u16) { + self.$ucxbrw().write(|w| unsafe { w.bits(val) }); } #[inline(always)] - fn tx_wr(&self, bits: u8) { - self.$ucaxtxbuf() - .write(|w| unsafe { w.uctxbuf().bits(bits) }); + fn uclisten_set(&self) { + unsafe { self.$ucxstatw().set_bits(|w| w.uclisten().set_bit()) } + } + #[inline(always)] + fn uclisten_clear(&self) { + unsafe { self.$ucxstatw().clear_bits(|w| w.uclisten().clear_bit()) } } #[inline(always)] - fn txie_set(&self) { - unsafe { self.$ucaxie().set_bits(|w| w.uctxie().set_bit()) }; + fn rxbuf_rd(&self) -> u8 { + self.$ucxrxbuf().read().ucrxbuf().bits() } #[inline(always)] - fn txie_clear(&self) { - unsafe { self.$ucaxie().clear_bits(|w| w.uctxie().clear_bit()) }; + fn txbuf_wr(&self, val: u8) { + self.$ucxtxbuf().write(|w| unsafe { w.uctxbuf().bits(val) }); } #[inline(always)] - fn rxie_set(&self) { - unsafe { self.$ucaxie().set_bits(|w| w.ucrxie().set_bit()) }; + fn set_transmit_interrupt(&self) { + unsafe { self.$ucxie().set_bits(|w| w.uctxie().set_bit()) } } #[inline(always)] - fn rxie_clear(&self) { - unsafe { self.$ucaxie().clear_bits(|w| w.ucrxie().clear_bit()) }; + fn clear_transmit_interrupt(&self) { + unsafe { self.$ucxie().clear_bits(|w| w.uctxie().clear_bit()) } } #[inline(always)] - fn txifg_rd(&self) -> bool { - self.$ucaxifg().read().uctxifg().bit() + fn set_receive_interrupt(&self) { + unsafe { self.$ucxie().set_bits(|w| w.ucrxie().set_bit()) } } #[inline(always)] - fn rxifg_rd(&self) -> bool { - self.$ucaxifg().read().ucrxifg().bit() + fn clear_receive_interrupt(&self) { + unsafe { self.$ucxie().clear_bits(|w| w.ucrxie().clear_bit()) } + } + + #[inline(always)] + // Set the SPI mode without disturbing the rest of the register. + fn set_spi_mode(&self, mode: Mode) { + let ucckph = match mode.phase { + Phase::CaptureOnFirstTransition => true, + Phase::CaptureOnSecondTransition => false, + }; + let ucckpl = match mode.polarity { + Polarity::IdleLow => false, + Polarity::IdleHigh => true, + }; + self.$ucxctlw0().modify(|_, w| w + .ucckph().bit(ucckph) + .ucckpl().bit(ucckpl)); + } + + #[inline(always)] + fn transmit_flag(&self) -> bool { + self.$ucxifg().read().uctxifg().bit() + } + + #[inline(always)] + fn receive_flag(&self) -> bool { + self.$ucxifg().read().ucrxifg().bit() + } + + #[inline(always)] + fn overrun_flag(&self) -> bool { + self.$ucxstatw().read().ucoe().bit() } #[inline(always)] fn iv_rd(&self) -> u16 { - self.$ucaxiv().read().bits() + self.$ucxiv().read().uciv().bits() + } + } + + impl SpiStatw for $StatwSpi { + #[inline(always)] + fn uclisten(&self) -> bool { + self.uclisten().bit() + } + + #[inline(always)] + fn ucfe(&self) -> bool { + self.ucfe().bit() } + + #[inline(always)] + fn ucoe(&self) -> bool { + self.ucoe().bit() + } + + // #[inline(always)] + // fn ucbusy1(&self) -> bool{ + // self.ucbusy().bit() + // } } + }; +} + +macro_rules! eusci_a_impl { + ($intr_vec:ident,$EUsci:ident, $eusci:ident, $ucaxctlw0:ident, $ucaxctlw1:ident, $ucaxbrw:ident, + $ucaxmctlw:ident, $ucaxstatw:ident, $ucaxrxbuf:ident, $ucaxtxbuf:ident, $ucaxie:ident, + $ucaxifg:ident, $ucaxiv:ident, $Statw:ty, + $StatwSpi:ty, + $ucaxctlw0spi:ident, $ucaxstatwspi:ident, $ucaxiespi:ident, $ucaxifgspi:ident) => { + eusci_impl!( + $intr_vec, + $EUsci, + $eusci, + $ucaxctlw0spi, + $ucaxctlw1, + $ucaxbrw, + $ucaxstatwspi, + $ucaxrxbuf, + $ucaxtxbuf, + $ucaxiespi, + $ucaxifgspi, + $ucaxiv, + $StatwSpi + ); impl EUsciUart for pac::$EUsci { type Statw = $Statw; #[inline(always)] - fn ctl0_settings(&self, reg: UcxCtl0) { + fn ctl0_settings(&self, reg: UcaCtlw0) { self.$ucaxctlw0().write(|w| { w.ucpen() .bit(reg.ucpen) @@ -170,12 +564,73 @@ macro_rules! eusci_a_impl { } #[inline(always)] - fn statw_rd(&self) -> Self::Statw { + fn statw_rd(&self) -> ::Statw { self.$ucaxstatw().read() } + + #[inline(always)] + fn txie_set(&self) { + unsafe { self.$ucaxie().set_bits(|w| w.uctxie().set_bit()) }; + } + + #[inline(always)] + fn txie_clear(&self) { + unsafe { self.$ucaxie().clear_bits(|w| w.uctxie().clear_bit()) }; + } + + #[inline(always)] + fn rxie_set(&self) { + unsafe { self.$ucaxie().set_bits(|w| w.ucrxie().set_bit()) }; + } + + #[inline(always)] + fn rxie_clear(&self) { + unsafe { self.$ucaxie().clear_bits(|w| w.ucrxie().clear_bit()) }; + } + + #[inline(always)] + fn ctl0_reset(&self) { + self.$ucaxctlw0().write(|w| w.ucswrst().set_bit()); + } + + #[inline(always)] + fn brw_settings(&self, ucbr: u16) { + self.$ucaxbrw().write(|w| unsafe { w.bits(ucbr) }); + } + + #[inline(always)] + fn loopback(&self, loopback: bool) { + self.$ucaxstatw().write(|w| w.uclisten().bit(loopback)); + } + + #[inline(always)] + fn rx_rd(&self) -> u8 { + self.$ucaxrxbuf().read().ucrxbuf().bits() + } + + #[inline(always)] + fn tx_wr(&self, bits: u8) { + self.$ucaxtxbuf() + .write(|w| unsafe { w.uctxbuf().bits(bits) }); + } + + #[inline(always)] + fn txifg_rd(&self) -> bool { + self.$ucaxifg().read().uctxifg().bit() + } + + #[inline(always)] + fn rxifg_rd(&self) -> bool { + self.$ucaxifg().read().ucrxifg().bit() + } + + #[inline(always)] + fn iv_rd(&self) -> u16 { + self.$ucaxiv().read().bits() + } } - impl UcaxStatw for $Statw { + impl UartUcxStatw for $Statw { #[inline(always)] fn ucfe(&self) -> bool { self.ucfe().bit() @@ -204,7 +659,293 @@ macro_rules! eusci_a_impl { }; } +macro_rules! eusci_b_impl { + ($intr_vec:ident, $EUsci:ident, $eusci:ident, $ucbxctlw0:ident, $ucbxctlw1:ident, $ucbxbrw:ident, + $ucbxstatw:ident, $ucbxtbcnt:ident, $ucbxrxbuf:ident, $ucbxtxbuf:ident, $ucbxi2coa0:ident, + $ucbxi2coa1:ident, $ucbxi2coa2:ident, $ucbxi2coa3:ident, $ucbxaddrx:ident, $ucbxaddmask:ident, + $ucbxi2csa:ident, $ucbxie:ident, + $ucbxifg:ident, $ucbxiv:ident, $Statw:ty, $Ifg:ty, + $StatwSpi:ty, + $ucbxctlw0spi:ident, $ucbxstatwspi:ident, $ucbxiespi:ident, $ucbxifgspi:ident) => { + eusci_impl!( + $intr_vec, + $EUsci, + $eusci, + $ucbxctlw0spi, + $ucbxctlw1, + $ucbxbrw, + $ucbxstatwspi, + $ucbxrxbuf, + $ucbxtxbuf, + $ucbxiespi, + $ucbxifgspi, + $ucbxiv, + $StatwSpi + ); + + impl EUsciI2C for pac::$EUsci { + type IfgOut = $Ifg; + + #[inline(always)] + fn ctw0_rd_rst(&self) -> bool { + self.$ucbxctlw0().read().ucswrst().bit() + } + + #[inline(always)] + fn ctw0_set_rst(&self) { + unsafe { self.$ucbxctlw0().set_bits(|w| w.ucswrst().set_bit()) } + } + + #[inline(always)] + fn ctw0_clear_rst(&self) { + unsafe { self.$ucbxctlw0().clear_bits(|w| w.ucswrst().clear_bit()) } + } + + #[inline(always)] + fn transmit_ack(&self) { + unsafe { self.$ucbxctlw0().set_bits(|w| w.uctxack().set_bit()) } + } + + #[inline(always)] + fn transmit_nack(&self) { + unsafe { self.$ucbxctlw0().set_bits(|w| w.uctxnack().set_bit()) } + } + + #[inline(always)] + fn transmit_start(&self) { + unsafe { self.$ucbxctlw0().set_bits(|w| w.uctxstt().set_bit()) } + } + + #[inline(always)] + fn transmit_stop(&self) { + unsafe { self.$ucbxctlw0().set_bits(|w| w.uctxstp().set_bit()) } + } + + #[inline(always)] + fn uctxstt_rd(&self) -> bool { + self.$ucbxctlw0().read().uctxstp().bit() + } + + #[inline(always)] + fn uctxstp_rd(&self) -> bool { + self.$ucbxctlw0().read().uctxstp().bit() + } + + #[inline(always)] + fn set_ucsla10(&self, bit: bool) { + match bit { + true => unsafe { self.$ucbxctlw0().set_bits(|w| w.ucsla10().set_bit()) }, + false => unsafe { self.$ucbxctlw0().clear_bits(|w| w.ucsla10().clear_bit()) }, + } + } + + #[inline(always)] + fn set_uctr(&self, bit: bool) { + match bit { + true => unsafe { self.$ucbxctlw0().set_bits(|w| w.uctr().set_bit()) }, + false => unsafe { self.$ucbxctlw0().clear_bits(|w| w.uctr().clear_bit()) }, + } + } + + #[inline(always)] + fn txifg0_rd(&self) -> bool { + self.$ucbxifg().read().uctxifg0().bit() + } + + #[inline(always)] + fn rxifg0_rd(&self) -> bool { + self.$ucbxifg().read().ucrxifg0().bit() + } + + #[inline(always)] + fn ctw0_wr(&self, reg: &UcbCtlw0) { + self.$ucbxctlw0().write(UcbCtlw0_wr! {reg}); + } + + #[inline(always)] + fn ctw1_wr(&self, reg: &UcbCtlw1) { + self.$ucbxctlw1.write(UcbCtlw1_wr! {reg}); + } + + #[inline(always)] + fn brw_rd(&self) -> u16 { + self.$ucbxbrw().read().bits() + } + #[inline(always)] + fn brw_wr(&self, val: u16) { + self.$ucbxbrw().write(|w| unsafe { w.bits(val) }); + } + + #[inline(always)] + fn tbcnt_rd(&self) -> u16 { + self.$ucbxtbcnt.read().bits() + } + #[inline(always)] + fn tbcnt_wr(&self, val: u16) { + self.$ucbxtbcnt.write(|w| unsafe { w.bits(val) }); + } + + #[inline(always)] + fn ucrxbuf_rd(&self) -> u8 { + self.$ucbxrxbuf().read().bits() as u8 + } + #[inline(always)] + fn uctxbuf_wr(&self, val: u8) { + self.$ucbxtxbuf().write(|w| unsafe { w.bits(val as u16) }); + } + + fn i2coa_rd(&self, which: u8) -> UcbI2coa { + match which { + 1 => { + let content = self.$ucbxi2coa1.read(); + UcbI2coa { + ucgcen: false, + ucoaen: content.ucoaen().bit(), + i2coa0: ::from(content.i2coa1().bits()), + } + } + 2 => { + let content = self.$ucbxi2coa2.read(); + UcbI2coa { + ucgcen: false, + ucoaen: content.ucoaen().bit(), + i2coa0: ::from(content.i2coa2().bits()), + } + } + 3 => { + let content = self.$ucbxi2coa3.read(); + UcbI2coa { + ucgcen: false, + ucoaen: content.ucoaen().bit(), + i2coa0: ::from(content.i2coa3().bits()), + } + } + _ => { + let content = self.$ucbxi2coa0.read(); + UcbI2coa { + ucgcen: content.ucgcen().bit(), + ucoaen: content.ucoaen().bit(), + i2coa0: ::from(content.i2coa0().bits()), + } + } + } + } + + fn i2coa_wr(&self, which: u8, reg: &UcbI2coa) { + match which { + 1 => { + self.$ucbxi2coa1.write(|w| unsafe { + w.ucoaen().bit(reg.ucoaen).i2coa1().bits(reg.i2coa0 as u16) + }); + } + 2 => { + self.$ucbxi2coa2.write(|w| unsafe { + w.ucoaen().bit(reg.ucoaen).i2coa2().bits(reg.i2coa0 as u16) + }); + } + 3 => { + self.$ucbxi2coa3.write(|w| unsafe { + w.ucoaen().bit(reg.ucoaen).i2coa3().bits(reg.i2coa0 as u16) + }); + } + _ => { + self.$ucbxi2coa0.write(|w| unsafe { + w.ucgcen() + .bit(reg.ucgcen) + .ucoaen() + .bit(reg.ucoaen) + .i2coa0() + .bits(reg.i2coa0 as u16) + }); + } + } + } + + #[inline(always)] + fn addrx_rd(&self) -> u16 { + self.$ucbxaddrx.read().bits() + } + + #[inline(always)] + fn addmask_rd(&self) -> u16 { + self.$ucbxaddmask.read().bits() + } + #[inline(always)] + fn addmask_wr(&self, val: u16) { + self.$ucbxaddmask.write(|w| unsafe { w.bits(val) }); + } + + #[inline(always)] + fn i2csa_rd(&self) -> u16 { + self.$ucbxi2csa.read().bits() + } + #[inline(always)] + fn i2csa_wr(&self, val: u16) { + self.$ucbxi2csa.write(|w| unsafe { w.bits(val) }); + } + + #[inline(always)] + fn ie_wr(&self, reg: &UcbIe) { + self.$ucbxie().write(UcbIe_wr! {reg}); + } + + #[inline(always)] + fn ifg_rd(&self) -> Self::IfgOut { + self.$ucbxifg().read() + } + + #[inline(always)] + fn ifg_wr(&self, reg: &UcbIFG) { + self.$ucbxifg().write(UcbIFG_wr! {reg}); + } + + #[inline(always)] + fn iv_rd(&self) -> u16 { + self.$ucbxiv().read().bits() + } + } + + impl I2CUcbIfgOut for $Ifg { + #[inline(always)] + fn ucbcntifg(&self) -> bool { + self.ucbcntifg().bit() + } + + #[inline(always)] + fn ucnackifg(&self) -> bool { + self.ucnackifg().bit() + } + + #[inline(always)] + fn ucalifg(&self) -> bool { + self.ucalifg().bit() + } + + #[inline(always)] + fn ucstpifg(&self) -> bool { + self.ucstpifg().bit() + } + + #[inline(always)] + fn ucsttifg(&self) -> bool { + self.ucsttifg().bit() + } + + #[inline(always)] + fn uctxifg0(&self) -> bool { + self.uctxifg0().bit() + } + + #[inline(always)] + fn ucrxifg0(&self) -> bool { + self.ucrxifg0().bit() + } + } + }; +} + eusci_a_impl!( + EUSCI_A0, E_USCI_A0, e_usci_a0, uca0ctlw0, @@ -217,10 +958,16 @@ eusci_a_impl!( uca0ie, uca0ifg, uca0iv, - pac::e_usci_a0::uca0statw::R + pac::e_usci_a0::uca0statw::R, + pac::e_usci_a0::uca0statw_spi::R, + uca0ctlw0_spi, + uca0statw_spi, + uca0ie_spi, + uca0ifg_spi ); eusci_a_impl!( + EUSCI_A1, E_USCI_A1, e_usci_a1, uca1ctlw0, @@ -233,5 +980,70 @@ eusci_a_impl!( uca1ie, uca1ifg, uca1iv, - pac::e_usci_a1::uca1statw::R + pac::e_usci_a1::uca1statw::R, + pac::e_usci_a1::uca1statw_spi::R, + uca1ctlw0_spi, + uca1statw_spi, + uca1ie_spi, + uca1ifg_spi +); + +eusci_b_impl!( + EUSCI_B0, + E_USCI_B0, + e_usci_b0, + ucb0ctlw0, + ucb0ctlw1, + ucb0brw, + ucb0statw, + ucb0tbcnt, + ucb0rxbuf, + ucb0txbuf, + ucb0i2coa0, + ucb0i2coa1, + ucb0i2coa2, + ucb0i2coa3, + ucb0addrx, + ucb0addmask, + ucb0i2csa, + ucb0ie, + ucb0ifg, + ucb0iv, + pac::e_usci_b0::ucb0statw::R, + pac::e_usci_b0::ucb0ifg::R, + pac::e_usci_b0::ucb0statw_spi::R, + ucb0ctlw0_spi, + ucb0statw_spi, + ucb0ie_spi, + ucb0ifg_spi +); + +eusci_b_impl!( + EUSCI_B1, + E_USCI_B1, + e_usci_b1, + ucb1ctlw0, + ucb1ctlw1, + ucb1brw, + ucb1statw, + ucb1tbcnt, + ucb1rxbuf, + ucb1txbuf, + ucb1i2coa0, + ucb1i2coa1, + ucb1i2coa2, + ucb1i2coa3, + ucb1addrx, + ucb1addmask, + ucb1i2csa, + ucb1ie, + ucb1ifg, + ucb1iv, + pac::e_usci_b1::ucb1statw::R, + pac::e_usci_b1::ucb1ifg::R, + pac::e_usci_b1::ucb1statw_spi::R, + ucb1ctlw0_spi, + ucb1statw_spi, + ucb1ie_spi, + ucb1ifg_spi ); diff --git a/src/hw_traits/gpio.rs b/src/hw_traits/gpio.rs index f35b72f..c49faca 100644 --- a/src/hw_traits/gpio.rs +++ b/src/hw_traits/gpio.rs @@ -61,7 +61,7 @@ macro_rules! reg_methods { #[inline(always)] fn $wr(&self, bits: u8) { - self.$reg.write(|w| unsafe {w.bits(bits) }); + self.$reg.write(|w| unsafe { w.bits(bits) }); } #[inline(always)] @@ -73,7 +73,7 @@ macro_rules! reg_methods { fn $clear(&self, bits: u8) { unsafe { self.$reg.clear_bits(|w| w.bits(bits)) } } - } + }; } macro_rules! gpio_impl { diff --git a/src/i2c.rs b/src/i2c.rs new file mode 100644 index 0000000..9a56f3d --- /dev/null +++ b/src/i2c.rs @@ -0,0 +1,552 @@ +//! I2C +//! +//! Peripherals eUSCI_B0 and eUSCI_B1 can be used for I2C communication. +//! +//! Begin by calling `I2cBusConfig::new()`. Once configured an `I2cBus` will be returned. +//! +//! `I2cBus` implements the blocking embedded_hal `Read`, `Write` and `WriteRead` traits. +//! Passing a `u8` address to these methods uses 7-bit addressing, passing a `u16` uses 10-bit addressing. +//! +//! Pins used: +//! +//! eUSCI_B0: {SCL: `P1.3`, SDA: `P1.2`}. `P1.1` can optionally be used as an external clock source. +//! +//! eUSCI_B1: {SCL: `P4.7`, SDA: `P4.6`}. `P4.5` can optionally be used as an external clock source. +//! + +use crate::clock::{Aclk, Smclk}; +use crate::gpio::{Pin1, Pin5}; +use crate::hw_traits::eusci::I2CUcbIfgOut; +use crate::{ + gpio::{Alternate1, Pin, Pin2, Pin3, Pin6, Pin7, P1, P4}, + hal::blocking::i2c::{ + Read, SevenBitAddress, TenBitAddress, Write, WriteRead, + }, + hw_traits::eusci::{ + EUsciI2C, Ucastp, UcbCtlw0, UcbCtlw1, UcbI2coa, UcbIFG, UcbIe, Ucclto, Ucglit, Ucmode, + Ucssel, + }, + pac, +}; +use core::marker::PhantomData; +use msp430::asm; + +/// Configure bus to use 7bit or 10bit I2C slave addressing mode +#[derive(Clone, Copy)] +enum AddressingMode { + /// 7 Bit addressing mode + SevenBit = 0, + /// 10 bit addressing mode + TenBit = 1, +} + +impl From for bool { + fn from(f: AddressingMode) -> bool { + match f { + AddressingMode::SevenBit => false, + AddressingMode::TenBit => true, + } + } +} + +/// Configure between master receiver and master transmitter modes +#[derive(Clone, Copy)] +enum TransmissionMode { + /// master receiver mode + Receive = 0, + /// master transmitter mode + Transmit = 1, +} + +impl From for bool { + fn from(f: TransmissionMode) -> bool { + match f { + TransmissionMode::Receive => false, + TransmissionMode::Transmit => true, + } + } +} + +/// Configure the automatic glitch filter on the SDA and SCL lines +#[derive(Clone, Copy)] +pub enum GlitchFilter { + ///Pulses of maximum 50-ns length are filtered. + Max50ns = 0, + ///Pulses of maximum 25-ns length are filtered. + Max25ns = 1, + ///Pulses of maximum 12.5-ns length are filtered. + Max12_5ns = 2, + ///Pulses of maximum 6.25-ns length are filtered. + Max6_25ns = 3, +} + +impl From for Ucglit { + fn from(f: GlitchFilter) -> Ucglit { + match f { + GlitchFilter::Max50ns => Ucglit::Max50ns, + GlitchFilter::Max25ns => Ucglit::Max25ns, + GlitchFilter::Max12_5ns => Ucglit::Max12_5ns, + GlitchFilter::Max6_25ns => Ucglit::Max6_25ns, + } + } +} + +///Struct used to configure a I2C bus +pub struct I2CBusConfig { + usci: USCI, + divisor: u16, + + // Register configs + ctlw0: UcbCtlw0, + ctlw1: UcbCtlw1, + i2coa0: UcbI2coa, + i2coa1: UcbI2coa, + i2coa2: UcbI2coa, + i2coa3: UcbI2coa, + ie: UcbIe, + ifg: UcbIFG, + _phantom: PhantomData, +} + +/// Marks a usci capable of I2C communication +pub trait I2cUsci: EUsciI2C { + /// I2C SCL pin + type ClockPin; + /// I2C SDA pin + type DataPin; + /// I2C external clock source pin. Only necessary if UCLKI is selected as a clock source. + type ExternalClockPin; +} + +impl I2cUsci for pac::E_USCI_B0 { + type ClockPin = UsciB0SCLPin; + type DataPin = UsciB0SDAPin; + type ExternalClockPin = UsciB0UCLKIPin; +} + +impl I2cUsci for pac::E_USCI_B1 { + type ClockPin = UsciB1SCLPin; + type DataPin = UsciB1SDAPin; + type ExternalClockPin = UsciB1UCLKIPin; +} + +// Allows a GPIO pin to be converted into an I2C object +macro_rules! impl_i2c_pin { + ($struct_name: ident, $port: ty, $pin: ty) => { + impl From>> for $struct_name { + #[inline(always)] + fn from(_val: Pin<$port, $pin, Alternate1>) -> Self { + $struct_name + } + } + }; +} + +/// I2C SCL pin for eUSCI B0 +pub struct UsciB0SCLPin; +impl_i2c_pin!(UsciB0SCLPin, P1, Pin3); + +/// I2C SDA pin for eUSCI B0 +pub struct UsciB0SDAPin; +impl_i2c_pin!(UsciB0SDAPin, P1, Pin2); + +/// UCLKI pin for eUSCI B0. Used as an external clock source. +pub struct UsciB0UCLKIPin; +impl_i2c_pin!(UsciB0UCLKIPin, P1, Pin1); + +/// I2C SCL pin for eUSCI B1 +pub struct UsciB1SCLPin; +impl_i2c_pin!(UsciB1SCLPin, P4, Pin7); + +/// I2C SDA pin for eUSCI B1 +pub struct UsciB1SDAPin; +impl_i2c_pin!(UsciB1SDAPin, P4, Pin6); + +/// UCLKI pin for eUSCI B1. Used as an external clock source. +pub struct UsciB1UCLKIPin; +impl_i2c_pin!(UsciB1UCLKIPin, P4, Pin5); + +/// Typestate for an I2C bus configuration with no clock source selected +pub struct NoClockSet; +/// Typestate for an I2C bus configuration with a clock source selected +pub struct ClockSet; + +impl I2CBusConfig { + /// Create a new configuration for setting up a EUSCI peripheral in I2C master mode + pub fn new(usci: USCI, deglitch_time: GlitchFilter) -> I2CBusConfig { + let ctlw0 = UcbCtlw0 { + uca10: false, + ucsla10: false, + ucmm: false, + ucmst: true, + ucsync: true, + uctxack: false, + uctr: false, + uctxnack: false, + uctxstp: false, + uctxstt: false, + ucswrst: true, + ucmode: Ucmode::I2CMode, + ucssel: Ucssel::Smclk, // overwritten by `use_smclk/uclk/aclk()` + }; + + let ctlw1 = UcbCtlw1 { + ucetxint: false, + ucstpnack: false, + ucswack: false, + ucclto: Ucclto::Ucclto00b, + ucastp: Ucastp::Ucastp00b, + ucglit: deglitch_time.into(), + }; + + let i2coa0 = UcbI2coa { + ucgcen: false, + ucoaen: false, + i2coa0: 0, + }; + + let i2coa1 = UcbI2coa { + ucgcen: false, + ucoaen: false, + i2coa0: 0, + }; + + let i2coa2 = UcbI2coa { + ucgcen: false, + ucoaen: false, + i2coa0: 0, + }; + + let i2coa3 = UcbI2coa { + ucgcen: false, + ucoaen: false, + i2coa0: 0, + }; + + let ie = UcbIe { + ucbit9ie: false, + uctxie3: false, + ucrxie3: false, + uctxie2: false, + ucrxie2: false, + uctxie1: false, + ucrxie1: false, + uccltoie: false, + ucbcntie: false, + ucnackie: false, + ucalie: false, + ucstpie: false, + ucsttie: false, + uctxie0: false, + ucrxie0: false, + }; + + let ifg = UcbIFG { + ucbit9ifg: false, + uctxifg3: false, + ucrxifg3: false, + uctxifg2: false, + ucrxifg2: false, + uctxifg1: false, + ucrxifg1: false, + uccltoifg: false, + ucbcntifg: false, + ucnackifg: false, + ucalifg: false, + ucstpifg: false, + ucsttifg: false, + uctxifg0: false, + ucrxifg0: false, + }; + + I2CBusConfig { + usci, + divisor: 1, + ctlw0, + ctlw1, + i2coa0, + i2coa1, + i2coa2, + i2coa3, + ie, + ifg, + _phantom: PhantomData, + } + } + + /// Configures this peripheral to use SMCLK + #[inline] + pub fn use_smclk(mut self, _smclk: &Smclk, clk_divisor: u16) -> I2CBusConfig { + self.ctlw0.ucssel = Ucssel::Smclk; + self.divisor = clk_divisor; + I2CBusConfig{ + usci: self.usci, + divisor: self.divisor, + ctlw0: self.ctlw0, + ctlw1: self.ctlw1, + i2coa0: self.i2coa0, + i2coa1: self.i2coa1, + i2coa2: self.i2coa2, + i2coa3: self.i2coa3, + ie: self.ie, + ifg: self.ifg, + _phantom: PhantomData } + } + + /// Configures this peripheral to use ACLK + #[inline] + pub fn use_aclk(mut self, _aclk: &Aclk, clk_divisor: u16) -> I2CBusConfig { + self.ctlw0.ucssel = Ucssel::Aclk; + self.divisor = clk_divisor; + I2CBusConfig{ + usci: self.usci, + divisor: self.divisor, + ctlw0: self.ctlw0, + ctlw1: self.ctlw1, + i2coa0: self.i2coa0, + i2coa1: self.i2coa1, + i2coa2: self.i2coa2, + i2coa3: self.i2coa3, + ie: self.ie, + ifg: self.ifg, + _phantom: PhantomData } + } + /// Configures this peripheral to use UCLK + #[inline] + pub fn use_uclk >(mut self, _uclk: Pin, clk_divisor: u16) -> I2CBusConfig { + self.ctlw0.ucssel = Ucssel::Uclk; + self.divisor = clk_divisor; + I2CBusConfig{ + usci: self.usci, + divisor: self.divisor, + ctlw0: self.ctlw0, + ctlw1: self.ctlw1, + i2coa0: self.i2coa0, + i2coa1: self.i2coa1, + i2coa2: self.i2coa2, + i2coa3: self.i2coa3, + ie: self.ie, + ifg: self.ifg, + _phantom: PhantomData } + } +} + +#[allow(private_bounds)] +impl I2CBusConfig { + /// Performs hardware configuration and creates the I2C bus + pub fn configure, D: Into>( + &self, + _scl: C, + _sda: D, + ) -> I2cBus { + self.configure_regs(); + I2cBus(PhantomData) + } + + /// Performs hardware configuration + #[inline] + fn configure_regs(&self) { + self.usci.ctw0_set_rst(); + + self.usci.ctw0_wr(&self.ctlw0); + self.usci.ctw1_wr(&self.ctlw1); + self.usci.i2coa_wr(0, &self.i2coa0); + self.usci.i2coa_wr(1, &self.i2coa1); + self.usci.i2coa_wr(2, &self.i2coa2); + self.usci.i2coa_wr(3, &self.i2coa3); + self.usci.ie_wr(&self.ie); + self.usci.ifg_wr(&self.ifg); + + self.usci.brw_wr(self.divisor); + self.usci.tbcnt_wr(0); + + self.usci.ctw0_clear_rst(); + } +} + +/// I2C data bus +pub struct I2cBus(PhantomData); + +/// I2C transmit/receive errors +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum I2CErr { + /// Address was never acknolwedged by slave + GotNACK, + /// Device lost arbitration + ArbitrationLost, + // Other errors such as the 'clock low timeout' UCCLTOIFG may appear here in future. +} + +impl I2cBus { + #[inline(always)] + fn set_addressing_mode(&mut self, mode: AddressingMode) { + let usci = unsafe { USCI::steal() }; + usci.set_ucsla10(mode.into()) + } + + #[inline(always)] + fn set_transmission_mode(&mut self, mode: TransmissionMode) { + let usci = unsafe { USCI::steal() }; + usci.set_uctr(mode.into()) + } + + /// Blocking read + fn read(&mut self, address: u16, buffer: &mut [u8]) -> Result<(), I2CErr> { + if buffer.is_empty() { return Ok(()) } + + let usci = unsafe { USCI::steal() }; + + usci.i2csa_wr(address); + usci.transmit_start(); + + while usci.uctxstt_rd() { + asm::nop(); + } + + let mut ifg = usci.ifg_rd(); + if ifg.ucnackifg() { + usci.transmit_stop(); + while usci.uctxstp_rd() { + asm::nop(); + } + return Err::<(), I2CErr>(I2CErr::GotNACK); + } + + let len = buffer.len(); + for (idx, byte) in buffer.iter_mut().enumerate() { + if idx == len - 1 { + usci.transmit_stop(); + } + while !ifg.ucrxifg0() { + ifg = usci.ifg_rd(); + } + *byte = usci.ucrxbuf_rd(); + } + + while usci.uctxstp_rd() { + asm::nop(); + } + + Ok(()) + } + + /// Blocking write + fn write(&mut self, address: u16, bytes: &[u8]) -> Result<(), I2CErr> { + if bytes.is_empty() { return Ok(()) } + let usci = unsafe { USCI::steal() }; + + usci.i2csa_wr(address); + usci.transmit_start(); + + let mut ifg = usci.ifg_rd(); + while !ifg.uctxifg0() { + ifg = usci.ifg_rd(); + } + + while usci.uctxstt_rd() { + asm::nop(); + } + + ifg = usci.ifg_rd(); + if ifg.ucnackifg() { + usci.transmit_stop(); + while usci.uctxstp_rd() { + asm::nop(); + } + return Err::<(), I2CErr>(I2CErr::GotNACK); + } + + for &byte in bytes { + usci.uctxbuf_wr(byte); + ifg = usci.ifg_rd(); + while !ifg.uctxifg0() { + ifg = usci.ifg_rd(); + } + if ifg.ucnackifg() { + usci.transmit_stop(); + while usci.uctxstp_rd() { + asm::nop(); + } + return Err::<(), I2CErr>(I2CErr::GotNACK); + } + } + // usci.uctxbuf_wr(bytes[bytes.len()-1]); + usci.transmit_stop(); + while usci.uctxstp_rd() { + asm::nop(); + } + + Ok(()) + } + + /// blocking write then blocking read + fn write_read(&mut self, address: u16, bytes: &[u8], buffer: &mut [u8]) -> Result<(), I2CErr> { + self.set_transmission_mode(TransmissionMode::Transmit); + self.read(address, buffer)?; + self.set_transmission_mode(TransmissionMode::Receive); + self.write(address, bytes) + } +} + +impl Read for I2cBus { + type Error = I2CErr; + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::SevenBit); + self.set_transmission_mode(TransmissionMode::Receive); + I2cBus::read(self, address as u16, buffer) + } +} + +impl Read for I2cBus { + type Error = I2CErr; + fn read(&mut self, address: u16, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::TenBit); + self.set_transmission_mode(TransmissionMode::Receive); + I2cBus::read(self, address, buffer) + } +} + +impl Write for I2cBus { + type Error = I2CErr; + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::SevenBit); + self.set_transmission_mode(TransmissionMode::Transmit); + I2cBus::write(self, address as u16, bytes) + } +} + +impl Write for I2cBus { + type Error = I2CErr; + fn write(&mut self, address: u16, bytes: &[u8]) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::TenBit); + self.set_transmission_mode(TransmissionMode::Transmit); + I2cBus::write(self, address, bytes) + } +} + +impl WriteRead for I2cBus { + type Error = I2CErr; + fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::SevenBit); + I2cBus::write_read(self, address as u16, bytes, buffer) + } +} + +impl WriteRead for I2cBus { + type Error = I2CErr; + fn write_read( + &mut self, + address: u16, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.set_addressing_mode(AddressingMode::TenBit); + I2cBus::write_read(self, address, bytes, buffer) + } +} diff --git a/src/lib.rs b/src/lib.rs index fbdff96..dcb663d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,9 @@ #![feature(specialization)] #![feature(asm_experimental_arch)] #![deny(missing_docs)] +#![feature(asm_const)] +pub mod adc; pub mod batch_gpio; pub mod capture; pub mod clock; @@ -43,4 +45,9 @@ pub mod watchdog; mod hw_traits; mod util; +pub mod delay; +pub mod i2c; +pub mod spi; + +pub use embedded_hal as hal; pub use msp430fr2355 as pac; diff --git a/src/serial.rs b/src/serial.rs index 2dca4ef..f695103 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -8,8 +8,9 @@ use crate::clock::{Aclk, Clock, Smclk}; use crate::gpio::{Alternate1, Pin, Pin1, Pin2, Pin3, Pin5, Pin6, Pin7, P1, P4}; -use crate::hw_traits::eusci::{EUsciUart, UcaxStatw, Ucssel, UcxCtl0}; +use crate::hw_traits::eusci::{EUsciUart, UartUcxStatw, UcaCtlw0, Ucssel}; use core::marker::PhantomData; +use core::num::NonZeroU32; use embedded_hal::serial::{Read, Write}; use msp430fr2355 as pac; @@ -135,32 +136,28 @@ impl SerialUsci for pac::E_USCI_A0 { type RxPin = UsciA0RxPin; } +macro_rules! impl_serial_pin { + ($struct_name: ident, $port: ty, $pin: ty) => { + impl From>> for $struct_name { + #[inline(always)] + fn from(_val: Pin<$port, $pin, Alternate1>) -> Self { + $struct_name + } + } + }; +} + /// UCLK pin for E_USCI_A0 pub struct UsciA0ClockPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA0ClockPin { - UsciA0ClockPin - } -} +impl_serial_pin!(UsciA0ClockPin, P1, Pin5); /// Tx pin for E_USCI_A0 pub struct UsciA0TxPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA0TxPin { - UsciA0TxPin - } -} +impl_serial_pin!(UsciA0TxPin, P1, Pin7); /// Rx pin for E_USCI_A0 pub struct UsciA0RxPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA0RxPin { - UsciA0RxPin - } -} +impl_serial_pin!(UsciA0RxPin, P1, Pin6); impl SerialUsci for pac::E_USCI_A1 { type ClockPin = UsciA1ClockPin; @@ -170,34 +167,19 @@ impl SerialUsci for pac::E_USCI_A1 { /// UCLK pin for E_USCI_A1 pub struct UsciA1ClockPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA1ClockPin { - UsciA1ClockPin - } -} +impl_serial_pin!(UsciA1ClockPin, P4, Pin1); /// Tx pin for E_USCI_A1 pub struct UsciA1TxPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA1TxPin { - UsciA1TxPin - } -} +impl_serial_pin!(UsciA1TxPin, P4, Pin3); /// Rx pin for E_USCI_A1 pub struct UsciA1RxPin; -impl Into for Pin> { - #[inline(always)] - fn into(self) -> UsciA1RxPin { - UsciA1RxPin - } -} +impl_serial_pin!(UsciA1RxPin, P4, Pin2); /// Typestate for a serial interface with an unspecified clock source pub struct NoClockSet { - baudrate: u32, + baudrate: NonZeroU32, } /// Typestate for a serial interface with a specified clock source @@ -246,6 +228,7 @@ impl SerialConfig { loopback: Loopback, baudrate: u32, ) -> Self { + const ONE: NonZeroU32 = NonZeroU32::new(1).unwrap(); SerialConfig { order, cnt, @@ -253,7 +236,9 @@ impl SerialConfig { parity, loopback, usci, - state: NoClockSet { baudrate }, + state: NoClockSet { + baudrate: NonZeroU32::new(baudrate).unwrap_or(ONE), + }, } } @@ -307,18 +292,20 @@ struct BaudConfig { } #[inline] -fn calculate_baud_config(clk_freq: u32, bps: u32) -> BaudConfig { - // Prevent division by 0 - let bps = bps.max(1); +fn calculate_baud_config(clk_freq: u32, bps: NonZeroU32) -> BaudConfig { // Ensure n stays within the 16 bit boundary - let n = (clk_freq / bps).max(1).min(0xFFFF); + let n = (clk_freq / bps).clamp(1, 0xFFFF); let brs = lookup_brs(clk_freq, bps); - if n >= 16 { - let div = bps * 16; + if (n >= 16) && (bps.get() < u32::MAX / 16) { + // div = bps * 16 + const SIXTEEN: NonZeroU32 = NonZeroU32::new(16).unwrap(); + let div = bps.saturating_mul(SIXTEEN); + // n / 16, but more precise let br = (clk_freq / div) as u16; + // same as n % 16, but more precise let brf = ((clk_freq % div) / bps) as u8; BaudConfig { @@ -338,54 +325,64 @@ fn calculate_baud_config(clk_freq: u32, bps: u32) -> BaudConfig { } #[inline(always)] -fn lookup_brs(clk_freq: u32, bps: u32) -> u8 { +fn lookup_brs(clk_freq: u32, bps: NonZeroU32) -> u8 { + // bps is between [1, 5_000_000] (datasheet max) + // clk_freq is between [0, 24_000_000] (datasheet max) + + // modulo = clk_freq % bps => modulo is between [0, 4_999_999] let modulo = clk_freq % bps; - // Fractional part lookup for the baud rate. Not extremely precise - if modulo * 19 < bps { - 0x0 - } else if modulo * 14 < bps { - 0x1 - } else if modulo * 12 < bps { - 0x2 - } else if modulo * 10 < bps { - 0x4 - } else if modulo * 8 < bps { - 0x8 - } else if modulo * 7 < bps { - 0x10 - } else if modulo * 6 < bps { - 0x20 - } else if modulo * 5 < bps { - 0x11 - } else if modulo * 4 < bps { - 0x22 - } else if modulo * 3 < bps { - 0x44 - } else if modulo * 11 < bps * 4 { - 0x49 - } else if modulo * 5 < bps * 2 { - 0x4A - } else if modulo * 7 < bps * 3 { - 0x92 - } else if modulo * 2 < bps { - 0x53 - } else if modulo * 7 < bps * 4 { - 0xAA - } else if modulo * 13 < bps * 8 { - 0x6B - } else if modulo * 3 < bps * 2 { - 0xAD - } else if modulo * 11 < bps * 8 { - 0xD6 - } else if modulo * 4 < bps * 3 { - 0xBB - } else if modulo * 5 < bps * 4 { - 0xDD - } else if modulo * 9 < bps * 8 { - 0xEF - } else { - 0xFD + // fraction = modulo * 10_000 / (bps), so within [0, ((bps-1) * 10_000) / bps]. + // To prove upper bound we note `(bps-1)/bps` is largest when bps == 5_000_000: + // (4_999_999 * 10_000) / 5_000_000 = 49_999_990_000 (watch out for overflow!) / 5_000_000 = 9999.99... truncated to 9_999 because integer division + // So fraction is within [0, 9999] + let fraction_as_ten_thousandths = if modulo < u32::MAX/10_000 { + // Most accurate + ((modulo * 10_000) / bps) as u16 + } + else { + // Avoid overflow if modulo is large. Assume modulo < 5_000_000 from datasheet max + (((modulo * 500) / bps) * 20) as u16 + }; + + // See Table 22-4 from MSP430FR4xx and MSP430FR2xx family user's guide (Rev. I) + match fraction_as_ten_thousandths { + 0..529 => 0x00, + 529..715 => 0x01, + 715..835 => 0x02, + 835..1001 => 0x04, + 1001..1252 => 0x08, + 1252..1430 => 0x10, + 1430..1670 => 0x20, + 1670..2147 => 0x11, + 2147..2224 => 0x21, + 2224..2503 => 0x22, + 2503..3000 => 0x44, + 3000..3335 => 0x25, + 3335..3575 => 0x49, + 3575..3753 => 0x4A, + 3753..4003 => 0x52, + 4003..4286 => 0x92, + 4286..4378 => 0x53, + 4378..5002 => 0x55, + 5002..5715 => 0xAA, + 5715..6003 => 0x6B, + 6003..6254 => 0xAD, + 6254..6432 => 0xB5, + 6432..6667 => 0xB6, + 6667..7001 => 0xD6, + 7001..7147 => 0xB7, + 7147..7503 => 0xBB, + 7503..7861 => 0xDD, + 7861..8004 => 0xED, + 8004..8333 => 0xEE, + 8333..8464 => 0xBF, + 8464..8572 => 0xDF, + 8572..8751 => 0xEF, + 8751..9004 => 0xF7, + 9004..9170 => 0xFB, + 9170..9288 => 0xFD, + 9288.. => 0xFE, } } @@ -402,7 +399,7 @@ impl SerialConfig { usci.brw_settings(baud_config.br); usci.mctlw_settings(baud_config.ucos16, baud_config.brs, baud_config.brf); usci.loopback(self.loopback.to_bool()); - usci.ctl0_settings(UcxCtl0 { + usci.ctl0_settings(UcaCtlw0 { ucpen: self.parity.ucpen(), ucpar: self.parity.ucpar(), ucmsb: self.order.to_bool(), @@ -508,6 +505,24 @@ impl Rx { let usci = unsafe { USCI::steal() }; usci.rxie_clear(); } + + /// Reads raw value from Rx buffer with no checks for validity + /// # Safety + /// May read duplicate data + #[inline(always)] + pub unsafe fn read_no_check(&mut self) -> u8 { + let usci = unsafe { USCI::steal() }; + usci.rx_rd() + } + + #[inline(always)] + /// Writes a byte into the Tx buffer with no checks for validity + /// # Safety + /// May clobber unsent data still in the buffer + pub unsafe fn write_no_check(&mut self, data: u8) { + let usci = unsafe { USCI::steal() }; + usci.tx_wr(data); + } } /// Serial receive errors diff --git a/src/spi.rs b/src/spi.rs new file mode 100644 index 0000000..f8773e9 --- /dev/null +++ b/src/spi.rs @@ -0,0 +1,357 @@ +//! SPI +//! +//! Peripherals eUSCI_A0, eUSCI_A1, eUSCI_B0 and eUSCI_B1 can be used for SPI communication. +//! +//! Begin by calling `SpiBusConfig::new()`. Once configured an `SpiBus` will be returned. +//! +//! `SpiBus` implements the embedded_hal `FullDuplex` trait with non-blocking `.read()` and `.send()` methods, +//! and the blocking embedded_hal `Transfer` and `Write` traits, with `.transfer()` and `.write()` methods respectively. +//! +//! Pins used: +//! +//! eUSCI_A0: {MISO: `P1.7`, MOSI: `P1.6`, SCLK: `P1.5`}. `P1.4` can optionally used as a hardware-controlled chip select pin. +//! +//! eUSCI_A1: {MISO: `P4.3`, MOSI: `P4.2`, SCLK: `P4.1`}. `P4.0` can optionally used as a hardware-controlled chip select pin. +//! +//! eUSCI_B0: {MISO: `P1.3`, MOSI: `P1.2`, SCLK: `P1.1`}. `P1.0` can optionally used as a hardware-controlled chip select pin. +//! +//! eUSCI_B1: {MISO: `P4.7`, MOSI: `P4.6`, SCLK: `P4.5`}. `P4.4` can optionally used as a hardware-controlled chip select pin. +use crate::hal::spi::{Mode, Phase, Polarity}; +use crate::{ + clock::{Aclk, Smclk}, + gpio::{Alternate1, Pin, Pin0, Pin1, Pin2, Pin3, Pin4, Pin5, Pin6, Pin7, P1, P4}, + hw_traits::eusci::{EusciSPI, Ucmode, Ucssel, UcxSpiCtw0}, +}; +use core::marker::PhantomData; +use embedded_hal::spi::FullDuplex; +use msp430fr2355 as pac; +use nb::Error::WouldBlock; + +/// Marks a eUSCI capable of SPI communication (in this case, all euscis do) +pub trait SpiUsci: EusciSPI { + /// Master In Slave Out (refered to as SOMI in datasheet) + type MISO; + /// Master Out Slave In (refered to as SIMO in datasheet) + type MOSI; + /// Serial Clock + type SCLK; + /// Slave Transmit Enable (acts like CS) + type STE; +} + +impl SpiUsci for pac::E_USCI_A0 { + type MISO = UsciA0MISOPin; + type MOSI = UsciA0MOSIPin; + type SCLK = UsciA0SCLKPin; + type STE = UsciA0STEPin; +} + +impl SpiUsci for pac::E_USCI_A1 { + type MISO = UsciA1MISOPin; + type MOSI = UsciA1MOSIPin; + type SCLK = UsciA1SCLKPin; + type STE = UsciA1STEPin; +} + +impl SpiUsci for pac::E_USCI_B0 { + type MISO = UsciB0MISOPin; + type MOSI = UsciB0MOSIPin; + type SCLK = UsciB0SCLKPin; + type STE = UsciB0STEPin; +} + +impl SpiUsci for pac::E_USCI_B1 { + type MISO = UsciB1MISOPin; + type MOSI = UsciB1MOSIPin; + type SCLK = UsciB1SCLKPin; + type STE = UsciB1STEPin; +} + +// Allows a GPIO pin to be converted into an SPI object +macro_rules! impl_spi_pin { + ($struct_name: ident, $port: ty, $pin: ty) => { + impl From>> for $struct_name { + #[inline(always)] + fn from(_val: Pin<$port, $pin, Alternate1>) -> Self { + $struct_name + } + } + }; +} + +/// SPI MISO pin for eUSCI A0 +pub struct UsciA0MISOPin; +impl_spi_pin!(UsciA0MISOPin, P1, Pin7); + +/// SPI MOSI pin for eUSCI A0 +pub struct UsciA0MOSIPin; +impl_spi_pin!(UsciA0MOSIPin, P1, Pin6); + +/// SPI SCLK pin for eUSCI A0 +pub struct UsciA0SCLKPin; +impl_spi_pin!(UsciA0SCLKPin, P1, Pin5); + +/// SPI STE pin for eUSCI A0 +pub struct UsciA0STEPin; +impl_spi_pin!(UsciA0STEPin, P1, Pin4); + +/// SPI MISO pin for eUSCI A1 +pub struct UsciA1MISOPin; +impl_spi_pin!(UsciA1MISOPin, P4, Pin3); + +/// SPI MOSI pin for eUSCI A1 +pub struct UsciA1MOSIPin; +impl_spi_pin!(UsciA1MOSIPin, P4, Pin2); + +/// SPI SCLK pin for eUSCI A1 +pub struct UsciA1SCLKPin; +impl_spi_pin!(UsciA1SCLKPin, P4, Pin1); +/// SPI STE pin for eUSCI A1 +pub struct UsciA1STEPin; +impl_spi_pin!(UsciA1STEPin, P4, Pin0); + +/// SPI MISO pin for eUSCI B0 +pub struct UsciB0MISOPin; +impl_spi_pin!(UsciB0MISOPin, P1, Pin3); + +/// SPI MOSI pin for eUSCI B0 +pub struct UsciB0MOSIPin; +impl_spi_pin!(UsciB0MOSIPin, P1, Pin2); + +/// SPI SCLK pin for eUSCI B0 +pub struct UsciB0SCLKPin; +impl_spi_pin!(UsciB0SCLKPin, P1, Pin1); + +/// SPI STE pin for eUSCI B0 +pub struct UsciB0STEPin; +impl_spi_pin!(UsciB0STEPin, P1, Pin0); + +/// SPI MISO pin for eUSCI B1 +pub struct UsciB1MISOPin; +impl_spi_pin!(UsciB1MISOPin, P4, Pin7); + +/// SPI MOSI pin for eUSCI B1 +pub struct UsciB1MOSIPin; +impl_spi_pin!(UsciB1MOSIPin, P4, Pin6); + +/// SPI SCLK pin for eUSCI B1 +pub struct UsciB1SCLKPin; +impl_spi_pin!(UsciB1SCLKPin, P4, Pin5); + +/// SPI STE pin for eUSCI B1 +pub struct UsciB1STEPin; +impl_spi_pin!(UsciB1STEPin, P4, Pin4); + +/// Typestate for an SPI bus configuration with no clock source selected +pub struct NoClockSet; +/// Typestate for an SPI bus configuration with a clock source selected +pub struct ClockSet; + +/// Struct used to configure a SPI bus +pub struct SpiBusConfig { + usci: USCI, + prescaler: u16, + + // Register configs + ctlw0: UcxSpiCtw0, + _phantom: PhantomData, +} + +impl SpiBusConfig { + /// Create a new configuration for setting up a EUSCI peripheral in SPI mode + pub fn new(usci: USCI, mode: Mode, msb_first: bool) -> Self { + let ctlw0 = UcxSpiCtw0 { + ucckph: match mode.phase { + Phase::CaptureOnFirstTransition => true, + Phase::CaptureOnSecondTransition => false, + }, + ucckpl: match mode.polarity { + Polarity::IdleLow => false, + Polarity::IdleHigh => true, + }, + ucmsb: msb_first, + uc7bit: false, + ucmst: true, + ucsync: true, + ucstem: true, + ucswrst: true, + ucmode: Ucmode::FourPinSPI0, // overwritten by `configure_with_software_cs()` + ucssel: Ucssel::Smclk, // overwritten by `use_smclk/aclk()` + }; + + SpiBusConfig { + usci, + prescaler: 0, + ctlw0, + _phantom: PhantomData, + } + } + + /// Configures this peripheral to use smclk + #[inline] + pub fn use_smclk(mut self, _smclk: &Smclk, clk_divisor: u16) -> SpiBusConfig{ + self.ctlw0.ucssel = Ucssel::Smclk; + self.prescaler = clk_divisor; + SpiBusConfig { usci: self.usci, prescaler: self.prescaler, ctlw0: self.ctlw0, _phantom: PhantomData } + } + + /// Configures this peripheral to use aclk + #[inline] + pub fn use_aclk(mut self, _aclk: &Aclk, clk_divisor: u16) -> SpiBusConfig { + self.ctlw0.ucssel = Ucssel::Aclk; + self.prescaler = clk_divisor; + SpiBusConfig { usci: self.usci, prescaler: self.prescaler, ctlw0: self.ctlw0, _phantom: PhantomData } + } +} +#[allow(private_bounds)] +impl SpiBusConfig { + /// Performs hardware configuration and creates an SPI bus. The STE pin is used as an automatically controlled chip select pin. Suitable for systems with only one slave device. + #[inline(always)] + pub fn configure_with_hardware_cs< + SO: Into, + SI: Into, + CLK: Into, + STE: Into, + >( + &mut self, + _miso: SO, + _mosi: SI, + _sclk: CLK, + _cs: STE, + ) -> SpiBus { + self.configure_hw(); + SpiBus(PhantomData) + } + + /// Performs hardware configuration and creates an SPI bus. You must configure and control any chip select pins yourself. Suitable for systems with multiple slave devices. + #[inline(always)] + pub fn configure_with_software_cs< + SO: Into, + SI: Into, + CLK: Into, + >( + &mut self, + _miso: SO, + _mosi: SI, + _sclk: CLK + ) -> SpiBus { + self.ctlw0.ucmode = Ucmode::ThreePinSPI; + self.configure_hw(); + SpiBus(PhantomData) + } + + #[inline] + fn configure_hw(&self) { + self.usci.ctw0_set_rst(); + + self.usci.ctw0_wr(&self.ctlw0); + self.usci.brw_wr(self.prescaler); + self.usci.uclisten_clear(); + + self.usci.ctw0_clear_rst(); + + self.usci.clear_transmit_interrupt(); + self.usci.clear_receive_interrupt(); + } +} + +/// Represents a group of pins configured for SPI communication +pub struct SpiBus(PhantomData); + +impl SpiBus { + /// Enable Rx interrupts, which fire when a byte is ready to be read + #[inline(always)] + pub fn set_rx_interrupt(&mut self) { + let usci = unsafe { USCI::steal() }; + usci.set_receive_interrupt(); + } + + /// Disable Rx interrupts, which fire when a byte is ready to be read + #[inline(always)] + pub fn clear_rx_interrupt(&mut self) { + let usci = unsafe { USCI::steal() }; + usci.clear_receive_interrupt(); + } + + /// Enable Tx interrupts, which fire when the transmit buffer is empty + #[inline(always)] + pub fn set_tx_interrupt(&mut self) { + let usci = unsafe { USCI::steal() }; + usci.set_transmit_interrupt(); + } + + /// Disable Tx interrupts, which fire when the transmit buffer is empty + #[inline(always)] + pub fn clear_tx_interrupt(&mut self) { + let usci = unsafe { USCI::steal() }; + usci.clear_transmit_interrupt(); + } + + /// Writes raw value to Tx buffer with no checks for validity + /// # Safety + /// May clobber unsent data still in the buffer + #[inline(always)] + pub unsafe fn write_no_check(&mut self, val: u8) { + let usci = unsafe { USCI::steal() }; + usci.txbuf_wr(val) + } + + #[inline(always)] + /// Reads a raw value from the Rx buffer with no checks for validity + /// # Safety + /// May read duplicate data + pub unsafe fn read_no_check(&mut self) -> u8 { + let usci = unsafe { USCI::steal() }; + usci.rxbuf_rd() + } + + #[inline(always)] + /// Change the SPI mode + pub fn change_mode(&mut self, mode: Mode) { + let usci = unsafe { USCI::steal() }; + usci.ctw0_set_rst(); + usci.set_spi_mode(mode); + usci.ctw0_clear_rst(); + } +} + +/// SPI transmit/receive errors +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum SPIErr { + /// Data in the recieve buffer was overwritten before it was read. The contained data is the new contents of the recieve buffer. + OverrunError(u8), + // In future the framing error bit UCFE may appear here. Right now it's unimplemented. +} + +impl FullDuplex for SpiBus { + type Error = SPIErr; + fn read(&mut self) -> nb::Result { + let usci = unsafe { USCI::steal() }; + + if usci.receive_flag() { + if usci.overrun_flag() { + Err(nb::Error::Other(SPIErr::OverrunError(usci.rxbuf_rd()))) + } + else { + Ok(usci.rxbuf_rd()) + } + } else { + Err(WouldBlock) + } + } + + fn send(&mut self, word: u8) -> nb::Result<(), Self::Error> { + let usci = unsafe { USCI::steal() }; + if usci.transmit_flag() { + usci.txbuf_wr(word); + Ok(()) + } else { + Err(WouldBlock) + } + } +} + +// Implementing FullDuplex above gets us a blocking write and transfer implementation for free +impl embedded_hal::blocking::spi::write::Default for SpiBus {} +impl embedded_hal::blocking::spi::transfer::Default for SpiBus {}