//http://www.ee.oulu.fi/~zyc/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
typedef uint8_t byte;
typedef int8_t sbyte;
typedef uint16_t word;
typedef int16_t sword;
#define DATAPORT PORTD
#define DATA PIND2
#define SHCLK PIND4
#define LCLK PIND5
#define OE PIND3
#define EE_DATAPORT PORTB
#define EE_INPORT PINB
#define EE_DATADR DDRB
#define EE_CLKPORT PORTD
#define EE_DATA PINB7
#define EE_CLK PIND6
//Rivien ohjaus (rivi ykkösellä aktiivinen):
#define ROWPORT PORTB
#define ROW0 PINB0
#define ROW1 PINB1
#define ROW2 PINB2
#define ROW3 PINB3
#define ROW4 PINB4
#define ROW5 PINB5
#define ROW6 PINB6
/* */
#define DISP_WIDTH 50 //Näytön leveys
#define MAX_FONT_WIDTH 6 //Fontin maksimileveys
#define UART_BUFFER_SIZE 20 //Sarjapuskurin koko tavuina
#define DISP_MEM_SIZE DISP_WIDTH+MAX_FONT_WIDTH
//Näitä viiveitä voi muuttaa tarpeen mukaan
#define DISP_SCROLL_DELAY 4 //Näyttöä siirretään (65536*DISP_SCROLL_DELAY)/F_CPU sekunnin välein
#define RECOVER_DELAY 10 //Viive, joka pidetään edellisen rivin sammuttamisen ja seuraavan sytyttämisen välillä
#define EE_FONTS_START_BLOCK 3 //Mistä EEPROMin 256-tavuisesta lohkosta alkaa fonttidata.
//Tekstin maksimimerkkimäärä on EE_FONTS_START_BLOCK * 256.
#define EE_TOTAL_BLOCKS 8
#define EE_FONT_WIDTH 5 //Montako tavua pitkä on yhden merkin fonttidata
// (montako pikseliä leveä fontin näkyvä osa voi olla)
#define FONT_MAX_COUNT (EE_TOTAL_BLOCKS - EE_FONTS_START_BLOCK)*256/EE_FONT_WIDTH
#define F_CPU 10000000LL /* 10Mhz */
#define UART_BAUD_RATE 9600LL /* 9600 baud */
#define UART_BAUD_SELECT (F_CPU/(UART_BAUD_RATE*16)-1)
#define CHANGE_MODE_DELAY 10 //Montako täyttä fontin leveyttä piirretään tyhjää, kun automaattisessa
//tekstimoodissa vaihdetaan tekstin lähdettä EEPROM <-> UART
/****************************************************************************************/
//Tietoliikennekomennot:
#define CMD_START 0xFF //Kaikki tietokoneeelta tulevat komennot alkavat tällä.
// Merkin CMD_START jälkeen tulee joku seuraavista:
//----------------------------------------------------------------------------------------------------
#define CMD_GET_FONT 0xFE //Käsketään näyttöä lähettämään fontit EEPROMilta
#define CMD_GET_TEXT 0xFD //Käsketään näyttöä lähettämään teksti EEPROMilta
#define CMD_MODE_TEXT_AUTO 0xEF //Asetetaan automaattinen tekstimoodin valinta
#define CMD_MODE_TEXT_UART 0xEE //Pakotetaan teksti luettavaksi ainoastaan sarjaportilta
//#define CMD_MODE_DIRECT_DRAW 0xED //Sarjaportilta tuleva data tulkitaan suoraan grafiikaksi
//#define CMD_MODE_SHIFT_DRAW 0xEC //Sama kuin edellä, mutta näyttöä siirretään vain, kun sarjaportilta tulee merkki.
//#define CMD_SCR_OFF 0xEB //Kääntää näytön piirron pois päältä
//#define CMD_SCR_ON 0xEA //Kääntää näytön piirron takaisin päälle
#define MESSAGE_FONT_START 0xDD //Tämä komento aloittaa fonttidatan EEPROMille kirjoitettavaksi
#define MESSAGE_TEXT_START 0x00 //Tämä komento aloittaa tekstidatan EEPROMille kirjoitettavaksi
//----------------------------------------------------------------------------------------------------
#define TEXT_END 0x00 //Tämä merkki lopettaa tekstidatan
//Toimintamoodit, joissa näyttö voi olla (arvoa pidetään muuttujassa control_mode):
#define MODE_TEXT_AUTO 0 //Automaattinen tekstimoodin valinta
#define MODE_TEXT_UART 1 //Teksti luetaan ainoastaan sarjaportilta
#define MODE_WRITE_TEXT 2 //Tekstin kirjoitus EEPROMille
#define MODE_WRITE_FONT 3 //Fontin kirjoitus EEPROMille
#define MODE_SEND_TEXT 4 //Tekstin lähetys tietokoneelle
#define MODE_SEND_FONT 5 //Fontin lähetys tietokoneelle
//#define MODE_DIRECT_DRAW 6 //Grafiikkamoodi (sarjaportilta tulevat tavut tulkitaan kuin yhden
// pikselin levyiset fontit ja piirretään suoraan vierivälle ruudulle)
//#define MODE_SHIFT_DRAW 7 //Grafiikkamoodi. Kuten edellä, mutta ruutua siirretään vain kun sarja-
//portilta luetaan uusi merkki.
/*********************************************************************************************/
byte display[DISP_MEM_SIZE]; //Näyttöpuskuri (rengaspuskuri)
byte uart_buffer[UART_BUFFER_SIZE]; //Sarjaliikennepuskuri merkkien vastaanottoa varten (rengaspuskuri)
byte font[MAX_FONT_WIDTH]; //Väliaikainen varasto yhden merkin "kuvalle"
volatile byte disp_start = 0; //Näyttöpuskurin alkukohta
volatile byte disp_end = DISP_WIDTH-1; //Näyttöpuskurin loppukohta
volatile sbyte disp_shift_count=0; //Tähän lasketaan näytön siirtoja, jotta tiedetään
//milloin näyttöpuskuriin tulee lisätä uusi merkki
byte uart_buffer_start = 0; //Sarjaliikennepuskurin alkukohta
volatile byte uart_buffer_end = 0; //Sarjaliikennepuskurin loppukohta
volatile byte bytes_in_buffer = 0; //Montako merkkiä sarjaliikennepuskurissa on odottamassa
//EEPROM:in muistiosoitteen käsittelyyn:
byte txt_block=0;
byte txt_address=0;
byte control_mode = MODE_TEXT_AUTO; //Ohjelman toimintamoodi
byte prev_mode = MODE_TEXT_AUTO;
byte change_mode_count = CHANGE_MODE_DELAY;
byte font_count=0;
word byte_count=0;
byte timeout=0;
/**************************************************************************************************/
//Pääohjelman käyttämät funktiot (esittely):
//Lähettää merkin sarjaportin kautta. Odottaa mahdollisen edellisen lähetyksen valmistumista.
void uart_send_byte(byte b);
//Hakee vastaanotetun merkin sarjaliikennepuskurista.
byte get_byte_from_buffer(void);
//Lisää merkin näyttöpuskurin loppuun. Päättää haetaanko merkin koodi sarjapuskurista vai EEPROMilta ja
//kopioi koodia vastaavan kuvan (fontin) EEPROMilta näyttömuistiin. Jos näytettävää ei ole, näyttömuistiin
//kopioidaan tyhjä merkki.
void char_to_scr(void);
//Piirtää ruudun yhden kerran. Tätä on kutsuttava säännöllisesti jotta taataan välkkymätön kuva.
void draw_screen(void);
//Pitää 10ms tauon.
void delay_10ms(void);
//Luetaan tavu EEPROMilta, parametreina annetuista muistilohkosta ja osoitteesta.
byte ee_read_byte(byte block, byte address);
//Kirjoitetaan tavu EEPROMille annettuun muistilohkoon ja osoitteeseen. Palauttaa 0, jos onnistui.
byte ee_write_byte(byte b, byte block, byte address);
/**************************************************************************************************/
//Keskeytysten käsittely:
// Keskeytys: Ajastimen 1 arvo asetetun arvon OCR1A kanssa täsmää:
SIGNAL(SIG_OUTPUT_COMPARE1A) {
if (control_mode == MODE_WRITE_TEXT || control_mode == MODE_WRITE_FONT) {
uart_send_byte(UART_BUFFER_SIZE-bytes_in_buffer);
}
else {
if (disp_shift_count < 0) return;
//Scroll display
disp_start++;
if (disp_start >= DISP_MEM_SIZE) disp_start=0;
disp_end++;
if (disp_end >= DISP_MEM_SIZE) disp_end=0;
disp_shift_count--;
}
}
// Keskeytys: Merkin vastaanotto sarjaportin kautta suoritettu:
SIGNAL(SIG_UART_RECV) {
if (bytes_in_buffer == UART_BUFFER_SIZE) {
byte crap = UDR;
}
else {
uart_buffer[uart_buffer_end] = UDR;
uart_buffer_end++;
if (uart_buffer_end == UART_BUFFER_SIZE) uart_buffer_end=0;
bytes_in_buffer++;
}
}
/****************************************************************************************************/
/***************************************************************************************/
/* Pääohjelma: päivittää näyttöä ja/tai toimii asetetun toimintamoodin mukaisesti: */
/***************************************************************************************/
int main(void) {
byte a;
for(a=0; a<DISP_MEM_SIZE; a++) {
display[a]=0;
}
PORTB=0;
PORTD=0;
DDRB=0xFF; /* PortB output */
DDRD=0xFF; /* PortD output */
TIMSK = _BV(OCIE1A); /* Enable interrupt on compare match */
TCCR1B = (4 | _BV(WGM12)); /* count with cpu clock/256, clear timer on compare match */
OCR1AH = DISP_SCROLL_DELAY;
OCR1AL = 0;
/* enable RxD/TxD and int */
UCSRB = _BV(RXCIE) | _BV(RXEN) | _BV(TXEN);
/* set baud rate */
UBRRL = UART_BAUD_SELECT;
sei(); /* enable interrupts */
a=0;
while (1) {
if (control_mode == MODE_SEND_TEXT) {
if ((UCSRA & _BV(UDRE)) != 0) {
byte c = TEXT_END;
if (txt_block < EE_FONTS_START_BLOCK) c = ee_read_byte(txt_block,txt_address);
UDR = c;
if (c == TEXT_END) {
txt_address=0;
txt_block=0;
control_mode = prev_mode;
if (control_mode == MODE_TEXT_UART) change_mode_count=2*CHANGE_MODE_DELAY;
else change_mode_count=0;
}
else if (++txt_address == 0) txt_block++;
}
}
else if (control_mode == MODE_WRITE_TEXT) {
while (1) {
if (bytes_in_buffer == 0) continue;
byte c = get_byte_from_buffer();
if ( txt_block == EE_FONTS_START_BLOCK || (ee_write_byte(c,txt_block,txt_address) != 0) || (c == TEXT_END) ) {
txt_address=0;
txt_block=0;
control_mode = prev_mode;
if (control_mode == MODE_TEXT_UART) change_mode_count=2*CHANGE_MODE_DELAY;
else change_mode_count=0;
break;
}
else if (++txt_address == 0) txt_block++;
}
}
else if (control_mode == MODE_SEND_FONT) {
byte a;
for (a=0; a < EE_FONT_WIDTH; a++) {
byte c = ee_read_byte(txt_block,txt_address);
uart_send_byte(c);
if (++txt_address == 0) txt_block++;
}
if (--font_count == 0) {
txt_address=0;
txt_block=0;
control_mode = prev_mode;
if (control_mode == MODE_TEXT_UART) change_mode_count=2*CHANGE_MODE_DELAY;
else change_mode_count=0;
}
}
else if (control_mode == MODE_WRITE_FONT) {
while (1) {
while (bytes_in_buffer > 0) {
byte c = get_byte_from_buffer();
if ((ee_write_byte(c,txt_block,txt_address) != 0) || (--byte_count == 0)) {
timeout=0xFF; //End
}
else {
if (++txt_address == 0) txt_block++;
timeout=0;
}
}
timeout++;
if (timeout == 0) {
txt_address=0;
txt_block=0;
control_mode = prev_mode;
if (control_mode == MODE_TEXT_UART) change_mode_count=2*CHANGE_MODE_DELAY;
else change_mode_count=0;
break;
}
delay_10ms();
}
}
draw_screen();
if (disp_shift_count < 0) char_to_scr();
}
}
/****************************************************************************************************/
/*********************/
/* Aliohjelmat: */
/*********************/
//Lähettää merkin sarjaportin kautta
void uart_send_byte(byte b) {
while ((UCSRA & _BV(UDRE)) == 0) ; //Odotetaan, että edellinen lähetys valmistuu.
UDR = b;
}
byte get_byte_from_buffer(void) {
if (bytes_in_buffer == 0) return 0;
byte b = uart_buffer[uart_buffer_start++];
if (uart_buffer_start == UART_BUFFER_SIZE) uart_buffer_start=0;
bytes_in_buffer--;
return b;
}
void set_mode(void) {
byte cmd=get_byte_from_buffer(); //Otetaan CMD_START pois puskurista
while (bytes_in_buffer == 0); //Odotetaan komentoa
cmd=get_byte_from_buffer();
switch(cmd) {
case CMD_GET_TEXT:
prev_mode = control_mode;
control_mode = MODE_SEND_TEXT;
uart_send_byte(MESSAGE_TEXT_START);
txt_address=0;
txt_block=0;
break;
case MESSAGE_TEXT_START:
prev_mode = control_mode;
control_mode = MODE_WRITE_TEXT;
txt_address=0;
txt_block=0;
break;
case CMD_GET_FONT:
prev_mode = control_mode;
control_mode = MODE_SEND_FONT;
uart_send_byte(MESSAGE_FONT_START);
txt_address=EE_FONT_WIDTH; //Jätetään nollas merkki välistä
txt_block=EE_FONTS_START_BLOCK;
font_count=(byte)(FONT_MAX_COUNT-1);
break;
case MESSAGE_FONT_START:
prev_mode = control_mode;
control_mode = MODE_WRITE_FONT;
txt_address=EE_FONT_WIDTH;
txt_block=EE_FONTS_START_BLOCK;
byte_count=(FONT_MAX_COUNT-1)*EE_FONT_WIDTH;
timeout=0;
break;
}
}
void row_delay(void) {
byte a;
for(a=0; a<RECOVER_DELAY; a++) asm("nop");
}
void get_font(byte c) {
byte b;
byte font_address=0, font_block=EE_FONTS_START_BLOCK;
word address=(word)c*EE_FONT_WIDTH;
font_block+=address/256;
font_address=address%256;
for(b=0; b<EE_FONT_WIDTH; b++) {
font[b]=ee_read_byte(font_block, font_address);
if (++font_address==0) font_block++;
}
}
void clear_font(void) {
byte a;
byte mask=1;
for(a=0; a<MAX_FONT_WIDTH; a++) {
if (MAX_FONT_WIDTH & mask) font[a] = 1;
else font[a] = 0;
mask<<=1;
}
}
//Piirtää fontin fonttipuskurista näyttömuistiin.
//Funktio palauttaa piirrettyjen sarakkeiden määrän.
byte font_to_scr(void) {
byte c, width=0;
byte mask=1;
for(c=0;c<EE_FONT_WIDTH; c++) {
if (font[c] & 1) width |= mask;
mask<<=1;
}
if (width > MAX_FONT_WIDTH) width = MAX_FONT_WIDTH;
byte char_col=disp_end+1;
for(c=0;c<width;c++) {
if (char_col >= DISP_MEM_SIZE) char_col=0;
display[char_col]=font[c];
char_col++;
}
return width;
}
void char_to_scr(void) {
clear_font();
if (control_mode == MODE_TEXT_AUTO || control_mode == MODE_TEXT_UART) {
if (bytes_in_buffer == 0) {
uart_send_byte(UART_BUFFER_SIZE);
if (control_mode == MODE_TEXT_AUTO) {
change_mode_count++;
if (change_mode_count > CHANGE_MODE_DELAY) {
change_mode_count = 2*CHANGE_MODE_DELAY;
byte c = ee_read_byte(txt_block,txt_address);
if (c == TEXT_END) {
txt_address=0;
txt_block=0;
}
else {
get_font(c);
if (++txt_address == 0) txt_block++;
}
}
}
else change_mode_count=0;
}
else {
uart_send_byte(UART_BUFFER_SIZE-bytes_in_buffer);
if (uart_buffer[uart_buffer_start] == CMD_START) set_mode();
else if (change_mode_count > CHANGE_MODE_DELAY) change_mode_count--;
else {
byte c = get_byte_from_buffer();
if (c != TEXT_END && c != CMD_START) get_font(c);
change_mode_count=0;
txt_address=0;
txt_block=0;
}
}
}
disp_shift_count += font_to_scr();
}
void draw_screen(void) {
byte rowmask,i;
//Bittimaski, jolla muistista otetaan oikea bitti. Aloitetaan toisen piirrettävän rivin kohdalta.
//Vähiten merkitsevää bittiä ei siirretä muistista näytölle, koska näytössä on vain 7 riviä
byte memmask=4;
for(rowmask=1; rowmask<0x80; rowmask<<=1) { //Piirretään rivit 0-6
//Edellisen rivin data siirtorekisterien ulostuloihin
DATAPORT |= _BV(LCLK); //Aseta nasta LCLK (Latch clock)
DATAPORT &= ~_BV(LCLK); //Nollaa nasta LCLK
//Edellinen rivi palaa samalla kun siirretään dataa nykyiselle riville
ROWPORT |= rowmask; //Sytytä maskin osoittama rivi (eli transistorin ohjausnasta ylös).
if (memmask == 0) memmask=2; //Kun muistimaski menee viimeisen rivin ohi, valitaan ensimmäinen rivi
cli(); //Rivi halutaan tallentaa rauhassa; ei keskeytyksiä. Nyt rivi ei muutu kesken kaiken.
i=disp_start; //Aloitetaan ensimmäisestä sarakkeesta.
do {
if (i >= DISP_MEM_SIZE) i=0;
if (display[i] & memmask) DATAPORT |= _BV(DATA); //sbi(DATAPORT,DATA);
DATAPORT |= _BV(SHCLK); //sbi(DATAPORT,SHCLK);
DATAPORT &= ~_BV(SHCLK); //cbi(DATAPORT,SHCLK);
DATAPORT &= ~_BV(DATA); //cbi(DATAPORT,DATA);
} while(i++ != disp_end);
sei(); //Nyt saa taas keskeyttää.
memmask<<=1;
ROWPORT &= ~rowmask; //Sammutetaan edellinen rivi. Siirtorekisterissä on nyt seuraavan rivin data
row_delay(); //Pidetään tauko, jotta transistori ehtii kunnolla lakata johtamasta.
}
//Eli silmukan päätteeksi siirtorekistereihin jää seuraavaa
//päivitystä varten ensimmäisen rivin sisältö.
}
//----------------------------------------------------------------------
/**********************************************************/
/* I2C - rutiineja (EEPROMia varten) */
/**********************************************************/
//Muuttujat, joita lukurutiini käyttää pitämään kirjaa nykyisestä
//muistilohkosta ja osoitteesta lohkon sisällä. Näin pääohjelman
//ei tarvitse tietää, milloin pitää kirjoittaa uusi osoite EEPROMille
byte ee_block=255, ee_address=255;
void delay_10ms(void) {
byte a; byte b;
for (a=0; a < ((F_CPU/102400LL)+1); a++) { //Yksi kierros vie noin 1024 kelloa
for (b=0; b<255; b++);
}
}
//Tauko, joka pidetään vähän joka välissä.
void ee_wait(void) {
asm("nop\nnop\nnop\nnop");
}
//Asettaa AVR:stä EEPROMin datapinnin lukutilaan
void ee_set_data_input(void) {
EE_DATADR &= ~_BV(EE_DATA); //cbi(EE_DATADR, EE_DATA); //Datapinni lukutilaan
}
//Asettaa AVR:stä EEPROMin datapinnin kirjoitustilaan
void ee_set_data_output(void) {
EE_DATADR |= _BV(EE_DATA); //sbi(EE_DATADR, EE_DATA); //Datapinni kirjoitustilaan
}
void ee_set_data(void) {
EE_DATAPORT |= _BV(EE_DATA); //sbi(EE_DATAPORT, EE_DATA);
ee_wait();
}
void ee_clear_data(void) {
EE_DATAPORT &= ~_BV(EE_DATA); //cbi(EE_DATAPORT, EE_DATA);
ee_wait();
}
void ee_set_clock(void) {
EE_CLKPORT |= _BV(EE_CLK); //sbi(EE_CLKPORT, EE_CLK);
ee_wait();
}
void ee_clear_clock(void) {
EE_CLKPORT &= ~_BV(EE_CLK); //cbi(EE_CLKPORT, EE_CLK);
ee_wait();
}
void ee_start(void) {
ee_set_data();
ee_set_clock();
ee_clear_data();
ee_clear_clock();
}
void ee_stop(void) {
ee_set_clock();
ee_set_data();
ee_clear_clock();
ee_clear_data();
}
byte ee_byte_in(void) {
ee_set_data_input();
EE_DATAPORT |= _BV(EE_DATA); //sbi(EE_DATAPORT, EE_DATA);
byte b=0,mask;
for (mask=0x80; mask>0; mask>>=1) {
ee_set_clock();
if (EE_INPORT&(1<<EE_DATA)) b|=mask;
ee_clear_clock();
}
ee_set_data_output();
EE_DATAPORT &= ~_BV(EE_DATA); //cbi(EE_DATAPORT, EE_DATA);
return b;
}
byte ee_byte_out(byte b) {
byte mask;
for (mask=0x80; mask>0; mask>>=1) {
if (b & mask) ee_set_data();
else ee_clear_data();
ee_set_clock();
ee_clear_clock();
}
ee_set_data_input();
ee_set_data();
ee_set_clock();
byte timeout;
for(timeout=255; timeout>0; timeout--) { //Wait for ACK
if ((EE_INPORT & _BV(EE_DATA)) == 0) {
EE_DATAPORT &= ~_BV(EE_DATA); //cbi(EE_DATAPORT, EE_DATA);
ee_set_data_output();
ee_clear_clock();
return 0;
}
}
ee_clear_clock();
ee_clear_data();
ee_set_data_output();
return 1; //Timeout
}
byte ee_addr(byte block, byte address) {
ee_block=block;
ee_address=address;
byte control=0xA0; //10100000b
control |= (block<<1);
if (ee_byte_out(control)) return 1;
if (ee_byte_out(address)) return 1;
return 0;
}
/*************************************************
Näitä rutiineja pääohjelma kutsuu:
*************************************************/
//Luetaan tavu EEPROMilta annetusta muistilohkosta ja osoitteesta
byte ee_read_byte(byte block, byte address) {
if (block!=ee_block || address!=ee_address) {
ee_start();
if (ee_addr(block,address)) return 0;
}
ee_start();
byte control=0xA1; //10100001b
control |= (block<<1);
if (ee_byte_out(control)) return 0;
byte b=ee_byte_in();
ee_stop();
if (++ee_address == 0) {
if (++ee_block >= EE_TOTAL_BLOCKS) ee_block = 0;
}
return b;
}
//Kirjoitetaan tavu EEPROMille annettuun muistilohkoon ja osoitteeseen. Palauttaa 0, jos onnistui.
byte ee_write_byte(byte b, byte block, byte address) {
ee_start();
if (ee_addr(block,address)) return 1;
if (ee_byte_out(b)) return 1;
ee_stop();
ee_address++;
delay_10ms(); //Odotetaan, että merkki ehditään varmasti kirjoittaa
return 0;
}