lcd demo di avrlibc

Ho provato il codice lcd demo incluso nella libreria avrlibc, devo dire che è rapidissimo, io pensavo fosse il display lento ad aggiornarsi invece era il codice che usavo prima che aveva latenze.

In quel codice ci sono davvero cose interessanti, la prima è la scrittura nel display tramite la comune funzione fprintf. Ciò è dovuto a questa dichiarazione iniziale presente nel codice che ancora non ho compreso a fondo. Lo stesso sistema viene usato per la scrittura in seriale:

FILE lcd_str = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE);

dove lcd_putchar è la funzione che scrive un carattere nel display.

Esempio di uso:

    set_cursor_at(0,0);
    fprintf(stderr, "locate 0,0");
    set_cursor_at(1, 1);
    fprintf(stderr, "locate 1,1");
    set_cursor_at(2, 2);
    fprintf(stderr, "locate 2,2");
    set_cursor_at(3, 3);
    fprintf(stderr, "locate 3,3"

io ho aggiunto la funzione locate, l'interpretazione del carattere \n che ritorna a capo.
Sto lavorando allo scroll verticale o meglio lo scroll funziona ma vorrei aggiungere altro come ad esempio un buffer che mantiene le righe che a seguito dello scroll si perdono, con la possibilità di fare lo scroll in senso contrario tramite tasti di navigazione su e giu.

Oppure come lo fareste voi?

Ciao.

Ti costruisci un buffer video con un array X*Y, con Y pari al numero di colonne del display e con X che contiene un numero arbitrario ma superiore alle reali righe del display, in modo che l'LCD sia una semplice "finestra" aperta su una specie di nastro scorrevole.
In questo modo premendo il tasto "su" non fai altro che spostare l'indice della "finestra" in modo che ciò che deve essere visualizzato venga poi spedito all'LCD da una seconda funzione.

Esempio:

XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
oooooooooo
oooooooooo
oooooooooo
oooooooooo
XXXXXXXXXX
XXXXXXXXXX

Qui hai un buffer 10x10. L'indice punterà a 4 (5a riga) per cui la funzione visualizzaDati(indice) stamperà le righe da indice a indice+3.
Premendo il tasto in "giù" l'indice scorrerà fino a 6 (perché 6+3=9, che è l'ultima riga del buffer).

Ok grazie leo per la risposta.

Dunque ora vediamo, diciamo che deve essere efficiente in termini di memoria, quindi è d'obbligo l'allocazione di memoria dinamica.

Per adesso uso un buffer di riga grande quanto è largo il display.

char *rowBuffer = NULL;
rowBuffer = calloc(_LCD_nColumns+1, sizeof(char));

Nel mio caso:
_LCD_nCoulumns + 1 = 21, che sarebbe 20 carattere + il terminatore \0.

Mi basta ora un'array di puntatori ad rowBuffer, grande almeno quanto il numero di righe del display in uso.

Però ancora non ho chiaro se conviene scrivere nel buffer e poi dal buffer trasferire nel display, cosa che deve essere fatta per ogni carattere, oppure cominciare a scrivere nel display, come faccio ora, e se c'è scroll dimensionare il buffer per contenere le righe perse e salvargli la riga, in questo modo la scrittura sarà più rapida perchè diretta e solo se c'è necessità di scroll impegno ram che invece dovrei avere per forza a prescindere.

Ciao.

Quella demo è rilasciata sotto questa incredibile licenza.

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * ----------------------------------------------------------------------------
 *
 * Stdio demo, upper layer of LCD driver.
 *
 * $Id: lcd.c 1008 2005-12-28 21:38:59Z joerg_wunsch $
 */

Io preferirei la licenza LGPL, ma non so se visto le modifiche sia possibile cambiarla in LGPL, mi tocca riscriverla e specificare l'origine del codice.

// file lcd.c

#include "defines.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>

#include "hd44780.h"
#include "lcd.h"

/*
 * _LCD_rowAddress[], _LCD_nRows, _LCD_nColumns vengono inizializzati
 * grazie alla macro LCD_20x4 o LCD_16x1 ecc presenti in lcd.h
 */
extern uint8_t _LCD_row_address[];
extern uint8_t _LCD_n_rows;
extern uint8_t _LCD_n_columns;

/*
 * Mantiene info circa la riga corrente
 */
static uint8_t currentRow = 0;
/*
 * Mantiene info circa la colonna corrente
 */
static uint8_t currentCol = 0;

/*
 * il verso dello scroll verticale
 */
enum ScrollDirection { up, down };

/*
 * il buffer di riga
 */
char *rowBuffer = 0;

/*
 * sposta il "cursore" in base ai valori di currentRow currentColumn;
 */
void setRowAddress();
void setRowAddress()
{

    // wait_ready da chiamare sempre prima di ogni dialogo con il display
    hd44780_wait_ready(false);
    // contiene l'indirizzo di ogni inizio riga
    uint8_t currentAddress = _LCD_row_address[currentRow];
    hd44780_outcmd(HD44780_DDADDR(currentAddress + currentCol));

}

void scroolView(enum ScrollDirection dir);
void scroolView(enum ScrollDirection dir)
{
    uint8_t crow;
    switch(dir) {
    case up:
        break;
    case down:
        // conta da riga 1 a riga _LCD_n_rows - 1
        for (uint8_t irow = 1; irow < _LCD_n_rows; irow++) {

            // crow contiene l'indirizzo della riga corrente irow
            crow = _LCD_row_address[irow];
            
            // conta da 0 a _LCD_n_columns - 1
            for (uint8_t icol = 0; icol < _LCD_n_columns; icol++) {
                hd44780_wait_ready(false);
                hd44780_outcmd(HD44780_DDADDR(crow + icol));
                hd44780_wait_ready(false);
                //  legge un carattere dal display e lo salva nel rowBuffer
                rowBuffer[icol] = (char)hd44780_inbyte(1);

            }

            // scrive il contenuro di rowBuffer nel display partendo 
            // da irow-1 che sarebbe la riga precedente
            for (uint8_t icol = 0; icol < _LCD_n_columns; icol++) {

                hd44780_wait_ready(false);
                hd44780_outcmd(HD44780_DDADDR(_LCD_row_address[irow-1]+icol));
                hd44780_wait_ready(false);
                hd44780_outdata(rowBuffer[icol]);

            }
        }

        // pulisce l'ultima riga del display scrivendo 20 spazi.
        for (uint8_t icol = 0; icol < _LCD_n_columns; icol++) {

            hd44780_wait_ready(false);
            hd44780_outcmd(HD44780_DDADDR(_LCD_row_address[_LCD_n_rows-1]+icol));
            hd44780_wait_ready(false);
            hd44780_outdata(' ');

        }
        break;
    }
}


/*
 * Setup the LCD controller.  First, call the hardware initialization
 * function, then adjust the display attributes we want.
 */
void
lcd_init(void)
{
    /*
     * alloca lo spazio per il buffer di riga
     * numero di colonne + 1 per fare spazio a \0
     */
    rowBuffer = calloc(_LCD_n_columns+1, sizeof(char));

    hd44780_init();

    /*
   * Clear the display.
   */
    hd44780_outcmd(HD44780_CLR);
    hd44780_wait_ready(false);

    /*
   * Entry mode: auto-increment address counter, no display shift in
   * effect.
   */
    hd44780_outcmd(HD44780_ENTMODE(1, 0));
    hd44780_wait_ready(false);

    /*
   * Enable display, activate non-blinking cursor.
   */
    hd44780_outcmd(HD44780_DISPCTL(1, 1, 0));
    hd44780_wait_ready(false);
}

/*
 * Send character c to the LCD display.  After a '\n' has been seen,
 * the next character will first clear the display.
 */
int
lcd_putchar(char c, FILE *unused)
{
    if (c == '\n') {

        currentRow++;
        // se necessario scrolla
        if (currentRow > _LCD_n_rows - 1) {

            scroolView(down);
            currentRow = _LCD_n_rows - 1;

        }

        currentCol = 0;
        setRowAddress();

    } else {

        hd44780_wait_ready(false);
        hd44780_outdata(c);
        currentCol++;
        if (currentCol > _LCD_n_columns-1) {

            currentCol = 0;
            currentRow++;
            // se necessario scrolla
            if (currentRow > _LCD_n_rows - 1) {

                scroolView(down);
                currentRow = _LCD_n_rows - 1;

            }
        }
        setRowAddress();
    }

    return 0;
}

int set_cursor_at(uint8_t row, uint8_t col)
{
    if ((row > _LCD_n_rows-1) || (col > _LCD_n_columns))
        return -1;

    currentRow = row;
    currentCol = col;
    setRowAddress();

    return 0;
}
// file lcd.h

#include <stdint.h>

/*
 * LCD 16 column one row
 * macro to be invoked outside the main, before lcd_init ()
 */
#define LCD_16x1 uint8_t _LCD_n_columns = 16;\
    uint8_t _LCD_row_address[] = { 0x0, 0x40 };\
    uint8_t _LCD_n_rows = 4
/*
 * LCD 20 column four row
 * macro to be invoked outside the main, before lcd_init ()
 */
#define LCD_20x4 uint8_t _LCD_n_columns = 20; \
    uint8_t _LCD_row_address[] = { 0x0, 0x40, 0x14, 0x54 };\
    uint8_t _LCD_n_rows = 4

/*
 * Initialize LCD controller.  Performs a software reset.
 */
void	lcd_init(void);

/*
 * Send one character to the LCD.
 */
int	lcd_putchar(char c, FILE *stream);

/*
 * set cursor position at row and col.
 * return 0 on success, -1 otherwise
 */
int     set_cursor_at(uint8_t row, uint8_t col);

Il main non è cambiato ad eccezione della macro che serve a specificare quale display usare, nel mio caso
un LCD 20x4.

// use LCD 20x4
LCD_20x4;

int
main(void)
{
    ...

Ciao.

Licenza stupenda XD

Qualcosa in più l'ho capita, mi manca ancora un tassello.

Provo a spiegare:
Le funzioni io della libreria standard C necessitano di una certa flessibilità, in genere fprint() e fprintf() servono per scrivere su file, però dal momento che i file su arduino per default non ci sono queste funzioni rimangono inutilizzate o meglio di default fprint stampa su stdout che è mappata sulla seriale zero, rimangono gli altri stream standard stdin e stderr, chi conosce unix o linux sa di cosa parlo.

La seguente riga prima misteriosa, serve a creare una istanza di struttura __file, perchè nel file stdio.h si vede che "FILE" è una macro e nella descrizione viene spiegato che FILE è un tipo opaco, infatti non posso accedere ai campi della struttura dalla macro FILE.

FILE lcd_str = FDEV_SETUP_STREAM(lcd_putchar, NULL, _FDEV_SETUP_WRITE);

Segue un'estratto di codice dal file stdio.h:

struct __file {
	char	*buf;		/* buffer pointer */
	unsigned char unget;	/* ungetc() buffer */
	uint8_t	flags;		/* flags, see below */
#define __SRD	0x0001		/* OK to read */
#define __SWR	0x0002		/* OK to write */
#define __SSTR	0x0004		/* this is an sprintf/snprintf string */
#define __SPGM	0x0008		/* fmt string is in progmem */
#define __SERR	0x0010		/* found error */
#define __SEOF	0x0020		/* found EOF */
#define __SUNGET 0x040		/* ungetc() happened */
#define __SMALLOC 0x80		/* handle is malloc()ed */
#if 0
/* possible future extensions, will require uint16_t flags */
#define __SRW	0x0100		/* open for reading & writing */
#define __SLBF	0x0200		/* line buffered */
#define __SNBF	0x0400		/* unbuffered */
#define __SMBF	0x0800		/* buf is from malloc */
#endif
	int	size;		/* size of buffer */
	int	len;		/* characters read or written so far */
	int	(*put)(char, struct __file *);	/* function to write one char to device */
	int	(*get)(struct __file *);	/* function to read one char from device */
	void	*udata;		/* User defined and accessible data. */
};

#endif /* not __DOXYGEN__ */

/*@{*/
/**
   \c FILE is the opaque structure that is passed around between the
   various standard IO functions.
*/
#define FILE	struct __file

Ora sapete come creare tipi opachi, nulla impedisce di fare ciò anche con il C++ e le classi. Quello che ancora non ho capito e se posso aggiungere uno stream oltre a quelli tipici di unix, se date un'occhio al file vedete che gli stardard stream sono così: #define stderr (__iob[2]) e iob è dichiarato extern così :
extern struct __file *__iob[]; che in pratica è un'array di puntatori a struttura che si trova in qualche file ".c" della libc standard, mi basterebbe avere la possibilità di ridimensionare __iob[] per far spazio a più puntatori, es uno per la twi, per le altre seriali, per la ISP ecc.

Ora che la lib per lcd è terminata e funzionante passo a twi dove ho ancora le idee confuse, aspettatevi
domande generiche sulla implementazione TWI.

Ciao e grazie per l'attenzione.