fw: start debug i2c

This commit is contained in:
David Lenfesty 2023-06-17 19:17:55 +00:00
parent c1c0122684
commit 349fe96b0d
6 changed files with 290 additions and 116 deletions

View File

@ -36,7 +36,7 @@ const NUM_TX_SLOTS: u32 = 2;
const MTU: usize = 1530;
const SLOT_LEN: u32 = 2048;
use crate::{busy_wait, read_reg, write_reg};
use crate::{read_reg, timer::busy_wait_ms, write_reg};
pub struct LiteEthDevice {
csr_addr: u32,

View File

@ -2,146 +2,289 @@
//!
//! See `gateware/i2c.py` for register information
use crate::timer::busy_wait_us;
use crate::{read_reg, write_reg};
use core::arch::asm;
/// TODO repr(C) a register bank, and add instances
// 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::{Read, SevenBitAddress, Write};
use embedded_hal::blocking::i2c::{Operation, Read, SevenBitAddress, Transactional, Write};
const CR: u32 = 0;
const SR: u32 = 1;
const DWR: u32 = 2;
const DRR: u32 = 3;
pub const BASE: u32 = 0xF000_1800;
const REG_W: u32 = 0x00;
const FIELD_W_SCL: u32 = 0;
const FIELD_W_OE: u32 = 1;
const FIELD_W_SDA: u32 = 2;
const REG_R: u32 = 0x04;
const FIELD_R_SDA: u32 = 0;
const WAIT_PERIOD_US: u64 = (1_000_000 / 100_000) / 2;
#[derive(Clone, Copy, Debug)]
pub enum Error {
/// Device is busy for some reason
Busy,
/// I2C bus returned a NACK
Nack,
}
pub struct AmlibI2c {
pub struct LitexI2c {
base_addr: u32,
}
use core::fmt::Write as _;
impl AmlibI2c {
impl LitexI2c {
pub fn new(base_addr: u32) -> Self {
AmlibI2c { base_addr }
LitexI2c { base_addr }
}
fn wait_while_busy(&mut self) {
fn set_scl(&mut self, val: u32) {
unsafe {
while read_reg::<u8>(self.base_addr + SR) & 1 != 0 {
asm!("nop");
}
write_reg(self.base_addr + REG_W, (val & 0x01) << FIELD_W_SCL);
}
}
fn is_nack(&self) -> bool {
unsafe { read_reg::<u8>(self.base_addr + SR) & 0x02 == 0 }
fn set_sda(&mut self, val: u32) {
unsafe {
write_reg(self.base_addr + REG_W, (val & 0x01) << FIELD_W_SDA);
}
}
fn set_oe(&mut self, val: u32) {
unsafe {
write_reg(self.base_addr + REG_W, (val & 0x01) << FIELD_W_OE);
}
}
fn get_sda(&mut self) -> u8 {
unsafe { read_reg::<u8>(self.base_addr + REG_R) & (1 << FIELD_R_SDA) }
}
/// Generate a START condition. The next operation should be a clock
/// transition to low.
///
/// Assumes SCL and SDA have been high due to bus inactivity
unsafe fn start(&mut self) {
self.set_oe(1);
self.set_scl(1);
self.set_sda(1);
busy_wait_us(WAIT_PERIOD_US);
self.set_sda(0);
busy_wait_us(WAIT_PERIOD_US);
}
/// Generate a repeated START condition.
///
/// Assumes it was called after an ACK bit
unsafe fn repeated_start(&mut self) {
self.set_scl(0);
self.set_sda(1);
self.set_oe(1);
busy_wait_us(WAIT_PERIOD_US);
self.set_scl(1);
busy_wait_us(WAIT_PERIOD_US);
// TODO does this third half-clock break timing?
self.set_sda(0);
busy_wait_us(WAIT_PERIOD_US);
}
/// Handles STOP conditions.
///
/// Assumes it was called after an ACK bit
unsafe fn stop(&mut self) {
self.set_scl(0);
self.set_sda(0);
self.set_oe(1);
busy_wait_us(WAIT_PERIOD_US);
self.set_scl(1);
busy_wait_us(WAIT_PERIOD_US);
// TODO does this third half-clock break timing?
//self.set_oe(0); // Disable and let pullup take bus high
self.set_sda(1);
busy_wait_us(WAIT_PERIOD_US);
}
unsafe fn write_byte(&mut self, byte: u8) {
self.set_oe(1); // Take SDA to write data out
for i in 0..8 {
// Write data out before we clock high
self.set_scl(0);
busy_wait_us(WAIT_PERIOD_US);
// Clock data in
self.set_scl(1);
self.set_sda(byte as u32 >> (7 - i));
busy_wait_us(WAIT_PERIOD_US)
}
}
unsafe fn read_byte(&mut self) -> u8 {
self.set_oe(0); // De-assert to read data
let mut out = 0;
for i in 0..8 {
self.set_scl(0);
busy_wait_us(WAIT_PERIOD_US);
self.set_scl(1);
busy_wait_us(WAIT_PERIOD_US);
// TODO sampling at the end of the clock may not work 100% of the time
// ideally should sample in the middle
out |= (self.get_sda() << 7 - i);
}
out
}
// Returns if there was an ACK
unsafe fn get_ack(&mut self) -> bool {
self.set_oe(0); // Release SDA to read ACK
self.set_sda(1);
self.set_scl(0);
busy_wait_us(WAIT_PERIOD_US);
self.set_scl(1);
busy_wait_us(WAIT_PERIOD_US);
self.get_sda() == 0
}
unsafe fn set_ack(&mut self, ack: bool) {
self.set_oe(1); // Take SDA
self.set_sda(!ack as u32);
self.set_scl(0);
busy_wait_us(WAIT_PERIOD_US);
self.set_scl(1);
busy_wait_us(WAIT_PERIOD_US);
}
}
impl Write<SevenBitAddress> for AmlibI2c {
impl Write<SevenBitAddress> for LitexI2c {
// TODO errors
type Error = Error;
fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> {
unsafe {
if (read_reg::<u8>(self.base_addr + SR) & 1) != 0 {
return Err(Error::Busy);
}
self.start();
// START
write_reg(self.base_addr + CR, 0x01u8);
self.wait_while_busy();
// 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() {
// Write address and write bit
self.write_byte((address << 1) | 0);
if !self.get_ack() {
self.stop();
return Err(Error::Nack);
}
// Write data
for byte in bytes {
// Write byte
write_reg(self.base_addr + DWR, *byte);
// Send byte
write_reg(self.base_addr + CR, 0x04u8);
self.wait_while_busy();
if self.is_nack() {
for b in bytes {
self.write_byte(*b);
if !self.get_ack() {
self.stop();
return Err(Error::Nack);
}
}
// STOP
write_reg(self.base_addr + CR, 0x02u8);
self.stop();
self.wait_while_busy();
Ok(())
}
}
}
impl Read<SevenBitAddress> for AmlibI2c {
impl Read<SevenBitAddress> for LitexI2c {
// TODO errors
type Error = Error;
fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> {
unsafe {
if (read_reg::<u8>(self.base_addr + SR) & 1) != 0 {
return Err(Error::Busy);
}
self.start();
// Set read ACK to 1. Should probably ideally be done in an init() function
// but it doesn't really matter
write_reg(self.base_addr + CR, 0x30u8);
// 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() {
self.write_byte((address << 1) | 1);
if !self.get_ack() {
self.stop();
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);
for b in buffer {
*b = self.read_byte();
self.set_ack(true);
}
// STOP
write_reg(self.base_addr + CR, 0x02u8);
self.stop();
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(())
// }
//}
impl Transactional for LitexI2c {
type Error = Error;
fn exec<'a>(
&mut self,
address: u8,
operations: &mut [Operation<'a>],
) -> Result<(), Self::Error> {
unsafe {
let mut last_operation_was_read: Option<bool> = None;
for op in operations {
let op_is_read = match op {
Operation::Write(_) => false,
Operation::Read(_) => true,
};
match last_operation_was_read {
None => {
// First operation; send regular start and address
self.start();
self.write_byte((address << 1) | op_is_read as u8);
if !self.get_ack() {
self.stop();
return Err(Error::Nack);
}
}
Some(last_op_was_read) => {
// Not the first, send repeated start
self.repeated_start();
if last_op_was_read != op_is_read {
// Operations don't match, so we need to send address byte
self.write_byte((address << 1) | op_is_read as u8);
if !self.get_ack() {
self.stop();
return Err(Error::Nack);
}
}
}
}
match op {
Operation::Read(buffer) => {
for b in buffer.iter_mut() {
*b = self.read_byte();
self.set_ack(true);
}
}
Operation::Write(buffer) => {
for b in buffer.iter() {
self.write_byte(*b);
if !self.get_ack() {
self.stop();
return Err(Error::Nack);
}
}
}
}
last_operation_was_read = Some(op_is_read);
}
self.stop();
}
Ok(())
}
}

View File

@ -24,7 +24,7 @@ unsafe impl defmt::Logger for DefmtLogger {
unsafe fn write(bytes: &[u8]) {
static mut UART: Option<LitexUart> = None;
if UART.is_none() {
UART = Some(LitexUart::new(0xf000_4000));
UART = Some(LitexUart::new(0xf000_4800));
}
let mut dev = UART.unwrap();

View File

@ -6,9 +6,7 @@
extern crate panic_halt;
use core::fmt::Write;
use core::{
ptr::{read_volatile, write_volatile},
};
use core::ptr::{read_volatile, write_volatile};
use embedded_hal::prelude::{_embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write};
use mcp4726::Status;
@ -30,9 +28,9 @@ mod i2c;
mod logging;
mod mcp4726;
mod proto;
mod uart;
mod sampler;
mod timer;
mod uart;
use timer::millis;
@ -115,6 +113,9 @@ fn main() -> ! {
let mut cmd = command_interface::CommandInterface::new();
let mut i2c = i2c::LitexI2c::new(i2c::BASE);
let mut dac = mcp4726::MCP4726::new(3);
loop {
let now = millis();
iface.poll(Instant::from_millis(now), &mut device, &mut socket_set);
@ -123,7 +124,7 @@ fn main() -> ! {
cmd.run(socket_set.get_mut(command_socket));
// TODO the need for the second check screams something is unsound somewhere
if now - last_blink > 1000 && now > last_blink {
if now - last_blink >= 1000 && now > last_blink {
last_blink = now;
toggle = !toggle;
write_led(if toggle { 1 } else { 0 });
@ -133,31 +134,59 @@ fn main() -> ! {
sock.listen(3000);
}
if toggle {
sampler::clear_buffers();
sampler::start_sampling();
defmt::debug!("heartbeat, time: {}", millis());
let buf = unsafe {sampler::get_sample_buffer(0) };
let status = sampler::read_status();
let raw_reg: u32 = unsafe { read_reg(0x8040_0004) };
defmt::debug!("Start: len: {}, complete: {}, running: {}, status: {}", buf.len(), status.capture_complete, status.sampling, raw_reg);
} else {
sampler::stop_sampling();
let buf = unsafe {sampler::get_sample_buffer(0) };
defmt::debug!("Stopped, len: {}", buf.len());
match dac.write_config(
&mut i2c,
mcp4726::Config {
vref_source: mcp4726::VRef::UnbufferedVRef,
operation: mcp4726::PowerDown::NormalOperation,
use_2x_gain: false,
},
) {
Ok(_) => defmt::debug!("Write config success"),
Err(_) => defmt::debug!("Write config failed"),
}
//match dac.read_status(&mut i2c) {
// Ok(status) => {
// defmt::debug!(
// "DAC status: ready: {}, powered: {}",
// status.ready,
// status.device_powered
// );
// if sock.can_send() {
// write!(sock, "DAC: ready: {}, powered: {}", status.ready, status.device_powered);
// }
// }
// Err(_) => defmt::debug!("Error reading from DAC"),
//};
//if toggle {
// sampler::clear_buffers();
// sampler::start_sampling();
// let buf = unsafe {sampler::get_sample_buffer(0) };
// let status = sampler::read_status();
// let raw_reg: u32 = unsafe { read_reg(0x8040_0004) };
// defmt::debug!("Start: len: {}, complete: {}, running: {}, status: {}", buf.len(), status.capture_complete, status.sampling, raw_reg);
//} else {
// sampler::stop_sampling();
// let buf = unsafe {sampler::get_sample_buffer(0) };
// defmt::debug!("Stopped, len: {}", buf.len());
//}
}
// TODO I think the timer might actually stop until the event is cleared? this may pose
// problems, might explain why moving this above the smoltcp stuff "broke" things
timer::poll();
}
}
fn write_led(val: u32) {
unsafe {
write_reg(0xf000_2000, val);
write_reg(0xf000_2800, val);
}
}

View File

@ -1,6 +1,6 @@
//! Driver for custom sampler peripheral
use crate::{write_reg, read_reg};
use crate::{read_reg, write_reg};
const BASE: u32 = 0x8040_0000;

View File

@ -3,15 +3,17 @@
use crate::{read_reg, write_reg};
use core::arch::asm;
const BASE: u32 = 0xf000_4000;
/// Initializes the timer to use for polling events as a wall clock.
///
/// Timer is running at system frequency (60MHz)
pub fn init() {
unsafe {
write_reg(0xf000_3808, 0u32); // Disable timer
write_reg(0xf000_3800, 0u32); // Set LOAD value
write_reg(0xf000_3804, 60_000_000u32); // Set RELOAD value
write_reg(0xf000_3808, 1u32); // Enable timer
write_reg(BASE + 0x08, 0u32); // Disable timer
write_reg(BASE + 0x00, 0u32); // Set LOAD value
write_reg(BASE + 0x04, 60_000_000u32); // Set RELOAD value
write_reg(BASE + 0x08, 1u32); // Enable timer
}
}
@ -21,13 +23,13 @@ static mut SECONDS: u32 = 0;
/// Processes potential timer events. Must be polled regularly
pub fn poll() {
unsafe {
if read_reg::<u32>(0xf000_3818) == 0 {
if read_reg::<u32>(BASE + 0x18) == 0 {
// No event yet, continue
return;
}
// Clear TIMER0 event status, and update time
write_reg(0xf000_3818, 1u32);
write_reg(BASE + 0x18, 1u32);
SECONDS += 1;
}
}
@ -37,9 +39,9 @@ pub fn millis() -> u32 {
riscv::interrupt::free(|| {
unsafe {
// Latch timer value
write_reg(0xf000_380c, 1u32);
write_reg(BASE + 0x0C, 1u32);
// Read timer value
let val: u32 = read_reg(0xf000_3810);
let val: u32 = read_reg(BASE + 0x10);
let val = 60_000_000 - val;
(SECONDS * 1000) + val / 60_000
}
@ -51,9 +53,9 @@ pub fn micros() -> u64 {
riscv::interrupt::free(|| {
unsafe {
// Latch timer value
write_reg(0xf000_380c, 1u32);
write_reg(BASE + 0x0C, 1u32);
// Read timer value
let val: u32 = read_reg(0xf000_3810);
let val: u32 = read_reg(BASE + 0x10);
let val = 60_000_000 - val;
(SECONDS as u64 * 1_000_000) + (val as u64 / 60)
}