Skip to content

Commit 395c2eb

Browse files
committed
Added renamed file to repository.
(Forgot to add in the previous commit ).
1 parent 33b6475 commit 395c2eb

4 files changed

Lines changed: 645 additions & 0 deletions

File tree

src/i2s/i2s_slave_duplex.pio

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
;
2+
; RP2040 / RP2035 PIO program for the duplex I2S transfer in slave mode.
3+
;
4+
;
5+
; The I2S signal wave form is depicted below :
6+
;
7+
; ┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ 
8+
; BCLK └─┘ └─┘ └─┘ └─┘ └─
9+
; ┐
10+
; WS └─────────────────
11+
;
12+
; BIT SLOT │ 00│ 31│ 30 │ 29│ 
13+
;
14+
; The rising edge of the BCLK is the sampling edge of the I2S.
15+
; So we can call the period from a falling edge to the next falling
16+
; edge as a "BIT SLOT".
17+
;
18+
; Per definition of I2S standard, the first bit slot after the
19+
; falling edge of the WS is the last bit slot (00) of the previous
20+
; right channel.
21+
;
22+
; In the program below, we have 3 sync loop before the actual data
23+
; transfer. These sync loops guarantees the data integrity for
24+
; any timing of the program start.
25+
;
26+
; Pin mappings :
27+
; - Out pin 0 : Signal Data out.
28+
; - In pin 0 : Signal Data in.
29+
; - In pin 1 : BCLK in.
30+
; - In pin 2 : WS. Configured as JMP pin.
31+
32+
.pio_version 0 ; only requires PIO version 0
33+
34+
.program i2s_slave_duplex ; Program entry point.
35+
36+
; The following 3 loops avoid the trouble from the timing problem.
37+
; The beginning of the PIO state machine program is not synchronized
38+
; with the running I2S master signal.
39+
; As a result, the PIO program may start :
40+
; - At the middle of Left ch.
41+
; - At the right ch.
42+
; - At the boundary of left and right ch.
43+
; These timing may cause the LR swap or noise issue. Also other
44+
; unknown issue may cause.
45+
; To avoid this trouble, the following 3 loops guarantee the
46+
; data transfer start from the precisely beginning of left ch.
47+
48+
sync2_loop: ; Loop to wait for left ch
49+
wait 0 pin 1 ; Capture falling edge of BCLK(Sync to the driving edge of BCLK).
50+
wait 1 pin 1 ; Capture rising edge of BCLK(Sync to the Sampling edge of BCLK).
51+
jmp pin sync2_loop ; repeat while WS == 1.
52+
53+
sync1_loop: ; Loop to wait for right ch.
54+
wait 0 pin 1 ; Capture falling edge of BCLK(Sync to the driving edge of BCLK).
55+
wait 1 pin 1 ; Capture rising edge of BCLK(Sync to the Sampling edge of BCLK).
56+
jmp pin sync0_loop ; Exit loop if WS == 1
57+
jmp sync1_loop ; repeat while WS == 0.
58+
59+
sync0_loop: ; We are fully sync with Right channel here.
60+
wait 0 pin 1 ; Capture falling edge of BCLK(Sync to the driving edge of BCLK).
61+
wait 1 pin 1 ; Capture rising edge of BCLK(Sync to the Sampling edge of BCLK).
62+
jmp pin sync0_loop ; repeat while WS == 1.
63+
64+
65+
; In the following transfer loop, the pull instructions are
66+
; blocking operation. This allows small under under flow from the
67+
; main program. n the other words, delay of the TX data is
68+
; allowed ( alternatively, we may have some noise) and transfer
69+
; process recovers in next cycle.
70+
; The push instructions are not blocking because the
71+
; timing of these instruction is critical and non-recoverable.
72+
73+
.wrap_target ; Beginning of stereo data transfer. WS is 0 at here.
74+
; Transfer Left ch
75+
pull block ; We have empty OSR. Load left ch data.
76+
77+
left_loop: ; Loop to transfer left ch.
78+
wait 0 pin 1 ; Capture falling edge of BCLK(Sync to the driving edge of BCLK).
79+
out pins, 1 ; Output 1 bit to DAC.
80+
wait 1 pin 1 ; Capture rising edge of BCLK(Sync to the Sampling edge of BCLK).
81+
in pins, 1 ; Input 1 bit from ADC.
82+
jmp pin left_exit ; Exit loop if WS == 1
83+
jmp left_loop ; repeat while WS == 0.
84+
85+
left_exit: ; Tearing off the shift registers. WS is 1 at here.
86+
push noblock ; We have filled ISR. Push ISR to store right data.
87+
88+
; Transfer right ch
89+
pull block ; We have empty OSR. Load right ch data.
90+
91+
right_loop: ; Loop to transfer right ch
92+
wait 0 pin 1 ; Capture falling edge of BCLK(Sync to the driving edge of BCLK).
93+
out pins, 1 ; Output 1 bit to DAC.
94+
wait 1 pin 1 ; Capture rising edge of BCLK(Sync to the Sampling edge of BCLK).
95+
in pins, 1 ; Input 1 bit from ADC.
96+
jmp pin right_loop ; repeat while WS == 1.
97+
98+
; Tearing off the shift register.
99+
push noblock ; We have filled ISR. Push ISR to store left data.
100+
.wrap
101+
102+
; This program takes 5 cycles for the second half of a bit slot.
103+
; This happens when program wraps. So, for the entire clock cycle,
104+
; we need 10 cycle.
105+
;
106+
; For the 96kHz I2S stereo ( 32bit word), the BCLK is 6MHz. As a result,
107+
; we need abut 60MHz (96kHz * 2 * 32 *10) for PIO clock.

src/i2s/i2sslaveduplex.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#include "i2sslaveduplex.hpp"
2+
3+
#include "sdkwrapper.hpp"
4+
5+
#if __has_include(<hardware/pio.h>) // Is build in Pico Environment?
6+
extern "C" {
7+
#include "i2s_slave_duplex.pio.h" // Generated by PIO compiler.
8+
}
9+
#else
10+
static pio_program_t i2s_slave_duplex_program;
11+
#endif // __has_include(<hardware/pio.h>)
12+
13+
rpp_driver::I2sSlaveDuplex::I2sSlaveDuplex(::rpp_driver::SdkWrapper &sdk,
14+
PIO pio, uint pin_base)
15+
: sdk_(sdk),
16+
pio_(pio),
17+
sm_(sdk_.pio_claim_unused_sm(
18+
pio_, true)), // true mean required. assert if no room.
19+
pin_base_(pin_base) {}
20+
21+
rpp_driver::I2sSlaveDuplex::I2sSlaveDuplex(::rpp_driver::SdkWrapper &sdk,
22+
PIO pio, uint32_t sm, uint pin_base)
23+
: sdk_(sdk), pio_(pio), sm_(sm), pin_base_(pin_base) {
24+
sdk_.pio_sm_claim(pio_, sm_);
25+
}
26+
27+
rpp_driver::I2sSlaveDuplex::~I2sSlaveDuplex() {
28+
sdk_.pio_sm_unclaim(pio_, sm_);
29+
}
30+
31+
uint32_t rpp_driver::I2sSlaveDuplex::GetStateMachine() { return sm_; }
32+
33+
int32_t rpp_driver::I2sSlaveDuplex::GetFifoBlocking() {
34+
return sdk_.pio_sm_get_blocking(pio_, sm_);
35+
}
36+
37+
void rpp_driver::I2sSlaveDuplex::PutFifoBlocking(int32_t value) {
38+
sdk_.pio_sm_put_blocking(pio_, sm_, value);
39+
}
40+
41+
void rpp_driver::I2sSlaveDuplex::Start() {
42+
// Assign these pins for PIO by GPIO mux.
43+
sdk_.pio_gpio_init(pio_, pin_base_);
44+
sdk_.pio_gpio_init(pio_, pin_base_ + 1);
45+
sdk_.pio_gpio_init(pio_, pin_base_ + 2);
46+
sdk_.pio_gpio_init(pio_, pin_base_ + 3);
47+
48+
// Configure the physical direction of these pins inside PIO module.
49+
sdk_.pio_sm_set_consecutive_pindirs(pio_, sm_,
50+
pin_base_, // BASE GPIO pin number.
51+
1, // 1 pin for output.
52+
true); // true for output.
53+
sdk_.pio_sm_set_consecutive_pindirs(pio_, sm_,
54+
pin_base_ + 1, // BASE GPIO pin number.
55+
3, // 3 pins for input.
56+
false); // false for input.
57+
58+
// Load the PIO program to the memory.
59+
uint instruction_offset =
60+
sdk_.pio_add_program(pio_, &i2s_slave_duplex_program);
61+
62+
// Create a state machine configuration structure.
63+
// The duplex_i2s_program_get_default_config() is generated by the
64+
// PIO compiler. So, there is no wrapper.
65+
pio_sm_config config =
66+
i2s_slave_duplex_program_get_default_config(instruction_offset);
67+
68+
// Assign GPIO pins to IN/OUT/JMP instructions inside SM.
69+
sdk_.sm_config_set_out_pins(&config,
70+
pin_base_, // PIN_SDOUT
71+
1); // 1 pin for output.
72+
sdk_.sm_config_set_in_pin_base(&config,
73+
pin_base_ + 1); // PIN_SDIN.
74+
#if 0
75+
// sm_config_set_in_pin_count is buggy in pico sdk v2.0.0
76+
// See https://github.com/raspberrypi/pico-sdk/issues/1878 for details.
77+
sdk_.sm_config_set_in_pin_count(&config,
78+
2); // 2 pins for input.
79+
#endif
80+
sdk_.sm_config_set_jmp_pin(&config, pin_base_ + 3); // WS
81+
82+
// Set the PIO clock divider.
83+
// We need 96kHz stereo 32bit transfer. So the BCLCK is 96'000*2*32Hz.
84+
// The clock for the duplex I2S PIO program must be 10 times or grather
85+
// ( See the comment in the duplex_i2s.pio_ ).
86+
// To avoid the jitter, we calculate the division factor in
87+
// integer.
88+
float div = (sdk_.clock_get_hz(clk_sys) / (96'000 * 2 * 32 * 10));
89+
sdk_.sm_config_set_clkdiv(&config, div);
90+
91+
// Input shift register configuration.
92+
sdk_.sm_config_set_in_shift(&config,
93+
false, // false to left shift.
94+
false, // false to no auto push.
95+
32); // 32bit word.
96+
// Output shift register confiuration.
97+
sdk_.sm_config_set_out_shift(&config,
98+
false, // false to left shift.
99+
false, // false to no auto pull.
100+
32); // 32bit word.
101+
102+
// Configure SM. Flush FIFO, and ready to run the program.
103+
sdk_.pio_sm_init(pio_, sm_, instruction_offset, &config);
104+
105+
// Placing dummy data in FIFO to avoid the underflow at first TX.
106+
// This must be done after calling pio_sm_init() because that routine
107+
// flushes the FIFO.
108+
// We need 2 stereo samples.
109+
// The first stereo sample is for the initial transfer. At this transfer,
110+
// CPU program is waiting the received signal without the transmit data.
111+
// So, we need to fill dummy TX sample.
112+
// After the first data transfer, the main routine starts to process data.
113+
// And to get the transmit data ( as proessed data ), we need time which is
114+
// max 1 sample. Then, we need to fill another dummy.
115+
// That is why we need two stereo sample here inside TX FIFO.
116+
sdk_.pio_sm_put(pio_, sm_, 0); // Put left word for the first TX.
117+
sdk_.pio_sm_put(pio_, sm_, 0); // Put right word for the first TX.
118+
sdk_.pio_sm_put(pio_, sm_, 0); // Put left word for the second TX.
119+
sdk_.pio_sm_put(pio_, sm_, 0); // Put right word for the second TX.
120+
121+
// Start the state machine.
122+
sdk_.pio_sm_set_enabled(pio_, sm_, true);
123+
}
124+
125+
void rpp_driver::I2sSlaveDuplex::Stop() {
126+
// Stop state machine.
127+
sdk_.pio_sm_set_enabled(pio_, sm_, false);
128+
// Clean up FIFO for the next processing.
129+
sdk_.pio_sm_clear_fifos(pio_, sm_);
130+
// We don't unclaim the state machine.
131+
// The SM will be unclaimed by destructor.
132+
}

src/i2s/i2sslaveduplex.hpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#ifndef PICO_DRIVER_SRC_I2S_DUPLEXSLAVEI2S_HPP_
2+
#define PICO_DRIVER_SRC_I2S_DUPLEXSLAVEI2S_HPP_
3+
4+
#if __has_include(<hardware/pio.h>)
5+
#include "hardware/pio.h"
6+
#else
7+
// Alternate definition for unit test.
8+
#define i2s_slave_duplex_program_get_default_config(config) 1
9+
#define clk_sys 51
10+
#endif //__has_include(<hardware/i2c.h>)
11+
12+
#if __has_include(<gmock/gmock.h>)
13+
#include <gmock/gmock.h>
14+
#endif
15+
16+
#include "sdkwrapper.hpp"
17+
18+
namespace rpp_driver {
19+
/**
20+
* @brief Duplex Slave I2S controller class.
21+
* @details
22+
* This class support the duplex communication of the I2S on the PIO port of the
23+
* RP2040/2350 SoC MCU.
24+
*
25+
* The timing signal (BCLK and WS) of the I2S must be provided from the external
26+
* device. This class support up to 192kHz Fs if the MCU system clock is higher
27+
* than 120MHz.
28+
*
29+
* The I2S pins can be mapped on the GPIO. This mapping is based on the
30+
* pin_base parameter of the constructors.
31+
*
32+
* The pin_base parameter of the constructors is the first pin of 4 I2S signals.
33+
* The signals must be consecutive on the GPIO pins as like :
34+
* -# SDOUT
35+
* -# SDIN
36+
* -# BCLK
37+
* -# WS
38+
*
39+
* To start and stop the I2S transfer, call the Start() and the Stop() member
40+
* functions respectively.
41+
*
42+
* The audio sample in and out are through the GetFifoBlocking() and the
43+
* PutFifoBlocking() member function, respectively. These are blocking function.
44+
* That mean, program will wait until the data is ready, or FIFO has room for
45+
* data.
46+
*
47+
* This class assumes polling based data transfer instead of interrupt / DMA
48+
* based data transfer.
49+
*/
50+
class I2sSlaveDuplex {
51+
private:
52+
::rpp_driver::SdkWrapper &sdk_;
53+
PIO pio_;
54+
const uint32_t sm_; // State machine ( 0..3 )
55+
const uint pin_base_; // first GPIO pin number of the I2S signal.
56+
57+
public:
58+
I2sSlaveDuplex(/* args */) = delete;
59+
/**
60+
* @brief Construct a new Duplex Slave I 2 S object
61+
*
62+
* @param sdk SDK wrapper class injection.
63+
* @param pio PIO to use.
64+
* @param pin_base The GPIO pin number of SDOUT signal.
65+
* @details
66+
* The state machine number is not specified in this constructor.
67+
* Internally, the state machine will be allocate from the unused one.
68+
*
69+
*/
70+
I2sSlaveDuplex(::rpp_driver::SdkWrapper &sdk, PIO pio, uint pin_base);
71+
/**
72+
* @brief Construct a new Duplex Slave I 2 S object
73+
*
74+
* @param sdk SDK wrapper class injection.
75+
* @param pio PIO to use.
76+
* @param sm State machine to use.
77+
* @param pin_base The GPIO pin number of SDOUT signal.
78+
* @details
79+
*/
80+
I2sSlaveDuplex(::rpp_driver::SdkWrapper &sdk, PIO pio, uint32_t sm,
81+
uint pin_base);
82+
83+
/**
84+
* @brief Stop the state machine and make FIFO empty.
85+
*/
86+
~I2sSlaveDuplex();
87+
88+
/**
89+
* @brief Initialize the I2S port, and run.
90+
* @details
91+
* Assign the GPIO, configure them, load the PIO program, configure the state
92+
* machine and run.
93+
*/
94+
virtual void Start();
95+
/**
96+
* @brief Stop the I2S port and disable the PIO state machine in use.
97+
* @details
98+
* Make FIFOs empty.
99+
*
100+
*/
101+
virtual void Stop();
102+
/**
103+
* @brief Get the State Machine object
104+
*
105+
* @return uint32_t The number of the state machine which is claimed.
106+
* @details
107+
* This is convenient if the state machine is assigned internally.
108+
*/
109+
virtual uint32_t GetStateMachine();
110+
/**
111+
* @brief Get one audio data from RX FIFO.
112+
*
113+
* @return int32_t An audio data.
114+
* @details
115+
* To get one stere sample, call this member function twice. The left data
116+
* will be given and then, right data.
117+
*
118+
* This function is blocking. That mean, program will wait until the data has
119+
* been received.
120+
*/
121+
virtual int32_t GetFifoBlocking();
122+
/**
123+
* @brief Put one audio data to TX FIFO.
124+
*
125+
* @param value An audio data to send.
126+
* To put one stere sample, call this member function twice. The left data
127+
* must be put and then, right data.
128+
*
129+
*/
130+
virtual void PutFifoBlocking(int32_t value);
131+
};
132+
133+
#if __has_include(<gmock/gmock.h>)
134+
135+
class MockI2sSlaveDuplex : public I2sSlaveDuplex {
136+
public:
137+
MOCK_METHOD0(GetStateMachine, uint32_t(void));
138+
MOCK_METHOD0(Start, void(void));
139+
MOCK_METHOD0(Stop, void(void));
140+
MOCK_METHOD1(PutFifoBlocking, void(int32_t value));
141+
MOCK_METHOD0(GetFifoBlocking, int32_t());
142+
};
143+
#endif // __has_include(<gmock/gmock.h>)
144+
145+
} // namespace rpp_driver
146+
147+
#endif // PICO_DRIVER_SRC_I2S_DUPLEXSLAVEI2S_HPP_

0 commit comments

Comments
 (0)