Skip to content

Commit

Permalink
Merge pull request #1 from michaelkamprath/new_displays_oct2024
Browse files Browse the repository at this point in the history
Added support for dual hd44780 displays
  • Loading branch information
michaelkamprath authored Oct 19, 2024
2 parents ed1da19 + 33a52a1 commit 690fc81
Show file tree
Hide file tree
Showing 8 changed files with 928 additions and 362 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "i2c-character-display"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
description = "Driver for HD44780-based character displays connected via a I2C adapter"
license = "MIT"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Key features include:
- Backlight control
- `core::fmt::Write` implementation for easy use with the `write!` macro
- Compatible with the `embedded-hal` traits v1.0 and later
- Support for character displays that used multiple HD44780 drivers, such as the 40x4 display

## Usage
Add this to your `Cargo.toml`:
Expand All @@ -43,6 +44,8 @@ let delay = ...; // DelayMs implementation
let mut lcd = AdafruitLCDBackpack::new(i2c, LcdDisplayType::Lcd16x2, delay);
// PCF8574T adapter
let mut lcd = CharacterDisplayPCF8574T::new(i2c, LcdDisplayType::Lcd16x2, delay);
// Character display with dual HD44780 drivers using a PCF8574T I2C adapter
let mut lcd = CharacterDisplayDualHD44780::new(i2c, LcdDisplayType::Lcd40x4, delay);
```
When creating the display object, you can choose the display type from the `LcdDisplayType` enum. The display type should match the physical
display you are using. This display type configures the number of rows and columns, and the internal row offsets for the display.
Expand Down
268 changes: 34 additions & 234 deletions src/adapter_config.rs
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;
}
Loading

0 comments on commit 690fc81

Please sign in to comment.