Programmazione Arduino in C puro

PaoloP:
x iscrizione

Pure io.

partiamo a fare le cose serie.

da un pò di tempo a questa parte ho inizato a scroprire la X macro, in pratica è un for-each (ovvero un for su un gruppo di elementi NOTO) a livello di pre-compilatore. E me ne sono INNAMORATO!

vediamo come prenderne vantaggio:

  1. creiamo un file, ports.h, con cui dichiariamo la LETTERA delle porte dispoibili, sia in settagio (DDRx setta in/out), che in lettura (PINx) che in scrittura (PORTx)
#ifdef DDRA && PORTA && PINA
X(A)
#endif
#ifdef DDRB && PORTB && PINB
X(B)
#endif
#ifdef DDRC && PORTC && PINC
X(C)
#endif
#ifdef DDRD && PORTD && PIND
X(D)
#endif
#ifdef DDRE && PORTE && PINE
X(E)
#endif
#ifdef DDRF && PORTF && PINF
X(F)
#endif
#ifdef DDRG && PORTG && PING
X(G)
#endif
#ifdef DDRH && PORTH && PINH
X(H)
#endif

benissimo, ora abbiamo un elenco che se dato in pasto alla X macro, in questo modo:

#define X(a) a
	#include "ports.h"
#undef X

restituisce l'elenco delle lettere A, B, C, etcc.. in base a se esiste o meno la porta!

quindi a questo punto possiamo usare un sistema parecchio grezzo e fare:

#define SEP ,
#define X(a) &DDR##a SEP
static const uint8_t *set_reg[] =
{
	#include "ports.h"
};
#undef X

#define X(a) &PORT##a SEP
static const uint8_t *out_reg[] =
{
	#include "ports.h"
};
#undef X

#define X(a) &PIN##a SEP
static const uint8_t *in_reg[] =
{
	#include "ports.h"
};
#undef X
#undef SEP

che si traduce in 3 array che contengono tanti elementi quanti le porte, ed ognuno punta alla relativa porta (ovvero i 3 array dellesempio di qualche post fa)

Ma non è molto utile, vero? quindi sto studiando un sistema di union/struct per fare in modo tale che tutto diventi hardcodato.

lesto ti comunico che ci sei riuscito a fare andare nel pallone pure me. :wink:

è uno sfift se è una potenza di 2 il divisiore..

Si vero, dipende dal valore che assume la variabile pin.

Ma non è molto utile, vero? quindi sto studiando un sistema di union/struct per fare in modo tale che tutto diventi hardcodato.

Va bene tu hai un ritmo sostenuto per me, non voglio essere da freno, però il codice precedente non è malvagio da buttarlo cioè, ha i suoi pregi. Se parli di hardcodato penso che si perde la capacità di ricavare i registri a runtime, se è così potrebbero convivere due varianti di pinMode, di digitalWrite ecc.

Ciao.

esagerato, è che ho già fatto qualche gioco con la X macro e questo sistema (un "database" di strutture, tu in un file metti la tua struttura che vuoi in database, e lui ti genere getter e setter. (sempre tutto statico, un bel esercizio di stile se posso permettermi, GitHub - MauroMombelli/microStructDb: this is a fast example of a struct's based DB in C (ansi?))

per la storia di "non molto utile" sono combattutto a dire la verità; in realtà se metti i NULL è rischioso perchè se cambi il target di compilazione NON ti accorgi degli errori; se non metti i NULL, a seconda di che architettura compili ad un determinato indice non sai che "lettera" di registro è assegnata (ma in compenso non hai i buchi NULL).

a tra poco per l'idea sviluppata stamattina

questo sistema è il sistema arduino "sotto steroidi".

partiamo dal porting di una applicazione già esistente:

immagino che come ogni buon programmatore abbiate dichiarato ad inizio codice i vostri pin (in progmeme per non sprecare ram), ad esempio

const int bottone PROGMEM= 2;
const int led PROGMEM = 13;

bene, il porting consiste nel cambiare le diciarazioni per usare una struttura di tipo PIN.
Questa struttura in realtà contiene una MASCHERA ai pin da attivare; quindi la MACRO "SET_PIN" serve per fare una dichiarazione base su un pin usando il suo numero, ma volendo potete NON usarla e impostare una VOSTRA maschera, molto utile se volete settare in un colpo solo più pin sullo stesso registro.

quindi la dichiarazione precedente diventa (per un atmega 128/328):

const struct PIN bottone  PROGMEM = SET_PIN(D, 2);
const struct PIN bottone  PROGMEM = SET_PIN(B, 5);

se volessimo settare CONTEMPORANEMAMTE i pin B0 e B1 (digital 8 e 9 sull'atimega 328p) facciamo:

const struct PIN bottone  PROGMEM = {&B, 0b00000011}; //occhio, l'INDIRIZZO di B!

d'ora in poi una pinMode, digitalRead e digitalWrite su questi pin agisce su ENTRAMBI i pin; in particolare la digital read restituisce un byte (uint8_t) in cui ogni bit rappresenta un pin; se noi avessimo B0 spento e B1 acceso ritornerebbe 0b00000010.

(per chi non lo sapesse, con 0b vuol dire che il valore che segue è da interpretare come BINARIO, quindi sto accendendo solo i 2 bit più a sinistra)

BONUS: questo "libreria" NON USA RAM. NADA. NISBA. 0 byte. tutto in flash. allego il codice, se volete discuterne.

Uso la X macro per crare una struttura A, B, C, etc... che rappresenta una porta (e quindi contiene un puntatore al suo registro di in, di out, e di stato)

La cosa bella è che la numerazione "PORTA - numero pin" è da datasheet, e se usavate la porta Z e compilate su attiny, per esempio, avrete un errore di porta non esistente. A questo punto è semplice capire il problema e risolverlo cambiando semplicemente la porta usata. Resta il problema che alcuni registri NON hanno tutti i bit usabili (cristallo, reset, etc..) e questi errori non li noti.

che io sappia non esiste soluzione se non creando delle strutture per ogni modello. cosa che NON ho intenzione di fare.

La seconda parte della libreria dichiara le 3 funzioni arduiniche pinMode, digitalRead e digitalWrite ma che usano la struttura (in modo che se vi diemnticate qualche numero hardcodato vi da errore ed è semplice da identificare e sistemare). Qualcuno può dire che potrei passare iil puntatore invece che il valore della struct, ma in tal caso risparmierei 1 byte sullo stack (sempre che la funzione non venga messa inline) in cambio di una complessità maggiore alto utente (aggiiungere la &). quindi no, preferisco buttare via un byte. Perche ci tengo a essere "a prova di stupido" :grin:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>

const uint8_t LOW PROGMEM = 0;
const uint8_t HIGH PROGMEM = 1;

const uint8_t INPUT PROGMEM = 0;
const uint8_t OUTPUT PROGMEM = 1;

#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif 

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif

#ifndef SET_PIN
#define SET_PIN(port, bit) {&port, _BV(bit)}
#endif


struct PORT{
 uint8_t * const status;
 uint8_t * const input;
 uint8_t * const output;
};

#define X(val) const struct PORT val PROGMEM = {&PORT##val, &PIN##val, &DDR##val};
 #include "ports.h"
#undef X

struct PIN{
 const struct PORT *port;
 const uint8_t mask;
};

void pinMode(const struct PIN p, uint8_t val){
 if (val){
 *(p.port->status) |= p.mask;
 }else{
 *(p.port->status) &= ~p.mask;
 }
}

void digitalWrite(const struct PIN p, uint8_t val){
 if (val){
 *(p.port->output) |= p.mask;
 }else{
 *(p.port->output) &= ~p.mask;
 }
}

uint8_t digitalRead(const struct PIN p){
 return *(p.port->input) & p.mask;
}

edit: info su xmacro: X Macro - Wikipedia

Usage of X Macros dates back to 1960's.[1] It remains useful also in modern-day C and C++, but is nevertheless relatively unknown

io non l'ho trovato nemmeno nei libri :smiley:

lesto:

const struct PIN bottone  PROGMEM = {&B, 0b00000011}; //occhio, l'INDIRIZZO di B!

(per chi non lo sapesse, con 0x vuol dire che il valore che segue è da interpretare come BINARIO, quindi sto accendendo solo i 2 bit più a sinistra)

Scusa @lesto, penso volevi dire 0b e non 0x

nid69ita:
Scusa @lesto, penso volevi dire 0b e non 0x

Penso anche io... come è anche espresso nel codice.

Comunque dubito che chi ignora questi concetti di base possa capirci alcunché in tutto questo thread :smiley:

nid69ita:
Scusa @lesto, penso volevi dire 0b e non 0x

yes, corretto

paulus1969:
Comunque dubito che chi ignora questi concetti di base possa capirci alcunché in tutto questo thread :smiley:

nessuno ha mai scritto che lo sarebbe stato. Sinceramente le varie digressioni che faccio per aiutare a capire non so se vanno più male che bene, mi servirebbe una mano da parte di chi ci capisce qualcosa come capirebe di più :slight_smile:

vorrei fare alla fine un piccolo articolo stile blog dove spiego la libreria, che si aun riassunto di quanto detto finora

return *(p.port->input) & p.mask;

Si certo, il database ha come indice primario p, cioè il pin e da questo ricavi PORTn, PINn e DDRn.

Un tempo saresti stato costretto ad usare pmg_read_xx e &.

const struct PIN

Aggiungi un typedef

typedef struct PIN PIN

Ocio che per convenzione PIN dovrebbe chiamarsi pin_t. Cioè il tipo è di sistema.
PIN potrebbe anche essere un tipo opaco, simile a FILE della stdc.

esagerato, è che ho già fatto qualche gioco con la X macro e questo sistema (un "database" di strutture, tu in un file metti la tua struttura che vuoi in database, e lui ti genere getter e setter. (sempre tutto statico, un bel esercizio di stile se posso permettermi, GitHub - MauroMombelli/microStructDb: this is a fast example of a struct's based DB in C (ansi?))

No ho problemi con le macro, il codice lo seguo, ma non ho ancora chiara la visione di insieme, cioè vedo l'albero e non la foresta.

Ho scritto cose più complesse, ma le ho scritte io e quindi le capisco.

per la storia di "non molto utile" sono combattutto a dire la verità; in realtà se metti i NULL è rischioso perchè se cambi il target di compilazione NON ti accorgi degli errori; se non metti i NULL, a seconda di che architettura compili ad un determinato indice non sai che "lettera" di registro è assegnata (ma in compenso non hai i buchi NULL).

Ecco qui c'è un senso di smarrimento ad intuito risalgo alla necessità di definire NULL qualcosa che nel micro non c'è. Io il problema l'ho risolto con le macro, ma nel mio caso avevo un problema differente. Ho una struct che in base a delle flag di compilazione ha o meno dei campi.

In questo periodo non ho tempo e serenità mentale per creare un progetto e mettere tutto insieme. Giuro se passa sta bronchite, sinusite e mal di calli... :smiley:

MauroTec:
Un tempo saresti stato costretto ad usare pmg_read_xx e &.

dettagli che sarebbero stati immersi nella libreria,assolutamente nulal di visibile all'utente :slight_smile:

MauroTec:
Aggiungi un typedef

typedef struct PIN PIN

Ocio che per convenzione PIN dovrebbe chiamarsi pin_t. Cioè il tipo è di sistema.
PIN potrebbe anche essere un tipo opaco, simile a FILE della stdc.

ecco grazie. Seriamente. a me piace molto tenere ben visibile il fatto che sto parlando diuna struttura, e per questo ho sempre lasciato disseminato di "struct" evitando le typedef (lo so, quì sono controcorrente ma preferisco di molto)
mi piace l'idea del _t ma non sono sicuro se usarla, non mi sembra particolarmente standar per essere inizializzata con un {...}.
insomma, ha un comportamento "strano", che sia "strano" sempre, non ch finga di essere una inncoente tipo di dato

MauroTec:
No ho problemi con le macro, il codice lo seguo, ma non ho ancora chiara la visione di insieme, cioè vedo l'albero e non la foresta.

non capisco a cosa ti riferisci, se al DB è in reltàù una collezzione di strutture sincronizzata, cosa che uso in un sistema embedded con supporto multithreading (sul quad)

MauroTec:
Ecco qui c'è un senso di smarrimento ad intuito risalgo alla necessità di definire NULL qualcosa che nel micro non c'è. Io il problema l'ho risolto con le macro, ma nel mio caso avevo un problema differente. Ho una struct che in base a delle flag di compilazione ha o meno dei campi.

niente flag se possibile, voglio il minimo indispensabile, semplice, elegante e veloce. Non a caso con la "nuova" soluzione evito problemi di NULL, "costringo" l'utente a dichiarare una variabile ad hoc per il pin

rimettiti in sesto in fretta!

p.s ho un esempio completo, ma oggi githuib è sotto DDOS e non risco a caricare nulla, speriamo che rislvano presto

Non so l'avete già vista, ma la Application Note AVR035 di Atmel fornisce moltissime interessanti note su come scrivere codice C efficiente e ... dato che i chip li producono loro, le indicazioni sono veramente fondamentali per del buon codice ottimizzato sulle loro MCU della serie AVR.

Suggerisco, in particolare, la lettura del capitolo "Efficient Use of Variables" di pagina 11 ... se ne scoprono delle belle :smiley:

Vi metto il pdf in allegato, buono studio !!! :wink:

Guglielmo

Atmel - Efficient C Coding for AVR.pdf (140 KB)

gpb01:
Suggerisco, in particolare, la lettura del capitolo "Efficient Use of Variables" di pagina 11 ... se ne scoprono delle belle :smiley:

Infatti l'uso delle variabili locali è da preferire rispetto a quelle globali se servono solo all'interno della funzione in esecuzione.
Però se la variabile locale è dichiarata "static" l'impatto sulla ram e su i cicli macchina è identico in quanto viene comunque creata nella SRAM, non nei registri general purpose della cpu, 32 a 8 bit disponibili.
Attenzione che questo discorso è valido al 100% solo per gli AVR, con altri micro/mcu non è detto che sia la stessa cosa, dipende dall'architettura della cpu e da come è organizzata la memoria, p.e. le variabili locali possono essere localizzate in una porzione riservata della ram, ci pensa il compilatore a farlo, pertanto, sebbene l'impatto sulla SRAM libera è nullo, non cambia nulla per i cicli macchina necessari all'esecuzione del codice tra variabili locali e globali in quanto dovranno comunque essere preventivamente copiate su un registro cpu.

yes, mi piacciono questi interventi, studierò il PDF

osti!

The ANSI standard does not define how bitfields are packed into the byte, i.e., a bitfield
placed in the MSB (Most Significant Bit) with one compiler can be placed in the LSB
(Least Significant Bit) in another compiler. With bitmasks the user has complete control
of the bit placement inside the variables.

e' legato al discroso degli indiani forse ?
little endian big endian in base all'architettura del processore
con il bitmasking decidi in modo fisso quali bit di una world siano 1/0

osti!
Quote

The ANSI standard does not define how bitfields are packed into the byte, i.e., a bitfield
placed in the MSB (Most Significant Bit) with one compiler can be placed in the LSB
(Least Significant Bit) in another compiler. With bitmasks the user has complete control
of the bit placement inside the variables.

ANSI, no C11, quindi non è un problema.

Inoltre ricordi la riga di comando per compilare?

avr-gcc -c -Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -fdata-sections -ffunction-sections -mmcu=atmega328p -save-temps -std=gnu99 -DSOFTWARE_MODE -D__AVR_ATmega328P__ -DF_CPU=16000000UL -I/home/maurilio/.avrspecs/mkspecs/linux-avr8-gcc -I. -I../../../MTavr/src/Core/MTio -I../../../MTavr/src/Core/MThw_serial -I/usr/avr/include -o main.o main.c

-fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields

Ciao.

MauroTec:
ANSI, no C11, quindi non è un problema.

Inoltre ricordi la riga di comando per compilare?

avr-gcc -c -Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -fdata-sections -ffunction-sections -mmcu=atmega328p -save-temps -std=gnu99 -DSOFTWARE_MODE -D__AVR_ATmega328P__ -DF_CPU=16000000UL -I/home/maurilio/.avrspecs/mkspecs/linux-avr8-gcc -I. -I../../../MTavr/src/Core/MTio -I../../../MTavr/src/Core/MThw_serial -I/usr/avr/include -o main.o main.c

Ciao.

<3