# Why is my output signal aliased when my sampling rate is decently above the Nyquist theorem?

I'm really confused on what's happening here.

I have my ADC sampling at 44.410kHz and any frequency above 11Khz it would alias the output signal.

The max target frequency is 20kHz for clarification.

What I have checked so far is:

• if the ADC is actually sampling at 44.410kHz. Checked & good.
• if the DAC is bottle necking in any sort of way. I found this. STM32L43KC
• Checked my scope sampling rate. Used a trigger to get the most out of the sampling. Still the samething.

Mentioned in the datasheet on page 128:

$$\t_{SAMP} typical = 2us\$$ which should be good as the DAC is outputting at 44.410kHz which is 22us.

So I dont understand as to why my DAC is behaving like this.

Notes:

If you are curious as how I checked if the ADC is good, is two ways.

• In programming personally checked the register values
• Sampling at 44.410kHz, 4 samples in should finish around ~90us. Checked with scope and confirmed.

Pictures:

Code:

#include "main.h"
#include <stdint.h>

void init_Interrupt(void);
void init_Clock(void);
void init_Interrupt(void);
void init_DAC(void);
void init_GPIO_Test(void);
void init_Debug(void);
void init_Timer(void);

char buffer[20] = "ADC Value:     \n\r";

typedef struct PLL{
uint8_t PLLN;
uint8_t PLLR;
uint8_t PLLM;
uint8_t PLLSAI1N;
uint8_t PLLSAI1R;
} PLL;

PLL find_PLL(uint32_t, uint32_t);
PLL CFGR;

uint8_t escape = 0;
uint8_t half_transfer_complete = 0;
uint8_t transfer_complete = 0;
uint32_t PLLN_MAX = 86;
uint32_t PLLSAI1N_MAX = 86;
uint32_t PLLR_MAX = 8;
uint32_t PLLSAI1R_MAX = 8;
uint32_t PLLM_MAX = 8;
uint32_t CPU_Speed = 80000000;
uint16_t ADC_Value[8]; //  Hold 8 Samples

void DMA2_Channel3_IRQHandler(void){

if (((DMA2->ISR) & (DMA_ISR_HTIF3)) != 0){
half_transfer_complete = 1;
DMA2->IFCR |= DMA_IFCR_CHTIF3;
} else if (((DMA2->ISR) & (DMA_ISR_TCIF3)) != 0){
transfer_complete = 1;
DMA2->IFCR |= DMA_IFCR_CTCIF3;
}
}

int main(void) {

init_Clock();
//init_Debug();
init_DAC();
init_GPIO_Test();
init_Interrupt();
init_Timer();

while (1) {

if (half_transfer_complete == 1){
TIM6 ->CR1 |= TIM_CR1_CEN;
GPIOA->BSRR |= GPIO_BSRR_BS0;

half_transfer_complete = 0;
}

if (transfer_complete == 1){
GPIOA->BSRR |= GPIO_BSRR_BR0;

transfer_complete = 0;
}
};
}

PLL find_PLL(uint32_t CPU_Speed, uint32_t ADC_Speed) {

PLL settings;

for (int PLLN = 8; PLLN <= PLLN_MAX; PLLN ++){
if (escape == 1){
break;
}
for (int PLLM = 1; PLLM <= PLLM_MAX; PLLM ++){
if (escape == 1){
break;
}
for (int PLLR = 2; PLLR <= PLLR_MAX ; PLLR +=2){
if ((((4000000/PLLM) * PLLN) >= 64000000) & (((4000000/PLLM) * PLLN) <= 344000000)){
if (((4000000/PLLM) >= 4000000) & ((4000000/PLLM) <= 16000000)) {
if (((((4000000/PLLM)*PLLN)/PLLR) >= 8000000) & ((((4000000/PLLM)*PLLN)/PLLR) <= 80000000)){
uint32_t PLL_CALC = (((4000000/PLLM)*PLLN)/PLLR);
if (PLL_CALC == CPU_Speed){
settings.PLLM = PLLM;
settings.PLLR = PLLR;
settings.PLLN = PLLN;
escape = 1;
break;
}
}
}
}

}
}
}

escape = 0;

for (int PLLSAI1N = 8; PLLSAI1N <= PLLSAI1N_MAX; PLLSAI1N ++){
if (escape == 1){
break;
}
for (int PLLSAI1R = 2; PLLSAI1R <= PLLSAI1R_MAX; PLLSAI1R += 2){
if ((((4000000/settings.PLLM) * PLLSAI1N) >= 64000000) & (((4000000/settings.PLLM) * PLLSAI1N) <= 344000000)){
if (((((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R) >= 8000000) & ((((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R) <= 80000000)){
uint32_t PLLSAI1_CALC = (((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R);
settings.PLLSAI1R = PLLSAI1R;
settings.PLLSAI1N = PLLSAI1N;
escape = 1;
break;
}
}

}
}
}
return settings;
}

//Pin - A6

RCC   -> AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_ADCEN;
RCC   -> AHB1ENR |= RCC_AHB1ENR_DMA2EN;

GPIOA -> MODER &= ~GPIO_MODER_MODE7;
GPIOA -> MODER |=  GPIO_MODER_MODE7_Analog; //PIN A6

// 16-bit @ Sampling ~44.410kHZ
//Holding 8 samples at a time
// 16-bit = 2 byte * 8 =  16 bytes
DMA2_Channel3 -> CCR |= (DMA_CCR_PSIZE_16_Bit) |
(DMA_CCR_MSIZE_16_Bit)   |
(DMA_CCR_MINC)           |
(DMA_CCR_CIRC)           |
(DMA_CCR_TCIE)           |
(DMA_CCR_HTIE)           |
(DMA_CCR_PL_Very_High);
DMA2_CSELR    -> CSELR &= ~DMA_CSELR_C3S;
DMA2_Channel3 -> CNDTR |= 0x08;
DMA2_Channel3 -> CCR |= DMA_CCR_EN;

;

;

}

void init_Clock() {

// |----------------------------------- WAIT STATE: 0 -----------------------------------|
if (CPU_Speed <= 16000000) {

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_0WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_0WS) != FLASH_ACR_LATENCY_0WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 1 -----------------------------------|
} else if (CPU_Speed <= 32000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_1WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_1WS) != FLASH_ACR_LATENCY_1WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 2 -----------------------------------|
} else if (CPU_Speed <= 48000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_2WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_2WS) != FLASH_ACR_LATENCY_2WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 3 -----------------------------------|
} else if (CPU_Speed <= 64000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_3WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_3WS) != FLASH_ACR_LATENCY_3WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 4 -----------------------------------|
} else if (CPU_Speed <= 80000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_4WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_4WS) != FLASH_ACR_LATENCY_4WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

} else{
//Error: Clock Speed too high
}

RCC -> CFGR |= RCC_CFGR_SW_PLL;
PWR -> CR1  &= ~PWR_CR1_VOS_Msk;
PWR -> CR1  |= PWR_CR1_VOS_0;
RCC -> CR   |= RCC_CR_MSIRGSEL | RCC_CR_MSIRANGE_6;

// |----------------------------------- PLLCFGR: R -----------------------------------|
if (CFGR.PLLR == 2){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_2;
} else if (CFGR.PLLR == 4){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_4;
} else if (CFGR.PLLR == 6){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_6;
} else if (CFGR.PLLR == 8){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_8;
}

// |----------------------------------- PLLCFGR: M -----------------------------------|
if (CFGR.PLLM == 1){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLM_Msk;
} else {
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLM_Msk;
RCC -> PLLCFGR |= (CFGR.PLLM-1) << RCC_PLLCFGR_PLLM_Pos;
}

// |----------------------------------- PLLCFGR: N -----------------------------------|
RCC -> PLLCFGR &= ~(RCC_PLLCFGR_PLLN_Msk);
RCC -> PLLCFGR |= ((CFGR.PLLN) << RCC_PLLCFGR_PLLN_Pos) | (RCC_PLLCFGR_PLLREN) | (RCC_PLLCFGR_PLLSRC_MSI);

// |----------------------------------- PLLSAI1CFGR: R -----------------------------------|
if (CFGR.PLLSAI1R == 2){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_2;
} else if (CFGR.PLLSAI1R == 4){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_4;
} else if (CFGR.PLLSAI1R == 6){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_6;
} else if (CFGR.PLLSAI1R == 8){
RCC->PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC->PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_8;
}

// |----------------------------------- PLLSAI1CFGR: N -----------------------------------|
RCC -> PLLSAI1CFGR &= ~(RCC_PLLSAI1CFGR_PLLSAI1N_Msk);
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1REN | (CFGR.PLLSAI1N << RCC_PLLSAI1CFGR_PLLSAI1N_Pos);

RCC -> CR |= RCC_CR_PLLON;
while ((RCC->CR & RCC_CR_PLLRDY) == 0)
;
RCC -> CR |= RCC_CR_PLLSAI1ON;
while ((RCC -> CR & RCC_CR_PLLSAI1RDY) == 0)
;
if ((RCC -> CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL ) {
//Error: Clock Didn't switch
}
}

void init_DAC(){

//Pin A3
RCC   -> APB1ENR1 |= RCC_APB1ENR1_DAC1EN;
RCC   -> AHB2ENR  |= RCC_AHB2ENR_GPIOAEN;
GPIOA -> MODER    &= ~GPIO_MODER_MODE4;
GPIOA -> MODER    |= GPIO_MODER_MODE4_Analog;
DAC1  -> CR       |= DAC_CR_EN1;
}

void init_Interrupt(){

NVIC_EnableIRQ(DMA2_Channel3_IRQn);
NVIC_SetPriority(DMA2_Channel3_IRQn,0);
}

void init_GPIO_Test(){

RCC   -> AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
GPIOA -> MODER &= ~GPIO_MODER_MODE0;
GPIOA -> MODER |= GPIO_MODER_MODE0_Gen_Purpose;
}

short counter = 0;

buffer[14-counter] = (adcValue % 10) + '0';
counter++;
}

if (counter == 0){

buffer[13] = ' ';
buffer[12] = ' ';
buffer[11] = ' ';

} else if (counter == 1){
buffer[13] = ' ';
buffer[12] = ' ';
buffer[11] = ' ';

} else if (counter == 2){
buffer[12] = ' ';
buffer[11] = ' ';

} else if (counter == 3){
buffer[11] = ' ';
}

counter = 0;
}

void init_Debug(){

RCC -> APB1ENR1 |= RCC_APB1ENR1_USART2EN;
RCC -> AHB1ENR  |= RCC_AHB1ENR_DMA1EN;
RCC -> AHB2ENR  |= RCC_AHB2ENR_GPIOAEN;
RCC -> CCIPR    |= RCC_CCIPR_USART2SEL_System_Clock;

GPIOA -> MODER  &= ~GPIO_MODER_MODE2;
GPIOA -> MODER  |= GPIO_MODER_MODE2_Alt_Function;
GPIOA -> AFR[0] |= GPIO_AFRL_AFSEL2_USART2;

DMA1_Channel7 -> CCR  |= DMA_CCR_PL_High     |
DMA_CCR_MSIZE_8_Bit |
DMA_CCR_PSIZE_8_Bit |
DMA_CCR_MINC        |
DMA_CCR_CIRC        |
DMA_CCR_DIR;
DMA1_CSELR    -> CSELR |= DMA_CSELR_C7S_USART2;
DMA1_Channel7 -> CNDTR  = 0x14; // 20
DMA1_Channel7 -> CMAR   = (uint32_t)buffer;
DMA1_Channel7 -> CPAR   = (uint32_t)&USART2 -> TDR;
DMA1_Channel7 -> CCR  |= DMA_CCR_EN;

USART2 -> CR1 &= ~USART_CR1_M1 | ~USART_CR1_OVER16;
USART2 -> CR1 |= USART_CR1_TE;
USART2 -> CR3 |= USART_CR3_DMAT;
USART2 -> BRR = 0x208D;
USART2 -> CR1 |= USART_CR1_UE;
}

void init_Timer(){

RCC -> AHB1ENR  |= RCC_AHB1ENR_DMA1EN;
RCC -> APB1ENR1 |= RCC_APB1ENR1_TIM6EN;

DMA1_Channel3 -> CCR |= DMA_CCR_PL_Very_High |
DMA_CCR_MSIZE_16_Bit |
DMA_CCR_PSIZE_16_Bit |
DMA_CCR_MINC         |
DMA_CCR_CIRC         |
DMA_CCR_DIR;
DMA1_Channel3 -> CNDTR  = 0x08;
DMA1_Channel3 -> CPAR   = (uint32_t)&DAC1->DHR12R1;
DMA1_CSELR    -> CSELR |= DMA_CSELR_C3S_TIM_6_UP;
DMA1_Channel3 -> CCR   |= DMA_CCR_EN;

TIM6 -> DIER |= TIM_DIER_UDE;
TIM6 -> ARR   = 0x708;
TIM6 -> PSC   = 0x0;

}


EDIT: Added a FFT using the 13kHz signal.

EDIT 2: Added a new 13kHz picture but changing the scope's sampling rate to 50kSa/s from the previous higher sampling rate.

• Comments are not for extended discussion; this conversation has been moved to chat. – Voltage Spike Aug 12 at 18:32

Here's a graph I quickly threw together with Excel.

Does it look at all familiar? Maybe just a little like your last scope capture?
That's because this is what you get when you sample a 13kHz sine wave at 44.41kHz.
What you're seeing is exactly what you should expect to see. What you're definitely not seeing there is aliasing.

When you only sample 3 or 4 points for every cycle of the sine wave, you're never going to be able to just push those samples out of a DAC and expect to see something resembling that original sine wave.
You would need to feed your samples through a reconstruction filter, and you can do that in the analog domain after the DAC or in the digital domain before the DAC, or even a bit of both.

• Thank you for the insight, I guess this has to do with my arrogance and wanting to believe that it should start looking like a square at at higher frequencies closer to the 20kHz mark. Why is the lower frequencies so well shaped oppose to the 13kHz one then? – Leoc Aug 12 at 4:05
• He is definitely seeing aliasing, That's a what a reconstruction filter aka output anti-alias filter removes. It's the high frequeny aliases, that occur round multiples of the sample rate, that 'construct' the high frequency artifacts, the square edges etc, of the waveform. Remove the high frequencies, and you're left with the wanted baseband waveform. I would add an answer, but you've done most of the work. I would just like you to change that assertion that it's not aliasing – Neil_UK Aug 12 at 4:22
• @Neil_UK The anti-alias filter goes on the A/D, the reconstruction (anti-image) filter on the D/A. His plots show images (due to lack of reconstruction filter), but not aliasing (since these are pure tones recorded below Nyquist). Difference is that images can be removed while aliases cannot. – user1850479 Aug 12 at 4:28
• @Neil_UK Square edges in this case are a form of "aliasing", sure, but in the context of sampling theory what we're really talking about with Nyquist is foldback. The fidelity of a signal sampled below-near the nyquist frequency will be poor, but it will not suffer folding, or frequency aliasing, which is typically what we're worried about (and I think what brhans was getting at when saying there is no 'aliasing'). – J... Aug 12 at 20:20

In a word - filtering.

In 2 words - brickwall filter.

brhans is on to something when he answered,

When you only sample 3 or 4 points for every cycle of the sine wave, you're never going to be able to just push those samples out of a DAC and expect to see something resembling that original sine wave.

but he's wrong. What you're seeing is an output which has a large amount of energy above the Nyquist limit, due to the step nature of the output. You need to create a high-order lowpass filter at about 20 kHz, and feed the DAC output through that. If you've got, for instance, a 12-bit waveform and you want accuracy at the 1-bit level, you'll need a filter with a response which is down 78 db at 22 kHz. This will take your stepped output and turn it into a nice, smooth sine wave.

The same consideration, of course, applies to inputs - the Nyquist theorem applies specifically to a band-limited signal with no energy above the limit.

The filter gets its name from its response: flat over a larger frequency band, then an almost vertical falloff (in the ideal case), followed by a flat zero response range. It looks like one side of a brick wall.

Since you want a filter which has as large a useful bandwidth as possible, you'll need a high-order filter to minimize the transition bandwidth. CD players, which have to do this, take advantage of the fact that music power levels are much lower at the high end than in the 30 - 3kHz range which is where audible fundamentals occur. As a result, they use filters which are not as good as theory demands, in order to reduce costs. Nonetheless, 5th and 7th order filters are the norm.

Every CD player has such a filter on its outputs, and you need to emulate them.

A signal of frequency f, sampled at rate S, will be indistinguishable from a signal of frequency S-f, or an equal mix of signals at frequencies f and S-f. The latter will in turn be indistinguishable from a ring-modulated signal with a carrier frequency of S/2 and modulation frequency of f-S/2. For frequencies that close to S/2, the output will "look" like a ring-modulated signal because it's visually easier to recognize a signal at frequency S/2 than any nearby frequency.

If one takes a ring-modulated signal whose carrier frequency is C, and whose modulation frequency is M, and filters out all content whose frequency is above C, the result will be a signal at frequency C-M. Plugging in S/2 for C and f-S/2 for M, one will end up with a signal at frequency (S/2)-(f-S/2), i.e. the original signal f.

Yes, you are seeing aliasing, though some folks would insist on a different word for what is mathematically the same thing, 'imaging'

It occurs around every harmonic of the sampling frequency. For instance, with a 13 kHz tone, and 44 kHz sampling frequency (rounded for mathematical simplicity), the output consists of 13 k, 44 ± 13 k = 31 k and 57 k, 2*44 ± 13 k = 75 k and 101 k, etc for all integer multiples of the sampling frequency.

The 'sample-and-hold' action of a DAC, that turns dirac delta impulses into square edge pulses, forms an inherent anti-alias filter, see zero order hold. For extreme oversampling, fs > 100*fsig, this is usually sufficient for an oscilloscope picture.

As soon as the signal frequency approaches any practical fraction of Nyquist, the aliasing becomes apparent. Just as you would never normally use an ADC without a preceding anti-alias filter, you would never normally use a DAC without a following anti-alias (often called reconstruction) filter.

There is a difference in the process of ADC and DAC that complicates the terminology. When doing ADC without an anti-alias filter, the alias frequencies can fall in-band, in the frequency range of the wanted signal, and are impossible to remove with subsequent filtering. When doing DAC with ideal signals, all the aliases fall out of band, at high frequencies, and are easily removed with a reconstruction filter. As these are easy to remove, some people insist they are called 'images', to distinguish them from the more pathological ADC 'aliases'. This is just terminology. The mathematics is identical for both of them.

• Why has this been massively downvoted? It currently has +3/-3. While it is not obligatory to comment on the reason for downvotes it seems to me that on a technical subject like this that downvotes should indicate the belief that the answer is technically erroneous and relevant comment would be of great value. – Russell McMahon Aug 12 at 11:50
• @RussellMcMahon If somebody posts an answer which says "1+1=3, therefore the moon is made of green cheese" do you think downvoters should be required to explain why they downvoted? – alephzero Aug 12 at 12:50
• The downvotes are because aliasing and imaging are two very different phenomena -- you can't use the terms interchangeably, like the first paragraph implies. Aliasing occurs in the ADC, and information is lost. Imaging occurs in the DAC, and creates additional (redundant) information, from which the original signal can be recovered. – Dave Tweed Aug 12 at 12:51
• @Pllsz Aliasing is the wrong word, but you should implement a reconstruction filter. If this is an audio application, it would make more sense to buy a DAC with an integrated reconstruction filter designed for audio. – user1850479 Aug 12 at 13:50
• @Pllsz 'Why is it happenening'. Because an ordinary square-pulse DAC inherently implements a very bad reconstruction filter, so it's good a low frequencies, and awful at high frequencies. All the 'why' is in my and the other answers, but for some reason, you're not grokking it. Look at some waveforms with Excel to get a feel for the effect. Now implement a (slightly) better filter with linear interpolation. Ideally, learn DSP and switch to python, numpy and matplotlib, and implement some better filters. Unfortunately, wikipedia is not a good low level teaching aid for this. – Neil_UK Aug 13 at 1:38