use core::cell::RefCell;
use core::fmt::Debug;
use embedded_hal::digital::{OutputPin, StatefulOutputPin};
use embedded_hal::pwm::SetDutyCycle;
use crate::HalfH;
#[derive(Debug)]
pub struct L293x<A1, A2, A3, A4, EN12, EN34> {
a1: RefCell<A1>,
a2: RefCell<A2>,
a3: RefCell<A3>,
a4: RefCell<A4>,
en12: RefCell<EN12>,
en34: RefCell<EN34>,
}
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34> {
#[inline]
pub fn new(a1: A1, a2: A2, a3: A3, a4: A4, en12: EN12, en34: EN34) -> Self {
Self {
a1: RefCell::new(a1),
a2: RefCell::new(a2),
a3: RefCell::new(a3),
a4: RefCell::new(a4),
en12: RefCell::new(en12),
en34: RefCell::new(en34),
}
}
#[inline]
pub fn y1(&self) -> HalfH<A1, EN12> {
HalfH::new(&self.a1, &self.en12)
}
#[inline]
pub fn y2(&self) -> HalfH<A2, EN12> {
HalfH::new(&self.a2, &self.en12)
}
#[inline]
pub fn y3(&self) -> HalfH<A3, EN34> {
HalfH::new(&self.a3, &self.en34)
}
#[inline]
pub fn y4(&self) -> HalfH<A4, EN34> {
HalfH::new(&self.a4, &self.en34)
}
}
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
EN12: OutputPin,
{
pub fn enable_y1_and_y2(&mut self) -> Result<(), EN12::Error> {
self.en12.get_mut().set_high()
}
pub fn disable_y1_and_y2(&mut self) -> Result<(), EN12::Error> {
self.en12.get_mut().set_low()
}
}
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
EN34: OutputPin,
{
pub fn enable_y3_and_y4(&mut self) -> Result<(), EN34::Error> {
self.en34.get_mut().set_high()
}
pub fn disable_y3_and_y4(&mut self) -> Result<(), EN34::Error> {
self.en34.get_mut().set_low()
}
}
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
EN12: StatefulOutputPin,
{
pub fn y1_and_y2_enabled(&mut self) -> Result<bool, EN12::Error> {
self.en12.get_mut().is_set_high()
}
pub fn y1_and_y2_disabled(&mut self) -> Result<bool, EN12::Error> {
self.en12.get_mut().is_set_low()
}
}
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
EN34: StatefulOutputPin,
{
pub fn y3_and_y4_enabled(&mut self) -> Result<bool, EN34::Error> {
self.en34.get_mut().is_set_high()
}
pub fn y3_and_y4_disabled(&mut self) -> Result<bool, EN34::Error> {
self.en34.get_mut().is_set_low()
}
}
macro_rules! output_pin_impl {
($output:ident, $input:ident, $type_:ty) => {
paste::item! {
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
$type_: OutputPin,
{
#[doc = "Set the output " $output " high"]
#[doc = "This function sets the input of the output channel " $output]
pub fn [< set_ $output _high >](
&mut self
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_high()
}
#[doc = "Set the output " $output " low"]
#[doc = "This function sets the input of the output channel " $output]
pub fn [< set_ $output _low >](
&mut self
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_low()
}
#[doc = "Set the state of output " $output]
#[doc = $output "."]
pub fn [< set_ $output _state >](
&mut self,
state: embedded_hal::digital::PinState
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_state(state)
}
}
}
};
}
output_pin_impl!(y1, a1, A1);
output_pin_impl!(y2, a2, A2);
output_pin_impl!(y3, a3, A3);
output_pin_impl!(y4, a4, A4);
macro_rules! stateful_output_pin_impl {
($output:ident, $type_:ty, $enable_ty:ty) => {
paste::item! {
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
$type_: StatefulOutputPin,
$enable_ty: StatefulOutputPin,
{
#[doc = "Check if output " $output " is set high"]
pub fn [< is_ $output _set_high >](
&mut self
) -> Result<bool, <crate::half_h::HalfH<$type_, $enable_ty> as embedded_hal::digital::ErrorType>::Error> {
self.$output().is_set_high()
}
#[doc = "Check if output " $output " is set low"]
pub fn [< is_ $output _set_low >](
&mut self
) -> Result<bool, <crate::half_h::HalfH<$type_, $enable_ty> as embedded_hal::digital::ErrorType>::Error> {
self.$output().is_set_low()
}
#[doc = "Toggle the state of output " $output]
pub fn [< toggle_ $output >](
&mut self
) -> Result<(), <crate::half_h::HalfH<$type_, $enable_ty> as embedded_hal::digital::ErrorType>::Error> {
self.$output().toggle()
}
}
}
};
}
stateful_output_pin_impl!(y1, A1, EN12);
stateful_output_pin_impl!(y2, A2, EN12);
stateful_output_pin_impl!(y3, A3, EN34);
stateful_output_pin_impl!(y4, A4, EN34);
macro_rules! pwm_pin_impl {
($output:ident, $input:ident, $type_:ty) => {
paste::item! {
impl<A1, A2, A3, A4, EN12, EN34> L293x<A1, A2, A3, A4, EN12, EN34>
where
$type_: SetDutyCycle,
{
#[doc = "Get the max duty value of output " $output]
#[doc = "[L293x::set_" $output "_duty_cycle()]"]
#[doc = "let max_duty = l293x." $output "_max_duty_cycle();"]
#[doc = "l293x.set_" $output "_duty_cycle(max_duty).unwrap();"]
pub fn [< $output _max_duty_cycle >](&self) -> u16 {
self.$input.borrow().max_duty_cycle()
}
#[doc = "Set the duty cycle of output " $output]
#[doc = $output "."]
#[doc = "[L293x::" $output "_max_duty_cycle()] method."]
#[doc = "l293x.set_" $output "_duty_cycle(0).unwrap();"]
#[doc = "[L293x::" $output "_max_duty_cycle()] method:"]
#[doc = "let max_duty = l293x." $output "_max_duty_cycle();"]
#[doc = "l293x.set_" $output "_duty_cycle(max_duty).unwrap();"]
pub fn [< set_ $output _duty_cycle >](
&mut self, duty: u16
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_duty_cycle(duty)
}
#[doc = "Set the duty cycle of output " $output " by fraction."]
#[doc = $output "by a fraction defined using the given `num` and"]
#[doc = "l293x.set_" $output "_duty_cycle_fraction(input, max_value).unwrap();"]
pub fn [< set_ $output _duty_cycle_fraction >](
&mut self, num: u16, denom: u16
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_duty_cycle_fraction(num, denom)
}
#[doc = "Set the duty cycle of output " $output " by percent"]
#[doc = "This method sets the output " $output " active for the"]
pub fn [< set_ $output _duty_cycle_percent >](
&mut self, percent: u8,
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_duty_cycle_percent(percent)
}
#[doc = "Fully enable the output " $output]
#[doc = "This method set the PWM output channel " $output]
pub fn [< set_ $output _duty_cycle_fully_on >](
&mut self
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_duty_cycle_fully_on()
}
#[doc = "Fully disable the output " $output]
#[doc = "This method set the PWM output channel " $output]
pub fn [< set_ $output _duty_cycle_fully_off >](
&mut self
) -> Result<(), $type_::Error> {
self.$input.get_mut().set_duty_cycle_fully_off()
}
}
}
};
}
pwm_pin_impl!(y1, a1, A1);
pwm_pin_impl!(y2, a2, A2);
pwm_pin_impl!(y3, a3, A3);
pwm_pin_impl!(y4, a4, A4);
#[cfg(test)]
mod tests {
use coverage_helper::test;
use embedded_hal::digital::PinState;
use crate::mock::{DigitalError, DigitalPin, PwmPin};
use crate::pins::Vcc;
use crate::OutputStateError;
use super::*;
#[test]
fn test_enable12() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.enable_y1_and_y2().unwrap();
assert!(l293x.y1_and_y2_enabled().unwrap());
assert!(!l293x.y1_and_y2_disabled().unwrap());
l293x.disable_y1_and_y2().unwrap();
assert!(!l293x.y1_and_y2_enabled().unwrap());
assert!(l293x.y1_and_y2_disabled().unwrap());
}
#[test]
fn test_enable34() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.enable_y3_and_y4().unwrap();
assert!(l293x.y3_and_y4_enabled().unwrap());
assert!(!l293x.y3_and_y4_disabled().unwrap());
l293x.disable_y3_and_y4().unwrap();
assert!(!l293x.y3_and_y4_enabled().unwrap());
assert!(l293x.y3_and_y4_disabled().unwrap());
}
#[test]
fn test_enable12_fail() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.en12.get_mut().fail();
assert!(matches!(
l293x.is_y1_set_high().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y2_set_high().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y1_set_low().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y2_set_low().unwrap_err(),
OutputStateError::EnablePinError(..)
));
}
#[test]
fn test_enable34_fail() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.en34.get_mut().fail();
assert!(matches!(
l293x.is_y3_set_high().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y4_set_high().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y3_set_low().unwrap_err(),
OutputStateError::EnablePinError(..)
));
assert!(matches!(
l293x.is_y4_set_low().unwrap_err(),
OutputStateError::EnablePinError(..)
));
}
macro_rules! test_output {
($name:ident, $input:ident, $bname:ident) => {
paste::item! {
#[test]
fn [< test_ $name >]() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.[< set_ $name _low >]().unwrap();
l293x.[< disable_ $bname >]().unwrap();
assert!(matches!(l293x.[< is_ $name _set_high >](), Err(OutputStateError::NotEnabled)));
assert!(matches!(l293x.[< is_ $name _set_low >](), Err(OutputStateError::NotEnabled)));
l293x.[< set_ $name _high >]().unwrap();
l293x.[< disable_ $bname >]().unwrap();
assert!(matches!(l293x.[< is_ $name _set_high >](), Err(OutputStateError::NotEnabled)));
assert!(matches!(l293x.[< is_ $name _set_low >](), Err(OutputStateError::NotEnabled)));
l293x.[< set_ $name _low >]().unwrap();
l293x.[< enable_ $bname >]().unwrap();
assert!(!l293x.[< is_ $name _set_high >]().unwrap());
assert!(l293x.[< is_ $name _set_low >]().unwrap());
l293x.[< set_ $name _high >]().unwrap();
l293x.[< enable_ $bname >]().unwrap();
assert!(l293x.[< is_ $name _set_high >]().unwrap());
assert!(!l293x.[< is_ $name _set_low >]().unwrap());
}
#[test]
fn [< test_ $name _set_state >]() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.[< set_ $name _state >](PinState::Low).unwrap();
l293x.[< disable_ $bname >]().unwrap();
assert!(matches!(l293x.[< is_ $name _set_high >](), Err(OutputStateError::NotEnabled)));
assert!(matches!(l293x.[< is_ $name _set_low >](), Err(OutputStateError::NotEnabled)));
l293x.[< set_ $name _state >](PinState::High).unwrap();
l293x.[< disable_ $bname >]().unwrap();
assert!(matches!(l293x.[< is_ $name _set_high >](), Err(OutputStateError::NotEnabled)));
assert!(matches!(l293x.[< is_ $name _set_low >](), Err(OutputStateError::NotEnabled)));
l293x.[< set_ $name _state >](PinState::Low).unwrap();
l293x.[< enable_ $bname >]().unwrap();
assert!(!l293x.[< is_ $name _set_high >]().unwrap());
assert!(l293x.[< is_ $name _set_low >]().unwrap());
l293x.[< set_ $name _state >](PinState::High).unwrap();
l293x.[< enable_ $bname >]().unwrap();
assert!(l293x.[< is_ $name _set_high >]().unwrap());
assert!(!l293x.[< is_ $name _set_low >]().unwrap());
}
#[test]
fn [< test_toggle_ $name >]() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.[< set_ $name _state >](PinState::Low).unwrap();
l293x.[< enable_ $bname >]().unwrap();
let old_state = l293x.[< is_ $name _set_high >]().unwrap();
l293x.[< toggle_ $name >]().unwrap();
assert_ne!(l293x.[< is_ $name _set_high >]().unwrap(), old_state);
l293x.[< toggle_ $name >]().unwrap();
assert_eq!(l293x.[< is_ $name _set_high >]().unwrap(), old_state);
}
#[test]
fn [< test_ $name _pwm >]() {
let mut l293x = L293x::new(
PwmPin::new(),
PwmPin::new(),
PwmPin::new(),
PwmPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
let max_duty = l293x.[< $name _max_duty_cycle >]();
l293x.[< set_ $name _duty_cycle >](max_duty).unwrap();
assert_eq!(l293x.$input.borrow().get_duty_cycle(), max_duty);
l293x.[< set_ $name _duty_cycle_fraction >](1, 2).unwrap();
assert_eq!(l293x.$input.borrow().get_duty_cycle(), max_duty / 2);
l293x.[< set_ $name _duty_cycle_percent >](25).unwrap();
assert_eq!(l293x.$input.borrow().get_duty_cycle(), max_duty / 4);
l293x.[< set_ $name _duty_cycle_fully_on >]().unwrap();
assert_eq!(l293x.$input.borrow().get_duty_cycle(), max_duty);
l293x.[< set_ $name _duty_cycle_fully_off >]().unwrap();
assert_eq!(l293x.$input.borrow().get_duty_cycle(), 0);
}
}
};
}
test_output!(y1, a1, y1_and_y2);
test_output!(y2, a2, y1_and_y2);
test_output!(y3, a3, y3_and_y4);
test_output!(y4, a4, y3_and_y4);
macro_rules! test_fail {
($name:ident, $input:ident) => {
paste::item! {
#[test]
fn [< test_ $name _fail >]() {
let mut l293x = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
l293x.enable_y1_and_y2().unwrap();
l293x.enable_y3_and_y4().unwrap();
l293x.$input.get_mut().fail();
assert!(matches!(l293x.[< set_ $name _low >](), Err(DigitalError(..))));
assert!(matches!(l293x.[< set_ $name _high >](), Err(DigitalError(..))));
assert!(matches!(l293x.[< is_ $name _set_high >](), Err(OutputStateError::InputPinError(..))));
assert!(matches!(l293x.[< is_ $name _set_low >](), Err(OutputStateError::InputPinError(..))));
assert!(matches!(l293x.[< toggle_ $name >](), Err(OutputStateError::InputPinError(..))));
}
}
};
}
test_fail!(y1, a1);
test_fail!(y2, a2);
test_fail!(y3, a3);
test_fail!(y4, a4);
#[test]
fn test_partial_chip() {
let mut l293x_part = L293x::new(DigitalPin::new(), (), (), (), Vcc(), ());
l293x_part.set_y1_high().unwrap();
assert!(l293x_part.is_y1_set_high().unwrap())
}
#[test]
fn test_split() {
let split = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
let mut y1 = split.y1();
let mut y1_cpy = split.y1();
y1.enable().unwrap();
y1.set_high().unwrap();
assert!(y1_cpy.is_set_high().unwrap())
}
#[test]
fn test_cascading_l293() {
let split = L293x::new(
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
DigitalPin::new(),
);
let mut l293x = L293x::new(split.y1(), split.y2(), split.y3(), split.y4(), Vcc(), Vcc());
split.y1().enable().unwrap();
l293x.set_y1_high().unwrap();
assert!(split.y1().is_set_high().unwrap());
}
}