GhettoVaper – an extended GhettoVape III

Preamble

Following on from Vaping – Box Mods, I decided to extend the GhettoVape III sketch, which was posted by Julian Loiacono:

Special characters on a 1602 LCD

Do you need to print an “unusual” character on a 1602 LCD display, such as a degree symbol, or an Omega for a resistance value? From Print degree symbol ° on LCD, The character set of the HD44780:

HD44780 character set
HD44780 character set

See also Enhanced LiquidCrystal. The code for the Ohm symbol, omega, is 244.

The Schematic

I made a few modifications:

  • Added reverse polarity protection and reverse voltage LED
  • Added low voltage detection
  • Flipped the logic of the switch S2, so that it pulls HIGH, and not LOW, when pressed.
  • Ability to measure the voltage across the coil, by tapping the connection between the coil and the NFET, to pin A1
  • Added switchable unregulated vaping circuitry
  • Added 2 x 22μF capacitors to power supply side
  • Fuses
  • Master ON/OFF power switch

The aim of the modified circuit is so that it can be used as either an unregulated box mod, or a PWM controlled box mod (using an Arduino to regulate the vape. Either mode can be selected by using a toggle switch to switch either function.

GhettoVaper schematic
GhettoVaper schematic

This schematic was my initial draft, and used the second switch to both fire the coil in unregulated mode and select the features in the GhettoVaper firmware in Arduino mode. However this was not quite right, nor provided for a consistent UX between the unregulated and PWM controls. This second iteration fired the coil differently, in unregulated mode, and the switch S2 is only used in PWM (Arduino) mode:

GhettoVaper Schematic - No Overvoltage Protection
GhettoVaper Schematic – No Overvoltage Protection

Note that switches S3, S4 and S5 are DPDT switches, which change mode of usage:

  • S3 – selects between unregulated and Arduino control
  • S4 – reference for current measurement is internal/external
  • S5 – enables and disables current measurement for temperature control

Also, the use of two 18650 in parallel is not a very safe configuration, due to the two cells’ properties changing over time, and one cell could end up discharging in to the other (in the parallel configuration). Therefore, using just one cell might be preferable, also, the current capacity of the mod will be less, obviously.

Undervoltage protection is dealt with below, see Revisiting Low Voltage Protection.

Parts List

These are the components required:

  • Unregulated basic mod
    • NFET – IRLB3034PBF or IRLB3813 M
    • 10/15k resistor
    • 18650 Battery sled (1 or 2 cell)
    • 2 x 15A fuse
    • 1 x 30A fuse (optional)
    • Toggle switch (30 A) – Master on/off switch for the load (optional, see Mosfet box mod master on off)
    • Toggle switch (0.5A) – Master on/off switch for the fire button
    • Momentary push button switch (30A)
    • 510 connector – Varitube or Fat Daddy
    • 2 x 22 μF electrolytic capacitors (optional)
  • PWM control
    • Arduino – any Arduino is fine, although, for size, ideally a Nano, or Micro at 3 V or 5 V)
    • 2 x 22 μF electrolytic capacitors (optional)
    • Boost to 5V (optional – if using 3V Arduino Micro, not required)
    • Toggle switches (low current) – how many. 2Pole, 1 pole, single throw (ST), double throw (DT) ????
    • Momentary push button switch (1 A)
  • Reverse polarity protection
    • 220R Resistor
    • LED
    • PFET: SUP75P03-07-E3
  • Low battery voltage protection (using a Zener diode)
    • For a low battery at 3.2V
      • Zener for
        • 2S: (3.2 x 2) – 0.7 = 5.7 V
        • 2P: 3.2 – 0.7 = 2.5 V
    • For a low battery at 3.8V
      • Zener for
        • 2S: (3.2 x 2) – 0.7 = 5.7 V
        • 2P: 3.2 – 0.7 = 2.5 V
    • Toggle switch (low current) – 1P1T – to make low voltage protection switchable (optional – do you want the user to be able to override low voltage protection?)
  • Low battery voltage protection (using a voltage comparator)
    • TLV3012
    • For 3.3V
      • 1 MΩ
      • 2 MΩ
    • For 3.8V
      • 1 MΩ
      • 2.06 MΩ
    • For 3.7V
      • 1 MΩ
      • 1.98 MΩ
    • PFET
  • Temperature control
    • 1 mΩ Resistor
    • Amplifier:
      • INA199
      • 0.1 uF Capacitor
    • Voltage Reference:
      • IN4040
      • Resistor Rs

The code

I made a few modifications:

  • Added compiler directives for the logic of the S2 switch, so you can choose the positive, or LOW logic behaviour of the switch S2.
  • Added coil resistance
  • Added power selection
  • Tidied and indented the code.

The code is on Github: GhettoVaper.

The code below does not match up with any version on Github, as it intended as an example of what the code does. While it does compile, it is better not to use this code, and to use the updated code from Github: GhettoVaper, instead. If you do decide to compile this, note that the following files are also required:

  • LiquidCrystalFast.h
  • LiquidCrystalFast.cpp
  • mButton.h
  • mButton.cpp

These files can be found in the original, GhettoVapeIII, or GhettoVaper.

This is the first iteration below, which may contain some redundant, or incomplete code.

#include "mButton.h"
#include "LiquidCrystalFast.h"
#include "EEPROM.h"
#include 

/* Problems:
 * 
 * 1. You have to wait for the Arduino to boot, before each vape
 * 2. Need to be able to select coil resistance, R_coil
 * 3. You can't measure the power yet - need to read voltage below coil, on A1 and then P=(A0-A1)*(A0-A1)/R_Coil
 * 
 * 
 * Can modify switch S1 to be a toggle switch, so Arduino is always on and then S2 still controls the vape, with a hold, but multiple switches select the "mode select" mode
 */

 /* Changes:
  *  S2 push to high
  *  Added defines
  */
 
/*
 
 The circuit:
 See : http://i.imgur.com/6NgEkOm.jpg
 
 * Switch between +3.7v and 5V boost converter
 * Switch between GND and pin 10
 * Resistor between pin 10 and +5v
 
 * LCD RS pin to digital pin 9
 * LCD WR pin to digital pin 8
 * LCD Enable pin to digital pin 7
 * LCD D4 pin to digital pin 6
 * LCD D5 pin to digital pin 5
 * LCD D6 pin to digital pin 4
 * LCD D7 pin to digital pin 3
 * 10K potentiometer:
 * ends to +5V and ground
 * wiper to LCD VO pin (pin 3)
 
 */

//

#define __S2_To_HIGH__
//#define __S2_To_LOW__  // default to this, as original
//#define __MULTI_PUSH_S2__

// include the library code:

#define fetPin        11
#define lcd_backlight 2
#define secondButton  10
#define batteryPin    A0

#define STATE_BAT        0
#define STATE_COIL       1
#define STATE_POWER      2
#define STATE_RESISTANCE 3 
#define STATE_MATERIAL   4 
#define STATE_READER     5
#define STATE_ADDRESS    6 

// Coil Materials
#define kMaterial_SS316     0
#define kMaterial_Ni200     1
#define kMaterial_Ti        2

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd(9,  8,  7,  6,  5, 4, 3);   // Original GhettoVape III wiring
// LCD pins:          RS  RW  EN  D4 D5  D6 D7
MomentaryButton button(secondButton);

const char speedMessage[] = {"Vape on it!!!"}; // use this form

#define EE_voltageAddress 0
#define EE_programAddress 2
#define EE_resistanceAddress 4
#define EE_powerAddress 6
#define EE_materialAddress 8
//#define numStates 5                 // not used
#define minResistance 0.0
#define maxResistance 2.0
#define numResistanceSteps 20
#define stepResistanceWeight (maxResistance - minResistance)/numResistanceSteps
#define minPower 0.0
#define maxPower 70.0
#define numPowerSteps 70
#define stepPowerWeight (maxPower - minPower)/numPowerSteps
#define minVoltage 1.0
#define maxVoltage 4.2
#define numVoltageSteps 20
#define stepVoltageWeight (maxVoltage - minVoltage)/numVoltageSteps
#define numProgs 3
#define numMaterialProgs 3
#define interval 100

int strPos = 0;
int desiredVoltage;
int state = 0; 
int wpm = 350;
int readPeriod = 60000 / wpm; //period in ms
char thisWord[30];
int wordLoc;
long startTime = 0;


///BIG CHARACTER SHIZ

// the 8 arrays that form each segment of the custom numbers
byte LT[8] = 
{
  B00111,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte UB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000
};
byte RT[8] =
{
  B11100,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte LL[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  B00111
};
byte LB[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111
};
byte LR[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100
};
byte MB[8] =
{
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111
};
byte block[8] =
{
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
byte blank[8] =
{
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
};

byte cross[8] =
{
  B11100,
  B11110,
  B11110,
  B01111,
  B01111,
  B01111,
  B00111,
  B00111,
};


void customJ(int location)
{ // uses segments to build the number 0
  lcd.setCursor(location,0); // set cursor to column 0, line 0 (first row)
  lcd.write(8);  // call each segment to create
  lcd.write(8);  // bottom half of the number
  lcd.write(2);
  lcd.setCursor(location, 1); // set cursor to colum 0, line 1 (second row)
  lcd.write(3);  // call each segment to create
  lcd.write(4);  // bottom half of the number
  lcd.write(5);
}

void customU(int location)
{
  lcd.setCursor(location,0);
  lcd.write(2);
  lcd.write(8);
  lcd.write(2);
  lcd.setCursor(location,1);
  lcd.write(3);
  lcd.write(4);  
  lcd.write(5);
}

void customI(int location)
{
  lcd.setCursor(location,0);
  lcd.write(8);
  lcd.write(7);
  lcd.write(8);
  lcd.setCursor(location, 1);
  lcd.write(8);
  lcd.write(7);
  lcd.write(8);
}

void customC(int location)
{
  lcd.setCursor(location,0);
  lcd.write(7);
  lcd.write(1);
  lcd.write(1);
  lcd.setCursor(location, 1);
  lcd.write(3);
  lcd.write(4);
  lcd.write(4); 
}

void customE(int location)
{
  lcd.setCursor(location,0);
  lcd.write(7);
  lcd.write(6);
  //lcd.write(6);
  lcd.setCursor(location, 1);
  lcd.write(7);
  lcd.write(4);
  //lcd.write(4);
}

void customF(int location)
{
  lcd.setCursor(location,0);
  lcd.write(7);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(location, 1);
  lcd.write(3);
  //lcd.write();
  //lcd.write(4);
}

void customR(int location)
{
  lcd.setCursor(location,0);
  lcd.write(7);
  lcd.write(6);
  lcd.write(7);
  lcd.setCursor(location, 1);
  lcd.write(3);
  lcd.write(8);
  lcd.write(9);
}

void customS(int location)
{
  lcd.setCursor(location,0);
  lcd.write(7);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(location, 1);
  lcd.write(4);
  lcd.write(4);
  lcd.write(5);
}

void customH(int location)
{
  lcd.setCursor(location,0);
  lcd.write(2);
  lcd.write(4);
  lcd.write(2);
  lcd.setCursor(location,1);
  lcd.write(3);
  lcd.write(8);  
  lcd.write(5);
}

//END BIG CHARACTER SHIZ

void setup() {

  // set up the LCD's number of rows and columns: 
  lcd.begin(16, 2);

  // Turn on the backlight
  pinMode(lcd_backlight, OUTPUT);
  analogWrite(lcd_backlight, 255);

  button.setup(); // set as INPUT, set HIGH
  button.setThreshold(300); //1 sec between short and long hold

  // assigns each segment a write number
  lcd.createChar(0,LT);
  lcd.createChar(1,UB);
  lcd.createChar(2,RT);
  lcd.createChar(3,LL);
  lcd.createChar(4,LB);
  lcd.createChar(5,LR);
  lcd.createChar(6,MB);
  lcd.createChar(7,block);
  lcd.createChar(8,blank);
  lcd.createChar(9,cross);
}

void loop() {

#if defined (__S2_To_HIGH__)
// For push S2 to HIGH
if(digitalRead(secondButton)){      // if S2 is held
#elif defined (__MULTI_PUSH_S2__)
// For multi-push S2
if(!digitalRead(secondButton)){   // if S2 is clicked five times
#else
// For push S2 to LOW - default, as original
  if(!digitalRead(secondButton)){   // if S2 is held
#endif
    stateMachine();
  }
  else{                             // if S2 does not meet the if
    desiredVoltage = (minVoltage + EEPROM.read(EE_voltageAddress)*stepVoltageWeight)*255/(analogRead(batteryPin)*5.25/1024);
    if(minVoltage + EEPROM.read(EE_voltageAddress)*stepVoltageWeight > analogRead(batteryPin)*5.25/1024)
      desiredVoltage = 255;
    analogWrite(fetPin, desiredVoltage);

    switch(EEPROM.read(EE_programAddress)){
      case(0):
      //JUICE
      do{
        customJ(0);    // displays custom 0 on the LCD
        delay(interval);
        customU(4);
        delay(interval);
        customI(7);
        delay(interval);
        customC(10);
        delay(interval);
        customE(14);
        delay(interval);
        lcd.clear();
        delay(interval);

      }
      while(true);
      break;

      case(1):
      do{
        customF(0);    // displays custom 0 on the LCD
        customR(3);
        customE(7);
        customS(10);
        customH(13);
        delay(interval);
        lcd.clear(); 
        delay(interval);
      }
      while(true);
      break;


      case(2):
      speedRead();
      break;
    }
  }
}

void stateMachine(){
  while(digitalRead(secondButton));
  delay(50);

  while(true){
    switch(state){

      case(STATE_BAT):  // show battery voltage
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Battery Voltage:");
        lcd.setCursor(0,1);
        lcd.print(analogRead(batteryPin)*5.2/1024);
        lcd.print(" volts");
        button.check();
        if(button.wasHeld())
//          state = 1;
          state++;
        break;
      }

      case(STATE_COIL):  // adjust coil voltage
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Coil Voltage:");
        lcd.setCursor(0,1);
        lcd.print(minVoltage + EEPROM.read(EE_voltageAddress)*stepVoltageWeight);
        lcd.print(" V");
        button.check();
        if(button.wasClicked())
          EEPROM.write(EE_voltageAddress, (EEPROM.read(EE_voltageAddress)+1)%numVoltageSteps);
        if(button.wasHeld())
//          state = 2;
          state++;
        break;
      }

      case(STATE_POWER):  // adjust power
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Power:");
        lcd.setCursor(0,1);
        lcd.print(minPower + EEPROM.read(EE_powerAddress)*stepPowerWeight);
        lcd.print(" W");
        button.check();
        if(button.wasClicked())
          EEPROM.write(EE_powerAddress, (EEPROM.read(EE_powerAddress)+1)%numPowerSteps);
        if(button.wasHeld())
//          state = 4;
          state++;
        break;
      }

      case(STATE_RESISTANCE):  // adjust coil resistance
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Coil Resistance:");
        lcd.setCursor(0,1);
        lcd.print(minResistance + EEPROM.read(EE_resistanceAddress)*stepResistanceWeight);
        lcd.print(" \364"); //Ohm symbol (Omega) in octal
        button.check();
        if(button.wasClicked())
          EEPROM.write(EE_resistanceAddress, (EEPROM.read(EE_resistanceAddress)+1)%numResistanceSteps);
        if(button.wasHeld())
//          state = 4;
          state++;
        break;
      }

      case(STATE_MATERIAL):  // adjust materials
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Coil Material:");
        lcd.setCursor(0,1);
        switch(EEPROM.read(EE_materialAddress))
        {
          case(kMaterial_SS316):
            lcd.print("SS 316");
            break;
          case(kMaterial_Ni200):
            lcd.print("Ni200");
            break;
          case(kMaterial_Ti):
            lcd.print("Ti");
            break;
        }

        button.check();
        if(button.wasClicked())
          EEPROM.write(EE_materialAddress, (EEPROM.read(EE_materialAddress)+1)%numMaterialProgs);
        if(button.wasHeld())
//          state = 5;
          state++;
        break;
      }

      case(STATE_READER):
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("LCD Program:");
        lcd.setCursor(0,1);
        switch(EEPROM.read(EE_programAddress)){
          case(0):
          lcd.print("JUICE");
          break;
          case(1):
          lcd.print("FRESH");
          break;
          case(2):
          lcd.print("SpeedRead");
          break;
        }

        button.check();
        if(button.wasClicked())
          EEPROM.write(EE_programAddress, (EEPROM.read(EE_programAddress)+1)%numProgs);
        if(button.wasHeld())
//          state = 6;
          state++;
        break;
      }

      case(STATE_ADDRESS):
      {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("lets roll");
        button.check();
        if(button.wasHeld())
          state = 0;
        break;
      }
    }

    delay(30); 
  }
}

void speedRead(){
  int i;
  strPos = 0;
  while(speedMessage[strPos] != '\0'){
    for(i = 0; speedMessage[strPos] != ' ' && speedMessage[strPos] != '\0'; i++){
      thisWord[i] = speedMessage[strPos++];
    }
    strPos++;
    thisWord[i] = '\0';
    lcd.clear();
    lcd.setCursor(4-i/5,0);
    lcd.print(thisWord);
    if (thisWord[i-1] == '.'  || thisWord[i-1] == ',')
      delay(250);
    else
      delay(100);
  }
}

Testing

If you are testing the above code with a DRRobot 1602 LCD shield, you need to change a couple of things:

Firstly, change the call to the lcd class constructor like so:

// initialize the library with the numbers of the interface pins
LiquidCrystalFast lcd(8, 255, 9, 4, 5, 6, 7); // For DFRobot 1602 shield
//LiquidCrystalFast lcd(9, 8, 7, 6, 5, 4, 3); // Original GhettoVape III wiring
// LCD pins: RS RW EN D4 D5 D6 D7

Secondly, the A0 pin is used by the DFRobot 1602 LCD shield as an output, for the buttons. So, the pin used for the battery sense needs to be changed to A1.

Thirdly, change the secondButton to pin 12, as pin 10 is used by the DRRobot shield:

#define secondButton 12

Additionally, if closing switch S2 then pulls the pin high, then you need to change a line in mButton.cpp, in void MomentaryButton::setup() so that the the input pin uses pull down resistors (and not pull up, as used by the original GhettoVape III):

digitalWrite(pin, HIGH);

becomes

digitalWrite(pin, LOW);

Note that the code on Github has processor directives already in place, so you do not need to manually change the code, just uncomment out the #define line:

// Testing with DFRobot 1602 display
//#define __Using_DFRobot_1602_LCD__

Nice and easy.

Likewise, there is a series of #define lines, in mButton.h, for the changing of the S2 switch from pull up to pull down:

// Switch S2 behaviour
//#define __S2_To_HIGH__
//#define __S2_To_LOW__  // default to this, as original
//#define __MULTI_PUSH_S2__

A nice touch, to make the code more versatile. Note that this #define makes the changes in mButton.cpp as well, not only GhettoVaper.ino

Further changes

Further iterations added the following features:

How to use

It is a notoriously difficult UI to navigate, or rather it is not the UI but the UX: The single multi function button makes it annoyingly awkward to control.

Hold the switch S2 down when powering on, or resetting to gain access to the UI. Then, moderately slow clicks, of S2, to select the function, and then immediately start clicking quickly to cycle through that particular function’s options. You almost have to start clicking fast, straight away, as soon as the required function has been selected, so you really need to engage your brain, and fingers, in gear when you reach the function (or state) prior to the function (or state) that you require, in readiness for the rapid clicking.

Sounds difficult? Yes, it is and for that reason I am not particularly happy with this set up.

UPDATE: Actually, there was a bug in the mButton.cpp, and having fixed that the UX is much much better. Now, a long press changes function, short presses select setting in that function.

Also two buttons would be better, as you can only cycle in one direction, so if you miss your required option, or setting, then you have to cycle all of the way round again.

Power setting

You select the power, and the new voltage (based on the resistance) is set simultaneously. – requires the resistance to be set correctly!

Temperature Setting and TCR

To use both the temperature setting and the material temperature coefficients of resistance (TCR), it is necessary to be able to measure the resistance of the coil, as it heats up. It is relatively easy to do this. See Arduino Ohmmeter Tutorial. So, if we add a precise resistance below the NFET, to GND, and we measure the voltage across the resistor, via the analogue pin A2, then we can calculate the current following through that precise resistor, and hence through the coil. As we are already measuring to voltage across the coil, the resistance of the vaping coil can be calculated. Note, the current measured will be a little off, due to current drain of the Arduino analogue inputs, but we will ignore that.

However, as always, there is a caveat. In order to not limit the current able to flow through the coil, the resistance needs to be very low – Or, looking at another way, a large precision resistance will have a large voltage drop across it and thus reduce the voltage across the coil. So, we need a small precision resistance. However, as already stated, this will provide us with a very small voltage to measure, certainly less than the 5V max accepted by the Arduino’s analogue input. Therefore, this voltage, or signal, would require amplifying with a very precise op-amp. In fact, an instrumentation amplifier would be required.

Instrumentation Amplifier
Instrumentation Amplifier

See Scale 30-50 mV signal to 0-5 V range for more information. Not difficult to do, but more work than this project probably merits.

However, having said that, the code is not that much, and a multiplier (set to 1 – for the moment) can be included in the code, should an amplifier be added at a later date, and then the multiplier can be set as appropriate in a #define.

An INA199 could be used, with a 0.15 mΩ shunt resistor (500A 75 mV). However, these are quite large, and 1m to 15m are more commonly found in small devices, PCN PBV Series Radial Metal Film Fixed Resistor 1mΩ ±0.5% 1.5W ±50ppm/°C, £11.19. An example circuit may be found here:

Schematic of current sensing amplifier
Schematic of current sensing amplifier

The INA199 is priced:

An instrumentation amplifier could be used, INA115Texas Instruments INA155EA/250, Instrumentation Amplifier, 1mV Offset, R-RO, 3 V, 5 V, 8-Pin MSOP, £2.26

A voltage reference may also be required, such as the LM4040. Note that a reference is not required, for the INA199, if the current is unidirectional. See also Precision shunt regulator failure behavior:

LM4040-N
LM4040-N

The LM4040 comes in multiple flavours, with respect to voltage and tolerence – looking at 2.5 V and 5 V, with 0.1%.

SMD:

Not SMD (TO-92):

However, as previously stated, these are not required for uni-direction mode of the INA199.

The 2.5V version may be desired, to allow for voltage drop in the LiPo, as it discharges, as  otherwise the INA199 will be amplifying against a varying voltage reference from its supply voltage, if a LM4040 is not used. Such a setup would suggest an inaccurate reading may be obtained as the LiPo discharges – but I am not sure about that.

The custom characters

I noticed that there was a problem drawing the “C” in “JUICE” with the original version. This is because only up to eight custom characters can be assigned to the  HD44780, any more definitions then “wrap around”, so that custom character “8” points to custom character “0”, custom character “9” points to custom character “1” and so on… So, there are two characters too many. The solution:

  • A blank (8) is already defined by space, or use character 254;
  • The block (7) can be replaced by other custom characters, to give more stylised lettering, or use character 255.

The cross custom character moved from code 9 to code 7. The custom characters are now:

  1. LT (code 0)
  2. UB (code 1) – Upper Bar
  3. RT (code 2)
  4. LL (code 3)
  5. LB (code 4) – Lower Bar
  6. LR (code 5)
  7. MB (code 6) – Middle (and Upper) bar
  8. cross (code 7)

Replacing the block custom character

The original custom character block was code 7. The changes were:

  • “I” – The top block (7) became 2 (RT) and the bottom block became 3 (LL)
  • “C” – This was incorrect in the original anyway (see also “S”): The top block (7) became 0 (LT)
  • “E” – The top block (7) became 0 (LT) and the bottom block (7) became 3 (LL)
  • “F” – The top block (7) became 0 (LT) and the bottom block (7) became 5 (LR)
  • “R” – The top block (7) became 0 (LT) and the bottom block (7) became 2/5 (RT/LR)
  • “S” – this was incorrect in the original anyway (see also “C”): The top block (7) became 0 (LT)

Note that:

  • the upper block can be replaced by 0 (LT) or 2 (RT)
  • the lower block can be replaced by 3 (LL) or 5 (LR)
  • Is there an inconsistency in naming here?
    • Should LT be TL (Top Left) and RT be TR (Top Right)
    • Should LL be BL (Bottom Left) and LR be (Bottom Right)
  • For stylistic consistency, these pairs should be used together:
    • LT and LR, and;
    • RT and LL
  • For further stylistic consistency, the same pair should be used for the upstrokes, in all letters, and the opposite pair should be used for the downstrokes in all letters. It does not really matter which pair is used for which, just keep it consistent.
    • Note that the “I” could be the opposite to the upperstroke of the “H” for example.

Replacing the blank custom character

The changes were:

  • “J” – The top two blanks (8) were replaced by spaces
  • “U” – The top blank (8) was replaced by a space
  • “I”  – The top two blanks (8) were replaced by spaces, and The bottom two blanks (8) were replaced by spaces
  • “R” – The bottom blank (8) was replaced by a space

Other custom character modifications

Other changed made were:

  • Although the original “H” did not use the block character, I modified it anyway. “H” – top and bottom – should this be opposite to “I”? It is a matter of stylistic preference. The lower 5 (LR) became a 3 (LL).
  • The “R” was also modified with the 3 (LL) becoming a 5 (LR)
  • The “C” was very wrong in the original, partially due to the wrap around of the custom character indexes, and also, the wrong code number for the third top character. The “C” was correctly modified to show the final 1 (UB) to a 7 (cross)
  • The “F’ had:
    • two upper 6 (MB) characters, I reduced this to one, effectively reducing the width.
    • a lower 3 (LL) which I changed to a 5 (LR)
    • I added (reinstated the lower two blanks, with spaces
    • I added a space to the top at the end.
    • The “F” is now three characters wide, with the final column blank.
  • The “E” had:
    • was three characters wide, although it can only be two wide in the “JUICE” message
    • The third column can be spaces, or 6 (MB) and 4 (LB) for the top and bottom respectively. It depends on whether you want the “E” to be two or three characters wide.
    • The “E” is now three characters wide, with the final column blank.
  • The R” was extensively modified. As well as the blocks and blanks described above:
    • the 9 (cross) became 7 (cross), due to the code change of the cross custom character.
    • The lower 3 (LL) became a 5 (LR) due to stylistic preference only.
    • The upper right character is ugly, and 2 (RT) or 5 (LR) may be used. I used a 2 (RT) – either is ugly
  • The “S” was modified as described above, with the block character changing for a 0 (LT)

Additional custom characters

I have added A, B, D, K, L, O, P, T

Resetting the EEPROM values

A EEPROM reset feature was added

Diagnostics

There are four diagnostic programs:

  • DAC and voltages of battery and FET
  • Actual and Stored DAC and voltage of current measuring resistance
  • Actual and Stored DAC and voltage of FET
  • Actual and Stored DAC and voltage of Battery

These can be selected for display whilst the mod box is vaping

Additionally it is possible to read the stored values of the voltages of:

  • battery,
  • FET, and
  • current measuring resistance
  •  across the coil

and the power in the coil can be seen in the settings section of the UI.

Revisiting Power Control

The power control simply works out, from the required power, and the given resistance, what the voltage needs to be. That is it. There is not any power adjustment code (as yet)

Revisiting Temperature Control

From the previously provided links the formula for the vaping coils temperature is simple enough:

TempOfHotCoil = RoomTemp + (ResistanceWhenHot - ResistanceWhenCold)/(ResistanceWhenCold*TCR)

However, once you have that information, what do you do with it, and how can you use it to control, and maintain, the coil temperature?

From Coding Temperature Control,

float startR = 0.0; # starting resistance
float currentR = 0.0; # current resistance
float TCR = 0.006; # the Temperature Coefficient of Resistance
float currentTemp = 0;
float targetTemp = 200.0;
int buttonPressed = 0;
while(1) {
buttonPressed = check_if_button_active();
if (buttonPressed) {
 currentR = read_resistance();
 if (startR == 0 || startR  > currentR) {
    startR = currentR;
 }
 currentTemp = (startR - currentR) / ( TCR * startR);
 if (currentTemp < targetTemp) {
     turn_power_on();
 } else {
     turn_power_off();
 }
 buttonPressed = check_if_button_active();
}
turn_power_off();
}

That is the pseudo code, and is more or less the same as I had designed, except that I had omitted the resistance at cold in the denominator. However, I am using PWM, so en lieu of turning off the power, I simply reduce the output to the PWM pin by 1.

There is still the issue of what is the starting voltage used to trigger the NFET? Too high and the temperature will be immediately surpassed, and the coil may burn out before the output voltage is decreased. Too low and the temperature amy take a while to ramp up (although this delay will be short but if may be significant nevertheless – more than 500 mS and the human begins to think that the vaper has died).

The problem with desiredVoltage

The original code had a variable called desiredVoltage. However the name of this variable was a bit of a misnomer as desiredVoltage does not hold a voltage, but rather a PWM encoded value, for the DAC. It would be more aptly named DACVoltage, or, better still, DACValue.

Also, it is declared as a global. However, it is only used in the loop() function, and more specifically in the voltage/power/temperature control sections/functions. There is no sharing of its value across functions, as it is recalculated within each section/function, so it could, nay, should be declared as a local variable.

Moving code out of loop()

loop() was becoming rather busy, so I farmed the program display code into its own separate function.

The voltage/power/temperature control code was also moved into their own isolated functions. This helped isolate variables used, clarified the code and (hopefully) redefined the usage of desiredVoltage from an, unnecessarily, global variable to having more of a local scope.

Improved display

I added the ability to use a 1.44″ 128×128 colour display, en lieu of the 1602 LCD display.

This new display adds scope for more information to be displayed, as well as offering a more crisp display and some pointless colour graphics.

I used the TFT_ILI9163C library, by sumotoy, see A library for ILI9163C displays for Teensy, Arduino, ESP82266 and more… I also extended the TFT_ILI9163C library, see TFT_ILI9163C_Extended_Char. The extension was a simple affair, to provide compatibility with the setCursor() method used by the 1602 LCD display. Indeed, it should be only necessary to change only the class declaration, used for the lcd object – no further code changes should be required.

However, even though I have added the ability to use a more funky looking display, moving forward, I will attempt to maintain backwards compatibility with the original 1602 LCD display.

Admittedly this particular display is not well supported and not as ubiquitous as others, but it was the only other display that I had to hand.

3D printed case

I will build a 3D printed case. There are a number of designs out there:

Initial EEPROM values

There is the EE_Presets() function,  however, I have noticed that on a new Arduino Mega the EEPROM value of EE_programAddress was 255 and therefore the initial default was invalid. I fixed this for EE_programAddress in displayProgram():

if (EEPROM.read(EE_programAddress)>=numProgs) // if numProgs is out of range, on a freshly installed machine (bug fix)
EEPROM.write(EE_programAddress,0); // set it to zero

However, other EEPROM held values may also need initialising. So a initial call to EE_Presets() is required, but with checks on each EE value to check for out of range. As it is not possible for the code to tell from a brand new Arduino and a rebooted Arduino, these checks will be a bit hit and miss. If we check all of the EEPROM values, and any are out of range, then it is a good bet that we have a brand new Arduino that requires initialising. However, not all of the EEPROMs have top limits, these settings can legitimately range from 0-255. :

  • power, EE_powerAddress
  • resistance, EE_resistanceAddress
  • temperature, EE_temperatureAddress
  • current measure voltage, EE_currentMeasureAddress
  • coil voltage, EE_voltageAddress
  • coil voltage drop, EE_coilVoltageDropAddress
  • battery voltage drop EE_batteryVoltageDropAddress

Only the following can be used for an “out of range” check:

  • Program EE_programAddress
  • DefaultsSure, EE_defaultsSureAddress
  • CONTROL_TYPE, EE_controlTypeAddress
  • DEFAULTS, EE_defaultsAddress
  • material, EE_materialAddress
  • temperature units, EE_temperatureUnitsAddress
  • program voltage, drop EE_programVoltageDropAddress

Added checkOutOfRange() and called from setup().

Clearing the LCD

There was too much flashing of the display, especially noticeable in the TFT, so, I moved the constant LCD.clear() to only when the state button, S2, was clicked. Dynamically displayed values are cleared by rows of spaces on the value line only, the title is no longer cleared and then refreshed.

speedRead() issues

speedRead() had a couple of issues:

  • speedRead() only prints on the top line, and this makes for not a very legible message display
  • speedRead() prints random characters, on the second line, if the speedMessage[] string is not terminated with ‘\0’.
  • some words are not centered on line 0, when scrolled

I modified speedRead() to print on both lines and to scroll the message over the two lines. I then added further modifications so that it would scroll over a custom defined number of lines so that it would work over 16 lines, for the TFT. I ended up with two or three versions of speedRead(): speedRead(), speedRead2() and speedRead3().

mButton

A method needs to be added/modified to mButton library, in order to make buttons to be configurable to be either pull up or pull down. It seems that the best way forward is to derive an inherited object from the original mButton code, which is in the bicycle project by Mark Fickett and make the changes there.

displayProgram()

In displayProgram(), the FRESH and JUICE display cases do not exit, due to the while(true). This will inhibit the power and temperature control. The do-while(true) statements were deleted.

Problem with TFT and blanks

speedRead3() did not work as expected on the TFT.

It seems as if spaces aren’t actually printed, but rather they are ignored. Or more precisely, only the set pixels in a character are set by the print(), and println(), statements. The unset, or blank, pixels are not actually unset, but rather left as they were, prior to the print() statement.

Looking at my snippet below, I would have expected the “Hello World!” message to have been blanked out by the following print statement, which prints spaces. However, it does not:

tft.setCursor(29, 63);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.println("Hello World!");
tft.setCursor(29, 63);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.println(" "); // blank out Hello World!

Similarly, if one substitutes the space for any other character, the print statement will superimpose, not overwrite, whatever is already written there. For example if you substitute

tft.println(" ");

for

tft.println("HHHHH");

Then “H” will be superimposed upon, yet not overwrite, the “HelloWorld”. This behaviour I can sort of understand, but I had hoped that a space would clear the pixels for the pixels at that character position, as defined by the setCursor(). Is there a setting, that I am missing, to toggle between superimpose and overwrite mode, or is this how it is supposed to work?

I don’t want to call clearScreen(), or fillScreen(), as it leads to too much flashing of the display, and I was hoping that I could just write blanks first to clear a part of the screen, or better still, just overwrite existing pixels, with the new pixels of the new character.

————–

Here is the complete sketch:

// From http://henrysbench.capnfatz.com/henrys-bench/arduino-displays/arduino-1-44-in-spi-tft-display-tutorial/
// Henry's Bench
// 1.44" 128 * 128 SPI V1.1 Display Tutorial
//
// Filename: LCD1.44_128*128_HelloWorld.ino
//
// Uses RESET pin 8, not 3.3V

#include 
#include 
#include 

 

// Definition of WHITE
#define WHITE 0xFFFF

/*
Your Connections to an Uno (Through a Level Shifter)

LED to 3.3V
SCK to D13
SDA to D11
A0 to D8
RST to D9
CS to D10
GND to GND
VCC to 3.3V
*/

 

/*
#define CS 10
#define DC 9

 

// Declare an instance of the ILI9163
//TFT_ILI9163C tft = TFT_ILI9163C(CS, DC);
TFT_ILI9163C tft = TFT_ILI9163C(CS, 8, DC);
*/

// All wiring required, for TFT_ILI9163C, only 3 defines for hardware SPI on 328P
#define __DC 9
#define __CS 10
// MOSI --> (SDA) --> D11
#define __RST 12
// SCLK --> (SCK) --> D13

TFT_ILI9163C tft = TFT_ILI9163C(__CS, __DC, __RST);

void setup() {
tft.begin();
tft.fillScreen();
}

void loop(){
testText();
delay(500);
}

unsigned long testText() {

tft.setCursor(29, 63);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.println("Hello World!");
tft.setCursor(29, 63);
tft.setTextColor(BLACK);
tft.setTextSize(1);
tft.println(" ");
// tft.println("Hello World!");
}

The solution to this problem can be found here, Refreshing/overwriting display (1.8″ TFT) – flashes#6:

// line 439 of Adafruit_GFX.cpp
void Adafruit_GFX::setTextColor(uint16_t c, uint16_t b)

Simple!

However, this slowed the printing down considerably, especially, if the entire previous line is blanked. So to speed up the display, only the printed characters were blanked, rather than the entire line.

Revisiting Low Voltage Protection

Using the MOSFET

In an unregulated mod, the low voltage protection can be achieved, quite simply without the need for a Zener diode, by selecting an NFET, which is used for firing the coil, with a VGS threshold of just a little lower than your desired low voltage cut off point. Max or Min??

From LOW VOLTAGE PROTECTION / CUTOFF,

If you want a more positive decline then you can select a MOSFET with a Gate (Threshold Voltage Max) a bit lower than you want your lowest battery voltage to be under load, as performance drops off quickly when you start getting around the Gate (TH Max) voltage or lower-i hope that makes sense.

If you are using high discharge Lipo’s I would suggest a FET with Gate TH Max 3.0v I am currently using 2 FET’s in my little box with 4 25A Lipo’s these- Buy MOSFET Transistors IRF3711PBF, N-channel MOSFET Transistor, 110 A 20 V, 3-Pin TO-220AB International Rectifier IRF3711PBF online from RS for next day delivery.

and

the mosfet has a much lower loss than a zener diode so is the best option.

Now, from GENUINE IRLB3813 MOSFET • 15kΩ DALE RESISTOR • BETTER THAN USUAL FET FOR BOX MOD:

Why are IRLB3813 MOSFETS better than IRLB3034 MOSFETS?

1. They’re less likely to burn out
2. They switch on way faster

First, let’s take a look at the gate to source voltage (Vgs). Having a lower Vgs gives you a wider margin between operate and damage levels. I’ve personally drained batteries down to 2.4V accidentally, at which point they would not even fire a coil. This is VERY VERY BAD. This is how you break a MOSFET and vent your batteries at the same time. CORRECTION – This is how you break an IRLB3034 which has a Vgs of 2.5V. Voltage at the gate of an IRLB3813 is 1.9V making it very difficult to damage with IMR batteries.

So, if this is true then you need a NFET with a min (not max) VGS of just less than your low voltage cutoff, so for a single 18650 cell, that would be 3.2 to 3.7 (which neither the 3813 nor the 3034 provide). Therefore the use of Zener diode is preferable.

Using a Zener diode

See also Low voltage protection in unregulated MOSFET box?, for adding a Zener to this schematic. Differing opinions:

It doesn’t work, you’re basically turning the mosfet into an fancy expensive resistor that will kill itself quickly as batteries die.

and

Put it in before the lead leaving fuses to the top of the push button. You’ll need a different zener thats 3.2v I think is what you drain those batteries to tops. The one in the okr is around 6.9v

Like wise, Using a Zener for low voltage cutoff with a mosfet switch? also states that the use of a Zener turns the FET into a variable resistor:

The problem you’ll run into is you’ll get into the active region of the MOSFET so it won’t actually act like a switch, it will act like more like a variable resistor.

You need to provide a logic signal to the MOSFET (on or off).  You could do it with an arrangement of transistors, but with the component count, it would actually be easier to use a voltage detector.  It’s a simple 3 wire chip that provides an on or off signal depending on voltage level.  It makes for a very simple circuit, simply a voltage detector and MOSFET, no other components.

Here are some that will work with a single battery;  http://www.onsemi.com/PowerSolutions/product.do?id=NCP300

I’ve not found ones that go up to the 6V you need for dual batts, but it should be possible to find one with some digging around.

However these are using Zener diodes in parallel with the FET but I am considering using it in series, as per Tinkering with the OKL2-T20 20A 110W dc/dc converter….

However, using a Zener diode either in series or in parallel is a non-starter. See Help a noob with how to wire under-voltage protection in a parallel unregulated mosfet box?

You can’t put it on the load line because it will blow the diode. You can’t put it on the gate line because it can situationally cause the MOSFET to not fully gate, which will make it act like a resistor instead of a switch which will cause it to heat up and burn out.

and

From BreakTru

“You can not simply use a Zener diode on the gate pin of a MOSFET to provide a low voltage cut-out. This is actually the third time I’ve seen this idea, don’t understand why people keep coming up with it. You have to provide a digital voltage level to the MOSFET gate, either on or off. A Zener diode is an analog part that is not able to generate a digital voltage level. It ramps current up and down with voltage. Using one in a divider on the MOSFET gate will put the transistor in its active region. The active region of a transistor is where it behaves more like a variable resistor than a switch. Remember the primary function of a transistor is to provide a variable current source. Though MOSFETs are designed to be used as a switch, they still have this active region where they behave like a traditional transistor. It’s possible to use a Zener with an array or transistors to generate a digital signal. You can use a comparator chip to do that more simply. The most simple way is with a voltage detector which is a chip designed exactly for that purpose.”

Again a voltage comparator circuit is recommended instead (see Using a voltage comparator below)

Comparator with variable resistor
Comparator with variable resistor

Again, see NCP300 below, the NCP300LSN30T1G is recommended, connected to the gate of the NFET:

This is what I use for single cells. Check data sheet for the pinout. You only need the three pins on one side. I wire the ground pin to source on the NFET. The one in the middle goes to Vin, the other pin is the output and is wired to one side of your switch, the other side of switch to the gate on the fet. Use a resistor from gate to source as normal. The current draw from the chip is negligible. If I can find the diagram I have I’ll post it up. If you go to the breaktru forum and search low voltage cut off you can probably find it there. Those voltage detectors are available in all kinds of different values. They provide the logic signals to trigger the gate on the fet and can be had active low for use with a nfet, or active high for a pfet. Word of warning these things are tiny, you either have to make a host board for them, or I just solder leads to the pins which is really tricky. If you solder leads on bed them in epoxy so they don’t snap off.

NCP300

However, in the above quote, from Using a Zener for low voltage cutoff with a mosfet switch?, Craig recommends a voltage detector IC, NCP300, such as V DETECTOR, 3VTH, CMOS, ACT LW, 5TSOP Part # ON SEMICONDUCTOR NCP300LSN30T1G, £1.77:

MOSFET mod with voltage protection
MOSFET mod with voltage protection

That should work, but I would probably put the switch inline with the reset output and NMOS gate.  It’s not a problem to leave the voltage detector powered full time.  The current demand is very low for that part, only about 150nA as in billionths of an Amp.  Though the specs say 250nA, I’ve found that part actually draws closer to 150nA in using it myself.  That’s a negligible power consumption that will have virtually zero effect on battery drain.

There’s a benefit in leaving the part powered all the time in that you’ll be able to take advantage of it’s “hysteresis”.  That’s a 150mV gap between enable and disable depending on whether there’s a rise or fall in voltage.  It keeps the part from cycling from one state to another on the verge of its detection.  When it has to power up every time, you won’t get the hysteresis.

Also, in that configuration you would need to “debounce” the switch to filter the noise a switch creates when the contacts open and close.  It’s not a big deal to do that, just a cap and a resistor, but still you would need that since it could cause the detector to operate unreliably.

If the detector is powered full time, debouncing the switch is not required, though it would still be a good idea to put a 1uF MLCC cap as close as possible to the detector’s input pin and ground.  It may not be required, but it’s still good practice to do that on the power supply for any chip.  It cleans up the power supply to the chip an amount to keep it operating reliably.

Using a voltage comparator

TLV3012

From Circuit to protect against undervoltage? Use a TLV3012, which has the reference voltage built-in, to drive a PFET, COMPARATOR, VOLT REF, SMD, SC-70-6 Part # TEXAS INSTRUMENTS TLV3012AIDCKT, £3.48

Voltage comparator
Voltage comparator

So, this would be the schematic

Voltage comparator schematic
Voltage comparator schematic

See Stack Exchange, Low voltage protection for a LiPo. Note choice of PFET RDS(On) must be << 10% of the coil’s Rcold or << 1% of the coil’s Rhot so FET does not Vape with juice. The same PFET as was used by the reverse current protection is suitable, the SUP75P03-07-E3.

MCP112-315

From To protect a LiPO cell from undervoltage, how low current is low enough?, it is possible to use a voltage monitor, such as the MCP112-315. This does. pretty much, the same job as the comparator above, but in one package and which cuts off at 3.0 V (the MCP112-300 cuts off at 2.93 V), in conjunction with a load switch FDC6331L (but which only supports a continuous current of 2.8 A).

MCP112-315 and Fairchild PCD6331L
MCP112-315 and Fairchild PCD6331L

A load switch that handles 30A or more is required. See RS: Intelligent power switches.

On eBay, Microchip MCP112-315E/TO, Voltage Supervisor, 3.003 → 3.157 V, TO-92 3-Pin, £6.63 for 20 pcs

MCP130-315

Another voltage monitor, MCP130-315, on eBay MCP130-315FI Integrated Circuit – CASE: TO92, £5.89

MCP101-315

MCP100/101 datasheet. Cut off 3 – 3.15 V

Active high (MCP100-315 active low)

On eBay:

RS, Microchip MCP101-315DI/TO, Processor Supervisor 3.07V, TO-92 3-Pin £5.06, minimium 20 pcs (£0.253 each)

MCP1317

See also Trouble understanding MCP111 Micropower Voltage Detector,

MCP1317T with PFET
MCP1317T with PFET

Unfortunately the PFET only handles 480 mA.

Issues to remember

  • Is the comparator, or voltage monitor, active low or active high, when the battery voltage falls below the desired cut off point? From Using a Zener for low voltage cutoff with a mosfet switch?
    • You want the correct logic for the detector, you have the choice of active low or active high.  For an N-channel you need active low, for an P-channel, you need active high.

  • The FET VGS needs to be small, within the range of the comparator’s output
  • The FETs current capability needs to be large, around 30 A
  • Using two FETs, an n- and p-channel, in conjunction allows a small active low input to drive a large load current, as per the internals of PCD6331L.
  • RDS needs to be much less than the coil resistance

General notes about p- and n-channel FETs

  • Turning on and off (see P-Channel MOSFET Basics and Effect of gate voltage on current). You need a:
    • high gate voltage to turn on an enhancement mode NFET, therefore an active low voltage comparator output to turn off an enhancement mode NFET
    • low gate voltage to turn on a depletion mode NFET, therefore an active high voltage comparator output to turn off a depletion mode NFET
    • high gate voltage to turn on an enhancement mode PFET, therefore an active low voltage comparator output to turn off an enhancement mode PFET.
    • low gate voltage to turn on a deletion mode PFET, therefore an active high voltage comparator output to turn off a depletion mode PFET.
  • An enhancement mode NFET is generally denoted by a positive VGS(threshold), whereas an enhancement mode PFET is generally denoted by a negative VGS(threshold). The opposite polarities apply for depletion mode FETs. See Depletion and enhancement modes. So, the SUB75N03-07 and SUP75P03-07-E3 are both enhancement mode FETs.

So, using a NFET, SUB75N03-07, which has a VGS(threshold) = 2V, therefore it is an enhancement mode FET, as it has a positive VGS(threshold), so an active low MCP112 will stop the current when the battery level falls. However, as the FET is, effectively, in series with the coil and the firing NFET, will the voltage be sufficient to keep it open, and what is the voltage relative to? Certainly not ground, as the firing NFET will be closed and the source(?) will be left floating. So, maybe a PFET has to be used so that the Gate voltage is relative to the positive supply and a active high output is required?

Using a RC battery monitor

See Make a Battery Protection Circuit (low Voltage Cut-off)

Simple version:

RC low voltage alarm and cutoff - no switch
RC low voltage alarm and cutoff – no switch

with a switch

RC low voltage alarm and cutoff
RC low voltage alarm and cutoff

Parts:

How to Make a Battery Protection Circuit (over-discharge protection)

Adding a parallel charger

Using a TP4056

The TP4056 has an under voltage protection circuit built in using a ML8205A NFET pair with connected drains (VGS = 2.5V) and a DW01A, with voltage protection of 2.4V, see New TP4056 Lithium Cell Charger Module with Battery Protection and Appropriate charging current for parallel 18650 lithium cells. The only problems are that

  • a connected load could fool the TP4056 to constantly charging the LiPo, and hence an endless floating charge.
  • the cut off voltage is advertised at 2.5V. However, it’s quite possible that V= 2.5V is an appropriate cutoff for a battery with Voc(min) = 3.0V, if IRinternal ~ 0.5V. See also Safe Lithium Battery Management.
  • Current draw, through the ML8205A is limited to , way below the 30A required

I purchased Hot Micro USB 5V 1A 18650 Lithium Battery Charging Board Charger Module 2PCS, C $0.56, Approximately £0.34

The case, lid and magnets

From IPV2 50 watt Box mod… mod using magnets! (magnet door mod) place magnets of opposing polarities for case lid, so that it can only be replaced one way round.

Adding the 0.91″ display

See also 0.91″ SSD1306

I first tried to get it to work with speedread() in debug mode

I had to sprinkle throughout the various functions (setup(), loop(), speedread() and displayProgram()) test prints to trace it, to see why it was not displaying anything.

The first problem that I encountered was the incorrect class construction –  ‘F’ not ‘I’ (U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C)

then secondButton was floating, so I set to the same as DFRobot

then running it revealed that the labels were being printed in the wrong place, due to the characters being written above the cursor.

I had to override setCursor() so that it moves by character size, not pixel

managed to get the positioning of the speedread() text correct (more or less – further tweaking will be required – what is the width of the characters?

not over writing with blanks – set font mode (setFontMode() to non transparent u8g2reference/setfontmode and find a non transparent font: u8g2_font_6x10_mf (nice font, u8g2_font_ncenB14_tr, doesn’t have non transparent version). Font list: single font files. See also Font Groups.

speedread() looks good using the off screen buffer, with a sendBuffer() at the end of the for loop.

Problem with standard settings changes, button pushing not displaying, need to use sendBuffer() constantly, for and after each print statement, or after each *set* of print statements ( however it is too intrusive to have to sprinkle sendBuffer() all through the code – sendBuffer() needs to be centralised, but where to put it? Solutions:
– put sendBuffer() at button press, but not good enough, not good timing,
– need to override print()

How to override print()? see :: How to Use the Print Class ::, which helps a bit (also interesting Understanding how methods and pointers inside a function workWhat does the F() do exactly? and printing a double variable)

overriding print(char[]) enables display of labels, (taken from Arduino – Print.h and Arduino – Print.cpp and add sendBuffer() to overridden print(char[]) function)
but no values are printed – need to override the double, int, long, etc., print statements

override remaining print functions, again taken from Arduino – Print.h and Arduino – Print.cpp and add sendBuffer() to each overridden function

However, now speedread() looks jerky.

To workaround this issue, use a setPrintMode(boolean pushBuffer) function to set when you want to push the buffer or not

Can also apply this to the setCursor() different, setCursorMode(boolean useCharacterResolution), to switch between character and pixel resolution for the cursor

Can retrospectively apply this to the TFT_ILI9163C_Extended_Char

I had to override println(...) functions as well.

Soldering the 510 connector

How not to do it: Soldering a FatDaddyVapes 510 V3 Connector

The best way, MOD TIPS #03: Fat Daddy vs Varitube 510 Connectors

My cheap 510 connector, Hot Low Profile Spring Loaded 22mm 510 Battery Connector For Mechanical Mod tb US $1.25 (approximately £0.96), did not have a hollow center pin, so I had to precariously solder the lead to the flat end, at the bottom of the pin. Not easy, and not a secure connection, but eventually it seemed firm enough. Cold solder joints and a weak mechanical join are the main issues. It would be best in the long run to upgrade the 510 connector, to one of a known brand (FatDaddy or Varitube).

More fonts problems

For the new fonts of U8g2, the degree symbol code changed

search for omega symbol for resistance – tried various fonts, having trouble printing char(xxx) >256. Could change fonts, but font size between different fonts (at least fonts that contain the omega) vary, causing problems with consistent display. symbol font, u8g2_font_unifont_t_symbols, have both the degree and omega symbols.

Fixed reset default EE question logic

font sizes vary. therefore screen width & height varies, with respect to the number of characters possible. So if you change font, then speedscreen() stops working. Need to get font size, divide the screen size by font size for number of characters

coil temperature, missing colon for 16 character wide screen

It might be easier to just write “ohms”?

is it possible to find font size from fontfile? From u8g2_font.c:

  offset	bytes	description
  0		1		glyph_cnt		number of glyphs
  1		1		bbx_mode	0: proportional, 1: common height, 2: monospace, 3: multiple of 8
  2		1		bits_per_0	glyph rle parameter
  3		1		bits_per_1	glyph rle parameter
  4		1		bits_per_char_width		glyph rle parameter
  5		1		bits_per_char_height	glyph rle parameter
  6		1		bits_per_char_x		glyph rle parameter
  7		1		bits_per_char_y		glyph rle parameter
  8		1		bits_per_delta_x		glyph rle parameter
  9		1		max_char_width
  10		1		max_char_height
  11		1		x offset
  12		1		y offset (descent)

and

  /* offset 9 */
  font_info->max_char_width = u8g2_font_get_byte(font, 9);
  font_info->max_char_height = u8g2_font_get_byte(font, 10);

Compare font description and font file, width/height = 12/13 with 14/13
u8x8 and u8g2 font file formats look to be different, see u8x8_fonts.c for example font file formats. From u8g2/wiki

Only fonts allowed with fixed size per character (8×8 pixel).

How to determine size of 8×8 fonts? From file name, either by making note of which font is set, or is it possible to determine which font is selected (is there a getFontSelected()?). Or in the file of the U8x8 fonts, see u8x8_fonts.c , note the “…/7/7/… ” in the line:

const uint8_t u8x8_font_amstrad_cpc_extended_f[1794] U8X8_FONT_SECTION("u8x8_font_amstrad_cpc_extended_f") =   " \377\0\0\0\0\0\0\0\0\0\0\0__\0\0\0\0\7\7\0\7\7\0\0\24\34

From disableUTF8Print

“Description: Disables UTF8 support for the Arduino print function. This is also the default setting.”

From drawStr:

“Use drawUTF8 or drawGlyph to access glyphs with encoding greater or equal to 256.”

From enableUTF8Print

Description: Activates UTF8 support for the Arduino print function. When activated, unicode symbols are allowed for strings passed to the print function. Usually this function is called after begin():

void setup(void) {
  u8g2.begin();
  u8g2.enableUTF8Print(); // enable UTF8 support for the Arduino print()
}

Update:
setBitMapMode(0) is the effect I want. Only u8g2_font_6x10_mf this font behaves, w.r.t. the overwriting.

Also, spacing and position of the text in speedread4() is only good with u8g2_font_6x10_mf, as it is monospaced font – other fonts do not behave as expected, variable widths or too large characters do not work well.
Use drawGlyph to get the omega on the symbol font, u8g2_font_unifont_t_symbols.
However, the font u8g2_font_unifont_t_symbols is too large, at 16×16 – however from fntgrpx11#10×20, the font u8g2_font_9x15_t_symbols would be better. Font sizes w.r.t. characters on screen are as below (the display should not display less characters than a 1602 LCD display:

// For 0.91" 128x32
// TextSize = 6x10 gives display 20 characters wide, 3 characters high
// TextSize = 16x16 gives display 8 characters wide, 2 characters high
// TextSize = 8x16 gives display 16 characters wide, 2 characters high - ???

Transparency seems a bit hit and miss, see [How do I overwrite a character with another character, using a transparent font](https://github.com/olikraus/u8g2/issues/335). The upshot seems to be that one needs to use a ‘m’ or ‘h’ version of the font, but these do NOT contain an omega. May have to derive my own font. Need to select/try more fonts,
It is possible to determine the height of a character programmatically using getAscent()-getDescent()+1, and the width from the width of the underscore character – SSD1306_Extended_Char has been changed to do this. This removed the need to define the font used in both the .ino AND the SSD1306_Extended_Char class, and dispensed with the use of a separate config.h file (that would need to be read (or included) by the SSD1306_Extended_Char class).

Bug fix for the location of sendBuffer in speedreader4()

Looking at the symbols fonts, from:

The best font to use was u8g2_font_pxplusibmvga8_m_all, as it contained both omega and degree and was 8×16, which was the ideal size for a 128×32 screen, as this gives a character resolution of 16×2.

Vaping display diagnostic screen – flashing updates

To reduce the flashing of the screen on the diagnostic screens of the vaping display program, on the SSD1306 0.91″ display, I disabled the instant printing and used the screenBuffer, and then pushed the screen buffer to the screen once the data has fully populated the buffer.

Special characters for 1.44″ display using Adafruit GFX library

From A small program to display the font, the Omega symbol is 0xE9.

Character Map for Adafruit GFX library
Character Map for Adafruit GFX library

Memory issues

Using the TFT_ILI9163C library causes low memory issues on an Uno/Nano/Mini:

Build options changed, rebuilding all
Sketch uses 17282 bytes (56%) of program storage space. Maximum is 30720 bytes.
Global variables use 1574 bytes (76%) of dynamic memory, leaving 474 bytes for local variables. Maximum is 2048 bytes.
Low memory available, stability problems may occur.

Whilst using the U8g2 library is fine on an Arduino Mega 2560, using the U8g2 library causes out of memory issues on an Uno/Nano/Mini.

Build options changed, rebuilding all

Sketch uses 30272 bytes (93%) of program storage space. Maximum is 32256 bytes.
Global variables use 2696 bytes (131%) of dynamic memory, leaving -648 bytes for local variables. Maximum is 2048 bytes.
Not enough memory; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing your footprint.
Error compiling for board Arduino/Genuino Uno.

Obviously, this is problematic as an Arduino Mega will not fit into a Mod Box case. An alternative to U8g2 is needed in order for the code to fit on a small form factor Arduino.

Right justification of EEPROM readings

From Right justify, this modified u2s() function converts an integer to a right justified string:

/*============================================================================*/
/* Convert unsigned long value to d-digit decimal string in local buffer      */
/*============================================================================*/
char *u2s(unsigned long x,unsigned d)
{
  static char b[16];
   char *p;
   unsigned digits = 0;
   unsigned long t = x;

   do ++digits; while (t /= 10);
   // if (digits > d) d = digits; // uncomment to allow more digits than spec'd
   *(p = b + d) = '\0';
   do *--p = x % 10 + '0'; while (x /= 10);
   while (p != b) *--p = ' ';
   return b;
}

Right justification of decimal readings

To right justify decimal values, for voltage, power, temperature and resistance, dtostrf() can be used, see float to string function. However, this is not strictly necessary, as all float values, using specially selected variable limits, will have the same number of figures. This also means that less, or no, line clearing is required, as the values can be printed at the same locations, and will over-write the previously displayed value, thereby further reducing the flashing of the screen.

I implemented it a ultostr() and u2str(). A floating point version, ftostr() did not work initially, see below.

More flashing display reductions

The use of lcd.clear() was removed for almost all button clicks and holds, in the functions buttonWasClicked() and buttonWasHeld(), in the state machine. In the place oflcd.clear(), all lines now are padded out to 16 characters wide, with spaces.

More memory issues

As the lengths of all print strings have increased, this caused memory issues. So, all print strings have been placed into flash memory, with F(). This still did not help when compiling for the Arduino Uno, and basically anything smaller than the Mega

Wrote my own display library

As U8g2 was way too big and I hit memory restrictions when trying to compile it on a Uno (137% memory used)

I ended up writing my own library, which became an oddessy in its own right, see 0.91″ SSD1306.

Large characters

As the new library came with a couple of character sets, I realised that I could reinstate the BigChars.cpp which originally came with the first, original, Ghetto-Vaper-III version which were programmed into the 1602. Added to the smaller character set 5 x 8, but could be added to the larger 8 x 16 characters too.

Puff timer and counter

I added a puff timer function using millis() and a puff counter, which is basically the number of times that the Arduino has been turned on for vaping only,  i.e. whilst not looking at settings, and stored in EEPROM. Progress bar should be added too, for the puff timer function.

More right justification (floats this time)

As the puff timer can, currently range beyond 9, the number of characters can be greater than 4, i.e. less than 9 (“X.xx”), and over 9.99 (“XX.xx”), and so a right justified float print routine is required.

I finally managed to write a floating point to string function last night, on the Arduino. That was a bit of a head scratcher:

/*============================================================================*/
/* Convert unsigned long value to d-digit long string in local buffer         */
/*============================================================================*/
char *ul2s(unsigned long x, unsigned d)
{
   static char b[7];
   char *p;
   unsigned digits = 0;
   unsigned long t = x;

   do ++digits; while (t /= 10);
   // if (digits > d) d = digits; // uncomment to allow more digits than spec'd
   *(p = b + d) = '\0';
   do *--p = x % 10 + '0'; while (x /= 10);
   while (p != b) *--p = ' ';
   return b;
}

/*============================================================================*/
/* Convert float value to d-digit long string in local buffer                 */
/* d includes decimal place and preceding '0'                                 */
/*============================================================================*/
char *f2s(float x, unsigned d, unsigned dp)
{
  const int buffMaxSize = 7;
  static char b[buffMaxSize];
  char *p;   

  // Error checks
  if (d > buffMaxSize-2) return ("STL");  // Sanity check - "String Too Long" - d is more than maximum string length, plus terminating '\0' (plus preceeding '0'?)
  if (d < dp + 2) return ("STS");         // Sanity check - "String Too Short" - d is less than number of decimal places plus preceeding '0'
  if (dp < 1) return("NDP");              // Sanity check - "No Decimal Places" - must have at least one decimal place

  unsigned dp_count = dp;
  unsigned magnitude = pow(10, dp);
  unsigned long ul = x*magnitude;

  strcpy (b, ul2s(ul, d));
  *(p = b + d + 1) = '\0';    // shift end of string along by one, to give space for decimal place (this line is redundant, can subsume into next line)
  do *--p = *(p-1); while (dp_count--);  // shift all characters after decimal place along by one, to give space for decimal place
  *p = '.';  // insert decimal point
  if (ul<magnitude){              // need to add preceding 0s, both before AND after the decimal point
    *--p = '0';                   // put 0 preceding decimal point 0
    while (p++ < b + d)
      if (*p == ' ') *p = '0';    // replace any spaces after decimal point
      else if (*p != '.') break;  // stop incrementing p (and checking) after non '0' characters after the decimal point   
  }
  return b; 
}
I wrote the f2s() function, but I had found the ul2s() function in an Arduino forum, see Right justify.
ul2s() right justifies an unsigned long integer.  f2s() right justifies a floating point number for printing, by multiplying the decimal number by ten sufficient times, so that it becomes an integer and then calling ul2s() to get the right justified formatted integer, and then divides by ten sufficient times to get back to the original floating point number.
For example, you have the number 2.56 and want to right justify it in a string of 10 characters:
2.56      
f2s() multiplies it by 100, so that it becomes
256    
then ul2s() makes it
       256
and finally f2s() divides it by 100
     2.56
Might seem crazy but it is the most efficient way of doing it, I think. It came to me in a brainwave: as I already had a right justifying integer function, I just had to multiply the floating point by ten enough times, in order to make it an integer too and then convert it to a string, using the already existing integer to string function, ul2s(). The tricky part is then dividing the resulting string by 10, or 100, or whatever magnitude of ten you need, and then inserting the decimal point in the correct point (index) of the string.

Adding a third button

I found the use of just one button infuriating to scroll through the settings, as if one scrolled past the required setting, it was then necessary to scroll again, all of the way around. The addition of an optional third button, to allow scrolling in the opposite direction would make the user interface much more pleasurable to use.

Adding screen contrast

The ability to dim the screen

Adding screen lock

The ability to lock the screen

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s