signal generator for an stm32f446re nucleo board
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add sine wave output with button-controlled frequency selection

DAC CH1 (PA4) outputs a 256-sample sine wave via DMA1/TIM6. Button B1
(PC13) cycles through presets: 100, 310, 1k, 3.1k, 10k Hz. Amplitude
reduced to 1800/2048 to avoid DAC output buffer clipping near VDD rail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+62 -65
+62 -65
Core/Src/main.c
··· 3 3 4 4 #define SINE_SAMPLES 256 5 5 6 + /* Frequency presets in Hz, cycled by the user button */ 7 + static const uint32_t freqs[] = { 100, 310, 1000, 3100, 10000 }; 8 + #define NUM_FREQS 5 9 + static int freq_idx = 0; /* start at 100 Hz */ 10 + 6 11 static uint16_t sine_table[SINE_SAMPLES]; 7 12 8 - /* -------------------------------------------------------------------------- 9 - * clock_init 10 - * 11 - * HSI (16 MHz) -> PLL -> 84 MHz SYSCLK 12 - * PLL: M=16, N=336, P=4 => 16/16 * 336 / 4 = 84 MHz 13 - * APB1 = 84/2 = 42 MHz (DAC, TIM6, USART2) 14 - * APB2 = 84/1 = 84 MHz 15 - * APB1 timer clock = 84 MHz (doubled because APB1 prescaler > 1) 16 - * -------------------------------------------------------------------------- */ 17 13 static void clock_init(void) 18 14 { 19 - /* Voltage scale 3 (supports up to 120 MHz, fine for 84 MHz). 20 - PWR clock must be enabled before touching PWR registers. */ 21 15 RCC->APB1ENR |= RCC_APB1ENR_PWREN; 22 16 PWR->CR = (PWR->CR & ~PWR_CR_VOS) | (1UL << PWR_CR_VOS_Pos); 23 17 24 - /* 2 wait states required for 84 MHz. Must be set before speeding up. */ 25 18 FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_2WS; 26 19 27 - /* Enable HSI and wait for it to be ready */ 28 20 RCC->CR |= RCC_CR_HSION; 29 21 while (!(RCC->CR & RCC_CR_HSIRDY)) {} 30 22 31 - /* Configure PLL (still off at this point) */ 32 - RCC->PLLCFGR = (16 << RCC_PLLCFGR_PLLM_Pos) | /* M: divide HSI down to 1 MHz */ 33 - (336 << RCC_PLLCFGR_PLLN_Pos) | /* N: multiply to 336 MHz VCO */ 34 - (1 << RCC_PLLCFGR_PLLP_Pos) | /* P=4 (01): 336/4 = 84 MHz */ 35 - (2 << RCC_PLLCFGR_PLLQ_Pos) | /* Q: USB etc, unused here */ 36 - (2 << RCC_PLLCFGR_PLLR_Pos); /* R: I2S etc, unused here */ 37 - /* PLLSRC bit = 0 => HSI source (default) */ 23 + RCC->PLLCFGR = (16 << RCC_PLLCFGR_PLLM_Pos) | 24 + (336 << RCC_PLLCFGR_PLLN_Pos) | 25 + (1 << RCC_PLLCFGR_PLLP_Pos) | 26 + (2 << RCC_PLLCFGR_PLLQ_Pos) | 27 + (2 << RCC_PLLCFGR_PLLR_Pos); 38 28 39 29 RCC->CR |= RCC_CR_PLLON; 40 30 while (!(RCC->CR & RCC_CR_PLLRDY)) {} 41 31 42 - /* APB1 prescaler /2 so APB1 peripherals run at 42 MHz. 43 - Timers on APB1 still get 84 MHz (hardware doubles it when prescaler > 1). */ 44 32 RCC->CFGR = RCC_CFGR_PPRE1_DIV2; 45 - 46 - /* Switch SYSCLK to PLL */ 47 33 RCC->CFGR |= RCC_CFGR_SW_PLL; 48 34 while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) {} 49 35 50 36 SystemCoreClock = 84000000; 51 37 } 52 38 53 - /* -------------------------------------------------------------------------- 54 - * dac_dma_init 55 - * 56 - * TIM6 fires at 256 kHz => DMA copies one sample per tick => 57 - * 256-sample table plays back at 256000/256 = 1000 Hz 58 - * -------------------------------------------------------------------------- */ 39 + /* Update TIM6 ARR to produce the requested output frequency. 40 + freq = timer_clock / ((ARR+1) * SINE_SAMPLES) 41 + ARR = timer_clock / (freq * SINE_SAMPLES) - 1 */ 42 + static void set_frequency(uint32_t hz) 43 + { 44 + TIM6->CR1 &= ~TIM_CR1_CEN; /* stop so counter can't overshoot new ARR */ 45 + TIM6->CNT = 0; 46 + TIM6->ARR = (84000000 / (hz * SINE_SAMPLES)) - 1; 47 + TIM6->CR1 |= TIM_CR1_CEN; 48 + } 49 + 59 50 static void dac_dma_init(void) 60 51 { 61 - /* Enable clocks for everything we touch */ 62 52 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_DMA1EN; 63 53 RCC->APB1ENR |= RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN; 64 54 65 - /* PA4 = DAC_OUT1: set to analog mode (MODER = 0b11) so the DAC 66 - drives the pin directly without the GPIO output stage interfering */ 55 + /* PA4 = DAC_OUT1: analog mode */ 67 56 GPIOA->MODER |= (3U << (4 * 2)); 68 57 69 - /* TIM6 --------------------------------------------------------------- 70 - Counter clock = APB1 timer clock = 84 MHz 71 - Sample rate = 84 MHz / (PSC+1) / (ARR+1) = 84 MHz / 1 / 328 = ~256 kHz 72 - TRGO fires on every update event, which triggers the DAC */ 58 + /* TIM6: TRGO on update, frequency set via set_frequency() */ 73 59 TIM6->PSC = 0; 74 - TIM6->ARR = 3279; /* reload value: 84MHz/3280 = ~25.6kHz, /256 samples = ~100Hz */ 75 - TIM6->CR2 = (2 << TIM_CR2_MMS_Pos); /* MMS=010: Update -> TRGO */ 60 + TIM6->CR2 = (2 << TIM_CR2_MMS_Pos); /* MMS=010: Update -> TRGO */ 76 61 77 - /* DMA1 Stream5 Channel7 (hardwired to DAC channel 1 in the DMA mux) -- 78 - Memory (sine_table) -> Peripheral (DAC->DHR12R1) 79 - Circular: wraps back to the start after SINE_SAMPLES transfers */ 62 + /* DMA1 Stream5 Ch7: sine_table -> DAC->DHR12R1, circular, halfword */ 80 63 DMA1_Stream5->CR = 0; 81 - while (DMA1_Stream5->CR & DMA_SxCR_EN) {} /* wait for any prior EN to clear */ 64 + while (DMA1_Stream5->CR & DMA_SxCR_EN) {} 82 65 83 - DMA1_Stream5->CR = (7 << DMA_SxCR_CHSEL_Pos) | /* channel 7 */ 84 - DMA_SxCR_DIR_0 | /* memory -> peripheral */ 85 - DMA_SxCR_CIRC | /* circular mode */ 86 - DMA_SxCR_MINC | /* increment memory pointer */ 87 - DMA_SxCR_MSIZE_0 | /* memory word = 16 bit */ 88 - DMA_SxCR_PSIZE_0; /* periph word = 16 bit */ 66 + DMA1_Stream5->CR = (7 << DMA_SxCR_CHSEL_Pos) | 67 + DMA_SxCR_DIR_0 | 68 + DMA_SxCR_CIRC | 69 + DMA_SxCR_MINC | 70 + DMA_SxCR_MSIZE_0 | 71 + DMA_SxCR_PSIZE_0; 89 72 90 - DMA1_Stream5->NDTR = SINE_SAMPLES; /* number of transfers */ 91 - DMA1_Stream5->PAR = (uint32_t)&DAC->DHR12R1; /* destination: DAC data reg*/ 92 - DMA1_Stream5->M0AR = (uint32_t)sine_table; /* source: our table */ 73 + DMA1_Stream5->NDTR = SINE_SAMPLES; 74 + DMA1_Stream5->PAR = (uint32_t)&DAC->DHR12R1; 75 + DMA1_Stream5->M0AR = (uint32_t)sine_table; 93 76 DMA1_Stream5->CR |= DMA_SxCR_EN; 94 77 95 - /* DAC channel 1 ------------------------------------------------------- 96 - TEN1 = trigger enable 97 - TSEL1 = 000 (default) = TIM6 TRGO triggers each conversion 98 - DMAEN1 = DAC issues a DMA request on each trigger 99 - BOFF1 = 0 (default) = output buffer ON, drives PA4 directly 100 - EN1 = channel enable */ 101 78 DAC->CR = DAC_CR_TEN1 | DAC_CR_DMAEN1 | DAC_CR_EN1; 102 79 103 - /* Start the timer last so everything is ready when samples start flowing */ 104 - TIM6->CR1 = TIM_CR1_CEN; 80 + set_frequency(freqs[freq_idx]); 81 + } 82 + 83 + static void button_init(void) 84 + { 85 + /* B1 is on PC13, active low (external pull-up on Nucleo) */ 86 + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; 87 + /* PC13 is input by default (MODER=00), nothing else needed */ 105 88 } 106 89 107 90 int main(void) 108 91 { 109 - /* Blink LD2 (PA5) 5 times to confirm the MCU is executing */ 92 + /* Blink LD2 (PA5) to confirm the MCU is executing */ 110 93 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; 111 94 GPIOA->MODER = (GPIOA->MODER & ~(3U << (5*2))) | (1U << (5*2)); 112 95 for (int b = 0; b < 5; b++) { ··· 118 101 119 102 clock_init(); 120 103 121 - /* Build sine table at runtime: 12-bit unsigned, centred at mid-scale. 122 - Output swings 0-3.3V (or 0-Vref) peak-to-peak */ 123 104 for (int i = 0; i < SINE_SAMPLES; i++) 124 - sine_table[i] = (uint16_t)(2048 + 2047.0f * sinf(2.0f * 3.14159265f * i / SINE_SAMPLES)); 105 + sine_table[i] = (uint16_t)(2048 + 1800.0f * sinf(2.0f * 3.14159265f * i / SINE_SAMPLES)); 125 106 126 107 dac_dma_init(); 108 + button_init(); 109 + 110 + uint32_t last_btn = 1; /* 1 = not pressed (active low) */ 127 111 128 - while (1) {} 112 + while (1) { 113 + uint32_t btn = (GPIOC->IDR >> 13) & 1; 114 + 115 + if (last_btn == 1 && btn == 0) { 116 + /* falling edge: button just pressed */ 117 + freq_idx = (freq_idx + 1) % NUM_FREQS; 118 + set_frequency(freqs[freq_idx]); 119 + 120 + /* debounce: wait for release before allowing next press */ 121 + while (((GPIOC->IDR >> 13) & 1) == 0) {} 122 + } 123 + 124 + last_btn = btn; 125 + } 129 126 } 130 127 131 128 void Error_Handler(void)