USB Sensor Node
PCB Layout Review — Programmable Function Generator
Table
| Function | MCU Pin | Net | Connected To | Direction | Notes |
|---|---|---|---|---|---|
| USB DM | PA11 | USB_DM | USB-C D- via 22 Ω | USB | USB FS device |
| USB DP | PA12 | USB_DP | USB-C D+ via 22 Ω | USB | USB FS device |
| SPI SCK | PA5 | SPI1_SCK | AD9833, AD5290, DAC80501 | Output | SPI1 |
| SPI MOSI | PA7 | SPI1_MOSI | AD9833 SDATA, AD5290 SDI, DAC SDIN | Output | SPI1 |
| SPI MISO | PA6 | SPI1_MISO | AD5290 SDO | Input | Open-drain SDO has 10 kΩ pull-up |
| DDS CS | PA4 | DDS_CS | AD9833 FSYNC | Output | Active low |
| Digipot CS | PA15 | DIGIPOT_CS | AD5290 CS | Output | Active low |
| DAC CS | PB0 | DAC_CS | DAC80501 SYNC | Output | Active low |
| Square output | PA8 | SQUARE_TIMER | Square attenuator | Timer output | TIM1_CH1 target |
| Mux enable | PB9 | MUX_EN | ADG758 EN | Output | Also 10 kΩ pull-up |
| Mux A0 | PB6 | MUX_A0 | ADG758 A0 | Output | Selects waveform channel |
| Mux A1 | PB7 | MUX_A1 | ADG758 A1 | Output | Selects waveform channel |
| Mux A2 | PB8 | MUX_A2 | ADG758 A2 | Output | Selects waveform channel |
| SWDIO | PA13 | SWDIO | SWD header | Debug | 10-pin SWD |
| SWCLK/BOOT0 | PA14 | SWCLK_BOOT0 | SWD header, BOOT0 pull-down | Debug/boot | 10 kΩ pull-down |
| Reset | PF2-NRST | NRST | SWD reset, RC reset | Reset | 10 kΩ + 100 nF |
freq * 2^28 / 25e6.C
#include "main.h" #include <stdint.h> #include <stdbool.h> #include <math.h> SPI_HandleTypeDef hspi1; TIM_HandleTypeDef htim1; #define DDS_CS_GPIO_Port GPIOA #define DDS_CS_Pin GPIO_PIN_4 #define SQUARE_GPIO_Port GPIOA #define SQUARE_Pin GPIO_PIN_8 #define DIGIPOT_CS_GPIO_Port GPIOA #define DIGIPOT_CS_Pin GPIO_PIN_15 #define DAC_CS_GPIO_Port GPIOB #define DAC_CS_Pin GPIO_PIN_0 #define MUX_A0_GPIO_Port GPIOB #define MUX_A0_Pin GPIO_PIN_6 #define MUX_A1_GPIO_Port GPIOB #define MUX_A1_Pin GPIO_PIN_7 #define MUX_A2_GPIO_Port GPIOB #define MUX_A2_Pin GPIO_PIN_8 #define MUX_EN_GPIO_Port GPIOB #define MUX_EN_Pin GPIO_PIN_9 #define AD9833_MCLK_HZ 25000000UL #define AD9833_B28 0x2000 #define AD9833_RESET 0x0100 #define AD9833_FREQ0 0x4000 #define AD9833_PHASE0 0xC000 #define AD9833_MODE_TRI 0x0002 #define AD9833_SLEEP12 0x0040 static void Error_Handler(void) { __disable_irq(); while (1) {} } static void gpio_write(GPIO_TypeDef *port, uint16_t pin, bool high) { HAL_GPIO_WritePin(port, pin, high ? GPIO_PIN_SET : GPIO_PIN_RESET); } static void spi_tx(uint8_t *data, uint16_t len) { if (HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY) != HAL_OK) Error_Handler(); } static void ad9833_write16(uint16_t word) { uint8_t b[2] = { (uint8_t)(word >> 8), (uint8_t)(word & 0xFF) }; gpio_write(DDS_CS_GPIO_Port, DDS_CS_Pin, false); spi_tx(b, 2); gpio_write(DDS_CS_GPIO_Port, DDS_CS_Pin, true); } static void ad9833_set_frequency(float hz, bool triangle) { if (hz < 1.0f) hz = 1.0f; if (hz > 1000000.0f) hz = 1000000.0f; uint32_t fw = (uint32_t)((hz * 268435456.0f) / (float)AD9833_MCLK_HZ); ad9833_write16(AD9833_B28 | AD9833_RESET | (triangle ? AD9833_MODE_TRI : 0)); ad9833_write16(AD9833_FREQ0 | (fw & 0x3FFF)); ad9833_write16(AD9833_FREQ0 | ((fw >> 14) & 0x3FFF)); ad9833_write16(AD9833_PHASE0); ad9833_write16(AD9833_B28 | (triangle ? AD9833_MODE_TRI : 0)); } static void ad5290_set_wiper(uint8_t value) { gpio_write(DIGIPOT_CS_GPIO_Port, DIGIPOT_CS_Pin, false); spi_tx(&value, 1); gpio_write(DIGIPOT_CS_GPIO_Port, DIGIPOT_CS_Pin, true); } static void dac80501_write24(uint8_t addr, uint16_t data) { uint8_t b[3] = { (uint8_t)(addr & 0x0F), (uint8_t)(data >> 8), (uint8_t)data }; gpio_write(DAC_CS_GPIO_Port, DAC_CS_Pin, false); spi_tx(b, 3); gpio_write(DAC_CS_GPIO_Port, DAC_CS_Pin, true); } static void mux_select(uint8_t channel) { gpio_write(MUX_EN_GPIO_Port, MUX_EN_Pin, true); gpio_write(MUX_A0_GPIO_Port, MUX_A0_Pin, channel & 0x01); gpio_write(MUX_A1_GPIO_Port, MUX_A1_Pin, channel & 0x02); gpio_write(MUX_A2_GPIO_Port, MUX_A2_Pin, channel & 0x04); } static void square_set(float hz, float duty_percent) { if (hz < 1.0f) hz = 1.0f; if (hz > 1000000.0f) hz = 1000000.0f; if (duty_percent < 1.0f) duty_percent = 1.0f; if (duty_percent > 99.0f) duty_percent = 99.0f; uint32_t timer_clk = HAL_RCC_GetPCLK2Freq(); uint32_t period = (uint32_t)((float)timer_clk / hz); if (period < 2) period = 2; __HAL_TIM_SET_AUTORELOAD(&htim1, period - 1); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint32_t)((period * duty_percent) / 100.0f)); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); } void functiongen_set_sine(float hz, uint8_t amplitude_code, uint16_t zero_code) { mux_select(0); // ADG758 S1 = DDS path dac80501_write24(0x08, zero_code); ad5290_set_wiper(amplitude_code); ad9833_set_frequency(hz, false); } void functiongen_set_triangle(float hz, uint8_t amplitude_code, uint16_t zero_code) { mux_select(0); // AD9833 shares same analog VOUT pin dac80501_write24(0x08, zero_code); ad5290_set_wiper(amplitude_code); ad9833_set_frequency(hz, true); } void functiongen_set_square(float hz, float duty_percent, uint8_t amplitude_code, uint16_t zero_code) { square_set(hz, duty_percent); mux_select(1); // ADG758 S2 = attenuated MCU square dac80501_write24(0x08, zero_code); ad5290_set_wiper(amplitude_code); } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef g = {0}; HAL_GPIO_WritePin(GPIOA, DDS_CS_Pin | DIGIPOT_CS_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, DAC_CS_Pin | MUX_EN_Pin, GPIO_PIN_SET); g.Mode = GPIO_MODE_OUTPUT_PP; g.Pull = GPIO_NOPULL; g.Speed = GPIO_SPEED_FREQ_HIGH; g.Pin = DDS_CS_Pin | DIGIPOT_CS_Pin; HAL_GPIO_Init(GPIOA, &g); g.Pin = DAC_CS_Pin | MUX_A0_Pin | MUX_A1_Pin | MUX_A2_Pin | MUX_EN_Pin; HAL_GPIO_Init(GPIOB, &g); } static void MX_SPI1_Init(void) { __HAL_RCC_SPI1_CLK_ENABLE(); hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(&hspi1) != HAL_OK) Error_Handler(); } static void MX_TIM1_Init(void) { __HAL_RCC_TIM1_CLK_ENABLE(); htim1.Instance = TIM1; htim1.Init.Prescaler = 0; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 63; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) Error_Handler(); TIM_OC_InitTypeDef s = {0}; s.OCMode = TIM_OCMODE_PWM1; s.Pulse = 32; s.OCPolarity = TIM_OCPOLARITY_HIGH; s.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim1, &s, TIM_CHANNEL_1) != HAL_OK) Error_Handler(); } int main(void) { HAL_Init(); SystemClock_Config(); // Generate with CubeMX for 64 MHz system clock and USB clock. MX_GPIO_Init(); MX_SPI1_Init(); MX_TIM1_Init(); functiongen_set_sine(1000.0f, 128, 32768); // startup: 1 kHz sine, mid amplitude, nominal zero trim while (1) { // TODO: parse USB CDC commands such as: // WAVE SINE|TRI|SQUARE, FREQ <Hz>, AMP <0..255>, DUTY <1..99>, ZERO <0..65535> HAL_Delay(10); } }
cmake --build if using STM32Cube CMake export.Platform & Toolchain
Pin Mapping
Register/Device Notes
Complete Firmware Source Skeleton
Build & Flash