/*
* Bare-metal Morse Code Transmitter for Arduino Uno R3 (ATmega328P)
*
* Reads a line of text from UART (USART0) — terminated by Enter — and
* then flashes the entire message in Morse code on an LED connected
* to PB5 (digital pin 13). Supports backspace for correcting typos
* before sending.
*
* Timing follows the standard PARIS unit:
* dot = 1 unit ON
* dash = 3 units ON
* intra-char gap = 1 unit OFF (between dots/dashes of same letter)
* inter-char gap = 3 units OFF (between letters)
* word gap = 7 units OFF (a space character)
*
* UART: 9600 8N1
* F_CPU: 16 MHz
*/
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
/* One Morse "unit" in milliseconds. ~120 ms => ~10 WPM, easy to watch. */
#define UNIT_MS 120
/* ---------- LED on PB5 (Arduino pin 13) ---------- */
static inline void led_init(void) { DDRB |= (1 << PB5); }
static inline void led_on(void) { PORTB |= (1 << PB5); }
static inline void led_off(void) { PORTB &= ~(1 << PB5); }
/* ---------- UART (USART0) at 9600 8N1, 16 MHz ---------- */
static void uart_init(void) {
/* UBRR = F_CPU / (16 * BAUD) - 1 = 16000000 / (16 * 9600) - 1 = 103 */
const uint16_t ubrr = 103;
UBRR0H = (uint8_t)(ubrr >> 8);
UBRR0L = (uint8_t)(ubrr);
/* Enable RX and TX */
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
/* 8 data bits, 1 stop bit, no parity (default after reset, set explicitly) */
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
static uint8_t uart_rx_byte(void) {
while (!(UCSR0A & (1 << RXC0))) { /* wait for data */ }
return UDR0;
}
static void uart_tx_byte(uint8_t b) {
while (!(UCSR0A & (1 << UDRE0))) { /* wait for empty buffer */ }
UDR0 = b;
}
static void uart_tx_str(const char *s) {
while (*s) uart_tx_byte((uint8_t)*s++);
}
/* ---------- Variable-length delays (avr-libc requires compile-time const) ---------- */
static void delay_units(uint16_t units) {
/* Each loop = 1 ms, so units * UNIT_MS milliseconds total. */
uint32_t ms = (uint32_t)units * UNIT_MS;
while (ms--) _delay_ms(1);
}
/* ---------- Morse code table ----------
*
* Encoding trick: each symbol is packed into a single byte.
* Bits are read from MSB toward LSB; a leading '1' marks the start
* of the pattern (sentinel), and the bits after it are the actual code:
* 0 = dot, 1 = dash.
*
* Examples:
* 'A' = .- -> sentinel 1, then 0,1 = 0b00000101 = 0x05
* 'B' = -... -> sentinel 1, then 1,0,0,0 = 0b00011000 = 0x18
* 'E' = . -> sentinel 1, then 0 = 0b00000010 = 0x02
* 'T' = - -> sentinel 1, then 1 = 0b00000011 = 0x03
*
* 0x00 means "no code defined".
*/
/* Letters A..Z */
static const uint8_t morse_letters[26] = {
0x05, /* A .- */
0x18, /* B -... */
0x1A, /* C -.-. */
0x0C, /* D -.. */
0x02, /* E . */
0x12, /* F ..-. */
0x0E, /* G --. */
0x10, /* H .... */
0x04, /* I .. */
0x17, /* J .--- */
0x0D, /* K -.- */
0x14, /* L .-.. */
0x07, /* M -- */
0x06, /* N -. */
0x0F, /* O --- */
0x16, /* P .--. */
0x1D, /* Q --.- */
0x0A, /* R .-. */
0x08, /* S ... */
0x03, /* T - */
0x09, /* U ..- */
0x11, /* V ...- */
0x0B, /* W .-- */
0x19, /* X -..- */
0x1B, /* Y -.-- */
0x1C, /* Z --.. */
};
/* Digits 0..9 */
static const uint8_t morse_digits[10] = {
0x3F, /* 0 ----- */
0x2F, /* 1 .---- */
0x27, /* 2 ..--- */
0x23, /* 3 ...-- */
0x21, /* 4 ....- */
0x20, /* 5 ..... */
0x30, /* 6 -.... */
0x38, /* 7 --... */
0x3C, /* 8 ---.. */
0x3E, /* 9 ----. */
};
static uint8_t morse_lookup(char c) {
if (c >= 'a' && c <= 'z') c = (char)(c - 'a' + 'A');
if (c >= 'A' && c <= 'Z') return morse_letters[c - 'A'];
if (c >= '0' && c <= '9') return morse_digits[c - '0'];
return 0x00;
}
/* ---------- Flash one Morse symbol on the LED ---------- */
static void flash_symbol(uint8_t code) {
if (code == 0x00) return;
/* Find the sentinel '1' bit by scanning from MSB. */
uint8_t mask = 0x80;
while (mask && !(code & mask)) mask >>= 1;
/* Skip the sentinel itself; the bits below it are the real pattern. */
mask >>= 1;
uint8_t first = 1;
while (mask) {
if (!first) {
led_off();
delay_units(1); /* intra-character gap */
}
led_on();
if (code & mask) {
delay_units(3); /* dash */
} else {
delay_units(1); /* dot */
}
led_off();
first = 0;
mask >>= 1;
}
}
/* ---------- Pretty-print the dits and dahs back over UART ---------- */
static void print_pattern(uint8_t code) {
if (code == 0x00) {
uart_tx_str("?");
return;
}
uint8_t mask = 0x80;
while (mask && !(code & mask)) mask >>= 1;
mask >>= 1;
while (mask) {
uart_tx_byte((code & mask) ? '-' : '.');
mask >>= 1;
}
}
/* ---------- Transmit one character on the LED ---------- */
static void transmit_char(char c) {
/* Word gap: 7 units of silence. The 3-unit inter-character gap
* that already follows the previous letter does not count toward
* this; we add the full 7 here for simplicity (slightly more than
* spec, but visually correct and easier to read). */
if (c == ' ' || c == '\t') {
delay_units(7);
return;
}
uint8_t code = morse_lookup(c);
if (code == 0x00) return; /* unknown character: skip silently */
flash_symbol(code);
delay_units(3); /* inter-character gap */
}
/* ---------- Line buffer ---------- */
#define LINE_MAX 80
static char line_buf[LINE_MAX + 1];
/* Read a line from UART into line_buf. Returns the number of characters
* stored (not counting the terminating null). Handles backspace so you
* can correct typos before pressing Enter. Echoes everything back so
* the user sees what they're typing. */
static uint8_t read_line(void) {
uint8_t len = 0;
for (;;) {
uint8_t c = uart_rx_byte();
if (c == '\r' || c == '\n') {
uart_tx_str("\r\n");
line_buf[len] = '\0';
return len;
}
/* Backspace (0x08) or DEL (0x7F) — let the user fix typos */
if (c == 0x08 || c == 0x7F) {
if (len > 0) {
len--;
/* Erase on the terminal: back up, overwrite with space, back up */
uart_tx_str("\b \b");
}
continue;
}
/* Ignore other non-printables */
if (c < 0x20) continue;
if (len < LINE_MAX) {
line_buf[len++] = (char)c;
uart_tx_byte(c); /* local echo */
}
/* If buffer is full, silently drop further characters until Enter */
}
}
int main(void) {
led_init();
uart_init();
uart_tx_str("\r\nMorse Code Transmitter ready.\r\n");
uart_tx_str("Type a message (A-Z, 0-9, spaces) and press Enter.\r\n");
for (;;) {
uart_tx_str("\r\n> ");
uint8_t len = read_line();
if (len == 0) continue; /* empty line, just prompt again */
/* Show the morse translation on the serial line first, so the
* user can read along while the LED is flashing. */
for (uint8_t i = 0; i < len; i++) {
char c = line_buf[i];
if (c == ' ' || c == '\t') {
uart_tx_str(" / "); /* visual word separator */
continue;
}
uint8_t code = morse_lookup(c);
if (code == 0x00) {
uart_tx_byte('?');
} else {
print_pattern(code);
}
uart_tx_byte(' ');
}
uart_tx_str("\r\n");
/* Now play it on the LED. */
for (uint8_t i = 0; i < len; i++) {
transmit_char(line_buf[i]);
}
}
}