USB Sensor Node


Board is now set to a 100 × 80 mm, 4-layer stackup with internal GND and power planes. I’m placing the functional blocks next, keeping switching power away from the low-level DDS/analog path.
Placement is complete: USB/power is grouped on the left/lower-left, DDS and low-level analog are central, and the output amplifier sits behind the right-edge BNC. I’m checking and tightening any remaining decoupling-placement issues now.
Created a project doc

PCB Layout Review — Programmable Function Generator

View Document

Board outline set to 100 mm × 80 mm with 2 mm rounded corners. Stackup set to 4 copper layers for USB, SPI, analog output, and multi-rail po...
PCB layout placement is complete on a 100 × 80 mm, 4-layer board: USB/power is on the left, LT3471 power is lower-left, DDS/control/conditioning are central, and the THS3091/BNC output is on the right. I ran placement, DRC, and BOM checks; details are captured in PCB Layout Review.
Open items:
  • Board is not routed yet, so airwires are expected.
  • DRC still reports exposed-pad footprint copper overlap on U3, U6, and U10.
  • Generic resistors/capacitors/inductors still need final orderable MPNs for assembly.
Suggested next steps:
  1. Repair or replace U3/U6/U10 exposed-pad footprints.
  2. Assign real MPNs/packages to generic passives and inductors.
  3. Route the PCB, then re-run full DRC.
Fix footprints and BOM
Fix footprint overlaps
Assign passive MPNs
Route the PCB
Run full checks

Firmware Starter — STM32G0B1CBT6 Function Generator
Platform & Toolchain
  • MCU: STM32G0B1CBT6
  • Framework: STM32 HAL
  • Build system: STM32CubeIDE / CMake / PlatformIO STM32 HAL project
  • USB: USB FS device on PA11/PA12; suggested user interface is USB CDC serial commands.
Pin Mapping

Table


FunctionMCU PinNetConnected ToDirectionNotes
USB DMPA11USB_DMUSB-C D- via 22 ΩUSBUSB FS device
USB DPPA12USB_DPUSB-C D+ via 22 ΩUSBUSB FS device
SPI SCKPA5SPI1_SCKAD9833, AD5290, DAC80501OutputSPI1
SPI MOSIPA7SPI1_MOSIAD9833 SDATA, AD5290 SDI, DAC SDINOutputSPI1
SPI MISOPA6SPI1_MISOAD5290 SDOInputOpen-drain SDO has 10 kΩ pull-up
DDS CSPA4DDS_CSAD9833 FSYNCOutputActive low
Digipot CSPA15DIGIPOT_CSAD5290 CSOutputActive low
DAC CSPB0DAC_CSDAC80501 SYNCOutputActive low
Square outputPA8SQUARE_TIMERSquare attenuatorTimer outputTIM1_CH1 target
Mux enablePB9MUX_ENADG758 ENOutputAlso 10 kΩ pull-up
Mux A0PB6MUX_A0ADG758 A0OutputSelects waveform channel
Mux A1PB7MUX_A1ADG758 A1OutputSelects waveform channel
Mux A2PB8MUX_A2ADG758 A2OutputSelects waveform channel
SWDIOPA13SWDIOSWD headerDebug10-pin SWD
SWCLK/BOOT0PA14SWCLK_BOOT0SWD header, BOOT0 pull-downDebug/boot10 kΩ pull-down
ResetPF2-NRSTNRSTSWD reset, RC resetReset10 kΩ + 100 nF
Register/Device Notes
  • AD9833: 16-bit SPI frames, data clocked on SCLK falling edge; FSYNC low during each 16-bit frame. Frequency word = freq * 2^28 / 25e6.
  • AD5290: 8-bit SPI data, CS rising edge loads wiper register; 0 = minimum, 255 = maximum.
  • DAC80501: 24-bit SPI frames; internal 2.5 V reference enabled by default. Configure gain for 0–2.5 V output range before using offset trim.
  • ADG758: EN high enables mux. Address 0 selects S1 = DDS path. Address 1 selects S2 = square path.
Complete Firmware Source Skeleton

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);
    }
}
Build & Flash
  • Generate STM32CubeMX project for STM32G0B1CBT6 with USB device, SPI1 on PA5/PA6/PA7, TIM1_CH1 on PA8, and GPIO outputs listed above.
  • Build in STM32CubeIDE or with cmake --build if using STM32Cube CMake export.
  • Flash using SWD header J3 with STM32CubeProgrammer or ST-LINK.
  • Platform & Toolchain

  • Pin Mapping

  • Register/Device Notes

  • Complete Firmware Source Skeleton

  • Build & Flash