Résolu - [ATtiny85] Erreur calcul tension d'alimentation / Ref. 1,1v

Bonsoir,

j'ai pour habitude de faire des petits projets afin de tester mes "briques" histoire de ne pas me retrouver dans un debug géant sur des programmes plus vastes... et là je tombe sur un os pour l'un d'eux!

En résumé, j'ai un AT85 alimenté par une alim de labo, sur lequel est branché un petit circuit IR (PCB chinois à base d'AS312), et une led verte.
Le principe est simple: lorsque le détecteur voit passer quelque chose, la led verte s'allume puis s'éteint (le holding time est donné par l'IR).
Entre deux mesures, le µC est mis en sommeil et se réveille grâce au WDT réglé toutes les 0,25s (à la louche de la précision près de l'ensemble).
Juste que là ça fonctionne bien.

J'ai ensuite ajouté une fonction qui doit mesurer la tension d'alimentation à intervalle régulier (30s) en prenant référence sur les 1,1v internes... et un test associé doit allumer la led rouge si la tension devient inférieure à 2,2V.
Le tout démarre correctement, sauf qu'après 30s le test est systématiquement positif, et la led rouge clignote indépendamment de la tension d'alimentation, qu'elle soit inférieure ou supérieure au seuil de 2,2v choisi :confused:

Bref, je patauge et un peu d'aide me serait utile pour débusquer ma boulette (voire mes boulettes) :grin:

Le code, avec les conditions de compilation dans l'en-tête, ainsi que le montage simplifié...
N'hésitez pas si j'ai oublié de mentionner quelque chose!

/***************************************************************************************
*  Détecteur de présence via IR                            
*  Détecte la présence d'une personne et allume une led verte de confirmation 
*  Sleep mode pour un usage sur 2 piles AA (WDT) 
*  Inclu une led rouge qui flashe si niveau bas de batterie (<2,2v) 
*                                                                                       
*  Processor: ATTiny85 @ 1MHz; target @125kHz 
*  Compiler avr-gcc 7.4.0                                    
*  Programmer: USBasp                                                                   
*                         ___                                                           
*                    PB5 *| + |*  VCC                                                    
*   LED verte   <-- PB3 *|   |*  PB2                                  
*   LED rouge   <-- PB4 *|   |*  PB1                                  
*                   GND *|___|*  PB0   <-- PCINT0 (IR0)                                 
*                                                                                       
* WatchDog Timer: wake up from Power Down sleep mode 
*                                                                                       
* Lfuse: 0x62 / Hfuse: 0xDF / Efuse: 0xFF @1MHz 
* 
* Bernique BGY#020_v1_alert_debug                                                       
* Common Creative BY-NC-SA                                                              
* 2020/02/14                                                                            
*                                                                                       
* Status: NOK                                                                            
****************************************************************************************/

#define F_CPU 1000000L // 1Mhz (8Mhz, prescaled by fuse = 8)

#include <avr/io.h>
#include <avr/wdt.h> 
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdbool.h>

/**************** User parameters *********************************************************
 *****************************************************************************************/

/******************************************************************************************
 *****************************************************************************************/

#define IR0 PB0 // Signal from IR
#define LEDv PB3 // LED verte
#define LEDr PB4 // LED rouge

volatile uint8_t wdt_count = 0; // quantity of Watchdog overflow
uint8_t _prescaler = 0; // internal variable for conversion purpose
volatile uint8_t i = 0; // counter

volatile bool batt = false; // false means battery OK
uint32_t millis = 0; // timer, incremental based on WDT timer ISR
uint32_t Tref = 120; // time for Vbat test; 30s = 120 WDT@prescal=4 (0,25s)
uint16_t result; // mesure de la tension résiduelle des batteries
float tension;


void main(void) {
 // setup
 // I/O configuration 
 DDRB |= (1<<LEDv) | // Pin configuration as output
 (1<<LEDr) | // Pin configuration as output
 (0<<IR0);
    
    PORTB &=~(1<<IR0); // turns IR OFF     
 
 //ADC setup
 ADMUX = 0X8C; // Voltage ref. internal @1.1V and input channel as VBG
 ADCSRA = 0x06; // ADC off to save battery but ready to be activated later with 64 prescaller
 
 //Timer0 setup
 /** SWITCH OFF to save batt... do it later!
 */
          
 //Timer1 setup
 /** SWITCH OFF to save batt... do it later!
 */
 
 // switch Analog Comparator OFF
 ACSR  |= (1<<ACD);
  
 // other variable setup

 
 // sequence to confirm initialization
 for (i = 0; i < 5; i++) {
 PORTB |= (1<<LEDv) | (1<<LEDr); // turns LEDs ON
 _delay_ms(100);
 PORTB &=~((1<<LEDv) | (1<<LEDr)); // turns LEDs OFF
 _delay_ms(300);
 }

 _delay_ms(5000); // waiting for IR initialization
 
 sei(); // make interrupt active
 
 // end of setup

 while(1) { // main loop 
 // il est temps de vérifier la charge de la batterie
 if (millis>Tref){
 readVcc(); // on vérifie la charge de la batterie
 millis = 0; // remise à zéro compteur
 }
 
 if (!batt){ // si pas de défaut de charge batterie 
 //si PCINT0 = high alors il y a quelqu'un!
 if (PINB & (1<<IR0)){
 PORTB |= (1<<LEDv); // on allume LED verte
 PORTB &=~(1<<LEDr); // on éteind LED rouge
 }
 
 //si PCINT0 = low alors il n'y a personne!
 if (!(PINB & (1<<IR0))){ 
 PORTB &=~(1<<LEDv); // on éteind LED rouge
 PORTB &=~(1<<LEDr); // on éteind LED verte
 }
 }
 else { // si défaut de charge batterie
 if ((PINB & (1<<IR0))){ // si détecteur activé
 PORTB &=~((1<<LEDv) | (1<<LEDr)); // turns LEDs OFF to ensure green led cannot stay on!
 for (i = 0; i < 10; i++) { // on flashe le LED rouge pour alerter
 PORTB |= (1<<LEDr); // turns red LEDs ON
 _delay_ms(50);
 PORTB &=~(1<<LEDr); // turns red LEDs OFF
 _delay_ms(150);
 }
 }
 else{
 // tout reste éteint!
 }
 } 
 OFF(1); // roughly 0,25s
 
 } // end of main loop
} //end of main

void readVcc() {
 ADCSRA = 0x86; // ADC on with 64 prescaller
 ADMUX = 0X8C; // Voltage ref. internal @1.1V and input channel as VBG
 _delay_ms(2); // Wait for Vref to settle, almost 1ms
 for (i = 0; i < 5; i++) { // measure several time as first results might be incorrect!
 ADCSRA |= (1<<ADSC); // Convert
 while (ADCSRA & (1<<ADSC));     // wait for conversion complete
 } 
 result = ADCL | (ADCH << 8);
 tension = (1023*1.1)/result;
 if (tension<2.2) batt=true; // Back-calculate AVcc in V and compare with 2,2V
 
 ADCSRA = 0x06; // ADC off with 64 prescaller
}

void OFF(uint16_t duree){                               // turns devices for "duree" x 0,25 seconds
/** @param duration "duree"x0.25 seconds
 * prescal value WDT fixed below @0.25s
 */ 
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);                // Configure attiny85 sleep mode
 wdt_count = 0;                                      // usual delay function is replaced by putting processor into sleep power down mode for "D" milli-second using watchdog
 watchdog_start_interrupt(4);                        // prescale of 4 ~= 0.25sec
 while(wdt_count < duree){                           // Wait "duree" watchdog interrupts
 sleep_mode();                                   // Make CPU sleep until next WDT interrupt
 }
 watchdog_stop();                                    // end of watchdog
} // end of OFF

void watchdog_stop() { // Turn OFF Watchdog
 WDTCR |= (1<<WDCE) | (1<<WDE);
 WDTCR = 0x00;
}
 
void watchdog_start_interrupt(uint8_t wd_prescaler) {   // Turn ON Watchdog with Interrupt
 if(wd_prescaler > 9) wd_prescaler = 9;
 _prescaler = wd_prescaler & 0x7;
 if (wd_prescaler > 7 ) _prescaler |= (1<<WDP3);     // ^ fourth bit of the prescaler is somewhere else in the register...
 WDTCR = _prescaler;                                 // set new watchdog timer prescaler valuee
 WDTCR |= (1<<WDIE) | (1<<WDCE) | (1<<WDE);          // start timed sequence
}

ISR(WDT_vect) { // Watchdog Interrupt Service / is executed when watchdog timed out
 wdt_count++;
 millis++;
 WDTCR |= (1<<WDIE);           // Watchdog goes to interrupt not reset
}

PS: désolé, la mise en forme de mon code passe mal après le Cc/Cv depuis Geany :confused:

Le manque de réponse me fait dire : tu n'utilises pas l'IDE ARDUINO ni la core librairie associée, alors que tu pourrais.
C'est bien d'utiliser AVRGCC, je ne critique pas, mais ce forum n'est pas vraiment l'endroit rêvé.
Un forum ATTINY peut être ?

Bonsoir,

et merci pour ce retour :grin:
J'avoue que d'adresser directement les registres n'est pas toujours commode ni très usité (sans parler de la convivialité), mais sur une application qui fonctionne sur batterie je préfère essayé de contrôler assez strictement le comportement du µC pour de ne rien gâcher des mA disponibles.
j'ai bien noté ton point... vais essayé de trouver un forum peut-être plus adapté même s'il y a ici des membres qui en savent bien plus que moi sur la programmation des AT85 vias avr-gcc je présume!

En attendant un secours éventuel, j'ai trouvé deux premières boulettes je crois, car je prenais comme tension de référence 1,1v en lieu et place de Vcc tel que déclaré dans le registre ADMUX.
De plus, mon préscaler n'était pas adapté lui non plus... je suis passé de 64 à 8 (mon horloge était déjà divisée par 8 par les fuses)
Voici mon nouveau setup:

//ADC setup
 ADCSRA = 0x83; // ADC ON with 8 prescaller => 125kHz sampling @1Mhz CPU
 ADMUX = 0X12; // Voltage ref. Vcc and input channel as Vbg
 _delay_ms(2);

Pour autant, maintenant la led rouge ne s'allume plus, que je sois au dessus ou au dessous du seuil!

Bref, il y a encore quelque chose qui cloche... :confused:

sur une application qui fonctionne sur batterie je préfère essayé de contrôler assez strictement le comportement du µC pour de ne rien gâcher des mA disponibles.

Je ne vois pas en quoi un code élaboré sans librairie serait plus à même d'économiser l'énergie.

Bonjour,

effectivement, sans plus d'explications, cela reste obscure.

Loin de moi l'idée de critiquer les bibliothèques et leurs utilisateurs. Elles sont généralement très bien écrites, hyper pratiques et je m'en sert très régulièrement aussi, surtout pour des fonctions que je ne sais pas programmer par moi-même en C (affichage sur écran OLED par exemple... le protocole I2C en C, ce n'est pas encore de mon niveau :smiley: !).
Elles permettent de configurer le µC rapidement, mais restent un peu une boite noire sauf a vouloir éplucher leurs codes pour comprendre comment elles fonctionnent dans le détails des niveaux inférieurs du µC.

Même si adresser un à un les registres reste fastidieux, cela oblige a se poser la question du fonctionnement intime du µC. Il faut alors choisir pour chacun si on doit garder ou non telle ou telle fonction active, sachant qu'elle est potentiellement consommatrice d'énergie.
Le référence en la matière reste Nick Gammon et son célèbre travail sur le sujet visible ici: Gammon Forum : Electronics : Microprocessors : Power saving techniques for microprocessors
Le passage en C par les registre permet aussi dans certains cas de faire un code plus rapide, notamment pour les entrées/sorties qui peuvent être adressées en bulk plutôt que séquentiellement.

En résumé, chacun choisi selon ses convictions et ses envies... perso, lorsque la consommation électrique est en jeu, je préfère délaisser les bibliothèques complexes, ce qui me donne au moins l'impression de mieux maîtriser le sujet (enfin quand ça fonctionne :smiley: )

J'y retourne... si je trouve, je viendrai poster la solution ici-même.

Re,

je me réponds à moi-même ayant trouvé la solution... comme prévu, une boulette de ma part.
Le réglage de ADMUX contenait une erreur (la passage par une bibliothèque m'aurait évité ce problème :wink: )

Il suffit de remplacer le setup de l'ADC du code précédent par:

//ADC setup
	ADCSRA = 0x03;			// ADC-Off, No-start, No-auto-Trig,No-interrupt, clk/8
	ADMUX = 0X0C;			// Voltage ref. Vcc and input channel as Vbg
	_delay_ms(2);			// for Vref to settle!

Du coup la fonction de test de la tension devient elle aussi:

void readVcc() {
	ADCSRA = 0x83; 						// ADC on with 8 prescaler
	_delay_ms(2); 						// Wait for Vref to settle, almost 1ms
	for (i = 0; i < 5; i++) {			// measure several time as first results might be incorrect!
		ADCSRA |= (1<<ADSC); 			// Convert
		while (ADCSRA & (1<<ADSC));     // wait for conversion complete
	} 
	result = ADC;
	tension = (1024*1.1)/result;
	if (tension<2.2) batt=true; // Back-calculate AVcc in mV and compare with 2,2V
	ADCSRA = 0x03; 						// ADC OFF
}

Bon dimanche à toutes et tous :grin: