diff --git a/Cargo.toml b/Cargo.toml index ca159ee2b2..4476951abe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "examples/arduino-nano", "examples/arduino-uno", "examples/atmega2560", + "examples/avr-butterfly", "examples/nano168", "examples/sparkfun-promicro", "examples/sparkfun-promini-3v3", diff --git a/avr-hal-generic/src/clock.rs b/avr-hal-generic/src/clock.rs index f7588747bc..e0f3ba27bf 100644 --- a/avr-hal-generic/src/clock.rs +++ b/avr-hal-generic/src/clock.rs @@ -71,6 +71,20 @@ impl Clock for MHz8 { const FREQ: u32 = 8_000_000; } +/// 4 MHz Clock +#[derive(ufmt::derive::uDebug, Debug)] +pub struct MHz4; +impl Clock for MHz4 { + const FREQ: u32 = 4_000_000; +} + +/// 2 MHz Clock +#[derive(ufmt::derive::uDebug, Debug)] +pub struct MHz2; +impl Clock for MHz2 { + const FREQ: u32 = 2_000_000; +} + /// 1 MHz Clock #[derive(ufmt::derive::uDebug, Debug)] pub struct MHz1; diff --git a/examples/avr-butterfly/.cargo/config.toml b/examples/avr-butterfly/.cargo/config.toml new file mode 100644 index 0000000000..c6e01e86cc --- /dev/null +++ b/examples/avr-butterfly/.cargo/config.toml @@ -0,0 +1,9 @@ +[build] +target = "avr-none" +rustflags = ["-C", "target-cpu=atmega169pa"] + +[target.'cfg(target_arch = "avr")'] +runner = "ravedude" + +[unstable] +build-std = ["core"] diff --git a/examples/avr-butterfly/Cargo.toml b/examples/avr-butterfly/Cargo.toml new file mode 100644 index 0000000000..a552b19a0c --- /dev/null +++ b/examples/avr-butterfly/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "avr-butterfly-examples" +description = "Examples for the AVR Butterfly board with Atmega169V chip" +version = "0.0.0" +authors = ["Markus Kolb "] +edition = "2024" +publish = false + +[features] +critical-section-impl = ["avr-device/critical-section-impl"] + +[dependencies] +panic-halt = "1.0.0" +ufmt = "0.2.0" +nb = "1.1.0" +embedded-hal = "1.0" +avr-device = { version = "0.7", features = ["rt"] } + +[dependencies.atmega-hal] +path = "../../mcu/atmega-hal/" +features = ["atmega169pa"] diff --git a/examples/avr-butterfly/Ravedude.toml b/examples/avr-butterfly/Ravedude.toml new file mode 100644 index 0000000000..f659a5c7f5 --- /dev/null +++ b/examples/avr-butterfly/Ravedude.toml @@ -0,0 +1,7 @@ +[general] +board = "butterfly" +serial-baudrate = 57600 +open-console = false + +# For documentation about this file, check here: +# https://github.com/Rahix/avr-hal/blob/main/ravedude/README.md#ravedudetoml-format diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-adc.rs b/examples/avr-butterfly/src/bin/avr-butterfly-adc.rs new file mode 100644 index 0000000000..1977a167ab --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-adc.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::usart::{Baudrate, Usart}; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock in the root crate +type CoreClock = atmega_hal::clock::MHz8; +// Use it as follows in the rest of the project +type Adc = atmega_hal::adc::Adc; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + + // double set in 4 cycles to disable JTAG and make shared ADC pins usable + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // set up serial interface for text output + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + let mut adc = Adc::new(dp.ADC, Default::default()); + + // To store multiple channels in an array, we use the `into_channel()` method. + let channels: [atmega_hal::adc::Channel; 5] = [ + pins.pf0.into_analog_input(&mut adc).into_channel(), + pins.pf1.into_analog_input(&mut adc).into_channel(), + pins.pf2.into_analog_input(&mut adc).into_channel(), + pins.pf3.into_analog_input(&mut adc).into_channel(), + pins.pf4.into_analog_input(&mut adc).into_channel(), + ]; + + loop { + for (i, ch) in channels.iter().enumerate() { + let v = adc.read_blocking(ch); + ufmt::uwrite!(&mut serial, "PF{}: {} ", i, v).unwrap(); + } + + ufmt::uwriteln!(&mut serial, "\r").unwrap(); + delay.delay_ms(1000); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-blink.rs b/examples/avr-butterfly/src/bin/avr-butterfly-blink.rs new file mode 100644 index 0000000000..b1b7fc4ce9 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-blink.rs @@ -0,0 +1,47 @@ +/*! + # A led blink application + + This is a simple circuit with a blinking led. + + # The hardware + + * a led + * a 330 Ohm resistor + * some patch cables/connections + * The ports on the board (GND (JTAG/J402 Pin 2), PF4 (JTAG/J402 Pin 1), 3.3V (JTAG/J402 Pin 4)) + + # The board layout + + Connect the resistor to Pin 1 of J402 socket. Connect led anode to other end of resistor, led cathode to Pin 2 of J402. + Connect your 3.3V power source to Pin 4 (+) and Pin 2 (-) of J402 socket. + So yes, led cathode and negative part of powersource connects both at Pin 2 of J402, maybe use a breadboard. +*/ +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + + // double set in 4 cycles to disable JTAG and make shared ADC pins usable + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + let mut led = pins.pf4.into_output(); + + loop { + led.toggle(); + delay.delay_ms(1000); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-eeprom.rs b/examples/avr-butterfly/src/bin/avr-butterfly-eeprom.rs new file mode 100644 index 0000000000..98ad3169fa --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-eeprom.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use atmega_hal::Eeprom; +use atmega_hal::delay::Delay; +use atmega_hal::usart::{Baudrate, Usart}; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock in the root crate +type CoreClock = atmega_hal::clock::MHz8; + +const BOOT_COUNT_OFFSET: u16 = 0; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // set up serial interface for text output + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + let mut eeprom = Eeprom::new(dp.EEPROM); + + let eeprom_capacity = eeprom.capacity(); + ufmt::uwriteln!(&mut serial, "eeprom capacity is: {}", eeprom_capacity).unwrap(); + + let mut boot_count = eeprom.read_byte(BOOT_COUNT_OFFSET); + boot_count = boot_count.wrapping_add(1); + eeprom.write_byte(BOOT_COUNT_OFFSET, boot_count); + + ufmt::uwriteln!(&mut serial, "\rBoot count: {}\r", boot_count).unwrap(); + + loop { + delay.delay_ms(1000); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-joystick.rs b/examples/avr-butterfly/src/bin/avr-butterfly-joystick.rs new file mode 100644 index 0000000000..26958e59dd --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-joystick.rs @@ -0,0 +1,165 @@ +//! Joystick test code for AVR Butterfly +//! +//! Modifies display counter with up-right and down-left. +//! Pressing button displays PRESS. +//! + +#![no_std] +#![no_main] +#![feature(abi_avr_interrupt)] + +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use atmega_hal::delay::Delay; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +use avr_butterfly_lcd::Lcd; + +#[allow(dead_code)] +mod avr_butterfly_lcd; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +const PRESS: &[u8] = b"PRESS"; +const PRESS_OFFSET: usize = 6 - PRESS.len(); + +static CTRL_CHANGE: AtomicBool = AtomicBool::new(false); +static CTRL_PRESS: AtomicBool = AtomicBool::new(false); +static CTRL_VALUE: AtomicU8 = AtomicU8::new(127); + +/// Convert u8 number to ascii digits +fn u8_to_ascii(mut n: u8, buf: &mut [u8; 3]) -> usize { + if n < 10 { + buf[0] = b'0' + n; + return 1; + } + + let mut i = 0; + let mut tmp = [0u8; 3]; + while n > 0 { + let digit = n % 10; + tmp[i] = b'0' + digit; + n /= 10; + i += 1; + } + for j in 0..i { + buf[j] = tmp[i - 1 - j]; + } + + i +} + +/// Update LCD with current `CTRL_VALUE` +fn update_lcd(lcd: &mut Lcd) { + let val = CTRL_VALUE.load(Ordering::Relaxed); + let mut ascii_buf = [0u8; 3]; + let n = u8_to_ascii(val, &mut ascii_buf); + let display_val = &ascii_buf[..n]; + lcd.lcd_sync_enable(false); + // 1 for len of PRESS + for i in 1..(6 - n) { + lcd.lcd_write(0, i as u8); + } + for (i, &char_val) in display_val.iter().enumerate() { + lcd.lcd_write(char_val, (6 - n + i) as u8); + } + lcd.lcd_sync_enable(true); +} + +#[avr_device::entry] +fn main() -> ! { + let mut delay = Delay::::new(); + + let dp = atmega_hal::Peripherals::take().unwrap(); + + let pins = atmega_hal::pins!(dp); + let _bt_down = pins.pb7.into_pull_up_input(); + let _bt_left = pins.pe2.into_pull_up_input(); + let _bt_press = pins.pb4.into_pull_up_input(); + let _bt_right = pins.pe3.into_pull_up_input(); + let _bt_up = pins.pb6.into_pull_up_input(); + + // enable irq for pe2 (pcint2), pe3 (pcint3) + dp.EXINT.pcmsk0.write(|w| w.pcint().bits(0b0000_1100)); + // enable irq for pb4 (pcint12), pb6 (pcint14), pb7 (pcint15) + dp.EXINT.pcmsk1.write(|w| w.pcint().bits(0b1101_0000)); + // enable pcie0 and pcie1 for above pin irqs + dp.EXINT + .eimsk + .write(|w| w.pcie0().set_bit().pcie1().set_bit()); + + let lcd = Lcd::lcd_init(dp.LCD, Some(dp.TC2)).unwrap(); + unsafe { + avr_device::interrupt::enable(); // LCD uses start of frame interrupt + } + + update_lcd(lcd); + + loop { + if CTRL_CHANGE.load(Ordering::Relaxed) { + update_lcd(lcd); + CTRL_CHANGE.store(false, Ordering::Relaxed); + } else if CTRL_PRESS.load(Ordering::Relaxed) { + lcd.lcd_sync_enable(false); + for (i, &char_val) in PRESS.iter().enumerate() { + lcd.lcd_write(char_val, (PRESS_OFFSET + i) as u8); + } + lcd.lcd_sync_enable(true); + CTRL_PRESS.store(false, Ordering::Relaxed); + } + + delay.delay_ms(10); // should be ok for demo + } +} + +/// Irq handler for pcint2/3, so left/right +#[avr_device::interrupt(atmega169pa)] +fn PCINT0() { + // safety: in irq it is ok to use like this to check pin state + let dp = unsafe { atmega_hal::Peripherals::steal() }; + + let pe = dp.PORTE.pine.read(); + if pe.pe2().bit_is_clear() { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val > u8::MIN { + CTRL_VALUE.store(val - 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } else if pe.pe3().bit_is_clear() { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val < u8::MAX { + CTRL_VALUE.store(val + 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } +} + +/// Irq handler for pb4,6,7, so button/up/down +#[avr_device::interrupt(atmega169pa)] +fn PCINT1() { + // safety: in irq it is ok to use like this to check pin state + let dp = unsafe { atmega_hal::Peripherals::steal() }; + + let pb = dp.PORTB.pinb.read(); + if pb.pb7().bit_is_clear() { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val > u8::MIN { + CTRL_VALUE.store(val - 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } else if pb.pb6().bit_is_clear() { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val < u8::MAX { + CTRL_VALUE.store(val + 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } + + if pb.pb4().bit_is_clear() { + CTRL_PRESS.store(true, Ordering::Relaxed); + } else if pb.pb4().bit_is_set() { + CTRL_CHANGE.store(true, Ordering::Relaxed); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-ky040-rotary.rs b/examples/avr-butterfly/src/bin/avr-butterfly-ky040-rotary.rs new file mode 100644 index 0000000000..3b53beb27f --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-ky040-rotary.rs @@ -0,0 +1,179 @@ +//! Rotary encoder KY040 test code for AVR Butterfly +//! +//! Modifies display counter with rotating encoder. +//! Pressing button displays PRESS. +//! +//! KY040 needs to be connected with CLK to PB6, DT to PB7 and SW to PB4. +//! VCC and GND corresponding to AVR Butterfly. +//! + +#![no_std] +#![no_main] +#![feature(abi_avr_interrupt)] + +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use atmega_hal::delay::Delay; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +use avr_butterfly_lcd::Lcd; + +#[allow(dead_code)] +mod avr_butterfly_lcd; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +const PRESS: &[u8] = b"PRESS"; +const PRESS_OFFSET: usize = 6 - PRESS.len(); + +static CTRL_CHANGE: AtomicBool = AtomicBool::new(false); +static CTRL_PRESS: AtomicBool = AtomicBool::new(false); +static CTRL_VALUE: AtomicU8 = AtomicU8::new(127); + +/// Convert u8 number to ascii digits +fn u8_to_ascii(mut n: u8, buf: &mut [u8; 3]) -> usize { + if n < 10 { + buf[0] = b'0' + n; + return 1; + } + + let mut i = 0; + let mut tmp = [0u8; 3]; + while n > 0 { + let digit = n % 10; + tmp[i] = b'0' + digit; + n /= 10; + i += 1; + } + for j in 0..i { + buf[j] = tmp[i - 1 - j]; + } + + i +} + +/// Update LCD with current `CTRL_VALUE` +fn update_lcd(lcd: &mut Lcd) { + let val = CTRL_VALUE.load(Ordering::Relaxed); + let mut ascii_buf = [0u8; 3]; + let n = u8_to_ascii(val, &mut ascii_buf); + let display_val = &ascii_buf[..n]; + lcd.lcd_sync_enable(false); + // 1 for len of PRESS + for i in 1..(6 - n) { + lcd.lcd_write(0, i as u8); + } + for (i, &char_val) in display_val.iter().enumerate() { + lcd.lcd_write(char_val, (6 - n + i) as u8); + } + lcd.lcd_sync_enable(true); +} + +#[avr_device::entry] +fn main() -> ! { + let mut delay = Delay::::new(); + + let dp = atmega_hal::Peripherals::take().unwrap(); + + let pins = atmega_hal::pins!(dp); + let _clk = pins.pb6.into_pull_up_input(); + let _dt = pins.pb7.into_pull_up_input(); + let _sw = pins.pb4.into_pull_up_input(); + + // enable irq for pb4 (pcint12), pb6 (pcint14), pb7 (pcint15) + dp.EXINT.pcmsk1.write(|w| w.pcint().bits(0b1101_0000)); + // enable pcie0 for above pin irqs + dp.EXINT.eimsk.write(|w| w.pcie1().set_bit()); + + let lcd = Lcd::lcd_init(dp.LCD, Some(dp.TC2)).unwrap(); + unsafe { + avr_device::interrupt::enable(); // LCD uses start of frame interrupt + } + + update_lcd(lcd); + + loop { + if CTRL_CHANGE.load(Ordering::Relaxed) { + update_lcd(lcd); + CTRL_CHANGE.store(false, Ordering::Relaxed); + } else if CTRL_PRESS.load(Ordering::Relaxed) { + lcd.lcd_sync_enable(false); + for (i, &char_val) in PRESS.iter().enumerate() { + lcd.lcd_write(char_val, (PRESS_OFFSET + i) as u8); + } + lcd.lcd_sync_enable(true); + } + + delay.delay_ms(10); // should be ok for demo + } +} + +/// previous sample storage for `PCINT1()` +static mut SAMPLE_PREV: u8 = 0b11; + +/// Irq handler for pb4,6,7, so button/rotate CW-CCW +#[avr_device::interrupt(atmega169pa)] +fn PCINT1() { + // safety: in irq it is ok to use like this to check pin state + let dp = unsafe { atmega_hal::Peripherals::steal() }; + let pb = dp.PORTB.pinb.read(); + + if pb.pb4().bit_is_clear() { + CTRL_PRESS.store(true, Ordering::Relaxed); + return; + } else if pb.pb4().bit_is_set() && CTRL_PRESS.load(Ordering::Relaxed) { + CTRL_PRESS.store(false, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + return; + } + + let sample = ((pb.pb7().bit() as u8) << 1) | pb.pb6().bit() as u8; + + unsafe { + // 1st part of bitfield is used to count rotary step parts + // 2nd part, the 2 lsb is the previous sample + // 3rd part is current sample + #[allow(clippy::unusual_byte_groupings)] + match (SAMPLE_PREV & 0b111_111_00, SAMPLE_PREV & 0b11, sample) { + // + // CW 11 -> 10 -> 00 -> 01 -> 11 + (0b1_00, 0b10, 0b00) => { + SAMPLE_PREV |= 0b10_00; + } + (0b11_00, 0b00, 0b01) => { + SAMPLE_PREV |= 0b100_00; + } + (0b111_00, 0b01, 0b11) => { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val < u8::MAX { + CTRL_VALUE.store(val + 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } + // here CW order begins because of wildcard match + (_, 0b11, 0b10) => SAMPLE_PREV = 0b1_11, + // + // CCW 11 -> 01 -> 00 -> 10 -> 11 + (0b1_000_00, 0b01, 0b00) => { + SAMPLE_PREV |= 0b10_000_00; + } + (0b11_000_00, 0b00, 0b10) => { + SAMPLE_PREV |= 0b100_000_00; + } + (0b111_000_00, 0b10, 0b11) => { + let val = CTRL_VALUE.load(Ordering::Relaxed); + if val > u8::MIN { + CTRL_VALUE.store(val - 1, Ordering::Relaxed); + CTRL_CHANGE.store(true, Ordering::Relaxed); + } + } + // here CCW order begins because of wildcard match + (_, 0b11, 0b01) => SAMPLE_PREV = 0b1_000_11, + _ => {} + } + + SAMPLE_PREV = (SAMPLE_PREV & !0b11) | sample; + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-lcd.rs b/examples/avr-butterfly/src/bin/avr-butterfly-lcd.rs new file mode 100644 index 0000000000..02e31294f5 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-lcd.rs @@ -0,0 +1,126 @@ +//! LCD test code for AVR Butterfly +//! +//! Uses all available segments and displays all available and supportable ascii chars on +//! all LCD digits. +//! + +#![no_std] +#![no_main] +#![feature(abi_avr_interrupt)] + +use atmega_hal::delay::Delay; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +use avr_butterfly_lcd::{LCD_MAP, Lcd, SpecialSeg}; + +mod avr_butterfly_lcd; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +const DIGITS_AVRHAL: &[u8; 6] = b"AVRHAL"; + +#[avr_device::entry] +fn main() -> ! { + let mut delay = Delay::::new(); + + let dp = atmega_hal::Peripherals::take().unwrap(); + + // let lcd = Lcd::lcd_init(dp.LCD, None).unwrap(); + let lcd = Lcd::lcd_init(dp.LCD, Some(dp.TC2)).unwrap(); + + unsafe { + avr_device::interrupt::enable(); // LCD uses start of frame interrupt + } + + for n in 0..6 { + for m in 0..n { + lcd.lcd_write(b'-', m); + } + lcd.lcd_write(b'>', n); + delay.delay_ms(500); + } + + for (d, n) in (0..5).rev().enumerate() { + for m in 0..n { + lcd.lcd_write(b'-', m); + } + lcd.lcd_write(b'>', n); + + for i in 0..=(d as u8) { + lcd.lcd_write(DIGITS_AVRHAL[i as usize], i + n + 1); + } + + delay.delay_ms(500); + } + + lcd.lcd_sync_enable(false); + for (digit_nr, &char_val) in DIGITS_AVRHAL.iter().enumerate() { + lcd.lcd_write(char_val, digit_nr as u8); + } + lcd.lcd_sync_enable(true); + delay.delay_ms(1000); + + lcd.lcd_sync_enable(false); + for n in 0..6 { + lcd.lcd_write(32, n); + } + lcd.lcd_sync_enable(true); + + let seg_list = [ + SpecialSeg::N1, + SpecialSeg::N2, + SpecialSeg::N4, + SpecialSeg::N5, + SpecialSeg::N9, + SpecialSeg::N10, + SpecialSeg::S1, + SpecialSeg::S2, + SpecialSeg::S3, + SpecialSeg::S4, + SpecialSeg::S9, + SpecialSeg::S10, + ]; + + for seg in seg_list { + while !lcd.lcd_is_sync() { + delay.delay_ms(100); + } + lcd.lcd_special_seg(seg, true); + delay.delay_ms(1000); + } + delay.delay_ms(1000); + + for seg in seg_list { + while !lcd.lcd_is_sync() { + delay.delay_ms(100); + } + lcd.lcd_special_seg(seg, false); + delay.delay_ms(500); + } + + while !lcd.lcd_is_sync() { + delay.delay_ms(100); + } + + loop { + for char_val in 32..(LCD_MAP.len() + 32) { + let char_val = char_val as u8; + for digit_nr in 0..6 { + lcd.lcd_write(char_val, digit_nr); + delay.delay_ms(500); + } + } + + lcd.lcd_sync_enable(false); + for (digit_nr, &char_val) in DIGITS_AVRHAL.iter().enumerate() { + lcd.lcd_write(char_val, digit_nr as u8); + } + lcd.lcd_sync_enable(true); + delay.delay_ms(3000); + lcd.lcd_enable(false); + delay.delay_ms(2000); + lcd.lcd_enable(true); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-ldr.rs b/examples/avr-butterfly/src/bin/avr-butterfly-ldr.rs new file mode 100644 index 0000000000..dc404f1e30 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-ldr.rs @@ -0,0 +1,70 @@ +/*! + # A light sensor application + + This example uses the AVR butterfly onboard ldr sensor. + According to the brightness the lightsensor is seeing a sentences like: + "This is a well lit room!" are emitted using the serial USB connection. + +!*/ + +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::usart::{Baudrate, Usart}; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock in the root crate +type CoreClock = atmega_hal::clock::MHz8; +// Use it as follows in the rest of the project +type Adc = atmega_hal::adc::Adc; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + + // double set in 4 cycles to disable JTAG and make shared ADC pins usable + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + dp.JTAG.mcucr.write(|w| w.jtd().set_bit()); + + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // setup the serial connection for the output. + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + // setup the analog digital converter + let mut adc = Adc::new(dp.ADC, Default::default()); + // in this example we only need pin pf2 which is connected to ldr sensor_2 + let pf2 = pins.pf2.into_analog_input(&mut adc); + + loop { + // read the voltage at pf2 and convert it to an integer between 0 and 1023 + let sensor_value = pf2.analog_read(&mut adc); + + // convert the number to human readable words + let worded = match sensor_value { + x if x > 950 => "very dark", + x if x > 800 => "rather dark", + x if x > 650 => "dimmly lit", + x if x > 400 => "lit", + x if x > 100 => "well lit", + x if x <= 100 => "bright", + _ => "invalid", // to satisfy the compiler () + }; + + // output to the serial + ufmt::uwrite!(&mut serial, "This is a {} room! – ", worded).unwrap(); + ufmt::uwrite!(&mut serial, "Raw value: {} ", sensor_value).unwrap(); + ufmt::uwriteln!(&mut serial, "\r").unwrap(); + + // wait for half a second then measure again + delay.delay_ms(500); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-simple-pwm-sound.rs b/examples/avr-butterfly/src/bin/avr-butterfly-simple-pwm-sound.rs new file mode 100644 index 0000000000..a5cd5a0c83 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-simple-pwm-sound.rs @@ -0,0 +1,46 @@ +/*! + * Example of using simple_pwm to generate some sound with piezo element + */ +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::simple_pwm::{IntoPwmPin as _, Prescaler, Timer1Pwm}; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock in the root crate +type CoreClock = atmega_hal::clock::MHz8; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + let timer1 = Timer1Pwm::new(dp.TC1, Prescaler::Prescale64); + + // Pin PB5 is connected to onboard piezo element + let mut pwm_piezo = pins.pb5.into_output().into_pwm(&timer1); + pwm_piezo.enable(); + + loop { + for (duty, duration, pause) in [ + (100u8, 250, 400), + (150, 300, 300), + (90, 375, 450), + (120, 170, 500), + (140, 200, 500), + (180, 250, 600), + (200, 300, 400), + (160, 200, 375), + ] { + pwm_piezo.set_duty(duty); + delay.delay_ms(duration); + pwm_piezo.set_duty(0); + delay.delay_ms(pause); + } + delay.delay_ms(1500); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-spi-feedback.rs b/examples/avr-butterfly/src/bin/avr-butterfly-spi-feedback.rs new file mode 100644 index 0000000000..b00c72f61e --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-spi-feedback.rs @@ -0,0 +1,58 @@ +//! This example demonstrates how to set up a SPI interface and communicate +//! over it. The physical hardware configuration consists of connecting a +//! jumper directly from pin `PB2` to pin `PB3` (Socket J400 Port B). +//! +//! Run the program using `cargo run`. +//! You should see it output the line `data: 42` repeatedly. +//! If the output you see is `data: 255`, you may need to check your jumper. + +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::spi; +use atmega_hal::usart::{Baudrate, Usart}; +use embedded_hal::delay::DelayNs; +use embedded_hal::spi::SpiBus; +use panic_halt as _; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // set up serial interface for text output + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + // Create SPI interface. + let (mut spi, _) = spi::Spi::new( + dp.SPI, + pins.pb1.into_output(), + pins.pb2.into_output(), + pins.pb3.into_pull_up_input(), + pins.pb0.into_output(), + spi::Settings::default(), + ); + + loop { + // Send a byte + let data_out: [u8; 1] = [42]; + let mut data_in: [u8; 1] = [0]; + // Send a byte + // Because MISO is connected to MOSI, the read data should be the same + spi.transfer(&mut data_in, &data_out).unwrap(); + + ufmt::uwriteln!(&mut serial, "data: {}\r", data_in[0]).unwrap(); + delay.delay_ms(1000); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-usart.rs b/examples/avr-butterfly/src/bin/avr-butterfly-usart.rs new file mode 100644 index 0000000000..d304f31ea0 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-usart.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use atmega_hal::prelude::*; +use atmega_hal::usart::{Baudrate, Usart}; +use panic_halt as _; + +// Define core clock. This can be used in the rest of the project. +type CoreClock = atmega_hal::clock::MHz8; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + ufmt::uwriteln!(&mut serial, "Hello from ATmega!\r").unwrap(); + + loop { + // Read a byte from the serial connection + let b = nb::block!(serial.read()).unwrap(); + + // Answer + ufmt::uwriteln!(&mut serial, "Got {}!\r", b).unwrap(); + } +} diff --git a/examples/avr-butterfly/src/bin/avr-butterfly-watchdog.rs b/examples/avr-butterfly/src/bin/avr-butterfly-watchdog.rs new file mode 100644 index 0000000000..079da3388d --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr-butterfly-watchdog.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use atmega_hal::delay::Delay; +use atmega_hal::usart::{Baudrate, Usart}; +use atmega_hal::wdt; +use embedded_hal::delay::DelayNs; +use panic_halt as _; + +// Define core clock in the root crate +type CoreClock = atmega_hal::clock::MHz8; + +#[avr_device::entry] +fn main() -> ! { + let dp = atmega_hal::Peripherals::take().unwrap(); + let pins = atmega_hal::pins!(dp); + + let mut delay = Delay::::new(); + + // setup the serial connection for the output. + let mut serial = Usart::new( + dp.USART0, + pins.pe0, + pins.pe1.into_output(), + Baudrate::::new(57600), + ); + + ufmt::uwrite!(&mut serial, "\r\nSetup started...").unwrap(); + + for _ in 0..20 { + ufmt::uwrite!(&mut serial, ".").unwrap(); + delay.delay_ms(100); + } + ufmt::uwriteln!(&mut serial, "\r\nEnabling watchdog...\r").unwrap(); + + let mut watchdog = wdt::Wdt::new(dp.WDT, &dp.CPU.mcusr); + watchdog.start(wdt::Timeout::Ms2000).unwrap(); + + ufmt::uwriteln!(&mut serial, "Watchdog on watch...\r").unwrap(); + + ufmt::uwrite!(&mut serial, "\rWaiting...").unwrap(); + loop { + ufmt::uwrite!(&mut serial, ".").unwrap(); + delay.delay_ms(1000); + // watchdog.feed(); + } +} diff --git a/examples/avr-butterfly/src/bin/avr_butterfly_lcd/mod.rs b/examples/avr-butterfly/src/bin/avr_butterfly_lcd/mod.rs new file mode 100644 index 0000000000..ef74b0ab34 --- /dev/null +++ b/examples/avr-butterfly/src/bin/avr_butterfly_lcd/mod.rs @@ -0,0 +1,555 @@ +//! `LCD` driver for the ATmega169 LCD controller + +use avr_device::atmega169pa::{LCD as Mega169_LCD, TC2}; + +macro_rules! seg { + ( $($s:ident),* ) => {{ + 0u16 $(| seg_bit!($s))* + }}; +} + +macro_rules! seg_bit { + (A) => { + 1 << 0 + }; + (S) => { + 1 << 1 + }; + (X) => { + 1 << 2 + }; + (K) => { + 1 << 3 + }; + (B) => { + 1 << 4 + }; + (H) => { + 1 << 5 + }; + (F) => { + 1 << 6 + }; + (J) => { + 1 << 7 + }; + (C) => { + 1 << 8 + }; + (G) => { + 1 << 9 + }; + (E) => { + 1 << 10 + }; + (L) => { + 1 << 11 + }; + (D) => { + 1 << 12 + }; + (N) => { + 1 << 13 + }; + (P) => { + 1 << 14 + }; + (M) => { + 1 << 15 + }; +} + +/// Mapping of LCD segments to supported ascii chars +/// +/// The supported ascii chars start with space (32) and end with ~ (126). +/// All other char codes from u8 range become replaced with an _ (95). +/// +/// The segment arrangement is +/// +///
+///   AAAAAAAAA
+/// F H   J   K B
+/// F  H  J  K  B
+/// F   H J K   B
+/// F    HJK    B
+///   GGGG LLLL
+/// E    PNM    C
+/// E   P N M   C
+/// E  P  N  M  C
+/// E P   N   M C
+///   DDDDDDDDD
+/// 
+/// +/// The LCD display on AVR butterfly board supports some additional fixed small symbols: +/// - numbers 1, 2, 4, 5, 9, 10 +/// - markers S1, S2, S3, S4, S9, S10 +/// +/// See [`SpecialSeg`]. +/// +/// # See also +/// +/// [STK502 User Guide](https://usermanual.wiki/Atmel/AtmelStk502UsersManual500190.957703282.pdf) +/// and AVR App Note +/// [AVR065: LCD Driver for the STK502](https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/doc2530.pdf) +/// +pub const LCD_MAP: [u16; 95] = [ + 0, // space + seg!(D, J, N), // ! + seg!(B, J), // " + seg!(B, C, D, G, J, L, N), // # + seg!(A, C, D, F, G, L, J, N), // $ + seg!(C, F, G, H, J, K, L, M, N, P), // % + seg!(A, D, E, G, H, J, M), // & + seg!(J), // ' + seg!(K, M), // ( + seg!(H, P), // ) + seg!(G, H, J, K, L, M, N, P), // * + seg!(G, J, L, N), // + + seg!(P), // , + seg!(G, L), // - + seg!(P), // . + seg!(K, P), // / + // + // digits + seg!(A, B, C, D, E, F, K, P), // 0 + seg!(B, C, K), // 1 + seg!(A, B, D, E, G, L), // 2 + seg!(A, B, C, D, G, L), // 3 + seg!(B, C, F, G, L), // 4 + seg!(A, D, F, G, M), // 5 + seg!(A, C, D, E, F, G, L), // 6 + seg!(A, B, C, F), // 7 + seg!(A, B, C, D, E, F, G, L), // 8 + seg!(A, B, C, D, F, G, L), // 9 + // + // + seg!(G, P), // : + seg!(J, P), // ; + seg!(G, K, M), // < + seg!(G, L, D), // = + seg!(H, L, P), // > + seg!(A, B, D, L, N), // ? + seg!(A, B, D, E, F, J, L), // @ + // + // uppercase letters A–Z + seg!(A, B, C, E, F, G, L), // A + seg!(A, B, C, D, J, L, N), // B + seg!(A, D, E, F), // C + seg!(A, B, C, D, J, N), // D + seg!(A, D, E, F, G), // E + seg!(A, E, F, G), // F + seg!(A, C, D, E, F, L), // G + seg!(B, C, E, F, G, L), // H + seg!(A, D, J, N), // I + seg!(B, C, D, E), // J + seg!(E, F, G, K, M), // K + seg!(D, E, F), // L + seg!(B, C, E, F, H, K), // M + seg!(B, C, E, F, H, M), // N + seg!(A, B, C, D, E, F), // O + seg!(A, B, E, F, G, L), // P + seg!(A, B, C, D, E, F, M), // Q + seg!(A, B, E, F, G, L, M), // R + seg!(A, C, D, F, G, L), // S + seg!(A, J, N), // T + seg!(B, C, D, E, F), // U + seg!(E, F, K, P), // V + seg!(B, C, E, F, P, M), // W + seg!(H, K, M, P), // X + seg!(B, C, D, F, G, L), // Y + seg!(A, D, K, P), // Z + // + // + seg!(A, D, E, F), // [ + seg!(H, M), // \ + seg!(A, B, C, D), // ] + seg!(M, P), // ^ + seg!(D), // _ + seg!(H), // ` + // + // lowercase letters a–z + seg!(D, E, G, N), // a + seg!(D, E, F, G, M), // b + seg!(D, E, G, L), // c + seg!(B, C, D, L, P), // d + seg!(D, E, G, P), // e + seg!(G, K, L, N), // f + seg!(B, C, D, K, L), // g + seg!(E, F, G, N), // h + seg!(N), // i + seg!(B, C, D, P), // j + seg!(J, K, M, N), // k + seg!(E, F), // l + seg!(C, E, G, L, N), // m + seg!(E, G, N), // n + seg!(C, D, E, G, L), // o + seg!(A, E, F, G, K), // p + seg!(A, B, C, H, L), // q + seg!(E, G), // r + seg!(A, C, D, H, L), // s + seg!(D, E, F, G), // t + seg!(C, D, E), // u + seg!(E, P), // v + seg!(C, E, M, P), // w + seg!(H, K, M, P), // x + seg!(B, C, D, H, L), // y + seg!(D, G, P), // z + // + // + seg!(A, D, G, H, P), // { + seg!(J, N), // | + seg!(A, D, K, L, M), // } + seg!(G, K, L, P), // ~ +]; + +/// The LCD display on AVR butterfly board supports some additional fixed small symbols: +/// - numbers 1, 2, 4, 5, 9, 10 +/// - markers S1, S2, S3, S4, S9, S10 +/// +/// At least it is documented like this, but my physical display has additional bars above the two +/// right digits, and overall the mapping of these symbols is not working as expected. +/// See the enum fields for what might be happening. +#[derive(Clone, Copy)] +pub enum SpecialSeg { + N1, + /// board enables here 2 and 9 + N2, + /// board enables 4 and a bar above 2nd right most digit + N4, + N5, + /// board enables 9 and S10 + N9, + N10, + S1, + /// board enables S2 and S9 + S2, + S3, + S4, + /// board enables S9 and a bar above the right most + S9, + S10, +} + +static mut LCD_USED: bool = false; + +static mut LCD: Lcd = Lcd { + lcd: None, + lcd_buf: LCDBuf { + digits: [0u8; 6], + n_digits: 0u8, + s_digits: 0u8, + }, + sync: true, + sync_enable: true, +}; + +/// `LCD` driver for the ATmega169 LCD controller +pub struct Lcd { + lcd: Option, + lcd_buf: LCDBuf, + sync: bool, + sync_enable: bool, +} + +impl Lcd { + /// Enable and disable LCD + pub fn lcd_enable(&mut self, enable: bool) { + if let Some(ref lcd) = self.lcd { + lcd.lcdcra.write(|w| { + if enable { + w.lcden() // LCD enable + .set_bit() + .lcdie() // LCD SOF interrupt enable, global interrupts need to be enabled external + .set_bit() + } else { + w.lcden() // LCD disable + .clear_bit() + .lcdie() // LCD SOF interrupt disable + .clear_bit() + } + }); + } + } + + /// `true` when buffer has been used in irq and LCD is in sync + pub fn lcd_is_sync(&self) -> bool { + self.sync + } + + /// Init LCD for usage + /// + /// Global interrupts need to be enabled for updating the LCD + /// ```rust + /// unsafe { avr_device::interrupt::enable(); } + /// ``` + /// + /// When providing the optional `tc2` async timer clock, it used with on-board 32khz crystal + /// for efficient LCD frame timing. + /// + /// Without `tc2` the LCD is timed by sys clock. + /// + pub fn lcd_init(lcd: Mega169_LCD, tc2: Option) -> Option<&'static mut Self> { + if avr_device::interrupt::free(|_| { + if unsafe { LCD_USED } { + true + } else { + unsafe { + LCD_USED = true; + } + false + } + }) { + return None; + } + + if let Some(tc2) = tc2 { + tc2.assr.write(|w| w.as2().set_bit()); + lcd.lcdcrb.write(|w| { + w.lcdmux() + .bits(0b11) // 1/4 duty + .lcd2b() + .clear_bit() // 1/3 bias + .lcdpm() + .bits(0b111) // all LCD port pins + .lcdcs() + .set_bit() // clock souce TOSC1 pin (32khz osci) + }); + lcd.lcdfrr.write(|w| w.lcdps().clklcd_16().lcdcd()._3()); // prescaler/divider => 16 / 4 => =64Hz + } else { + lcd.lcdcrb.write(|w| { + w.lcdmux() + .bits(0b11) // 1/4 duty + .lcd2b() + .clear_bit() // 1/3 bias + .lcdpm() + .bits(0b111) // all LCD port pins + }); + lcd.lcdfrr.write(|w| w.lcdps().clklcd_4096().lcdcd()._3()); // prescaler/divider => 4096 / 4 => ~64Hz + } + + lcd.lcdccr.write(|w| w.lcdcc()._3_00v()); // recommended contrast + + lcd.lcdcra.write(|w| { + w.lcden() // LCD enable + .set_bit() + .lcdie() // LCD SOF interrupt enable, global interrupts need to be enabled external + .set_bit() + }); + + unsafe { + LCD.lcd = Some(lcd); + } + + let p = &raw mut LCD; + unsafe { p.as_mut() } + } + + /// Enable and disable a [`SpecialSeg`] + pub fn lcd_special_seg(&mut self, seg: SpecialSeg, enable: bool) { + let (digits, bit) = match seg { + SpecialSeg::N1 => (&mut self.lcd_buf.n_digits, 0u8), + SpecialSeg::N2 => (&mut self.lcd_buf.n_digits, 1), + SpecialSeg::N4 => (&mut self.lcd_buf.n_digits, 2), + SpecialSeg::N5 => (&mut self.lcd_buf.n_digits, 3), + SpecialSeg::N9 => (&mut self.lcd_buf.n_digits, 4), + SpecialSeg::N10 => (&mut self.lcd_buf.n_digits, 5), + SpecialSeg::S1 => (&mut self.lcd_buf.s_digits, 0), + SpecialSeg::S2 => (&mut self.lcd_buf.s_digits, 1), + SpecialSeg::S3 => (&mut self.lcd_buf.s_digits, 2), + SpecialSeg::S4 => (&mut self.lcd_buf.s_digits, 3), + SpecialSeg::S9 => (&mut self.lcd_buf.s_digits, 4), + SpecialSeg::S10 => (&mut self.lcd_buf.s_digits, 5), + }; + + Self::update_special(&mut self.sync, digits, bit, enable); + } + + /// Enable and disable refresh sync the LCD from buffer + pub fn lcd_sync_enable(&mut self, enable: bool) { + self.sync_enable = enable; + } + + /// Write supported ascii char `char_val` to LCD digit `digit_nr` + /// + /// This actual writes to buffer and LCD is synced in interrupt handler + /// + pub fn lcd_write(&mut self, char_val: u8, digit_nr: u8) { + if self.lcd_buf.digits[digit_nr as usize] != char_val { + self.lcd_buf.digits[digit_nr as usize] = char_val; + self.sync = false; + } + } + + /// Set LCD register to modify display + /// + /// `digit_nr` is the number of digit starting from left with `0`. + /// The AVR Butterfly doesn't support the 1st digit on LCD display, + /// so `digit_nr` is the 2nd physical segment area. + /// + /// `digit` is the segment control code for the requested signs. + /// + /// `n_digits` is a bitfield for special number segments + /// + /// `s_digits` is a bitfield for special s segments + /// + fn set_lcd_digit(lcd: &Mega169_LCD, digit_nr: u8, digit: u16, n_digits: u8, s_digits: u8) { + match digit_nr { + 0 => { + let mut digit_odd = lcd.lcddr0.read().bits() & 0xF0; + let special = ((n_digits & 0x1) << 2) | ((s_digits & 0x1) << 1); + lcd.lcddr0 + .write(|w| w.bits((digit & 0xF) as u8 | special | digit_odd)); + digit_odd = lcd.lcddr5.read().bits() & 0xF0; + lcd.lcddr5 + .write(|w| w.bits((digit >> 4 & 0xF) as u8 | digit_odd)); + digit_odd = lcd.lcddr10.read().bits() & 0xF0; + lcd.lcddr10 + .write(|w| w.bits((digit >> 8 & 0xF) as u8 | digit_odd)); + digit_odd = lcd.lcddr15.read().bits() & 0xF0; + lcd.lcddr15 + .write(|w| w.bits((digit >> 12 & 0xF) as u8 | digit_odd)); + } + 1 => { + let mut digit_even = lcd.lcddr0.read().bits() & 0xF; + let special = ((n_digits & 0x2) << 5) | ((s_digits & 0x2) << 4); + lcd.lcddr0 + .write(|w| w.bits(((digit & 0xF) << 4) as u8 | special | digit_even)); + digit_even = lcd.lcddr5.read().bits() & 0xF; + lcd.lcddr5 + .write(|w| w.bits((digit & 0xF0) as u8 | digit_even)); + digit_even = lcd.lcddr10.read().bits() & 0xF; + lcd.lcddr10 + .write(|w| w.bits(((digit & 0xF00) >> 4) as u8 | digit_even)); + digit_even = lcd.lcddr15.read().bits() & 0xF; + lcd.lcddr15 + .write(|w| w.bits(((digit & 0xF000) >> 8) as u8 | digit_even)); + } + + 2 => { + let mut digit_odd = lcd.lcddr1.read().bits() & 0xF0; + let special = ((n_digits & 0x4) >> 1) | (s_digits & 0x4); + lcd.lcddr1 + .write(|w| w.bits((digit & 0xF) as u8 | special | digit_odd)); + digit_odd = lcd.lcddr6.read().bits() & 0xF0; + lcd.lcddr6 + .write(|w| w.bits((digit >> 4 & 0xF) as u8 | digit_odd)); + digit_odd = lcd.lcddr11.read().bits() & 0xF0; + lcd.lcddr11 + .write(|w| w.bits((digit >> 8 & 0xF) as u8 | digit_odd)); + digit_odd = lcd.lcddr16.read().bits() & 0xF0; + lcd.lcddr16 + .write(|w| w.bits((digit >> 12 & 0xF) as u8 | digit_odd)); + } + 3 => { + let mut digit_even = lcd.lcddr1.read().bits() & 0xF; + let special = ((n_digits & 0x8) << 2) | ((s_digits & 0x8) << 3); + lcd.lcddr1 + .write(|w| w.bits(((digit & 0xF) << 4) as u8 | special | digit_even)); + digit_even = lcd.lcddr6.read().bits() & 0xF; + lcd.lcddr6 + .write(|w| w.bits((digit & 0xF0) as u8 | digit_even)); + digit_even = lcd.lcddr11.read().bits() & 0xF; + lcd.lcddr11 + .write(|w| w.bits(((digit & 0xF00) >> 4) as u8 | digit_even)); + + digit_even = lcd.lcddr16.read().bits() & 0xF; + lcd.lcddr16 + .write(|w| w.bits(((digit & 0xF000) >> 8) as u8 | digit_even)); + } + + 4 => { + let mut digit_odd = lcd.lcddr2.read().bits() & 0xF0; + let special = ((n_digits & 0x16) >> 2) | ((s_digits & 0x16) >> 3); + lcd.lcddr2 + .write(|w| w.bits((digit & 0xF) as u8 | special | digit_odd)); + digit_odd = lcd.lcddr7.read().bits() & 0xF0; + lcd.lcddr7 + .write(|w| w.bits((digit >> 4 & 0xF) as u8 | digit_odd)); + + digit_odd = lcd.lcddr12.read().bits() & 0xF0; + lcd.lcddr12 + .write(|w| w.bits((digit >> 8 & 0xF) as u8 | digit_odd)); + + digit_odd = lcd.lcddr17.read().bits() & 0xF0; + lcd.lcddr17 + .write(|w| w.bits((digit >> 12 & 0xF) as u8 | digit_odd)); + } + 5 => { + let mut digit_even = lcd.lcddr2.read().bits() & 0xF; + let special = ((n_digits & 0x32) << 1) | (s_digits & 0x32); + lcd.lcddr2 + .write(|w| w.bits(((digit & 0xF) << 4) as u8 | special | digit_even)); + digit_even = lcd.lcddr7.read().bits() & 0xF; + lcd.lcddr7 + .write(|w| w.bits((digit & 0xF0) as u8 | digit_even)); + digit_even = lcd.lcddr12.read().bits() & 0xF; + lcd.lcddr12 + .write(|w| w.bits(((digit & 0xF00) >> 4) as u8 | digit_even)); + digit_even = lcd.lcddr17.read().bits() & 0xF; + lcd.lcddr17 + .write(|w| w.bits(((digit & 0xF000) >> 8) as u8 | digit_even)); + } + _ => assert!(digit_nr < 6), + } + } + + /// Set digit `digit_nr` to an ascii representation of ascii `char_val` + /// + /// Unsupported values are replaced with `_`. + /// + fn set_lcd_digit_char( + lcd: &Mega169_LCD, + digit_nr: u8, + char_val: u8, + n_digits: u8, + s_digits: u8, + ) { + let char_val = if (32..=126).contains(&char_val) { + char_val + } else if char_val == 0 { + b' ' + } else { + b'_' + }; + + let digit = LCD_MAP[char_val as usize - 32]; + + Self::set_lcd_digit(lcd, digit_nr, digit, n_digits, s_digits); + } + + /// Set corresponding bit in `digits` bitfield + fn update_special(sync: &mut bool, digits: &mut u8, bit: u8, enable: bool) { + if *digits & (1u8 << bit) != enable as u8 { + *sync = false; + if enable { + *digits |= 1u8 << bit; + } else { + *digits &= !(1u8 << bit); + } + } + } +} + +struct LCDBuf { + digits: [u8; 6], + n_digits: u8, + s_digits: u8, +} + +/// Updates the `LCDDRn` registers from changed buffered data and when `sync_enable` is true +#[avr_device::interrupt(atmega169pa)] +fn LCD() { + if unsafe { LCD.sync || !LCD.sync_enable } { + return; + } + let lcd = unsafe { (&raw const LCD.lcd) }; + if let Some(lcd) = unsafe { lcd.as_ref() }.unwrap() { + let buf = unsafe { (&raw const LCD.lcd_buf).as_ref() }.unwrap(); + for (digit_nr, &char_val) in buf.digits.iter().enumerate() { + Lcd::set_lcd_digit_char(lcd, digit_nr as u8, char_val, buf.n_digits, buf.s_digits); + } + unsafe { + LCD.sync = true; + } + } +} diff --git a/mcu/atmega-hal/Cargo.toml b/mcu/atmega-hal/Cargo.toml index bff9738b51..06518f224d 100644 --- a/mcu/atmega-hal/Cargo.toml +++ b/mcu/atmega-hal/Cargo.toml @@ -18,6 +18,7 @@ atmega48p = ["avr-device/atmega48p", "device-selected"] atmega16 = ["avr-device/atmega16", "device-selected"] atmega164pa = ["avr-device/atmega164pa", "device-selected"] atmega168 = ["avr-device/atmega168", "device-selected"] +atmega169pa = ["avr-device/atmega169pa", "device-selected"] atmega328p = ["avr-device/atmega328p", "device-selected"] atmega328pb = ["avr-device/atmega328pb", "device-selected"] atmega32a = ["avr-device/atmega32a", "device-selected"] diff --git a/mcu/atmega-hal/src/adc.rs b/mcu/atmega-hal/src/adc.rs index 2881ae62fa..7895c5afa0 100644 --- a/mcu/atmega-hal/src/adc.rs +++ b/mcu/atmega-hal/src/adc.rs @@ -405,3 +405,26 @@ avr_hal_generic::impl_adc! { channel::Gnd: crate::pac::adc::admux::MUX_A::ADC_GND, }, } + +#[cfg(any(feature = "atmega169pa"))] +avr_hal_generic::impl_adc! { + hal: crate::Atmega, + peripheral: crate::pac::ADC, + settings: AdcSettings, + apply_settings: |peripheral, settings| { apply_settings(peripheral, settings) }, + channel_id: u8, + set_channel: |peripheral, id| { + peripheral.admux.modify(|_, w| w.mux().variant(id)); + }, + pins: { + port::PF0: (0b000000, didr0::adc0d), + port::PF1: (0b000001, didr0::adc1d), + port::PF2: (0b000010, didr0::adc2d), + port::PF3: (0b000011, didr0::adc3d), + port::PF4: (0b000100, didr0::adc4d), + port::PF5: (0b000101, didr0::adc5d), + port::PF6: (0b000110, didr0::adc6d), + port::PF7: (0b000111, didr0::adc7d), + }, + channels: {}, +} diff --git a/mcu/atmega-hal/src/eeprom.rs b/mcu/atmega-hal/src/eeprom.rs index 865929676f..2a032d9a0a 100644 --- a/mcu/atmega-hal/src/eeprom.rs +++ b/mcu/atmega-hal/src/eeprom.rs @@ -86,7 +86,7 @@ avr_hal_generic::impl_eeprom_atmega! { }, } -#[cfg(any(feature = "atmega8", feature = "atmega16"))] +#[cfg(any(feature = "atmega8", feature = "atmega16", feature = "atmega169pa"))] avr_hal_generic::impl_eeprom_atmega_old! { hal: crate::Atmega, peripheral: crate::pac::EEPROM, @@ -97,7 +97,7 @@ avr_hal_generic::impl_eeprom_atmega_old! { }, } -#[cfg(any(feature = "atmega32a"))] +#[cfg(feature = "atmega32a")] avr_hal_generic::impl_eeprom_atmega_old! { hal: crate::Atmega, peripheral: crate::pac::EEPROM, @@ -108,7 +108,7 @@ avr_hal_generic::impl_eeprom_atmega_old! { }, } -#[cfg(any(feature = "atmega128a",))] +#[cfg(feature = "atmega128a")] avr_hal_generic::impl_eeprom_atmega_old! { hal: crate::Atmega, peripheral: crate::pac::EEPROM, diff --git a/mcu/atmega-hal/src/lib.rs b/mcu/atmega-hal/src/lib.rs index d076052345..e507f96974 100644 --- a/mcu/atmega-hal/src/lib.rs +++ b/mcu/atmega-hal/src/lib.rs @@ -75,6 +75,10 @@ pub use avr_device::atmega164pa as pac; /// #[cfg(feature = "atmega168")] pub use avr_device::atmega168 as pac; +/// Reexport of `atmega169pa` from `avr-device` +/// +#[cfg(feature = "atmega169pa")] +pub use avr_device::atmega169pa as pac; /// Reexport of `atmega2560` from `avr-device` /// #[cfg(feature = "atmega2560")] @@ -193,7 +197,7 @@ macro_rules! pins { }; } -#[cfg(any(feature = "atmega128a"))] +#[cfg(any(feature = "atmega128a", feature = "atmega169pa"))] #[macro_export] macro_rules! pins { ($p:expr) => { diff --git a/mcu/atmega-hal/src/port.rs b/mcu/atmega-hal/src/port.rs index f33d2368a2..b9a27156dd 100644 --- a/mcu/atmega-hal/src/port.rs +++ b/mcu/atmega-hal/src/port.rs @@ -64,7 +64,7 @@ avr_hal_generic::impl_port_traditional! { } } -#[cfg(any(feature = "atmega128a"))] +#[cfg(any(feature = "atmega128a", feature = "atmega169pa"))] avr_hal_generic::impl_port_traditional_old! { enum Ports { A: crate::pac::PORTA = [0, 1, 2, 3, 4, 5, 6, 7], diff --git a/mcu/atmega-hal/src/simple_pwm.rs b/mcu/atmega-hal/src/simple_pwm.rs index 296280f3c9..75608b5657 100644 --- a/mcu/atmega-hal/src/simple_pwm.rs +++ b/mcu/atmega-hal/src/simple_pwm.rs @@ -251,6 +251,138 @@ avr_hal_generic::impl_simple_pwm! { } } +#[cfg(feature = "atmega169pa")] +avr_hal_generic::impl_simple_pwm! { + /// Use `TC0` for PWM (pins `PB4`) + /// + /// # Example + /// ``` + /// let mut timer0 = Timer0Pwm::new(dp.TC0, Prescaler::Prescale64); + /// + /// let mut pb4 = pins.pb4.into_output().into_pwm(&mut timer0); + /// + /// pb4.set_duty(128); + /// pb4.enable(); + /// ``` + pub struct Timer0Pwm { + timer: crate::pac::TC0, + init: |tim, prescaler| { + tim.tccr0a.modify(|_r, w| { + w.wgm00().set_bit().wgm01().set_bit(); // pwm_fast + match prescaler { + Prescaler::Direct => w.cs0().direct(), + Prescaler::Prescale8 => w.cs0().prescale_8(), + Prescaler::Prescale64 => w.cs0().prescale_64(), + Prescaler::Prescale256 => w.cs0().prescale_256(), + Prescaler::Prescale1024 => w.cs0().prescale_1024() + } + }); + }, + pins: { + PB4: { + ocr: ocr0a, + into_pwm: |tim| if enable { + tim.tccr0a.modify(|_r, w| w.com0a().match_clear()); + } else { + tim.tccr0a.modify(|_r, w| w.com0a().disconnected()); + }, + }, + }, + } +} + +#[cfg(feature = "atmega169pa")] +avr_hal_generic::impl_simple_pwm! { + /// Use `TC1` for PWM (pins `PB5`, `PB6`) + /// + /// # Example + /// ``` + /// let mut timer1 = Timer1Pwm::new(dp.TC1, Prescaler::Prescale64); + /// + /// let mut pb5 = pins.pb5.into_output().into_pwm(&mut timer1); + /// let mut pb6 = pins.pb6.into_output().into_pwm(&mut timer1); + /// + /// pb5.set_duty(128); + /// pb5.enable(); + /// pb6.set_duty(128); + /// pb6.enable(); + /// ``` + pub struct Timer1Pwm { + timer: crate::pac::TC1, + init: |tim, prescaler| { + tim.tccr1a.modify(|_r, w| w.wgm1().bits(0b01)); + tim.tccr1b.modify(|_r, w| w.wgm1().bits(0b01)); + + tim.tccr1b.modify(|_r, w| match prescaler { + Prescaler::Direct => w.cs1().direct(), + Prescaler::Prescale8 => w.cs1().prescale_8(), + Prescaler::Prescale64 => w.cs1().prescale_64(), + Prescaler::Prescale256 => w.cs1().prescale_256(), + Prescaler::Prescale1024 => w.cs1().prescale_1024(), + }); + }, + pins: { + PB5: { + ocr: ocr1a, + into_pwm: |tim| if enable { + tim.tccr1a.modify(|_r, w| w.com1a().match_clear()); + } else { + tim.tccr1a.modify(|_r, w| w.com1a().disconnected()); + }, + }, + + PB6: { + ocr: ocr1b, + into_pwm: |tim| if enable { + tim.tccr1a.modify(|_r, w| w.com1b().match_clear()); + } else { + tim.tccr1a.modify(|_r, w| w.com1b().disconnected()); + }, + }, + }, + } +} + +#[cfg(feature = "atmega169pa")] +avr_hal_generic::impl_simple_pwm! { + /// Use `TC1` for PWM (pin `PB7`) + /// + /// # Example + /// ``` + /// let mut timer1 = Timer1Pwm::new(dp.TC1, Prescaler::Prescale64); + /// + /// let mut pb7 = pins.pb7.into_output().into_pwm(&mut timer1); + /// + /// pb7.set_duty(128); + /// pb7.enable(); + /// ``` + pub struct Timer2Pwm { + timer: crate::pac::TC2, + init: |tim, prescaler| { + tim.tccr2a.modify(|_r, w| { + w.wgm20().set_bit().wgm21().set_bit(); // pwm_fast + match prescaler { + Prescaler::Direct => w.cs2().direct(), + Prescaler::Prescale8 => w.cs2().prescale_8(), + Prescaler::Prescale64 => w.cs2().prescale_64(), + Prescaler::Prescale256 => w.cs2().prescale_256(), + Prescaler::Prescale1024 => w.cs2().prescale_1024(), + } + }); + }, + pins: { + PB7: { + ocr: ocr2a, + into_pwm: |tim| if enable { + tim.tccr2a.modify(|_r, w| w.com2a().match_clear()); + } else { + tim.tccr2a.modify(|_r, w| w.com2a().disconnected()); + }, + }, + }, + } +} + #[cfg(any(feature = "atmega1280", feature = "atmega2560"))] avr_hal_generic::impl_simple_pwm! { /// Use `TC0` for PWM (pins `PB7`, `PG5`) diff --git a/mcu/atmega-hal/src/spi.rs b/mcu/atmega-hal/src/spi.rs index 5338e89fe5..bfc78affd5 100644 --- a/mcu/atmega-hal/src/spi.rs +++ b/mcu/atmega-hal/src/spi.rs @@ -35,8 +35,9 @@ pub use avr_hal_generic::spi::*; #[cfg(any( feature = "atmega128a", feature = "atmega1280", + feature = "atmega169pa", feature = "atmega2560", - feature = "atmega32u4" + feature = "atmega32u4", ))] pub type Spi = avr_hal_generic::spi::Spi< crate::Atmega, @@ -49,6 +50,7 @@ pub type Spi = avr_hal_generic::spi::Spi< #[cfg(any( feature = "atmega128a", feature = "atmega1280", + feature = "atmega169pa", feature = "atmega2560", feature = "atmega32u4" ))] diff --git a/mcu/atmega-hal/src/usart.rs b/mcu/atmega-hal/src/usart.rs index 6f630fcfd3..c3dfd928aa 100644 --- a/mcu/atmega-hal/src/usart.rs +++ b/mcu/atmega-hal/src/usart.rs @@ -122,14 +122,23 @@ avr_hal_generic::impl_usart_traditional! { tx: port::PD3, } -#[cfg(any(feature = "atmega128a", feature = "atmega1280", feature = "atmega2560"))] +#[cfg(any( + feature = "atmega128a", + feature = "atmega1280", + feature = "atmega2560", + feature = "atmega169pa" +))] pub type Usart0 = Usart< crate::pac::USART0, port::Pin, port::Pin, CLOCK, >; -#[cfg(any(feature = "atmega1280", feature = "atmega2560"))] +#[cfg(any( + feature = "atmega1280", + feature = "atmega2560", + feature = "atmega169pa" +))] avr_hal_generic::impl_usart_traditional! { hal: crate::Atmega, peripheral: crate::pac::USART0, diff --git a/mcu/atmega-hal/src/wdt.rs b/mcu/atmega-hal/src/wdt.rs index 6ff553e90b..82250e2b28 100644 --- a/mcu/atmega-hal/src/wdt.rs +++ b/mcu/atmega-hal/src/wdt.rs @@ -7,7 +7,8 @@ pub type Wdt = avr_hal_generic::wdt::Wdt; feature = "atmega8", feature = "atmega16", feature = "atmega32a", - feature = "atmega128a" + feature = "atmega128a", + feature = "atmega169pa" )))] avr_hal_generic::impl_wdt! { hal: crate::Atmega, @@ -47,3 +48,22 @@ avr_hal_generic::impl_wdt! { Timeout::Ms8000 => panic!() // Does not exist for ATmega8 ... }, } + +#[cfg(feature = "atmega169pa")] +avr_hal_generic::impl_wdt! { + hal: crate::Atmega, + peripheral: crate::pac::WDT, + mcusr: crate::pac::cpu::MCUSR, + wdtcsr_name: wdtcr, + timeout: |to, w| match to { + Timeout::Ms16 => w.wdpl().cycles_16k(), + Timeout::Ms32 => w.wdpl().cycles_32k(), + Timeout::Ms64 => w.wdpl().cycles_64k(), + Timeout::Ms125 => w.wdpl().cycles_128k(), + Timeout::Ms250 => w.wdpl().cycles_256k(), + Timeout::Ms500 => w.wdpl().cycles_512k(), + Timeout::Ms1000 => w.wdpl().cycles_1024k(), + Timeout::Ms2000 => w.wdpl().cycles_2048k(), + Timeout::Ms4000 | Timeout::Ms8000 => panic!() // Does not exist for ATmega169 + }, +}