cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F0 TIMER + DAC + DMA!

mikemillernow
Associate II
Posted on March 19, 2014 at 06:29

The original post was too long to process during our migration. Please click on the attachment to read the original post.
11 REPLIES 11
Posted on March 19, 2014 at 13:08

A bunch of issues in there,

To generate a 20 KHz signal with 10 samples, you need to output the samples at 200 KHz.

The TIM prescaler should be the smaller factor of prescaler and period, ideally if within 16-bit range the prescaler should be zero (DIV1). Period = (SystemCoreClock / 200000) - 1; for 200 KHz where TIMCLK = CPUCLK

You need to set the Update as the Trigger source.

You need to enable the GPIOA clock prior to configuration, AN mode is sufficient.

The DMA needs to point at an address, *wavetable is the content, you want (uint32_t)wavetable or (uint32_t)&wavetable[0], I'd use the form (uint32_t)&DAC->DR for the peripheral.

No need to use interrupts unless you plan to do something with them, ie change table

I'd tinker, but the supplied code isn't sufficiently complete.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
mikemillernow
Associate II
Posted on March 19, 2014 at 19:13

Clive1,

Awesome post! Thank you so much for your time responding. Yes, several issues here.

1.) Here is my response on the timer issues:

Looking at the timer 6 (TIM6) register map there are 8 total registers to work with.

Only 4 of these looks to require any changes for my applications.

Do you mind doing a double check here? :)

TIM6_CR1 (control register 1) - updating

------------------------------------------

In this register here are the bits I am setting to 1:

ARPE - auto-reload preload enable (TIM6_ARR is buffered)

URS - updated request source (overflow should generated a DMA request)

CEN - counter enable.

TIM6_CR2 (control register 2) - updating

------------------------------------------

MMS - Master mode selection set to 010 (update).

 

The update event is selected as a trigger output (TRGO). For instance a

master timer can then be used as a prescaler for a slave timer.

Is this TRGO going to the DMA controller or the DAC? Looking at the

DAC datasheet TIM6_TRGO looks to be fed into the control unit of the DAC. I am not clear on when the DMA know  when to move the data over from memory?

TIM6_DIER (DMA/Interrupt enable register)  - updating

-------------------------------------------

UDE: Update DMA request enable

How is this different than setting the update event as a trigger output (TRGO) as shown above?  Should I not be enable anything with DMA for the timer?

TIM6_SR (Status Register) - no update.

--------------------------------

I am not updating anything in this register.  The bit UIF indicates an update interrupt flag.

TIM6_EGR (event generation register) - no update

-----------------------------------------------------

I am not updating anything in this register.  The bit UG is used to re-initializes the timer counter and generates an update of the registers

TIMx_CNT (counter) - no update

---------------------------------------------

I am not updating anything in this register.

The value starts off at zero (0) and will count up to what is in the TIM6_ARR register?

TIMx_PSC (prescaler) - no update

------------------------------------

I am not updating anything in this register.  The reset value is zero and this will divide the internal clock by 1.

TIM6_ARR (auto-reload register) - updating

--------------------------------------------

Isn't this the value that the counter will count up to and then generate an update event ?

I believe the system clock is set to 48 MHz.

48,000,000 / 200,000 = 240.

I believe that setting this register to 240 will give the update event of 200 kHz.

2.) When you said that I need to set the update as the trigger source.

I believe this is what I am doing now in the TIM6_CR2 (control register 2) with the master mode selection. The update event is selected as a trigger output (TRGO).

Was this what you were referring to?

3.) Yous said ''You need to enable the GPIOA clock prior to configuration, AN mode is sufficient''.

I have added in the following line of code:

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE)

But when I did this earlier:

   RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

And setting DAC1, PA4 to analog mode I was able to set different values on the DAC by single stepping in the debugger.

4.) For the DMA memory and peripheral address I have made the following updates:

DMA_Initstrucx.DMA_MemoryBaseAddr = (uint32_t)&wave_data[0];                     

DMA_Initstrucx.DMA_PeripheralBaseAddr = (uint32_t) DAC->DHR8R1;    

5.) On the interrupts at some point I am going to want to know when the DMA transfer is half way complete or fully complete so that I can move some data around.  I am not sure how to get this section setup.

6.) To provide code that is sufficiently complete what would you recommend doing? Attached the project as an attachment?

Thank you.

-Mike

Posted on March 19, 2014 at 20:23

So 240 -1, the Prescaler and Period expect N-1 values

The DAC drives the DMA through the external trigger, in this case one from the timer with a connectivity defined at each end of the connection. If you set the DAC to trigger on TIMx_TRGO then you need to configure the timer end of that contract. You could alternatively use TIMx_CCx, or use a DMA channel driven by a timer directly. There are a myriad of options here to permit multiple devices to be synchronized, ie ADC, DAC, and multiple channels thereof.

The DMA expects addresses, use the ampersand (&) to do this in C

ie not (uint32_t) DAC->DHR8R1;  but  (uint32_t)&DAC->DHR8R1; 

Completeness, observe the examples I have posted, they are stand-alone, and compile. They are placed in the body of the message using the ''Format Code Block'' tool.
Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on March 19, 2014 at 21:16

// STM32F0-Discovery Single DAC 6.25 KHz Sine - sourcer32@gmail.com
#include ''stm32f0_discovery.h''
//**************************************************************************************
const uint16_t Sine12bit[32] = {
2047, 2447, 2831, 3185,
3498, 3750, 3939, 4056,
4095, 4056, 3939, 3750,
3495, 3185, 2831, 2447,
2047, 1647, 1263, 909,
599, 344, 155, 38,
0, 38, 155, 344,
599, 909, 1263, 1647};
//**************************************************************************************
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
/* Configure PA4 in analogue (output) mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; /* overkill? */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//**************************************************************************************
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
int Period;
/* TIM2 Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
Period = (SystemCoreClock / 200000); // 200 KHz timebase, 6.25 KHz Sine
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = Period - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // not relevant here
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM2 TRGO selection: update event is selected as trigger for DAC */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}
//**************************************************************************************
void DAC_Configuration(void)
{
DAC_InitTypeDef DAC_InitStructure;
/* DAC Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* Fill DAC InitStructure */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO; /* Select the receiving end */
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
/* DAC channel1 Configuration */
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
/* Enable DAC Channel1: Once the DAC channel1 is enabled, PA.04 is
automatically connected to the DAC converter. */
DAC_Cmd(DAC_Channel_1, ENABLE);
/* Enable DMA for DAC Channel2 */
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
//**************************************************************************************
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* Enable DMA1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 32;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16-bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
/* Enable DMA1 Channel3 */
DMA_Cmd(DMA1_Channel3, ENABLE);
}
//**************************************************************************************
int main(void)
{
GPIO_Configuration();
DMA_Configuration();
DAC_Configuration();
TIM_Configuration();
/* Infinite loop */
while(1);
}
//**************************************************************************************
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(''Wrong parameters value: file %s on line %d

'', file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Posted on March 19, 2014 at 21:19

// STM32F0-Discovery Single DAC 20 KHz Sine - sourcer32@gmail.com
#include ''stm32f0_discovery.h''
//**************************************************************************************
const uint16_t Sine12bit[32] = {
2047, 2447, 2831, 3185,
3498, 3750, 3939, 4056,
4095, 4056, 3939, 3750,
3495, 3185, 2831, 2447,
2047, 1647, 1263, 909,
599, 344, 155, 38,
0, 38, 155, 344,
599, 909, 1263, 1647};
//**************************************************************************************
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
/* Configure PA4 in analogue (output) mode */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; /* overkill? */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//**************************************************************************************
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
int Period;
/* TIM2 Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
Period = (SystemCoreClock / (20000 * 32)); // 20 KHz Sine, 32 sample wave table
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = Period - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // not relevant here
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* TIM2 TRGO selection: update event is selected as trigger for DAC */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
}
//**************************************************************************************
void DAC_Configuration(void)
{
DAC_InitTypeDef DAC_InitStructure;
/* DAC Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* Fill DAC InitStructure */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO; /* Select the receiving end */
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
/* DAC channel1 Configuration */
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
/* Enable DAC Channel1: Once the DAC channel1 is enabled, PA.04 is
automatically connected to the DAC converter. */
DAC_Cmd(DAC_Channel_1, ENABLE);
/* Enable DMA for DAC Channel2 */
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
//**************************************************************************************
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* Enable DMA1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 32;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 16-bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
/* Enable DMA1 Channel3 */
DMA_Cmd(DMA1_Channel3, ENABLE);
}
//**************************************************************************************
int main(void)
{
GPIO_Configuration();
DMA_Configuration();
DAC_Configuration();
TIM_Configuration();
/* Infinite loop */
while(1);
}
//**************************************************************************************
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(''Wrong parameters value: file %s on line %d

'', file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
mikemillernow
Associate II
Posted on March 20, 2014 at 22:03

Thank you Clive1. Very helpful.

mikemillernow
Associate II
Posted on March 21, 2014 at 18:32

Thank you so much for your time on reading and responding to this post. I really appreciate it.

From the above information the TIM6 + DMA + DAC are working correctly.

1.) The next step I have now is going the other direction and using the ADC. TIM6 + DMA + ADC.

2.) Now for the timer it doesn't like like I can use TIM6 to input into the ADC. I looks like the options are TIM1, TIM2, TIM3 and TIM15. What timer should I use for the ADC? There are different types of timers - advanced control timers and general purpose which are different from the basic timer 6.

3.)  Since I will be sampling with the ADC and placing into memory at some point I need to have the CPU grab this data to write to my external SPI flash.  I have read the ST application note (AN3126) and this discusses using two separate buffers for the DMA writes.

For example I have buffer1 and buffer2:

A.) I start off sampling the ADC the DMA controller places the data into buffer1.

B.)  At the end of the DMA transfer from the ADC to memory an interrupt is generated.(Transfer complete interrupt?).  Is this interrupt generated for each sample or for the entire block of data being sent?  For example, if my DMA buffer size is set to 255.  This interrupt happens after the 255th sample transfers over?

How do I detect and service this interrupt?  At this point I have no interrupt handling in my source code.

For the interrupt, I will be enabling the TCIE (transfer complete) of the DMA controller in the configuration register.

C.) The DMA is reconfigured now to point to buffer2.

D.) The application now also mentions decrementing the ''wavedatalength'' counter?  Is this the DMA_CNDTR1 register that is the number of data to be transferred.  This register shows the remaining bytes to be transferred.  Does this happen automatically by the hardware? Or do I need to decrement this myself?

E.)  The cpu then takes what is in buffer1 and writes out to SPI flash.

Thank you very much for your comments.

-Mike

Posted on March 21, 2014 at 18:54

#1 - That's going to be hard, as they don't route. This is why I'd use TIM2, 3 or 15 if I wanted to tie the ADC and DAC together

#2 - There are limited resources and connectivity, part of the task is to manage them, and fit the solution. #3 - I'm not a big fan of the double buffer scheme, it's designed to allow for a scatter-gather or DMA chaining, where you have a longer list of buffers to manage. I prefer to double the buffer size and use it in a Ping-Pong fashion, with the HT (Half Transfer), TC (Transfer Compete), but this depends on the in-flow and out-flow rates expected. I've posted other STM32 ADC examples that illustrate some of these concepts.


Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
mikemillernow
Associate II
Posted on March 21, 2014 at 21:33

Clive,

Yes, the longer buffer seems like a better idea.

BTW, what is the deal with you?  You have over 8,000 post. Do you work for ST?

Thanks a million!

-Mike