rust: Generalize I2C peripheral

This commit is contained in:
Wladimir J. van der Laan 2019-05-18 19:07:17 +00:00
parent 13b91fe993
commit b14679e35a
5 changed files with 230 additions and 178 deletions

View File

@ -10,9 +10,9 @@ use k210_hal::stdout::Stdout;
use k210_shared::board::def::{io,DISP_WIDTH,DISP_HEIGHT,MSA300_SLV_ADDR,MSA300_ADDR_BITS,MSA300_CLK};
use k210_shared::board::lcd::{LCD,self};
use k210_shared::board::lcd_colors;
use k210_shared::board::msa300;
use k210_shared::board::msa300::Accelerometer;
use k210_shared::soc::fpioa;
use k210_shared::soc::i2c;
use k210_shared::soc::i2c::{I2C,I2CExt};
use k210_shared::soc::sleep::usleep;
use k210_shared::soc::spi::SPIExt;
use k210_shared::soc::sysctl;
@ -85,11 +85,12 @@ fn main() -> ! {
let mut image: ScreenImage = [0; DISP_WIDTH * DISP_HEIGHT / 2];
writeln!(stdout, "MSA300 init").unwrap();
i2c::init(MSA300_SLV_ADDR, MSA300_ADDR_BITS, MSA300_CLK);
msa300::init().unwrap();
let i2c = p.I2C0.constrain();
i2c.init(MSA300_SLV_ADDR, MSA300_ADDR_BITS, MSA300_CLK);
let acc = Accelerometer::init(i2c).unwrap();
loop {
let (x, y, z) = msa300::measure().unwrap();
let (x, y, z) = acc.measure().unwrap();
let mag = (x*x+y*y+z*z).sqrt();
// writeln!(stdout, "m/s^2 x={} y={} z={} (size={})", x, y, z, mag).unwrap();

View File

@ -12,7 +12,7 @@ use k210_shared::board::lcd::{LCD,self};
use k210_shared::board::lcd_colors;
use k210_shared::board::ns2009::TouchScreen;
use k210_shared::soc::fpioa;
use k210_shared::soc::i2c;
use k210_shared::soc::i2c::{I2C,I2CExt};
use k210_shared::soc::sleep::usleep;
use k210_shared::soc::spi::SPIExt;
use k210_shared::soc::sysctl;
@ -163,9 +163,10 @@ fn main() -> ! {
let mut image: ScreenImage = [0; DISP_WIDTH * DISP_HEIGHT / 2];
writeln!(stdout, "NS2009 init").unwrap();
i2c::init(NS2009_SLV_ADDR, NS2009_ADDR_BITS, NS2009_CLK);
let i2c = p.I2C0.constrain();
i2c.init(NS2009_SLV_ADDR, NS2009_ADDR_BITS, NS2009_CLK);
let mut filter = if let Some(filter) = TouchScreen::init(NS2009_CAL) {
let mut filter = if let Some(filter) = TouchScreen::init(i2c, NS2009_CAL) {
filter
} else {
writeln!(stdout, "NS2009 init failure").unwrap();

View File

@ -1,6 +1,6 @@
/** Support for MSA300 accelerometer */
/* MSA300 code based on 'accelerometer' demo by j.m.voogd@gmail.com */
use crate::soc::i2c;
use crate::soc::i2c::I2C;
/** MSA300 Registers */
enum reg {
@ -41,10 +41,14 @@ const DATARATE_1000_HZ: u8 = 0x0F;
/** Gravity constant (Earth surface) */
const GRAVITY: f32 = 9.80665;
pub struct Accelerometer<IF> {
i2c: IF,
}
/** Read a register of the MSA300 via I2C */
fn read_register(reg: reg) -> Result<u8, ()> {
fn read_register<IF: I2C>(i2c: &IF, reg: reg) -> Result<u8, ()> {
let mut reg_val = [0u8; 2];
if i2c::recv_data(&[reg as u8], &mut reg_val).is_ok() {
if i2c.recv_data(&[reg as u8], &mut reg_val).is_ok() {
Ok(reg_val[0])
} else {
Err(())
@ -52,63 +56,67 @@ fn read_register(reg: reg) -> Result<u8, ()> {
}
/** Set a register of the MSA300 via I2C */
fn set_register(reg: reg, val: u8) -> Result<(), ()> {
i2c::send_data(&[reg as u8, val])
fn set_register<IF: I2C>(i2c: &IF, reg: reg, val: u8) -> Result<(), ()> {
i2c.send_data(&[reg as u8, val])
}
/** Initialize chip */
pub fn init() -> Result<(), ()> {
let correct_id = 0x13;
if let Ok(part_id) = read_register(reg::PARTID) {
if part_id != correct_id {
//writeln!(stdout, "MSA device not found (ID should be {:02x} but is {:02x})", correct_id, part_id).unwrap();
impl<IF: I2C> Accelerometer<IF> {
/** Initialize chip */
pub fn init(i2c: IF) -> Result<Self, ()> {
let correct_id = 0x13;
if let Ok(part_id) = read_register(&i2c, reg::PARTID) {
if part_id != correct_id {
//writeln!(stdout, "MSA device not found (ID should be {:02x} but is {:02x})", correct_id, part_id).unwrap();
return Err(());
}
} else {
//writeln!(stdout, "Could not read MSA device ID").unwrap();
return Err(());
}
} else {
//writeln!(stdout, "Could not read MSA device ID").unwrap();
return Err(());
// set (and check) the power mode to 0x1A: normal power mode + 500Hz bandwidth
let desired_mode = 0x1A;
if set_register(&i2c, reg::PWR_MODE_BW, desired_mode).is_err() {
//writeln!(stdout, "Problem: setting power mode went wrong").unwrap();
return Err(());
}
let pwr_mode = read_register(&i2c, reg::PWR_MODE_BW).unwrap();
if pwr_mode != desired_mode {
//writeln!(stdout, "Power mode should be {:02x} but is {:02x}", desired_mode, pwr_mode).unwrap();
return Err(());
}
// Enable x, y and z + set output data rate to 500 Hz
set_register(&i2c, reg::ODR, 0x09)?;
// resolution 14 bits (MSB=8 bits, LSB=6 bits + 2 zero bits), range +- 4G
set_register(&i2c, reg::RES_RANGE, 0x01)?;
// no interrupts
set_register(&i2c, reg::INT_SET_0, 0x00)?;
set_register(&i2c, reg::INT_SET_1, 0x00)?;
// reset all latched interrupts, temporary latched 250ms
set_register(&i2c, reg::INT_LATCH, 0x81)?;
Ok(Self {
i2c: i2c,
})
}
// set (and check) the power mode to 0x1A: normal power mode + 500Hz bandwidth
let desired_mode = 0x1A;
if set_register(reg::PWR_MODE_BW, desired_mode).is_err() {
//writeln!(stdout, "Problem: setting power mode went wrong").unwrap();
return Err(());
/** Return measurement in m/s^2 for x, y, z */
pub fn measure(&self) -> Result<(f32, f32, f32), ()> {
// for x, y, and z: read the LSB (6 bits + 2 zero bits) and the MSB (8 bits)
// shift the MSB left 8 bits, add the LSB, and multiply this with a calibration constant
let tx = (read_register(&self.i2c, reg::ACC_X_LSB)? as i32) +
((read_register(&self.i2c, reg::ACC_X_MSB)? as i8) as i32)*256;
let x = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (tx as f32);
let ty = (read_register(&self.i2c, reg::ACC_Y_LSB)? as i32) +
((read_register(&self.i2c, reg::ACC_Y_MSB)? as i8) as i32)*256;
let y = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (ty as f32);
// looks like Z has a large bias - don't know if this is general or just on my board
let tz = (read_register(&self.i2c, reg::ACC_Z_LSB)? as i32) +
((read_register(&self.i2c, reg::ACC_Z_MSB)? as i8) as i32)*256 + 3386;
let z = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (tz as f32);
Ok((x, y, z))
}
let pwr_mode = read_register(reg::PWR_MODE_BW).unwrap();
if pwr_mode != desired_mode {
//writeln!(stdout, "Power mode should be {:02x} but is {:02x}", desired_mode, pwr_mode).unwrap();
return Err(());
}
// Enable x, y and z + set output data rate to 500 Hz
set_register(reg::ODR, 0x09)?;
// resolution 14 bits (MSB=8 bits, LSB=6 bits + 2 zero bits), range +- 4G
set_register(reg::RES_RANGE, 0x01)?;
// no interrupts
set_register(reg::INT_SET_0, 0x00)?;
set_register(reg::INT_SET_1, 0x00)?;
// reset all latched interrupts, temporary latched 250ms
set_register(reg::INT_LATCH, 0x81)?;
Ok(())
}
/** Return measurement in m/s^2 for x, y, z */
pub fn measure() -> Result<(f32, f32, f32), ()> {
// for x, y, and z: read the LSB (6 bits + 2 zero bits) and the MSB (8 bits)
// shift the MSB left 8 bits, add the LSB, and multiply this with a calibration constant
let tx = (read_register(reg::ACC_X_LSB)? as i32) +
((read_register(reg::ACC_X_MSB)? as i8) as i32)*256;
let x = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (tx as f32);
let ty = (read_register(reg::ACC_Y_LSB)? as i32) +
((read_register(reg::ACC_Y_MSB)? as i8) as i32)*256;
let y = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (ty as f32);
// looks like Z has a large bias - don't know if this is general or just on my board
let tz = (read_register(reg::ACC_Z_LSB)? as i32) +
((read_register(reg::ACC_Z_MSB)? as i8) as i32)*256 + 3386;
let z = 0.25f32 * MG2G_MULTIPLIER_4_G * GRAVITY * (tz as f32);
Ok((x, y, z))
}

View File

@ -1,6 +1,6 @@
use core::result::Result;
use crate::soc::i2c;
use crate::soc::i2c::I2C;
use crate::util::filters;
/** Touch event will trigger at a z1 value higher than this (lower values regarded as noise)
@ -24,10 +24,10 @@ pub enum command {
}
/** Read a 12-bit value. */
pub fn read(cmd: command) -> Result<u16, ()>
pub fn read<IF: I2C>(i2c: &IF, cmd: command) -> Result<u16, ()>
{
let mut buf = [0u8; 2];
if i2c::recv_data(&[cmd as u8], &mut buf).is_ok() {
if i2c.recv_data(&[cmd as u8], &mut buf).is_ok() {
Ok(((buf[0] as u16) << 4) | ((buf[1] as u16) >> 4))
} else {
Err(())
@ -92,19 +92,21 @@ pub struct Event {
}
/** High-level touch screen abstraction */
pub struct TouchScreen {
pub struct TouchScreen<IF> {
i2c: IF,
filter: TSFilter,
press: bool,
x: i32,
y: i32,
}
impl TouchScreen {
impl<IF: I2C> TouchScreen<IF> {
/* input: calibration matrix */
pub fn init(cal: [i32; 7]) -> Option<Self> {
pub fn init(i2c: IF, cal: [i32; 7]) -> Option<Self> {
// Do a test read
if let Ok(_) = read(command::LOW_POWER_READ_Z1) {
if let Ok(_) = read(&i2c, command::LOW_POWER_READ_Z1) {
Some(Self {
i2c: i2c,
filter: TSFilter::new(cal),
press: false,
x: 0,
@ -118,9 +120,9 @@ impl TouchScreen {
/** Poll for touch screen event, return the current event (Begin, Move, End) or None */
pub fn poll(&mut self) -> Option<Event> {
let mut ev: Option<Event> = None;
if let Ok(z1) = read(command::LOW_POWER_READ_Z1) {
if let Ok(z1) = read(&self.i2c, command::LOW_POWER_READ_Z1) {
if z1 > TOUCH_THR_MIN && z1 < TOUCH_THR_MAX {
if let (Ok(x), Ok(y)) = (read(command::LOW_POWER_READ_X), read(command::LOW_POWER_READ_Y)) {
if let (Ok(x), Ok(y)) = (read(&self.i2c, command::LOW_POWER_READ_X), read(&self.i2c, command::LOW_POWER_READ_Y)) {
let (x, y) = self.filter.update(x, y);
if !self.press
{

View File

@ -1,128 +1,168 @@
use core::cmp;
use core::convert::TryInto;
use core::ops::Deref;
use core::result::Result;
use k210_hal::pac;
use k210_hal::pac::{I2C0,I2C1,I2C2,i2c0};
use crate::soc::sysctl;
// TODO parametrize type, other I2C than I2C0
fn clk_init()
{
sysctl::clock_enable(sysctl::clock::I2C0);
sysctl::clock_set_threshold(sysctl::threshold::I2C0, 3);
/// Extension trait that constrains I2C peripherals
pub trait I2CExt: Sized {
/// Constrains I2C peripheral so it plays nicely with the other abstractions
fn constrain(self) -> I2CImpl<Self>;
}
pub fn init(slave_address: u16, address_width: u32, i2c_clk: u32)
{
assert!(address_width == 7 || address_width == 10);
/// Trait for generalizing over I2C0 and I2C1 (I2C2 is slave-only and I2C3 is !!!special!!!)
pub trait I2C012: Deref<Target = i2c0::RegisterBlock> {
#[doc(hidden)]
const CLK: sysctl::clock;
#[doc(hidden)]
const DIV: sysctl::threshold;
}
clk_init();
impl I2C012 for I2C0 {
const CLK: sysctl::clock = sysctl::clock::I2C0;
const DIV: sysctl::threshold = sysctl::threshold::I2C0;
}
impl I2C012 for I2C1 {
const CLK: sysctl::clock = sysctl::clock::I2C1;
const DIV: sysctl::threshold = sysctl::threshold::I2C1;
}
impl I2C012 for I2C2 {
const CLK: sysctl::clock = sysctl::clock::I2C2;
const DIV: sysctl::threshold = sysctl::threshold::I2C2;
}
let v_i2c_freq = sysctl::clock_get_freq(sysctl::clock::I2C0);
let v_period_clk_cnt = v_i2c_freq / i2c_clk / 2;
let v_period_clk_cnt: u16 = v_period_clk_cnt.try_into().unwrap();
let v_period_clk_cnt = cmp::max(v_period_clk_cnt, 1);
unsafe {
let ptr = pac::I2C0::ptr();
(*ptr).enable.write(|w| w.bits(0));
(*ptr).con.write(|w| w.master_mode().bit(true)
.slave_disable().bit(true)
.restart_en().bit(true)
.addr_slave_width().bit(address_width == 10) // TODO variant
.speed().bits(1)); // TODO variant
(*ptr).ss_scl_hcnt.write(|w| w.count().bits(v_period_clk_cnt));
(*ptr).ss_scl_lcnt.write(|w| w.count().bits(v_period_clk_cnt));
(*ptr).tar.write(|w| w.address().bits(slave_address));
(*ptr).intr_mask.write(|w| w.bits(0));
(*ptr).dma_cr.write(|w| w.bits(0x3));
(*ptr).dma_rdlr.write(|w| w.bits(0));
(*ptr).dma_tdlr.write(|w| w.bits(4));
(*ptr).enable.write(|w| w.enable().bit(true));
impl<I2C: I2C012> I2CExt for I2C {
fn constrain(self) -> I2CImpl<I2C> {
I2CImpl::<I2C>::new(self)
}
}
pub fn recv_data(send_buf: &[u8], receive_buf: &mut [u8]) -> Result<(), ()>
{
unsafe {
let ptr = pac::I2C0::ptr();
let mut txi = 0;
let mut tx_left = send_buf.len();
while tx_left != 0 {
let fifo_len = 8 - ((*ptr).txflr.read().bits() as usize);
let fifo_len = cmp::min(tx_left, fifo_len);
for _ in 0..fifo_len {
(*ptr).data_cmd.write(|w| w.data().bits(send_buf[txi]));
txi += 1;
}
if (*ptr).tx_abrt_source.read().bits() != 0 {
return Err(());
}
tx_left -= fifo_len;
}
let mut cmd_count = receive_buf.len();
let mut rx_left = receive_buf.len();
let mut rxi = 0;
while cmd_count != 0 || rx_left != 0 {
/* XXX this is a kind of strange construction, sanity check */
let fifo_len = (*ptr).rxflr.read().bits() as usize;
let fifo_len = cmp::min(rx_left, fifo_len);
for _ in 0..fifo_len {
receive_buf[rxi] = (*ptr).data_cmd.read().data().bits();
rxi += 1;
}
rx_left -= fifo_len;
/* send 0x100 for every byte that we want to receive */
let fifo_len = 8 - (*ptr).txflr.read().bits() as usize;
let fifo_len = cmp::min(cmd_count, fifo_len);
for _ in 0..fifo_len {
(*ptr).data_cmd.write(|w| w.cmd().bit(true));
}
if (*ptr).tx_abrt_source.read().bits() != 0 {
return Err(());
}
cmd_count -= fifo_len;
}
}
Ok(())
pub struct I2CImpl<IF> {
i2c: IF,
}
pub fn send_data(send_buf: &[u8]) -> Result<(), ()>
{
unsafe {
let ptr = pac::I2C0::ptr();
let mut txi = 0;
let mut tx_left = send_buf.len();
pub trait I2C {
fn init(&self, slave_address: u16, address_width: u32, i2c_clk: u32);
fn recv_data(&self, send_buf: &[u8], receive_buf: &mut [u8]) -> Result<(), ()>;
fn send_data(&self, send_buf: &[u8]) -> Result<(), ()>;
}
// Clear TX abort by reading from clear register
// Hopefully this is not optimized out
(*ptr).clr_tx_abrt.read().bits();
impl<IF: I2C012> I2CImpl<IF> {
pub fn new(i2c: IF) -> Self {
Self { i2c }
}
}
// Send all data that is left, handle errors that occur
while tx_left != 0 {
let fifo_len = 8 - ((*ptr).txflr.read().bits() as usize);
let fifo_len = cmp::min(tx_left, fifo_len);
for _ in 0..fifo_len {
(*ptr).data_cmd.write(|w| w.data().bits(send_buf[txi]));
txi += 1;
}
if (*ptr).tx_abrt_source.read().bits() != 0 {
return Err(());
}
tx_left -= fifo_len;
}
impl<IF: I2C012> I2C for I2CImpl<IF> {
fn init(&self, slave_address: u16, address_width: u32, i2c_clk: u32) {
assert!(address_width == 7 || address_width == 10);
// Wait for TX succeed
while (*ptr).status.read().activity().bit() || !(*ptr).status.read().tfe().bit() {
// NOP
}
sysctl::clock_enable(IF::CLK);
sysctl::clock_set_threshold(IF::DIV, 3);
// Check for errors one last time
if (*ptr).tx_abrt_source.read().bits() != 0 {
return Err(());
let v_i2c_freq = sysctl::clock_get_freq(sysctl::clock::I2C0);
let v_period_clk_cnt = v_i2c_freq / i2c_clk / 2;
let v_period_clk_cnt: u16 = v_period_clk_cnt.try_into().unwrap();
let v_period_clk_cnt = cmp::max(v_period_clk_cnt, 1);
unsafe {
self.i2c.enable.write(|w| w.bits(0));
self.i2c.con.write(|w| w.master_mode().bit(true)
.slave_disable().bit(true)
.restart_en().bit(true)
.addr_slave_width().bit(address_width == 10) // TODO variant
.speed().bits(1)); // TODO variant
self.i2c.ss_scl_hcnt.write(|w| w.count().bits(v_period_clk_cnt));
self.i2c.ss_scl_lcnt.write(|w| w.count().bits(v_period_clk_cnt));
self.i2c.tar.write(|w| w.address().bits(slave_address));
self.i2c.intr_mask.write(|w| w.bits(0));
self.i2c.dma_cr.write(|w| w.bits(0x3));
self.i2c.dma_rdlr.write(|w| w.bits(0));
self.i2c.dma_tdlr.write(|w| w.bits(4));
self.i2c.enable.write(|w| w.enable().bit(true));
}
}
Ok(())
fn recv_data(&self, send_buf: &[u8], receive_buf: &mut [u8]) -> Result<(), ()> {
unsafe {
let mut txi = 0;
let mut tx_left = send_buf.len();
while tx_left != 0 {
let fifo_len = 8 - (self.i2c.txflr.read().bits() as usize);
let fifo_len = cmp::min(tx_left, fifo_len);
for _ in 0..fifo_len {
self.i2c.data_cmd.write(|w| w.data().bits(send_buf[txi]));
txi += 1;
}
if self.i2c.tx_abrt_source.read().bits() != 0 {
return Err(());
}
tx_left -= fifo_len;
}
let mut cmd_count = receive_buf.len();
let mut rx_left = receive_buf.len();
let mut rxi = 0;
while cmd_count != 0 || rx_left != 0 {
/* XXX this is a kind of strange construction, sanity check */
let fifo_len = self.i2c.rxflr.read().bits() as usize;
let fifo_len = cmp::min(rx_left, fifo_len);
for _ in 0..fifo_len {
receive_buf[rxi] = self.i2c.data_cmd.read().data().bits();
rxi += 1;
}
rx_left -= fifo_len;
/* send 0x100 for every byte that we want to receive */
let fifo_len = 8 - self.i2c.txflr.read().bits() as usize;
let fifo_len = cmp::min(cmd_count, fifo_len);
for _ in 0..fifo_len {
self.i2c.data_cmd.write(|w| w.cmd().bit(true));
}
if self.i2c.tx_abrt_source.read().bits() != 0 {
return Err(());
}
cmd_count -= fifo_len;
}
}
Ok(())
}
fn send_data(&self, send_buf: &[u8]) -> Result<(), ()> {
unsafe {
let mut txi = 0;
let mut tx_left = send_buf.len();
// Clear TX abort by reading from clear register
// Hopefully this is not optimized out
self.i2c.clr_tx_abrt.read().bits();
// Send all data that is left, handle errors that occur
while tx_left != 0 {
let fifo_len = 8 - (self.i2c.txflr.read().bits() as usize);
let fifo_len = cmp::min(tx_left, fifo_len);
for _ in 0..fifo_len {
self.i2c.data_cmd.write(|w| w.data().bits(send_buf[txi]));
txi += 1;
}
if self.i2c.tx_abrt_source.read().bits() != 0 {
return Err(());
}
tx_left -= fifo_len;
}
// Wait for TX succeed
while self.i2c.status.read().activity().bit() || !self.i2c.status.read().tfe().bit() {
// NOP
}
// Check for errors one last time
if self.i2c.tx_abrt_source.read().bits() != 0 {
return Err(());
}
}
Ok(())
}
}