Skip to content

Add stm32f411e-discovery example sound_out #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/stm32/f4/stm32f411e-discovery/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# README

This directory contains samples for the STM32F411EDISCOVERY
board. This is similar to the original STM32F4DISCOVERY, but
has the the STM32F411VE MCU.
The board is labeled 'MB-1115 B-02'

4 changes: 4 additions & 0 deletions examples/stm32/f4/stm32f411e-discovery/sound_out/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BINARY = sound_out
DEVICE=STM32F411VE
include ../../Makefile.include

25 changes: 25 additions & 0 deletions examples/stm32/f4/stm32f411e-discovery/sound_out/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# README

This example demonstrates using the oboard audio DAC on
STM32F411-Discovery board.
It plays a continuous 1 kHz sine wave.

## Board connections

Sepeakers attached to the 3.5mm audio jack.
Note: volume is turned low, but beware of loud sounds
in case of bugs (i.e. plug the headphones to your ear
only after playback starts!)

## DMA mode

There is a compile-time option (#define USE_DMA) that
demonstrates how to have the DMA engine write audio
data to the DAC.

## LEDs

Both versions of the program blink the orange
led (PD13) at ~0.5Hz. The DMA version toggles the
green led (PD12) on every DMA interrupt, which means
the pattern is visible with oscilloscope only (500Hz).
216 changes: 216 additions & 0 deletions examples/stm32/f4/stm32f411e-discovery/sound_out/sound_out.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Example for how to use I2C and I2S with libopencm3.
* This is intended for the STM32F411-Discovery demoboard.
*
* The device plays a 1kHz sine through the audio jack.
*/

/* Compile time option to use DMA to feed audio to the I2S.
* Without this, the audio is sent by polling the I2S peripheral's
* TXE bit */
#define USE_DMA


#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/i2c.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/spi.h>

#ifdef USE_DMA
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/dma.h>
#endif


/* Test audio: 8 points of a sine.
* At Fs=8kHz, this means a 1kHz audio sine
* (the other channel is mute)
*/
#define VOL 0x0020
#define D16(x) ((int16_t)(x*VOL) )
int16_t audio[16]=
{ D16(0), 0,
D16(0.70711), 0,
D16(1), 0,
D16(0.70711), 0,
D16(0), 0,
D16(-0.70711),0,
D16(-0.9999), 0,
D16(-0.707), 0 };


static void write_i2c_to_audiochip( uint8_t reg, uint8_t contents)
{
uint8_t packet[2];
packet[0] = reg;
packet[1] = contents;
/* STM32F411 discovery user's manual gives device address with R/W bit,
* libopencm3 wants the address without it. */
uint8_t address = (0x94)>>1;

i2c_transfer7(I2C1, address, packet, 2, NULL, 0);
}

#ifdef USE_DMA
/* Interrupt service routine for the DMA.
* The name is "magic" and suffices as installation hook into libopencm3. */
void dma1_stream5_isr(void)
{
gpio_toggle(GPIOD, GPIO12);

/* Clear the 'transfer complete' interrupt, or execution would jump right back to this ISR. */
if (dma_get_interrupt_flag(DMA1, DMA_STREAM5, DMA_TCIF)) {
dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF);
}
}
#endif

int main(void)
{
/* Set device clocks from opencm3 provided preset.*/
const struct rcc_clock_scale *clocks = &rcc_hsi_configs[RCC_CLOCK_3V3_84MHZ];
rcc_clock_setup_pll( clocks );

rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_GPIOB);
rcc_periph_clock_enable(RCC_GPIOC);
rcc_periph_clock_enable(RCC_GPIOD);

/* Initialize "heartbeat" LED GPIOs. */
#ifdef USE_DMA
gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO12); /* green led */
#endif
gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO13); /* orange led */


/* Initialize I2C.
* I2C is needed to initialize the onboard audio DAC chip.
* PB6 - SCL (I2C clock)
* PB9 - SDA (I2C data)
* The board does not have pullups on the I2C lines, so
* we use the chip internal pullups.
* Also the pins must be open drain, as per I2C specification.
* STM32F411 datasheet "Alternate Functions table" tells that
* I2C is AlternateFucntion 4 for both pins.
*/
gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO6);
gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO9);
gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO6);
gpio_set_output_options(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_25MHZ, GPIO9);
gpio_set_af(GPIOB, GPIO_AF4, GPIO6);
gpio_set_af(GPIOB, GPIO_AF4, GPIO9);

/* Initialize the I2C itself.
* Since we are master, we would not need to initialize slave
* address, but this is the only libopencm3 API call that sets
* the 'bit14 of CCR' - a bit in the I2C that is HW reset to 0,
* but manual says 'must be 1' */
rcc_periph_clock_enable(RCC_I2C1);
i2c_peripheral_disable(I2C1);
i2c_set_speed(I2C1, i2c_speed_sm_100k, clocks->apb1_frequency/1000000);
i2c_set_own_7bit_slave_address(I2C1, 0);
i2c_peripheral_enable(I2C1);


/* Initialize I2S.
* I2S is implemented as a HW mode of the SPI peripheral.
* Since this is a STM32F411, there is a separate I2S PLL
* that needs to be enabled.
*/
rcc_osc_on(RCC_PLLI2S);
rcc_periph_clock_enable(RCC_SPI3);
i2s_disable(SPI3);
i2s_set_standard(SPI3, i2s_standard_philips);
i2s_set_dataformat(SPI3, i2s_dataframe_ch16_data16);
i2s_set_mode(SPI3, i2s_mode_master_transmit);
i2s_masterclock_enable(SPI3);
/* RCC_PLLI2SCFGR is left at reset value: 0x24003010 i.e.
* PLLR = 2
* PLLI2SN = 192
* PLLI2SM = 16
* And since the input is PLL source (i.e. HSI = 16MHz)
* The I2S clock = 16 / 16 * 192 / 2 = 96MHz
* Calculate sampling frequency from equation given in
* STM32F411 reference manual:
* Fs = I2Sclk/ (32*2 * ((2*I2SDIV)+ODD)*4)
* I2SDIV = I2Sclk/(512*Fs)
* Fs=8kHz => I2SDIV=23,4 so 23 + ODD bit set
*/
i2s_set_clockdiv(SPI3, 23, 1);
#ifdef USE_DMA
/* Have the SPI/I2S peripheral ping the DMA each time data is sent.
* The DMA peripheral is configured later. */
spi_enable_tx_dma(SPI3);
#endif
i2s_enable(SPI3);

/* I2S pins:
* Master clock: PC7
* Bit clock: PC10
* Data: PC12
* L/R clock: PA4
*/
gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO7);
gpio_set_af(GPIOC, GPIO_AF6, GPIO7);
gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO10);
gpio_set_af(GPIOC, GPIO_AF6, GPIO10);
gpio_mode_setup(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO12);
gpio_set_af(GPIOC, GPIO_AF6, GPIO12);
gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO4);
gpio_set_af(GPIOA, GPIO_AF6, GPIO4);


/* Initialize the Audio DAC, as per its datasheet.
* CS43L22 /RESET is connected to PD4, first release it. Then write
* minimum set of needed settings. */
gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO4);
gpio_set(GPIOD, GPIO4);
write_i2c_to_audiochip(0x06, 0x04); // interface control 1: set I2S dataformat
write_i2c_to_audiochip(0x02, 0x9e); // power control 1: Magic value to power up the chip


#ifdef USE_DMA
/* Enable DMA from memory to I2S peripheral.
* The DMA is configured as circular, i.e. it restarts automatically when
* the requested amount of datasamples are set.
* SPI3/I2S3 is available on DMA1 stream 5, channel 0 (see RM 0383, table 27) */
rcc_periph_clock_enable(RCC_DMA1);
nvic_enable_irq(NVIC_DMA1_STREAM5_IRQ);
dma_disable_stream(DMA1, DMA_STREAM5);
dma_set_priority(DMA1, DMA_STREAM5, DMA_SxCR_PL_HIGH);
dma_set_memory_size(DMA1, DMA_STREAM5, DMA_SxCR_MSIZE_16BIT);
dma_set_peripheral_size(DMA1, DMA_STREAM5, DMA_SxCR_PSIZE_16BIT);
dma_enable_memory_increment_mode(DMA1, DMA_STREAM5);
dma_enable_circular_mode(DMA1, DMA_STREAM5);
dma_set_transfer_mode(DMA1, DMA_STREAM5, DMA_SxCR_DIR_MEM_TO_PERIPHERAL);
dma_set_peripheral_address(DMA1, DMA_STREAM5, (uint32_t) &SPI_DR(SPI3));
dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t) audio);
dma_set_number_of_data(DMA1, DMA_STREAM5, sizeof(audio)/sizeof(audio[0]));
dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM5);
dma_channel_select(DMA1, DMA_STREAM5, DMA_SxCR_CHSEL_0);
dma_enable_stream(DMA1, DMA_STREAM5);
#endif


while(1) {
/* Blink the heartbeat LED at ~0,5Hz*/
#ifdef USE_DMA
const int blinkslowdown = 20e6;
#else
const int blinkslowdown = 1000;
#endif
static int i=0;
if( ++i > blinkslowdown) {
gpio_toggle(GPIOD, GPIO13);
i=0;
}

#ifndef USE_DMA
/* Blocking send of the data buffer */
for( unsigned j=0; j < sizeof(audio)/sizeof(audio[0]); j++)
spi_send(SPI3, audio[j]);
#endif
}
return 0;
}