Skip to content

Commit 5d395d1

Browse files
Move MCP23S17 register code into separate module.
1 parent 6b002a5 commit 5d395d1

2 files changed

Lines changed: 103 additions & 61 deletions

File tree

src/main.rs

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
4545
// Sub-modules
4646
// -----------------------------------------------------------------------------
4747

48+
pub mod mcp23s17;
4849
pub mod rtc;
4950
pub mod vga;
5051

@@ -108,12 +109,14 @@ type I2cPins = (
108109
Pin<bank0::Gpio15, Function<hal::gpio::I2C>>,
109110
);
110111

112+
type SpiBus = hal::Spi<hal::spi::Enabled, pac::SPI0, 8>;
113+
111114
/// All the hardware we use on the Pico
112115
struct Hardware {
113116
/// All the pins we use on the Raspberry Pi Pico
114117
pins: Pins,
115118
/// The SPI bus connected to all the slots
116-
spi_bus: hal::Spi<hal::spi::Enabled, pac::SPI0, 8>,
119+
spi_bus: SpiBus,
117120
/// Something to perform small delays with. Uses SysTICK.
118121
delay: cortex_m::delay::Delay,
119122
/// Current 5-bit value shown on the LEDs (including the HDD in bit 0).
@@ -215,11 +218,11 @@ static HARDWARE: Mutex<core::cell::RefCell<Option<Hardware>>> =
215218
pub static OS_IMAGE: [u8; include_bytes!("thumbv6m-none-eabi-flash1002-libneotron_os.bin").len()] =
216219
*include_bytes!("thumbv6m-none-eabi-flash1002-libneotron_os.bin");
217220

218-
// Tracks if we have had an IO interrupt.
219-
//
220-
// Set by the GPIO interrupt routine to reflect the level state of the IRQ input
221-
// from the IO chip. This this value is `true` if any of `IRQ0` through `IRQ7`
222-
// is currently active (low).
221+
/// Tracks if we have had an IO interrupt.
222+
///
223+
/// Set by the GPIO interrupt routine to reflect the level state of the IRQ input
224+
/// from the IO chip. This this value is `true` if any of `IRQ0` through `IRQ7`
225+
/// is currently active (low).
223226
static INTERRUPT_PENDING: AtomicBool = AtomicBool::new(false);
224227

225228
/// The table of API calls we provide the OS
@@ -474,27 +477,6 @@ impl Hardware {
474477
/// Give the BMC 6us to calculate its response
475478
const BMC_REQUEST_RESPONSE_DELAY_CLOCKS: u32 = 6_000 / Self::NS_PER_CLOCK_CYCLE;
476479

477-
/// Data Direction Register A on the MCP23S17
478-
const MCP23S17_DDRA: u8 = 0x00;
479-
480-
/// Interrupt-on-Change Control Register B on the MCP23S17
481-
const MCP23S17_GPINTENB: u8 = 0x05;
482-
483-
/// Interrupt Control Register B on the MCP23S17
484-
const MCP23S17_DEFVALB: u8 = 0x07;
485-
486-
/// Interrupt Control Register B on the MCP23S17
487-
const MCP23S17_INTCONB: u8 = 0x09;
488-
489-
/// GPIO Pull-Up Register B on the MCP23S17
490-
const MCP23S17_GPPUB: u8 = 0x0D;
491-
492-
/// GPIO Data Register A on the MCP23S17
493-
const MCP23S17_GPIOA: u8 = 0x12;
494-
495-
/// GPIO Data Register B on the MCP23S17
496-
const MCP23S17_GPIOB: u8 = 0x13;
497-
498480
/// Build all our hardware drivers.
499481
///
500482
/// Puts the pins into the right modes, builds the SPI driver, etc.
@@ -729,43 +711,45 @@ impl Hardware {
729711
/// You are required to have called `self.release_cs_lines()` previously,
730712
/// otherwise the I/O chip and your selected bus device will both see a
731713
/// chip-select signal.
732-
fn with_io_cs<F>(&mut self, func: F)
714+
fn with_io_cs<F, T>(&mut self, func: F) -> T
733715
where
734-
F: FnOnce(&mut hal::Spi<hal::spi::Enabled, pac::SPI0, 8_u8>),
716+
F: FnOnce(&mut hal::Spi<hal::spi::Enabled, pac::SPI0, 8_u8>) -> T,
735717
{
736718
// Select MCP23S17
737719
self.pins.nspi_cs_io.set_low().unwrap();
738720
// Setup time
739721
cortex_m::asm::delay(Self::CS_IO_SETUP_CPU_CLOCKS);
740722
// Do the SPI thing
741-
func(&mut self.spi_bus);
723+
let result = func(&mut self.spi_bus);
742724
// Hold the CS pin a bit longer
743725
cortex_m::asm::delay(Self::CS_IO_HOLD_CPU_CLOCKS);
744726
// Release the CS pin
745727
self.pins.nspi_cs_io.set_high().unwrap();
728+
// Return the result from the closure
729+
result
746730
}
747731

748732
/// Write to a register on the MCP23S17 I/O chip.
749733
///
750734
/// * `register` - the address of the register to write to
751-
/// * `data` - the value to write
752-
fn io_chip_write(&mut self, register: u8, data: u8) {
735+
/// * `value` - the value to write
736+
fn io_chip_write(&mut self, register: mcp23s17::Register, value: u8) {
753737
// Inter-packet delay
754738
cortex_m::asm::delay(Self::CS_IO_DISABLE_CPU_CLOCKS);
755739

756740
// Do the operation with CS pin active
757741
self.with_io_cs(|spi| {
758-
spi.write(&[0x40, register, data]).unwrap();
742+
mcp23s17::write_register(spi, register, value);
759743
});
760744

761745
// Inter-packet delay
762746
cortex_m::asm::delay(Self::CS_IO_DISABLE_CPU_CLOCKS);
763747

764748
let read_back = self.io_chip_read(register);
765-
if read_back != data {
749+
if read_back != value {
766750
defmt::panic!(
767-
"Wrote 0x{:02x} to IO chip register {}, got 0x{:02x}",
768-
data,
751+
"Wrote 0x{:02x} to IO chip register {:?}, got 0x{:02x}",
752+
value,
769753
register,
770754
read_back
771755
);
@@ -775,20 +759,12 @@ impl Hardware {
775759
/// Read from a register on the MCP23S17 I/O chip.
776760
///
777761
/// * `register` - the address of the register to read from
778-
fn io_chip_read(&mut self, register: u8) -> u8 {
762+
fn io_chip_read(&mut self, register: mcp23s17::Register) -> u8 {
779763
// Inter-packet delay
780764
cortex_m::asm::delay(Self::CS_IO_DISABLE_CPU_CLOCKS);
781765

782-
// Starts with outbound, is replaced with inbound
783-
let mut buffer = [0x41, register, 0x00];
784-
785766
// Do the operation with CS pin active
786-
self.with_io_cs(|spi| {
787-
spi.transfer(&mut buffer).unwrap();
788-
});
789-
790-
// Last byte has the value we read
791-
buffer[2]
767+
self.with_io_cs(|spi| mcp23s17::read_register(spi, register))
792768
}
793769

794770
/// Set the four debug LEDs on the PCB.
@@ -798,7 +774,10 @@ impl Hardware {
798774
// LEDs are active-low.
799775
let leds = (leds ^ 0xFF) & 0xF;
800776
self.led_state = leds << 1 | (self.led_state & 1);
801-
self.io_chip_write(0x12, self.led_state << 3 | self.last_cs);
777+
self.io_chip_write(
778+
mcp23s17::Register::GPIOA,
779+
self.led_state << 3 | self.last_cs,
780+
);
802781
}
803782

804783
/// Set the HDD LED on the PCB.
@@ -807,7 +786,10 @@ impl Hardware {
807786
fn set_hdd_led(&mut self, enabled: bool) {
808787
// LEDs are active-low.
809788
self.led_state = (self.led_state & 0x1e) | u8::from(!enabled);
810-
self.io_chip_write(0x12, self.led_state << 3 | self.last_cs);
789+
self.io_chip_write(
790+
mcp23s17::Register::GPIOA,
791+
self.led_state << 3 | self.last_cs,
792+
);
811793
}
812794

813795
/// Perform some SPI transaction with a specific bus chip-select pin active.
@@ -823,7 +805,7 @@ impl Hardware {
823805

824806
if cs != self.last_cs {
825807
// Set CS Outputs into decoder/buffer
826-
self.io_chip_write(0x12, self.led_state << 3 | cs);
808+
self.io_chip_write(mcp23s17::Register::GPIOA, self.led_state << 3 | cs);
827809
self.last_cs = cs;
828810
}
829811

@@ -863,20 +845,16 @@ impl Hardware {
863845
// Undrive CS lines from decoder/buffer
864846
self.release_cs_lines();
865847

866-
// Inter-packet delay
867848
cortex_m::asm::delay(Self::CS_IO_DISABLE_CPU_CLOCKS);
868849

869850
// Set IODIRA = 0x00 => GPIOA is all outputs
870-
self.io_chip_write(Self::MCP23S17_DDRA, 0x00);
871-
872-
// Inter-packet delay
873-
cortex_m::asm::delay(Self::CS_IO_DISABLE_CPU_CLOCKS);
851+
self.io_chip_write(mcp23s17::Register::DDRA, 0x00);
874852

875853
// Set GPIOA = 0x00 => GPIOA is all low
876-
self.io_chip_write(Self::MCP23S17_GPIOA, 0x00);
854+
self.io_chip_write(mcp23s17::Register::GPIOA, 0x00);
877855

878856
// Set GPPUB to = 0xFF => GPIOB is pulled-up
879-
self.io_chip_write(Self::MCP23S17_GPPUB, 0xFF);
857+
self.io_chip_write(mcp23s17::Register::GPPUB, 0xFF);
880858

881859
// Set up interrupts. We want the line to go low if anything on GPIOB is
882860
// low.
@@ -885,18 +863,18 @@ impl Hardware {
885863
// for the interrupt-on-change feature. All the bits are set, so the
886864
// corresponding I/O pin is compared against the associated bit in the
887865
// DEFVAL register.
888-
self.io_chip_write(Self::MCP23S17_INTCONB, 0xFF);
866+
self.io_chip_write(mcp23s17::Register::INTCONB, 0xFF);
889867

890868
// All the bits are set, so the corresponding pins are enabled for
891869
// interrupt-on-change. The DEFVAL and INTCON registers must also be
892870
// configured if any pins are enabled for interrupt-on-change.
893-
self.io_chip_write(Self::MCP23S17_GPINTENB, 0xFF);
871+
self.io_chip_write(mcp23s17::Register::GPINTENB, 0xFF);
894872

895873
// The default comparison value is configured in the DEFVAL register. If
896874
// enabled (via GPINTEN and INTCON) to compare against the DEFVAL
897875
// register, an opposite value on the associated pin will cause an
898876
// interrupt to occur.
899-
self.io_chip_write(Self::MCP23S17_DEFVALB, 0xFF);
877+
self.io_chip_write(mcp23s17::Register::DEFVALB, 0x00);
900878
}
901879

902880
/// If the interrupt flag was set by the IRQ handler, read the interrupt
@@ -912,9 +890,11 @@ impl Hardware {
912890
}
913891
}
914892

915-
/// Returns the contents of the GPIOB register, i.e. eight bits, where if bit N is low, IRQN is active.
893+
/// Returns the contents of the GPIOB register, i.e. eight bits, where if
894+
/// bit N is low, IRQN is active.
916895
fn io_read_interrupts(&mut self) -> u8 {
917-
self.io_chip_read(Self::MCP23S17_GPIOB)
896+
self.io_chip_read(mcp23s17::Register::GPIOB)
897+
}
918898
}
919899

920900
/// Send a request to the BMC (a register read or a register write) and get

src/mcp23s17.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! MCP23S17 Control Functions
2+
3+
use embedded_hal::blocking::spi::{Transfer, Write};
4+
5+
use super::SpiBus;
6+
7+
/// The Registers on the MCP23S17
8+
///
9+
/// Assumes the device is in `BANK = 0` mode.
10+
#[derive(defmt::Format, Debug, Copy, Clone, PartialEq, Eq)]
11+
#[repr(u8)]
12+
pub enum Register {
13+
/// Data Direction Register A
14+
DDRA = 0x00,
15+
/// GPIO Input Polarity Register B
16+
IPOLB = 0x03,
17+
/// Interrupt-on-Change Control Register B
18+
GPINTENB = 0x05,
19+
/// Interrupt Control Register B
20+
DEFVALB = 0x07,
21+
/// Interrupt Control Register B
22+
INTCONB = 0x09,
23+
/// GPIO Pull-Up Register B
24+
GPPUB = 0x0D,
25+
/// GPIO Data Register A
26+
GPIOA = 0x12,
27+
/// GPIO Data Register B
28+
GPIOB = 0x13,
29+
}
30+
31+
/// The Control Byte prefix
32+
const CONTROL_PREFIX: u8 = 0b0100;
33+
34+
/// The value for Device Address 000
35+
const ADDRESS_0: u8 = 0;
36+
37+
/// Perform a write on the MCP23S17
38+
///
39+
/// Is of the format `0 1 0 0 A2 A1 A0 R/W`
40+
const WRITE_COMMAND: u8 = (CONTROL_PREFIX << 4) | (ADDRESS_0 << 1) | 0;
41+
42+
/// Perform a write on the MCP23S17
43+
///
44+
/// Is of the format `0 1 0 0 A2 A1 A0 R/W`
45+
const READ_COMMAND: u8 = (CONTROL_PREFIX << 4) | (ADDRESS_0 << 1) | 1;
46+
47+
/// Write to a register on the MCP23S17.
48+
///
49+
/// Assumes the Chip Select has been asserted already.
50+
pub fn write_register(spi: &mut SpiBus, register: Register, value: u8) {
51+
spi.write(&[WRITE_COMMAND, register as u8, value]).unwrap()
52+
}
53+
54+
/// Write to a register on the MCP23S17.
55+
///
56+
/// Assumes the Chip Select has been asserted already.
57+
pub fn read_register(spi: &mut SpiBus, register: Register) -> u8 {
58+
// Starts with outbound, is replaced with inbound
59+
let mut buffer = [READ_COMMAND, register as u8, 0x00];
60+
spi.transfer(&mut buffer).unwrap();
61+
buffer[2]
62+
}

0 commit comments

Comments
 (0)