Skip to content

Commit

Permalink
added basic unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkamprath committed Oct 1, 2024
1 parent 764e651 commit ed1da19
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 2 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ categories = ["no-std", "embedded"]
name = "i2c_character_display"
path = "src/lib.rs"
crate-type = ["lib"]
test = false
test = true
bench = false
doctest = false

[dependencies]
embedded-hal = { version = "1.0" }
bitfield = "0.17"
defmt = { version = "0.3", optional = true }

[features]
defmt = ["dep:defmt", "embedded-hal/defmt-03"]
defmt = ["dep:defmt", "embedded-hal/defmt-03"]

[dev-dependencies]
embedded-hal-mock = "0.11"
96 changes: 96 additions & 0 deletions src/adapter_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,99 @@ where
Ok(())
}
}

#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};

#[test]
fn test_generic_pcf8574t_config() {
let mut config = GenericPCF8574TConfig::<I2cMock>::default();
config.set_rs(1);
config.set_rw(0);
config.set_enable(1);
config.set_backlight(1);
config.set_data(0b1010);

assert_eq!(config.bits(), 0b10101101);
assert_eq!(
GenericPCF8574TConfig::<I2cMock>::default_i2c_address(),
0x27
);

config.set_rs(0);
config.set_rw(1);
config.set_enable(0);
config.set_backlight(0);
config.set_data(0b0101);

assert_eq!(config.bits(), 0b01010010);
}

#[test]
fn test_adafruit_lcd_backpack_config() {
let mut config = AdafruitLCDBackpackConfig::<I2cMock>::default();
config.set_rs(1);
config.set_enable(1);
config.set_backlight(1);
config.set_data(0b1010);
// adafruit backpack doesn't use RW

assert_eq!(config.bits(), 0b11010110);
assert_eq!(
AdafruitLCDBackpackConfig::<I2cMock>::default_i2c_address(),
0x20
);

config.set_rs(0);
config.set_enable(0);
config.set_backlight(0);
config.set_data(0b0101);

assert_eq!(config.bits(), 0b00101000);
}

#[test]
fn test_generic_pcf8574t_config_write_bits_to_gpio() {
let mut config = GenericPCF8574TConfig::<I2cMock>::default();
config.set_rs(1);
config.set_rw(0);
config.set_enable(1);
config.set_backlight(1);
config.set_data(0b1010);

let expected_transactions = [I2cTransaction::write(0x27, std::vec![0b10101101])];
let mut i2c = I2cMock::new(&expected_transactions);

config.write_bits_to_gpio(&mut i2c, 0x27).unwrap();
i2c.done();
}

#[test]
fn test_adafruit_lcd_backpack_config_write_bits_to_gpio() {
let mut config = AdafruitLCDBackpackConfig::<I2cMock>::default();
config.set_rs(1);
config.set_enable(1);
config.set_backlight(1);
config.set_data(0b1010);

let expected_transactions = [I2cTransaction::write(0x20, std::vec![0x09, 0b11010110])];
let mut i2c = I2cMock::new(&expected_transactions);

config.write_bits_to_gpio(&mut i2c, 0x20).unwrap();
i2c.done();
}

#[test]
fn test_adafruit_init() {
let config = AdafruitLCDBackpackConfig::<I2cMock>::default();

let expected_transactions = [I2cTransaction::write(0x20, std::vec![0x00, 0x00])];
let mut i2c = I2cMock::new(&expected_transactions);

config.init(&mut i2c, 0x20).unwrap();
i2c.done();
}
}
135 changes: 135 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ where
Ok(())
}

/// returns a reference to the I2C peripheral. mostly needed for testing
fn i2c(&mut self) -> &mut I2C {
&mut self.i2c
}

fn send_command(&mut self, command: u8) -> Result<(), Error<I2C>> {
self.bits.set_rs(0);
self.write_8_bits(command)?;
Expand Down Expand Up @@ -444,3 +449,133 @@ where
Ok(())
}
}

#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use embedded_hal_mock::eh1::{
delay::NoopDelay,
i2c::{Mock as I2cMock, Transaction as I2cTransaction},
};

#[test]
fn test_character_display_pcf8574t_init() {
let i2c_address = 0x27_u8;
let expected_i2c_transactions = std::vec![
// the PCF8574T has no adapter init sequence, so nothing to prepend
// the LCD init sequence
// write low nibble of 0x03 3 times
I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b0011_0100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0011_0000]), // low nibble, rw=0, enable=0
// write high nibble of 0x02 one time
I2cTransaction::write(i2c_address, std::vec![0b0010_0100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0010_0000]), // high nibble, rw=0, enable=0
// turn on the backlight
// I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
// LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
// = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
I2cTransaction::write(i2c_address, std::vec![0b0010_1100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b1000_1100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b1000_1000]), // low nibble, rw=0, enable=0
// LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
// = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
I2cTransaction::write(i2c_address, std::vec![0b0000_1100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b1100_1100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b1100_1000]), // low nibble, rw=0, enable=0
// LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
// = 0x04 | 0x02 | 0x00 = 0x06
I2cTransaction::write(i2c_address, std::vec![0b0000_1100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b0110_1100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0110_1000]), // low nibble, rw=0, enable=0
// LCD_CMD_CLEARDISPLAY
// = 0x01
I2cTransaction::write(i2c_address, std::vec![0b0000_1100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b0001_1100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0001_1000]), // low nibble, rw=0, enable=0
// LCD_CMD_RETURNHOME
// = 0x02
I2cTransaction::write(i2c_address, std::vec![0b0000_1100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0b0010_1100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0b0010_1000]), // low nibble, rw=0, enable=0
];

let i2c = I2cMock::new(&expected_i2c_transactions);
let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
let result = lcd.init();
assert!(result.is_ok());

// finish the i2c mock
lcd.i2c().done();
}

#[test]
fn test_adafruit_lcd_backpack_init() {
let i2c_address = 0x20_u8;
let expected_i2c_transactions = std::vec![
// the Adafruit Backpack need to init the adapter IC first
// write 0x00 to the MCP23008 IODIR register to set all pins as outputs
I2cTransaction::write(i2c_address, std::vec![0x00, 0x00]),
// the LCD init sequence
// write low nibble of 0x03 3 times
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0011_000]), // low nibble, rw=0, enable=0
// write high nibble of 0x02 one time
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b0_0010_000]), // high nibble, rw=0, enable=0
// turn on the backlight
// I2cTransaction::write(i2c_address, std::vec![0b0000_1000]), // backlight on
// LCD_CMD_FUNCTIONSET | LCD_FLAG_4BITMODE | LCD_FLAG_5x8_DOTS | LCD_FLAG_2LINE
// = 0x20 | 0x00 | 0x00 | 0x08 = 0x28
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_1000_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_1000_000]), // low nibble, rw=0, enable=0
// LCD_CMD_DISPLAYCONTROL | LCD_FLAG_DISPLAYON | LCD_FLAG_CURSOROFF | LCD_FLAG_BLINKOFF
// = 0x08 | 0x04 | 0x00 | 0x00 = 0x0C
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_1100_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_1100_000]), // low nibble, rw=0, enable=0
// LCD_CMD_ENTRYMODESET | LCD_FLAG_ENTRYLEFT | LCD_FLAG_ENTRYSHIFTDECREMENT
// = 0x04 | 0x02 | 0x00 = 0x06
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0110_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0110_000]), // low nibble, rw=0, enable=0
// LCD_CMD_CLEARDISPLAY
// = 0x01
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0001_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0001_000]), // low nibble, rw=0, enable=0
// LCD_CMD_RETURNHOME
// = 0x02
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_100]), // high nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0000_000]), // high nibble, rw=0, enable=0
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_100]), // low nibble, rw=0, enable=1
I2cTransaction::write(i2c_address, std::vec![0x09, 0b1_0010_000]), // low nibble, rw=0, enable=0
];

let i2c = I2cMock::new(&expected_i2c_transactions);
let mut lcd = AdafruitLCDBackpack::new(i2c, LcdDisplayType::Lcd16x2, NoopDelay::new());
let result = lcd.init();
assert!(result.is_ok());

// finish the i2c mock
lcd.i2c().done();
}
}

0 comments on commit ed1da19

Please sign in to comment.