C STM32F4 Discovery + AudioDAC per DMA ansprechen

chillking

Lieutenant
Registriert
Juni 2010
Beiträge
926
Hi zusammen,

diesmal habe ich folgendes Problem:

ich habe ein STM32F4 Discovery und möchte den Audio-DAC CS43L22 verwenden.
Konfigurieren hat auch funktioniert und wenn ich in meiner main-while-Schleife das passende Flag wie folgt abfrage und daraufhin einen neuen Wert sende kommt auch was raus.
Code:
//DAC abfragen
if (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_TXE))
{
	//naechstes Sample senden
	SPI_I2S_SendData(SPI3, sample);

	//Trennung Kanal
	if(channel)
	{
		sample=sinus[stelle];
		stelle++;
		if (stelle>=48)
			stelle-=48;
		}
	else
		sample=0;
}

Da ich aber noch ein paar andere Sachen nebenher mache, unter anderem ein Display ansteuern, braucht das Programm zu lange und man hört eine Unterbrechung, wenn z.B. das Display neu beschrieben wird.

Also habe ich einen Timer mit 50kHz eingestellt und die Abfrage darin gemacht. Nun hört man nichts mehr, allerdings sieht man auf dem Display deutlich, dass der Interrupt des Timers kommt. Die einzelnen Zeichen werden nicht mehr fast "gleichzeitig" angezeigt, sondern erscheinen wie eine Laufschrift nacheinander. ("Das ist kein Bug, das ist ein Feature")


Daher wäre es top, wenn ich den DAC per DMA gespielen würde, die bekomme ich aber einfach nicht zum laufen...
Die Config der DMA sieht bisher so aus:
Code:
void setupDMA()
{
	//Clock
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);


	NVIC_InitTypeDef NVIC_InitStructure;

	// DMA Stream IRQ Channel0
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// DMA Stream IRQ Channel5
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	//DMA config
	DMA_InitTypeDef DMA_InitStructure;

	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable ;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull ;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ;

	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(SPI3->DR));
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;


	//SPI3_RX = Stream0 Channel 0
	//SPI3_TX = Stream5 Channel 0


	// Configure Tx DMA
	DMA_Cmd(DMA1_Stream5, DISABLE);
	while (DMA1_Stream5->CR & DMA_SxCR_EN);	//warten bis EN=0
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t) &DMAbufferTx[0];
	DMA_InitStructure.DMA_BufferSize = DMAbufferSizeTx;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
	DMA_Init(DMA1_Stream5, &DMA_InitStructure);


	// Configure Rx DMA
	DMA_Cmd(DMA1_Stream0, DISABLE);
	while (DMA1_Stream0->CR & DMA_SxCR_EN);	//warten bis EN=0
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t) &DMAbufferRx[0];
	DMA_InitStructure.DMA_BufferSize = DMAbufferSizeRx;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_Init(DMA1_Stream0, &DMA_InitStructure);



	DMA_ITConfig(DMA1_Stream0, DMA_IT_TC , ENABLE); //| DMA_IT_HT

	/* Enable the DMA channel */

	DMA_ClearFlag(DMA1_Stream0, DMA_FLAG_FEIF0|DMA_FLAG_DMEIF0|DMA_FLAG_TEIF0|DMA_FLAG_HTIF0|DMA_FLAG_TCIF0);
	DMA_ClearFlag(DMA1_Stream5, DMA_FLAG_FEIF5|DMA_FLAG_DMEIF5|DMA_FLAG_TEIF5|DMA_FLAG_HTIF5|DMA_FLAG_TCIF5);


	DMA_Cmd(DMA1_Stream0, ENABLE); // Enable the DMA SPI TX Stream
	DMA_Cmd(DMA1_Stream5, ENABLE); // Enable the DMA SPI RX Stream


	// Enable the SPI Rx/Tx DMA request
	SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Rx, ENABLE);
	SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE);

}

Die IRQ_Handler werden nicht einmal aufgerufen, und bei den Kopfhörern kommt ebenfalls nichts an (es rauscht ein wenig, daher denke ich des DAC ist wieder richtig konfiguriert, allerdings bekommt er keine Daten);

Hat jemand eine Ahnung was ich falsch gemacht haben könnte?

EDIT: Achso,
Code:
I2S_Cmd(SPI3, ENABLE);
wird natürlich nach dem Aufruf der setupDMA() gemacht.
 
Zuletzt bearbeitet:
Hey,
das hört sich ja interessant an.
Ich arbeite auch viel mit einem STM32F4 aber habe DMA noch nie ausprobiert.
Meine Interrupt-initialisierung setzt immer 1 statt 0 hier:
nvicStructure.NVIC_IRQChannelSubPriority = 1;
aber das wird wahrscheinlich nicht die Ursache deines Problems sein.
Kannst du noch einmal erklären, was genau eine Interrupts auslösen sollte? Ich mache bisher nur Interrupts sensitiv auf externe GPIO inputs.

Als generellen Tipp, den du wahrscheinlich schon kennst:
Wenn irgendwas generell überhaupt nicht funktioniert würde ich immer erstmal den Quellcode so weit wie nur möglich auf das nötigste zusammen kürzen um genau das dann zum laufen zu bringen.
Außerdem habe ich mir sehr früh auf 4 Arten Debug-Möglichkeiten geschaffen, weil ansonsten das Arbeiten mit µC ja etwas mühsam ist:
- Da man ja nicht jede Zeile mit printf printen kann nutz ich die 4 LEDs des discovery boards um zB den Zustand anzuzeigen um zu erkennen, wo die Software aussteigt bzw um überhaupt erstmal sicher zu sehen, dass eine Funktion beendet wurde. Die 4 LEDs lassen ja 0-15 binär kodiert zu, wodurch man immerhin etwas sieht.
- Meine mainloop nutzt einen output um einen 'heartbeat' anzuzeigen zB alle 100000 Durchläufe (if counter % 100000==0) { TOGGLE }. Somit erkenne ich wenn das Board in Fehlerzustände (=> while(true){} hat stm hier vorgesehen) geht.
- Ich hab sehr früh einen rs232 UART in Betrieb genommen und mir eine PC Software als Receiver gebaut. So kann ich wenigstens std::string übertragen wie, wenn man printf debuggt. Gute USB to RS232 gibts füer deutlich unter 5€ bei ebay. So spart man sich das gefummel am PC und kann über nen USB Hum gehen.
- Natürlich hab ich mich auch darum gekümmert überhaupt debuggen zu können. Hier ist eine sehr gute Anleitung: https://istarc.wordpress.com/2014/11/03/stm32f4-in-circuit-debugging-revisited/
 
Zuletzt bearbeitet:
kuddlmuddl schrieb:
nvicStructure.NVIC_IRQChannelSubPriority = 1;
aber das wird wahrscheinlich nicht die Ursache deines Problems sein.
Habe im Moment ja nur diesen Interrupt konfiguriert, sollte also keinen Unterschied machen ob 0 oder 1 (zumal SubPriority).

kuddlmuddl schrieb:
Kannst du noch einmal erklären, was genau eine Interrupts auslösen sollte? Ich mache bisher nur Interrupts sensitiv auf externe GPIO inputs.
Also meines Wissen kann man der DMA sagen, dass sie Daten von einem Punkt zum anderen übertragen soll (bei mir vom Speicher zum externen DAC, der per SPI3 bespielt wird, also eigentlich zum Ausgangsbuffer des SPI3. Und wenn er damit fertig ist sollte ein Interrupt kommen.

kuddlmuddl schrieb:
Als generellen Tipp, den du wahrscheinlich schon kennst:
Wenn irgendwas generell überhaupt nicht funktioniert würde ich immer erstmal den Quellcode so weit wie nur möglich auf das nötigste zusammen kürzen um genau das dann zum laufen zu bringen.
Das hab ich gerade, DAC manuell in der main() bespielen klappt.

kuddlmuddl schrieb:
Außerdem habe ich mir sehr früh auf 4 Arten Debug-Möglichkeiten geschaffen, weil ansonsten das Arbeiten mit µC ja etwas mühsam ist:
- Da man ja nicht jede Zeile mit printf printen kann nutz ich die 4 LEDs des discovery boards um zB den Zustand anzuzeigen um zu erkennen, wo die Software aussteigt bzw um überhaupt erstmal sicher zu sehen, dass eine Funktion beendet wurde. Die 4 LEDs lassen ja 0-15 binär kodiert zu, wodurch man immerhin etwas sieht.
Danke für den Tipp! Die LEDs hab ich auch schon verwendet, allerdings einzeln und nicht als Binärcode.
Aber das Discovery kann man doch debuggen...hat bisher in Keil µVision und CooCox funktioniert (oder ich kenne die eigentliche Bedeutung von debuggen nicht :D )
 
Aber das Discovery kann man doch debuggen...hat bisher in Keil µVision und CooCox funktioniert (oder ich kenne die eigentliche Bedeutung von debuggen nicht )
Ja deins ist natürlich richtiges debuggen - ich hatte anfangs nur probleme den gnu debugger zu verbinden und mir dann so geholfen.
Außerdem verändern breakpoints ja deutlich das zeitliche verhalten. Dh mir sind buffer immer dann übergelaufen wenn ich mal kurze zeit in einem breakpoint hing wodurch ich meine fehler dann nicht suchen konnte.

Das hab ich gerade, DAC manuell in der main() bespielen klappt.
Dann würd ich versuchen ein noch einfacheres minimalst beispiel zum DMA zu suchen.

Gegoogelt wirst du ja wahrscheinlich schon haben... aber hier trotzdem ein paar Tipps von Seiten die mir immer sehr geholfen haben:
http://www.diller-technologies.de/stm32_wide.html#dma
http://stm32f4-discovery.com/2015/06/library-63-dma-for-stm32f4xx/
http://stackoverflow.com/questions/30077785/stm32f4-spi-dma-receive
http://www.embedds.com/using-direct-memory-access-dma-in-stm23-projects/
 
Hab es mittlerweile hinbekommen, falls es jemanden interessiert, hier ist mein Code:

Code:
void setupDAC()
{
	//Clocks automatisch

	initI2C();	//I2C-Schnittstelle konfigurieren
	initI2S();	//I2S/SPI-Schnittstelle konfigurieren


	DMA_InitTypeDef	DMA_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	//Enable DMA1 clock
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

	//Enable the DMA1 global interrupt
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream7_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// SPI3_Tx = DMA1 Stream7 Channel0
	// DMA1 Stream7 Channel 0 configuration
	DMA_InitStructure.DMA_Channel = DMA_Channel_0;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI3->DR;
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&dac_buffer_0;
	DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
	DMA_InitStructure.DMA_BufferSize = DAC_BUFFER_SIZE;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA1_Stream7, &DMA_InitStructure);

	//DoubleBuffer
	DMA_DoubleBufferModeConfig(DMA1_Stream7, (uint32_t)&dac_buffer_1, DMA_Memory_0);
	DMA_DoubleBufferModeCmd(DMA1_Stream7, ENABLE);

	DMA_Cmd(DMA1_Stream7, ENABLE);

	DMA_ITConfig(DMA1_Stream7, DMA_IT_TC, ENABLE);

	// Enable DMA request after last transfer
	//ADC_DMARequestAfterLastTransferCmd(SPI3, ENABLE);


	// Enable SPI DMA
	//ADC_DMACmd(SPI3, ENABLE);
	SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE);


	device.initAudioDac();	//Audio-DAC per I2C konfigurieren

	setVol();	//Lautstärke einstellen damit mir nicht die Ohren abfallen

	I2S_Cmd(SPI3, ENABLE);	//I2S/SPI start
}

Dabei sind in dac_buffer_0 und dac_buffer_1 jeweils die Daten, die ausgegeben werden enthalten, Größe der Arrays hab ich mit DAC_BUFFER_SIZE definiert.

Der Interrupt kommt immer, wenn der DMA mit einem Buffer komplett fertig ist.
 
Zurück
Oben