Hallo,
die erste Version des neuen Teensy 3.5 Raumluftwächters ist fertig .
So sieht es aus :
Ich habe auch noch ein kleines Video dazu gemacht, so sieht man in mal in Aktion :
Matthias war so nett und hat uns den ersten Sketch programmiert .
Arduino_Workspace_RaumluftWaechter_TEENSY_3_5.zip
Das Oled Display wird als SPI Display angesteuert, man braucht also nicht mehr umjumpern , die werden meist als SPI ausgeliefert .
Um das Display ordentlich anzusteuern, muss im SouceCode noch das SPI Display aktiviert werden und die folgende Zeile eingefügt werden :
//U8G2_SSD1309_128X64_NONAME2_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 26, /* reset=*/ 25);
U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 27, /* data=*/ 28, /* cs=*/ 10, /* dc=*/ 26, /* reset=*/ 25);
hier noch mal der gesamte Quellcode , so wie ich ihn momentan geflasht habe :
/*
*
* Teensy 3.5, 120 Mhz, Faster
*
*
*
Notes:
- Teensy 3.5 cut the solder pads bridge at the back to prevent back current from USB 5V
- Speaker use a "LSM-S2514K" Speaker (the small ones have bad sound or are not working), use pluggable wires
- OLED 1309 use SPI insteat of I2C, because of random I2C Display problems
- The soundfile on the SDCArd has to be named: "alert.wav"
(by the way, not all SDCards are working! So, if you can not hear a sound after a few trys, use another SDCard)
*/
#define SENSOR_SCD30
//#define SENSOR_MHZ19
#include <EEPROM.h>
int updateInterval = 10; // Seconds
int dayLedBrightness = 30;
int nightLedBrightness = 10;
int displayBrightness = 100;
boolean nightDisplay = true;
boolean daySound = true;
boolean nightSound = true;
int airGoodValue = 400;
int airBadValue = 2400;
int airAlarmValue = 2000;
int badAir_LedNrStart = 7; // Here you can adjust from which LEDnr the Air is going to bad:-)
int ledBrightness = dayLedBrightness;
// *** Air Sensor SCD30 ***
#ifdef SENSOR_SCD30
#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30
SCD30 airSensor;
#endif
// *** Air Sensor MHZ-19 ***
#ifdef SENSOR_MHZ19
#include <Mhz19.h>
Mhz19 sensor;
#endif
// Do not edit
int airValue = 400;
int airTem = 20;
int airHum = 50;
// *** RGB LEDS 2812 ***
#include <Adafruit_NeoPixel.h>
#define PIN 33
#define NUMPIXELS 16
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
// *** LED 1309 Display ***
#include <U8g2lib.h>
#define USE_SPI 1 // 0 = I2C, 1 = SPI
#define WIRE_PORT Wire // Used if USE_SPI == 0
#define SPI_PORT SPI // Used if USE_SPI == 1
#define RES_PIN 25
#define DC_PIN 26 // Used only if USE_SPI == 1
#define CS_PIN 10 // Used only if USE_SPI == 1
#if USE_SPI
//U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 26, /* reset=*/ 25);
U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 27, /* data=*/ 28, /* cs=*/ 10, /* dc=*/ 26, /* reset=*/ 25);
#else
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // All Boards without Reset of the Display - SPI I2c IIc Ebay bords
#endif
// *** Light Sensor ***
#include <BH1750.h>
BH1750 lightMeter;
// *** Buttons ***
#define BUTTON_LEFT 7
#define BUTTON_MIDDLE 9
#define BUTTON_RIGHT 5
// *** LED ***
#define LED_PIN 13
// *** AUDIO ***
#include <Audio.h>
AudioPlaySdWav playWav1;
AudioOutputAnalogStereo audioOutput;
AudioConnection patchCord1(playWav1, 0, audioOutput, 0);
AudioConnection patchCord2(playWav1, 1, audioOutput, 1);
// GUItool: end automatically generated code
// *** SD Card ***
#include <SD.h>
#define SDCARD_CS_PIN BUILTIN_SDCARD
boolean SDInserted = false;
// *** RTC ***
#include <TimeLib.h>
time_t RTCTime;
//time_t t = now();
// *** MENU ***
const int STATE_IDLE = 0;
const int STATE_READY_TO_OPEN = 1;
const int STATE_OPENED = 2;
int menuState = STATE_IDLE;
int submenu = 0;
String submenuNames[] = {"UPDATE INTERVALL", "BRIGHTNESS @ DAY", "BRIGHTNESS @ NIGHT", "DISLPAY BRIGHTNESS", "DISLPAY @ NIGHT", "SOUND @ DAY", "SOUND @ NIGHT",
"AIR VERY GOOD VALUE", "AIR VERY BAD VALUE", "AIR ALARM VALUE", "CLOCK", "EXIT"
};
int submenuNamesAmount = 12;
int EXIT_VALUE = submenuNamesAmount - 1;
int displayYPosStart = 15;
void setup() {
loadValues();
pinMode(BUTTON_LEFT, INPUT_PULLUP);
pinMode(BUTTON_MIDDLE, INPUT_PULLUP);
pinMode(BUTTON_RIGHT, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
if ((SD.begin(SDCARD_CS_PIN))) SDInserted = true;
AudioMemory(12);
u8g2.begin();
//u8g2.setFont(u8g2_font_micro_tr);
//u8g2.setFont(u8g2_font_5x8_tf);
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.clearBuffer();
Wire.begin();
Wire.setClock(400000);
pixels.begin();
clearRGBLEDs();
lightMeter.begin();
#ifdef SENSOR_SCD30
if (airSensor.begin() == false) {
Serial.println("Air sensor SCD-30 not detected. Please check wiring. Freezing...");
while (1);
}
#endif
#ifdef SENSOR_MHZ19
Serial1.begin(9600);
sensor.begin(&Serial1);
sensor.setMeasuringRange(Mhz19MeasuringRange::Ppm_5000);
sensor.enableAutoBaseCalibration();
if (sensor.isReady() == false) {
Serial.println("Air sensor MHZ-19 not detected. Please check wiring. Freezing...");
while (1);
}
#endif
executeValues();
// *** Menu Button ***
attachInterrupt(digitalPinToInterrupt(BUTTON_LEFT), button1Event, LOW);
}
void loop() {
drawMainScreen();
if (isDay() || nightDisplay == true) {
// Draw Progress Rectangle
long t = millis();
while (millis() - t < updateInterval * 1000) {
drawProgress(10, displayYPosStart + 30, 106, 4, updateInterval * 1000, millis() - t);
yield();
}
}
}
void drawMainScreen() {
u8g2.clearDisplay();
executeValues();
if (isDay() || nightDisplay == true) {
drawLine(10, displayYPosStart + 24, 106);
u8g2.sendBuffer();
updateAirText(10, displayYPosStart);
updateLightText(10, displayYPosStart + 15);
updateInfoText(10, displayYPosStart + 46);
}
checkToPlayAlertSound();
updateRGBLeds();
if (isDay() || nightDisplay == true) drawProgressFrame(10, displayYPosStart + 30, 106, 4);
}
long lastButton1Event = millis();
void button1Event() {
if (millis() - lastButton1Event < 200) return;
lastButton1Event = millis();
if (menuState == STATE_IDLE) menuButtonEvent();
}
void _delay(int ms) {
long t = millis();
while (millis() - t < ms) {
yield();
}
}
void drawLine(int x, int y, int len) {
u8g2.drawLine(x, y, x + len, y);
}
void drawProgress(int x, int y, int w, int h, int timeDelay, int time) {
u8g2.drawFrame(x + ((float)w / timeDelay * time), y, 1, h);
u8g2.sendBuffer();
}
void drawProgressFrame(int x, int y, int w, int h) {
u8g2.drawFrame(x, y, w + 1, h);
u8g2.sendBuffer();
}
void clearProgress(int x, int y, int w, int h) {
u8g2.drawFrame(x, y, w + 1, h);
u8g2.sendBuffer();
}
void updateLightText(int x, int y) {
u8g2.setCursor(x, y);
u8g2.print("LIGHT SENSOR: ");
u8g2.print((int)readLux());
u8g2.sendBuffer();
}
void updateAirText(int x, int y) {
u8g2.setCursor(x, y);
u8g2.print("AIR QUALITY : ");
u8g2.sendBuffer();
u8g2.print(readAir());
u8g2.sendBuffer();
}
void updateInfoText(int x, int y) {
u8g2.setCursor(x, y);
u8g2.print(airTem);
u8g2.print(F("'"));
u8g2.print("C");
u8g2.setCursor(x + 43, y);
u8g2.print(airHum);
u8g2.print("%");
u8g2.setCursor(x + 78, y);
printClock();
u8g2.sendBuffer();
}
void printClock() {
time_t t = Teensy3Clock.get();
u8g2.print(twoDigits(hour(t)));
u8g2.print(":");
u8g2.print(twoDigits(minute(t)));
}
String twoDigits(String s) {
if (s.length() < 2) s = "0" + s;
return s;
}
float readLux() {
float lux = lightMeter.readLightLevel();
return lux;
}
int readAir() {
int val = 0;
int tem = 0;
int hum = 0;
#ifdef SENSOR_SCD30
val = airSensor.getCO2();
tem = airSensor.getTemperature();
hum = airSensor.getHumidity();
// it needs sometimes about 1 Second to get the new Air Value data!
do {
_delay(10);
val = airSensor.getCO2();
tem = airSensor.getTemperature();
hum = airSensor.getHumidity();
}
while (val == 0);
do {
_delay(10);
tem = airSensor.getTemperature();
}
while (tem == 0);
do {
_delay(10);
hum = airSensor.getHumidity();
}
while (hum == 0);
#endif
#ifdef SENSOR_MHZ19
do {
_delay(10);
val = sensor.getCarbonDioxide();
// tem = sensor.getTemperature();
// hum = sensor.getHumidity();
}
while (val == 0);
#endif
airValue = val;
airTem = tem;
airHum = hum;
return airValue;
}
void checkToPlayAlertSound() {
if ((isDay() && !daySound) || (!isDay() && !nightSound)) return;
if (SDInserted && airValue >= airAlarmValue) {
if (playWav1.isPlaying() == false)playWav1.play("ALERT.WAV");
}
}
boolean isDay() {
time_t t = Teensy3Clock.get();
int h = hour(t);
if (h > 9 && h < 21) return true;
return false;
}
float yellowCounter = 0;
float redCounter = 0;
void updateRGBLeds() {
int singleLED_range = airBadValue / 16;
int lightUpLEDCount = airValue / singleLED_range;
float yellowCounter = 0;
float redCounter = 0;
for (int i = 0; i < lightUpLEDCount; i++) {
if (i <= badAir_LedNrStart) yellowCounter += (float)ledBrightness / (badAir_LedNrStart+3);
else redCounter += (float)ledBrightness / (12 - badAir_LedNrStart);
if (i < badAir_LedNrStart) pixels.setPixelColor(i, pixels.Color(yellowCounter, ledBrightness, 0)); // 0-255
else pixels.setPixelColor(i, pixels.Color(yellowCounter, ledBrightness - redCounter, 0)); // 0-255
pixels.show();
_delay(50);
}
// * All upper Leds Off *
for (int i = lightUpLEDCount; i < 16; i++) pixels.setPixelColor(i, pixels.Color(0, 0, 0));
pixels.show();
}
void clearRGBLEDs() {
for (int i = 0; i < 16; i++) pixels.setPixelColor(i, pixels.Color(0, 0, 0)); // 0-255
pixels.show();
}
boolean editMode = false;
void menuButtonEvent() {
if (menuState == STATE_IDLE) {
menuState = STATE_OPENED;
submenu = 0;
updateOptionsMenu();
doSubmenu();
}
}
void doSubmenu() {
boolean submenuFirstEnter = true;
while (menuState == STATE_OPENED) {
if (digitalRead(BUTTON_LEFT) == LOW) {
while (digitalRead(BUTTON_LEFT) == LOW) {};
if (submenu == EXIT_VALUE) {
saveValues();
drawMainScreen();
menuState = STATE_IDLE;
submenu = 0;
lastButton1Event = millis();
return;
}
else {
if (!submenuFirstEnter) subMenuEventLeft();
}
}
else if (digitalRead(BUTTON_MIDDLE) == LOW) {
subMenuEventMiddle();
long t = millis();
while (digitalRead(BUTTON_MIDDLE) == LOW) {
if (millis() - t > 200) break;
};
}
else if (digitalRead(BUTTON_RIGHT) == LOW) {
subMenuEventRight();
long t = millis();
while (digitalRead(BUTTON_RIGHT) == LOW) {
if (millis() - t > 200) break;
};
}
submenuFirstEnter = false;
delay(20);
}
}
void subMenuEventLeft() {
if (menuState != STATE_OPENED) return;
while (digitalRead(BUTTON_LEFT) == LOW) {};
if (!editMode) {
editMode = true;
updateOptionsMenu();
}
else {
editMode = false;
updateOptionsMenu();
}
}
void subMenuEventMiddle() {
if (menuState != STATE_OPENED) return;
if (!editMode) {
submenu--;
if (submenu < 0) submenu = submenuNamesAmount - 1;
updateOptionsMenu();
}
else {
changeValues(1);
}
}
void subMenuEventRight() {
if (menuState != STATE_OPENED) return;
if (!editMode) {
submenu++;
if (submenu >= submenuNamesAmount) submenu = 0;
updateOptionsMenu();
}
else {
changeValues(2);
}
}
void updateOptionsMenu() {
if (menuState != STATE_OPENED) return;
u8g2.clearDisplay();
u8g2.setCursor(10, 20);
u8g2.print(submenuNames[submenu]);
u8g2.sendBuffer();
// Exlude "Exit"
if (submenu != EXIT_VALUE) {
u8g2.setCursor(10, 35);
showOptions();
if (editMode) showEditOptions();
else {
u8g2.setFont(u8g2_font_micro_tr);
int w = (int)((float)100/submenuNamesAmount);
u8g2.drawBox(10+submenu*w, 5, w, 2);
u8g2.setCursor(31, 56);
u8g2.print("EDIT");
u8g2.setCursor(60, 56);
u8g2.print("<-");
u8g2.setCursor(87, 56);
u8g2.print("->");
u8g2.setFont(u8g2_font_6x10_tf);
}
u8g2.sendBuffer();
}
}
void showOptions() {
switch (submenu) {
case 0:
if (updateInterval < 60) {
u8g2.print(updateInterval);
u8g2.print(" sec");
} else {
u8g2.print(updateInterval / 60);
u8g2.print(" min");
}
break;
case 1:
u8g2.print(dayLedBrightness);
break;
case 2:
u8g2.print(nightLedBrightness);
break;
case 3:
u8g2.print(displayBrightness);
break;
case 4:
u8g2.print(convertBooleanToYesNo(nightDisplay));
break;
case 5:
u8g2.print(convertBooleanToYesNo(daySound));
break;
case 6:
u8g2.print(convertBooleanToYesNo(nightSound));
break;
case 7:
u8g2.print(airGoodValue);
break;
case 8:
u8g2.print(airBadValue);
break;
case 9:
u8g2.print(airAlarmValue);
break;
case 10:
printClock();
break;
default:
break;
}
}
void showEditOptions() {
u8g2.setFont(u8g2_font_micro_tr);
u8g2.setCursor(30, 57);
u8g2.print("BACK");
u8g2.setCursor(63, 57);
u8g2.print("-");
u8g2.setCursor(89, 57);
u8g2.print("+");
u8g2.sendBuffer();
u8g2.setFont(u8g2_font_6x10_tf);
}
char * convertBooleanToYesNo(boolean state) {
if (state) return "YES";
else return "NO";
}
void changeValues(int buttonMiddleRight) {
if (buttonMiddleRight == 1) { // Middle Button pressed
switch (submenu) {
case 0:
if (updateInterval > 60) updateInterval -= 60;
else if (updateInterval > 10) updateInterval -= 10;
break;
case 1:
if (dayLedBrightness > 10) dayLedBrightness -= 10;
break;
case 2:
if (nightLedBrightness > 10) nightLedBrightness -= 10;
break;
case 3:
if (displayBrightness > 10) displayBrightness -= 10;
break;
case 4:
nightDisplay = false;
break;
case 5:
daySound = false;
break;
case 6:
nightSound = false;
break;
case 7:
if (airGoodValue > 400) airGoodValue -= 100;
break;
case 8:
if (airBadValue > 1000) airBadValue -= 100;
break;
case 9:
if (airAlarmValue > 500) airAlarmValue -= 100;
break;
default:
break;
}
}
else if (buttonMiddleRight == 2) { // Right Button pressed
switch (submenu) {
case 0:
if (updateInterval < 60) updateInterval += 10;
else if (updateInterval <= 540) updateInterval += 60;
break;
case 1:
if (dayLedBrightness < 250) dayLedBrightness += 10;
break;
case 2:
if (nightLedBrightness < 250) nightLedBrightness += 10;
break;
case 3:
if (displayBrightness < 250) displayBrightness += 10;
break;
case 4:
nightDisplay = true;
break;
case 5:
daySound = true;
break;
case 6:
nightSound = true;
break;
case 7:
if (airGoodValue < 1000) airGoodValue += 100;
break;
case 8:
if (airBadValue < 3000) airBadValue += 100;
break;
case 9:
if (airAlarmValue < 3000) airAlarmValue += 100;
break;
default:
break;
}
}
updateOptionsMenu();
}
void loadValues() {
if (EEPROM.read(100) != 1 || EEPROM.read(101) != 2 || EEPROM.read(102) != 3) {
saveValues();
}
else {
updateInterval = EEPROM.read(110) & 0xff;
dayLedBrightness = EEPROM.read(111) & 0xff;
nightLedBrightness = EEPROM.read(112) & 0xff;
displayBrightness = EEPROM.read(113) & 0xff;
nightDisplay = EEPROM.read(114) & 0xff;
daySound = EEPROM.read(115) & 0xff;
nightSound = EEPROM.read(116) & 0xff;
airGoodValue = ((EEPROM.read(117) & 0xff) << 8) | (EEPROM.read(118) & 0xff);
airBadValue = ((EEPROM.read(119) & 0xff) << 8) | (EEPROM.read(120) & 0xff);
airAlarmValue = ((EEPROM.read(121) & 0xff) << 8) | (EEPROM.read(122) & 0xff);
}
}
void saveValues() {
EEPROM.write(100, 1);
EEPROM.write(101, 2);
EEPROM.write(102, 3);
EEPROM.write(110, updateInterval);
EEPROM.write(111, dayLedBrightness);
EEPROM.write(112, nightLedBrightness);
EEPROM.write(113, displayBrightness);
EEPROM.write(114, nightDisplay);
EEPROM.write(115, daySound);
EEPROM.write(116, nightSound);
EEPROM.write(117, airGoodValue >> 8);
EEPROM.write(118, airGoodValue & 0xff);
EEPROM.write(119, airBadValue >> 8);
EEPROM.write(120, airBadValue & 0xff);
EEPROM.write(121, airAlarmValue >> 8);
EEPROM.write(122, airAlarmValue & 0xff);
executeValues();
}
void executeValues() {
u8g2.setContrast(displayBrightness);
if (isDay()) ledBrightness = dayLedBrightness;
else ledBrightness = nightLedBrightness;
}
Alles anzeigen
Es ist eine "Soundkarte" On Board Die spielt eine "alert.wav" , die auf der Micro-SD Karte ist ab, wenn der eingestellte Warnlevel erreicht ist .
Die Wave Datei sollte 44100Hz und 16Bit Stereo haben
Es wird die RTC Uhr im Teensy genutzt und über eine 1225 LiIon Zelle gehalten .
Es gibt 3 Tasten mit denen man ein paar Dinge einstellen kann . Habe ich im Video ein wenig erklärt .
Es sind aber nicht alle Funktionen umgesetzt .
man kann noch keine Offsets im Menu einstellen und die Uhrzeit nicht .
( Die Uhrzeit stellt sich beim flashen , dazu nehme ich immer die Batterie raus und lösche praktisch die RTC Zeit . Dann wird beim Flashen die aktuelle PC Zeit mit übertragen um im RTC gespeichert )
der Helligkeitssensor 1750 ist integriert aber wird (noch) nicht zum Dimmen oder aufhellen benutzt . Das müssen wir uns noch hin- bzw. einbauen .
Auf jedenfalls ist das auch wieder ein schönes Stück Hardware . Werr Interesse an Platinen hat , kann sich bei mir melden, ich werden mal, von einer neuen Version, ein paar herstellen lassen, Matthias hat noch etwas am Layout geändert .
Gruß Martin
Aktuelle Version vom 10.02.2022 Arduino_Workspace_RaumluftWaechter_TEENSY_3_5.zip