Skip to content

Instantly share code, notes, and snippets.

@jonbro
Last active May 23, 2021 01:25
Show Gist options
  • Save jonbro/aa8fd7a4913e2392f142814cb9929426 to your computer and use it in GitHub Desktop.
Save jonbro/aa8fd7a4913e2392f142814cb9929426 to your computer and use it in GitHub Desktop.
driver for tlv320aic3204 for pi pico
;
; 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 input_output_copy_i2s
.side_set 2
; /--- LRCLK
; |/-- BCLK
bitloop1: ; ||
out pins, 1 side 0b10 [1]
in pins, 1 side 0b10
jmp x-- bitloop1 side 0b11
out pins, 1 side 0b00 [1]
in pins, 1 side 0b00
set x, 14 side 0b01
bitloop0:
out pins, 1 side 0b00 [1]
in pins, 1 side 0b00
jmp x-- bitloop0 side 0b01
out pins, 1 side 0b10 [1]
in pins, 1 side 0b10
public entry_point:
set x, 14 side 0b11
% c-sdk {
static inline void input_output_copy_i2s_program_init(PIO pio, uint sm, uint offset, uint data_out_pin, uint data_in_pin, uint clock_pin_base) {
pio_gpio_init(pio, data_in_pin);
pio_gpio_init(pio, clock_pin_base);
pio_gpio_init(pio, clock_pin_base+1);
pio_gpio_init(pio, data_out_pin);
gpio_pull_down(data_in_pin);
pio_sm_config sm_config = input_output_copy_i2s_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_out_pin, 1);
sm_config_set_in_pins(&sm_config, data_in_pin);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
sm_config_set_in_shift(&sm_config, false, true, 32);
pio_sm_init(pio, sm, offset, &sm_config);
uint pin_mask = (1u << (data_in_pin)) | (1u << data_out_pin) | (3u << clock_pin_base);
//uint pin_mask = (1u << (data_in_pin)) | (3u << clock_pin_base);
uint pin_dirs = (1u << data_out_pin) | (3u << clock_pin_base);
//uint pin_dirs = (3u << clock_pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, pin_dirs, pin_mask);
pio_sm_set_pins(pio, sm, 0); // clear pins
pio_sm_exec(pio, sm, pio_encode_jmp(offset + input_output_copy_i2s_offset_entry_point));
}
%}
/*
.data_pin = I2S_DATA_PIN,
.clock_pin_base = I2S_BCLK_PIN,
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/pio.h"
#include <math.h>
#include "hardware/clocks.h"
#include "hardware/structs/clocks.h"
#include "input_output_copy_i2s.pio.h"
#define POCKETMOD_IMPLEMENTATION
#include "pocketmod.h"
//#include "sundown.h"
#include "bananasplit.h"
#if USE_AUDIO_I2S
#include "pico/audio_i2s.h"
#elif USE_AUDIO_PWM
#include "pico/audio_pwm.h"
#elif USE_AUDIO_SPDIF
#include "pico/audio_spdif.h"
#endif
// I2C defines
// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.
// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments
#define I2C_PORT i2c1
#define I2C_SDA 26
#define I2C_SCL 27
#define TLV_RESET_PIN 21
#define MCLK_OUTPUT_PIN 16
#define TLV_I2C_ADDR 0x18
#define TLV_REG_PAGESELECT 0
#define TLV_REG_LDOCONTROL 2
#define TLV_REG_RESET 1
#define TLV_REG_CLK_MULTIPLEX 0x04
#define SAMPLES_PER_BUFFER 512
#define I2S_DATA_PIN 19
#define I2S_BCLK_PIN 17
#define BLINK_PIN_LED 25
#define SINE_WAVE_TABLE_LEN 2048
static int16_t sine_wave_table[SINE_WAVE_TABLE_LEN];
struct audio_buffer_pool *init_audio() {
static audio_format_t audio_format = {
.sample_freq = 44100,
.format = AUDIO_BUFFER_FORMAT_PCM_S16,
.channel_count = 2
};
static struct audio_buffer_format producer_format = {
.format = &audio_format,
.sample_stride = 4
};
struct audio_buffer_pool *producer_pool = audio_new_producer_pool(&producer_format, 3,
SAMPLES_PER_BUFFER*2); // todo correct size
bool __unused ok;
const struct audio_format *output_format;
struct audio_i2s_config config = {
/*
.data_pin = PICO_AUDIO_I2S_DATA_PIN,
.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE,
*/
.data_pin = I2S_DATA_PIN,
.clock_pin_base = I2S_BCLK_PIN,
.dma_channel = 0,
.pio_sm = 0,
};
output_format = audio_i2s_setup(&audio_format, &config);
if (!output_format) {
panic("PicoAudio: Unable to open audio device.\n");
}
ok = audio_i2s_connect(producer_pool);
assert(ok);
audio_i2s_set_enabled(true);
return producer_pool;
}
uint8_t currentPage = -1;
bool writeRegister(uint8_t reg, uint8_t val)
{
uint8_t data[2];
data[0] = reg;
data[1] = val;
int attempt=0;
while (1) {
attempt++;
int ret = i2c_write_blocking(I2C_PORT, TLV_I2C_ADDR, data, 2, false);
if (ret == 2) {
printf("TLV320 write ok, %d tries\n", attempt);
return true;
}
if (attempt >= 12) {
printf("TLV320 write failed, %d tries\n", attempt);
return false;
}
sleep_us(80);
}
}
bool readRegister(uint8_t reg)
{
int attempt=0;
while (1) {
attempt++;
int ret = i2c_write_blocking(I2C_PORT, TLV_I2C_ADDR, &reg, 1, true);
sleep_ms(5);
if (ret == 1) {
int ret;
uint8_t rxdata;
attempt = 0;
ret = i2c_read_blocking(I2C_PORT, TLV_I2C_ADDR, &rxdata, 1, false);
if(ret == 1)
{
printf("reg: %x val: %x\n", reg, rxdata);
return true;
}
else
{
printf("failed read %i\n", ret);
return false;
}
}
if (attempt >= 12) {
printf("TLV320 read failed, %d tries\n", attempt);
return false;
}
sleep_us(80);
}
}
bool write(uint8_t page, uint8_t reg, uint8_t val)
{
if(currentPage != page)
{
if(!writeRegister(0, page))
{
printf("failed to go to page %i\n", page);
return false;
}
currentPage = page;
}
return writeRegister(reg, val);
}
static float clip(float value)
{
value = value < -1.0f ? -1.0f : value;
value = value > +1.0f ? +1.0f : value;
return value;
}
int dma_chan_input;
int dma_chan_output;
uint sm;
uint32_t capture_buf[256];
uint32_t silence_buf[256];
bool flipFlop = false;
int dmacount = 0;
int inputBufCount = 0;
int inputBufOffset = 0;
void dma_input_handler() {
dma_hw->ints0 = 1u << dma_chan_input;
uint32_t* bufWrite = capture_buf;
uint32_t* readAddr = capture_buf;
if(flipFlop)
{
inputBufOffset = 1;
flipFlop = false;
readAddr+=128;
}
else
{
inputBufOffset = 0;
bufWrite+=128;
flipFlop = true;
}
inputBufCount++;
dma_channel_set_write_addr(dma_chan_input, bufWrite, true);
}
bool hi = false;
uint32_t output_buf[256];
uint16_t out_count = 0;
int flipflopout = 0;
uint16_t halffourfourty = 655;
void dma_output_handler() {
dma_hw->ints1 = 1u << dma_chan_output;
/*
uint32_t* output = output_buf+(flipflopout)*128;
dma_channel_set_read_addr(dma_chan_output, output, true);
flipflopout = (flipflopout+1)%2;
output = output_buf+(flipflopout)*128;
// fill the other buffer
for(int i=0;i<128;i++)
{
uint16_t* chan = (uint16_t*)(output+i);
chan[0] = out_count;
chan[1] = out_count;
out_count+=halffourfourty;
}
*/
// this is where we would check to see if there is an input buffer ready to point at
if(inputBufCount>0)
{
if(inputBufOffset==0)
{
dma_channel_set_read_addr(dma_chan_output, capture_buf, true);
}
else
{
dma_channel_set_read_addr(dma_chan_output, capture_buf+128, true);
}
inputBufCount--;
}
else
{
dma_channel_set_read_addr(dma_chan_output, silence_buf, true);
}
/**/
}
int main()
{
set_sys_clock_khz(220000, true);
stdio_init_all();
// fill the silence buffer so we get something out
for(int i=0;i<128;i++)
{
uint16_t* chan = (uint16_t*)(silence_buf+i);
chan[0] = 0;
chan[1] = 0;
//chan[0] = i<<9;
//chan[1] = i<<9;
}
// I2C Initialisation. Using it at 100Khz.
i2c_init(I2C_PORT, 100*1000);
//struct audio_buffer_pool *ap = init_audio();
sleep_ms(2000);
gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
gpio_pull_up(I2C_SDA);
gpio_pull_up(I2C_SCL);
gpio_init(TLV_RESET_PIN);
gpio_set_dir(TLV_RESET_PIN, GPIO_OUT);
// hardware reset
gpio_put(TLV_RESET_PIN, 1);
sleep_ms(250);
gpio_put(TLV_RESET_PIN, 0);
sleep_ms(250);
gpio_put(TLV_RESET_PIN, 1);
sleep_ms(250);
/**/
PIO pio = pio1;
uint offset = pio_add_program(pio, &input_output_copy_i2s_program);
printf("loaded program at offset: %i\n", offset);
sm = pio_claim_unused_sm(pio, true);
printf("claimed sm: %i\n", sm); //I2S_DATA_PIN
int sample_freq = 44100;
printf("setting pio freq %d\n", (int) sample_freq);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
assert(system_clock_frequency < 0x40000000);
uint32_t divider = system_clock_frequency * 2 / sample_freq; // avoid arithmetic overflow
printf("System clock at %u, I2S clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider);
assert(divider < 0x1000000);
input_output_copy_i2s_program_init(pio, sm, offset, I2S_DATA_PIN, I2S_DATA_PIN+1, I2S_BCLK_PIN);
pio_sm_set_enabled(pio, sm, true);
pio_sm_set_clkdiv_int_frac(pio, sm, divider >> 8u, divider & 0xffu);
// just use a dma to pull the data out. Whee!
dma_chan_input = dma_claim_unused_channel(true);
dma_channel_config c = dma_channel_get_default_config(dma_chan_input);
channel_config_set_read_increment(&c,false);
// increment on write
channel_config_set_write_increment(&c,true);
channel_config_set_dreq(&c,pio_get_dreq(pio,sm,false));
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
dma_channel_configure(
dma_chan_input,
&c,
capture_buf, // Destination pointer
&pio->rxf[sm], // Source pointer
128, // Number of transfers
true// Start immediately
);
// Tell the DMA to raise IRQ line 0 when the channel finishes a block
dma_channel_set_irq0_enabled(dma_chan_input, true);
// Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
irq_set_exclusive_handler(DMA_IRQ_0, dma_input_handler);
irq_set_enabled(DMA_IRQ_0, true);
// need another dma channel for output
dma_chan_output = dma_claim_unused_channel(true);
dma_channel_config cc = dma_channel_get_default_config(dma_chan_output);
// increment on read, but not on write
channel_config_set_read_increment(&cc,true);
channel_config_set_write_increment(&cc,false);
channel_config_set_dreq(&cc,pio_get_dreq(pio,sm,true));
channel_config_set_transfer_data_size(&cc, DMA_SIZE_32);
dma_channel_configure(
dma_chan_output,
&cc,
&pio->txf[sm], // Destination pointer
silence_buf, // Source pointer
128, // Number of transfers
true// Start immediately
);
dma_channel_set_irq1_enabled(dma_chan_output, true);
// Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
irq_set_exclusive_handler(DMA_IRQ_1, dma_output_handler);
irq_set_enabled(DMA_IRQ_1, true);
// write initial values into the output, so we don't block
//pio_sm_put(pio, sm, 0);
// reset
write(0, 0x01, 0x01);
// configure pll dividers
// our bitclock runs at 32x(sample_freq)
// so with 44100 we use the following values
// pllp 1 pllr 3 pllj 20 plld 0 MADC 3 NADC 5 AOSR 128 MDAC 3 NDAC 5 DOSR 128
// 44100*32*3*20.0
// use bitclock for pll input, pll for clock input
write(0, 0x04, 0x07);
// pll p & r & powerup
write(0, 0x05, (0x01<<7)|(0x01<<4)|0x03);
// pll j
write(0, 0x06, 0x14);
// ndac &p powerup
write(0, 0x0b, 0x80|0x05);
// mdac & powerup
write(0, 0x0c, 0x80|0x03);
// adc clocks should be driven by dac clocks, so no power necessary, but need to be set correctly
// but you still need to set them correctly
write(0, 0x12, 0x05);
write(0, 0x13, 0x03);
// dosr 64
write(0, 0x0d, 0x00);
write(0, 0x0e, 0x40);
// adc osr 64
write(0, 0x14, 0x40);
// Select ADC PRB_R7
write(0, 0x3d, 0x07);
// word length
// processing block? default doesn't have the beep generator :(
// printf("setup clocks\n");
// sleep_ms(1000);
// printf("one second to power\n");
// sleep_ms(1000);
// printf("power\n");
// power enable
// disable internal avdd prior to powering up internal LDO
write(1, 0x01, 0x08);
// enable analog power blocks
// common mode voltage
//
// set ref to slow powerup
write(1, 0x7b, 0x00);
// HP softstepping settingsfor optimal pop performance at power up
// Rpopusedis 6k withN = 6 and softstep= 20usec.Thisshouldworkwith47uFcoupling
// capacitor.Can try N=5, 6 or 7 time constants as well.Trade-offdelay vs “pop” sound.
write(1, 0x14, 0x25);
// enable LDO
write(1, 0x02, 0x01);
// setup headphones to be powered by ldo
// and set LDO input to 1.8-3.3
write(1, 0x0a, 0x0B);
// Set MicPGA startup delay to 3.1ms
write(1, 0x47, 0x32);
/**/
// disable micbias
write(1, 0x33, 0);
// route IN2L to MicPGAL positive with 20k input impedance
write(1, 0x34, 0x20);
// route Commonmode to MicPGAL neg with 20k input impedance
write(1, 0x36, 0x80);
// route IN2R to MicPGAR positive with 20k input impedance
write(1, 0x37, 0x20);
// route Commonmode to MicPGAR neg with 20k input impedance
write(1, 0x39, 0x80);
// might need 0x3b / 0x3c gain control for MICPGA - leaving it at 0 gain for now
write(1, 0x3b, 0x0c);
write(1, 0x3c, 0x0c);
/*
write(1, 0x34, 0x80);
write(1, 0x36, 0x80);
write(1, 0x37, 0x80);
write(1, 0x39, 0x80);
write(1, 0x3b, 0x0c);
write(1, 0x3c, 0x0c);
*/
// analog bypass & dac routed to headphones
// write(1, 0x0c, 0x0a);
// write(1, 0x0d, 0x0a);
// dac & mixer to headphones
write(1, 0x0c, 0x0a);
write(1, 0x0d, 0x0a);
// route dac only to headphones
write(1, 0x0c, 0x08);
write(1, 0x0d, 0x08);
write(1, 0x03, 0x00);
write(1, 0x04, 0x00);
// Set the ADC PTM Mode to PTM_R1
write(1, 0x3d, 0xff);
// Set the HPL gainto 0dBw 30
// set gain to whatever the heck this is?
write(1, 0x10, 0x00);
write(1, 0x11, 0x00);
// power up headphones & mixer amp
write(1, 0x09, 0x33);
// Powerup the Left and Right DAC Channels
write(0, 0x3f, 0xd6);
// Unmutethe DAC digital volume control
write(0, 0x40, 0x00);
sleep_ms(200);
printf("powering up adc, lets go\n");
sleep_ms(200);
printf("powering up adc, now\n");
// power up ADC
write(0, 0x51, 0xc0);
write(0, 0x52, 0x00);
// Waitfor 1 sec for softsteppingto takeeffect
sleep_ms(1000);
/*
uint16_t counter = 0;
uint32_t step = 0x50000;
uint32_t pos = 0;
uint32_t pos_max = 0x10000 * SINE_WAVE_TABLE_LEN;
uint vol = 128;
for (int i = 0; i < SINE_WAVE_TABLE_LEN; i++) {
sine_wave_table[i] = 32767 * cosf(i * 2 * (float) (M_PI / SINE_WAVE_TABLE_LEN));
}
pocketmod_context pm_c;
pocketmod_init(&pm_c, bananasplit, 19035*16, 44100);
float renderTarget[SAMPLES_PER_BUFFER*16];
while(true)
{
struct audio_buffer *buffer = take_audio_buffer(ap, true);
int16_t *samples = (int16_t *) buffer->buffer->bytes;
// 4 bytes per channel * 2 channels
pocketmod_render(&pm_c, renderTarget, buffer->max_sample_count*2*4);
for (uint i = 0; i < buffer->max_sample_count*2; i++) {
samples[i] = 0x7fff*clip(renderTarget[i]);
samples[i] = 0x7fff;
}
// printf("sample value: %i\n", samples[0]);
buffer->sample_count = buffer->max_sample_count;
give_audio_buffer(ap, buffer);
}
*/
gpio_init(BLINK_PIN_LED);
gpio_set_dir(BLINK_PIN_LED, GPIO_OUT);
int count = 0;
while(true)
{
sleep_ms(10);
if(count++ > 50)
{
//gpio_put(BLINK_PIN_LED, hi);
hi = hi?false:true;
count = 0;
}
}
/**/
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment