diff --git a/firmware/src/i2c.rs b/firmware/src/i2c.rs index c8dccfa..8723560 100644 --- a/firmware/src/i2c.rs +++ b/firmware/src/i2c.rs @@ -7,12 +7,10 @@ // Using the blocking API because the peripheral requires fairly tight timing to operate // correctly, and I don't feel like writing the gateware to resolve that. -use embedded_hal::blocking::i2c::{Write, Read, SevenBitAddress, Transactional, Operation}; +use embedded_hal::blocking::i2c::{Write, Read, SevenBitAddress}; use crate::{read_reg, write_reg}; use core::arch::asm; -// TODO I think there may be bus address semantics I'm not 100% on. -// There's a possiblity these addresses are wrong, and they need to be 4 bytes each const CR: u32 = 0; const SR: u32 = 1; const DWR: u32 = 2; @@ -30,18 +28,24 @@ pub struct AmlibI2c { base_addr: u32, } -impl AmlibI2c { +use core::fmt::Write as _; + +impl AmlibI2c{ pub fn new(base_addr: u32) -> Self { AmlibI2c { base_addr } } - fn wait_while_busy(&self) { + fn wait_while_busy(&mut self) { unsafe { - while read_reg::(self.base_addr + SR) & 1 != 0 { + while read_reg::(self.base_addr + SR) & 1 != 0 { asm!("nop"); } } } + + fn is_nack(&self) -> bool { + unsafe { read_reg::(self.base_addr + SR) & 0x02 != 0 } + } } impl Write for AmlibI2c { @@ -50,34 +54,40 @@ impl Write for AmlibI2c { fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> { unsafe { - if (read_reg::(self.base_addr + SR) & 1) != 0 { + if (read_reg::(self.base_addr + SR) & 1) != 0 { return Err(Error::Busy); } // START - write_reg(self.base_addr + CR, 0x01u32); - // Pre-load data w/ address (R/~W = 0) - write_reg(self.base_addr + DWR, (address << 1) as u32); + write_reg(self.base_addr + CR, 0x01u8); self.wait_while_busy(); - // Send address byte - write_reg(self.base_addr + CR, 0x04u32); - if read_reg::(self.base_addr + SR) & 0x02 != 0 { + // Send address byte (R/~W = 0) + write_reg(self.base_addr + DWR, address << 1); + write_reg(self.base_addr + CR, 0x04u8); + self.wait_while_busy(); + + // Check NACK + if self.is_nack() { return Err(Error::Nack); } + // Write data for byte in bytes { // Write byte - write_reg(self.base_addr + DWR, *byte as u32); + write_reg(self.base_addr + DWR, *byte); + // Send byte + write_reg(self.base_addr + CR, 0x04u8); self.wait_while_busy(); - // Send byte once done sending the last byte - write_reg(self.base_addr + CR, 0x04u32); + + if self.is_nack() { + return Err(Error::Nack); + } } - self.wait_while_busy(); // STOP - write_reg(self.base_addr + CR, 0x02u32); + write_reg(self.base_addr + CR, 0x02u8); self.wait_while_busy(); Ok(()) @@ -90,15 +100,46 @@ impl Read for AmlibI2c { type Error = Error; fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> { - Ok(()) + unsafe { + if (read_reg::(self.base_addr + SR) & 1) != 0 { + return Err(Error::Busy); + } + + // START + write_reg(self.base_addr + CR, 0x01u8); + + self.wait_while_busy(); + + // Send address byte (R/~W = 1) + write_reg(self.base_addr + DWR, (address << 1) + 1); + write_reg(self.base_addr + CR, 0x04u8); + self.wait_while_busy(); + if self.is_nack() { + return Err(Error::Nack); + } + + for byte in buffer { + // Start reading in a byte + write_reg(self.base_addr + CR, 0x08u8); + self.wait_while_busy(); + // Value is available once busy is clear + *byte = read_reg::(self.base_addr + DRR); + } + + // STOP + write_reg(self.base_addr + CR, 0x02u8); + + Ok(()) + } } } -impl Transactional for AmlibI2c { - type Error = Error; - - fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) - -> Result<(), Self::Error> { - Ok(()) - } -} +// I don't need this for MCP4726 +//impl Transactional for AmlibI2c { +// type Error = Error; +// +// fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) +// -> Result<(), Self::Error> { +// Ok(()) +// } +//} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 4ae1a0c..72cb53c 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -3,15 +3,16 @@ extern crate panic_halt; -use core::{arch::asm, ptr::{write, read}}; +use core::{arch::asm, ptr::{write_volatile, read_volatile}}; use core::fmt::Write; -use embedded_hal::prelude::_embedded_hal_blocking_i2c_Write; +use embedded_hal::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_Read}; use riscv_rt::entry; mod eth; mod i2c; mod uart; +mod mcp4726; // use `main` as the entry point of this application // `main` is not allowed to return @@ -28,15 +29,29 @@ fn main() -> ! { //}; let blink_period = 10_000_000u32; - //let mut i2c = i2c::AmlibI2c::new(0x0200_0000); - //let data = [0u8, 2u8]; - //i2c.write(0xAA, &data).unwrap(); - let mut uart = uart::AmlibUart::new(0x0200_0040); + // Configure DAC + let mut i2c = i2c::AmlibI2c::new(0x0200_0000); + //let mut buf = [0u8; 1]; + //i2c.read(0b110_0011, &mut buf); + + + let mut dac = mcp4726::MCP4726::new(3); + writeln!(uart, "Reading DAC status"); + dac.read_status(&mut i2c).unwrap(); + writeln!(uart, "Configuring DAC"); + dac.write_config(&mut i2c, mcp4726::Config { + vref_source: mcp4726::VRef::UnbufferedVRef, + operation: mcp4726::PowerDown::NormalOperation, + use_2x_gain: false, + }).unwrap(); + writeln!(uart, "Setting DAC"); + dac.write_dac(&mut i2c, 0x0800).unwrap(); + loop { //eth::tranmsit(); - uart.write_str("Hello world!\r\n"); + //writeln!(uart, "Hello world!"); write_led(0); busy_wait(blink_period); write_led(1); @@ -57,9 +72,9 @@ fn write_led(val: u32) { } unsafe fn write_reg(addr: u32, value: T) { - write(addr as *mut T, value); + write_volatile(addr as *mut T, value); } unsafe fn read_reg(addr: u32) -> T { - return read(addr as *mut T); + return read_volatile(addr as *mut T); } diff --git a/firmware/src/mcp4726.rs b/firmware/src/mcp4726.rs new file mode 100644 index 0000000..f52e09b --- /dev/null +++ b/firmware/src/mcp4726.rs @@ -0,0 +1,133 @@ +//! Blocking driver for MCP2746 + +use embedded_hal::blocking::i2c::{Write, Read}; + +const COMMAND_WRITE_VOLATILE_DAC: u8 = 0x00; +const COMMAND_WRITE_VOLATILE_MEM: u8 = 0x40; +const COMMAND_WRITE_ALL_MEM: u8 = 0x60; +const COMMAND_WRITE_VOLATILE_CONFIG: u8 = 0x80; + +const BASE_ADDRESS: u8 = 0b110_0000; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum VRef { + UnbufferedVDD, + UnbufferedVRef, + BufferedVRef, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PowerDown { + NormalOperation, + PoweredDownVout1k, + PoweredDownVout100k, + PoweredDownVout500k, +} + +/// Configuration to apply +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Config { + pub vref_source: VRef, + /// Set device operation + pub operation: PowerDown, + /// True to set gain 2x, false for 1x + pub use_2x_gain: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Status { + /// Is the device ready to use? Only false in an EEPROM programming cycle + pub ready: bool, + + /// Power-on reset status indicator. Low if VDD is below the minimum operational + /// voltage. + pub device_powered: bool, +} + +pub struct MCP4726 { + address: u8, + power_status: PowerDown, +} + +impl MCP4726 { + pub fn new(sub_address: u8) -> Self { + Self { + address: BASE_ADDRESS + (sub_address & 0x07), + // TODO maybe come up with better semantics for setting this on init + power_status: PowerDown::NormalOperation, + } + } + + pub fn read_status(&self, i2c: &mut dyn Read) -> Result { + let mut buf = [0u8; 1]; + + self.read_all_mem(i2c, &mut buf)?; + + Ok(Status { + ready: buf[0] & 0x80 != 0, + device_powered: buf[0] & 0x40 != 0, + }) + } + + pub fn write_config(&mut self, i2c: &mut dyn Write, config: Config) -> Result<(), Error> { + let mut buf = [0u8; 1]; + buf[0] |= COMMAND_WRITE_VOLATILE_CONFIG; + buf[0] |= MCP4726::vref_config(config.vref_source) << 3; + buf[0] |= MCP4726::power_config(config.operation) << 1; + if config.use_2x_gain { + buf[0] |= 1; + } + + // Make sure power config is set properly + self.power_status = config.operation; + + i2c.write(self.address, &buf)?; + Ok(()) + } + + pub fn write_dac(&self, i2c: &mut dyn Write, dac: u16) -> Result<(), Error> { + let mut buf = [0u8; 2]; + buf[0] |= COMMAND_WRITE_VOLATILE_DAC; + // Note this "overlaps" the command bits, the lowest command bit is not used for this one + buf[0] |= MCP4726::power_config(self.power_status) << 4; + + buf[0] |= (dac >> 8) as u8 & 0x0F; + buf[1] = (dac & 0xFF) as u8; + + i2c.write(self.address, &buf)?; + Ok(()) + } + + pub fn write_nonvolatile(&mut self, i2c: &mut dyn Write, config: Config, dac: u16) -> Result<(), Error> { + todo!() + } + + /// Reads all memory, up to 6 bytes into the out buffer. + fn read_all_mem(&self, i2c: &mut dyn Read, out: &mut [u8]) -> Result { + let buf = if out.len() <= 6 { + out + } else { + &mut out[0..6] + }; + + i2c.read(self.address, buf)?; + Ok(buf.len() as u8) + } + + fn vref_config(vref: VRef) -> u8 { + match vref { + VRef::UnbufferedVDD => 0b00, + VRef::UnbufferedVRef => 0b10, + VRef::BufferedVRef => 0b11, + } + } + + fn power_config(pd: PowerDown) -> u8 { + match pd { + PowerDown::NormalOperation => 0b00, + PowerDown::PoweredDownVout1k => 0b01, + PowerDown::PoweredDownVout100k => 0b10, + PowerDown::PoweredDownVout500k => 0b11, + } + } +} diff --git a/firmware/src/uart.rs b/firmware/src/uart.rs index 89e6cf1..ba16c8c 100644 --- a/firmware/src/uart.rs +++ b/firmware/src/uart.rs @@ -10,10 +10,10 @@ const REG_DIVISOR_OFFSET: u32 = 0; const REG_SR_OFFSET: u32 = 2; const REG_DR_OFFSET: u32 = 3; -pub const FLAG_SR_TX_FULL: u8 = (1 << 0); -pub const FLAG_SR_TX_EMPTY: u8 = (1 << 1); -pub const FLAG_SR_RX_FULL: u8 = (1 << 2); -pub const FLAG_SR_RX_EMPTY: u8 = (1 << 3); +const FLAG_SR_TX_FULL: u8 = 1 << 0; +const FLAG_SR_TX_EMPTY: u8 = 1 << 1; +const FLAG_SR_RX_FULL: u8 = 1 << 2; +const FLAG_SR_RX_EMPTY: u8 = 1 << 3; pub enum Error { TxFull,