My intention was to develop a temperature measuring and datalogging device because I had none.
It should show the temperature measurement data on a display and at the same time store it on a SD card. That was the main requirement I set for myself. In addition, it should have batteries for wireless operation, so the whole system must be designed for low-power consumption.
The application is quite simple, you turn on the logger and start the operation. You can make various settings for the logger operation. In the video you can see the settings with two active channels and an automated measurement every 5sec.
DesignThe µC I chosen was the Atmega32U4 because it has a built-in USB interface and is easy to program via the Arduino IDE. Everything is powered by a linear voltage regulator (TPS782) with 3.3V, which is optimal for this application because it consumes only 500nA during operation. Three AAA batteries provide the power supply.The voltage at the PT1000 is amplified once by a differential amplifier and measured by the analog input of the Atmega. The measuring range is defined from -25°C to 120°C and corresponds to the voltage from 0V to 3.3V.The measurement data is displayed once on a 128x64 Oled and optionally stored on a FAT16 SD card. The time and date is provided by a real-time clock (RV3028C7), which only consumes 45nA in idle mode. Additionally, there are three user buttons for navigating in the menu and a reset button on the back.And two user leds and a USB interface. Almost all externally accessible parts are still equipped with ESD clamping diodes.
AssemblyStep 1: PCB and solder paste
The first step in PCB assembly is the application of solder paste with a soldering stencil. After that there should be a thin layer of paste everywhere on the pads without it running over into other pads.
Step 2: PCB Parts Assembly and Reflow Process
After the paste has been applied, all SMD components must be placed in the correct position. This is especially difficult with small components with many pins, such as the Atmega32U4.
When all the components have been placed on the pads, it is necessary to put the PCB in a reflow oven and heat it according to the soldering profile of the solder paste. Alternatively, you can use a hot air gun to melt the paste, but unfortunately I have not had any experience with this.
Finally, the THT components have to be soldered, i.e. the oled display and the battery holder.
Step 3: Cable Assembly
The next step is crimping and soldering the cables with the PT1000. A 3 wire measurement is realized, therefore two cables must be soldered to one pin. The other pin of the PT1000 is connected to the middle pin of the Dupont connector, so it doesn't matter how the connector is plugged in.
Step 4: Testing
After everything has been assembled I would check the complete circuit for short circuits and if necessary rework it with a soldering iron.
ProgrammingStep 1: Bootloader
As the first step of programming you need to load an additional board, the Sparkfun Pro micro. Available in the Arduino IDE → Board Manager → SparkFun AVR BoardsI programmed the Bootloader via an Arduino Uno and Arduino as ISP but you have to be carefull because you need a level converter from 5V to 3V3.See: Arduino as ISP and Arduino Bootloaders | ArduinoOn the Backside of the PCB you have access to the programming pads with the standard lines MOSI, MISO, SCK, RST, GND, +3V3.I built an adapter for this pads with integrates level converter, maybe I will make a project out of it.
Step 2: Programming
The last step is to program the software via the USB interface. Additional libraries are needed for the code.
- SSD1306 for OLED GitHub - greiman/SSD1306Ascii: Text only Arduino Library for SSD1306 OLED displays
- FAT 16 SD cardGitHub - greiman/Fat16: Smaller FAT16 only library for Arduino
- Standard Arduino libraries
The different functions of the program can all be selected in the menu displayed on the OLED. Among them is a normal logger operation, which measures and stores the temperature once in a certain time interval between 5s-100min each. In this mode, the OLED can also be switched off to achieve a power consumption of less than 1mA.
Manual mode, where the temperature is constantly measured and output.
And another settings menu, there everything can be set, such as the time, delay time between measurements, active channel and whether the display should be active.
In every Mode I tried to use as little power as possible. That's why I send the µC into a sleep mode every time. It is woken up by the user button or the watch dog timer to update the time. All hardware can be switched off actively, including the differential amplifier, the OLED and the SD card.
/* Autor: Hummer L.
Datum: 21.4.2021
Software fuer den Temperatur Datalogger
Revision:
V0.16: Temperatur curve adjusted and compared with thermoelement
Anmerkung:
*/
//---------- Libraries einbinden ----------
#include <Wire.h>
#include <EEPROM.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <Fat16.h>
//---------- I2C Adressen definieren ----------
#define oled_address 0x3C
#define rtc_address 0x52
//---------- REGISTERS RTC RV3028 ----------
//Clock registers
#define RV3028_SECONDS 0x00
#define RV3028_MINUTES 0x01
#define RV3028_HOURS 0x02
//Calendar registers
#define RV3028_WEEKDAY 0x03
#define RV3028_DATE 0x04
#define RV3028_MONTHS 0x05
#define RV3028_YEARS 0x06
#define RV3028_STATUS 0x0E
//---------- Objekte fuer SD und Oled erstellen ----------
SSD1306AsciiWire oled;
SdCard sd;
Fat16 file;
//---------- Pinnummer einem Namen zuweisen ----------
#define led_g 30 // TX LED
#define led_b 17 // RX LED
#define button1 1
#define button2 0
#define button3 7
#define temp1_enb 5
#define temp2_enb A5
#define temp3_enb 6
#define temp4_enb 8
#define temp1_adc A1
#define temp2_adc A2
#define temp3_adc A0
#define temp4_adc A3
#define bat_adc A4
#define sd_io 9
#define sd_cs 10
#define sd_enb 12
#define oled_enb 11
#define opv_enb 13
//---------- Struktur ber wichtige Infos des Datalogger ----------
struct fileinfo {
float temp_value[4];
bool temp_active[4];
word _time[7]; //0..sek 1..min 2..hour 3..day 4..date 5..month 6..year
word pasttime[7];
volatile bool buttonstate[3];
unsigned long starttime;
byte delay_time; //Zeit zwischen zwei Messungen in sek / Gibt den Index der delay_choice funktion an
bool oled_state;
int id; //Gibt die Anzahl an Messungen an
};
fileinfo datalogger = {0};
//---------- Zusatzliche globale Variablen deklarieren ----------
#define bounce_time 50
#define eep_add_temp 0
#define eep_add_oled 1
#define eep_add_time 2
byte rows; // Rows per line.
volatile word choice = 0;
char data[30];
byte bounce_count[3] = {0};
volatile bool wdt_status = 0;
const word delay_choice[10] = {5, 15, 30, 60, 180, 300, 600, 900, 1800, 6000 };
byte channel_sel = 0;
char filename[] = "XX_XXLog.csv";
bool sleep_status = 0;
byte daycount = 0;
byte day_old = 0;
const byte samples = 5; //Anzahl der Messungen, aus der Anzahl wird dann das endgltige Ergebniss gemittelt, hhere Genausigkeit
void setup() {
Wire.begin();
Wire.setClock(400000L);
//---------- Pin Deklaration, ob In oder Output ----------
pinMode(led_g, OUTPUT);
pinMode(led_g, OUTPUT);
pinMode(temp1_enb, OUTPUT);
pinMode(temp2_enb, OUTPUT);
pinMode(temp3_enb, OUTPUT);
pinMode(temp4_enb, OUTPUT);
pinMode(sd_enb, OUTPUT); //Vorerst nicht mglich
pinMode(sd_cs, OUTPUT);
pinMode(oled_enb, OUTPUT);
pinMode(opv_enb, OUTPUT);
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(button3, INPUT);
pinMode(sd_io, INPUT);
digitalWrite(sd_cs, 1);
digitalWrite(led_g, 1);
digitalWrite(led_b, 1);
//---------- Start Konfiguration ----------
if (EEPROM.read(eep_add_oled) > 1)EEPROM.write(eep_add_oled, 1);
datalogger.oled_state = EEPROM.read(eep_add_oled);
if (EEPROM.read(eep_add_time) > 9)EEPROM.write(eep_add_time, 0);
datalogger.delay_time = EEPROM.read(eep_add_time);
channel_sel = EEPROM.read(eep_add_temp);
if (channel_sel > 9)EEPROM.write(eep_add_temp, 0b0011);
datalogger.temp_active[0] = 0b0001 & channel_sel;
datalogger.temp_active[1] = 0b0010 & channel_sel;
datalogger.temp_active[2] = 0b0100 & channel_sel;
datalogger.temp_active[3] = 0b1000 & channel_sel;
channel_sel = 0;
//---------- RTC Konfiguration ----------
rtc_initalize(); //Alle anderen Werte sind default, wie 24h Format
//---------- Timer Konfiguration ----------
// TIMER 1 for interrupt frequency 1000 Hz:
cli(); // stop interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
// set compare match register for 1000 Hz increments
OCR1A = 7999; // = 8000000 / (1 * 1000) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // allow interrupts
//---------- Interrupts Konfiguration ----------
attachInterrupt(digitalPinToInterrupt(button1), int1_event, LOW);
attachInterrupt(digitalPinToInterrupt(button2), int2_event, LOW);
attachInterrupt(digitalPinToInterrupt(button3), int3_event, LOW);
//gab sonst probleme, beim aktivieren der Tasterinterrupts erst alle Tastervariablen reseten
datalogger.buttonstate[0] = 0;
datalogger.buttonstate[1] = 0;
datalogger.buttonstate[2] = 0;
}
void loop() {
//bei jedem WDT aufruf, wird diese Funktion ausgefhrt, weil wdt_status in der wdt Routine 1 gesetzt wird
if (wdt_status == 1) {
rtc_gettime();
oled_displaytime();
wdt_status = 0;
}
//Menstruktur fr die unterschiedlichen Flle, wie Manuell, Logger oder Einstellungen
switch (choice) {
//---------- Startsetting erste Frage, ob hndisch, auomatik betrieb oder einstellungen ------------------------------------------------------------
case 0:
//---------- Watch Dog Timer Konfiguration ----------
wdt_setE5(1);
//---------- OLED Konfiguration ----------
if (digitalRead(oled_enb) == 0) {
digitalWrite(oled_enb, 1);
oled.begin(&Adafruit128x64, oled_address);
oled.setFont(Callibri11); // Auswahl der Schriftart
rows = oled.fontRows();
oled.clear();
oled.setContrast(10);
datalogger.pasttime[4] = 0;
datalogger.pasttime[2] = 111;
datalogger.pasttime[1] = 111;
oled_displaytime();
rtc_gettime();
}
//Text auf das Display ausgeben
oled_displaybat();
oled.setLetterSpacing(2);
oled_print(1, -1, 0, "-----Betriebsart-----");
oled.setLetterSpacing(1);
choice = 1;
break;
case 1:
oled_print(2, -1, 0, "Logger | Manuell | Setting");
choice = 2;
break;
case 2:
oled_print(3, -1, 0, "Logger");
choice = 7;
break;
case 3:
oled_print(3, -1, 0, "Manuell");
choice = 8;
break;
case 4: //Case fr Einsellung, welche in 200 abgearbeitet werden
oled_print(3, -1, 0, "Setting");
choice = 9;
break;
case 7: //Abfrage fr den logger Betrieb
//enter_sleep(); //Ers setzten, wenn alles fertig sonst probleme mit uart
but_do(10, 3, 4);
break;
case 8://Abfrage fr den Manuellen Betrieb
enter_sleep();
but_do(100, 4, 2);
break;
case 9:// Abfrage fr den Einstellungscase
enter_sleep();
but_do(200, 2, 3);
break;
//---------- Logger Betrieb ------------------------------------------------------------
case 10: //Logger Betrieb
oled.clear(0, 128 , rows, 3 * rows + rows - 1);
oled_print(1, -1, 0, "Datalogger");
oled.setCursor(0, 2 * rows);
if (datalogger.temp_active[0] == 1)oled.print("1 ");
if (datalogger.temp_active[1] == 1)oled.print("2 ");
if (datalogger.temp_active[2] == 1)oled.print("3 ");
if (datalogger.temp_active[3] == 1)oled.print("4 ");
sprintf(data, "On | Time: %dsek", delay_choice[datalogger.delay_time]);
oled.print(data);
oled_displaybat();
oled_print(3, -1, 0, "<-Back | v Start v | -----");
choice = 11;
break;
case 11: //Logger Betrieb
enter_sleep();
if (but_do(12, -1, 0) == 1 && digitalRead(sd_io) == 1) {
choice = 14;
//Startvariablen fr Loggerbetrieb vergeben
datalogger.starttime = rtc_gettime(); //Startzeit der Messung
datalogger.id = 1; //ID ist 1
sd_config(); //SD initalisieren
}
break;
case 12: //Case Fehler keine SD-Karte
oled_print(2, -1, 0, "SD-Card missing");
choice = 13;
break;
case 13: //Case warten, bis nochmal start gedrckt und SD vorhanden nochmals berprfen
enter_sleep();
if (but_do(11, -1, 0) == 1) {
datalogger.buttonstate[0] = 1;
}
break;
case 14: //Alles ok, es kann gestartet werden
//Je nach delayzeit anderen wdt setzen
if (datalogger.delay_time > 2 && datalogger.delay_time <= 4) {
wdt_set2E(1);
}
else if (datalogger.delay_time > 4) {
wdt_set8E(1);
}
digitalWrite(oled_enb, datalogger.oled_state); //OLED an/aus, je nach konfiguraion
oled.clear(0, 128, rows, 2 * rows + rows - 1);
oled_displayvalue(0, 1);
oled_print(3, 0, 0, "<-Back");
oled_print(3, 50, 50, "ID: ");
opv_getvalue();
//Ersten Messwert aufnehmen
oled_displayvalue(1, 0);
oled_displaybat();
sd_update();
choice = 16;
break;
case 15: //Case Logger Messausfhrung
enter_sleep();
//Datenerfassung
if (rtc_gettime() >= datalogger.starttime + delay_choice[datalogger.delay_time]*datalogger.id) {
datalogger.id++;
opv_getvalue();
oled_displayvalue(1, 0);
sd_update();
oled_displaybat();
choice = 16;
}
//Im Betrieb OLED an-Aus schalten und Konfigurieren
if (but_do(-1, 15, 0) == 2) {
datalogger.oled_state = !datalogger.oled_state;
if (datalogger.oled_state == 1) {
if (digitalRead(oled_enb) == 0) {
digitalWrite(oled_enb, 1);
oled.begin(&Adafruit128x64, oled_address);
oled.setFont(Callibri11); // Auswahl der Schriftart
rows = oled.fontRows();
oled.clear();
oled.setContrast(10);
datalogger.pasttime[4] = 0;
datalogger.pasttime[2] = 111;
datalogger.pasttime[1] = 111;
oled_displaytime();
rtc_gettime();
oled_displaytime();
oled_displaybat();
oled.clear(0, 128, rows, 3 * rows + rows - 1);
oled_displayvalue(0, 1);
oled_print(3, 0, 0, "<-Back");
oled_print(3, 50, 150, "ID: ");
sprintf(data, "%d", datalogger.id);
oled_print(3, 65, 65, data);
}
}
else {
digitalWrite(oled_enb, 0);
}
}
break;
case 16: //Neue ID am OLED ausgeben
sprintf(data, "%d", datalogger.id);
oled_print(3, 65, 65, data);
choice = 15;
break;
//---------- Manueller Betrieb ------------------------------------------------------------
case 100: //Manueller Betrieb Einmalsetting
oled.clear(0, 128, rows, 2 * rows + rows - 1);
oled_displayvalue(0, 1);
oled_print(3, 0, 0, "<-Back");
choice = 101;
break;
case 101: //Datenerfassung + Ausgabe
opv_getvalue();
oled_displayvalue(1, 0);
enter_sleep();
but_do(-1, -1, 0);
break;
//---------- Settings Menstruktur ------------------------------------------------------------
case 200:
oled_displaybat();
oled_print(1, -1, 0, "Settings");
choice = 201;
break;
case 201:
oled_print(2, -1, 0, "Tim | Del | Act | Dis | Back");
choice = 206;
break;
case 202:
oled_print(3, -1, 0, "Time");
choice = 207;
break;
case 203:
oled_print(3, -1, 0, "Delay");
choice = 208;
break;
case 204:
oled_print(3, -1, 0, "ADC active");
choice = 209;
break;
case 205:
oled_print(3, -1, 0, "Display");
choice = 210;
break;
case 206:
oled_print(3, -1, 0, "Back");
choice = 211;
break;
case 207: //Case zur Zeiteinstellung auswahl
enter_sleep();
but_do(300, 203, 206);
break;
case 208://Case zur Einstellung der Zeit zwischen Messung
enter_sleep();
but_do(230, 204, 202);
break;
case 209: //Case Einstellung welcher Channel Aktiv
enter_sleep();
but_do(240, 205, 203);
break;
case 210://Case Oled an aus
enter_sleep();
but_do(250, 206, 204);
break;
case 211://Case zurck zum Start
//enter_sleep();
but_do(0, 202, 205);
break;
//---------- Delay Settings ------------------------------------------------------------
case 230:
oled_print(1, -1, 0, "Delay Settings");
oled_print(3, -1, 0, "<- -- | v Set v | ++ ->");
choice = 231;
break;
case 231:
sprintf(data, "Delaytime: %dsek", delay_choice[datalogger.delay_time]);
oled_print(2, 0, 0, data);
choice = 232;
break;
case 232:
enter_sleep();
switch (but_do(200, 231, 231)) {
case 1:
EEPROM.update(eep_add_time, datalogger.delay_time);
break;
case 2:
datalogger.delay_time++;
if (datalogger.delay_time > 9) {
datalogger.delay_time = 0;
}
break;
case 3:
datalogger.delay_time--;
if (datalogger.delay_time < 0 || datalogger.delay_time > 9) {
datalogger.delay_time = 9;
}
break;
}
break;
//---------- Channel Aktiv Settings ------------------------------------------------------------
case 240:
oled_print(1, -1, 0, "ADC actice Settings");
oled_print(3, -1, 0, "<-Back | vOn-Offv | Next->");
choice = 241;
break;
case 241:
sprintf(data, "Channel T%d: ", channel_sel + 1);
oled_print(2, 0, 0, data);
oled_print(2, 60, 60, datalogger.temp_active[channel_sel] ? "On" : "Off");
choice = 242;
break;
case 242:
enter_sleep();
switch (but_do(241, 241, 200)) {
case 1:
datalogger.temp_active[channel_sel] = !datalogger.temp_active[channel_sel];
break;
case 2:
channel_sel++;
if (channel_sel > 3) {
channel_sel = 0;
}
break;
case 3:
EEPROM.update(eep_add_temp, datalogger.temp_active[0] | datalogger.temp_active[1] << 1 | datalogger.temp_active[2] << 2 | datalogger.temp_active[3] << 3);
channel_sel = 0;
break;
}
break;
//---------- Display aktiv Settings ------------------------------------------------------------
case 250:
oled_print(1, -1, 0, "Display Settings");
oled_print(2, 0, 0, "Display: ");
oled_print(3, 0, 0, "<-Back | vOn-Offv | ");
choice = 251;
break;
case 251:
oled_print(2, 46, 46, datalogger.oled_state ? "On" : "Off");
choice = 252;
break;
case 252:
enter_sleep();
switch (but_do(251, -1, 200)) {
case 1:
datalogger.oled_state = !datalogger.oled_state;
break;
case 3:
EEPROM.update(eep_add_oled, datalogger.oled_state);
break;
}
break;
//---------- Zeiteinstellung Settings ------------------------------------------------------------
case 300:
oled_print(1, -1, 0, "Time Settings");
oled_print(3, -1, 0, "<- -- | v Next v | ++ ->");
wdt_setE5(0);
choice = 301;
break;
case 301: //Jahr ndern
sprintf(data, "Year: %d", datalogger._time[6] + 2000);
oled_print(2, 0, 0, data);
choice = 302;
break;
case 302:
switch (but_do(303, 301, 301)) {
case 2:
datalogger._time[6] ++;
break;
case 3:
datalogger._time[6] --;
break;
}
break;
case 303: //Monat ndern
sprintf(data, "Month: %d", datalogger._time[5]);
oled_print(2, 0, 0, data);
choice = 304;
break;
case 304:
switch (but_do(305, 303, 303)) {
case 2:
datalogger._time[5] ++;
break;
case 3:
datalogger._time[5] --;
break;
}
if (datalogger._time[5] < 1 || datalogger._time[5] > 12)datalogger._time[5] = 1;
break;
case 305: //Tag ndern
sprintf(data, "Day: %d", datalogger._time[4]);
oled_print(2, 0, 0, data);
choice = 306;
break;
case 306:
switch (but_do(307, 305, 305)) {
case 2:
datalogger._time[4] ++;
break;
case 3:
datalogger._time[4] --;
break;
}
if (datalogger._time[4] < 1 || datalogger._time[4] > 31)datalogger._time[4] = 1;
break;
case 307: //Stunde ndern
sprintf(data, "Hour: %d", datalogger._time[2]);
oled_print(2, 0, 0, data);
choice = 308;
break;
case 308:
switch (but_do(309, 307, 307)) {
case 2:
datalogger._time[2] ++;
break;
case 3:
datalogger._time[2] --;
break;
}
if (datalogger._time[2] < 0 || datalogger._time[2] > 24)datalogger._time[2] = 1;
break;
case 309: //Minute ndern
sprintf(data, "Minute: %d", datalogger._time[1]);
oled_print(2, 0, 0, data);
choice = 310;
break;
case 310:
switch (but_do(311, 309, 309)) {
case 2:
datalogger._time[1] ++;
break;
case 3:
datalogger._time[1] --;
break;
}
if (datalogger._time[1] < 0 || datalogger._time[1] > 60)datalogger._time[1] = 1;
break;
case 311: //Setzten der zuvor eingestellten Zeit
datalogger._time[0] = 0;
rtc_settime();
datalogger.pasttime[4] = 0;
datalogger.pasttime[2] = 111;
datalogger.pasttime[1] = 111;
wdt_setE5(1);
choice = 200;
break;
//---------- Default Fehler ------------------------------------------------------------
default:
choice = 0;
oled_print(2, -1, 0, "Fehler");
oled_print(3, -1, 0, "Restart");
delay(2000);
break;
}
}
byte but_do(int16_t choice_0, int16_t choice_1, int16_t choice_2) {//---------- Tastervariablenabfrage ----------
//Abfrage der Tastervariablen, welche in der ISR gesetzt werden und je nach Stellungen, wird die choice gendert, also die cases
byte x = 0; //Hilfsvariable
//Wenn Tastervariable gesetzt, die choice je nach bergabewert ndern bzw. , wenn negativ dann ignorieren und Tastervariable NULL setzen
if (datalogger.buttonstate[0] == 1) {
if (choice_0 >= 0) {
choice = choice_0;
}
x = 1;
datalogger.buttonstate[0] = 0;
}
//Wenn Tastervariable gesetzt, die choice je nach bergabewert ndern bzw. , wenn negativ dann ignorieren und Tastervariable NULL setzen
else if (datalogger.buttonstate[1] == 1) {
if (choice_1 >= 0) {
choice = choice_1;
}
x = 2;
datalogger.buttonstate[1] = 0;
}
//Wenn Tastervariable gesetzt, die choice je nach bergabewert ndern bzw. , wenn negativ dann ignorieren und Tastervariable NULL setzen
else if (datalogger.buttonstate[2] == 1) {
if (choice_2 >= 0) {
choice = choice_2;
}
x = 3;
datalogger.buttonstate[2] = 0;
}
return x; //Rckgabewert, je nach ausgefhrten case 0..keine Variabele gesetzt 1.. taster1 2..taster2 3..taster3
}
void sd_update() {//---------- Sd Karte mit Messwerte beschreiben ----------
//String mit allen Daten, wie timestamp, ID und Messwerte bilden
String dataString = "";
dataString += String(datalogger.id);
sprintf(data, ";%02d.%02d.%04d ", datalogger._time[4], datalogger._time[5], datalogger._time[6] + 2000);
dataString += String(data);
sprintf(data, "%02d:%02d:%02d;", datalogger._time[2], datalogger._time[1], datalogger._time[0]);
dataString += String(data);
//Die 4 Messwerte in String einfgen und je nach aktiv oder nicht, den Wert oder "inactice" schreiben
for (int i = 0; i < 4; i++) {
if (datalogger.temp_active[i] == 1) {
sprintf(data, "%d,%d;", (int)datalogger.temp_value[i], abs((int)(datalogger.temp_value[i] * 10) % 10));
}
else {
sprintf(data, "inactive;");
}
dataString += String(data);
}
digitalWrite(sd_enb, 1);
//File append ffnen und den zuvor kreiierten String hineinschreiben
file.open(filename, O_CREAT | O_APPEND | O_WRITE);
if (file.isOpen()) {
file.println(dataString);
file.close();
}
digitalWrite(sd_enb, 0);
}
void sd_config() { //---------- SD Karte Konfiguration ----------
digitalWrite(sd_enb, 1);
if (sd.begin(sd_cs)) { //SD initalisieren
Fat16::init(&sd);
//Name fr die csv Datei ndern
filename[0] = (int)(datalogger._time[2] / 10) + '0';
filename[1] = datalogger._time[2] % 10 + '0';
filename[3] = datalogger._time[1] / 10 + '0';
filename[4] = datalogger._time[1] % 10 + '0';
}
file.open(filename, O_CREAT | O_APPEND | O_WRITE); //File ffnen mit dem zuvor genderten Namen
if (file.isOpen()) { //Wenns File offen, dann...
file.println("ID;Timestamp;Temp1 [*C];Temp2 [*C];Temp3 [*C];Temp4 [*C]"); //...Starttext in die erste Zeile der csv Datei schreiben
file.close(); //File schlieen
}
digitalWrite(sd_enb, 0);
}
void oled_print(uint8_t row_, int8_t cur, uint8_t start_del, char text[30]) {//---------- OLED Test ausgeben und lschen ----------
oled.clear(start_del, 128 , row_ * rows, row_ * rows + rows - 1); //bestimmte Zeile lschen
//Auswhlen, ob Text an best. Stelle oder in der Mitte vom Display
if (cur < 0) { //Case in der Mitte
oled.setCursor(64 - oled.strWidth(text) / 2, row_ * rows);
}
else { //Case best. Stelle
oled.setCursor(cur, row_ * rows);
}
oled.print(text);//Text ausgeben an der zuvorig gesetzten Stelle
}
void oled_displaytime() {//---------- OLED Zeit ausgeben ----------
//Die aktuelle Zeit ausgeben, aber nur wenn sie sich zur vorherigen unterscheidet
// case fr Datum
if (datalogger._time[4] != datalogger.pasttime[4]) {
oled.clear(0, 45, 0 * rows, rows - 1);
sprintf(data, "%02d.%02d.%02d", datalogger._time[4], datalogger._time[5], datalogger._time[6]);
oled.print(data);
}
// case fr Stunden
if (datalogger._time[2] != datalogger.pasttime[2]) {
oled.clear(46, 60, 0 * rows, rows - 1);
sprintf(data, "%02d:", datalogger._time[2]);
oled.print(data);
}
// case fr Minuten
if (datalogger._time[1] != datalogger.pasttime[1]) {
oled.clear(61, 75, 0 * rows, rows - 1);
sprintf(data, "%02d:", datalogger._time[1]);
oled.print(data);
}
// case fr Sekunden
if (datalogger._time[0] != datalogger.pasttime[0]) {
oled.clear(76, 90, 0 * rows, rows - 1);
sprintf(data, "%02d", datalogger._time[0]);
oled.print(data);
}
//Die aktuelle Zeit der alten Zeit zuweisen, damit er dann beim nchsten Mal wieder alt und neu berprfen kann
memcpy(datalogger.pasttime, datalogger._time, sizeof(datalogger.pasttime));
}
void oled_displaybat() {//---------- OLED BatterieSpannung asugeben ----------
float x = analogRead(bat_adc) * 0.026316 - 8.0263; //Spannung messen und in V umwandeln
sprintf(data, "%d.%02dV", (int)x, (int)(x * 100) % 100); //Umwandeln in char[], weil mit float inkompatibel, zweimal x und Modulo Funktion(Trick)
oled_print(0, 128 - oled.strWidth(data), 128 - oled.strWidth(data), data);//ausgeben am OLED
}
void oled_displayvalue(uint8_t aktual, bool preset) {//---------- OLED Messwerte ausgeben ----------
//Hilfsvariablen erstellen
uint8_t col_number = 0;
uint8_t row_number = 0;
char buf1 [6];
char buf2 [5];
char buf3 [4];
//For schleife fr die 4 Messwerte
for (uint8_t i = 0; i < 4; i++) {
switch (i) {
case 0:
col_number = 0;
row_number = 1;
break;
case 1:
col_number = 1;
row_number = 1;
break;
case 2:
col_number = 0;
row_number = 2;
break;
case 3:
col_number = 1;
row_number = 2;
break;
}
sprintf(buf1, "T%d: ", (int)(i + 1));
//Nur aktuellen Wert beschreiben, wenn berhaupt aktiv
if (datalogger.temp_active[i] == 1) {
if (aktual == 1) { //Aktualisierungscase nur Zahl wird ausgegeben
//oled.clear(col_number * 65 + oled.strWidth(buf1), col_number * 65 + oled.strWidth(buf1) + oled.strWidth(buf2) , row_number * rows, row_number * rows + rows - 1);
sprintf(buf2, "%04d.%01d", (int)datalogger.temp_value[i], abs((int)(datalogger.temp_value[i] * 10) % 10));
oled.clear(col_number * 65 + oled.strWidth(buf1), col_number * 65 + oled.strWidth(buf1)+oled.strWidth(buf2) , row_number * rows, row_number * rows + rows - 1);
oled.print(buf2);
}
else { //Einmalcase am Anfang, Zahl und Text wird ausgeegeben
oled.clear(col_number * 65, col_number * 65 + 66 , row_number * rows, row_number * rows + rows - 1);
sprintf(buf2, "%04d.%01d", (int)datalogger.temp_value[i], abs((int)(datalogger.temp_value[i] * 10) % 10));
oled.print(buf1);
oled.print(buf2);
//oled.print("*C"); //Wieso Probleme????????????????????
sprintf(buf3, "*C");
oled.print(buf3);
}
}
//Falls man die Voreinstellungen macht, wird "inactive" geschrieben, wenn der Channel inaktiv ist
else if (preset == 1) {
oled.clear(col_number * 65, col_number * 65 + 65 , row_number * rows, row_number * rows + rows - 1);
oled.print(buf1);
oled.print("inactive");
}
}
}
void rtc_initalize() {//---------- RTC Initalisierung ----------
Wire.beginTransmission(rtc_address); //Kommunikation an RTC adress starten
Wire.write(RV3028_STATUS); //Ins Status register schreiben
Wire.write((0x00)); //0 schreiben, weil das alle bedrfnisse abdeckt
Wire.endTransmission();
}
void rtc_settime() {//---------- RTC Zeit ndern ----------
Wire.beginTransmission(rtc_address); //Kommunikation an RTC adress starten
Wire.write(RV3028_SECONDS); //als erstes in sekunden register schreiben, wird auto. inkrementiert fr die anderen register
for (byte i = 0; i < 7; i++) { //Write multiple Registers
Wire.write(DECtoBCD(datalogger._time[i])); //Die eingestellte Zeit der RTC bergeben
}
Wire.endTransmission();
oled_displaytime(); //Zeit ausgeben
}
unsigned long rtc_gettime() {//---------- RTC Zeit auslesen ----------
Wire.beginTransmission(rtc_address); //Kommunikation an RTC adresse starten
Wire.write(RV3028_SECONDS);// das Sekunden register auswhlen
Wire.requestFrom(rtc_address, 7); //Dann 7Byte auslesen, also Sekunden bis Jahr
while (Wire.available()) {
for (byte i = 0; i < 7; i++) { //Read multiple Registers
datalogger._time[i] = BCDtoDEC(Wire.read());
}
}
Wire.endTransmission();
// Die Zeit in Sekunden zurckgeben, damit man dann die Messzeitpunkte zwischen alt und neu abgleichen kann
if (datalogger._time[4] != day_old) { //Die vergangenen Tag zhlen, damit mein kein Problem bei einem Monatssprung hat
daycount ++;;
day_old = datalogger._time[4];
}
return datalogger._time[0] + 60 * datalogger._time[1] + 3600 * datalogger._time[2] + 24 * 3600 * daycount;
}
byte BCDtoDEC(uint8_t val) {//---------- BCD zu DEC formatieren ----------
return ((val / 0x10) * 10) + (val % 0x10);
}
byte DECtoBCD(uint8_t val) {//---------- DEC zu BCD formatieren ----------
return ((val / 10) * 0x10) + (val % 10);
}
void opv_getvalue() {//---------- Temperaturspannung messen ----------
digitalWrite(led_b, 0); //led anschalten
digitalWrite(opv_enb, 1); //Opv aktivieren
//Die Channels aktivieren, wie in Settings
digitalWrite(temp1_enb, datalogger.temp_active[0]);
digitalWrite(temp2_enb, datalogger.temp_active[1]);
digitalWrite(temp3_enb, datalogger.temp_active[2]);
digitalWrite(temp4_enb, datalogger.temp_active[3]);
delay(15);
//Den aktuellen und aktiven Channel den Hilfsvariablen zuweisen, damit mit for-schleife mglich
for (byte i = 0; i < 4; i++) {
datalogger.temp_value[i] = 0;
int pin = 0;
int pin_read = 0;
if (datalogger.temp_active[i] == 1) {
switch (i) {
case 0:
pin = temp1_enb;
pin_read = temp1_adc;
break;
case 1:
pin = temp2_enb;
pin_read = temp2_adc;
break;
case 2:
pin = temp3_enb;
pin_read = temp3_adc;
break;
case 3:
pin = temp4_enb;
pin_read = temp4_adc;
break;
}
for(byte j = 0; j < samples; j++){
datalogger.temp_value[i] += analogRead(pin_read); //Spannung an best. Channel messen und in Temp umwandeln
delayMicroseconds(200);
}
datalogger.temp_value[i] = ((datalogger.temp_value[i]/samples)*0.153479)-27.23; //10bit ADC Wert in Temp umwandel
digitalWrite(pin, 0); //Channel ausschalten
}
}
digitalWrite(opv_enb, 0); //OPV deaktivieren
digitalWrite(led_b, 1); //Led ausschalten
}
void int1_event() {//---------- Interrupt Event Taster 1 ----------
if (bounce_count[0] == 0) { //Nur wenn Entprell Routine wieder zurckgesetzt wurde, interrupt erlauben
if (sleep_status == 1) { //Nur wenn im Sleep mode
sleep_disable(); //...dann Sleep mode aussschalten
sleep_status = 0; //...und variable zurcksetzen
}
datalogger.buttonstate[0] = 1;//Tastervariable setzen
bounce_count[0] = 1;
}
}
void int2_event() {//---------- Interrupt Event Taster 2 ----------
if (bounce_count[1] == 0) { //Nur wenn Entprell Routine wieder zurckgesetzt wurde, interrupt erlauben
if (sleep_status == 1) { //Nur wenn im Sleep mode
sleep_disable(); //...dann Sleep mode aussschalten
sleep_status = 0; //...und variable zurcksetzen
}
datalogger.buttonstate[1] = 1;//Tastervariable setzen
bounce_count[1] = 1;
}
}
void int3_event() {//---------- Interrupt Event Taster 3 ----------
if (bounce_count[2] == 0) { //Nur wenn Entprell Routine wieder zurckgesetzt wurde, interrupt erlauben
if (sleep_status == 1) { //Nur wenn im Sleep mode
sleep_disable(); //...dann Sleep mode aussschalten
sleep_status = 0; //...und variable zurcksetzen
}
datalogger.buttonstate[2] = 1;//Tastervariable setzen
bounce_count[2] = 1;
}
}
void wdt_setE5(bool enable) {//---------- WDT konfiguration 0,5s ----------
wdt_reset(); // Reset Watchdog Timer
cli();//Interrupts verhindern
MCUSR &= ~(1 << WDRF); /* WDT reset flag loeschen */
WDTCSR |= (1 << WDCE) | (1 << WDE); /* WDCE setzen, Zugriff auf Presclaler etc. */
WDTCSR = 1 << WDP0 | 1 << WDP2; /* Prescaler auf 0.5 s */
WDTCSR |= enable << WDIE;
sei();//Interrupts wieder erlauben
}
void wdt_set2E(bool enable) {//---------- WDT konfiguration 2s ----------
wdt_reset(); // Reset Watchdog Timer
cli();//Interrupts verhindern
MCUSR &= ~(1 << WDRF); /* WDT reset flag loeschen */
WDTCSR |= (1 << WDCE) | (1 << WDE); /* WDCE setzen, Zugriff auf Presclaler etc. */
WDTCSR = 1 << WDP0 | 1 << WDP1 | 1 << WDP2; /* Prescaler auf 2.0 s */
WDTCSR |= enable << WDIE;
sei();//Interrupts wieder erlauben
}
void wdt_set8E(bool enable) {//---------- WDT konfiguration 8s ----------
wdt_reset(); // Reset Watchdog Timer
cli(); //Interrupts verhindern
MCUSR &= ~(1 << WDRF); /* WDT reset flag loeschen */
WDTCSR |= (1 << WDCE) | (1 << WDE); /* WDCE setzen, Zugriff auf Presclaler etc. */
WDTCSR = 1 << WDP0 | 1 << WDP3; /* Prescaler auf 8.0 s */
WDTCSR |= enable << WDIE;
sei(); //Interrupts wieder erlauben
}
ISR(WDT_vect) {//---------- Watchdog Timer Interrupt Service Routine ----------
wdt_status = 1;
}
void enter_sleep(void) {//---------- Sleep Funktion ----------
sleep_status = 1; //Status setzen, wenn Sleep Mode aktiv, wird in der ISR fr Taster verwendet
//digitalWrite(led_g, 1);
bounce_count[0] = 0;
bounce_count[2] = 0;
bounce_count[1] = 0;
set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* Es geht auch SLEEP_MODE_PWR_DOWN */
sleep_enable();
power_adc_disable(); /* Analog-Eingaenge abschalten */
power_spi_disable(); /* SPI abschalten */
power_timer0_disable(); /* Timer0 abschalten */
power_timer1_disable(); /* Timer1 abschalten */
power_timer2_disable(); /* Timer2 abschalten */
power_twi_disable(); /* TWI abschalten */
sleep_mode();
sleep_disable(); //Sleep mode disable
power_all_enable(); /* Komponenten wieder aktivieren */
//digitalWrite(led_g, 0);
sleep_status = 0;
}
ISR(TIMER1_COMPA_vect) {//---------- Timerinterrupt 1000Hz ----------
// Entprellen des Tasters 1, wenn gedrckt wird 1, dann bis bounce_time hochzhlen, dann taster erst wieder freigegeben
if (bounce_count[0] >= 1) {
bounce_count[0]++;
if (bounce_count[0] >= bounce_time) {
bounce_count[0] = 0;
}
}
// Entprellen des Tasters 2, wenn gedrckt wird 1, dann bis bounce_time hochzhlen, dann taster erst wieder freigegeben
if (bounce_count[1] >= 1) {
bounce_count[1]++;
if (bounce_count[1] >= bounce_time) {
bounce_count[1] = 0;
//Serial.println("BounceCount Zeit erreicht");
}
}
// Entprellen des Tasters 3, wenn gedrckt wird 1, dann bis bounce_time hochzhlen, dann taster erst wieder freigegeben
if (bounce_count[2] >= 1) {
bounce_count[2]++;
if (bounce_count[2] >= bounce_time) {
bounce_count[2] = 0;
}
}
}
The measured values are stored on the SD card in the form of a csv file, which can later be analyzed in a suitable program.
2 Component Case printed with a dual extrusion printer.
Material 1: PLA (Case solid / Top solid)
Material 2: TPU (Case soft / Top soft)
- 4-Channel temperature measurement between -25°C to 120°C
- Powered by three AAA batteries
- Atmega 32U4 as µC
- GUI (OLED / Leds)
- 3 user-buttons
- low power consumption ~5mA On with OLED<1mA while measurement OLED off<1µA switch off, only RTC
- USB Interface
- Power switch
- RTC
Comments