/*
USB Telegraph sounder.
*/
#define F_CPU 12000000
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
/* USART error conditions. */
#define FRAMING_ERROR _BV(FE)
#define DATA_OVERRUN _BV(DOR)
#define PARITY_ERROR _BV(UPE)
#define PORT PORTD
#define DDR DDRD
#define OUT _BV(PD6)
#define DTR _BV(PD5)
#define DSR _BV(PD4)
#define RTS _BV(PD3)
#define CTS _BV(PD2)
#define OUTBITS OUT | RTS | DTR
#define INBITS CTS | DSR
#define MODE_OUTPUT 0
#define MODE_SET_WPM 1
/* Macro to get number of entries in ASCII -> MORSE table. */
#define CHAR_COUNT sizeof(ascii_lookup_table) / sizeof(ascii_lookup_table[0])
/* Macros to implement RTS/CTS flow control. */
#define USART_PAUSE() PORT |= RTS
#define USART_RESUME() PORT &= ~RTS
#define USART_CLEAR() PORT & CTS
/*****/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <inttypes.h>
uint8_t mode;
uint8_t wpm;
/*
struct mapping an ASCII char -> morse code.
Each two bits are encoded as:
01 -> short (1)
11 -> long (3)
00 marks the end of the letter.
*/
const struct ascii_morse {
uint8_t ascii;
uint16_t morse;
} ascii_lookup_table[]
PROGMEM = {
{ '0', 0b1111111111000000 },
{ '1', 0b0111111111000000 },
{ '2', 0b0101111111000000 },
{ '3', 0b0101011111000000 },
{ '4', 0b0101010111000000 },
{ '5', 0b0101010101000000 },
{ '6', 0b1101010101000000 },
{ '7', 0b1111010101000000 },
{ '8', 0b1111110101000000 },
{ '9', 0b1111111101000000 },
{ 'A', 0b0111000000000000 },
{ 'B', 0b1101010100000000 },
{ 'C', 0b1101110100000000 },
{ 'D', 0b0011010100000000 },
{ 'E', 0b0100000000000000 },
{ 'F', 0b0101110100000000 },
{ 'G', 0b1111010000000000 },
{ 'H', 0b0101010100000000 },
{ 'I', 0b0101000000000000 },
{ 'J', 0b0111111100000000 },
{ 'K', 0b1101110000000000 },
{ 'L', 0b0111010100000000 },
{ 'M', 0b1111000000000000 },
{ 'N', 0b1101000000000000 },
{ 'O', 0b1111110000000000 },
{ 'P', 0b0111110100000000 },
{ 'Q', 0b1111011100000000 },
{ 'R', 0b0111010000000000 },
{ 'S', 0b0101010000000000 },
{ 'T', 0b1100000000000000 },
{ 'U', 0b0101110000000000 },
{ 'V', 0b0101011100000000 },
{ 'W', 0b0111110000000000 },
{ 'X', 0b1101011100000000 },
{ 'Y', 0b1101111100000000 },
{ 'Z', 0b1111010100000000 },
{ '\'', 0b0111111111010000 },
{ '/', 0b1101011101000000 },
{ '"', 0b0111010111010000 },
{ ',', 0b1111010111110000 },
{ '.', 0b0111011101110000 },
{ ':', 0b1111110101010000 },
{ ';', 0b1101110111000000 },
{ '?', 0b0101111101010000 },
{ '(', 0b1101111101000000 },
{ ')', 0b1101111101110000 },
{ '=', 0b1101010111000000 },
{ '+', 0b0111011101000000 },
{ '-', 0b1101010101110000 },
{ '@', 0b0111110111010000 },
// Not standard, but widely used.
{ '!', 0b1101110111110000 },
// Non-ITU
{ '&', 0b0111010101000000 },
{ '$', 0b0101011101011100 },
{ '_', 0b0101111101110000 },
{ ' ', 0b0000000000000000 }
};
const uint8_t intro_str[] PROGMEM = "What hath god wrought!";
/***
Incoming queue. Note that the add and remove functions disable interrupts for
their duration in order to ensure that they are atomic.
QUEUE_LENGTH is the total size of the queue.
When the free slot count fallse to QUEUE_MIN_FREE, hardware flow control
stops incoming bytes.
When the free slot rises to QUEUE_MAX_FREE, hardware flow control allows
incoming bytes again.
***/
#define QUEUE_LENGTH 64
#define QUEUE_MIN_FREE 8
#define QUEUE_MAX_FREE 16
#define QUEUE_ERROR_NONE 0x00
#define QUEUE_ERROR_EMPTY 0x01
#define QUEUE_ERROR_FULL 0x02
volatile uint8_t queue[QUEUE_LENGTH];
volatile uint8_t queue_front;
volatile uint8_t queue_filled;
volatile uint8_t queue_error;
#define QUEUE_REMAINING QUEUE_LENGTH - queue_filled
/*
Append a given number of characters to the queue. Returns the number of
characters actually added. Set queue_error as necesessary.
*/
uint8_t queue_add(uint8_t *in, int len)
{
uint8_t out_idx, in_idx;
cli();
out_idx = queue_front + queue_filled;
in_idx = 0;
queue_error = QUEUE_ERROR_NONE;
while (in_idx < len) {
while (out_idx >= QUEUE_LENGTH)
out_idx -= QUEUE_LENGTH;
if (queue_filled == QUEUE_LENGTH) {
queue_error = QUEUE_ERROR_FULL;
break;
}
queue[out_idx] = in[in_idx];
queue_filled++;
out_idx++;
in_idx++;
}
sei();
return in_idx;
}
/* Add a given number of characters from program memory to the queue. */
void queue_add_pgmem(uint8_t *addr, uint8_t len)
{
uint8_t i;
uint8_t byte;
for(i = 0; i < len; i++) {
byte = pgm_read_byte( &(addr[i]) );
queue_add( &byte, 1);
}
}
/*
Remove an element fron the queue. Set queue_error as necessary.
*/
uint8_t queue_remove()
{
uint8_t element = 0;
cli();
queue_error = QUEUE_ERROR_NONE;
if(queue_filled == 0) {
queue_error = QUEUE_ERROR_EMPTY;
return 0;
}
element = queue[queue_front];
queue_filled--;
queue_front++;
while (queue_front >= QUEUE_LENGTH)
queue_front -= QUEUE_LENGTH;
sei();
return element;
}
/****
_delay_ms is a library macro, but only works properly with constant values.
Therefore we give it this wrapper.
The ATTiny2313 does not support multiply / divide, so we use two loops to get
approximately the right delay. The standard time of a 'dit' in Morse code is
(1200 / wpm) in milliseconds.
****/
void delay (uint8_t n)
{
uint16_t i;
uint8_t j;
for (j = 0; j < n; j++)
for (i = 0; i < 1200; i += wpm)
_delay_ms(1);
}
/*****
Look up a character in the table and return it's encoded form.
*****/
uint16_t ascii_to_morse (uint8_t letter)
{
uint8_t n;
// Convert to upper-case.
if (letter >= 'a' && letter <= 'z')
letter -= 32;
// Find in table. This returns ' ' if not found.
for (
n = 0;
n < CHAR_COUNT - 1
&& pgm_read_byte(&(ascii_lookup_table[n].ascii)) != letter;
n++
);
return pgm_read_word(&(ascii_lookup_table[n].morse));
}
/*****
Output an ascii character to the telegraph via GPIO.
*****/
void telegraph_letter (uint8_t letter)
{
uint16_t s = ascii_to_morse (letter);
PORT &= ~OUT;
// Space
if (!s)
delay (5);
while(s)
{
PORT |= OUT;
delay (s >> 14);
PORT &= ~OUT;
delay (1);
s = s << 2;
}
// Total delay after letter = 3; if space = 7
delay (2);
}
void initialize (void)
{
DDR |= OUTBITS; // Enable outputs
DDR &= ~INBITS; // Enable inputs
UCSRB = (1 << RXEN) | (1 << TXEN); // Enable UART TX and RX
UBRRL = BAUD_PRESCALE; // baud rate
UCSRC = (1 << UCSZ1) | (1 << UCSZ0); // 8N1
PORT = 0; // Clear outputs.
UCSRB |= (1 << RXCIE); // Enable USART rx interrupt.
sei(); // Enable interrupts
}
void uart_tx (uint8_t data)
{
while (!(UCSRA & (1 << UDRE)));
UDR = data;
}
/***
Interrupt fires when USART receives a byte. Add it to the queue.
***/
ISR(USART_RX_vect)
{
uint16_t status = UCSRA;
uint8_t byte = UDR;
// If the queue is getting filled, de-assert RTS to stop incoming bytes.
if (QUEUE_REMAINING < QUEUE_MIN_FREE)
USART_PAUSE();
if ( (status & (FRAMING_ERROR | DATA_OVERRUN | PARITY_ERROR)) == 0)
queue_add(&byte, 1);
}
/***
Main. Watch for queue entries in a tight loop; output when found
***/
int main (void)
{
uint8_t letter = ' ';
wpm = 20;
initialize ();
queue_add_pgmem ( intro_str, sizeof(intro_str) );
while (1)
{
if (QUEUE_REMAINING > QUEUE_MAX_FREE)
USART_RESUME();
if (! queue_filled)
continue;
letter = queue_remove();
switch ( letter ) {
case 0x17: // Control-W, set morse wpm.
mode = MODE_SET_WPM;
wpm = 0;
break;
default:
if ( mode == MODE_SET_WPM && letter >= '0' && letter <= '9' ) {
wpm *= 10;
wpm += (letter - '0');
} else {
mode = MODE_OUTPUT;
if (wpm < 5) wpm = 5;
telegraph_letter (letter);
uart_tx (letter); // echo byte
}
break;
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1