diff --git a/Cargo.toml b/Cargo.toml index 5a1a6d8..51034a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,7 @@ description = """ Reusable components for the Arduino Uno. """ keywords = ["avr", "arduino", "uno"] + +[build-dependencies] +avr-mcu = "0.2" + diff --git a/avr-atmega328p.json b/avr-atmega328p.json new file mode 100644 index 0000000..951e1ef --- /dev/null +++ b/avr-atmega328p.json @@ -0,0 +1,26 @@ +{ + "llvm-target": "avr-unknown-unknown", + "cpu": "atmega328p", + "target-endian": "little", + "target-pointer-width": "16", + "os": "unknown", + "target-env": "", + "target-vendor": "unknown", + "arch": "avr", + "data-layout": "e-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8", + + "executables": true, + + "linker": "avr-gcc", + "linker-flavor": "gcc", + "pre-link-args": { + "gcc": ["-Os", "-mmcu=atmega328p"] + }, + "exe-suffix": ".elf", + "post-link-args": { + "gcc": ["-Wl,--gc-sections"] + }, + + "no-default-libraries": false +} + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..1ccf925 --- /dev/null +++ b/build.rs @@ -0,0 +1,161 @@ +extern crate avr_mcu; + +use avr_mcu::*; +use std::fs::{self, File}; +use std::io; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; + +fn cores_path() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("src").join("cores") +} + +fn core_module_name(mcu: &Mcu) -> String { + mcu.device.name.to_lowercase().to_owned() +} + +fn main() { + if !cores_path().exists() { + fs::create_dir_all(&cores_path()).expect("could not create cores directory"); + } + + let current_mcu = avr_mcu::current::mcu() + .expect("no target cpu specified"); + generate_cores(&[current_mcu]).unwrap() +} + +fn generate_cores(mcus: &[Mcu]) -> Result<(), io::Error> { + for mcu in mcus { + generate_core_module(mcu).expect("failed to generate mcu core"); + } + generate_cores_mod_rs(mcus) +} + +fn generate_core_module(mcu: &Mcu) -> Result<(), io::Error> { + let path = cores_path().join(format!("{}.rs", core_module_name(mcu))); + let mut file = File::create(&path)?; + write_core_module(mcu, &mut file) +} + +fn generate_cores_mod_rs(mcus: &[Mcu]) -> Result<(), io::Error> { + let path = cores_path().join("mod.rs"); + let mut w = File::create(&path)?; + + writeln!(w, "//! Cores")?; + writeln!(w)?; + for mcu in mcus { + writeln!(w, "/// The {}.", mcu.device.name)?; + writeln!(w, "pub mod {};", core_module_name(mcu))?; + } + writeln!(w) +} + +fn write_core_module(mcu: &Mcu, w: &mut Write) -> Result<(), io::Error> { + writeln!(w, "//! Core for {}.", mcu.device.name)?; + writeln!(w)?; + writeln!(w, "use {{HardwareSpi, Pin, Register}};")?; + writeln!(w)?; + + gen::write_registers(mcu, w)?; + gen::write_pins(mcu, w)?; + gen::write_spi_modules(mcu, w)?; + + writeln!(w) +} + +mod gen { + use avr_mcu::*; + use std::io; + use std::io::prelude::*; + + pub fn write_registers(mcu: &Mcu, w: &mut Write) -> Result<(), io::Error> { + for register in mcu.registers() { + let ty = if register.size == 1 { "u8" } else { "u16" }; + + // HACK: Skip, atmeg328p pack defines two of these. + if register.name == "GTCCR" { continue; } + + writeln!(w, "pub struct {};", register.name)?; + writeln!(w, "impl Register<{}> for {} {{", ty, register.name)?; + writeln!(w, " const ADDR: *mut {} = 0x{:x} as *mut {};", ty, register.offset, ty)?; + writeln!(w, "}}")?; + } + + Ok(()) + } + + pub fn write_pins(mcu: &Mcu, w: &mut Write) -> Result<(), io::Error> { + if let Some(port) = mcu.peripheral("PORT") { + for instance in port.instances.iter() { + for signal in instance.signals.iter() { + let idx = signal.index.expect("signal with no index"); + let struct_name = pin_name(instance, signal); + + let io_module = mcu.modules.iter().find(|m| m.name == "PORT") + .expect("no port io module defined for this port"); + let register_group = io_module.register_groups.iter() + .find(|rg| rg.name == instance.name) + .expect("no register group defined for this port"); + + writeln!(w, "pub struct {};", struct_name)?; + writeln!(w)?; + writeln!(w, "impl Pin for {} {{", struct_name)?; + for reg in register_group.registers.iter() { + let mut const_name = reg.name.clone(); + const_name.pop(); // Pop port character from register name (DDRB/PORTB/etc).. + + writeln!(w, " /// {}.", reg.caption)?; + writeln!(w, " type {} = {};", const_name, reg.name)?; + } + writeln!(w, " /// {}", signal.pad)?; + writeln!(w, " const MASK: u8 = 1<<{};", idx)?; + writeln!(w, "}}")?; + writeln!(w)?; + } + } + } + Ok(()) + } + + pub fn write_spi_modules(mcu: &Mcu, w: &mut Write) -> Result<(), io::Error> { + if let Some(module) = mcu.module("SPI") { + let peripheral = mcu.peripheral("SPI").expect("found SPI module but no peripheral"); + let port_peripheral = mcu.port_peripheral(); + + writeln!(w, "pub struct Spi;")?; + writeln!(w)?; + writeln!(w, "impl HardwareSpi for Spi {{")?; + + for spi_signal in peripheral.signals() { + let spi_signal_name = spi_signal.group.clone().expect("spi signal does not have group name"); + let (port_instance, port_signal) = port_peripheral.instance_signal_with_pad(&spi_signal.pad) + .expect("no port signal associated with the spi signal pad"); + let pin_name = self::pin_name(port_instance, port_signal); + + writeln!(w, " type {} = {};", spi_signal_name, pin_name)?; + } + + for reg in module.registers() { + let const_name = match ®.caption[..] { + "SPI Data Register" => "SPDR", + "SPI Status Register" => "SPSR", + "SPI Control Register" => "SPCR", + _ => panic!("unknown SPI module register: '{}'", reg.caption), + }; + + + writeln!(w, " /// {}.", reg.caption)?; + writeln!(w, " type {} = {};", const_name, reg.name)?; + } + writeln!(w, "}}")?; + } + Ok(()) + } + + /// Gets the name of a pin. + fn pin_name(instance: &Instance, signal: &Signal) -> String { + let idx = signal.index.expect("signal with no index"); + format!("{}{}", instance.name, idx) + } +} + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..f780e29 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#! /bin/sh +xargo build --target avr-atmega328p diff --git a/src/cores/.gitignore b/src/cores/.gitignore new file mode 100644 index 0000000..6f5f3d1 --- /dev/null +++ b/src/cores/.gitignore @@ -0,0 +1 @@ +*.rs diff --git a/src/cores/.gitkeep b/src/cores/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 08a1c3f..599ac9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,26 @@ #![no_core] -extern crate core; +#![no_std] + +pub use self::reg::Register; +pub use self::pin::Pin; +pub use self::spi::HardwareSpi; pub mod prelude; +pub mod serial; pub mod timer0; pub mod timer1; -pub mod serial; +pub mod cores; + +mod reg; +mod pin; +mod spi; + +pub enum DataDirection { + Input, + Output, +} macro_rules! bit { (-, $pos:expr) => {}; diff --git a/src/pin.rs b/src/pin.rs new file mode 100644 index 0000000..dfb76df --- /dev/null +++ b/src/pin.rs @@ -0,0 +1,81 @@ +use {DataDirection, Register}; + +/// An IO pin. +pub trait Pin { + /// The associated data direction registerr. + type DDR: Register; + /// The associated port register. + type PORT: Register; + /// The associated pin register. + /// + /// Reads from the register will read input bits. + /// Writes to the register will toggle bits. + type PIN: Register; + /// The mask of the pin used for accessing registers. + const MASK: u8; + + /// Sets the data direction of the pin. + #[inline(always)] + fn set_direction(direction: DataDirection) { + match direction { + DataDirection::Input => Self::set_input(), + DataDirection::Output => Self::set_output(), + } + } + + /// Sets the pin up as an input. + #[inline(always)] + fn set_input() { + Self::DDR::unset(Self::MASK); + } + + /// Sets the pin up as an output. + #[inline(always)] + fn set_output() { + Self::DDR::set(Self::MASK); + } + + /// Set the pin to high. + /// + /// The pin must be configured as an output. + #[inline(always)] + fn set_high() { + Self::PORT::set(Self::MASK); + } + + /// Set the pin to low. + /// + /// The pin must be configured as an output. + #[inline(always)] + fn set_low() { + Self::PORT::unset(Self::MASK); + } + + /// Toggles the pin. + /// + /// The pin must be configured as an output. + #[inline(always)] + fn toggle() { + // FIXME: We can optimise this on post-2006 AVRs. + // http://www.avrfreaks.net/forum/toggle-state-output-pin + // set(Self::PIN, Self::MASK); + Self::PORT::toggle(Self::MASK); + } + + /// Check if the pin is currently high. + /// + /// The pin must be configured as an input. + #[inline(always)] + fn is_high() -> bool { + Self::PIN::is_set(Self::MASK) + } + + /// Checks if the pin is currently low. + /// + /// The pin must be configured as an input. + #[inline(always)] + fn is_low() -> bool { + Self::PIN::is_clear(Self::MASK) + } +} + diff --git a/src/reg.rs b/src/reg.rs new file mode 100644 index 0000000..194823f --- /dev/null +++ b/src/reg.rs @@ -0,0 +1,95 @@ +use core::{cmp, convert, ops}; + +pub trait RegVal : Copy + Clone + + ops::BitAnd + + ops::BitAndAssign + + ops::BitOr + + ops::BitOrAssign + + ops::BitXor + + ops::BitXorAssign + + ops::Not + + cmp::PartialEq + cmp::Eq + + cmp::PartialOrd + cmp::Ord + + convert::From { +} + +/// A register. +pub trait Register { + /// The address of the register. + const ADDR: *mut T; + + /// Writes a value to the register. + #[inline(always)] + fn write(value: T) { + unsafe { + *Self::ADDR = value; + } + } + + /// Reads the value of the register. + #[inline(always)] + fn read() -> T { + unsafe { *Self::ADDR } + } + + /// Sets a bitmask in a register. + #[inline(always)] + fn set(mask: T) { + unsafe { + *Self::ADDR |= mask; + } + } + + /// Clears a bitmask from a register. + #[inline(always)] + fn unset(mask: T) { + unsafe { + *Self::ADDR &= !mask; + } + } + + /// Toggles a mask in the register. + #[inline(always)] + fn toggle(mask: T) { + unsafe { + *Self::ADDR ^= mask; + } + } + + /// Checks if a mask is set in the register. + #[inline(always)] + fn is_set(mask: T) -> bool { + unsafe { + (*Self::ADDR & mask) == mask + } + } + + /// Checks if a mask is clear in the register. + #[inline(always)] + fn is_clear(mask: T) -> bool { + unsafe { + (*Self::ADDR & mask) == T::from(0) + } + } + + /// Waits until some condition is true of the register. + #[inline(always)] + fn wait_until(mut f: F) + where F: FnMut() -> bool { + loop { + if f() { + break; + } + } + } + + /// Waits until a mask is set. + #[inline(always)] + fn wait_until_set(mask: T) { + Self::wait_until(|| Self::is_set(mask)) + } +} + +impl RegVal for u8 { } +impl RegVal for u16 { } + diff --git a/src/spi.rs b/src/spi.rs new file mode 100644 index 0000000..71d5892 --- /dev/null +++ b/src/spi.rs @@ -0,0 +1,166 @@ +use {Register, Pin}; + + +/// An SPI module. +/// +/// Information at +/// http://maxembedded.com/2013/11/the-spi-of-the-avr/ +pub trait HardwareSpi { + /// Master-in slave-out pin. + type MISO: Pin; + /// Master-out slave-in pin. + type MOSI: Pin; + /// Serial clock pin. + type SCK: Pin; + /// Slave-select pin. + type SS: Pin; + + /// The SPI control register. + type SPCR: Register; + /// The SPI status register. + type SPSR: Register; + /// The SPI data register. + type SPDR: Register; + + /// Sets up the SPI as a master. + fn setup_master() { + // Setup DDR registers. + Self::MISO::set_input(); + Self::MOSI::set_output(); + Self::SCK::set_output(); + Self::SS::set_input(); + + Self::set_master(); + Self::enable_interrupt(); + Self::enable(); + } + + /// Sets up the SPI as a slave. + fn setup_slave() { + // Setup DDR registers. + Self::MISO::set_output(); + Self::MOSI::set_input(); + Self::SCK::set_input(); + Self::SS::set_input(); + + Self::set_slave(); + Self::enable(); + } + + /// Enables interrupts for the spi module. + #[inline(always)] + fn enable_interrupt() { + Self::SPCR::set(spcr::INTERRUPT_ENABLE); + } + + /// Disables interrupts for the spi module. + #[inline(always)] + fn disable_interrupt() { + Self::SPCR::unset(spcr::INTERRUPT_ENABLE); + } + + /// Enables the SPI. + #[inline(always)] + fn enable() { + Self::SPCR::set(spcr::ENABLE); + } + + /// Disables the SPI. + #[inline(always)] + fn disable() { + Self::SPCR::unset(spcr::ENABLE); + } + + /// Enables least-significant-bit first. + #[inline(always)] + fn set_lsb() { + Self::SPCR::set(spcr::DATA_ORDER_LSB); + } + + /// Enables most-significant-bit first. + #[inline(always)] + fn set_msb() { + Self::SPCR::unset(spcr::DATA_ORDER_LSB); + } + + /// Enables master mode. + #[inline(always)] + fn set_master() { + Self::SPCR::set(spcr::MASTER); + } + + /// Enables slave mode. + #[inline(always)] + fn set_slave() { + Self::SPCR::unset(spcr::MASTER); + } + + /// Enables double speed mode. + #[inline(always)] + fn enable_double_speed() { + Self::SPSR::set(spsr::SPI2X); + } + + /// Disables double speed mode. + #[inline(always)] + fn disable_double_speed() { + Self::SPSR::unset(spsr::SPI2X); + } + + /// Checks if there is a write collision. + #[inline(always)] + fn is_write_collision() -> bool { + Self::SPSR::is_set(spsr::WCOL) + } + + /// Sends a byte through the serial. + #[inline(always)] + fn send_byte(byte: u8) { + Self::SPDR::write(byte); + Self::SPSR::wait_until_set(spsr::SPIF); + } + + /// Reads a byte from the serial. + #[inline(always)] + fn receive_byte() -> u8 { + Self::SPSR::wait_until_set(spsr::SPIF); + Self::SPDR::read() + } + + /// Sends and receives a byte. + #[inline(always)] + fn send_receive(byte: u8) -> u8 { + Self::SPDR::write(byte); + Self::SPSR::wait_until_set(spsr::SPIF); + Self::SPDR::read() + } +} + +/// Constants for the control register. +#[allow(dead_code)] +mod spcr { + pub const INTERRUPT_ENABLE: u8 = 1<<7; + pub const ENABLE: u8 = 1<<6; + pub const DATA_ORDER_LSB: u8 = 1<<5; + pub const MASTER: u8 = 1<<4; + /// Clock polarity. + pub const CPOL: u8 = 1<<3; + /// Clock phase. + pub const CPHA: u8 = 1<<2; + /// Clock rate select 1. + pub const SPR1: u8 = 1<<1; + /// Clock rate select 2. + pub const SPR0: u8 = 1<<0; +} + +/// Constants for the status register. +#[allow(dead_code)] +mod spsr { + /// SPI interrupt flag. + pub const SPIF: u8 = 1<<7; + /// Write collision flag. + pub const WCOL: u8 = 1<<6; + /// SPI double speed mode. + pub const SPI2X: u8 = 1<<0; +} +