Creiamo una libreria in C

Già mi immagino i primi pensieri degli utenti che leggono il titolo del topic. No non voglio creare una libreria C tutti insieme e che sono curioso e vado curiosando nei sorgenti altrui e mi faccio tante troppe domande.

Il core Arduino usa un sistema per mappare i pin e automatizzare le cose, esempio se metto ad 1 un pin con il core questo automaticamente lo imposta come pin di output (non sono sicuro che funzioni così), tuttavia mappa i pin in memory flash che necessita di tempo per essere letta, l'alternativa e quella di usare la macro _BV e le macro DDRB, PORTB e PINB.

Tutti gli header descritti qui sono all'interno di avr-libc e sono tutti publici

Ma cosa sono queste macro?, cosa contengono? allora sono andato a guardare il file iom328p.h dove c'è la definizione di questa macro

#define PINB _SFR_IO8(0x03)

Ok altra domanda? ma cosè _SFR_IO8?
Io mi aspettavo fosse una funzione builtin di avr-gcc, invece è una macro definita in sfr_defs.h, allora vediamo sta macro:

#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)
#define _SFR_IO16(io_addr) ((io_addr) + __SFR_OFFSET)

Si vede che io_addr è un argomento nel caso di esempio è 0x03 + _SFR_OFFSET vale 0x20, sempre se __AVR_ARCH è minore di 100 come si vede da questo codice:

#  if __AVR_ARCH__ >= 100
#    define __SFR_OFFSET 0x00
#  else
#    define __SFR_OFFSET 0x20
#  endif

Quindi questa macro non fa altro che sostituire ((0x03) + 0x20 quando io uso la macro PINB, quindi 0x23 è l'indirizzo di memoria a cui è mappata la porta B, in verità è l'indirizzo di memoria RAM in cui risiede il registro PINB (nome simbolico).

Ora supponiamo che io voglia mappare tutti i pin IO in modo efficiente, come posso fare?
So per certo che se passo un solo indirizzo si impiega meno tempo che passarne tanti (3 nel caso di DDRx, POTRx e PINx), quindi penso ad una struttura di 3 capi ed ognuno punta all'indirizzo di memoria. Ma in realtà mi viene che i campi della struttura risiedono in una porzione di memoria diversa da quella es 0x03, invece io voglio che ogni campo della struttura sia già la memoria all'indirizzo 0x03 e io non conosco alcun modo per farlo.

Se scrivo :

//                               DDRx,                      PORTx,                 PINx
#define GPIO_B0     &_SFR_IO8(0x04), &_SFR_IO8(0x05), &_SFR_IO8(0x03)

definisco una macro composta e con un solo nome (GPIO_XX) passo 3 parametri ad una funzione. Dal momento che questi dovrebbero essere indirizzi visto che c'è il dereferenziatore "&" non ci dovrebbe essere grande consumo di risorse, ma sono costretto a passare la stessa macro anche se poi la funzione usa solo uno dei parametri. Chissa se il compilatore ottimizza e non passa gli altri due argomenti visto che non li uso.

Ora vi starete chiedendo, ma cosa vuole mauro? niente, se avete qualcosa da dire in merito fate pure, magari ne esce qualcosa di interessante per qualcuno.

Ciao.

Se ho capito bene, tu vuoi creare una libreria con nuove funzioni di accesso ai pin del micro i cui riferimenti non vengano però inseriti nella Flash con PROGMEM.

Si, no forse, non lo so. :smiley:

Ho scritto codice per sperimentale al fine di verificare la comodità di uso e l'efficienza di alcune funzioni per I/O, ora volevo sperimentare altro.

Quindi nella mia testa io pensavo ad una struttura di questo tipo

struct port {
uint8_t *dir;
uint8_t *out;
uint8_t *in;
};

Solo che ogni campo occupa una locazione che contiene l'indirizzo di memoria a cui è mappato il registro ed io invece volevo che questo facesse parte della struttura, così avrei risparmiato memoria e organizzato i registri in strutture.

L'altra cosa che mi chiedo è la seguente: anche passando ad una funzione un puntatore a dato di una struttura poi c'è il modo di ricavare il puntatore alla struttura, faccio esempio creando una oggetto di struttura port.

port gpio_B0;
gpio.dir = 00;
gpio.out = 00;
ecc

se passo ad una funzione un puntatore &gpio_B0, posso accedere ad i suoi campi perchè ne conosco il nome se invece passo &gpio.out da dentro la funzione come posso fare a risalire al puntatore a struttura?

Non ho l'ambizione di creare chissa quale funzione o libreria, mi chiedo se è possibile fare qualcosa di diverso dal consueto per gestire I/O. La flash si sa è lenta in lettura rispetto alla lettura in RAM e questo è la prima causa di lentezza che affligono le funzioni I/O di arduino.

Tempo a dietro postasti un link e codice di esempio che facevo uso smodato delle macro tanto che poi il codice nel main era poco identificabile come linguaggio C, difatti si può stravolgere il linguaggio C rendendolo quasi irriconoscible facendo un'uso scriteriato delle macro.

Esempio:
#define GPIO_B0 ( &_SFR_IO8(0x04), &_SFR_IO8(0x05), &_SFR_IO8(0x03) )

Grazie a questa define posso richiamere una funzione in questo modo:

gpio_on GPIO_B0

Dove come vedi non ho necessità di scrivere le parentesi perchè sono contenure nella macro, ma questo mi impedisce di inserire nella funzione argomenti utente.

Il problema principale è che un pin non è identificabile in modo univoco attraverso il suo nome e neanche attraverso il suo indirizzo, l'unico modo è quello di usare la macro _BV che però è ostica.

Ora vedo come è fatta la macro _BV.

Ciao.

Ti seguo e non ti seguo, nel senso che non so aiutarti più di tanto con le struct perché la mia conoscenza del C si può classificare come "media".

Ti pongo una domanda. Ma usare gli operatori logici?
Mi pare semplice: devo attivare il 7° pin della porta D, basta manipolare DDRD.
Oppure vuoi saltare anche questo passaggio e scrivere direttamente nei registri contenuti nella memoria del micro?

se vuoi mappare i pin in modo efficiente, il metodo più semplice è basarsi sul fatto che in PINx i bit sono in ordine come i pin arduino (non so se è vero, mi pare di sì)
se così è, allora il metodo più efficiente è un poco di matematica per calcolare il valore con cui fare la and o l'xor logico.

Solo che ogni campo occupa una locazione che contiene l'indirizzo di memoria a cui è mappato il registro ed io invece volevo che questo facesse parte della struttura, così avrei risparmiato memoria e organizzato i registri in strutture.

non ho capito

passando ad una funzione un puntatore a dato di una struttura poi c'è il modo di ricavare il puntatore alla struttura

si ma no. Nel senso, sai la posizione del dato nella struttura(offset del dato), quindi "indirizzo dato"-"offset" = "indirizzo struttura". Però poi se cambi la struttura crei casini, senza contare l'illeggibilità del codice.
IMHO è sbagliato il fatto che alla funzione passi il dato: passagli tutta la struttura!

si ma no. Nel senso, sai la posizione del dato nella struttura(offset del dato), quindi "indirizzo dato"-"offset" = "indirizzo struttura". Però poi se cambi la struttura crei casini, senza contare l'illeggibilità del codice.
IMHO è sbagliato il fatto che alla funzione passi il dato: passagli tutta la struttura!

Si al di la del fatto che è sbagliato, se voglio risalire alla struttura dal dato come faccio? l'unica che mi viene e quella di operare l'aritmetica dei puntatori, ma questa cosa funziona solo se la struttura contiene variabili stivate in ram in sequenza, che è la regola poi, solo che a me questa regola stava stretta perchè volevo (per risparmiare) fare in modo che ogni campo punta ad un indirizzo di memoria nel quale è conservato il valore del registro. Insomma una magia che la struct non mi permette di fare.

non capisco perche' non la fai piu' semplice e non fai hardcoding: hai 4 porte gpio, usa le define per definire i dettagli che servono a 4 gruppi di operazioni, e per ogni operazione definisci una function, quindi per ogni gpio hai una set, una get, una init, ognuna hai parametri hardcoded definiti at the compile time dalla rispettiva define. Semplice semplice, altamente performante, specialmente se fai richiesta di inline.

Si però tu usi termini tecnici che a me non risultano di comprensione lampante. Cioè hardcoding, cosè?
Comunque il discorso della struct ho capito che non porta benefici su micro con poche risorse RAM, ed è poco efficiente nei confronti della RAM proprio perchè i registri sono già mappati in RAM e con una struct devo contenere campi puntatori a questi indirizzi per cui migliora forse la leggibilià del codice che tra l'altro risulta coeerente a tutti i micro ma occupo memoria e perdo pure rapidità di esecuzione (diciamo che siamo la).

Al momento ho giocato con le macro abusandone.

#include <avr/io.h>
#include "mio.h"

BEGIN {
1    gpio_mode(GPIO_B0, mode_output);
2    gpio_on(GPIO_B0);
3    volatile uint8_t *out_b0 = get_out(GPIO_B0);
4    *out_b0 _bit_unset(PB0);
5    T16_1_SET_PRESCALER(T16_1_PRESCALER_256);
6    T16_1_ENABLE_CLOCK;
7    T16_1_DISABLE_CLOCK;

END }

In mio.h ci sono macro e inline. Singolare il codice a linea 3 e 4. dove get_out è una macro e ritorna il puntatore al registro PORTB chiamato out. Ancora più singolare la macro _bit_unset perchè la sintassi e fuori dal C/C++, cioè prima scrivo la variabile su cui voglio operare e poi l'operazione che voglio compiere su questa.

1 imposta il bit 0 di porta B in modo output
2 accende il bit 0 di porta Bù
3 ricavo il valore di port B
4 spengo il bit 0 di port B e lo faccio in altro modo
5 imposto il prescale per il timer1 a 256 ma non lo avvio qui
6 abilito il timer1
7 disabilito

Queste sono le define per impostare un bit

#define _bit_set(bit) |= _BV(bit)
#define _bit_unset(bit) &= ~_BV(bit)

Sono alle prese con i timer e visto le mie ridotte risorse mentali faccio fatica a memorizzare tutti i registri coinvolti, cacchio il timer è un gatto a nove code.

Ciao

Succede una cosa strana, almeno a me sembra strana.

Ho una variabile static prescaler inizializzata in un file header, questa variabile dobrebbe essere visibile solo nel file in cui è dichiarata però una macro usata nel main aggiorna questa variabile. Vi faccio vedere il file main.cpp che ha subito il lavoro del preprocessore cpp.

// main.cpp
int main() {
    gpio_mode(&(*(volatile uint8_t *)((0x04) + 0x20)), &(*(volatile uint8_t *)((0x05) + 0x20)), &(*(volatile uint8_t *)((0x03) + 0x20)), 0, 0);
    gpio_on(&(*(volatile uint8_t *)((0x04) + 0x20)), &(*(volatile uint8_t *)((0x05) + 0x20)), &(*(volatile uint8_t *)((0x03) + 0x20)), 0, 0);
    volatile uint8_t *out_b0 = &(*(volatile uint8_t *)((0x05) + 0x20));
    *out_b0 &= ~(1 << (0));
    __prescaler__ = 0x4;;
    (*(volatile uint8_t *)(0x81)) = ((*(volatile uint8_t *)(0x81)) & 0xf8) | __prescaler__;;
    (*(volatile uint8_t *)(0x81)) = ((*(volatile uint8_t *)(0x81)) & 0xf8) | 0x0;;

while(1); }

come si vede la variabile dichiarata static in un header file non incluso direttamente viene aggiornata nel main dove questa variabile non dovrebbe essere visibile e quindi dovrebbe andare in errore la compilazione, invece non solo non va in errore ma anche il main.s risulta corretto

/* main.s */
	.file	"main.cpp"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__ = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
	.text
.global	main
	.type	main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
	sbi 36-32,0
	sbi 37-32,0
	cbi 37-32,0
	ldi r24,lo8(4)
	sts _ZL13__prescaler__,r24
	lds r24,129
	andi r24,lo8(-8)
	ori r24,lo8(4)
	sts 129,r24
	lds r24,129
	andi r24,lo8(-8)
	sts 129,r24
.L2:
	rjmp .L2
	.size	main, .-main
	.lcomm _ZL13__prescaler__,1
.global __do_clear_bss

Che dite è normale o no, comunque a me torna comodo davvero sta cosa ma se è un errore del compilatore poi non funzionerà più.

Ciao.

mmm... non capivo cosa fa .lcomm ZL13__prescaler_,1 e allora l'ho cercato e a quanto sembra è questo che risolve il mancato riferimento ad una
variabile statica locale, quindi GCC capisce che voglio aggiornare il valore di questa variabile nel main ed usa questo espediente, però non sono sicuro e cerco conferma.

Si al di la del fatto che è sbagliato, se voglio risalire alla struttura dal dato come faccio? l'unica che mi viene e quella di operare l'aritmetica dei puntatori, ma questa cosa funziona solo se la struttura contiene variabili stivate in ram in sequenza, che è la regola poi, solo che a me questa regola stava stretta perchè volevo (per risparmiare) fare in modo che ogni campo punta ad un indirizzo di memoria nel quale è conservato il valore del registro. Insomma una magia che la struct non mi permette di fare.

quindi tu hai una struct di puntatori. vero che i dati sono sparsi per la ram, ma la dimensione della struct resta fissa, 2byte a puntatore (come gli int). Come tu stesso hai detto, essendo nella struct i puntatori sono scritti sequenzialmente, quindi l'indirizzo del primo puntatore nella struct è l'indirizzo della struct. Quindi con un pò di algebra, e sapendo in che posizione si trova il puntaore che possiedi, puoi arrivare all'indirizzo della struct.

harcodare vuol dire usare valori fissi

una variabile static è visibile in tutto il programma, ed esiste una sola volta anche su più utanze della classe. In pratica è una variabile condivisa.

una variabile static è visibile in tutto il programma, ed esiste una sola volta anche su più utanze della classe. In pratica è una variabile condivisa.

No, la variabile dichiarata static è visibile solo nel file in cui risiede la dichiarazione, con arduino IDE questa cosa si perde perchè il tutto e fuso in un'unico sorgete ma nello sviluppo C/C++ classico è pratica comune isolare le variabili interne al file con static.

Quindi static riduce la visibilità della variabile all'interno di un blocco o file e mantiene il valore precedente all'uscita del blocco o file.

Vero ho provato nel main a dichiarare uint16_T * prescaler e come c'èra d'aspettarsi non compila. Io comunque pensavo che includendo il file indirettamente quella variabili sarebbe rimasta confinata li, sarebbe bastato fare il test per vedere che non può funzionare.

Comunque allora come risolvo?

Io lascerei le cose così, chiamando la variabile T01_prescaler che sarebbe una variabile globale alla fine, oppure devo creare un file .c è scrivere le define in questo file, ma così rimangono confinate nel file e non le posso usare?.

Oppure inline nel file .c, stessa cosa non sono visibili altrove, occore per forza una funzione classica con tanto di header file.

boh..
Ciao.