#![cfg_attr(not(test), no_std)]
mod configuration;
pub mod interface;
mod register;
pub use configuration::ConfigBuilder;
use configuration::{ConfigBuilderDeviceSpecific, Configuration};
use embedded_hal::delay::DelayNs;
use interface::RegisterAccess;
use register::{BitFlags, Register};
#[derive(Debug)]
pub enum Error<IE> {
Interface(IE),
BufferOverrun,
}
pub const T_CHIP_EN_US: u32 = 100;
pub trait ToRegisterValue<T> {
fn register_value(&self) -> T;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PwmFrequency {
Pwm125kHz,
Pwm62_5kHz,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineBlankingTime {
Blank1us,
Blank0_5us,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PwmScaleMode {
Linear,
Exponential,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DownDeghost {
None,
Weak,
Medium,
Strong,
}
impl ToRegisterValue<u8> for DownDeghost {
fn register_value(&self) -> u8 {
match self {
DownDeghost::None => 0,
DownDeghost::Weak => 1,
DownDeghost::Medium => 2,
DownDeghost::Strong => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpDeghost {
VledMinus2V,
VledMinus2_5V,
VledMinus3V,
Gnd,
}
impl ToRegisterValue<u8> for UpDeghost {
fn register_value(&self) -> u8 {
match self {
UpDeghost::VledMinus2V => 0,
UpDeghost::VledMinus2_5V => 1,
UpDeghost::VledMinus3V => 2,
UpDeghost::Gnd => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataRefMode {
Mode1,
Mode2,
Mode3,
}
impl ToRegisterValue<u8> for DataRefMode {
fn register_value(&self) -> u8 {
match self {
DataRefMode::Mode1 => 0,
DataRefMode::Mode2 => 1,
DataRefMode::Mode3 => 3,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CurrentSettingNonT {
Max3mA,
Max5mA,
Max10mA,
#[default]
Max15mA,
Max20mA,
Max30mA,
Max40mA,
Max50mA,
}
impl ToRegisterValue<u8> for CurrentSettingNonT {
fn register_value(&self) -> u8 {
match self {
CurrentSettingNonT::Max3mA => 0,
CurrentSettingNonT::Max5mA => 1,
CurrentSettingNonT::Max10mA => 2,
CurrentSettingNonT::Max15mA => 3,
CurrentSettingNonT::Max20mA => 4,
CurrentSettingNonT::Max30mA => 5,
CurrentSettingNonT::Max40mA => 6,
CurrentSettingNonT::Max50mA => 7,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CurrentSettingT {
Max7_5mA,
Max12_5mA,
Max25mA,
#[default]
Max37_5mA,
Max50mA,
Max75mA,
Max100mA,
}
impl ToRegisterValue<u8> for CurrentSettingT {
fn register_value(&self) -> u8 {
match self {
CurrentSettingT::Max7_5mA => 0,
CurrentSettingT::Max12_5mA => 1,
CurrentSettingT::Max25mA => 2,
CurrentSettingT::Max37_5mA => 3,
CurrentSettingT::Max50mA => 4,
CurrentSettingT::Max75mA => 5,
CurrentSettingT::Max100mA => 6,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CurrentSettingLP5861T {
Max7_5mA,
Max12_5mA,
Max25mA,
#[default]
Max37_5mA,
Max50mA,
Max75mA,
Max100mA,
Max125mA,
}
impl ToRegisterValue<u8> for CurrentSettingLP5861T {
fn register_value(&self) -> u8 {
match self {
CurrentSettingLP5861T::Max7_5mA => 0,
CurrentSettingLP5861T::Max12_5mA => 1,
CurrentSettingLP5861T::Max25mA => 2,
CurrentSettingLP5861T::Max37_5mA => 3,
CurrentSettingLP5861T::Max50mA => 4,
CurrentSettingLP5861T::Max75mA => 5,
CurrentSettingLP5861T::Max100mA => 6,
CurrentSettingLP5861T::Max125mA => 7,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Group {
Group0,
Group1,
Group2,
}
impl Group {
pub fn brightness_reg_addr(&self) -> u16 {
match self {
Group::Group0 => Register::GROUP0_BRIGHTNESS,
Group::Group1 => Register::GROUP1_BRIGHTNESS,
Group::Group2 => Register::GROUP2_BRIGHTNESS,
}
}
pub fn current_reg_addr(&self) -> u16 {
match self {
Group::Group0 => Register::GROUP0_CURRENT,
Group::Group1 => Register::GROUP1_CURRENT,
Group::Group2 => Register::GROUP2_CURRENT,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DotGroup {
None,
Group0,
Group1,
Group2,
}
impl ToRegisterValue<u8> for DotGroup {
fn register_value(&self) -> u8 {
match self {
DotGroup::None => 0,
DotGroup::Group0 => 0b01,
DotGroup::Group1 => 0b10,
DotGroup::Group2 => 0b11,
}
}
}
#[derive(Debug)]
pub struct GlobalFaultState {
led_open_detected: bool,
led_short_detected: bool,
}
impl GlobalFaultState {
pub(crate) fn from_reg_value(fault_state_value: u8) -> Self {
GlobalFaultState {
led_open_detected: fault_state_value & BitFlags::FAULT_STATE_GLOBAL_LOD > 0,
led_short_detected: fault_state_value & BitFlags::FAULT_STATE_GLOBAL_LSD > 0,
}
}
pub fn led_open_detected(&self) -> bool {
self.led_open_detected
}
pub fn led_short_detected(&self) -> bool {
self.led_short_detected
}
}
mod seal {
pub trait Sealed {}
}
pub trait DeviceVariant: seal::Sealed {
const NUM_LINES: u8;
const NUM_CURRENT_SINKS: u8 = 18;
const NUM_DOTS: u16 = Self::NUM_LINES as u16 * Self::NUM_CURRENT_SINKS as u16;
type CurrentSetting: ToRegisterValue<u8> + Clone + core::fmt::Debug + Default;
}
macro_rules! device_variant {
($name:ident, $num_lines:literal, $current_setting_type:path) => {
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct $name;
impl DeviceVariant for $name {
const NUM_LINES: u8 = $num_lines;
type CurrentSetting = $current_setting_type;
}
impl seal::Sealed for $name {}
};
}
device_variant!(Variant0, 11, CurrentSettingNonT);
device_variant!(Variant1, 1, CurrentSettingNonT);
device_variant!(Variant2, 2, CurrentSettingNonT);
device_variant!(Variant4, 4, CurrentSettingNonT);
device_variant!(Variant6, 6, CurrentSettingNonT);
device_variant!(Variant8, 8, CurrentSettingNonT);
device_variant!(Variant0T, 11, CurrentSettingT);
device_variant!(Variant1T, 1, CurrentSettingLP5861T);
device_variant!(Variant6T, 6, CurrentSettingT);
device_variant!(Variant8T, 8, CurrentSettingT);
pub trait DataModeMarker: seal::Sealed {}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct DataMode8Bit;
impl DataModeMarker for DataMode8Bit {}
impl seal::Sealed for DataMode8Bit {}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct DataMode16Bit;
impl DataModeMarker for DataMode16Bit {}
impl seal::Sealed for DataMode16Bit {}
pub struct Lp586x<DV, I, DM> {
interface: I,
_phantom_data: core::marker::PhantomData<(DV, DM)>,
}
impl<DV: DeviceVariant, DM: DataModeMarker, IE, I2C> Lp586x<DV, interface::I2cInterface<I2C>, DM>
where
I2C: embedded_hal::i2c::I2c<Error = IE>,
{
pub fn new_with_i2c(
config: &ConfigBuilderDeviceSpecific<DV, DM>,
i2c: I2C,
address: u8,
delay: impl DelayNs,
) -> Result<Lp586x<DV, interface::I2cInterface<I2C>, DM>, Error<IE>> {
Lp586x::<DV, _, DM>::new(config, interface::I2cInterface::new(i2c, address), delay)
}
}
impl<DV: DeviceVariant, DM: DataModeMarker, IE, SPI>
Lp586x<DV, interface::SpiDeviceInterface<SPI>, DM>
where
SPI: embedded_hal::spi::SpiDevice<Error = IE>,
{
pub fn new_with_spi_device(
config: &ConfigBuilderDeviceSpecific<DV, DM>,
spi_device: SPI,
delay: impl DelayNs,
) -> Result<Lp586x<DV, interface::SpiDeviceInterface<SPI>, DM>, Error<IE>> {
Lp586x::<DV, _, DM>::new(
config,
interface::SpiDeviceInterface::new(spi_device),
delay,
)
}
}
macro_rules! fault_per_dot_fn {
($name:ident, $reg:expr, $doc:literal) => {
#[doc=$doc]
pub fn $name(&mut self, dots: &mut [bool]) -> Result<(), Error<IE>> {
let mut buffer = [0u8; 33];
self.interface.read_registers($reg, &mut buffer)?;
dots[..DV::NUM_DOTS as usize]
.iter_mut()
.enumerate()
.map(|(i, dot)| {
(
i / DV::NUM_CURRENT_SINKS as usize,
i % DV::NUM_CURRENT_SINKS as usize,
dot,
)
})
.for_each(|(line, cs, led_is_open)| {
*led_is_open = buffer[line * 3 + cs / 8] & (1 << (cs % 8)) > 0;
});
Ok(())
}
};
}
impl<DV: DeviceVariant, I, DM, IE> Lp586x<DV, I, DM>
where
I: RegisterAccess<Error = Error<IE>>,
DM: DataModeMarker,
{
pub const NUM_CURRENT_SINKS: usize = DV::NUM_CURRENT_SINKS as usize;
pub const NUM_DOTS: usize = DV::NUM_DOTS as usize;
pub fn new(
config: &ConfigBuilderDeviceSpecific<DV, DM>,
interface: I,
mut delay: impl DelayNs,
) -> Result<Lp586x<DV, I, DM>, Error<IE>> {
let mut driver = Lp586x {
interface,
_phantom_data: core::marker::PhantomData,
};
driver.reset()?;
driver.chip_enable(true)?;
delay.delay_us(T_CHIP_EN_US);
driver.configure(&config.configuration)?;
driver.chip_enable(false)?;
delay.delay_us(T_CHIP_EN_US);
driver.chip_enable(true)?;
Ok(driver)
}
pub const fn num_lines(&self) -> u8 {
DV::NUM_LINES
}
pub const fn num_dots(&self) -> u16 {
DV::NUM_DOTS
}
pub fn chip_enable(&mut self, enable: bool) -> Result<(), Error<IE>> {
self.interface.write_register(
Register::CHIP_EN,
if enable { BitFlags::CHIP_EN_CHIP_EN } else { 0 },
)
}
pub(crate) fn configure(&mut self, configuration: &Configuration<DV>) -> Result<(), Error<IE>> {
self.interface.write_registers(
Register::DEV_INITIAL,
&[
configuration.dev_initial_reg_value(),
configuration.dev_config1_reg_value(),
configuration.dev_config2_reg_value(),
configuration.dev_config3_reg_value(),
],
)?;
Ok(())
}
pub fn reset(&mut self) -> Result<(), Error<IE>> {
self.interface.write_register(Register::RESET, 0xff)
}
pub fn set_dot_groups(&mut self, dot_groups: &[DotGroup]) -> Result<(), Error<IE>> {
let mut buffer = [0u8; 54];
assert!(dot_groups.len() <= self.num_dots() as usize);
assert!(!dot_groups.is_empty());
dot_groups
.iter()
.enumerate()
.map(|(i, dot_group)| {
(
i / Self::NUM_CURRENT_SINKS,
i % Self::NUM_CURRENT_SINKS,
dot_group,
)
})
.for_each(|(line, cs, dot_group)| {
buffer[line * 5 + cs / 4] |= dot_group.register_value() << (cs % 4 * 2)
});
let last_group = (dot_groups.len() - 1) / Self::NUM_CURRENT_SINKS * 5
+ (dot_groups.len() - 1) % Self::NUM_CURRENT_SINKS / 4;
self.interface
.write_registers(Register::DOT_GROUP_SELECT_START, &buffer[..=last_group])?;
Ok(())
}
pub fn set_dot_current(&mut self, start_dot: u16, current: &[u8]) -> Result<(), Error<IE>> {
assert!(current.len() <= self.num_dots() as usize);
assert!(!current.is_empty());
self.interface
.write_registers(Register::DOT_CURRENT_START + start_dot, current)?;
Ok(())
}
pub fn set_global_brightness(&mut self, brightness: u8) -> Result<(), Error<IE>> {
self.interface
.write_register(Register::GLOBAL_BRIGHTNESS, brightness)?;
Ok(())
}
pub fn set_group_brightness(&mut self, group: Group, brightness: u8) -> Result<(), Error<IE>> {
self.interface
.write_register(group.brightness_reg_addr(), brightness)?;
Ok(())
}
pub fn set_color_group_current(&mut self, group: Group, current: u8) -> Result<(), Error<IE>> {
self.interface
.write_register(group.current_reg_addr(), current.min(0x7f))?;
Ok(())
}
pub fn get_global_fault_state(&mut self) -> Result<GlobalFaultState, Error<IE>> {
let fault_state_value = self.interface.read_register(Register::FAULT_STATE)?;
Ok(GlobalFaultState::from_reg_value(fault_state_value))
}
fault_per_dot_fn!(
get_led_open_states,
Register::DOT_LOD_START,
"Get LED open states, starting from the first dot."
);
fault_per_dot_fn!(
get_led_short_states,
Register::DOT_LSD_START,
"Get LED short states, starting from the first dot."
);
pub fn clear_led_open_fault(&mut self) -> Result<(), Error<IE>> {
self.interface.write_register(Register::LOD_CLEAR, 0xF)
}
pub fn clear_led_short_fault(&mut self) -> Result<(), Error<IE>> {
self.interface.write_register(Register::LSD_CLEAR, 0xF)
}
}
pub trait PwmAccess<T> {
type Error;
fn set_pwm(&mut self, start: u16, values: &[T]) -> Result<(), Self::Error>;
fn get_pwm(&mut self, dot: u16) -> Result<T, Self::Error>;
}
impl<DV: DeviceVariant, I, IE> PwmAccess<u8> for Lp586x<DV, I, DataMode8Bit>
where
I: RegisterAccess<Error = Error<IE>>,
{
type Error = Error<IE>;
fn set_pwm(&mut self, start_dot: u16, values: &[u8]) -> Result<(), Self::Error> {
if values.len() + start_dot as usize > (DV::NUM_DOTS as usize) {
panic!("Too many values supplied for given start and device variant.");
}
self.interface
.write_registers(Register::PWM_BRIGHTNESS_START + start_dot, values)?;
Ok(())
}
fn get_pwm(&mut self, dot: u16) -> Result<u8, Self::Error> {
self.interface
.read_register(Register::PWM_BRIGHTNESS_START + dot)
}
}
impl<DV: DeviceVariant, I, IE> PwmAccess<u16> for Lp586x<DV, I, DataMode16Bit>
where
I: RegisterAccess<Error = Error<IE>>,
{
type Error = Error<IE>;
fn set_pwm(&mut self, start_dot: u16, values: &[u16]) -> Result<(), Self::Error> {
let mut buffer = [0; Variant0::NUM_DOTS as usize * 2];
if values.len() + start_dot as usize > (DV::NUM_DOTS as usize) {
panic!("Too many values supplied for given start and device variant.");
}
buffer
.chunks_exact_mut(2)
.zip(values.iter())
.for_each(|(dest, src)| dest.copy_from_slice(&src.to_le_bytes()));
self.interface.write_registers(
Register::PWM_BRIGHTNESS_START + start_dot * 2,
&buffer[..values.len() * 2],
)?;
Ok(())
}
fn get_pwm(&mut self, dot: u16) -> Result<u16, Self::Error> {
self.interface
.read_register_wide(Register::PWM_BRIGHTNESS_START + (dot * 2))
}
}
impl<DV, SPID: embedded_hal::spi::SpiDevice, DM>
Lp586x<DV, interface::SpiDeviceInterface<SPID>, DM>
{
pub fn release(self) -> SPID {
self.interface.release()
}
}
impl<DV, I2C: embedded_hal::i2c::I2c, DM> Lp586x<DV, interface::I2cInterface<I2C>, DM> {
pub fn release(self) -> I2C {
self.interface.release()
}
}
#[cfg(test)]
impl<DV, DM> Lp586x<DV, interface::mock::MockInterface, DM> {
pub fn release(self) -> interface::mock::MockInterface {
self.interface
}
}
#[cfg(test)]
mod tests {
use super::*;
use interface::mock::{Access, MockInterface};
#[test]
fn test_create_new() {
let interface = MockInterface::new(vec![
Access::WriteRegister(0x0a9, 0xff),
Access::WriteRegister(0x000, 1),
Access::WriteRegisters(0x001, vec![0x5E, 0x00, 0x00, 0x57]),
Access::WriteRegister(0x000, 0),
Access::WriteRegister(0x000, 1),
]);
let mut delay = embedded_hal_mock::eh1::delay::NoopDelay::new();
let config = ConfigBuilder::new_lp5860();
let ledmatrix = Lp586x::new(&config, interface, &mut delay).unwrap();
ledmatrix.release().done();
}
#[test]
fn test_set_dot_groups() {
#[rustfmt::skip]
let interface = MockInterface::new(vec![
Access::WriteRegister(0x0a9, 0xff),
Access::WriteRegister(0x000, 1),
Access::WriteRegisters(0x001, vec![0x5E, 0x00, 0x00, 0x57]),
Access::WriteRegister(0x000, 0),
Access::WriteRegister(0x000, 1),
Access::WriteRegisters(
0x00c,
vec![
0b01111001, 0b10011110, 0b11100111, 0b01111001, 0b1110,
0b01111001, 0b00111110,
],
),
Access::WriteRegisters(
0x00c,
vec![0b00]
),
]);
let mut delay = embedded_hal_mock::eh1::delay::NoopDelay::new();
let config = ConfigBuilder::new_lp5860();
let mut ledmatrix = Lp586x::new(&config, interface, &mut delay).unwrap();
ledmatrix
.set_dot_groups(&[
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group0,
DotGroup::Group1,
DotGroup::Group2,
DotGroup::Group2,
])
.unwrap();
ledmatrix.set_dot_groups(&[DotGroup::None]).unwrap();
ledmatrix.release().done();
}
}