-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from michaelkamprath/new_displays_oct2024
Added support for dual hd44780 displays
- Loading branch information
Showing
8 changed files
with
928 additions
and
362 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# `i2c-character-display` Change Log | ||
|
||
## [Unreleased] | ||
|
||
## [0.2.0] - 2024-10-19 | ||
* Added support for 40x4 character displays using two HD44780 controllers with a PCF8574T I2C adapter wired with two enable pins, one for each controller. | ||
* Improved unit tests | ||
|
||
## 0.1.0 | ||
Initial release. Support for both Generic PCF8574T I2C and Adafruit Backpack character display adapters. | ||
|
||
[Unreleased]: https://github.com/michaelkamprath/bespokeasm/compare/v0.2.0...HEAD | ||
[0.2.0]: https://github.com/michaelkamprath/bespokeasm/compare/v0.1.0...v0.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,260 +1,60 @@ | ||
use bitfield::bitfield; | ||
use core::marker::PhantomData; | ||
use embedded_hal::i2c; | ||
|
||
pub trait AdapterConfigTrait<I2C>: Default | ||
where | ||
I2C: i2c::I2c, | ||
{ | ||
fn bits(&self) -> u8; | ||
fn default_i2c_address() -> u8; | ||
|
||
fn set_rs(&mut self, value: u8); | ||
fn set_rw(&mut self, value: u8); | ||
fn set_enable(&mut self, value: u8); | ||
fn set_backlight(&mut self, value: u8); | ||
fn set_data(&mut self, value: u8); | ||
pub mod adafruit_lcd_backpack; | ||
pub mod dual_hd44780; | ||
pub mod generic_pcf8574t; | ||
|
||
fn init(&self, _i2c: &mut I2C, _i2c_address: u8) -> Result<(), I2C::Error> { | ||
Ok(()) | ||
} | ||
|
||
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), I2C::Error> { | ||
let data = [self.bits()]; | ||
i2c.write(i2c_address, &data)?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
// Configuration for the PCF8574T based 4-bit LCD interface sold | ||
bitfield! { | ||
pub struct GenericPCF8574TBitField(u8); | ||
impl Debug; | ||
impl BitAnd; | ||
pub rs, set_rs: 0, 0; | ||
pub rw, set_rw: 1, 1; | ||
pub enable, set_enable: 2, 2; | ||
pub backlight, set_backlight: 3, 3; | ||
pub data, set_data: 7, 4; | ||
} | ||
use crate::LcdDisplayType; | ||
use embedded_hal::i2c; | ||
|
||
pub struct GenericPCF8574TConfig<I2C> { | ||
bits: GenericPCF8574TBitField, | ||
_marker: PhantomData<I2C>, | ||
#[derive(Debug, PartialEq, Copy, Clone)] | ||
pub enum AdapterError { | ||
/// The device ID was not recognized | ||
BadDeviceId, | ||
} | ||
|
||
impl<I2C> Default for GenericPCF8574TConfig<I2C> | ||
where | ||
I2C: i2c::I2c, | ||
{ | ||
fn default() -> Self { | ||
Self { | ||
bits: GenericPCF8574TBitField(0), | ||
_marker: PhantomData, | ||
#[cfg(feature = "defmt")] | ||
impl defmt::Format for AdapterError { | ||
fn format(&self, fmt: defmt::Formatter) { | ||
match self { | ||
AdapterError::BadDeviceId => defmt::write!(fmt, "BadDeviceId"), | ||
} | ||
} | ||
} | ||
|
||
impl<I2C> AdapterConfigTrait<I2C> for GenericPCF8574TConfig<I2C> | ||
where | ||
I2C: i2c::I2c, | ||
{ | ||
fn bits(&self) -> u8 { | ||
self.bits.0 | ||
} | ||
|
||
fn default_i2c_address() -> u8 { | ||
0x27 | ||
} | ||
|
||
fn set_rs(&mut self, value: u8) { | ||
self.bits.set_rs(value); | ||
} | ||
|
||
fn set_rw(&mut self, value: u8) { | ||
self.bits.set_rw(value); | ||
} | ||
|
||
fn set_enable(&mut self, value: u8) { | ||
self.bits.set_enable(value); | ||
} | ||
|
||
fn set_backlight(&mut self, value: u8) { | ||
self.bits.set_backlight(value); | ||
} | ||
|
||
fn set_data(&mut self, value: u8) { | ||
self.bits.set_data(value); | ||
} | ||
} | ||
|
||
// Configuration for the MCP23008 based LCD backpack from Adafruit | ||
bitfield! { | ||
pub struct AdafruitLCDBackpackBitField(u8); | ||
impl Debug; | ||
impl BitAnd; | ||
pub rs, set_rs: 1, 1; | ||
pub enable, set_enable: 2, 2; | ||
pub backlight, set_backlight: 7, 7; | ||
pub data, set_data: 6, 3; | ||
} | ||
|
||
pub struct AdafruitLCDBackpackConfig<I2C> { | ||
bits: AdafruitLCDBackpackBitField, | ||
_marker: PhantomData<I2C>, | ||
} | ||
|
||
impl<I2C> Default for AdafruitLCDBackpackConfig<I2C> | ||
where | ||
I2C: i2c::I2c, | ||
{ | ||
fn default() -> Self { | ||
Self { | ||
bits: AdafruitLCDBackpackBitField(0), | ||
_marker: PhantomData, | ||
} | ||
} | ||
} | ||
/// Configuration for the MCP23008 based LCD backpack from Adafruit | ||
impl<I2C> AdapterConfigTrait<I2C> for AdafruitLCDBackpackConfig<I2C> | ||
pub trait AdapterConfigTrait<I2C>: Default | ||
where | ||
I2C: i2c::I2c, | ||
{ | ||
fn bits(&self) -> u8 { | ||
self.bits.0 | ||
} | ||
|
||
fn default_i2c_address() -> u8 { | ||
0x20 | ||
} | ||
|
||
fn set_rs(&mut self, value: u8) { | ||
self.bits.set_rs(value); | ||
} | ||
|
||
/// Adafruit LCD Backpack doesn't use RW | ||
fn set_rw(&mut self, _value: u8) { | ||
// Not used | ||
} | ||
|
||
fn set_enable(&mut self, value: u8) { | ||
self.bits.set_enable(value); | ||
} | ||
|
||
fn set_backlight(&mut self, value: u8) { | ||
self.bits.set_backlight(value); | ||
} | ||
fn bits(&self) -> u8; | ||
fn default_i2c_address() -> u8; | ||
|
||
fn set_data(&mut self, value: u8) { | ||
self.bits.set_data(value); | ||
} | ||
fn set_rs(&mut self, value: bool); | ||
fn set_rw(&mut self, value: bool); | ||
/// Sets the enable pin for the given device. Most displays only have one enable pin, so the device | ||
/// parameter is ignored. For displays with two enable pins, the device parameter is used to determine | ||
/// which enable pin to set. | ||
fn set_enable(&mut self, value: bool, device: usize) -> Result<(), AdapterError>; | ||
fn set_backlight(&mut self, value: bool); | ||
fn set_data(&mut self, value: u8); | ||
|
||
fn init(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), I2C::Error> { | ||
// Set the MCP23008 IODIR register to output | ||
i2c.write(i2c_address, &[0x00, 0x00])?; | ||
fn init(&self, _i2c: &mut I2C, _i2c_address: u8) -> Result<(), I2C::Error> { | ||
Ok(()) | ||
} | ||
|
||
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), I2C::Error> { | ||
// first byte is GPIO register address | ||
let data = [0x09, self.bits.0]; | ||
let data = [self.bits()]; | ||
i2c.write(i2c_address, &data)?; | ||
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(); | ||
fn device_count(&self) -> usize { | ||
1 | ||
} | ||
|
||
#[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(); | ||
/// Convert a row number to the row number on the device | ||
fn row_to_device_row(&self, row: u8) -> (usize, u8) { | ||
(0, row) | ||
} | ||
|
||
#[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(); | ||
} | ||
/// Determines of display type is supported by this adapter | ||
fn is_supported(display_type: LcdDisplayType) -> bool; | ||
} |
Oops, something went wrong.