Chat
F_CPU to the actual programmed fuse clock. The schematic indicates a 16 MHz crystal/resonator path.Table
| Function | MCU pin / register bit | Schematic net | Connected to | Direction | Notes |
|---|---|---|---|---|---|
| UART RX | PD0 / RXD0 | Net 36 | J6 Pin 3, J5 Pin 2 | Input | Serial receive |
| UART TX | PD1 / TXD0 | Net 24 | J6 Pin 2, J5 Pin 1 | Output | Serial transmit |
| Load 3 control | PD7 | Net 31 | R11 -> Q3 gate network | Output | Low-side output J4 |
| Load 2 control | PB0 | Net 32 | R9 -> Q2 gate network | Output | Low-side output J3 |
| Load 1 control | PB1 | Net 26 | U4 IN_A | Output | Gate-driver input for Q1/J2 |
| LED/status 1 | PB2 | Net 38 | R13 -> LED1 | Output | Status LED |
| LED/status 2 / SPI MOSI | PB3 / MOSI0 | Net 19 | J7 Pin 4, R14 -> LED3 | Output | Shared with programming header |
| External interface / SPI MISO | PB4 / MISO0 | Net 21 | J6 Pin 1 | Input by default | Also programmer signal if used as ISP MISO |
| Indicator / SPI SCK | PB5 / SCK0 | Net 18 | J7 Pin 3, R5 -> D7 path | Output | Shared with programming header |
| Sensor A ADC | PC0 / ADC0 | Net 13 | U7 VOUTA | Analog input | Pressure/flow channel A |
| Sensor B ADC | PC1 / ADC1 | Net 46 | U7 VOUTB | Analog input | Pressure/flow channel B |
| Reset | PC6 / RESET | Net 20 | R1, C17, J7 Pin 5 | Reset input | External reset / programmer reset |
| AREF | AREF | Net 16 | C16 | Analog reference | Decoupled to GND |
| AVCC | AVCC | Net 17 | L2/C15 filtered 3.3 V | Power | ADC supply |
| VCC | VCC | Net 47 | 3.3 V rail | Power | Main logic supply |
| GND | GND | GND | Board ground | Power | Common ground |
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
Bash
sudo apt-get update sudo apt-get install gcc-avr avr-libc avrdude make
Bash
make make flash
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); } }
Platform & Toolchain
Pin Mapping
Safety policy defaults
Project setup
Makefile
Complete starter firmware
Bring-up checklist
Firmware TODOs before production