firmware: i2c work, not finished
This commit is contained in:
parent
60e468340c
commit
b7b556f6d2
@ -7,12 +7,10 @@
|
|||||||
|
|
||||||
// Using the blocking API because the peripheral requires fairly tight timing to operate
|
// 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.
|
// 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 crate::{read_reg, write_reg};
|
||||||
use core::arch::asm;
|
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 CR: u32 = 0;
|
||||||
const SR: u32 = 1;
|
const SR: u32 = 1;
|
||||||
const DWR: u32 = 2;
|
const DWR: u32 = 2;
|
||||||
@ -30,18 +28,24 @@ pub struct AmlibI2c {
|
|||||||
base_addr: u32,
|
base_addr: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AmlibI2c {
|
use core::fmt::Write as _;
|
||||||
|
|
||||||
|
impl AmlibI2c{
|
||||||
pub fn new(base_addr: u32) -> Self {
|
pub fn new(base_addr: u32) -> Self {
|
||||||
AmlibI2c { base_addr }
|
AmlibI2c { base_addr }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_while_busy(&self) {
|
fn wait_while_busy(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
while read_reg::<u32>(self.base_addr + SR) & 1 != 0 {
|
while read_reg::<u8>(self.base_addr + SR) & 1 != 0 {
|
||||||
asm!("nop");
|
asm!("nop");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_nack(&self) -> bool {
|
||||||
|
unsafe { read_reg::<u8>(self.base_addr + SR) & 0x02 != 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write<SevenBitAddress> for AmlibI2c {
|
impl Write<SevenBitAddress> for AmlibI2c {
|
||||||
@ -50,34 +54,40 @@ impl Write<SevenBitAddress> for AmlibI2c {
|
|||||||
|
|
||||||
fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> {
|
fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if (read_reg::<u32>(self.base_addr + SR) & 1) != 0 {
|
if (read_reg::<u8>(self.base_addr + SR) & 1) != 0 {
|
||||||
return Err(Error::Busy);
|
return Err(Error::Busy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// START
|
// START
|
||||||
write_reg(self.base_addr + CR, 0x01u32);
|
write_reg(self.base_addr + CR, 0x01u8);
|
||||||
// Pre-load data w/ address (R/~W = 0)
|
|
||||||
write_reg(self.base_addr + DWR, (address << 1) as u32);
|
|
||||||
|
|
||||||
self.wait_while_busy();
|
self.wait_while_busy();
|
||||||
|
|
||||||
// Send address byte
|
// Send address byte (R/~W = 0)
|
||||||
write_reg(self.base_addr + CR, 0x04u32);
|
write_reg(self.base_addr + DWR, address << 1);
|
||||||
if read_reg::<u32>(self.base_addr + SR) & 0x02 != 0 {
|
write_reg(self.base_addr + CR, 0x04u8);
|
||||||
|
self.wait_while_busy();
|
||||||
|
|
||||||
|
// Check NACK
|
||||||
|
if self.is_nack() {
|
||||||
return Err(Error::Nack);
|
return Err(Error::Nack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write data
|
||||||
for byte in bytes {
|
for byte in bytes {
|
||||||
// Write byte
|
// 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();
|
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
|
// STOP
|
||||||
write_reg(self.base_addr + CR, 0x02u32);
|
write_reg(self.base_addr + CR, 0x02u8);
|
||||||
|
|
||||||
self.wait_while_busy();
|
self.wait_while_busy();
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -90,15 +100,46 @@ impl Read<SevenBitAddress> for AmlibI2c {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
Ok(())
|
unsafe {
|
||||||
|
if (read_reg::<u8>(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::<u8>(self.base_addr + DRR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STOP
|
||||||
|
write_reg(self.base_addr + CR, 0x02u8);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transactional for AmlibI2c {
|
// I don't need this for MCP4726
|
||||||
type Error = Error;
|
//impl Transactional for AmlibI2c {
|
||||||
|
// type Error = Error;
|
||||||
fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>])
|
//
|
||||||
-> Result<(), Self::Error> {
|
// fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>])
|
||||||
Ok(())
|
// -> Result<(), Self::Error> {
|
||||||
}
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
//}
|
||||||
|
@ -3,15 +3,16 @@
|
|||||||
|
|
||||||
extern crate panic_halt;
|
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 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;
|
use riscv_rt::entry;
|
||||||
|
|
||||||
mod eth;
|
mod eth;
|
||||||
mod i2c;
|
mod i2c;
|
||||||
mod uart;
|
mod uart;
|
||||||
|
mod mcp4726;
|
||||||
|
|
||||||
// use `main` as the entry point of this application
|
// use `main` as the entry point of this application
|
||||||
// `main` is not allowed to return
|
// `main` is not allowed to return
|
||||||
@ -28,15 +29,29 @@ fn main() -> ! {
|
|||||||
//};
|
//};
|
||||||
let blink_period = 10_000_000u32;
|
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);
|
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 {
|
loop {
|
||||||
//eth::tranmsit();
|
//eth::tranmsit();
|
||||||
uart.write_str("Hello world!\r\n");
|
//writeln!(uart, "Hello world!");
|
||||||
write_led(0);
|
write_led(0);
|
||||||
busy_wait(blink_period);
|
busy_wait(blink_period);
|
||||||
write_led(1);
|
write_led(1);
|
||||||
@ -57,9 +72,9 @@ fn write_led(val: u32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn write_reg<T>(addr: u32, value: T) {
|
unsafe fn write_reg<T>(addr: u32, value: T) {
|
||||||
write(addr as *mut T, value);
|
write_volatile(addr as *mut T, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn read_reg<T>(addr: u32) -> T {
|
unsafe fn read_reg<T>(addr: u32) -> T {
|
||||||
return read(addr as *mut T);
|
return read_volatile(addr as *mut T);
|
||||||
}
|
}
|
||||||
|
133
firmware/src/mcp4726.rs
Normal file
133
firmware/src/mcp4726.rs
Normal file
@ -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<Error>(&self, i2c: &mut dyn Read<Error = Error>) -> Result<Status, Error> {
|
||||||
|
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<Error>(&mut self, i2c: &mut dyn Write<Error = Error>, 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<Error>(&self, i2c: &mut dyn Write<Error = Error>, 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<Error>(&mut self, i2c: &mut dyn Write<Error = Error>, config: Config, dac: u16) -> Result<(), Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all memory, up to 6 bytes into the out buffer.
|
||||||
|
fn read_all_mem<Error>(&self, i2c: &mut dyn Read<Error = Error>, out: &mut [u8]) -> Result<u8, Error> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,10 +10,10 @@ const REG_DIVISOR_OFFSET: u32 = 0;
|
|||||||
const REG_SR_OFFSET: u32 = 2;
|
const REG_SR_OFFSET: u32 = 2;
|
||||||
const REG_DR_OFFSET: u32 = 3;
|
const REG_DR_OFFSET: u32 = 3;
|
||||||
|
|
||||||
pub const FLAG_SR_TX_FULL: u8 = (1 << 0);
|
const FLAG_SR_TX_FULL: u8 = 1 << 0;
|
||||||
pub const FLAG_SR_TX_EMPTY: u8 = (1 << 1);
|
const FLAG_SR_TX_EMPTY: u8 = 1 << 1;
|
||||||
pub const FLAG_SR_RX_FULL: u8 = (1 << 2);
|
const FLAG_SR_RX_FULL: u8 = 1 << 2;
|
||||||
pub const FLAG_SR_RX_EMPTY: u8 = (1 << 3);
|
const FLAG_SR_RX_EMPTY: u8 = 1 << 3;
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
TxFull,
|
TxFull,
|
||||||
|
Loading…
Reference in New Issue
Block a user