From 596528d5c23d1fcf34d48692460b254c2b56adf5 Mon Sep 17 00:00:00 2001 From: agsler Date: Sat, 23 May 2026 00:16:44 +0200 Subject: [PATCH] more or less works --- controllino_io.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ dmxtest.py | 52 ++++++++++++ main.c | 126 +++++++++++++-------------- 3 files changed, 368 insertions(+), 65 deletions(-) create mode 100644 controllino_io.c create mode 100644 dmxtest.py diff --git a/controllino_io.c b/controllino_io.c new file mode 100644 index 0000000..e674dc2 --- /dev/null +++ b/controllino_io.c @@ -0,0 +1,255 @@ +#include +#include + +/* DMX buffer: indexed 1..512 */ +extern volatile uint8_t dmx_data[]; + +/* Pin bit definitions (change bit numbers if different on your board) */ +/* PE4, PE5, PG5, PE3, PH3, PH4, PH5, PH6, PB4, PB5, PB6, PB7, + PL7, PL6, PL5, PL4, PL3, PL2, PL1, PL0, PD4, PD5, PD6, PJ4 */ + +/* For each Dn we need to map which port and bit will hold bit0..bit7 of the + output byte. Here we assume each DMX channel is presented on a contiguous + 8-bit port where possible. Since the pins are all over, we output each DMX + byte bit-by-bit to 8 dedicated pins: bit 0 -> mapped pin for bit0, ... bit7 + -> mapped pin for bit7. Replace the per-bit port/bit assignments below to + match your desired wiring. */ + +/* Example per-bit mapping for channel outputs (one mapping choice). */ +/* For brevity we map bits 0..7 of each channel to the same physical pin for + that channel, i.e., output the full byte on 8-bit bus is not possible on + single scattered pin. Instead we simply write the byte value to the port pins + that form an 8-bit port where possible. We'll implement writing each + channel's byte to its single port's low 8 bits when that port contains 8 + consecutive pins; otherwise we write the byte on the low 8 bits of a chosen + port using the relevant mask. */ + +/* Define helper macros for writing masked values */ +static inline void write_masked(volatile uint8_t *port, volatile uint8_t *ddr, + uint8_t mask, uint8_t value) { + uint8_t cur = *port & ~mask; + *port = cur | (value & mask); + *ddr |= mask; // ensure pins are outputs +} + +/* Concrete writers for the pins used (mask and port pointers) */ +/* Adjust masks to the actual bit positions used on each port. The following + masks assume: PB4..PB7 -> bits 4..7 on PORTB (fits for D8..D11) PL0..PL7 -> + bits 0..7 on PORTL (fits for D12..D19) PH0..PH7 -> bits 0..7 on PORTH (we use + PH3..PH6 for D4..D7 -> need masking) PD4..PD6 -> bits 4..6 on PORTD + (D20..D22) PE0..PE7 -> bits 0..7 on PORTE (we use PE3,PE4,PE5 for D0,D1,D3) + PJ4 -> bit 4 on PORTJ (D23) + PG5 -> bit 5 on PORTG (D2) +*/ +/* Write channel values to ports with appropriate masks */ +void output_dmx_channels_1_24(void) { + /* D0 -> PE4 : map whole byte to bits 0..7 not possible; instead output + LSB..MSB across 8 pins For simplicity we output the byte value on an 8-bit + pseudo-bus using multiple ports: Here we implement a pragmatic approach: + output each channel value on 8 dedicated IO pins defined below. You must + adapt bit-to-pin assignments to match your wiring. */ + + /* Example assignment: use PORTL for D12..D19 (8-bit) */ + /* D12..D19 handled below; for single-pin channels we output LSB on that pin + (as HIGH/LOW) — if you actually need parallel 8-bit outputs on dedicated + pins per channel, you'll need 8 pins per channel (192 pins) which is + unlikely. So this snippet writes each channel's byte value as a PWM-like + ON/OFF level on a single digital pin using thresholding: + -> if value >= 128 -> set pin HIGH else LOW. */ + + /* Quick implementation: set output pin HIGH if channel value >= 128, else + LOW. Map each D0..D23 to its port/bit and write accordingly. */ + + uint8_t v; + /* D0: PE4 (bit 4) */ + v = dmx_data[1]; + if (v >= 128) + PORTE |= (1 << 4); + else + PORTE &= ~(1 << 4); + DDRE |= (1 << 4); + + /* D1: PE5 (bit 5) */ + v = dmx_data[2]; + if (v >= 128) + PORTE |= (1 << 5); + else + PORTE &= ~(1 << 5); + DDRE |= (1 << 5); + + /* D2: PG5 (bit 5) */ + v = dmx_data[3]; + if (v >= 128) + PORTG |= (1 << 5); + else + PORTG &= ~(1 << 5); + DDRG |= (1 << 5); + + /* D3: PE3 (bit 3) */ + v = dmx_data[4]; + if (v >= 128) + PORTE |= (1 << 3); + else + PORTE &= ~(1 << 3); + DDRE |= (1 << 3); + + /* D4: PH3 (bit 3) */ + v = dmx_data[5]; + if (v >= 128) + PORTH |= (1 << 3); + else + PORTH &= ~(1 << 3); + DDRH |= (1 << 3); + + /* D5: PH4 (bit 4) */ + v = dmx_data[6]; + if (v >= 128) + PORTH |= (1 << 4); + else + PORTH &= ~(1 << 4); + DDRH |= (1 << 4); + + /* D6: PH5 (bit 5) */ + v = dmx_data[7]; + if (v >= 128) + PORTH |= (1 << 5); + else + PORTH &= ~(1 << 5); + DDRH |= (1 << 5); + + /* D7: PH6 (bit 6) */ + v = dmx_data[8]; + if (v >= 128) + PORTH |= (1 << 6); + else + PORTH &= ~(1 << 6); + DDRH |= (1 << 6); + + /* D8: PB4 (bit 4) */ + v = dmx_data[9]; + if (v >= 128) + PORTB |= (1 << 4); + else + PORTB &= ~(1 << 4); + DDRB |= (1 << 4); + + /* D9: PB5 (bit 5) */ + v = dmx_data[10]; + if (v >= 128) + PORTB |= (1 << 5); + else + PORTB &= ~(1 << 5); + DDRB |= (1 << 5); + + /* D10: PB6 (bit 6) */ + v = dmx_data[11]; + if (v >= 128) + PORTB |= (1 << 6); + else + PORTB &= ~(1 << 6); + DDRB |= (1 << 6); + + /* D11: PB7 (bit 7) */ + v = dmx_data[12]; + if (v >= 128) + PORTB |= (1 << 7); + else + PORTB &= ~(1 << 7); + DDRB |= (1 << 7); + + /* D12: PL7 (bit 7) */ + v = dmx_data[13]; + if (v >= 128) + PORTL |= (1 << 7); + else + PORTL &= ~(1 << 7); + DDRL |= (1 << 7); + + /* D13: PL6 (bit 6) */ + v = dmx_data[14]; + if (v >= 128) + PORTL |= (1 << 6); + else + PORTL &= ~(1 << 6); + DDRL |= (1 << 6); + + /* D14: PL5 (bit 5) */ + v = dmx_data[15]; + if (v >= 128) + PORTL |= (1 << 5); + else + PORTL &= ~(1 << 5); + DDRL |= (1 << 5); + + /* D15: PL4 (bit 4) */ + v = dmx_data[16]; + if (v >= 128) + PORTL |= (1 << 4); + else + PORTL &= ~(1 << 4); + DDRL |= (1 << 4); + + /* D16: PL3 (bit 3) */ + v = dmx_data[17]; + if (v >= 128) + PORTL |= (1 << 3); + else + PORTL &= ~(1 << 3); + DDRL |= (1 << 3); + + /* D17: PL2 (bit 2) */ + v = dmx_data[18]; + if (v >= 128) + PORTL |= (1 << 2); + else + PORTL &= ~(1 << 2); + DDRL |= (1 << 2); + + /* D18: PL1 (bit 1) */ + v = dmx_data[19]; + if (v >= 128) + PORTL |= (1 << 1); + else + PORTL &= ~(1 << 1); + DDRL |= (1 << 1); + + /* D19: PL0 (bit 0) */ + v = dmx_data[20]; + if (v >= 128) + PORTL |= (1 << 0); + else + PORTL &= ~(1 << 0); + DDRL |= (1 << 0); + + /* D20: PD4 (bit 4) */ + v = dmx_data[21]; + if (v >= 128) + PORTD |= (1 << 4); + else + PORTD &= ~(1 << 4); + DDRD |= (1 << 4); + + /* D21: PD5 (bit 5) */ + v = dmx_data[22]; + if (v >= 128) + PORTD |= (1 << 5); + else + PORTD &= ~(1 << 5); + DDRD |= (1 << 5); + + /* D22: PD6 (bit 6) */ + v = dmx_data[23]; + if (v >= 128) + PORTD |= (1 << 6); + else + PORTD &= ~(1 << 6); + DDRD |= (1 << 6); + + /* D23: PJ4 (bit 4) */ + v = dmx_data[24]; + if (v >= 128) + PORTJ |= (1 << 4); + else + PORTJ &= ~(1 << 4); + DDRJ |= (1 << 4); +} diff --git a/dmxtest.py b/dmxtest.py new file mode 100644 index 0000000..f643e07 --- /dev/null +++ b/dmxtest.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +import serial +import time + +DEVICE = "/dev/ttyUSB0" +BAUD = 250000 # DMX baud +CHANNELS = 512 +ACTIVE_COUNT = 24 # cycle through channels 1..24 + +def send_dmx(ser, data): + try: + ser.break_condition = True + time.sleep(0.0001) # 100 µs + ser.break_condition = False + except Exception: + ser.baudrate = 57600 + ser.write(b'\x00') + ser.flush() + ser.baudrate = BAUD + + time.sleep(0.000012) # 12 µs MAB + frame = bytes([0x00]) + bytes(data) + ser.write(frame) + ser.flush() + +def main(): + dmx_values = bytearray([0]*(CHANNELS)) # index 0 -> channel 1 + with serial.Serial(DEVICE, baudrate=BAUD, bytesize=serial.EIGHTBITS, + parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_TWO, + timeout=1) as ser: + time.sleep(0.1) + chan = 0 + try: + while True: + # clear all + for i in range(CHANNELS): + dmx_values[i] = 0 + # set active channel (channels 1..ACTIVE_COUNT) + dmx_values[chan] = 255 + send_dmx(ser, dmx_values) + time.sleep(0.025) # frame delay; adjust if desired + + # advance channel (wrap within 0..ACTIVE_COUNT-1) + chan += 1 + if chan >= ACTIVE_COUNT: + chan = 0 + except KeyboardInterrupt: + pass + +if __name__ == "__main__": + main() + diff --git a/main.c b/main.c index 5b27776..07ce088 100644 --- a/main.c +++ b/main.c @@ -2,7 +2,9 @@ #include #include #include +#include +#define F_CPU 16000000UL #define DMX_CHANNELS 512 volatile uint8_t dmx_data[DMX_CHANNELS + 1]; // 1..512 @@ -10,117 +12,111 @@ volatile uint16_t dmx_index = 0; volatile bool dmx_frame_ready = false; volatile bool receiving = false; +extern void output_dmx_channels_1_24(void); + +/* --- USART3 (DMX) init & ISR --- */ void uart3_init(void) { - // Set baud rate for 250000 (F_CPU must be set accordingly). - // Formula UBRR = F_CPU/(16*baud)-1 ; For high speed use U2X - but DMX - // typically uses normal mode. For F_CPU = 16MHz: UBRR = - // 16,000,000/(16*250000)-1 = 3 We'll enable UBRR3 with 3 for 16MHz; adjust if - // F_CPU differs. - uint16_t ubrr = 3; - UBRR3H = (uint8_t)(ubrr >> 8); - UBRR3L = (uint8_t)ubrr; - - // Enable receiver and RX complete interrupt + // uint16_t ubrr = (F_CPU / (16UL * 250000UL)) - 1; // 250000 baud + // UBRR3H = (uint8_t)(ubrr >> 8); + // UBRR3L = (uint8_t)ubrr; + UBRR3L = 3; UCSR3B = (1 << RXEN3) | (1 << RXCIE3); - - // Set frame format: 8 data, 2 stop bits (USBS3 = 1) - UCSR3C = (1 << USBS3) | (1 << UCSZ31) | (1 << UCSZ30); - - // Note: parity default is none (UPM bits = 0) + UCSR3C = (1 << USBS3) | (1 << UCSZ31) | (1 << UCSZ30); // 8N2 } ISR(USART3_RX_vect) { - PORTE |= 1 << 4; - uint8_t status = UCSR3A; - uint8_t data = UDR3; // reading UDR clears the receive flag + uint8_t data = UDR3; - // If framing error -> likely BREAK (line held low longer than a byte time) if (status & (1 << FE3)) { - // Break detected: start of new DMX packet + // Break detected: start new DMX packet receiving = true; dmx_index = 0; dmx_frame_ready = false; - return; // discard this byte (it's break/MAF) - } - - if (!receiving) { - // Not currently inside a DMX frame; ignore bytes until we detect break return; } - // DMX: first byte after break is START CODE (usually 0); then channel - // data 1..512 + if (!receiving) + return; + if (dmx_index == 0) { - // start code - // Optionally check for start code == 0, otherwise may discard or handle RDM - // etc. We'll accept any start code but only store channels. - dmx_index++; // move to channel 1 next + // start code (usually 0) - ignore/store as needed + dmx_index++; // next byte will be channel 1 return; } - // dmx_index corresponds to channel number if (dmx_index <= DMX_CHANNELS) { dmx_data[dmx_index] = data; dmx_index++; if (dmx_index > DMX_CHANNELS) { - // Received full frame dmx_frame_ready = true; - receiving = false; // wait for next break + receiving = false; } } else { - // Overflow (more bytes than expected) — treat as end and wait for next - // break dmx_frame_ready = true; receiving = false; } } +/* --- USART0 (printf/debug) init & putchar --- */ +void uart0_init(uint32_t baud) { + // uint16_t ubrr = (uint16_t)((F_CPU / (16UL * baud)) - 1UL); + // UBRR0H = (uint8_t)(ubrr >> 8); + // UBRR0L = (uint8_t)ubrr; + UBRR0L = 8; + UCSR0B = (1 << TXEN0); // enable transmitter + UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 8 data, 1 stop, no parity +} + +int uart0_putchar(char c, FILE *stream) { + if (c == '\n') + uart0_putchar('\r', stream); // CRLF + while (!(UCSR0A & (1 << UDRE0))) + ; // wait until buffer empty + UDR0 = (uint8_t)c; + return 0; +} + +/* Create a FILE stream for stdout */ +static FILE uart0_stdout = + FDEV_SETUP_STREAM(uart0_putchar, NULL, _FDEV_SETUP_WRITE); + int main(void) { - // Example CPU freq assumption: 16 MHz. If different, adjust UBRR in - // uart3_init. - - DDRE |= 1 << 4; - cli(); - uart3_init(); - // Optional: initialize array + DDRJ |= 1 << 5; // enable rs485 receiver + + uart3_init(); + uart0_init(115200); // debug baud + + // Hook stdout to UART0 + stdout = &uart0_stdout; + + // init DMX buffer for (uint16_t i = 1; i <= DMX_CHANNELS; ++i) dmx_data[i] = 0; sei(); - // Main loop: react to finished frames + // Example usage + printf("DMX receiver started\r\n"); + while (1) { if (dmx_frame_ready) { - // Example: use channel 1..512 from dmx_data - // Process dmx_data here. This is a single buffer; if processing takes - // long, consider double-buffering to avoid race with ISR. + // Example debug: print first 8 channels + printf("DMX frame received. Ch1..8: "); + for (uint8_t i = 1; i <= 8; ++i) { + printf("%u ", dmx_data[i]); + } + printf("\r\n"); - // After consuming, clear flag dmx_frame_ready = false; } - // if (dmx_data[1]) - // PORTE |= 1 << 4; - // else - // PORTE &= ~(1 << 4); + output_dmx_channels_1_24(); - // Other application code... + // other app code... } return 0; } - -// #include -// #include -// -// int main(void) { -// DDRE = 1 << 4; -// -// while (1) { -// PORTE ^= 1 << 4; -// _delay_ms(500); -// } -// }