Skip to main content

vs1003_pac/
lib.rs

1#![no_std]
2#![warn(missing_debug_implementations)]
3#![warn(missing_docs)]
4
5//! The low-level peripheral access definitions for VS1003 codec.
6//! Some of those definition may also work for other chips in the family, however
7//! there are some incompatible changes between them.
8//!
9//! Each method checks the DREQ pin and if it is low it will return [`Vs1003InterfaceError::Busy`] if so.
10//! Please note though, that the device may not lower this pin immediately after a successful transfer.
11//! This time doesn't seem well-specified, however on the forums they seem to recommend a 1us delay after
12//! completion of a command and before checking the DREQ pin.
13
14#[derive(Debug, Clone)]
15/// The necessary interfaces to communicate with a VS1003 chip.
16pub struct Vs1003Interface<TCsi, TDreq> {
17    csi: TCsi,
18    dreq: TDreq,
19}
20
21#[derive(thiserror::Error, Debug)]
22#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
23/// The error type that can be returned by various interface APIs
24pub enum Vs1003InterfaceError<ESpi, EDreq> {
25    /// The SPI peripheral has failed to compelte a transfer
26    #[error("SPI error")]
27    Spi(#[source] ESpi),
28    /// The Digital input failed to provide a value
29    #[error("Digital input error")]
30    Dreq(#[source] EDreq),
31    /// The DREQ signal is low and the command cannot be sent
32    #[error("VS1003 is busy (DREQ is low)")]
33    Busy,
34}
35
36impl<TCsi, TDreq> Vs1003Interface<TCsi, TDreq> {
37    /// Create a new interface.
38    ///
39    /// While you can pass anything here tou should pass appropriate HAL
40    /// stucts that implement necessary traits from [`embedded_hal`] and/or [`embedded_hal_async`].
41    pub const fn new(interface: TCsi, dreq: TDreq) -> Self {
42        Vs1003Interface {
43            csi: interface,
44            dreq,
45        }
46    }
47}
48
49impl<TCsi, TDreq> Vs1003Interface<TCsi, TDreq>
50where
51    TDreq: embedded_hal::digital::InputPin,
52{
53    /// Check if the device can accept commands or data
54    pub fn is_busy(&mut self) -> Result<bool, TDreq::Error> {
55        self.dreq.is_low()
56    }
57}
58
59impl<TCsi, TDreq> Vs1003Interface<TCsi, TDreq>
60where
61    TDreq: embedded_hal_async::digital::Wait,
62{
63    /// Wait until the device is ready to accept commands or data.
64    /// If the device is ready returns immediately
65    pub async fn wait_until_ready(&mut self) -> Result<(), TDreq::Error> {
66        self.dreq.wait_for_high().await
67    }
68}
69
70impl<TCsi, TDreq> device_driver::RegisterInterface for Vs1003Interface<TCsi, TDreq>
71where
72    TCsi: embedded_hal::spi::SpiDevice<u8>,
73    TDreq: embedded_hal::digital::InputPin,
74{
75    type Error = Vs1003InterfaceError<TCsi::Error, TDreq::Error>;
76    type AddressType = u8;
77
78    fn write_register(
79        &mut self,
80        address: Self::AddressType,
81        size_bits: u32,
82        data: &[u8],
83    ) -> Result<(), Self::Error> {
84        assert_eq!(size_bits, 16);
85        assert_eq!(data.len(), 2);
86
87        if !self.dreq.is_high().map_err(Vs1003InterfaceError::Dreq)? {
88            return Err(Vs1003InterfaceError::Busy);
89        }
90
91        let setup = [2u8, address, data[0], data[1]];
92
93        self.csi.write(&setup).map_err(Vs1003InterfaceError::Spi)
94    }
95
96    fn read_register(
97        &mut self,
98        address: Self::AddressType,
99        size_bits: u32,
100        data: &mut [u8],
101    ) -> Result<(), Self::Error> {
102        use embedded_hal::spi::Operation as Op;
103
104        assert_eq!(size_bits, 16);
105        assert_eq!(data.len(), 2);
106
107        if !self.dreq.is_high().map_err(Vs1003InterfaceError::Dreq)? {
108            return Err(Vs1003InterfaceError::Busy);
109        }
110
111        let setup = [3u8, address];
112        self.csi
113            .transaction(&mut [Op::Write(&setup), Op::Read(data)])
114            .map_err(Vs1003InterfaceError::Spi)
115    }
116}
117
118impl<TCsi, TDreq> device_driver::AsyncRegisterInterface for Vs1003Interface<TCsi, TDreq>
119where
120    TCsi: embedded_hal_async::spi::SpiDevice<u8>,
121    TDreq: embedded_hal::digital::InputPin,
122{
123    type Error = Vs1003InterfaceError<TCsi::Error, TDreq::Error>;
124    type AddressType = u8;
125
126    async fn write_register(
127        &mut self,
128        address: Self::AddressType,
129        size_bits: u32,
130        data: &[u8],
131    ) -> Result<(), Self::Error> {
132        assert_eq!(size_bits, 16);
133        assert_eq!(data.len(), 2);
134
135        if !self.dreq.is_high().map_err(Vs1003InterfaceError::Dreq)? {
136            return Err(Vs1003InterfaceError::Busy);
137        }
138
139        let setup = [2u8, address, data[0], data[1]];
140
141        self.csi
142            .write(&setup)
143            .await
144            .map_err(Vs1003InterfaceError::Spi)
145    }
146
147    async fn read_register(
148        &mut self,
149        address: Self::AddressType,
150        size_bits: u32,
151        data: &mut [u8],
152    ) -> Result<(), Self::Error> {
153        use embedded_hal::spi::Operation as Op;
154
155        assert_eq!(size_bits, 16);
156        assert_eq!(data.len(), 2);
157
158        if !self.dreq.is_high().map_err(Vs1003InterfaceError::Dreq)? {
159            return Err(Vs1003InterfaceError::Busy);
160        }
161
162        let setup = [3u8, address];
163        self.csi
164            .transaction(&mut [Op::Write(&setup), Op::Read(data)])
165            .await
166            .map_err(Vs1003InterfaceError::Spi)
167    }
168}
169
170impl<TCsi, TDreq> Vs1003<Vs1003Interface<TCsi, TDreq>>
171where
172    TDreq: embedded_hal::digital::InputPin,
173{
174    /// Check if the device is ready to accept commands or data
175    ///
176    /// Other functions may fail with [`Vs1003InterfaceError::Busy`] if this returns `Ok(false)`
177    pub fn is_busy(&mut self) -> Result<bool, TDreq::Error> {
178        self.interface.is_busy()
179    }
180}
181
182impl<TCsi, TDreq> Vs1003<Vs1003Interface<TCsi, TDreq>>
183where
184    TDreq: embedded_hal::digital::InputPin + embedded_hal_async::digital::Wait,
185{
186    /// Asynchronously waits until the device is ready to accept commands and data
187    pub async fn wait_until_ready(&mut self) -> Result<(), TDreq::Error> {
188        self.interface.dreq.wait_for_high().await
189    }
190}
191
192device_driver::create_device!(
193    device_name: Vs1003,
194    dsl: {
195        config {
196            type RegisterAddressType = u8;
197            type DefaultByteOrder = BE;
198            type DefmtFeature = "defmt-03";
199        }
200        /// Access any register to read or write a custom value.
201        ///
202        /// This is especially useful when uploading plugins as they are specified in
203        /// register address + value to write format.
204        register Raw {
205            const ADDRESS = 0x1;
206            const SIZE_BITS = 16;
207            const ALLOW_ADDRESS_OVERLAP = true;
208            const REPEAT = {
209                count: 16,
210                stride: 1,
211            };
212
213            /// The value
214            value: uint = 0..16,
215        },
216        /// The SCI_MODE register - controls various aspects of operation of the VS1003
217        register Mode {
218            const ADDRESS = 0x0;
219            const SIZE_BITS = 16;
220            const ALLOW_ADDRESS_OVERLAP = true;
221
222            /// If true invert left channel for differential output
223            differential: bool = 0,
224
225            /// Reserved - always set to zero.
226            reserved: bool = 1,
227
228            /// Set to true to initiate a software reset.
229            reset: bool = 2,
230
231            /// Jump out of WAV decoding
232            out_of_wav: bool = 3,
233
234            power_down: bool = 4,
235
236            /// Allow SDI tests
237            allow_tests: bool = 5,
238
239            /// Enable stream mode
240            stream_mode: bool = 6,
241
242            /// Reserved - always set to zero.
243            reserved_2: bool = 7,
244
245            /// Active edge for SDI interface
246            dclk_active_edge: uint as enum DclkEdge {
247                Rising = 0,
248                Falling = 1,
249            } = 8..=8,
250
251            /// The bit order for the SDI interface
252            sdi_bit_order: uint as enum SdiBitOrder {
253                MsbFirst = 0,
254                LsbFirst = 1,
255            } = 9..=9,
256
257            /// Whether to share xCS with xDCS (i.e. SCI & SDI interfaces).
258            ///
259            /// If true the xDCS signal is generated by inverting xCS.
260            sdi_share: bool = 10,
261
262            /// Whether to use VS10xx native mode.
263            sdi_new: bool = 11,
264
265            /// Set to true along with reset to start ADPCM recording session.
266            adpcm: bool = 12,
267
268            /// Enable or disable high-pass filter for recording.
269            adpcm_hp: bool = 13,
270
271            /// The recording input
272            adpcm_input: uint as enum AdpcmInput {
273                Microphone = 0,
274                LineIn = 1,
275            } = 14..=14,
276        },
277        /// The SCI_STATUS register
278        register Status {
279            const ADDRESS = 0x1;
280            const SIZE_BITS = 16;
281            const ALLOW_ADDRESS_OVERLAP = true;
282
283            /// The connected device
284            version: uint as enum Version {
285                Vs1001 = 0,
286                Vs1011 = 1,
287                Vs1002 = 2,
288                Vs1003 = 3,
289                Vs1053 = 5,
290                Vs1063 = 6,
291                Unknown = catch_all
292            } = 4..=7,
293
294            /// Controls the analog driver powerdown.
295            /// Normally controlled by the system firmware, however
296            /// can be set to 1 a few milliseconds before reset to reduce transients.
297            analog_driver_powerdown: bool = 3,
298
299            /// Controls internal analog powerdown.
300            /// This is meant for system firmware use only.
301            analog_internal_powerdown: bool = 2,
302
303            /// Analog volumen control.
304            /// This is meant for system firmware use only.
305            ///
306            /// 0 = -0 dB, 1 = -6 dB, 3 = -12 dB
307            analog_volume: uint = 0..=1,
308        },
309
310        /// Bass enhancer settings
311        register Bass {
312            const ADDRESS = 0x2;
313            const SIZE_BITS = 16;
314            const ALLOW_ADDRESS_OVERLAP = true;
315
316            /// Treble control in 1.5 dB steps.
317            /// Set to 0 to disable.
318            treble_amplitude: int = 12..=15,
319
320            /// Lower limit frequency in 1kHz steps.
321            /// ex. When set to 10 the DSP will enhance frequencies >=10kHz
322            treble_bottom_frequency: uint = 8..=11,
323
324            /// Bass enhancement in 1 dB steps.
325            /// Set to 0 to disable.
326            bass_amplitude:  uint = 4..=7,
327
328            /// Lower limit frequency in 10Hz steps.
329            /// Range: 2..=15
330            ///
331            /// Typically should be set to the lowest frequency that the audio
332            /// system can reproduce.
333            bass_bottom_frequency: uint = 0..=3,
334        },
335        /// Register responsible for controlling the clock settings
336        register Clockf {
337            const ADDRESS = 0x3;
338            const SIZE_BITS = 16;
339            const ALLOW_ADDRESS_OVERLAP = true;
340
341            /// CLKI multiplier.
342            ///
343            /// CLKI = XTALI * (1 + (multiplier/2)).
344            ///
345            /// I.e. 0 = 1.0x, 7 = 4.5x
346            multiplier: uint = 13..=15,
347
348            /// Maximum extra multiplier for WMA decoding in 0.5x steps.
349            ///
350            /// 0 - no extra multiplier, 3 - up to +1.5x
351            allowed_addition: uint = 11..=12,
352
353            /// Set XTALI frequency input.
354            ///
355            /// Set to:
356            ///
357            /// input_frequency = (XTALI - 8_000_000) / 4_000
358            ///
359            /// where XTALI - clock frequency in Hz.
360            /// Default value 0 is a synonym for the default 12.228 MHz
361            input_frequency: uint = 0..=10,
362        },
363        /// The current decode time in full seconds.
364        ///
365        /// The user may change the value of this register.
366        /// In that case the new value should be written twice.
367        register DecodeTime {
368            const ADDRESS = 0x4;
369            const SIZE_BITS = 16;
370            const ALLOW_ADDRESS_OVERLAP = true;
371
372            /// The current decode time in seconds.
373            value: uint = 0..16,
374        },
375        /// Contains information about the sample rate and number of channels.
376        ///
377        /// Can be written to overwrite the information.
378        register Audata {
379            const ADDRESS = 0x5;
380            const SIZE_BITS = 16;
381            const ALLOW_ADDRESS_OVERLAP = true;
382
383            /// If the sample rate is even: true = stereo, false = mono
384            /// If the sample rate is odd: true = mono, false = stereo
385            ///
386            /// This is due to a bug in the system firmware in VS1003b.
387            stereo: bool = 0,
388            sample_rate: uint = 1..16,
389        },
390        /// Wram is used to upload application programs and data to instruction and data RAMs.
391        /// The start address must be initialized by writing to WramAddr prior to the first write/read
392        /// of Wram. As 16 bits of data can be transferred with one Wram write/read, and the
393        /// instruction word is 32 bits long, two consecutive writes/reads are needed for each instruction
394        /// word. The byte order is big-endian (i.e. most significant words first). After each full-word
395        /// write/read, the internal pointer is autoincremented.
396        register Wram {
397            const ADDRESS = 0x6;
398            const SIZE_BITS = 16;
399            const ALLOW_ADDRESS_OVERLAP = true;
400
401            value: uint = 0..16,
402        },
403        /// WramAddr is used to set the program address for following Wram writes/reads.
404        /// Address offset of 0 is used for X, 0x4000 for Y, and 0x8000 for instruction memory. Peripheral
405        /// registers can also be accessed
406        register WramAddr {
407            type Access = WO;
408
409            const ADDRESS = 0x7;
410            const SIZE_BITS = 16;
411            const ALLOW_ADDRESS_OVERLAP = true;
412
413            /// The read/write pointer
414            value: uint = 0..16,
415        },
416        /// Value depends on decoded file type and running application.
417        /// Check the datasheet for details.
418        register Hdat0 {
419            type Access = RO;
420
421            const ADDRESS = 0x8;
422            const SIZE_BITS = 16;
423            const ALLOW_ADDRESS_OVERLAP = true;
424
425            /// The value
426            value: uint = 0..16,
427        },
428        /// Value depends on decoded file type and running application.
429        /// Check the datasheet for details.
430        register Hdat1 {
431            type Access = RO;
432
433            const ADDRESS = 0x9;
434            const SIZE_BITS = 16;
435            const ALLOW_ADDRESS_OVERLAP = true;
436
437            /// The value
438            value: uint = 0..16,
439        },
440
441        /// AiAddr indicates the start address of the application code written earlier with WramAddr
442        /// and Wram registers. If no application code is used, this register should not be initialized,
443        /// or it should be initialized to zero. For more details, see Application Notes for VS10XX.
444        register AiAddr {
445            const ADDRESS = 0xA;
446            const SIZE_BITS = 16;
447            const ALLOW_ADDRESS_OVERLAP = true;
448
449            /// The application start pointer.
450            value: uint = 0..16,
451        },
452        /// Volume control.
453        ///
454        /// Setting both channels to 255 will activate analog powerdown mode.
455        register Vol {
456            const ADDRESS = 0xB;
457            const SIZE_BITS = 16;
458            const ALLOW_ADDRESS_OVERLAP = true;
459
460            /// Right channel volume in -0.5 dB steps.
461            /// 0 = 0 dB, 254 = -127 dB
462            right: uint = 0..8,
463
464            /// Left channel volume in -0.5 dB steps.
465            /// 0 = 0 dB, 254 = -127 dB
466            left: uint = 8..16,
467        },
468        /// For communication with user's application on the VS1003
469        register AiCtrl {
470            const ADDRESS = 0xC;
471            const SIZE_BITS = 16;
472            const ALLOW_ADDRESS_OVERLAP = true;
473            const REPEAT = {
474                count: 4,
475                stride: 1,
476            };
477
478            /// The value
479            value: uint = 0..16,
480        },
481    }
482);