fw: start debug i2c
This commit is contained in:
parent
c1c0122684
commit
349fe96b0d
@ -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,
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
@ -71,7 +73,7 @@ pub fn busy_wait_ms(ms: u32) {
|
||||
|
||||
pub fn busy_wait_us(us: u64) {
|
||||
let start = micros();
|
||||
while micros() - start < us {
|
||||
while micros() - start < us {
|
||||
unsafe {
|
||||
asm!("nop");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user