| 
 | 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