Source

teletype / teletype.c

Full commit
/**
 * \file 45 baud sync teletype interface
 *
 * Uses a boost converter to make the 50-100V for driving the ciol.
 */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <string.h>
#include <util/delay.h>
#include "usb_serial.h"
#include "bits.h"


// Send a string to the USB serial port.  The string must be in
// flash memory, using PSTR
//
void send_str(const char *s)
{
	char c;
	while (1) {
		c = pgm_read_byte(s++);
		if (!c) break;
		usb_serial_putchar(c);
	}
}


static char
hexdigit(
	uint8_t x
)
{
	x &= 0xF;
	if (x < 0xA)
		return x + '0';
	else
		return x + 'A' - 0xA;
}


#define BAUDOT(a,b,c,d,e,m) ( \
	(a << 0) | \
	(b << 1) | \
	(c << 2) | \
	(d << 3) | \
	(e << 4) | \
	(m << 5) | \
	0 )

#define BAUDOT_LC(a,b,c,d,e) BAUDOT(a,b,c,d,e,0x1)
#define BAUDOT_UC(a,b,c,d,e) BAUDOT(a,b,c,d,e,0x2)
#define BAUDOT_BOTH(a,b,c,d,e) BAUDOT(a,b,c,d,e,0x3)

static uint8_t baudot[] = 
{
	[' '] = BAUDOT_BOTH(1,1,0,1,1),
	['\n'] = BAUDOT_BOTH(1,1,1,0,1),
	['\r'] = BAUDOT_BOTH(1,0,1,1,1),

	['a'] = BAUDOT_LC(0,0,1,1,1),
	['b'] = BAUDOT_LC(0,1,1,0,0),
	['c'] = BAUDOT_LC(1,0,0,0,1),
	['d'] = BAUDOT_LC(0,1,1,0,1),
	['e'] = BAUDOT_LC(0,1,1,1,1),
	['f'] = BAUDOT_LC(0,1,0,0,1),
	['g'] = BAUDOT_LC(1,0,1,0,0),
	['h'] = BAUDOT_LC(1,1,0,1,0),
	['i'] = BAUDOT_LC(1,0,0,1,1),
	['j'] = BAUDOT_LC(0,0,1,0,1),
	['k'] = BAUDOT_LC(0,0,0,0,1),
	['l'] = BAUDOT_LC(1,0,1,1,0),
	['m'] = BAUDOT_LC(1,1,0,0,0),
	['n'] = BAUDOT_LC(1,1,0,0,1),
	['o'] = BAUDOT_LC(1,1,1,0,0),
	['p'] = BAUDOT_LC(1,0,0,1,0),
	['q'] = BAUDOT_LC(0,0,0,1,0),
	['r'] = BAUDOT_LC(1,0,1,0,1),
	['s'] = BAUDOT_LC(0,1,0,1,1),
	['t'] = BAUDOT_LC(1,1,1,1,0),
	['u'] = BAUDOT_LC(0,0,0,1,1),
	['v'] = BAUDOT_LC(1,0,0,0,0),
	['w'] = BAUDOT_LC(0,0,1,1,0),
	['x'] = BAUDOT_LC(0,1,0,0,0),
	['y'] = BAUDOT_LC(0,1,0,1,0),
	['z'] = BAUDOT_LC(0,1,1,1,0),

	['0'] = BAUDOT_UC(1,0,0,1,0),
	['1'] = BAUDOT_UC(0,0,0,1,0),
	['2'] = BAUDOT_UC(0,0,1,1,0),
	['3'] = BAUDOT_UC(0,1,1,1,1),
	['4'] = BAUDOT_UC(1,0,1,0,1),
	['5'] = BAUDOT_UC(1,1,1,1,0),
	['6'] = BAUDOT_UC(0,1,0,1,0),
	['7'] = BAUDOT_UC(0,0,0,1,1),
	['8'] = BAUDOT_UC(1,0,0,1,1),
	['9'] = BAUDOT_UC(1,1,1,0,0),
	['-'] = BAUDOT_UC(0,0,1,1,1),
	['?'] = BAUDOT_UC(0,1,1,0,0),
	[':'] = BAUDOT_UC(1,0,0,0,1),
	['$'] = BAUDOT_UC(0,1,1,0,1),
	['!'] = BAUDOT_UC(0,1,0,0,1),
	['&'] = BAUDOT_UC(1,0,1,0,0),
	['#'] = BAUDOT_UC(1,1,0,1,0),
	['\''] = BAUDOT_UC(0,0,1,0,1),
	['('] = BAUDOT_UC(0,0,0,0,1),
	[')'] = BAUDOT_UC(1,0,1,1,0),
	['.'] = BAUDOT_UC(1,1,0,0,0),
	[','] = BAUDOT_UC(1,1,0,0,1),
	['\a'] = BAUDOT_UC(0,1,0,1,1),
	[';'] = BAUDOT_UC(1,0,0,0,0),
	['/'] = BAUDOT_UC(0,1,0,0,0),
	['"'] = BAUDOT_UC(0,1,1,1,0),

};

static uint8_t current_mode;

static const uint8_t mode_select[] = {
	[1] = BAUDOT_BOTH(0,0,0,0,0),
	[2] = BAUDOT_BOTH(0,0,1,0,0),
};


static void
tty_out_raw(
	uint8_t c
)
{
	// start bit
	out(0xD7, 0);

#define BIT_CLOCK 22
//#define BIT_CLOCK 16666

	_delay_ms(BIT_CLOCK); // + BIT_CLOCK/2);

	for (int i = 0 ; i < 5 ; i++)
	{
		int x = c & 1;
		c >>= 1;

		if (!x)
			out(0xD7, 1);
		else
			out(0xD7, 0);

		_delay_ms(BIT_CLOCK);
	}

	out(0xD7, 1);
	_delay_ms(BIT_CLOCK + BIT_CLOCK); // 1.42 stop bits
}


static void
tty_out(
	uint8_t ch
)
{
	uint8_t c = ch ? baudot[ch] : 0x00;
	if (c == 0 && ch != 0)
		return;

	const uint8_t mode = (c >> 5) & 0x3;

	// If we are not in the current mode, send the special
	// code to switch to the other typebar and record that
	// we are in that mode.
	if (current_mode != mode && mode != 0x3)
	{
		tty_out_raw(mode_select[mode]);
		_delay_ms(BIT_CLOCK + BIT_CLOCK); // give it a few bits
		current_mode = mode;
	}

	tty_out_raw(c);
}


static void
boost_setup(void)
{

        // Configure OC1x in fast-PWM mode, 8-bit
        sbi(TCCR1B, WGM12);
        cbi(TCCR1A, WGM11);
        sbi(TCCR1A, WGM10);

        // OC1C is used to generate the boost converter pump
        // Configure output mode to clear on match, set at top
        sbi(TCCR1A, COM1C1);
        cbi(TCCR1A, COM1C0);

        // Configure clock 1 at clk/1
        cbi(TCCR1B, CS12);
        cbi(TCCR1B, CS11);
        sbi(TCCR1B, CS10);

	OCR1C = 0xDA; // very little off time makes the mosfet unhappy
	ddr(0xB7, 1);
	out(0xB7, 1);
}


static void
boost_adjust(
	int delta
)
{
	OCR1C += delta;

	char buf[] = {
		hexdigit(OCR1C >> 12),
		hexdigit(OCR1C >>  8),
		hexdigit(OCR1C >>  4),
		hexdigit(OCR1C >>  0),
		'\r',
		'\n'
	};

	usb_serial_write(buf, sizeof(buf));
}




int main(void)
{
	// set for 16 MHz clock
#define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
	CPU_PRESCALE(0);

	// Disable the ADC
	ADMUX = 0;

	ddr(0xD7, 1);
	out(0xD7, 1);

	boost_setup();

	// initialize the USB, and then wait for the host
	// to set configuration.  If the Teensy is powered
	// without a PC connected to the USB port, this 
	// will wait forever.
	usb_init();
	while (!usb_configured())
		continue;

	_delay_ms(500);

	// wait for the user to run their terminal emulator program
	// which sets DTR to indicate it is ready to receive.
	while (!(usb_serial_get_control() & USB_SERIAL_DTR))
		continue;

	// discard anything that was received prior.  Sometimes the
	// operating system or other software will send a modem
	// "AT command", which can still be buffered.
	usb_serial_flush_input();

	ddr(0xD6, 1); // LED

	int status = 0;

	while (1)
	{
		int c = usb_serial_getchar();
		if (c == -1)
			continue;

		if (c == '+')
		{
			boost_adjust(10);
			continue;
		} else
		if (c == '-')
		{
			boost_adjust(-10);
			continue;
		}

		out(0xD6, 1);
		tty_out(c);
		//tty_out(0x00);
		out(0xD6, 0);
	}
}