Output I2S audio with PIO/DMA
Requires a MAX98357A amplifier and a 2W 8ohms speaker
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
; Transmit a mono or stereo I2S audio stream as stereo
|
||||
; This is 16 bits per sample; can be altered by modifying the "set" params,
|
||||
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
|
||||
;
|
||||
; Autopull must be enabled, with threshold set to 32.
|
||||
; Since I2S is MSB-first, shift direction should be to left.
|
||||
; Hence the format of the FIFO word is:
|
||||
;
|
||||
; | 31 : 16 | 15 : 0 |
|
||||
; | sample ws=0 | sample ws=1 |
|
||||
;
|
||||
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
|
||||
; Fractional divider will probably be needed to get correct bit clock period,
|
||||
; but for common syslck freqs this should still give a constant word select period.
|
||||
;
|
||||
; One output pin is used for the data output.
|
||||
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
|
||||
|
||||
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
|
||||
|
||||
.program audio_i2s
|
||||
.side_set 2
|
||||
|
||||
; /--- LRCLK
|
||||
; |/-- BCLK
|
||||
bitloop1: ; ||
|
||||
out pins, 1 side 0b10
|
||||
jmp x-- bitloop1 side 0b11
|
||||
out pins, 1 side 0b00
|
||||
set x, 14 side 0b01
|
||||
|
||||
bitloop0:
|
||||
out pins, 1 side 0b00
|
||||
jmp x-- bitloop0 side 0b01
|
||||
out pins, 1 side 0b10
|
||||
public entry_point:
|
||||
set x, 14 side 0b11
|
||||
|
||||
% c-sdk {
|
||||
|
||||
static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
|
||||
pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, 32);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
|
||||
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
|
||||
}
|
||||
|
||||
%}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Vincent Mistler
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "i2s.h"
|
||||
|
||||
i2s_config_t i2s_get_default_config(void) {
|
||||
i2s_config_t i2s_config = {
|
||||
.sample_freq = 44100,
|
||||
.channel_count = 2,
|
||||
.data_pin = 26,
|
||||
.clock_pin_base = 27,
|
||||
.pio = pio0,
|
||||
.sm = 0,
|
||||
.dma_channel = 0,
|
||||
.dma_buf = NULL,
|
||||
.dma_trans_count = 0,
|
||||
};
|
||||
|
||||
return i2s_config;
|
||||
}
|
||||
|
||||
void i2s_init(i2s_config_t *i2s_config) {
|
||||
uint8_t func=GPIO_FUNC_PIO0; // TODO: GPIO_FUNC_PIO0 for pio0 or GPIO_FUNC_PIO1 for pio1
|
||||
gpio_set_function(i2s_config->data_pin, GPIO_FUNC_PIO0);
|
||||
gpio_set_function(i2s_config->clock_pin_base, GPIO_FUNC_PIO0);
|
||||
gpio_set_function(i2s_config->clock_pin_base+1, GPIO_FUNC_PIO0);
|
||||
|
||||
i2s_config->sm = pio_claim_unused_sm(i2s_config->pio, true);
|
||||
|
||||
uint offset = pio_add_program(i2s_config->pio, &audio_i2s_program);
|
||||
|
||||
audio_i2s_program_init(i2s_config->pio, i2s_config->sm , offset, i2s_config->data_pin , i2s_config->clock_pin_base);
|
||||
|
||||
// Set PIO clock
|
||||
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
|
||||
uint32_t divider = system_clock_frequency * 4 / i2s_config->sample_freq; // avoid arithmetic overflow
|
||||
pio_sm_set_clkdiv_int_frac(i2s_config->pio, i2s_config->sm , divider >> 8u, divider & 0xffu);
|
||||
|
||||
pio_sm_set_enabled(i2s_config->pio, i2s_config->sm, false);
|
||||
|
||||
// Allocate memory for the DMA buffer
|
||||
i2s_config->dma_buf=malloc(i2s_config->dma_trans_count*sizeof(uint32_t));
|
||||
|
||||
// Direct Memory Access setup
|
||||
i2s_config->dma_channel = dma_claim_unused_channel(true);
|
||||
|
||||
dma_channel_config dma_config = dma_channel_get_default_config(i2s_config->dma_channel);
|
||||
channel_config_set_read_increment(&dma_config, true);
|
||||
channel_config_set_write_increment(&dma_config, false);
|
||||
channel_config_set_dreq(&dma_config, pio_get_dreq(i2s_config->pio, i2s_config->sm, true));
|
||||
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
|
||||
dma_channel_configure(i2s_config->dma_channel,
|
||||
&dma_config,
|
||||
&(i2s_config->pio->txf[i2s_config->sm]), // Destination pointer
|
||||
i2s_config->dma_buf, // Source pointer
|
||||
i2s_config->dma_trans_count, // Number of 32 bits words to transfer
|
||||
false // Start immediately
|
||||
);
|
||||
|
||||
pio_sm_set_enabled(i2s_config->pio, i2s_config->sm , true);
|
||||
}
|
||||
|
||||
void i2s_write(const i2s_config_t *i2s_config,const int16_t *samples,const size_t len) {
|
||||
for(size_t i=0;i<len;i++) {
|
||||
pio_sm_put_blocking(i2s_config->pio, i2s_config->sm, (uint32_t)samples[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Write samples to DMA buffer and initiate DMA transfer (non blocking)
|
||||
void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples) {
|
||||
// Wait the completion of the previous DMA transfer
|
||||
dma_channel_wait_for_finish_blocking(i2s_config->dma_channel);
|
||||
// Copy samples into the DMA buffer
|
||||
memcpy(i2s_config->dma_buf,samples,i2s_config->dma_trans_count*sizeof(int32_t));
|
||||
// Initiate the DMA transfer
|
||||
dma_channel_transfer_from_buffer_now(i2s_config->dma_channel,
|
||||
i2s_config->dma_buf,
|
||||
i2s_config->dma_trans_count);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Vincent Mistler
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <hardware/pio.h>
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/dma.h>
|
||||
#include "audio_i2s.pio.h"
|
||||
|
||||
typedef struct i2s_config
|
||||
{
|
||||
uint32_t sample_freq;
|
||||
uint16_t channel_count;
|
||||
uint8_t data_pin;
|
||||
uint8_t clock_pin_base;
|
||||
PIO pio;
|
||||
uint8_t sm;
|
||||
uint8_t dma_channel;
|
||||
uint16_t dma_trans_count;
|
||||
uint16_t *dma_buf;
|
||||
|
||||
} i2s_config_t;
|
||||
|
||||
|
||||
i2s_config_t i2s_get_default_config(void);
|
||||
void i2s_init(i2s_config_t *i2s_config);
|
||||
void i2s_write(const i2s_config_t *i2s_config,const int16_t *samples,const size_t len);
|
||||
void i2s_dma_write(i2s_config_t *i2s_config,const int16_t *samples);
|
||||
Reference in New Issue
Block a user