model100 / lcd.c

/**
 * \file HD44102 driver
 *
 * Preparation for Model 100 retrofit
 *
 * CS1 is tied to ground on all chips.
 * CS2 is exposed per chip
 * CS3 is common to all chips, named CS1 on schematic
 * 
 * To select a chip, CS2 and CS3 must be high
 *
 * In write mode, data is latched on the fall of LCD_EN
 * LCD_DI high == data, low == command
 *
 * Keep free:
 * i2c: PD0, PD1
 * RS232: PD2, PD3
 * SPI: PB3, PB2, PB1, PB0
 */

#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 "bits.h"
#include "lcd.h"

#define LCD_V2		0xB7 // 4, Analog voltage to generate negative voltage
#define LCD_VO		0xB6 // Analog voltage to control contrast
#define LCD_RESET	0xD4 // 17
#define LCD_CS1		0xD5 // 18
#define LCD_EN		0xE0 // 19
#define LCD_RW		0xD7 // 20
#define LCD_DI		0xE1 // 21
#define LCD_BZ		0xB5 // 2

#define LCD_DATA_PORT	PORTC // 22-29
#define LCD_DATA_PIN	PINC
#define LCD_DATA_DDR	DDRC

#define LCD_CS20	0xF0
#define LCD_CS21	0xF1
#define LCD_CS22	0xF2
#define LCD_CS23	0xF3
#define LCD_CS24	0xF4
#define LCD_CS25	0xF5
#define LCD_CS26	0xF6
#define LCD_CS27	0xF7
#define LCD_CS28	0xE6
#define LCD_CS29	0xE7


static uint8_t
lcd_command(
	const uint8_t byte,
	const uint8_t di // 0 == instruction, 1 == data
)
{
	out(LCD_DI, di);
	out(LCD_RW, 0); // write
	out(LCD_EN, 1);
	LCD_DATA_DDR = 0xFF;
	_delay_us(2);
	LCD_DATA_PORT = byte;
	_delay_us(2);
	out(LCD_EN, 0);

	// value has been sent, go into read mode
	_delay_us(2);
	LCD_DATA_PORT = 0x00; // no pull ups
	LCD_DATA_DDR = 0x00;

	out(LCD_DI, 0); // status command
	out(LCD_RW, 1); // read

	out(LCD_EN, 1);
	_delay_us(10);
	uint8_t rc = LCD_DATA_PIN;
	out(LCD_EN, 0);

	// Everything looks good.
	out(LCD_RW, 0); // go back into write mode
	return rc;
}


static inline uint8_t
lcd_write(
	const uint8_t byte
)
{
	return lcd_command(byte, 1);
}


static void
lcd_vee(
	uint16_t x
)
{
	OCR1C = x;
}


static void
lcd_contrast(
	uint16_t x
)
{
	OCR1B = x;
}


static void
lcd_on(
	const uint8_t pin
)
{
	out(pin, 1);

	// Turn on display
	lcd_command(0x39, 0);
	_delay_ms(1);

	// Up mode
	lcd_command(0x3B, 0);
	_delay_ms(1);

	// Start at location 0
	lcd_command(0x00, 0);
	_delay_ms(1);

	// Display start page 0
	lcd_command(0x3E, 0);
	_delay_ms(1);

	out(pin, 0);
}


void
lcd_init(void)
{
	LCD_DATA_PORT = 0x00;
	LCD_DATA_DDR = 0x00;

	out(LCD_DI, 0);
	out(LCD_RW, 0);
	out(LCD_EN, 0);
	out(LCD_V2, 0);
	out(LCD_VO, 0);
	out(LCD_DI, 0);
	out(LCD_CS1, 0);
	out(LCD_CS20, 0);
	out(LCD_CS21, 0);
	out(LCD_CS22, 0);
	out(LCD_CS23, 0);
	out(LCD_CS24, 0);
	out(LCD_CS25, 0);
	out(LCD_CS26, 0);
	out(LCD_CS27, 0);
	out(LCD_CS28, 0);
	out(LCD_CS29, 0);
	out(LCD_RESET, 0);
	out(LCD_BZ, 0);

	ddr(LCD_DI, 1);
	ddr(LCD_RW, 1);
	ddr(LCD_EN, 1);
	ddr(LCD_V2, 1);
	ddr(LCD_VO, 1);
	ddr(LCD_CS1, 1);
	ddr(LCD_RESET, 1);
	ddr(LCD_BZ, 1);

	ddr(LCD_CS20, 1);
	ddr(LCD_CS21, 1);
	ddr(LCD_CS22, 1);
	ddr(LCD_CS23, 1);
	ddr(LCD_CS24, 1);
	ddr(LCD_CS25, 1);
	ddr(LCD_CS26, 1);
	ddr(LCD_CS27, 1);
	ddr(LCD_CS28, 1);
	ddr(LCD_CS29, 1);


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

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

	// OC1B is used to control brightness via PWM
	// Configure output mode to clear on match, set at top
	sbi(TCCR1A, COM1B1);
	cbi(TCCR1A, COM1B0);

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

	lcd_vee(0x100); // 50% duty cycle
	lcd_contrast(0x280); // almost +5V
	
	_delay_ms(20);

	// Raise the reset line, to bring the chips online
	out(LCD_RESET, 1);

	// Raise the master select line, since we always want to talk to
	// all chips.  We leave it up since we'll be asserting each one
	// individually in a little while.
	out(LCD_CS1, 1);

	lcd_on(LCD_CS20);
	lcd_on(LCD_CS21);
	lcd_on(LCD_CS22);
	lcd_on(LCD_CS23);
	lcd_on(LCD_CS24);
	lcd_on(LCD_CS25);
	lcd_on(LCD_CS26);
	lcd_on(LCD_CS27);
	lcd_on(LCD_CS28);
	lcd_on(LCD_CS29);

	// Bring LCD select back down since we don't want to
	// talk to the LCD while strobing the keyboard.
	out(LCD_CS1, 0);
}


/** Enable the one chip, select the address and send the byte.
 *
 * x goes from 0 to 50, y goes from 0 to 32, rounded to 8.*/
static void
lcd_doit(
	const uint8_t pin,
	uint8_t x,
	uint8_t y,
	uint8_t val
)
{
	out(pin, 1);
	lcd_command((y >> 3) << 6 | x, 0);
	lcd_command(val, 1);
	out(pin, 0);
}


/** Display val at position x,y.
 *
 * x is ranged 0 to 240, for each pixel
 * y is ranged 0 to 64, rounded to 8
 */
void
lcd_display(
	uint8_t x,
	uint8_t y,
	uint8_t val
)
{
	out(LCD_CS1, 1);

	if (y < 32)
	{
		// Top half of the display
		if (x < 50)
			lcd_doit(LCD_CS20, x - 0, y - 0, val);
		else
		if (x < 100)
			lcd_doit(LCD_CS21, x - 50, y - 0, val);
		else
		if (x < 150)
			lcd_doit(LCD_CS22, x - 100, y - 0, val);
		else
		if (x < 200)
			lcd_doit(LCD_CS23, x - 150, y - 0, val);
		else
			lcd_doit(LCD_CS24, x - 200, y - 0, val);
	} else {
		// Bottom half of the display
		if (x < 50)
			lcd_doit(LCD_CS25, x - 0, y - 32, val);
		else
		if (x < 100)
			lcd_doit(LCD_CS26, x - 50, y - 32, val);
		else
		if (x < 150)
			lcd_doit(LCD_CS27, x - 100, y - 32, val);
		else
		if (x < 200)
			lcd_doit(LCD_CS28, x - 150, y - 32, val);
		else
			lcd_doit(LCD_CS29, x - 200, y - 32, val);
	}

	out(LCD_CS1, 0);
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.