Batteriespannung mit Attiny85 auslesen und via. HC12 versenden

Hallo Liebes Forum,

Ich hatte für mein Aktuelles Projekt "Die ESP32 Wetterstation" vorgehabt die Batteriespannung zu messen und über dem HC12 zu übertragen an den ESP32.
Den einzigen freien Analogen Pin des Attiny85 den ich verwenden kann ist Pin 2, dementsprechend wird für den DHT22 der Pin 1 verwendet.

Nun meine Frage:
ich habe den Code für den Attiny85 angepasst und wollte wissen ob diese Codes und die Anschluss belegung so korrekt ist, bevor ich anfage dies entprechend um zu Löten.

Der Lötplan des Attiny85:

Der Angepasste Code zur Messung der Batteriespannung:

#include <Arduino.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <SoftwareSerial.h>
#include <dht22.h>

constexpr byte DHTPIN {1};
constexpr byte BATTERY_PIN {2};  // Pin, der mit dem Pluspol der Batterien verbunden ist
constexpr byte UNUSEDPINS[] {0};

SoftwareSerial hc12(4, 3);   // RX = 4, TX = 3
dht22 dht;

// after how many watchdog wakeups we should collect and send the data
constexpr byte WATCHDOG_WAKEUPS_TARGET {7};   // 8 * 7 = 56 seconds between each data collection

// watchdog ISR
ISR(WDT_vect) {
  // nothing to do here, just wake up
}

void enableWatchdog() {
  cli();

  // clear the reset flag
  MCUSR &= ~(1 << WDRF);

  // set WDCE to be able to change/set WDE
  WDTCR |= (1 << WDCE) | (1 << WDE);
  WDTCR = 1 << WDP0 | 1 << WDP3;

  // enable the WD interrupt to get an interrupt instead of a reset
  WDTCR |= (1 << WDIE);

  sei();
}

// function to go to sleep
void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* SLEEP_MODE_PWR_DOWN for lowest power consumption. */
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

float readBatteryVoltage() {
  // Messung der Batteriespannung
  analogReference(INTERNAL);  // Interne Referenzspannung für die Messung verwenden
  int rawValue = analogRead(BATTERY_PIN);
  float voltage = rawValue * (4.5 / 1023.0);  // Umrechnung in die tatsächliche Spannung
  return voltage;
}

void setup() {
  hc12.begin(4800);
  dht_init(&dht, DHTPIN);

  // Set unused pins to INPUT_PULLUP to save power.
  for (auto pin : UNUSEDPINS) {
    pinMode(pin, INPUT_PULLUP);
  }

  // switch off ADC -320µA
  ADCSRA &= ~(1 << ADEN);
  delay(2000);

  // enable the watchdog
  enableWatchdog();
}

void loop() {
  float temperature;
  float humidity;
  char toSend[30];

  // Daten vom DHT22 Sensor lesen
  if (dht_read_data(&dht, &temperature, &humidity) == 1) {
    char tempData[6];
    char humData[5];
    char voltageData[5];
    char separator[] = ",";

    // Temperaturdaten konvertieren
    dtostrf(temperature, 4, 1, tempData);

    // Luftfeuchtigkeitsdaten konvertieren
    dtostrf(humidity, 2, 0, humData);

    // Batteriespannung messen und konvertieren
    float batteryVoltage = readBatteryVoltage();
    dtostrf(batteryVoltage, 3, 2, voltageData);

    // Daten zusammenstellen
    strcpy(toSend, tempData);
    strcat(toSend, separator);
    strcat(toSend, humData);
    strcat(toSend, separator);
    strcat(toSend, voltageData);
    strcat(toSend, separator);

  } else {
    strcpy(toSend, "99.9,99,0.00,");   // Fehler
  }

  hc12.print(toSend);   // Daten mit dem HC12-Sender übertragen

  // Tiefschlaf
  for (uint8_t i = 0; i < WATCHDOG_WAKEUPS_TARGET; i++) {
    enterSleep();
  }
}

Ein Beispiel-Code zum anzeigen der Batteriespannung:

#include <SoftwareSerial.h>
SoftwareSerial HC12Serial(26, 27);  // RX an Pin 26, TX an Pin 27

void setup() {
  Serial.begin(115200);
  HC12Serial.begin(4800);
}

void loop() {
  if (HC12Serial.available() > 0) {
    String receivedData = HC12Serial.readStringUntil('\n');
    float batteryVoltage = receivedData.toFloat();

    // Umrechnung der Batteriespannung in Prozent (Beispiel: Annahme von 4,5 V als maximaler Wert)
    int batteryPercentage = map(batteryVoltage, 0.0, 4.5, 0, 100);

    // Ausgabe des Batterieprozentsatzes über die serielle Schnittstelle
    Serial.print("Batteriespannung: ");
    Serial.print(batteryPercentage);
    Serial.println("%");
  }
}

Vielen dank im Vorrause

Der T85 kann seine Versorgungspannung messen, ohne dass ein Pin dafür verwendet werden müsste.
So wie du das machst, wird der ADC immer 1023 liefern

1 Like

Ah oki, gut zu wissen. Das Wusste ich nicht, ich habe mich nach diesem Beilspiel gerichtet:

Auch wenn die Batteriespannung nach na Zeit sinkt ?
Wie meinst du das ?

d.h. egal welche Spannung an liegt, sie wird identisch zu der auf dem VCC Pin und somit maximal Wert von 1023 bekomment.

habe für dich gegugelt und was als erstes war, ist:

Ah vielen dank, ich werde mich dort durchlesen

Wen du schon dabei bist noch eine Quelle :wink:
Measure VCC/Battery Voltage Without Using I/O Pin

1 Like

Ich habe etwas recherchiert und etwas vielversprechendes gefunden was funktionieren könnte, sorry wenn ich das so nachfrage aber ich bin auf dem Gebiet noch nicht so tief drinnen, der Code:

#include <SoftwareSerial.h>

SoftwareSerial hc12(4, 3); 

void setup() {
  // Initialisiere die serielle Kommunikation mit dem HC12-Modul
  hc12Serial.begin(9600);

  // Aktiviere den internen ADC des Attiny85
  ADCSRA |= (1 << ADEN);

  // Setze den Referenzspannung auf Vcc (AVcc)
  ADMUX |= (1 << REFS0);

  // Aktiviere den internen 1,1V-Bandgap-Referenzspannung
  // (für genauere Spannungsmessungen)
  ADMUX |= (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0);

  delay(100); // Warte auf die Stabilisierung der Referenzspannung
}

void loop() {
  // Starte eine ADC-Wandlung
  ADCSRA |= (1 << ADSC);

  // Warte auf das Ende der Wandlung
  while (ADCSRA & (1 << ADSC))
    ;

  // Lese den ADC-Wert aus
  uint16_t adcValue = ADC;

  // Berechne die tatsächliche Batteriespannung
  // (die interne Referenzspannung beträgt 1,1V)
  float batteryVoltage = (adcValue / 1024.0) * 1.1* 4; // Faktor 4 für Batteriespannung von 4,5V eigl.(4,4V)

  // Sende die Batteriespannung über das HC12-Modul
  hc12.print("Batteriespannung: ");
  hc12.print(batteryVoltage);
  hc12.println(" V");

  delay(50000); // Warte 5 Sekunden/Minuten, bevor die nächste Messung erfolgt
}

Da der Interne ADC genutzt wird brauche ich keien Pin zusätzlich, zudem habe ich mehr oder weniger versucht dein @fony Datenblatt zu verstehen.

ich habe Alternativ auf Mikrocontroller.net etwas gefunden wo ich aber den Code noch viel weniger verstehe: (was ich dort gelesen habe konnte ich auch im Datenblatt wiederfinden)
https://www.mikrocontroller.net/topic/315667

zudem wollte ich den wert auf die Versorgungsspannung Hochskalieren aber 3x 1,1 ergeben leider nicht 4,5 und 4x 1,1 wären 4,4

Darf man:

  float batteryVoltage = (adcValue / 1024.0) * 1.1* 4,1; 

machen ? ich befürchte nicht

EDIT:
Mit dem Multimeter gemessen liegt die Spannung bei 3 unbenutzten AA 1,5 Batterien 4,75V
Ginge das hier ?

float batteryVoltage = (adcValue / 1024.0) * 1.1 * 4.75 / 4.1;

Warnungen aktivieren, dann sagt es "Statement without effect", oder so ähnlich.
(ohne Gewähr)

Denn der Kommaoperator ist da fehl am Platze.
Suchtipp: "c++ comma operator"
z.B.: AngelikaLanger.com - Sequence Points and Expression Evaluation in C++ - Angelika Langer Training/Consulting

War jetzt bei mir zum Glück nicht der fall:


Die einzige Warnung bezog sich auf den HC12

C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp: In function 'void DebugPulse(uint8_t, uint8_t)':
C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp:60:32: warning: unused parameter 'pin' [-Wunused-parameter]
 inline void DebugPulse(uint8_t pin, uint8_t count)
                                ^~~
C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp:60:45: warning: unused parameter 'count' [-Wunused-parameter]
 inline void DebugPulse(uint8_t pin, uint8_t count)
                                             ^~~~~
C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp: At global scope:
C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp:375:6: warning: always_inline function might not be inlinable [-Wattributes]
 void SoftwareSerial::setRxIntMsk(bool enable)
      ^~~~~~~~~~~~~~
C:\Users\Migel\AppData\Local\Arduino15\packages\ATTinyCore\hardware\avr\1.5.2\libraries\SoftwareSerial\SoftwareSerial.cpp:121:6: warning: always_inline function might not be inlinable [-Wattributes]
 void SoftwareSerial::recv()
      ^~~~~~~~~~~~~~

was ist "4.1" ?
dir wird interne 1.1V Reference als 1.1 gezeigt, dann Hochskalieren ist
float batteryVoltage = (adcValue / 1024.0) / 1.1 * 4.75;

1 Like

Hi,

hier kannst Du nachlesen, wie man mit dem ADC die eigene Betriebsspannung misst, ohne einen Pin dafür zu verwenden:

Hier kannst Du Dir ansehen, wie ich das auf einem ATtiny84 schon benutzt habe:

Die Funktion float messenBatt() im Sketch ansehen.

Da Du einen ATtiny85 benutzt, musst Du die Zeile ADMUX für diesen ändern ( ADMUX = _BV(MUX3) | _BV(MUX2);
Du könntest noch alles in mV rechnen, und den Wert als INT übertragen, und wenn Du sonst in Deinem Sketch keine ratiometrische Messung machst auch das hin- und herschalten der Referenzen ausnehmen, und entschprechend einmal im setup unterbringen.

Gruß André

1 Like

Ich habe mir deine Links angeschaut und versucht dein Vorschlag anzuwenden. Ich habe für die übertragung mein HC12 im Code angegeben, da ich am Attiny kiene möglichkeit habe das Seriall anzeigen zu lassen.

Der erste Code zur Messung der Internen Referenzspannung: (diesen Wert brauche ich später um mit dieser gemessenen Referenzspannung weiter zu arbeiten)

#include <SoftwareSerial.h>
SoftwareSerial hc12(4, 3);  // RX = 4, TX = 3

const float VCC = 4.75; // Gemessen in Volt!

void setup() 
{
  hc12.begin(4800);  // Starte die serielle Kommunikation mit dem HC-12-Modul
}

void loop() {
  readInternalRef11();
  delay(2000);
}

void readInternalRef11() 
{
  #elif defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2);
  #endif

  delay(10); // Warten bis Referenz eingeschwungen
  ADCSRA = _BV(ADSC); // Start Umwandlung
  while (bit_is_set(ADCSRA,ADSC)); // Messen

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;
  float rvcc = 1.10 * 1023L / result;
  float internal1_1Ref = 1.1 * VCC / rvcc;

  hc12.print("Interne Referenz (1.1V): ");
  hc12.println(internal1_1Ref);
}

Scheint nicht zu funktionieren, ich habe den Code auf den Attiny85 geflasht und der ESP32 wird als ausgabe genutzt über den HC12 Modul:

Empfangene Daten: Interne Referenz (1.1V): 0.00
0.00 kann eigl. nicht sein

Der Code zum Empfangen und anzeigen:

#include <SoftwareSerial.h>

SoftwareSerial HC12Serial(26, 27); // RX = GPIO26, TX = GPIO27

void setup() {
  Serial.begin(115200);  // Starte die serielle Kommunikation mit dem Computer
  HC12Serial.begin(4800);  // Starte die SoftwareSerial-Kommunikation mit dem HC-12-Modul
}

void loop() {
  if (HC12Serial.available()) {
    String receivedData = "";
    while (HC12Serial.available()) {
      char c = HC12Serial.read();
      receivedData += c;
    }
    Serial.print("Empfangene Daten: ");
    Serial.println(receivedData);
  }
  delay(1000);
}

Da frage ich mich, wie Du den Code compilieren konntest.

 #elif defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2);
  #endif

Ist eine fehlerhafte Präprozessor Definition und müsste eigentlich einen Fehler bringen.
In Deinem Fall ist die auch gar nicht notwendig, weil Du den Code ja explizit für den ATtiny schreibst. Also entweder Du übernimmst das aus dem Beispiel komplett beginnend mit #if oder Du lässt die Präprozessordefinitionen ganz weg.

Z.b. so

#include <SoftwareSerial.h>
SoftwareSerial hc12(4, 3);  // RX = 4, TX = 3

const float VCC = 4.75; // Gemessen in Volt!

void readInternalRef11() 
{
  ADCSRA |= _BV(ADSC); // Start Umwandlung
  while (bit_is_set(ADCSRA,ADSC)); // Messen

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;
  float rvcc = 1.10 * 1023L / result;
  float internal1_1Ref = 1.1 * VCC / rvcc;

  hc12.print("Interne Referenz (1.1V): ");
  hc12.println(internal1_1Ref);
}

void setup() 
{
  hc12.begin(4800);  // Starte die serielle Kommunikation mit dem HC-12-Modul
  
  // Initialisiere ADC mit REFS[2:0] ist 0 = VCC als Ref,  MUX[3:0] 1100 = Vbg als Input,  
  ADMUX = _BV(MUX3) | _BV(MUX2); 
  delay(10);
}

void loop() {
  readInternalRef11();
  delay(2000);
}

[Edit] Die Anweisung ADCSRA = _BV(ADSC); // Start Umwandlung muss korrigiert werden in ADCSRA |= _BV(ADSC); // Start Umwandlung

Nicht besser 1024 ?!

Ansosnten...
Hier für einen T85 Digispark

#include <CombieAdc.h>
// Siehe: https://forum.arduino.cc/index.php?topic=496305.0


#include "DigiKeyboard.h"

using Combie::Adc;
Adc adc;


constexpr float ReferenzSpannung {1.06}; // anpassen



void setup() 
{
   adc  .enable()
        .setClockDivisor() 
        .setReference(Adc::REF_VCC)
        .setSource(Adc::MUX_REF); 
}

void loop() 
{
  DigiKeyboard.sendKeyStroke(0);
  DigiKeyboard.println(ReferenzSpannung * 1024.0 / adc);
  DigiKeyboard.delay(5000);

}

Frag mich das nicht. Ist aus dem "Original". Den Wert habe ich nicht angepasst, nur den Code so geändert, dass er eigentlich compilierbar sein und etwas machen sollte. Wäre auch besser, das nicht weiter zu diskutieren, sonst könnte das wieder etwas "entgleiten".

Aber ja... 1024 :wink:

Nochmal der Code aus #15. Nur ein bisschen aufgehübscht.

#include <SoftwareSerial.h>
SoftwareSerial hc12(4, 3);   // RX = 4, TX = 3

constexpr uint16_t VCC {4750};   // Gemessen in milliVolt!
constexpr uint32_t REFxRESOLUTION {1100 * 1024UL};
constexpr uint32_t VCCxREF {VCC * 1100UL};

void readInternalRef11() {
  uint32_t rvcc;

  ADCSRA |= _BV(ADSC);                     // Start conversion
  while (bit_is_set(ADCSRA, ADSC)) { ; }   // Measure
  uint32_t result = (ADCH << 8) | ADCL;
  rvcc = REFxRESOLUTION / result;

  float internal1_1Ref = static_cast<float>(VCCxREF / rvcc) / 1000;
  hc12.print("Interne Referenz (1.1V): ");
  hc12.println(internal1_1Ref, 3);
}

void setup() {
  hc12.begin(4800);   // Starte die serielle Kommunikation mit dem HC-12-Modul
  // Inittialisiere ADC mit REFS[2:0] ist 0 = VCC als Ref,  MUX[3:0] 1100 = Vb als Input,
  ADMUX = _BV(MUX3) | _BV(MUX2);
  bitSet(ADCSRA, ADEN);     // Enable
  delay(10);
}

void loop() {
  readInternalRef11();
  delay(2000);
}

Ah, das wusste ich nicht, dann lasse ich die Präprozessordefinitionen ganz weg.

EDIT:
So es hat endlich Funktioniert, als Ergebniss hatte ich das hier:

Interne Referenz: (1.1V): 0.92
Interne Referenz: (1.1V): 0.93

Ich hatte den Empfängercode und sender angepasst, sodass die Ausgabe etwas übersichtlicher ist. Es schwankt zwischen 0,92 und 0,93V bei der Internen Referenzspannung
@Kai-R vielen dank fürs Korrigieren.

Sender angepasst:

 hc12.print("(1.1V): ");
  hc12.println(internal1_1Ref);

Empfänger angepasst:

   Serial.print("Interne Referenz: ");
    Serial.println(receivedData);

EDIT:
ich habe 1023L durch 1024L getauscht, hat sich minimal verändert:

Interne Referenz: (1.1V): 0.92
Interne Referenz: (1.1V): 0.91

um 0,01V

Hi, die Werte halte ich für zu weit daneben, lt. Datenblatt sollten das Werte zwischen maximal 1.00 ... 1.20V sein. Ich selbst habe bisher Werte zwischen 1.05 und 1.12V bei versch. Exemplaren gehabt.

Gruß André

Ach misst stimmt, ja ich weiß auch warum, es dürfen keine anderen Verbraucher daran hängen. Ich habe die Außensendestation dafür genutzt, den DHT abgeklämmt, der wert hat sich verändert der DHT und der HC12 verfälschen das Ergebniss.

EDIT:
Ich muss ein weg finden das auszulessen ohne den HC12 zu nutzen