#![no_std]
#![deny(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
pub(crate) mod fmt;
use embedded_hal::{digital::OutputPin, i2c};
use embedded_hal::delay::DelayNs;
const CS43L22_ADDR: u8 = 74;
const CS43L22_REG_ID: u8 = 0x01;
const CS43L22_REG_POWER_CTL1: u8 = 0x02;
const CS43L22_REG_POWER_CTL2: u8 = 0x04;
const CS43L22_REG_CLOCKING_CTL: u8 = 0x05;
const CS43L22_REG_INTERFACE_CTL1: u8 = 0x06;
const CS43L22_REG_ANALOG_ZC_SR_SETT: u8 = 0x0A;
const CS43L22_REG_MISC_CTL: u8 = 0x0E;
const CS43L22_REG_PCMA_VOL: u8 = 0x1A;
const CS43L22_REG_PCMB_VOL: u8 = 0x1B;
const CS43L22_REG_BEEP_FREQ_ONTIME: u8 = 0x1C;
const CS43L22_REG_BEEP_VOL_OFFTIME: u8 = 0x1D;
const CS43L22_REG_BEEP_TONE_CFG: u8 = 0x1E;
const CS43L22_REG_TONE_CTL: u8 = 0x1F;
const CS43L22_REG_MASTER_A_VOL: u8 = 0x20;
const CS43L22_REG_MASTER_B_VOL: u8 = 0x21;
const CS43L22_REG_HP_A_VOL: u8 = 0x22;
const CS43L22_REG_HP_B_VOL: u8 = 0x23;
const CS43L22_REG_PCM_CH_SWAP: u8 = 0x26;
const CS43L22_REG_LIMIT_CTL1: u8 = 0x27;
#[derive(Debug)]
pub enum Error<I2CError> {
I2C(I2CError),
InvalidChipID,
}
pub type Result<T, I2CError> = core::result::Result<T, Error<I2CError>>;
impl<I2CError> From<I2CError> for Error<I2CError> {
fn from(value: I2CError) -> Self {
Self::I2C(value)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Config {
output_device: OutputDevice,
interface_format: InterfaceFormat,
word_length: WordLength,
treble_cutoff: TrebleCutoff,
treble_gain: ToneGain,
bass_cutoff: BassCutoff,
bass_gain: ToneGain,
tone_control: bool,
volume_a: u16,
volume_b: u16,
}
impl Config {
pub fn new() -> Self {
Self {
output_device: OutputDevice::Auto,
interface_format: InterfaceFormat::I2S,
word_length: WordLength::W32,
treble_cutoff: TrebleCutoff::_5kHz,
treble_gain: ToneGain::_0dB,
bass_cutoff: BassCutoff::_50Hz,
bass_gain: ToneGain::_0dB,
tone_control: false,
volume_a: 70,
volume_b: 70,
}
}
pub fn output_device(mut self, output_device: OutputDevice) -> Self {
self.output_device = output_device;
self
}
pub fn interface_format(mut self, interface_format: InterfaceFormat) -> Self {
self.interface_format = interface_format;
self
}
pub fn word_length(mut self, word_length: WordLength) -> Self {
self.word_length = word_length;
self
}
pub fn volume_a(mut self, volume: u16) -> Self {
self.volume_a = volume;
self
}
pub fn volume_b(mut self, volume: u16) -> Self {
self.volume_b = volume;
self
}
pub fn treble_cutoff(mut self, treble_cutoff: TrebleCutoff) -> Self {
self.treble_cutoff = treble_cutoff;
self
}
pub fn treble_gain(mut self, treble_gain: ToneGain) -> Self {
self.treble_gain = treble_gain;
self
}
pub fn bass_cutoff(mut self, bass_cutoff: BassCutoff) -> Self {
self.bass_cutoff = bass_cutoff;
self
}
pub fn bass_gain(mut self, bass_gain: ToneGain) -> Self {
self.bass_gain = bass_gain;
self
}
pub fn with_tone_control(mut self, tone_control: bool) -> Self {
self.tone_control = tone_control;
self
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
pub struct Cs43l22<I2C, RST, DL> {
pub i2c: I2C,
pub reset: RST,
pub delay: DL,
pub config: Config,
pub beep_config: BeepConfig,
running: bool,
}
impl<I2C, I2CError, RST, DL> Cs43l22<I2C, RST, DL>
where
I2C: i2c::I2c<u8, Error = I2CError>,
I2CError: i2c::Error,
RST: OutputPin,
DL: DelayNs,
{
pub fn new(
i2c: I2C,
reset: RST,
delay: DL,
config: Config,
beep_config: BeepConfig,
dsp_mode: bool,
) -> Result<Self, I2CError> {
let mut cs43l22 = Self {
i2c,
reset,
delay,
config,
beep_config,
running: false,
};
cs43l22.reset()?;
let mut chip_id = [0u8; 1];
cs43l22.i2c.write(CS43L22_ADDR, &[CS43L22_REG_ID])?;
cs43l22.i2c.read(CS43L22_ADDR, &mut chip_id)?;
if chip_id[0] & 0xF8 != 0xE0 {
return Err(Error::InvalidChipID);
}
trace!("Chip ID: {}", chip_id[0] & 0xF8);
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL1, 0x01])?;
cs43l22.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_POWER_CTL2, cs43l22.config.output_device.into()],
)?;
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_CLOCKING_CTL, 0x81])?;
let interface_ctl1 = cs43l22.config.word_length.value()
+ (cs43l22.config.interface_format.value() << 2)
+ ((dsp_mode as u8) << 3);
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_INTERFACE_CTL1, interface_ctl1])?;
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_ANALOG_ZC_SR_SETT, 0x00])?;
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_MISC_CTL, 0x04])?;
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_LIMIT_CTL1, 0x00])?;
let tone_cfg = cs43l22.config.tone_control as u8
| (cs43l22.config.treble_cutoff.value() << 3)
| (cs43l22.config.bass_cutoff.value() << 1);
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_TONE_CTL, tone_cfg])?;
let tone_ctl = cs43l22.config.bass_gain.value() | (cs43l22.config.treble_gain.value() << 4);
cs43l22
.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_TONE_CTL, tone_ctl])?;
Ok(cs43l22)
}
pub fn play(&mut self) -> Result<(), I2CError> {
if !self.running {
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_MISC_CTL, 0x06])?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL1, 0x9E])?;
self.running = true;
}
Ok(())
}
pub fn reset(&mut self) -> Result<(), I2CError> {
self.reset.set_low().ok();
self.delay.delay_ms(100);
self.reset.set_high().ok();
self.delay.delay_ms(100);
Ok(())
}
pub fn stop(&mut self) -> Result<(), I2CError> {
if self.running {
self.mute()?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_MISC_CTL, 0x04])?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL1, 0x9F])?;
self.running = false;
}
Ok(())
}
pub fn pause(&mut self) -> Result<(), I2CError> {
if self.running {
self.mute()?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL1, 0x01])?;
self.running = false;
}
Ok(())
}
pub fn resume(&mut self) -> Result<(), I2CError> {
if !self.running {
self.unmute()?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL1, 0x9E])?;
self.running = true;
}
Ok(())
}
pub fn mute(&mut self) -> Result<(), I2CError> {
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_POWER_CTL2, 0xFF])?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_HP_A_VOL, 0x01])?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_HP_B_VOL, 0x01])?;
Ok(())
}
pub fn unmute(&mut self) -> Result<(), I2CError> {
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_HP_A_VOL, 0x00])?;
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_HP_B_VOL, 0x00])?;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_POWER_CTL2, self.config.output_device.into()],
)?;
Ok(())
}
pub fn set_volume_a(&mut self, volume: u16) -> Result<(), I2CError> {
self.config.volume_a = volume;
self.i2c.write(
CS43L22_ADDR,
&[
CS43L22_REG_MASTER_A_VOL,
self.convert_volume(self.config.volume_a),
],
)?;
Ok(())
}
pub fn set_volume_b(&mut self, volume: u16) -> Result<(), I2CError> {
self.config.volume_b = volume;
self.i2c.write(
CS43L22_ADDR,
&[
CS43L22_REG_MASTER_B_VOL,
self.convert_volume(self.config.volume_b),
],
)?;
Ok(())
}
pub fn get_volume_a(&mut self) -> Result<u16, I2CError> {
Ok(self.config.volume_a)
}
pub fn get_volume_b(&mut self) -> Result<u16, I2CError> {
Ok(self.config.volume_b)
}
pub fn set_pcm_volume_a(&mut self, volume: u16) -> Result<(), I2CError> {
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMA_VOL, self.convert_pcm_volume(volume)],
)?;
Ok(())
}
pub fn set_pcm_volume_b(&mut self, volume: u16) -> Result<(), I2CError> {
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMB_VOL, self.convert_pcm_volume(volume)],
)?;
Ok(())
}
pub fn pcm_mute(&mut self) -> Result<(), I2CError> {
let mut current_volume_a = [0u8; 1];
let mut current_volume_b = [0u8; 1];
self.i2c
.write_read(CS43L22_ADDR, &[CS43L22_REG_PCMA_VOL], &mut current_volume_a)?;
self.i2c
.write_read(CS43L22_ADDR, &[CS43L22_REG_PCMB_VOL], &mut current_volume_b)?;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMA_VOL, current_volume_a[0] | 0x80],
)?;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMB_VOL, current_volume_b[0] | 0x80],
)?;
Ok(())
}
pub fn pcm_unmute(&mut self) -> Result<(), I2CError> {
let mut current_volume_a = [0u8; 1];
let mut current_volume_b = [0u8; 1];
self.i2c
.write_read(CS43L22_ADDR, &[CS43L22_REG_PCMA_VOL], &mut current_volume_a)?;
self.i2c
.write_read(CS43L22_ADDR, &[CS43L22_REG_PCMB_VOL], &mut current_volume_b)?;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMA_VOL, current_volume_a[0] & 0x7F],
)?;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCMB_VOL, current_volume_b[0] & 0x7F],
)?;
Ok(())
}
pub fn setup_beep(&mut self) -> Result<(), I2CError> {
let mut tone_cfg = [0u8; 1];
self.i2c
.write_read(CS43L22_ADDR, &[CS43L22_REG_BEEP_TONE_CFG], &mut tone_cfg)?;
tone_cfg[0] = tone_cfg[0] & 0x0F;
let frec_ontime_value =
self.beep_config.on_time.value() | (self.beep_config.pitch.value() << 4) + tone_cfg[0];
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_BEEP_FREQ_ONTIME, frec_ontime_value],
)?;
let vol_offtime_value = self.convert_beep_volume(self.beep_config.volume)
| (self.beep_config.off_time.value() << 5);
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_BEEP_VOL_OFFTIME, vol_offtime_value],
)?;
let tone_cfg_value =
(self.beep_config.mode.value() << 6) | ((!self.beep_config.mix as u8) << 5);
self.i2c
.write(CS43L22_ADDR, &[CS43L22_REG_BEEP_TONE_CFG, tone_cfg_value])?;
Ok(())
}
pub fn change_beep_settings(&mut self, beep_config: BeepConfig) -> Result<(), I2CError> {
self.beep_config = beep_config;
self.setup_beep()
}
pub fn set_pcm_mix(&mut self, mix: PCMmix) -> Result<(), I2CError> {
let pcm_channel_swap_val = mix.value() << 4;
self.i2c.write(
CS43L22_ADDR,
&[CS43L22_REG_PCM_CH_SWAP, pcm_channel_swap_val],
)?;
Ok(())
}
fn convert_volume(&self, volume: u16) -> u8 {
let mut volume = (volume * 255 / 100) as u8;
if volume > 0xE6 {
volume = volume - 0xE7;
} else {
volume = volume + 0x19;
}
volume
}
fn convert_pcm_volume(&self, volume: u16) -> u8 {
let mut volume = (volume * 114 / 100) as u8;
if volume > 0x67 {
volume = volume - 0x68;
} else {
volume = volume + 0x19;
}
volume
}
fn convert_beep_volume(&self, volume: u16) -> u8 {
let mut volume = (volume * 62 / 100) as u8;
if volume > 0x36 {
volume = volume - 0x37;
} else {
volume = volume + 0x07;
}
volume
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OutputDevice {
Speaker,
Headphone,
Both,
Auto,
}
impl OutputDevice {
fn value(self) -> u8 {
match self {
Self::Speaker => 0xFA,
Self::Headphone => 0xAF,
Self::Both => 0xAA,
Self::Auto => 0x05,
}
}
}
impl From<OutputDevice> for u8 {
fn from(device: OutputDevice) -> Self {
device.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterfaceFormat {
LeftJustified,
I2S,
RightJustified,
}
impl InterfaceFormat {
fn value(self) -> u8 {
match self {
Self::LeftJustified => 0x00,
Self::I2S => 0x01,
Self::RightJustified => 0x02,
}
}
}
impl From<InterfaceFormat> for u8 {
fn from(interface_format: InterfaceFormat) -> Self {
interface_format.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum WordLength {
W16,
W20,
W24,
W32,
}
impl WordLength {
fn value(self) -> u8 {
match self {
Self::W16 => 0x03,
Self::W20 => 0x02,
Self::W24 => 0x01,
Self::W32 => 0x00,
}
}
}
impl From<WordLength> for u8 {
fn from(word_length: WordLength) -> Self {
word_length.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BeepPitch {
C4,
C5,
D5,
E5,
F5,
G5,
A5,
B5,
C6,
D6,
E6,
F6,
G6,
A6,
B6,
C7,
}
impl BeepPitch {
fn value(self) -> u8 {
match self {
Self::C4 => 0b0000,
Self::C5 => 0b0001,
Self::D5 => 0b0010,
Self::E5 => 0b0011,
Self::F5 => 0b0100,
Self::G5 => 0b0101,
Self::A5 => 0b0110,
Self::B5 => 0b0111,
Self::C6 => 0b1000,
Self::D6 => 0b1001,
Self::E6 => 0b1010,
Self::F6 => 0b1011,
Self::G6 => 0b1100,
Self::A6 => 0b1101,
Self::B6 => 0b1110,
Self::C7 => 0b1111,
}
}
}
impl From<BeepPitch> for u8 {
fn from(pitch: BeepPitch) -> Self {
pitch.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BeepOnTime {
_86ms,
_430ms,
_780ms,
_1200ms,
_1500ms,
_1800ms,
_2200ms,
_2500ms,
_2800ms,
_3200ms,
_3500ms,
_3800ms,
_4200ms,
_4500ms,
_4800ms,
_5200ms,
}
impl BeepOnTime {
fn value(self) -> u8 {
match self {
Self::_86ms => 0b0000,
Self::_430ms => 0b0001,
Self::_780ms => 0b0010,
Self::_1200ms => 0b0011,
Self::_1500ms => 0b0100,
Self::_1800ms => 0b0101,
Self::_2200ms => 0b0110,
Self::_2500ms => 0b0111,
Self::_2800ms => 0b1000,
Self::_3200ms => 0b1001,
Self::_3500ms => 0b1010,
Self::_3800ms => 0b1011,
Self::_4200ms => 0b1100,
Self::_4500ms => 0b1101,
Self::_4800ms => 0b1110,
Self::_5200ms => 0b1111,
}
}
}
impl From<BeepOnTime> for u8 {
fn from(time: BeepOnTime) -> Self {
time.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BeepOffTime {
_1230ms,
_2580ms,
_3900ms,
_5220ms,
_6600ms,
_8050ms,
_9350ms,
_10800ms,
}
impl BeepOffTime {
fn value(self) -> u8 {
match self {
Self::_1230ms => 0b000,
Self::_2580ms => 0b001,
Self::_3900ms => 0b010,
Self::_5220ms => 0b011,
Self::_6600ms => 0b100,
Self::_8050ms => 0b101,
Self::_9350ms => 0b110,
Self::_10800ms => 0b111,
}
}
}
impl From<BeepOffTime> for u8 {
fn from(time: BeepOffTime) -> Self {
time.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BeepMode {
Off,
Single,
Multiple,
Continuous,
}
impl BeepMode {
fn value(self) -> u8 {
match self {
Self::Off => 0b00,
Self::Single => 0b01,
Self::Multiple => 0b10,
Self::Continuous => 0b11,
}
}
}
impl From<BeepMode> for u8 {
fn from(mode: BeepMode) -> Self {
mode.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BeepConfig {
pitch: BeepPitch,
on_time: BeepOnTime,
off_time: BeepOffTime,
mode: BeepMode,
mix: bool,
volume: u16,
}
impl BeepConfig {
pub fn new() -> Self {
Self {
pitch: BeepPitch::C4,
on_time: BeepOnTime::_86ms,
off_time: BeepOffTime::_1230ms,
mode: BeepMode::Off,
mix: false,
volume: 70,
}
}
pub fn pitch(mut self, pitch: BeepPitch) -> Self {
self.pitch = pitch;
self
}
pub fn on_time(mut self, on_time: BeepOnTime) -> Self {
self.on_time = on_time;
self
}
pub fn off_time(mut self, off_time: BeepOffTime) -> Self {
self.off_time = off_time;
self
}
pub fn volume(mut self, volume: u16) -> Self {
self.volume = volume;
self
}
pub fn mode(mut self, mode: BeepMode) -> Self {
self.mode = mode;
self
}
pub fn mix(mut self, mix: bool) -> Self {
self.mix = mix;
self
}
}
impl Default for BeepConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TrebleCutoff {
_5kHz,
_7kHz,
_10kHz,
_15kHz,
}
impl TrebleCutoff {
fn value(self) -> u8 {
match self {
Self::_5kHz => 0b00,
Self::_7kHz => 0b01,
Self::_10kHz => 0b10,
Self::_15kHz => 0b11,
}
}
}
impl From<TrebleCutoff> for u8 {
fn from(cutoff: TrebleCutoff) -> Self {
cutoff.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum BassCutoff {
_50Hz,
_100Hz,
_200Hz,
_250Hz,
}
impl BassCutoff {
fn value(self) -> u8 {
match self {
Self::_50Hz => 0b00,
Self::_100Hz => 0b01,
Self::_200Hz => 0b10,
Self::_250Hz => 0b11,
}
}
}
impl From<BassCutoff> for u8 {
fn from(cutoff: BassCutoff) -> Self {
cutoff.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ToneGain {
_12dB,
_10_5dB,
_9dB,
_7_5dB,
_6dB,
_4_5dB,
_3dB,
_1_5dB,
_0dB,
Min1_5dB,
Min3dB,
Min4_5dB,
Min6dB,
Min7_5dB,
Min9dB,
Min10_5dB,
}
impl ToneGain {
fn value(self) -> u8 {
match self {
Self::_12dB => 0b0000,
Self::_10_5dB => 0b0001,
Self::_9dB => 0b0010,
Self::_7_5dB => 0b0011,
Self::_6dB => 0b0100,
Self::_4_5dB => 0b0101,
Self::_3dB => 0b0110,
Self::_1_5dB => 0b0111,
Self::_0dB => 0b1000,
Self::Min1_5dB => 0b1001,
Self::Min3dB => 0b1010,
Self::Min4_5dB => 0b1011,
Self::Min6dB => 0b1100,
Self::Min7_5dB => 0b1101,
Self::Min9dB => 0b1110,
Self::Min10_5dB => 0b1111,
}
}
}
impl From<ToneGain> for u8 {
fn from(gain: ToneGain) -> Self {
gain.value()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PCMmix {
Left,
Half,
Right,
}
impl PCMmix {
fn value(self) -> u8 {
match self {
Self::Left => 0b00,
Self::Half => 0b01,
Self::Right => 0b11,
}
}
}
impl From<PCMmix> for u8 {
fn from(mix: PCMmix) -> Self {
mix.value()
}
}