Skip to content

Commit 0e515ba

Browse files
Added support for reading data from character display (#3)
Some I2C adaptors for character displays support reading data from the display. Specifically, the generic PCF8754T adaptor. This updated refactors code in order to support reading data from the display when the adaptor supports it.
1 parent a22aa11 commit 0e515ba

File tree

8 files changed

+477
-82
lines changed

8 files changed

+477
-82
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
## [0.3.0] - 2024-11-03
6+
* Added support for data reads from the LCD Character Display for I2C adapters that support it. Refactored code to support this.
7+
* Improved docuemntation
8+
59
## [0.2.1] - 2024-10-26
610
* Added support for `ufmt`
711
* Added `display_type()` method to `BaseCharacterDisplay` to allow for querying the display type

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "i2c-character-display"
3-
version = "0.2.1"
3+
version = "0.3.0"
44
edition = "2021"
55
description = "Driver for HD44780-based character displays connected via a I2C adapter"
66
license = "MIT"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Key features include:
2020
- `core::fmt::Write` implementation for easy use with the `write!` macro
2121
- Compatible with the `embedded-hal` traits v1.0 and later
2222
- Support for character displays that uses multiple HD44780 drivers, such as the 40x4 display
23+
- Optional support for the `defmt` and `ufmt` logging frameworks
24+
- Optional support for reading from the display on adapters that support it
2325

2426
## Usage
2527
Add this to your `Cargo.toml`:
@@ -82,6 +84,11 @@ of commands. For example:
8284
```rust
8385
lcd.backlight(true)?.clear()?.home()?.print("Hello, world!")?;
8486
```
87+
### Reading from the display
88+
Some I2C adapters support reading data from the HD44780 controller. Dor the I2C adapters that support it, the `read_device_data` method can be used to read
89+
from either the CGRAM or DDRAM at the current cursor position. The `read_address_counter` method can be used to read the address counter from the HD44780 controller.
90+
In both cases, the specific meaning of the data depends on the prior commands sent to the display. See the HD44780 datasheet for more information.
91+
8592
### Multiple HD44780 controller character displays
8693
Some character displays, such as the 40x4 display, use two HD44780 controllers to drive the display. This library supports these displays by
8794
treating them as one logical display with multiple HD44780 controllers. The `CharacterDisplayDualHD44780` type is used to control these displays.

src/adapter_config.rs

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,59 @@ pub mod adafruit_lcd_backpack;
22
pub mod dual_hd44780;
33
pub mod generic_pcf8574t;
44

5+
use core::fmt::Display;
6+
57
use crate::LcdDisplayType;
68
use embedded_hal::i2c;
79

810
#[derive(Debug, PartialEq, Copy, Clone)]
9-
pub enum AdapterError {
11+
pub enum AdapterError<I2C>
12+
where
13+
I2C: i2c::I2c,
14+
{
1015
/// The device ID was not recognized
1116
BadDeviceId,
17+
/// An I2C error occurred
18+
I2CError(I2C::Error),
1219
}
1320

1421
#[cfg(feature = "defmt")]
15-
impl defmt::Format for AdapterError {
22+
impl<I2C> defmt::Format for AdapterError<I2C>
23+
where
24+
I2C: i2c::I2c,
25+
{
1626
fn format(&self, fmt: defmt::Formatter) {
1727
match self {
1828
AdapterError::BadDeviceId => defmt::write!(fmt, "BadDeviceId"),
29+
AdapterError::I2CError(_) => defmt::write!(fmt, "I2CError"),
1930
}
2031
}
2132
}
2233

2334
#[cfg(feature = "ufmt")]
24-
impl ufmt::uDisplay for AdapterError {
35+
impl<I2C> ufmt::uDisplay for AdapterError<I2C>
36+
where
37+
I2C: i2c::I2c,
38+
{
2539
fn fmt<W>(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error>
2640
where
2741
W: ufmt::uWrite + ?Sized,
2842
{
2943
match self {
3044
AdapterError::BadDeviceId => ufmt::uwrite!(w, "BadDeviceId"),
45+
AdapterError::I2CError(_) => ufmt::uwrite!(w, "I2CError"),
46+
}
47+
}
48+
}
49+
50+
impl<I2C> Display for AdapterError<I2C>
51+
where
52+
I2C: i2c::I2c,
53+
{
54+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55+
match self {
56+
AdapterError::BadDeviceId => write!(f, "BadDeviceId"),
57+
AdapterError::I2CError(_) => write!(f, "I2CError"),
3158
}
3259
}
3360
}
@@ -36,28 +63,111 @@ pub trait AdapterConfigTrait<I2C>: Default
3663
where
3764
I2C: i2c::I2c,
3865
{
66+
/// Returns the bitfield value for the adapter
3967
fn bits(&self) -> u8;
68+
69+
/// Returns the default I2C address for the adapter
4070
fn default_i2c_address() -> u8;
4171

72+
/// Determines if reading from device is supported by this adapter
73+
fn supports_reads() -> bool {
74+
false
75+
}
76+
77+
/// Sets the RS pin for the display. A value of `false` indicates an instruction is being sent, while
78+
/// a value of `true` indicates data is being sent.
4279
fn set_rs(&mut self, value: bool);
80+
81+
/// Sets the RW pin for the display. A value of `false` indicates a write operation, while a value of
82+
/// `true` indicates a read operation. Not all displays support reading, so this method may not be
83+
/// implemented fully.
4384
fn set_rw(&mut self, value: bool);
85+
4486
/// Sets the enable pin for the given device. Most displays only have one enable pin, so the device
4587
/// parameter is ignored. For displays with two enable pins, the device parameter is used to determine
4688
/// which enable pin to set.
47-
fn set_enable(&mut self, value: bool, device: usize) -> Result<(), AdapterError>;
89+
fn set_enable(&mut self, value: bool, device: usize) -> Result<(), AdapterError<I2C>>;
90+
91+
/// Sets the backlight pin for the display. A value of `true` indicates the backlight is on, while a value
92+
/// of `false` indicates the backlight is off.
4893
fn set_backlight(&mut self, value: bool);
94+
4995
fn set_data(&mut self, value: u8);
5096

5197
fn init(&self, _i2c: &mut I2C, _i2c_address: u8) -> Result<(), I2C::Error> {
5298
Ok(())
5399
}
54100

55-
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), I2C::Error> {
101+
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), AdapterError<I2C>> {
56102
let data = [self.bits()];
57-
i2c.write(i2c_address, &data)?;
103+
i2c.write(i2c_address, &data)
104+
.map_err(AdapterError::I2CError)?;
105+
Ok(())
106+
}
107+
108+
/// writes a full byte to the indicated device. If `rs_setting` is `true`, the data is written to the data register,
109+
/// either the CGRAM or DDRAM, depending on prior command sent. If `rs_setting` is `false`, the data is written to
110+
/// command register.
111+
fn write_byte_to_device(
112+
&mut self,
113+
i2c: &mut I2C,
114+
i2c_address: u8,
115+
device: usize,
116+
rs_setting: bool,
117+
value: u8,
118+
) -> Result<(), AdapterError<I2C>> {
119+
self.write_nibble_to_device(i2c, i2c_address, device, rs_setting, value >> 4)
120+
.and_then(|_| {
121+
self.write_nibble_to_device(i2c, i2c_address, device, rs_setting, value & 0x0F)
122+
})
123+
}
124+
125+
/// writes the lower nibble of a `value` byte to the indicated device. Typically only used for device initialization in 4 bit mode.
126+
/// If `rs_setting` is `true`, the data is written to the data register,
127+
/// either the CGRAM or DDRAM, depending on prior command sent. If `rs_setting` is `false`, the data is written to
128+
/// command register.
129+
fn write_nibble_to_device(
130+
&mut self,
131+
i2c: &mut I2C,
132+
i2c_address: u8,
133+
device: usize,
134+
rs_setting: bool,
135+
value: u8,
136+
) -> Result<(), AdapterError<I2C>> {
137+
self.set_rs(rs_setting);
138+
self.set_rw(false);
139+
140+
// now write the low nibble
141+
self.set_data(value & 0x0F);
142+
self.set_enable(true, device)?;
143+
self.write_bits_to_gpio(i2c, i2c_address)?;
144+
self.set_enable(false, device)?;
145+
self.write_bits_to_gpio(i2c, i2c_address)?;
146+
58147
Ok(())
59148
}
60149

150+
/// read bytes from the indicated device. The size of the buffer is the number of bytes to read.
151+
/// What is read depends on the `rs_setting` parameter. If `rs_setting` is `true`, the data is read
152+
/// from the data register, either the CGRAM or DDRAM, depending on prior command sent. If `rs_setting`
153+
/// is `false`, the data is read from the busy flag and address register.
154+
/// Note that while nothing "breaks" passing a buffer size greater than one when `rs_setting` is `false`,
155+
/// the data returned will be the same for each byte read.
156+
fn read_bytes_from_device(
157+
&self,
158+
_i2c: &mut I2C,
159+
_i2c_address: u8,
160+
_device: usize,
161+
_rs_setting: bool,
162+
_buffer: &mut [u8],
163+
) -> Result<(), AdapterError<I2C>> {
164+
unimplemented!("Reads are not supported for device");
165+
}
166+
167+
fn is_busy(&self, _i2c: &mut I2C, _i2c_address: u8) -> Result<bool, AdapterError<I2C>> {
168+
Ok(false)
169+
}
170+
61171
fn device_count(&self) -> usize {
62172
1
63173
}

src/adapter_config/adafruit_lcd_backpack.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ where
4444
0x20
4545
}
4646

47+
fn supports_reads() -> bool {
48+
false
49+
}
50+
4751
fn set_rs(&mut self, value: bool) {
4852
self.bits.set_rs(value as u8);
4953
}
@@ -53,7 +57,7 @@ where
5357
// Not used
5458
}
5559

56-
fn set_enable(&mut self, value: bool, _device: usize) -> Result<(), AdapterError> {
60+
fn set_enable(&mut self, value: bool, _device: usize) -> Result<(), AdapterError<I2C>> {
5761
self.bits.set_enable(value as u8);
5862
Ok(())
5963
}
@@ -72,10 +76,11 @@ where
7276
Ok(())
7377
}
7478

75-
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), I2C::Error> {
79+
fn write_bits_to_gpio(&self, i2c: &mut I2C, i2c_address: u8) -> Result<(), AdapterError<I2C>> {
7680
// first byte is GPIO register address
7781
let data = [0x09, self.bits.0];
78-
i2c.write(i2c_address, &data)?;
82+
i2c.write(i2c_address, &data)
83+
.map_err(AdapterError::I2CError)?;
7984
Ok(())
8085
}
8186

src/adapter_config/dual_hd44780.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ bitfield! {
1515
pub data, set_data: 7, 4;
1616
}
1717

18+
impl Clone for DualHD44780_PCF8574TBitField {
19+
fn clone(&self) -> Self {
20+
Self(self.0)
21+
}
22+
}
23+
1824
pub struct DualHD44780_PCF8574TConfig<I2C> {
1925
bits: DualHD44780_PCF8574TBitField,
2026
_marker: PhantomData<I2C>,
@@ -44,6 +50,10 @@ where
4450
0x27
4551
}
4652

53+
fn supports_reads() -> bool {
54+
false
55+
}
56+
4757
fn set_rs(&mut self, value: bool) {
4858
self.bits.set_rs(value as u8);
4959
}
@@ -53,7 +63,7 @@ where
5363
// does nothing
5464
}
5565

56-
fn set_enable(&mut self, value: bool, device: usize) -> Result<(), AdapterError> {
66+
fn set_enable(&mut self, value: bool, device: usize) -> Result<(), AdapterError<I2C>> {
5767
if device == 0 {
5868
self.bits.set_enable1(value as u8);
5969
} else if device == 1 {
@@ -98,7 +108,7 @@ mod tests {
98108
#[test]
99109
fn test_bad_device_id() {
100110
let mut config = DualHD44780_PCF8574TConfig::<I2cMock>::default();
101-
assert_eq!(config.set_enable(true, 2), Err(AdapterError::BadDeviceId));
111+
assert!(config.set_enable(true, 2).is_err());
102112
}
103113

104114
#[test]

0 commit comments

Comments
 (0)