#define VERSION F("\n### Discharger GyverOled Enc 2.7\n")
//#define PROFILING
#ifndef __STM32__
// #define USE_MICRO_WIRE
#endif
#define P_ACCUM A0
#define P_SHUNT A6
#define P_TEST A7
#define P_PWM_DISCHARGE 9
// точное значение internal ref 1.1V
// Калибровка: https://blog.unlimite.net/?p=25
// internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())
// scale = 1125300 (1.1 * 1023 * 1000)
#define internal11Ref 1.1
// long scale = 1133947L; // (Для старой Nano)
long scale = internal11Ref * 1023 * 1000;
#define OLED_SPI_SPEED 4000000ul
#define EB_FAST_TIME 120
// #define MENU_SELECTED_H 10 // высота элемента меню
// #define MENU_PARAMS_LEFT_OFFSET 92 // смещение вправо для рендеринга значений
#define MENU_PAGE_ITEMS_COUNT 3 // Количество элементов меню на одной странице
#define MENU_ITEMS 6
#include <EncButton.h>
#include <GyverOLED.h>
#include <GyverOLEDMenu.h>
EncButton eb(2, 3, 4, INPUT_PULLUP, INPUT_PULLUP);
GyverOLED<SSD1306_128x32, OLED_BUFFER> oled;
OledMenu<MENU_ITEMS, GyverOLED<SSD1306_128x32>> menu(&oled);
int d_p = 10;
int d_i = 1000;
int d_d = 50;
byte tt11 = 10;
float tt1 = 5;
float refV = 5.0;
// float Rload = 5.0; // R нагрузки, Ом
float Rload = 1.0; // R нагрузки, Ом
float Rshunt = 0.1; // R шунта, Ом
float OA_R1 = 47740.0;
float OA_R2 = 5148.0;
int timeQuant = 2; // Sec
//### Profiling
#ifdef PROFILING
#define TIMED(X) { TimedObject t(#X); X }
class TimedObject{
public:
uint32_t startTime, stopTime;
const char* name;
TimedObject(const char* n) : startTime(micros()), name(n) {}
~TimedObject() {
stopTime = micros();
Serial.print("PROFILING: \""); Serial.print(name); Serial.print("\"");
Serial.print(" took: ");
Serial.print(stopTime - startTime);
Serial.println(" us");
}
};
#else
#define TIMED(X) X
#endif // Profiling
//### Main screens
class MainScreens {
private:
int _numScreens = 0;
public:
int currentScreen = 0;
MainScreens(int num){
_numScreens = num;
}
bool moveLeft(){
if(currentScreen > 0){
currentScreen--;
return true;
}
return false;
}
bool moveRight(){
if(currentScreen < _numScreens - 1){
currentScreen++;
return true;
}
return false;
}
};
MainScreens mScreen(2);
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(P_PWM_DISCHARGE, OUTPUT);
digitalWrite(P_PWM_DISCHARGE, LOW);
#ifndef __STM32__
analogReference(DEFAULT);
#endif
pinMode(P_ACCUM, INPUT);
pinMode(P_SHUNT, INPUT);
pinMode(P_TEST, INPUT);
// For Gyver core !!!
//analogPrescaler(64);
Serial.begin(115200);
// Wire.setClock(400000L);
oled.init();
oled.clear();
oled.update();
menu.onChange(onItemChange, false);
// menu.onPrintOverride(onItemPrintOverride); // если нужно сделать своё форматирование значений
menu.addItem(PSTR("<- ВЫХОД")); // 0
menu.addItem(PSTR("КОЭФ. P"), GM_N_INT(1), &d_p, GM_N_INT(0), GM_N_INT(100)); // 1
menu.addItem(PSTR("КОЭФ. I"), GM_N_INT(1), &d_i, GM_N_INT(-5), GM_N_INT(20)); // 2
menu.addItem(PSTR("КОЭФ. D"), GM_N_INT(1), &d_d, GM_N_INT(0), GM_N_INT(7000)); // 3
menu.addItem(PSTR("ВРЕМЯ ОПР."), GM_N_FLOAT(0.01), &tt1, GM_N_FLOAT(1), GM_N_FLOAT(20)); // 4
menu.addItem(PSTR("TIMER 1"), GM_N_BYTE(1), &tt11, GM_N_BYTE(1), GM_N_BYTE(255)); // 5
eb.attach(callBack);
menu.showMenu(false);
Serial.println(VERSION);
}
int count = 1;
float accum_v = 0;
float shunt_I = 0;
float test_V = 0;
float mAh = 0;
float mWh = 0;
float accum_v_unload = 0;
bool shuntON = false;
uint32_t timer1 = 0;
// ################ LOOP
void loop() {
eb.tick(); // опрос энкодера
// TEST
if (eb.click()) Serial.println("click");
if (eb.right()) Serial.println("Enc right");
if (eb.left()) Serial.println("Enc left");
if (eb.hold()) { // Переключение меню по долгому нажатию.
Serial.println("hold");
menu.showMenu(!menu.isMenuShowing);
if(menu.isMenuShowing){ // shunt OFF
shuntON = false;
digitalWrite(LED_BUILTIN, shuntON);
digitalWrite(P_PWM_DISCHARGE, shuntON);
Serial.println("Enter menu, Shunt OFF "+ String(shuntON));
} else { // init
count = 1;
shuntON = false;
timer1 = 0;
}
}
if (millis() - timer1 >= (timeQuant * 1000)) {
timer1 = millis(); // сброс
measure();
if(!menu.isMenuShowing){
printScreen(mScreen.currentScreen);
switch (count) {
case 1: // shunt OFF
Serial.println("Shunt OFF "+ String(shuntON));
shuntON = false;
break;
case 2: // shunt ON
Serial.println("Shunt ON "+ String(shuntON));
shuntON = true;
break;
case 12:
count = 0;
break;
default:
break;
}
count++;
digitalWrite(LED_BUILTIN, shuntON);
digitalWrite(P_PWM_DISCHARGE, shuntON);
}
}
} // END Loop
//#########################
//#########################
#ifndef __STM32__
/*
scale = 1125300 (1.1 * 1023 * 1000)
Калибровка 1.1Ref: https://blog.unlimite.net/?p=25
internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())
*
Return: Vcc in millivolts
*/
long readVcc(long scaleArg = 1125300L) { // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
long result;
for(int i=0; i<1; i++){
delay(20); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
result = (high<<8) | low;
// Serial.println(result);
}
result = scaleArg / result;
return result;
}
#endif
// ########################
void measure(){
float OA_V;
float shunt_V;
#ifndef __STM32__
TIMED( refV = readVcc(scale)/1000.0; )
#endif
TIMED( analogRead(P_SHUNT); )
OA_V = analogRead(P_SHUNT) * refV / 1024.0; // ОУ
shunt_V = OA_V / (1 + OA_R1 / OA_R2);
TIMED( analogRead(P_ACCUM); )
accum_v = analogRead(P_ACCUM) * refV / 1024.0; // + аккумулятора
//float shunt_top = analogRead(A1) * refV / 1024.0; // верх шунта
//float shunt_bottom = analogRead(A2) * refV / 1024.0; // низ шунта
analogRead(P_TEST);
test_V = analogRead(P_TEST) * refV / 1024.0;
shunt_I = shunt_V/Rshunt;
if(shuntON){
mAh += shunt_I * 1000 * timeQuant / 3600;
mWh += shunt_I * accum_v * 1000 * timeQuant / 3600;
} else {
accum_v_unload = accum_v;
}
Serial.print("Acc="); Serial.print(accum_v, 3);
//Serial.print(" Sh+="); Serial.print(shunt_top, 3);
Serial.print(" Sh_V="); Serial.print(shunt_V, 3); Serial.print("("); Serial.print(OA_V, 3); Serial.print(")");
Serial.print(" Test="); Serial.print(test_V, 3);
//Serial.print(" Sh-="); Serial.print(shunt_bottom, 3);
//Serial.print(" Sh_V="); Serial.print(shunt_V, 3);
Serial.print(" Sh_I="); Serial.print(shunt_I, 3);
Serial.print(" mAh="); Serial.print(mAh, 3);
Serial.print(" mWh="); Serial.print(mWh, 3);
Serial.print(" [Vcc="); Serial.print(refV, 3); Serial.print("]");
Serial.println("");
}
void printScreen(int screen){
String s1, s2, s3;
oled.clear();
if(screen == 0){
if(shuntON) s3 = String("# "); else s3 = String(" ");
s1 = String(s3) + String(accum_v, 2);
s2 = String("[" + String(refV, 3) + "]");
DisplayText(s1, 0, 0, 1);
DisplayText(s2, 64, 0, 1);
s1 = String(accum_v, 2);
s2 = String(accum_v_unload, 2);
DisplayText(s1, 0, 9, 2);
DisplayText(s2, 64, 9, 2);
} else { // Screen2
//DisplayText("Screen2", 0, 0, 3);
s1 = "Test=" + String(test_V, 3);
DisplayText(s1, 0, 9, 2);
oled.setScale(1);
}
s1 = String(String(shunt_I, 2) + " A");
DisplayText(s1, 1, 24, 1);
s1 = String(String(mAh, 2) + " mAh");
DisplayText(s1, 64, 24, 1);
oled.update();
}
/*
DisplayText(String text, int x, int y,int size, boolean d)
* text is the text string to be printed
* x is the integer x position of text
* y is the integer y position of text
* size is the text size, 1, 2, 3 etc
*/
void DisplayText(String text, int x, int y, int size) {
oled.setScale(size);
//display.setTextColor(WHITE);
oled.setCursorXY(x, y);
oled.print(text);
}
// #### Encoder
void onItemChange(const int index, const void* val, const byte valType) {
if (valType == VAL_ACTION) {
if (index == 0) {
menu.showMenu(false);
// init
count = 1;
shuntON = false;
timer1 = 0;
}
}
}
void callBack() {
switch (eb.action()) {
case EB_TURN:
if(menu.isMenuShowing){
if (eb.dir() == -1) { // 1, -1
menu.selectPrev(eb.fast());
} else {
menu.selectNext(eb.fast());
}
} else {
bool moved = false;
if (eb.dir() == -1) { // 1, -1
moved = mScreen.moveRight();
} else {
moved = mScreen.moveLeft();
}
if(moved) printScreen(mScreen.currentScreen);
}
break;
case EB_CLICK:
menu.toggleChangeSelected();
break;
}
}