Source

seven / seven.c

/**
 * \file Seven segment driver
 *
 */

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


#define LED			0xD6


void send_str(const char *s);
uint8_t recv_str(char *buf, uint8_t size);
void parse_and_execute_command(const char *buf, uint8_t num);

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



// 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);
	}
}



/*  4
 * 5 3
 *  7
 * 0 2
 *  1 
 *
 * 6 is not used since D6 drives the onboard LED.
 */
static const uint8_t digits[] = {
	1 << 3 | 1 << 1 | 1 << 0 | 1 << 5 | 1 << 4 | 1 << 2, // 0
	1 << 3 | 1 << 2, // 1
	1 << 4 | 1 << 1 | 1 << 7 | 1 << 0 | 1 << 3, // 2
	1 << 4 | 1 << 2 | 1 << 7 | 1 << 3 | 1 << 1, // 3
	1 << 5 | 1 << 7 | 1 << 2 | 1 << 3, // 4
	1 << 4 | 1 << 5 | 1 << 7 | 1 << 2 | 1 << 1, // 5
	1 << 4 | 1 << 0 | 1 << 7 | 1 << 2 | 1 << 5 | 1 << 1, // 6
	1 << 4 | 1 << 2 | 1 << 3, // 7
	1 << 3 | 1 << 1 | 1 << 0 | 1 << 5 | 1 << 4 | 1 << 2 | 1 << 7, // 8
	1 << 5 | 1 << 7 | 1 << 2 | 1 << 3 | 1 << 4, // 9
};

static const uint8_t chases[] = {
	1 << 4, // chase
	1 << 5, // chase
	1 << 0, // chase
	1 << 1, // chase
	1 << 2, // chase
	1 << 3, // chase
};




/** "Frame buffer" of output LEDs.
 * Since we can't drive all LEDs simultaneously, instead draw them from
 * this frame buffer.
 */
static uint8_t outputs[4];

/** Brightness goes from 0 to 15 */
static uint8_t brightness = 15;


/** PWM the output */
static void
draw(void)
{
	uint8_t mask = 1;
	if (brightness >= 16)
		brightness = 15;

	for (uint8_t i = 0 ; i < 7 ; i++, mask <<= 1)
	{
		// skip output 6
		if (i == 6)
			mask <<= 1;

		DDRC = outputs[0] & mask;
		DDRD = outputs[1] & mask;
		DDRB = outputs[2] & mask;
		DDRF = outputs[3] & mask;
		_delay_us(brightness);

		DDRB = 0;
		DDRC = 0;
		DDRD = 0;
		DDRF = 0;

		if (brightness < 15)
			_delay_us(15 - brightness);
	}

}


static void
report_count(void)
{
	for (uint16_t min = 0 ; min < 512 ; min++)
	{
		const uint8_t count = eeprom_read_byte((void*) min);
		if (count == 0)
			continue;
		char buf[8];
		uint8_t off = 0;
		buf[off++] = hexdigit(min >> 8);
		buf[off++] = hexdigit(min >> 4);
		buf[off++] = hexdigit(min >> 0);
		buf[off++] = '=';
		buf[off++] = hexdigit(count >> 4);
		buf[off++] = hexdigit(count >> 0);
		buf[off++] = '\r';
		buf[off++] = '\n';
		usb_serial_write(buf, off);
	}
}


static void
zero_count(void)
{
	for (uint16_t min = 0 ; min < 512 ; min++)
	{
		eeprom_write_byte((void*) min, 0);
	}
}


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

	// Disable the ADC
	ADMUX = 0;

	// 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();

	// LED is an output; will be pulled down once connected
	ddr(LED, 1);
	out(LED, 1);

	// Timer1 is used for psudeo random numbers
	TCCR1B = 0
		| 0 << CS12
		| 0 << CS12
		| 1 << CS10
		;

        // Timer 0 is used for a 64 Hz control loop timer.
        // Clk/1024 == 15.625 KHz, count up to 244
        // CTC mode resets the counter when it hits the top
        TCCR0A = 0
                | 1 << WGM01 // select CTC
                | 0 << WGM00
                ;

        TCCR0B = 0
                | 0 << WGM02
                | 1 << CS02 // select Clk/1024
                | 0 << CS01
                | 0 << CS00
                ;

        OCR0A = 244;
        sbi(TIFR0, OCF0A); // reset the overflow bit

#ifdef CONFIG_USB
	while (!usb_configured())
		;

	_delay_ms(1000);

	// 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))
		;

	// 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();

	send_str(PSTR("seven segment\r\n"));
#endif

	DDRC = 1 << 7;
	PORTC = 0;

	outputs[0] = digits[0];
	outputs[1] = digits[1];
	outputs[2] = digits[2];
	outputs[3] = digits[3];

	uint32_t ticks = 0;

	for (uint16_t i = 0 ; i < 64 ; )
	{
		draw();
		if (bit_is_clear(TIFR0, OCF0A))
			continue;
		sbi(TIFR0, OCF0A);
		i++;
	}

#ifdef CONFIG_COUNTER
	uint8_t chase = 0;
	uint16_t ticks = 0;
	uint16_t ms = 0;
	uint16_t sec = TCNT1;

	uint16_t total_count = 0;
	uint16_t minute_count = 0;
	uint16_t minute_current = 0;

	for ( ; minute_current < 512 ; minute_current++)
	{
		const uint8_t count = eeprom_read_byte((void*) minute_current);
		if (count == 0)
			break;
	}
#endif
	
	while (1)
	{
#ifdef CONFIG_COUNTER
		int c = usb_serial_getchar();
		if (c != -1)
		{
			if (c == '+')
			{
				if (brightness != 16)
					brightness++;
			} else
			if (c == '-')
			{
				if (brightness != 0)
					brightness --;
			}
			else
			if (c == ' ')
			{
				total_count++;
				minute_count++;
			}
			else
			if (c == '?')
			{
				report_count();
			} else
			if (c == '!')
			{
				zero_count();
				minute_count = total_count = 0;
			}
		}
#endif

		if (bit_is_clear(TIFR0, OCF0A))
		{
			draw();
			continue;
		}

		sbi(TIFR0, OCF0A); // reset the bit
		ticks++;

#ifdef CONFIG_COUNTER
		if (++ms == 200)
		{
			ms = 0;
			if (++sec == 60)
			{
				// Write minute_count
				eeprom_write_byte(minute_current++, minute_count);
				sec = 0;
				minute_count = 0;
				chase = 1;
			}
		}

		if (chase)
		{
			if (ticks % 16 != 0)
				continue;
			outputs[0] = chases[(chase+0) % 6];
			outputs[1] = chases[(chase+1) % 6];
			outputs[2] = chases[(chase+2) % 6];
			outputs[3] = chases[(chase+3) % 6];
			if (chase++ == 6*2)
				chase = 0;
		
			continue;
		}

		uint16_t o = total_count;
#endif

		uint16_t o = 9999 - (ticks / 200);

		outputs[3] = digits[o % 10]; o /= 10;
		outputs[2] = digits[o % 10]; o /= 10;
		outputs[1] = digits[o % 10]; o /= 10;
		outputs[0] = digits[o % 10]; o /= 10;
	}
}