|
| 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 | +} |
0 commit comments