Chat


How can I help you?

Explain project
Write a project description
Find schematic issues

U1
Manufacturer Part Number
ATMEGA328PB-AU
C22
Capacitance
10nF
C13
Capacitance
100nF
C16
Capacitance
10nF
C6
Capacitance
100nF
C15
Capacitance
100nF
C23
Capacitance
10nF
C7
Capacitance
22pF
C8
Capacitance
22pF
C17
Capacitance
100nF
C26
Capacitance
100nF
C19
Capacitance
100nF
U4
Manufacturer Part Number
TC4427AEOA
C20
Capacitance
10µF
C14
Capacitance
10µF
C25
Capacitance
10µF
C4
Capacitance
10µF
C12
Capacitance
10µF
C18
Capacitance
10µF
U2
Manufacturer Part Number
TPS54360BDDA
Q2
Q3
D4
Manufacturer Part Number
SMBJ30CA/TR13
L1
Inductance
10µH
U5
Manufacturer Part Number
Q1
5V
L2
Manufacturer Part Number
HZ0805E601R-10
R18
Y1
12V
F1
R6
Resistance
22 Ω
R11
Resistance
100 Ω
R7
Resistance
10kΩ
R8
Resistance
10 Ω
5V
12V
5V
12V
R17
Resistance
10kΩ
R9
Resistance
220 Ω
R1
Resistance
10kΩ
R12
Resistance
10kΩ
R10
Resistance
10kΩ
12V
C29
Capacitance
100µF
R16
MCU XTAL 1 (7)
C3
Capacitance
220uF
C1
Capacitance
470uF
J5
C2
Capacitance
100µF
C27
Not Recommended for New Designs
MCU RESET (29)
D1
L3
C10
D5
Capacitance
Capacitance
J3
D7
Capacitance
Capacitance
MCU RESET (29)
MCU RX (30)
MCU RESET (29)
MCU XTAL 1 (7)
J1
D6
Capacitance
Capacitance
C5
Capacitance
100µF
MCU TX (31)
J2
MCU RX (30)
MCU TX (31)
J4
C31
Capacitance
22uF
R3
C11
Capacitance
0.1uF
D2
R14
Resistance
1kΩ
J6
R19
R15
Resistance
1kΩ
J7
C21
Capacitance
1000pF
R5
Resistance
4.7kΩ
R13
U3
Manufacturer Part Number
-3.3
D3
Manufacturer Part Number
DSK34
R4
R2
C28
C9
Capacitance
100pF
Firmware Starter — ATMEGA328PB-AU
Platform & Toolchain
  • MCU: ATMEGA328PB-AU
  • Framework: AVR-GCC bare-metal C for exact port/pin control
  • Build system: Makefile using avr-gcc and avrdude
  • Clock: External crystal/resonator network on PB6/PB7; set F_CPU to the actual programmed fuse clock. The schematic indicates a 16 MHz crystal/resonator path.
  • Programmer: AVR ISP through J7. Confirm J7 pin order against the physical ISP cable before flashing.
Pin Mapping

Table


FunctionMCU pin / register bitSchematic netConnected toDirectionNotes
UART RXPD0 / RXD0Net 36J6 Pin 3, J5 Pin 2InputSerial receive
UART TXPD1 / TXD0Net 24J6 Pin 2, J5 Pin 1OutputSerial transmit
Load 3 controlPD7Net 31R11 -> Q3 gate networkOutputLow-side output J4
Load 2 controlPB0Net 32R9 -> Q2 gate networkOutputLow-side output J3
Load 1 controlPB1Net 26U4 IN_AOutputGate-driver input for Q1/J2
LED/status 1PB2Net 38R13 -> LED1OutputStatus LED
LED/status 2 / SPI MOSIPB3 / MOSI0Net 19J7 Pin 4, R14 -> LED3OutputShared with programming header
External interface / SPI MISOPB4 / MISO0Net 21J6 Pin 1Input by defaultAlso programmer signal if used as ISP MISO
Indicator / SPI SCKPB5 / SCK0Net 18J7 Pin 3, R5 -> D7 pathOutputShared with programming header
Sensor A ADCPC0 / ADC0Net 13U7 VOUTAAnalog inputPressure/flow channel A
Sensor B ADCPC1 / ADC1Net 46U7 VOUTBAnalog inputPressure/flow channel B
ResetPC6 / RESETNet 20R1, C17, J7 Pin 5Reset inputExternal reset / programmer reset
AREFAREFNet 16C16Analog referenceDecoupled to GND
AVCCAVCCNet 17L2/C15 filtered 3.3 VPowerADC supply
VCCVCCNet 473.3 V railPowerMain logic supply
GNDGNDGNDBoard groundPowerCommon ground
Safety policy defaults
  • All load outputs start OFF at reset.
  • Ozone/high-energy output should remain disabled until pressure/flow readings are valid.
  • This starter does not define final pressure thresholds because the mechanical pressure range and sensor calibration are not documented yet.
  • Treat Q1/J2 as the likely main ozone/HV output until confirmed.
Project setup
Makefile

Makefile


MCU=atmega328pb
F_CPU=16000000UL
CC=avr-gcc
OBJCOPY=avr-objcopy
AVRDUDE=avrdude
PROGRAMMER=usbasp
PORT=usb
CFLAGS=-mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -Wextra -std=gnu11

all: main.hex

main.elf: main.c
	$(CC) $(CFLAGS) -o $@ $<

main.hex: main.elf
	$(OBJCOPY) -O ihex -R .eeprom $< $@

flash: main.hex
	$(AVRDUDE) -c $(PROGRAMMER) -p m328pb -P $(PORT) -U flash:w:main.hex:i

clean:
	rm -f main.elf main.hex
Install dependencies on Debian/Ubuntu:

Bash


sudo apt-get update
sudo apt-get install gcc-avr avr-libc avrdude make
Build and flash:

Bash


make
make flash
Complete starter firmware
Save as main.c next to the Makefile.

C


#define F_CPU 16000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

// -----------------------------------------------------------------------------
// Pin definitions from schematic
// -----------------------------------------------------------------------------
#define LOAD1_Q1_DDR   DDRB
#define LOAD1_Q1_PORT  PORTB
#define LOAD1_Q1_BIT   PB1    // U1 PB1 -> U4 IN_A -> Q1/J2

#define LOAD2_Q2_DDR   DDRB
#define LOAD2_Q2_PORT  PORTB
#define LOAD2_Q2_BIT   PB0    // U1 PB0 -> R9 -> Q2/J3

#define LOAD3_Q3_DDR   DDRD
#define LOAD3_Q3_PORT  PORTD
#define LOAD3_Q3_BIT   PD7    // U1 PD7 -> R11 -> Q3/J4

#define LED1_DDR       DDRB
#define LED1_PORT      PORTB
#define LED1_BIT       PB2    // U1 PB2 -> R13 -> LED1

#define LED3_DDR       DDRB
#define LED3_PORT      PORTB
#define LED3_BIT       PB3    // U1 PB3 -> R14 -> LED3 / also J7 Pin 4

#define STATUS_DDR     DDRB
#define STATUS_PORT    PORTB
#define STATUS_BIT      PB5   // U1 PB5 -> R5/D7 path / also J7 Pin 3

#define EXT_PB4_DDR    DDRB
#define EXT_PB4_PORT   PORTB
#define EXT_PB4_PINREG PINB
#define EXT_PB4_BIT    PB4    // U1 PB4 -> J6 Pin 1

#define ADC_SENSOR_A_CHANNEL 0 // PC0 / ADC0 <- U7 VOUTA
#define ADC_SENSOR_B_CHANNEL 1 // PC1 / ADC1 <- U7 VOUTB

#define UART_BAUD 115200UL
#define UART_UBRR ((F_CPU / (16UL * UART_BAUD)) - 1UL)

// TODO: Replace with calibrated thresholds after pressure/flow characterization.
#define SENSOR_MIN_VALID_COUNTS  50U
#define SENSOR_MAX_VALID_COUNTS  1000U
#define SENSOR_ENABLE_DELTA_MIN  5U

static void uart_init(void) {
    uint16_t ubrr = (uint16_t)UART_UBRR;
    UBRR0H = (uint8_t)(ubrr >> 8);
    UBRR0L = (uint8_t)(ubrr & 0xFF);
    UCSR0A = 0;
    UCSR0B = (1 << TXEN0) | (1 << RXEN0);
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 8N1
}

static void uart_putc(char c) {
    while (!(UCSR0A & (1 << UDRE0))) {
        ;
    }
    UDR0 = c;
}

static void uart_puts(const char *s) {
    while (*s) {
        if (*s == '\n') {
            uart_putc('\r');
        }
        uart_putc(*s++);
    }
}

static void uart_print_u16(uint16_t value) {
    char buf[8];
    snprintf(buf, sizeof(buf), "%u", value);
    uart_puts(buf);
}

static void gpio_init(void) {
    // Outputs default LOW/OFF before changing DDR to avoid output glitches.
    LOAD1_Q1_PORT &= ~(1 << LOAD1_Q1_BIT);
    LOAD2_Q2_PORT &= ~(1 << LOAD2_Q2_BIT);
    LOAD3_Q3_PORT &= ~(1 << LOAD3_Q3_BIT);
    LED1_PORT &= ~(1 << LED1_BIT);
    LED3_PORT &= ~(1 << LED3_BIT);
    STATUS_PORT &= ~(1 << STATUS_BIT);

    LOAD1_Q1_DDR |= (1 << LOAD1_Q1_BIT);
    LOAD2_Q2_DDR |= (1 << LOAD2_Q2_BIT);
    LOAD3_Q3_DDR |= (1 << LOAD3_Q3_BIT);
    LED1_DDR |= (1 << LED1_BIT);
    LED3_DDR |= (1 << LED3_BIT);
    STATUS_DDR |= (1 << STATUS_BIT);

    // PB4 is external interface / possible SPI MISO. Keep as input with pull-up disabled.
    EXT_PB4_DDR &= ~(1 << EXT_PB4_BIT);
    EXT_PB4_PORT &= ~(1 << EXT_PB4_BIT);
}

static void adc_init(void) {
    // AVCC as ADC reference, external capacitor on AREF.
    ADMUX = (1 << REFS0);
    // Enable ADC, prescaler 128 for 125 kHz ADC clock at 16 MHz.
    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
    // Disable digital input buffers on ADC0 and ADC1 to reduce noise.
    DIDR0 = (1 << ADC0D) | (1 << ADC1D);
}

static uint16_t adc_read(uint8_t channel) {
    ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);
    _delay_us(10);
    ADCSRA |= (1 << ADSC);
    while (ADCSRA & (1 << ADSC)) {
        ;
    }
    return ADC;
}

static void outputs_all_off(void) {
    LOAD1_Q1_PORT &= ~(1 << LOAD1_Q1_BIT);
    LOAD2_Q2_PORT &= ~(1 << LOAD2_Q2_BIT);
    LOAD3_Q3_PORT &= ~(1 << LOAD3_Q3_BIT);
}

static void load1_set(bool on) {
    if (on) LOAD1_Q1_PORT |= (1 << LOAD1_Q1_BIT);
    else    LOAD1_Q1_PORT &= ~(1 << LOAD1_Q1_BIT);
}

static void load2_set(bool on) {
    if (on) LOAD2_Q2_PORT |= (1 << LOAD2_Q2_BIT);
    else    LOAD2_Q2_PORT &= ~(1 << LOAD2_Q2_BIT);
}

static void load3_set(bool on) {
    if (on) LOAD3_Q3_PORT |= (1 << LOAD3_Q3_BIT);
    else    LOAD3_Q3_PORT &= ~(1 << LOAD3_Q3_BIT);
}

static bool sensor_counts_valid(uint16_t a, uint16_t b) {
    if (a < SENSOR_MIN_VALID_COUNTS || a > SENSOR_MAX_VALID_COUNTS) return false;
    if (b < SENSOR_MIN_VALID_COUNTS || b > SENSOR_MAX_VALID_COUNTS) return false;

    uint16_t delta = (a > b) ? (a - b) : (b - a);
    return delta >= SENSOR_ENABLE_DELTA_MIN;
}

static void status_leds(bool sensor_ok, bool loads_on) {
    if (sensor_ok) LED1_PORT |= (1 << LED1_BIT);
    else           LED1_PORT &= ~(1 << LED1_BIT);

    if (loads_on) LED3_PORT |= (1 << LED3_BIT);
    else          LED3_PORT &= ~(1 << LED3_BIT);

    STATUS_PORT ^= (1 << STATUS_BIT); // heartbeat on D7/status path
}

int main(void) {
    cli();
    gpio_init();
    adc_init();
    uart_init();
    outputs_all_off();
    sei();

    uart_puts("Ozone controller firmware starter boot\n");
    uart_puts("All loads default OFF. Waiting for valid sensor readings.\n");

    while (1) {
        uint16_t sensor_a = adc_read(ADC_SENSOR_A_CHANNEL);
        uint16_t sensor_b = adc_read(ADC_SENSOR_B_CHANNEL);
        bool sensor_ok = sensor_counts_valid(sensor_a, sensor_b);

        if (sensor_ok) {
            // Safe starter behavior: enable auxiliary outputs only.
            // Keep likely ozone/HV output disabled until final thresholds are confirmed.
            load1_set(false); // Q1/J2 likely main ozone/HV output: require explicit firmware decision.
            load2_set(true);
            load3_set(true);
        } else {
            outputs_all_off();
        }

        bool loads_on = sensor_ok;
        status_leds(sensor_ok, loads_on);

        uart_puts("ADC_A=");
        uart_print_u16(sensor_a);
        uart_puts(" ADC_B=");
        uart_print_u16(sensor_b);
        uart_puts(" sensor_ok=");
        uart_puts(sensor_ok ? "1" : "0");
        uart_puts("\n");

        _delay_ms(250);
    }
}
Bring-up checklist
  1. Confirm fuses for external clock before relying on the 16 MHz crystal.
  2. Power the board from a current-limited bench supply.
  3. Verify 3.3 V rail before inserting/programming the MCU.
  4. Program over J7 only after confirming the J7 physical pinout.
  5. Open UART on J5/J6 at 115200 8N1 if connected to a USB-UART adapter.
  6. Confirm ADC values from U7 VOUTA/VOUTB at known sensor pressures.
  7. Calibrate thresholds before enabling Q1/J2 ozone/HV output.
  8. Confirm all external loads and safety interlocks before unattended operation.
Firmware TODOs before production
  • Define exact load assignment for J2/J3/J4.
  • Add calibrated pressure/flow thresholds.
  • Add watchdog configuration.
  • Add persistent fault latch behavior.
  • Add command protocol over UART if an external controller or display is used.
  • Add production test mode for LEDs, ADC channels, and all load outputs.
  • Platform & Toolchain

  • Pin Mapping

  • Safety policy defaults

  • Project setup

  • Makefile

  • Complete starter firmware

  • Bring-up checklist

  • Firmware TODOs before production