I found the problem, it was not on the MCU side.
I was initially led to suspect it was bad ADC configuration in the MCU, because enabling or disabling oversampling made a huge difference in the data I was receiving - a difference between some "noise" with lots of extected data, and lots of noise with little expected data, so I though it's probably not the transmission via USART, as the digital signal also looked allright on the scope.
But actually it was a bug on the receiving side's uart reception code which inserted some bogus bytes at random seeming places and wreak havoc on some samples to come.
So, it was another lesson in, as they say: Assume Nothing.
I'm using a Nucleo board with a stm32L476RG MCU.
The HSE I put on there is a Crystal at 12 MHz, using 22pF capacitors, the PLLs are set up like shown below.
This yields 80 MHz for the main clock, and 31,3.. MHz for the ADC2 clock, which is setup to 2.5 cycles samlping time - i.e. with the additional 12.5 cycles mentioned in the reference manual, it's 15 cycles, i.e. a sampling rate of 31,3.. / 15 = 2,08.. Msamples / sec, well in spec for the 4,21 Ms/s stated for the slow ADC channels.
ADC2_CH15 is used, with PB0 configured as "analog adc control".
It is fed a ~ 90 Hz sine of ~ 600mV p-p from a Signal Generator, with DC Offset such that only voltages above 0V are fed.
The DMA2_Ch4 is configured in normal mode to write into a buffer of 40K samples 16bit, i.e. 80KBytes Array. The test program's other RAM usage is small enough such that the 96KB consecutive RAM limit is not reached, if I am interpreting the GCC Output correctly.
After I have gotten the call to HAL_ADC_ConvCpltCallback, I set a flag there and in the main loop, call HAL_ADC_Stop_DMA to make sure it doesn't do anyting anymore (not sure whether necessary).
Then I Transfer the Array in max 64K chunks (Usart_Transmit size param is only uint16) in blocking mode to a PC receiving the data.
The Signal has glitches, the first Image Shows this with 256x oversampling at 8159,72 Hz. With oversampling disabled and pre-divider of 256 instead 1, i.e. at the same sampling rate, it Looks even worse, almost all of the data is 'glitch' and only very Little Signal (no Image shown for this).
The second Image is with 256x oversampling at mentioned rate, but instead of a sine wave, I connected a 10kOhm pot, with A and B to 3V3 and GND resp., and the wiper to PB0 / ADC Input, set such that it gets about 500mV constant voltage.
The Image Shows a (to me) surprizing result. Over 5 seconds duration, ther is this weird square Signal from about 550 to 60000 value range with 16 bit samples.
I do not see this on the scope, connected also to PB0. Removing the scope does not make a difference to the data I get. I have no idea where this is coming from.
So far I have tried 2 different nucleo boards, ADC1 and ADC2, and two different channels and port Pins on ADC1, all had the glitches of the first image.
Any ideas what could be wrong?
For the seconds Image, the values are not actually ranging from 500 to 60000 - that was with dedicated ground connection accidentally removed. With correct ground, this square wavy looking curve is still there, but values are from about 58000..61000 or so.
GCC link output:
text data bss dec hex filename
64024 1096 82552 147672 240d8 hw-test.elf
RCC_OscInitStruct.PLL.PLLM = 3;
RCC_OscInitStruct.PLL.PLLN = 40;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLP = 7;
RCC_OscInitStruct.PLL.PLLQ = 2;
For the ADC2, the PLLSAI2:
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI2;
PeriphClkInit.PLLSAI2.PLLSAI2R = RCC_PLLR_DIV6;
PeriphClkInit.PLLSAI2.PLLSAI2N = 47;
PeriphClkInit.PLLSAI2.PLLSAI2P = RCC_PLLP_DIV7;
PeriphClkInit.PLLSAI2.PLLSAI2ClockOut = RCC_PLLSAI2_ADC2CLK;
ADC DMA Setup:
AdcDmaHandle.Instance = DMA2_Channel4;
AdcDmaHandle.Init.Request = DMA_REQUEST_0;
AdcDmaHandle.Init.Direction = DMA_PERIPH_TO_MEMORY;
AdcDmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
AdcDmaHandle.Init.MemInc = DMA_MINC_ENABLE;
AdcDmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
AdcDmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
AdcDmaHandle.Init.Mode = DMA_NORMAL;
AdcDmaHandle.Init.Priority = DMA_PRIORITY_HIGH;
AdcHandle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; //ADC_CLOCK_ASYNC_DIV10;
AdcHandle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV256;
AdcHandle.Init.Resolution = ADC_RESOLUTION_12B; // 12-bit resolution for converted data
AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // Right-alignment for converted data
AdcHandle.Init.ScanConvMode = DISABLE; // Sequencer disabled (ADC conversion on only 1 channel: channel set on rank 1)
AdcHandle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; // EOC flag picked-up to indicate conversion end
AdcHandle.Init.LowPowerAutoWait = DISABLE; // Auto-delayed conversion feature disabled
AdcHandle.Init.ContinuousConvMode = ENABLE; // Continuous mode enabled (automatic conversion restart after each conversion)
AdcHandle.Init.NbrOfConversion = 1; // Parameter discarded because sequencer is disabled
AdcHandle.Init.DiscontinuousConvMode = DISABLE; // Parameter discarded because sequencer is disabled
AdcHandle.Init.NbrOfDiscConversion = 1; // Parameter discarded because sequencer is disabled
AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START; // Software start to trig the 1st conversion manually, without external event
AdcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; // Parameter discarded because software trigger chosen
AdcHandle.Init.DMAContinuousRequests = ENABLE; // DMA circular mode selected
AdcHandle.Init.Overrun = ADC_OVR_DATA_PRESERVED; // ADC_OVR_DATA_OVERWRITTEN // DR register is overwritten with the last conversion result in case of overrun
AdcHandle.Init.OversamplingMode = ENABLE;
AdcHandle.Init.OversamplingMode = DISABLE;// ENABLE;// DISABLE; /* No oversampling */
// Note: This parameter can be modified only if there is no conversion is ongoing (both ADSTART and JADSTART cleared)
AdcHandle.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_256;// ADC_OVERSAMPLING_RATIO_32; // ADC_OVERSAMPLING_RATIO_256;
AdcHandle.Init.Oversampling.OversamplingStopReset = 0;
AdcHandle.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_4; // ADC_RIGHTBITSHIFT_NONE;
AdcHandle.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;