|
| 1 | +/* |
| 2 | + * Example for how to use I2C and I2S with libopencm3. |
| 3 | + * This is intended for the STM32F411-Discovery demoboard. |
| 4 | + * |
| 5 | + * The device plays a 1kHz sine through the audio jack. |
| 6 | + */ |
| 7 | + |
| 8 | +/* Compile time option to use DMA to feed audio to the I2S. |
| 9 | + * Without this, the audio is sent by polling the I2S peripheral's |
| 10 | + * TXE bit */ |
| 11 | +#define USE_DMA |
| 12 | + |
| 13 | + |
| 14 | +#include <libopencm3/stm32/gpio.h> |
| 15 | +#include <libopencm3/stm32/i2c.h> |
| 16 | +#include <libopencm3/stm32/rcc.h> |
| 17 | +#include <libopencm3/stm32/spi.h> |
| 18 | + |
| 19 | +#ifdef USE_DMA |
| 20 | +#include <libopencm3/cm3/nvic.h> |
| 21 | +#include <libopencm3/stm32/dma.h> |
| 22 | +#endif |
| 23 | + |
| 24 | + |
| 25 | +/* Test audio: 8 points of a sine. |
| 26 | + * At Fs=8kHz, this means a 1kHz audio sine |
| 27 | + * (the other channel is mute) |
| 28 | + */ |
| 29 | +#define VOL 0x0020 |
| 30 | +#define D16(x) ((int16_t)(x*VOL) ) |
| 31 | +int16_t audio[16]= |
| 32 | +{ D16(0), 0, |
| 33 | + D16(0.70711), 0, |
| 34 | + D16(1), 0, |
| 35 | + D16(0.70711), 0, |
| 36 | + D16(0), 0, |
| 37 | + D16(-0.70711),0, |
| 38 | + D16(-0.9999), 0, |
| 39 | + D16(-0.707), 0 }; |
| 40 | + |
| 41 | + |
| 42 | +static void write_i2c_to_audiochip( uint8_t reg, uint8_t contents) |
| 43 | +{ |
| 44 | + uint8_t packet[2]; |
| 45 | + packet[0] = reg; |
| 46 | + packet[1] = contents; |
| 47 | + /* STM32F411 discovery user's manual gives device address with R/W bit, |
| 48 | + * libopencm3 wants the address without it. */ |
| 49 | + uint8_t address = (0x94)>>1; |
| 50 | + |
| 51 | + i2c_transfer7(I2C1, address, packet, 2, NULL, 0); |
| 52 | +} |
| 53 | + |
| 54 | +#ifdef USE_DMA |
| 55 | +/* Interrupt service routine for the DMA. |
| 56 | + * The name is "magic" and suffices as installation hook into libopencm3. */ |
| 57 | +void dma1_stream5_isr(void) |
| 58 | +{ |
| 59 | + gpio_toggle(GPIOD, GPIO12); |
| 60 | + |
| 61 | + /* Clear the 'transfer complete' interrupt, or execution would jump right back to this ISR. */ |
| 62 | + if (dma_get_interrupt_flag(DMA1, DMA_STREAM5, DMA_TCIF)) { |
| 63 | + dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF); |
| 64 | + } |
| 65 | +} |
| 66 | +#endif |
| 67 | + |
| 68 | +int main(void) |
| 69 | +{ |
| 70 | + /* Set device clocks from opencm3 provided preset.*/ |
| 71 | + const struct rcc_clock_scale *clocks = &rcc_hsi_configs[RCC_CLOCK_3V3_84MHZ]; |
| 72 | + rcc_clock_setup_pll( clocks ); |
| 73 | + |
| 74 | + rcc_periph_clock_enable(RCC_GPIOA); |
| 75 | + rcc_periph_clock_enable(RCC_GPIOB); |
| 76 | + rcc_periph_clock_enable(RCC_GPIOC); |
| 77 | + rcc_periph_clock_enable(RCC_GPIOD); |
| 78 | + |
| 79 | + /* Initialize "heartbeat" LED GPIOs. */ |
| 80 | +#ifdef USE_DMA |
| 81 | + gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO12); /* green led */ |
| 82 | +#endif |
| 83 | + gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13); /* orange led */ |
| 84 | + |
| 85 | + |
| 86 | + /* Initialize I2C. |
| 87 | + * I2C is needed to initialize the onboard audio DAC chip. |
| 88 | + * PB6 - SCL (I2C clock) |
| 89 | + * PB9 - SDA (I2C data) |
| 90 | + * The board does not have pullups on the I2C lines, so |
| 91 | + * we use the chip internal pullups. |
| 92 | + * Also the pins must be open drain, as per I2C specification. |
| 93 | + * STM32F411 datasheet "Alternate Functions table" tells that |
| 94 | + * I2C is AlternateFucntion 4 for both pins. |
| 95 | + */ |
| 96 | + gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO6); |
| 97 | + gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO9); |
| 98 | + gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO6); |
| 99 | + gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO9); |
| 100 | + gpio_set_af(GPIOB, GPIO_AF4, GPIO6); |
| 101 | + gpio_set_af(GPIOB, GPIO_AF4, GPIO9); |
| 102 | + |
| 103 | + /* Initialize the I2C itself. |
| 104 | + * Since we are master, we would not need to initialize slave |
| 105 | + * address, but this is the only libopencm3 API call that sets |
| 106 | + * the 'bit14 of CCR' - a bit in the I2C that is HW reset to 0, |
| 107 | + * but manual says 'must be 1' */ |
| 108 | + rcc_periph_clock_enable(RCC_I2C1); |
| 109 | + i2c_peripheral_disable(I2C1); |
| 110 | + i2c_set_speed(I2C1, i2c_speed_sm_100k, clocks->apb1_frequency/1000000); |
| 111 | + i2c_set_own_7bit_slave_address(I2C1, 0); |
| 112 | + i2c_peripheral_enable(I2C1); |
| 113 | + |
| 114 | + |
| 115 | + /* Initialize I2S. |
| 116 | + * I2S is implemented as a HW mode of the SPI peripheral. |
| 117 | + * Since this is a STM32F411, there is a separate I2S PLL |
| 118 | + * that needs to be enabled. |
| 119 | + */ |
| 120 | + rcc_osc_on(RCC_PLLI2S); |
| 121 | + rcc_periph_clock_enable(RCC_SPI3); |
| 122 | + i2s_disable(SPI3); |
| 123 | + i2s_set_standard(SPI3, i2s_standard_philips); |
| 124 | + i2s_set_dataformat(SPI3, i2s_dataframe_ch16_data16); |
| 125 | + i2s_set_mode(SPI3, i2s_mode_master_transmit); |
| 126 | + i2s_masterclock_enable(SPI3); |
| 127 | + /* RCC_PLLI2SCFGR is left at reset value: 0x24003010 i.e. |
| 128 | + * PLLR = 2 |
| 129 | + * PLLI2SN = 192 |
| 130 | + * PLLI2SM = 16 |
| 131 | + * And since the input is PLL source (i.e. HSI = 16MHz) |
| 132 | + * The I2S clock = 16 / 16 * 192 / 2 = 96MHz |
| 133 | + * Calculate sampling frequency from equation given in |
| 134 | + * STM32F411 reference manual: |
| 135 | + * Fs = I2Sclk/ (32*2 * ((2*I2SDIV)+ODD)*4) |
| 136 | + * I2SDIV = I2Sclk/(512*Fs) |
| 137 | + * Fs=8kHz => I2SDIV=23,4 so 23 + ODD bit set |
| 138 | + */ |
| 139 | + i2s_set_clockdiv(SPI3, 23, 1); |
| 140 | +#ifdef USE_DMA |
| 141 | + /* Have the SPI/I2S peripheral ping the DMA each time data is sent. |
| 142 | + * The DMA peripheral is configured later. */ |
| 143 | + spi_enable_tx_dma(SPI3); |
| 144 | +#endif |
| 145 | + i2s_enable(SPI3); |
| 146 | + |
| 147 | + /* I2S pins: |
| 148 | + * Master clock: PC7 |
| 149 | + * Bit clock: PC10 |
| 150 | + * Data: PC12 |
| 151 | + * L/R clock: PA4 |
| 152 | + */ |
| 153 | + gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO7); |
| 154 | + gpio_set_af(GPIOC, GPIO_AF6, GPIO7); |
| 155 | + gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO10); |
| 156 | + gpio_set_af(GPIOC, GPIO_AF6, GPIO10); |
| 157 | + gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO12); |
| 158 | + gpio_set_af(GPIOC, GPIO_AF6, GPIO12); |
| 159 | + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO4); |
| 160 | + gpio_set_af(GPIOA, GPIO_AF6, GPIO4); |
| 161 | + |
| 162 | + |
| 163 | + /* Initialize the Audio DAC, as per its datasheet. |
| 164 | + * CS43L22 /RESET is connected to PD4, first release it. Then write |
| 165 | + * minimum set of needed settings. */ |
| 166 | + gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO4); |
| 167 | + gpio_set(GPIOD, GPIO4); |
| 168 | + write_i2c_to_audiochip(0x06, 0x04); // interface control 1: set I2S dataformat |
| 169 | + write_i2c_to_audiochip(0x02, 0x9e); // power control 1: Magic value to power up the chip |
| 170 | + |
| 171 | + |
| 172 | +#ifdef USE_DMA |
| 173 | + /* Enable DMA from memory to I2S peripheral. |
| 174 | + * The DMA is configured as circular, i.e. it restarts automatically when |
| 175 | + * the requested amount of datasamples are set. |
| 176 | + * SPI3/I2S3 is available on DMA1 stream 5, channel 0 (see RM 0383, table 27) */ |
| 177 | + rcc_periph_clock_enable(RCC_DMA1); |
| 178 | + nvic_enable_irq(NVIC_DMA1_STREAM5_IRQ); |
| 179 | + dma_disable_stream(DMA1, DMA_STREAM5); |
| 180 | + dma_set_priority(DMA1, DMA_STREAM5, DMA_SxCR_PL_HIGH); |
| 181 | + dma_set_memory_size(DMA1, DMA_STREAM5, DMA_SxCR_MSIZE_16BIT); |
| 182 | + dma_set_peripheral_size(DMA1, DMA_STREAM5, DMA_SxCR_PSIZE_16BIT); |
| 183 | + dma_enable_memory_increment_mode(DMA1, DMA_STREAM5); |
| 184 | + dma_enable_circular_mode(DMA1, DMA_STREAM5); |
| 185 | + dma_set_transfer_mode(DMA1, DMA_STREAM5, DMA_SxCR_DIR_MEM_TO_PERIPHERAL); |
| 186 | + dma_set_peripheral_address(DMA1, DMA_STREAM5, (uint32_t) &SPI_DR(SPI3)); |
| 187 | + dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t) audio); |
| 188 | + dma_set_number_of_data(DMA1, DMA_STREAM5, sizeof(audio)/sizeof(audio[0])); |
| 189 | + dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM5); |
| 190 | + dma_channel_select(DMA1, DMA_STREAM5, DMA_SxCR_CHSEL_0); |
| 191 | + dma_enable_stream(DMA1, DMA_STREAM5); |
| 192 | +#endif |
| 193 | + |
| 194 | + |
| 195 | + while(1) { |
| 196 | + /* Blink the heartbeat LED at ~0,5Hz*/ |
| 197 | +#ifdef USE_DMA |
| 198 | + const int blinkslowdown = 20e6; |
| 199 | +#else |
| 200 | + const int blinkslowdown = 1000; |
| 201 | +#endif |
| 202 | + static int i=0; |
| 203 | + if( ++i > blinkslowdown) { |
| 204 | + gpio_toggle(GPIOD, GPIO13); |
| 205 | + i=0; |
| 206 | + } |
| 207 | + |
| 208 | +#ifndef USE_DMA |
| 209 | + /* Blocking send of the data buffer */ |
| 210 | + for( unsigned j=0; j < sizeof(audio)/sizeof(audio[0]); j++) |
| 211 | + spi_send(SPI3, audio[j]); |
| 212 | +#endif |
| 213 | + } |
| 214 | + return 0; |
| 215 | +} |
| 216 | + |
0 commit comments