cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 external DAC sine wave problem.

Andrei1
Associate II

Hi!

I would like to make a wav. player using STM32 microcontroller. My first step was to generate a sine wave using STM32F103C8T6, SPI interface and external DAC MCP4921. However, it's not working.

I attach the schematic of my device.

0693W000003Rt2wQAC.png

I wrote 2 versions of my program using CMSIS in STM32CubeIDE. In the first version of the program a timer is generating an interrupt every second. Every 4 seconds microcontroller sends a digital value to the external DAC using SPI interface. I checked the output of the DAC with multimeter and voltage levels are correct. It is working. I also checked RCC, GPIO, SPI and timer blocks of microcontroller: they all work. So, the first version of the program works perfectly.

In the second version of the program a buffer is initialised with 100 sine wave values. A timer is generating an interrupt with a frequency of 40kHz, microcontroller sends a sine value to the external DAC using SPI interface. The frequency of the sine wave should be 400Hz. However, I cant hear a correct tone when I plug in my headphones. I also cant see anything when I plug the signal to the virtual oscilloscope through the microphone input of my computer (unfortunately I don't have an oscilloscope). When I change the sampling frequency (interrupt frequency of the timer) I either dont hear anything or hear some disturbing gibberish.

Appreciate your help.

//#include "main.h"
#include "stm32f1xx.h"
 
void RCCinit(void);
void GPIOinit(void);
void SPIinit(void);
void SPItransmit(unsigned int data);
void TIMinit(void);
 
unsigned int i=0;
 
unsigned int buf[100]=
{
		620,659,698,736,774,812,848,884,919,952,
		984,1015,1044,1072,1098,1122,1143,1163,1181,1196,
		1210,1221,1229,1235,1239,1240,1239,1235,1229,1221,
		1210,1196,1181,1163,1143,1122,1098,1072,1044,1015,
		984,952,919,884,848,812,774,736,698,659,
		620,581,542,504,466,428,392,356,321,288,
		256,225,196,168,142,118,97,77,59,44,
		30,19,11,5,1,0,1,5,11,19,
		30,44,59,77,97,118,142,168,196,225,
		256,288,321,356,392,428,466,504,542,581
};
 
int main (void)
{
	RCCinit();
	GPIOinit();
	SPIinit();
	TIMinit();
 
	while(1)
	{
 
	}
}
 
void RCCinit(void)
{
	FLASH->ACR|=FLASH_ACR_PRFTBE; //Prefetch is enabled.
	while(!(FLASH->ACR&FLASH_ACR_PRFTBS)); //Prefetch buffer is disabled.
	FLASH->ACR&=~FLASH_ACR_HLFCYA; //Half cycle is disabled.
	FLASH->ACR&=~FLASH_ACR_LATENCY;
	//FLASH->ACR|=FLASH_ACR_LATENCY_0; //One wait state, if 24 MHz < SYSCLK ≤ 48 MHz
	FLASH->ACR|=FLASH_ACR_LATENCY_1; //Two wait states, if 48 MHz < SYSCLK ≤ 72 MHz.
 
 
	RCC->CR|=RCC_CR_HSEON; //HSE oscillator ON. 8MHz.
	while(!(RCC->CR&RCC_CR_HSERDY)); //HSE oscillator not ready.
 
	RCC->CFGR&=~RCC_CFGR_PLLXTPRE; //HSE clock not divided. 8MHz.
	RCC->CFGR|=RCC_CFGR_PLLSRC; //HSE oscillator clock selected as PLL input clock.
	RCC->CFGR&=~RCC_CFGR_PLLMULL;
	RCC->CFGR|=RCC_CFGR_PLLMULL9; //PLL input clock x 9. 72MHz.
	RCC->CFGR&=~RCC_CFGR_HPRE; //SYSCLK not divided.
	RCC->CFGR&=~RCC_CFGR_PPRE1;
	RCC->CFGR|=RCC_CFGR_PPRE1_DIV2; //HCLK divided by 2. 36MHz.
	RCC->CFGR&=~RCC_CFGR_PPRE2; //HCLK not divided.
	RCC->CFGR&=~RCC_CFGR_USBPRE; //PLL clock is divided by 1.5. 48MHz.
 
	RCC->CR|=RCC_CR_PLLON; //PLL ON.
	while(!(RCC->CR&RCC_CR_PLLRDY)); //PLL unlocked.
	RCC->CFGR&=~RCC_CFGR_SW;
	RCC->CFGR|=RCC_CFGR_SW_PLL; //PLL selected as system clock.
	while(!(RCC->CFGR&RCC_CFGR_SWS_PLL)); //PLL used as system clock.
 
 
	RCC->APB1ENR|=RCC_APB1ENR_TIM2EN; //TIM2 timer clock enable.
	RCC->APB2ENR|=RCC_APB2ENR_IOPCEN|RCC_APB2ENR_IOPAEN|RCC_APB2ENR_AFIOEN|RCC_APB2ENR_SPI1EN; //IO port C, IO port A, Alternate function I/O, SPI 1 clock enabled.
}
 
void GPIOinit(void)
{
	//SPI pins.
	GPIOA->CRL|=GPIO_CRL_MODE4|GPIO_CRL_MODE5|GPIO_CRL_MODE7; //Output mode, max speed 50 MHz.
	GPIOA->CRL&=~(GPIO_CRL_CNF4|GPIO_CRL_CNF5|GPIO_CRL_CNF7);
	GPIOA->CRL|=GPIO_CRL_CNF4_1|GPIO_CRL_CNF5_1|GPIO_CRL_CNF7_1; //Alternate function output Push-pull.
 
	//C13
	GPIOC->CRH|=GPIO_CRH_MODE13; //Output mode, max speed 50 MHz.
	GPIOC->CRH&=~GPIO_CRH_CNF13; //General purpose output push-pull.
	GPIOC->ODR|=GPIO_ODR_ODR13;
}
 
void SPIinit(void)
{
	SPI1->CR1&=~SPI_CR1_BR;
	SPI1->CR1|=SPI_CR1_BR_0; //fPCLK/4=72MHz/4=18MHz.
	SPI1->CR1&=~(SPI_CR1_CPOL|SPI_CR1_CPHA); //CK to 0 when idle. The first clock transition is the first data capture edge.
	SPI1->CR1|=SPI_CR1_DFF; //16-bit data frame format is selected for transmission/reception.
	SPI1->CR1&=~SPI_CR1_LSBFIRST; //MSB transmitted first.
	SPI1->CR1&=~(SPI_CR1_SSM|SPI_CR1_SSI); //Software slave management disabled.
	SPI1->CR2|=SPI_CR2_SSOE; //SS output is enabled in master mode and when the cell is enabled. The cell cannot work in a multimaster environment.
 
	SPI1->CR1&=~(SPI_CR1_BIDIMODE|SPI_CR1_RXONLY); //2-line unidirectional data mode selected. Full duplex (Transmit and receive).
	SPI1->CR1&=~SPI_CR1_CRCEN; //CRC calculation disabled.
	SPI1->CR2&=~(SPI_CR2_TXEIE|SPI_CR2_RXNEIE|SPI_CR2_ERRIE|SPI_CR2_TXDMAEN|SPI_CR2_RXDMAEN); //TXE, RXNE, Error interrupt is masked. Tx, Rx buffer DMA disabled.
 
	SPI1->I2SCFGR&=~SPI_I2SCFGR_I2SMOD; //SPI mode is selected.
 
	SPI1->CR1|=SPI_CR1_MSTR; //Master configuration.
}
 
void SPItransmit(unsigned int data)
{
	SPI1->CR1|=SPI_CR1_SPE; //Peripheral enabled.
	while(!(SPI1->SR&SPI_SR_TXE)); //Tx buffer not empty.
	SPI1->DR=0x7000U|(data&0xFFFU);
	while(!(SPI1->SR&SPI_SR_TXE)); //Tx buffer not empty.
	while(SPI1->SR&SPI_SR_BSY); //SPI (or I2S) is busy in communication or Tx buffer is not empty.
	SPI1->CR1&=~SPI_CR1_SPE; //Peripheral disabled.
}
 
void TIMinit(void)
{
	//fapb1=36MHz. fclk=36*2=72MHz.
	TIM2->CR1&=~TIM_CR1_UDIS; //UEV enabled.
	TIM2->CR1&=~TIM_CR1_URS; //Any of the following events generate an update interrupt or DMA request if enabled. These events can be: – Counter overflow/underflow. – Setting the UG bit. – Update generation through the slave mode controller.
	TIM2->CR1&=~TIM_CR1_OPM; //Counter is not stopped at update event.
	TIM2->CR1&=~TIM_CR1_DIR; //Counter used as upcounter.
	TIM2->CR1&=~TIM_CR1_CMS; //Edge-aligned mode.
	TIM2->CR1|=TIM_CR1_ARPE; //TIMx_ARR register is buffered.
	TIM2->CR1&=~TIM_CR1_CKD; //tDTS = tCK_INT.
	TIM2->DIER|=TIM_DIER_UIE; //Update interrupt enabled.
	NVIC_EnableIRQ(TIM2_IRQn);
	TIM2->PSC=17; //Prescaler value.
	TIM2->ARR=99; //Auto-reload register. fUEV=72MHz/()=Hz.
	TIM2->CNT=0; //Counter value.
	TIM2->CR1|=TIM_CR1_CEN; //Counter enabled.
}
 
void TIM2_IRQHandler(void)
{
	//C13 мигает
	//GPIOC->ODR^=GPIO_ODR_ODR13;
 
	if(i==100)
	{
		i=0;
	}
	SPItransmit(buf[i]);
	i++;
 
	TIM2->SR&=~TIM_SR_UIF; //Update interrupt flag. It is cleared by software. No update occurred.
}

1 ACCEPTED SOLUTION

Accepted Solutions

> TIM2->CR1|=TIM_CR1_ARPE; //TIMx_ARR register is buffered.

After reset, ARR=0. The above command means, that the subsequent write to ARR does not go into the "real" ARR, but to a buffer, from which it is transferred to "real" ARR upon update. But while "real" ARR=0, even if you enable timer, it won't run, so there is no update, do it will stay in that state forever.

Either don't set CR1.ARPE, or force am update after you set ARR (and PSC), by setting EGR.UG.

This was the main problem. Now lesser problems and style:

There's no need to repeatedly enable and disable SPI in the ISR. Enable it once at initialization.

If you know that the SPI transfer happens faster than is proof between ISRs, you also don't need to wait for TXNE not busy. A single write to the SPI_DR is all you need.

In the timer ISR, always check the respective flag in TIM_SR.

Don't use RMW to clear flag in TIM_SR.

JW

View solution in original post

4 REPLIES 4
LMI2
Lead

I cant comment about your code but if your dac is working, why don't you test your interrupts. Do they came too often or something.

> TIM2->CR1|=TIM_CR1_ARPE; //TIMx_ARR register is buffered.

After reset, ARR=0. The above command means, that the subsequent write to ARR does not go into the "real" ARR, but to a buffer, from which it is transferred to "real" ARR upon update. But while "real" ARR=0, even if you enable timer, it won't run, so there is no update, do it will stay in that state forever.

Either don't set CR1.ARPE, or force am update after you set ARR (and PSC), by setting EGR.UG.

This was the main problem. Now lesser problems and style:

There's no need to repeatedly enable and disable SPI in the ISR. Enable it once at initialization.

If you know that the SPI transfer happens faster than is proof between ISRs, you also don't need to wait for TXNE not busy. A single write to the SPI_DR is all you need.

In the timer ISR, always check the respective flag in TIM_SR.

Don't use RMW to clear flag in TIM_SR.

JW

Thank you for your answer! I will try out your suggestions a bit later.

The reason why I enable and disable SPI this way is because my external DAC needs to receive CS signal (low->high) after each 16-bit data transfer. This is quite inconvenient. Since I configured NSS on microcontroller in hardware mode, CS sets only after SPI is disabled. Thats' also why I wait for SPI to complete the transfer (SPI_SR_BSY) before disabling it.

Basically, I can make GPIO pin act as a CS signal. Then I wouldn't need to enable and disable SPI every time. But I would still need to wait for SPI to complete the transfer (SPI_SR_BSY) before setting CS(GPIO).

Hope it makes sense.

UPD: It's working! I didn't set CR1.ARPE and changed SPI transmit sequence as I described above. Now I don't enable and disable it every time and manage CS signal with GPIO pin.

As for the timer flag tips, I'll read about them a bit later.

Thank you a lot!

I tested a timer previously. In the first version of the program it generates an interrupt every second. It worked. So in the second version math should be correct.