STM32cubemx PWM + ADC DMA out of sync - stm32

I'm trying to get STM32F103 to work with ADC and DMA both in circular mode to perform DSP processing in a basic way (see picture).
DMA fills ADC buffer, on "ADC half full" interrupt we process samples inBuffer[0..half] and put it in outBuf[0..half].
Output buffer is also fed to timer-driven PWM via DMA.
My thought is: TIM1 prescaller is (2-1), period is (1024-1), which with 72MHz timer clock has to make 35156 kHz 10-bit PWM. With buffer length of 512 samples buffer is 14,5ms long. I wait for DMA to sample 256 samples every 7,25ms, pushing out 256 samples to PWM in total pointer-to-pointer sync.
The problem is that I'm not sure if ADC and PWM buffer are syncronised.
With following code i get this:
void dspStart(void){
HAL_TIM_Base_Start(&htim1); // start timer
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); //start PWM which triggers ADC
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)inBuf, ADC_BUFFER_LEN); // start DMA w/ ADC
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_3, (uint32_t*)outBuf, ADC_BUFFER_LEN); // start PWM output
}
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc1){
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_3); // yellow channel
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc1){
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_3); // yellow channel
}
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim1){
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_4); // blue channel
};
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim1){
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_4); // blue channel
}
ADC and PWM buffers are out of sync, although buffer size is same for both DMA channels and they share same TIM1.
What did I do wrong?
STM32cubeMX setting are following:

Related

STM32 Sine Wave works but Audio file produces static from DAC

I'm trying to play a simple WAV file using the internal DAC on the STM32F466RE. I have the DMA, writing to a dac by an interval set by Timer4. Using a Sine Wave, this works perfectly:
Sine Wav Example (This works fine):
const uint16_t sine_wave_array[32] = {2047, 1648, 1264, 910, 600, 345,
156, 39, 0, 39, 156, 345,
600, 910, 1264, 1648, 2048, 2447,
2831, 3185, 3495, 3750, 3939, 4056,
4095, 4056, 3939, 3750, 3495, 3185,
2831, 2447};
HAL_DAC_Start(&hdac,DAC_CHANNEL_1);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave_array, 32, DAC_ALIGN_12B_R);
HAL_TIM_Base_Start(&htim4);
//timer config
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 1136; // (16MHZ /(32 * 440hz))
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
//DAC Setup from Stm32cubeIDE
static void MX_DAC_Init(void)
{
/* USER CODE BEGIN DAC_Init 0 */
/* USER CODE END DAC_Init 0 */
DAC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN DAC_Init 1 */
/* USER CODE END DAC_Init 1 */
/** DAC Initialization
*/
hdac.Instance = DAC;
if (HAL_DAC_Init(&hdac) != HAL_OK)
{
Error_Handler();
}
/** DAC channel OUT1 config
*/
sConfig.DAC_Trigger = DAC_TRIGGER_T4_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN DAC_Init 2 */
/* USER CODE END DAC_Init 2 */
}
The DAC is set to use DMA Stream 5. The DMA is set to half-word (16 bit). The MCU is at 16 MHZ. Everything sounds good.
So now I'm trying to replace that sine_wave with an audio file (headers removed) sampled at 44.1khz form here: hello.h
Here's the modified code:
#include "helo.h"
//HELLO_LENGTH is 57890
HAL_DAC_Start(&hdac,DAC_CHANNEL_1);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)hello, HELLO_LENGTH, DAC_ALIGN_12B_R);
HAL_TIM_Base_Start(&htim4);
htim4.Init.Period = 362; // (16MHZ /44100)
I also changed the DMA Buffer from Half-word to Byte (8 bit). However, I'm just hearing static now from the speaker. But the static has a repetition to it, almost like the sound is there but all garbled.
I also tried with my own audio file (with headers removed, only the data portion) and I still get the same thing.
Any ideas?
Try to go one step at a time! You have massively ramped up the data rate, and changed the data format and source all at once.
First try the sine wave at the increased rate and verify that you get sensible output. Try different frequencies of sine wave. Start with a low frequency like 1kHz and work up. 44.1kHz sample rate should be able to get close to 20kHz signal.
Each time verify on the oscilloscope that the signal looks correct. If you don't have an oscilloscope then connect up a speaker and listen to the audio with a frequency monitoring app on your phone (eg: this one), or else maybe even an electronic guitar tuner.
Before you hook up a speaker think about how you will filter out the DC bias and amplify the signal. If you don't know what that means then you will have to ask on electronics stack exchange.
I expect that you may find that you are trying to run the sample rate faster that the DAC can go. Internal DACs are not usually meant for audio, also they are very non-linear.
Once you have found a sample rate that works then create a data file in that format. Remember that for wav files the data may be in unsigned or signed integer format. If it is signed then you need to convert to unsigned by adding a bias. Also think about endianness.

STM32 Use DMA to generate bit pattern on GPIO PIN

I am trying to generate a bit pattern on a GPIO pin. I have set-up the DMA engine to transfer from an array of GPIO pin states to the GPIO BSRR register
Here is the code I am using to configure the DMA
hdma_tim16_ch1_up.Instance = DMA1_Channel3;
hdma_tim16_ch1_up.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_tim16_ch1_up.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim16_ch1_up.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim16_ch1_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.Mode = DMA_NORMAL;
hdma_tim16_ch1_up.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_tim16_ch1_up) != HAL_OK)
{
Error_Handler();
}
/* Several peripheral DMA handle pointers point to the same DMA handle.
Be aware that there is only one channel to perform all the requested DMAs. */
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim16_ch1_up);
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_UPDATE],hdma_tim16_ch1_up);
Here is the code I use to setup the transfer:
uint32_t outputbuffer[] = {
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000,
0x0000100,0x01000000
/* ... */
};
if (HAL_DMA_Start_IT(htim16.hdma[TIM_DMA_ID_UPDATE], (uint32_t)outputbuffer, (uint32_t)&GPIOG->BSRR, 14) != HAL_OK)
{
/* Return error status */
return HAL_ERROR;
}
__HAL_TIM_ENABLE_DMA(&htim16,TIM_DMA_UPDATE);
HAL_TIM_Base_Start_IT(&htim16);
I am expecting to see every time the counter overflows, the DMA transfers 32 bits from the array and increments to the next array position until the DMA CNDTR register reads 0.
I set up a GPIO pin to toggle every time the timer over flows and I setup an alternating bit pattern in the array. I would expect the two GPIO pins to be similar in their output shape but I get one longer pulse on the line connected to the DMA. Any tips would be greatly appreciated
configure TIM2 as input capture direct mode (TIM2_CH1)
configure TIM2 DMA direction "memory to peripheral"
configure TIM2 data width Half word / Half word
configure GPIO pins as GPIO_OUTPUT, for example 16 pins GPIOD0..GPIOD15
copy and paste HAL_TIM_IC_Start_DMA() function from HAL library and give it a new name MY_TIM_IC_Start_DMA()
find HAL_DMA_Start_IT() function call in MY_TIM_IC_Start_DMA()
replace (uint32_t)&htim->Instance->CCR1 with (uint32_t)&GPIOD->ODR
if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)&GPIOD->ODR, (uint32_t)pData, Length) != HAL_OK)
Now you can start DMA to GPIO transfer by calling
MY_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1,(uint32_t*)gpioBuffer,GPIO_BUFFER_SIZE);
Actual transfer must be triggered by providing pulses on TIM2_CH1 input pin (for example, by using output compare pin from other timer channel). Those pulses originally was used to save Timer2 CCR1 register values to DMA buffer. Code was tweaked to transfer DMA buffer value to GPIOD ODR register.
For GPIO to Memory transfer change TIM2 DMA direction to "peripheral to memory", configure GPIO pins as GPIO_INPUT and use GPIOD->IDR instead of ODR in HAL_DMA_Start_IT parameters in modified MY_TIM_IC_Start_DMA() function.

two wire ADC, SPI read

I use Arduino UNO (Atmega328) and a 12bit ADC component (AD7893ANZ-10), datasheet available on https://www.analog.com/media/en/technical-documentation/data-sheets/AD7893.pdf
The problem:
I tried already few different codes but the read value is wrong always, even when the SDATA pin of ADC is not connected to MISO pin of Arduino, I get the same values (See figure1 here). I simulated it in proteus(See figure2 here) and used the virtual serial monitor in proteus. The MOSI and SS pin of Arduino are not connected but I set SS pin in code to LOW and HIGH to fullfill libraries requirements. More information about the timing of ADC is added as comments into the code below. Or availabe in the datasheet. I would be thanksfull if you take a look on it due I cant figure out what I did wrong.
PS: The ADC has just to pins to communicate with SPI: 1.SDATA(slaveout) and 2.SCLK. The pin CONVST on ADC is to initiate a conversion.
#include <SPI.h>
//source of code https://www.gammon.com.au/spi
void setup() {
Serial.begin (115200);
pinMode(7, OUTPUT); // this pin is connected to convst on adc to initiate conversion
// Put SCK, MOSI, SS pins into output mode (introductions from source)
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
pinMode(SCK, OUTPUT);
pinMode(MOSI, OUTPUT);
pinMode(SS, OUTPUT);
digitalWrite(SS, HIGH);
digitalWrite(SCK, LOW);
digitalWrite(MOSI, LOW);
SPCR = (1<<MSTR);
SPI.begin ();
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE1)); // set the clk frequency; take the MSB first;
//mode1 for CPOL=0 and CPHA=1 according to datasheet p.9 "CPOL bit set to a logic zero and its CPHA bit set to a logic one."
}
int transferAndWait (const byte what) //methode to read data
{
int a = SPI.transfer(what); //send zero(MOSI not connected)and read the first 8 bits and save
Serial.println (a); // show the value, in serial monitor -1
a =(a << 8); //shift the saved first 8 bits to left
Serial.println (a); // show the value, in serial monitor 255
a = a | SPI.transfer(what); //read and save the last 8 bits
Serial.println (a); // show the value, in serial monitor -256
delayMicroseconds (10);
return a;
}
void loop() {
int k;
digitalWrite(7, HIGH); //set pin high to initiate the conversion
delayMicroseconds(9); //the conversion time needed, actually 6 mikroseconds
digitalWrite(SS, LOW); // enable Slave Select to get the library working
k = transferAndWait (0); //call the method
delayMicroseconds(1);
digitalWrite(7, LOW);
digitalWrite(SS, HIGH); //disable chip select
delay(2000); //delay just to keep the overview on serial monitor
Serial.println (k); // show the value, in serial monitor -1
}
First the the return variable should be an unsigned int instead of a signed int.
Also the CONVST should only be low for a short period as conversion is started afterwards. See Timing sequence.

Is the sampling data missing or incorrect in DMA memory buffer, when using ADC with DMA circle mode?

My purpose is sampling signal by ADC channel with DMA data moving in STM32Fx board. Generate a square wave to ADC channel. If using DMA mode, some data is out of order or called mess. Same result happened on STM32F207 and STM32F373 board.
(1) When I collect converted data by using ADC EOC interrupt, the data array looks like a square wave. This is OK.
(2) I would like to try DMA circle instead of EOC IRQ, but the data array seems to mess up, some data missing or incorrect. It could worse if sampling rate was increasing. Below are my test results.
The picture shows my test result: EOC IRQ vs DMA circle mode
EOC IRQ vs DMA with sampling 62.5KHz The waveform in DMA became shorter.
The picture shows very worse DMA with sampling 200KHz The data on DMA mode mess up, but it's consistent by using ADC EOC IRQ.
enter code here
<<<< ADC config >>>>
/* ADC Common Init */
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div8;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_TwoSamplingDelay =
ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
/* ADC1 DeInit */
ADC_StructInit(&ADC_InitStructure);
/* Configure the ADC1 in continuous mode */
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channels 6 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1,
ADC_SampleTime_480Cycles);
ADC_EOCOnEachRegularChannelCmd(ADC1, ENABLE);
#ifdef __DMA_ENABLE__
/* Enable DMA request after last transfer (Single-ADC mode) */
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
/* Enable ADC1 DMA since ADC1 is the Master*/
ADC_DMACmd(ADC1, ENABLE);
#else
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
ADC_ITConfig(ADC1, ADC_IT_OVR, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
<<<< DMA config >>>>
/* DMA1 clock enable */
RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_DMA2, ENABLE );
/* DMA1 Channel1 Config */
DMA_DeInit(DMA2_Stream0);
DMA_DoubleBufferModeConfig(DMA2_Stream0, (uint32_t)ADCValB, DMA_Memory_1);
DMA_DoubleBufferModeCmd(DMA2_Stream0, ENABLE);
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCValA;
DMA_InitStructure.DMA_PeripheralBaseAddr = ((uint32_t) ADC1) + 0x4C;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = NUM_OF_ADC; // 512 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_Medium;
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(DMA2_Stream0, &DMA_InitStructure);
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* DMA1 Channel1 enable */
DMA_Cmd(DMA2_Stream0, ENABLE);
Finally, I expect the result should be same as that using ADC EOC IRQ.
I have encountered the same problem, as a part of my complicated application, the ADC reads continuously an input PWM data and configured with DMA request in circular mode.
First, I tried to store the converted data in a memory buffer at each End Of Conversion Interrupt. This works well, so I am confident that the ADC is correctly converting the data.
Second, I started the ADC with DMA request in circular mode, (for each End Of Conversion Interrupt, the DMA handler get the ADC converted data and store in a memory buffer). At this stage, when I check the memory buffer via the Debugger - It seams that the data are messed and it's like the DMA was skipping values).
Third, I just wanted to verify, if it's a DMA problem or it's a Debugger one. I started the ADC with DMA request in "normal" mode. And all of a sudden, when opening the Debugger, the memory buffer data are correctly stored.
To summarize, the main difference between the second and the third method when opening the Debugger, is that the DMA handler is still running (in circular mode) and as a result the Debugger can't be able to show correctly the memory buffer data, due to the speed of the ADC conversion time/ DMA Handler requests (~1 µs). And in the other hand (in normal mode), the DMA handlers stops once it has completed filling the buffer.
To conclude, the DMA handler works fine and you can output the square wave with the DAC using your buffer. And if you want to view correctly the data using the Debugger you need to stop the DMA (after a period of time; for example).
HAL_ADC_Stop_DMA(&hadc);
Based on the tabular view of the data you provided, it appears that the DMA is configured properly as the data looks nearly identical to the data obtained from ADC EOC IRQ.
The only variable between relying on DMA and an IRQ is that there may be unexpected bus "collisions" between the DMA Controller and the CPU as, unlike when using an IRQ, they are both running concurrently, potentially resulting in wait states.
From the STM32 Reference Manual Section 13.4:
The DMA controller performs direct memory transfer by sharing the system bus with the Cortex-M4 ® F core. The DMA request may stop the CPU access to the system bus for some bus cycles, when the CPU and DMA are targeting the same destination (memory or peripheral).
And your observed sampling rate-dependent degradation in performance certainly corroborates this hypothesis, as the busmatrix must arbitrate more frequent accesses between the DMA Controller and the CPU.
Without seeing the rest of your code that sets up and reads from the buffer, it is hard to say what aspect of your application code may be causing this issue.

Hardware interrupt between Raspberry pi and Atmega 328

I have connected my RPI and atmega328 together in order to control the start of an event on my arduino. In order to do so, GPIO 25 (RPI) is connected directly to pin7 (Arduino PD7). I've got a python script on the RPI witch set the GPIO 25 to high then back to LOW:
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(25, GPIO.OUT)
GPIO.output(25, 1)
#Do some stuff
GPIO.output(25, 0)
The arduino is waiting in a loop for either a physical button to be pressed or the pin7 to be set to HIGH by the RPI:
const int interrupt = 7;
const int button = 13;
const int led = 9;
void setup() {
Serial.begin(9600);
pinMode(interrupt, INPUT);
pinMode(button,INPUT);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
}
void loop() {
bool on = false;
bool buttonOn = false;
while (!on || !buttonOn) {
on = digitalRead(interrupt);
buttonOn = digitalRead(button);
digitalWrite(led, LOW);
}
digitalWrite(led, HIGH);
delay(1000);
}
Now unfortunately this doesn't work. I have checked the logic level of the atmega328 (https://learn.sparkfun.com/tutorials/logic-levels) and it seems that 3.3V is good enough for HIGH signal.
Am I missing something with the pull up /pull down resistance? I know the PD7 on the atmega is specified as follow:
Port D is an 8-bit bi-directional I/O port with internal pull-up
resistors (selected for each bit). The Port D output buffers have
symmetrical drive characteristics with both high sink and source
capability. As inputs, Port D pins that are externally pulled low will
source current if the pull-up resistors are activated. The Port D pins
are tristated when a reset condition becomes active, even if the clock
is not running.
EDIT:
I have done more testing and I am getting the HIGH or LOW value correctly. It seems that the issue comes from the:
while ((!on) || (!buttonOn)) {
Is there an issue with Arduino and the OR operator in a while loop? Even when one condition is true and the other one is false, it never goes out of the loop.
while ((!on) || (!buttonOn)) {
}
That loop will run as long as one of the variables is false.
Yesterday I was for some reason thinking that you were reading the interrupt pin twice when reading your code.
3.3 v output should be ok to turn the Arduino input high.
You can have a wiring problem or your raspberry pi can be so fast that the arduino misses the pulse.
Change your program on the raspberry pi to leave the output high for so long (e.g. 10) seconds that you can measure it with a multimeter to see that you are setting the right pin.
Does the Arduino now see the input?