About two years ago I purchase a home dehydrator, I was always willing to make my own dehydrated fruits, herbs and why not jerky too. After using it for a while I perceived that the device was very dumb, it took more effort than I was willing to do on a regular basics to dehydrate food, specially the thing cannot be leave working for long time without human intervention as t easily over dry or even burn the food. Sometimes I found that high content water food such as bananas or pine apple dry best with a high temperature for a couple of hours, then temperature should be settle lower for a long period of time. Many people on the internet have found better to dehydrate overnight, which makes the above situation not possible. Anyway last but not least because I am sort of nerd about taking things apart, I was anxious to modify my dehydrator to improve the whole process.
Arduino Fits Here Very WellI start working with a little Arduino module, the Mini, it happen that there is an official Arduino Mini 05 which is ideally the one I should be using but the little thing is somehow difficult to find so I did my tests using Arduino Pro which is pin compatible module. This little Arduino is well suited for the project since it goes well in price compared to my $70 dehydrator, a more capable Arduino can speed up things but it was not going to be the right choice, basically because I was also hoping to make this solution available for public. I end up taking the difficult road, solving some problems the best way in favor of budget but with consideration to make the solution attractive to hobbyists as well as engineers out there.
About the SolutionApart from using Arduino, the project has several parts that were addressed one after the other, from basci to more complicated and layer by layer. The Arduino Mini is a little device which is very powerful but it has some limitations, specially in terms of connectivity. The first problem I face was about setting up a development environment, remember this is a dehydrator and the way to test it is by putting it to work (with or without food but with trays and everything in place). Here is a picture of the Turbo Dehydrator made by Ronco.
The idea of normal Arduino development flow, that is, write some sketch, load onto Arduino using serial port, see results and repeat as necessary and the idea that I need the Arduino circuitry inside above thing was generating a lot of noise in my head even before I start working seriously on the project.But not only that, at that time I was also thinking on how I suppose to make the whole thing smarter. One thing was clear to me, I want everything on the cloud, been that my local home network or the internet. How I was going to connect my little Arduino to the local network. I came up with the idea to use the ESP8266 for that. The main reason is the price and community support for it. But wait a second dude, did you said Arduino Mini with ESP8266? If you have work with this Arduino module, you will fast realize it has only one hardware serial port, so at the end I cannot put "the circuit" inside the dehydrator and forget about it since I need to connect the Arduino IDE using serial port to program Arduino as well as ESP8266. This is exactly one thing I solve for this project, to phrase it."I can put both things inside the dehydrator, close it and develop Arduino code as well as ESP8266 code in an over the air fashion mode, even with dehydrator sitting on my kitchen"I separate the next two problems in different stages, the control involves playing literally with fire, I really don't like too much mains levels so I decide to leave it to the end when I got a robust setup. I decide to work on the acquisition first, which is not only acquire sensor signal but also present them to user, that way I can make my code robust before attempting to play with fire kid.The original dehydrator sensor/control solution is merely a mechanical thermostat attached to a knob as seen in previous picture. Designers leave an small aperture from heat exhaust to direct some of that hot air goes directly to the thermostat, which is actually cleaver and perhaps I will go ahead now and let you know that temperature differences without food are minimal when measuring at the thermostat location and inside the trays area.
Notice the black cable on above picture? The picture is actually from my final setup (notice thermostat is disconnected), the cable is the temperature probe, I am using DS18B20 sensor, the one that comes with a wire already assembly. Apart from temperature inside the unit, which I put there as first attempt to reassemble original product, I was willing to measure also humidity which is a good indicator of food water content. Here I haven't come to what a "final solution" sensor should use, but for my test I pick up one I have at hand and actually very good but also pricey (bad for a product). The SHT15 sensor from sparkfun. One thing is for sure is that I need a digital sensor since I need to wire up the thing from the inside to the tray area. Below is a picture of how it was done.
Originally dehydrator has a plate and plastic cover that screw there using two screws, so I manually fabricate another plastic cover with a cylinder to have the SHT15 sensor sort of encapsulated. Below are some pictures that clarify it better.
I was afraid the heat can melt the wires, because those wires I got form my "electronic garbage." Actually the plastic enclosure and trays of dehydrated start to shown some damage for usage, specially after I did some jerky with temperature set at 70 Celsius for 5 hour :(In order to protect them I took some cooper tape I have at hand to help dissipate heat as shown below.
After I solve all mechanical and interface things, and as project evolve, eventually I end up developing the heater control. For that I used a TRIAC that Arduino used to phase control the current, the circuit uses isolation devices to meet UL safety standard of a typical cooking appliance. In order to control the fan I used a small relay. This allow me to turn fan on even without heater on, and actually turn the whole thing OFF which was an annoying lacking feature of this unit, even if the know said OFF to the most left position, there is no switch that turn thing off.Some of the circuit I wired using a breadboard (Arduino + PS + ESP-01). The AC power control board was soldered on a universal PCB board for this first prototype stage. The circuit takes power from the AC mains using a laminated transformer with rectifier bridge. Here is a picture of the complete setup.
Notice the usage of tie wraps to make the things fixed and safe! Actually I have use the unit as shown from them on until one day I finish the second prototype PCB board.A closer look will reveal details of the wiring, of course those will be addressed in next sections.
This section will cover the hardware as well as firmware of the Arduino.
SensorsTemperature is acquired using DS18B20 and SHT15 sensor. Both sensors are read continuously, depending on the sensor there are some constrains in how fast the sensor can be read, or better say how long it will take to have a sample ready for Arduino, DS18B20 is around 750ms and STH15 is faster been worse case a 14 bit accuracy sample around 320ms. The code can implement different times by adjusting a define in code but we keep both around 1 second.Both sensors has libraries available that we use on this project. At the time of this writing I have implemented a simple control using temperature from ds18b20 as the input, data from sht15 have serve only for visualization but eventually humidity will help to control the unit too.Here is the snippet code that is called in Arduino loop.
bool handle_dallas(int index){
if(millis() - lastTempUpdate > TEMP_READ_DELAY){
temperature = temperatureSensors.getTempCByIndex(index);
temperatureF = temperature * 1.8 + 32.0;
root[ds_json_tag] = double_with_n_digits(temperature, 1);
lastTempUpdate = millis();
temperatureSensors.setWaitForConversion(false);
temperatureSensors.requestTemperatures(); //request reading for next time
return true;
}
return false;
}
temperature variable is of type float. For SHT15 we are saving the value directly in a json variable, the json facilitate the different values transmission to upper layers as will be show next. Here is the snippet for SHT15.
void handle_sht15(void){
if(millis() - lastRHUpdate > RH_READ_DELAY){
// now get the sht15 sensor data
readSensor();
lastRHUpdate = millis();
if(tempC < -40.0){
sht15_json["error"] = "comms";
}else{
sht15_json["error"] = "noerr";
sht15_json[sht15_temp_tag] = double_with_n_digits(tempC, 1);
sht15_json[sht15_rh_tag] = double_with_n_digits(humidity, 1);
}
sht15_array.set(0, sht15_json);
}
}
The code uses the following libraries (show as includes )
#include <DallasTemperature.h>
#include <OneWire.h>
#include <SHT1X.h>
#include <ArduinoJson.h>
sht15_json is declared as
JsonObject& sht15_json = jsonBuffer.createObject();
ActuatorsThe two actuators are the Relay and the Triac. The relay is really simple and it only needs an Output pin to control. The breadboard version nonetheless uses an Arduino Pro Mini, specifically the 3.3V version, which has some problem to interface the standard 5V relay board, for that reason I decide to include a buffer follower P/N SN74LVC1G125DBVR. This can be avoid in a second prototype that we can specify the relay to use. The AC phase control is implemented in Arduino software, it need an input from zero crossing AC mains, which is acquire by means of an AC input Optocoupler PN H11AA1. The triac circuitry is implemented using an opto triac PN MOC3011 to control a larger Triac PN 2N6073BG, the schematic show the details, a better explanation about the circuit can be found here. AC phase control was somehow difficult to get it work reliable since the conduction angle range should be established, as noted on AN1003 application note from littlefuse, the conduction angle range should be 30° to 150°. Here is a reference table.
Instead of trying to calculate an exact conduction angle of 30° for example, I start from the known fact that at 60 Hz AC mains frequency, a conduction angle of 180° or π is something like 8.333 ms after the zero crossing detection. Then 30° would be π/6 or around 1.38 ms, 150° would be 5π/6 or 6.94 ms. With those values in mind you are ready to follow me in the Arduino code. The zero crossing signal from H11AA1 device is feed to an interrupt pin of Arduino, then we can start a timer to measure above times and send the trigger signal to opto triac. The code uses timer1 which is a 16 bit timer, here is a good explanation of how to configure it. It's important to note that the code is for 3.3V part which is running at 8 MHz. Basically you do this
- Configure Timer prescaler to 256. At 8 MHz that would give 8000000 / 256 = 31250
- Divide 31250 through desired frequency, in this case we can image our frequency can be given by a period equal to above times of t30 and t150.
- For 30° that would be 31250 * 0.00138 = 43
- For 150° that would be 31250 * 0.00694 = 216
The Timer is configured during each zero crossing ISR and the reason of this is that we want to also produce a short trigger pulse, which means we need to set the output low some time after the trigger pulse, so the triac will self turn off in the next crossing detection, and the process will repeat.Here is the code at Zero crossing ISR
void zeroCrossingInterrupt(){ // zero cross detect
cli();
if(heater_en){
OCR1A = tc_value;
// turn on CTC mode:
TCCR1B |= (1 << WGM12);
// Set CS10 for 256 prescaler
TCCR1B |= (1 << CS12);
TIMSK1 = 0x03; //enable comparator A and overflow interrupts
TCNT1 = 0; //reset timer - count from zero
}
sei();
toggle = toggle ^ 1;
digitalWrite(11, toggle);
}
Notice the OCR1A value is a variable since we want to adjust it dynamically as necessary. The limits are defined in code like this
#define TC_OUT_MIN 40 // Minimum Value "97% AC Phase" Control.#define TC_OUT_MAX 220 // Maximum Value "3% AC Phase" Control.
This way we can avoid going out of this limits. The ISR of Timer1 would be
ISR(TIMER1_COMPA_vect){ //comparator match
digitalWrite(GATE,HIGH); //set TRIAC gate to high
TCNT1 = 65536-PULSE; //trigger pulse width
}
ISR(TIMER1_OVF_vect){ //timer1 overflow
digitalWrite(GATE,LOW); //turn off TRIAC gate
TCCR1B = 0x00; //disable timer stopd unintended triggers
}
In the case where the count match our previously programmed value TIMER1_COMPA_vect ISR will fire, we set the gate output pin high and then we set some predefined value to Timer count, which will cause the Timer to fire TIMER1_OVF_vect ISR when counter overflow, and there we low the pulse. The predefined value was something I really setup during tests but is something around 0.4 ms. Did you notice the use of D11 outout in zero crossing ISR, it's for debugging an square wave and see the gate pulse timing, you can see a couple of pictures below (I was lazy to use the USB snapshot feature of my scope...)
Not bad for a little Arduino. The firmware works well but I have experience some issues when going from maximum AC phase (150°) to a lower value, the problem is a lost of AC phase control, I have handle this on the controller part of the code explained later.
Arduino - ESP-01 InterfaceThe hardest part of the Arduino firmware was the AC phase control, but the interface with ESP-01 don't lag behind and actually without it this project could easily have become a nightmare. The reason for this is that I have handle to program the Arduino using ESP-01, that is, I can upload the.hex file remotely using the WiFi connection of ESP-01. Apart from the contribution of this to the Arduino community, the approach have several advantages to this project, among them:
- Arduino devices with only one serial port such as Mini 05 or Pro Mini can benefit from this, the serial port is used for programming and also for data transmission i.e. with a remote Host, in this case to transmit data values and configure device.
- I can develop the dehydrator unit assembled without messy wires, and because of this when I reach some stable production code I can flash the Arduino over the air, making this convenient even for a product release, in which you might offer upgrades, which is extremely important for Alexa activated products because as you might have experienced the API might change by i.e. a new skill capability addition, so you need the hardware adjust accordingly.
- As a consequence pf last point, I can develop faster, since apart from avoiding to reconnect Arduino to IDE to flash a firmware, I avoid reconnecting the serial interface between Arduino and esp-01 so I can go and do a test faster.
Technical Details of Arduino-ESP-01 Interface
I was looking for this feature since I started the project, then I found this very nice project which was really helpful. Nonetheless I wasn't happy with part of the author implementation because of the following: ESP-01 is running micropython.I was not willing to change MicroPython since that speed up things a lot here. So I decide to use part of the original work and port the rest to my esp-01 running MicroPython, and this was kind of painful since I got stuck in a bug while implemeting the STK500 protocol. This story end in a happy end so this is how it works.
- Hex file server.
The original work idea of having a server of Arduino hex files is preserved, the code has some little modifications, basically in the step that request the file transfer, which was somehow obscure to me and depends on some serial data that throw out at boot time the Arduino.
- micropython esp8266 client.
The client was implemented on MicroPython, hardwired values are used for IP and Port TCP server to connect with. You need to load this python script to running esp-01, then execute it by passing the name of the hex file to flash on arduino. The.hex file should be on same server working directory for this to work.For example, the command to Flash firmware hex file "AC_Phase.ino.hex" in a client once you are in a REPL shell is:arduino_programmer.run('AC_Phase.ino.hex')Here you can find the git code for server and client.
Arduino Temperature PID ControlThe control loop was implemented in arduino. It turns out that MicroPython on ESP8266 works well but I have found it has some problems with reliability, I have seen it to reboot sporadically and unexpectedly when running for long period of time. I decide to use MicroPython as a bridge for Arduino, it can parse back and forth data from an upper layer software such Alexa, it can speed up development things since it has good support and advance networking capabilities. Arduino proves to be better stable on this project, and is basically dedicated to control the temperature of dehydrator while keeping in sync with esp-01 for handling any configuration command such a change in set point for example. A PID controller is implemented with the help of an existing library for Arduino, the PID was tuned by trial and error, if you want to know more about it the author gives you some tutorial on using the library. I actually review other existing PID libraries and pick this one since it has the ability to change the PID tuning parameters dynamically, which I am using since when error value is high I want the PID to be more aggressive, and when output is close to setpoint I want the PID to change the output in a smooth manner. The code for that is shown below.
if(unit_on_off){
double gap = abs(Setpoint-temperature);
if(gap <= 2.0){
dehydratorPID.SetTunings(consKp, consKi, consKd);
}else{
dehydratorPID.SetTunings(aggKp, aggKi, aggKd);
}
dehydratorPID.Compute();
// ensure the Output of PID library is on allowed range
if((Output >= TC_OUT_MIN) && (Output <= TC_OUT_MAX)){
root["output"] = double_with_n_digits(Output, 1);
root["setpoint"] = double_with_n_digits(Setpoint, 1);
// Turn On heater when in AC phase control range
heater_en = true;
tc_value = Output;
one_time = true;
}else if(Output > TC_OUT_MAX){
heater_en = false;
if(one_time){
cli();
TCCR1A = 0; //timer control registers set for
TCCR1B = 0; //normal operation, timer disabled
digitalWrite(GATE,LOW); // set TRIAC gate to low
one_time = false;
digitalWrite(RELAY,HIGH); // set RELAY to OFF state
delay(200);
digitalWrite(RELAY,LOW); // set RELAY to OFF state
sei();
}
root["output"] = double_with_n_digits(Output, 1);
root["setpoint"] = double_with_n_digits(Setpoint, 1);
}else{
root["output"] = "error";
}
}
I am keeping a variable that indicates if the unit is ON or OFF first. Then the gap is computed to find which tuning to use. After that Compute() is called which calculate the Output. Notice we ensure Output is in the correct range, and the reason for doing this manually is because we are specifying the output limit range a little bigger using SetOutputLimits as this
// +1 something we will shutdown heater but leave air fan on
dehydratorPID.SetOutputLimits(TC_OUT_MIN, TC_OUT_MAX+1);
So why if there is a function that handle limits do I code my limits out of range? Well because I found that the lower limit range of triac firing actually don't turn off the unit completely and some current still heat the element, I have try several things to fix this such as removing the gate trigger completely which in theory should turn off the thing but anyway triac sometimes are black magic, in this case it seems trying to lower gate pulse only make triac to loose control, so if you know how this can be improved in hardware just let me know please.Returning back to our limits fix, The PID will try to set the output to lowest TC_OUT_MAX+1 value, specially when you change the setpoint and error gap is high like when going from 60° C to 40° C. When this is the case, I just turn the unit off and leave fan working which actually cool the thing faster. Remember I comment the lost of control of triac? Well I have to turn unit off and on quickly once in order to "reset" the triac and then from that moment don't apply again the gate pulse until PID output value is on the range values again. Since temperature setpoint will not change frequently this is really the best solution I found and actually work quit well.To initialize PID in void setup just do
dehydratorPID.SetSampleTime(PID_SAMPLE_TIME);
// +1 something we will shutdown heater but leave air fan on
dehydratorPID.SetOutputLimits(TC_OUT_MIN, TC_OUT_MAX+1);
dehydratorPID.SetMode(AUTOMATIC);
Arduino Serial Interface Protocol
The Arduino serial port is configured to receive in a loop using the SerialEvent function and a json parser is implemented. The string should end in a new line character for Arduino to process it as a new json command. Once a string is complete, the Arduino will parse it and look for several possible "keys" which are
- setpoint : The desired temperature.
- state: On or Off state the unit should be in.
- notify: whether or not should notify periodically to the upper layer(esp8266) the unit operation values such temperatury, humidity, output, etc.
The protocol is very simple and can easily supportmore commands, here is how it looks.
// process command from esp8266 WiFi Bridge
if (stringComplete) {
StaticJsonBuffer<300> jsonBuffer;
JsonObject& received = jsonBuffer.parseObject(inputString);
boolean saveCfg = false;
// Setpoint (Temperature in Integer value)
int sp = received["setpoint"];
if((sp >= TEMP_MIN) && (sp <= TEMP_MAX)){
Setpoint = sp*1.0;
cnfData.setpoint = (int)Setpoint;
saveCfg = true;
valid = true;
}else if(sp == 0){
// do nothing workaround
}else{
// turn off dehydrator if other value
digitalWrite(RELAY,HIGH); // set RELAY to OFF state
heater_en = false;
valid = false;
}
// ON/OFF State
const char* state = received["state"];
if(!strcmp(state, "on")){
send_ack = true;
if(valid){
unit_on_off = true;
digitalWrite(RELAY,LOW); // set RELAY to ON state
heater_en = true;
// remember we are ON state
cnfData.state = true; // next reboot will be ON
saveCfg = true;
root["reported"] = "on";
}else{
root["reported"] = "off";
}
}else if(!strcmp(state, "off")){
send_ack = true;
unit_on_off = false;
digitalWrite(RELAY,HIGH); // set RELAY to OFF state
heater_en = false;
cnfData.state = false; // next reboot will be OFF at start
saveCfg = true;
root["reported"] = "off";
}else{
// do nothing
}
if(saveCfg == true){
eeAddress = EEADDR;
EEPROM.put(eeAddress, cnfData);
}
// Notifications
const char* notify_cfg = received["notify"];
if(!strcmp(notify_cfg, "on")){
notify = true;
}else if(!strcmp(notify_cfg, "off")){
notify = false;
}else{
// do nothing
}
// clear the string:
inputString = "";
stringComplete = false;
}
Non Volatile MemoryThe Arduino Mini boards have an ATmega328P which has an EEPROM memory of 1024 bytes, this comes handy to this project. The reason to use an Eeprom arose at the time I was doing tests and notice that for any reason the unit can be disconnected momentarily (such a thing can happen in the kitchen more often than what you think). In those cases the esp8266 was slow at bringing to life since it has to connect to a network, even worse if for some reason the network is unavailable. To avoid shutting down and waiting for commands if already programmed, the unit just resume the last setting stored in eeprom.The code the save configuration values to eeprom is contained in the last section of serial reception, this little snippet
if(saveCfg == true){
eeAddress = EEADDR;
EEPROM.put(eeAddress, cnfData);
}
where cnfData is a variable of type configObj, an struct defined as
struct configObj{ int ver;
int setpoint; boolean state;};
The version integer element ver allow for resetting the parameters to default values on a firmware upgrade by doing this on setup function
EEPROM.get( eeAddress, cnfData );
if(cnfData.ver != VER){
cnfData.ver = VER;
cnfData.state = false;
cnfData.setpoint = 0;
}
This way we can change VER in code for every new version of the firmware.
MicroPython Firmware - ESP-01 (ESP8266)This section discuss details about MicroPython firmware.
Getting StartedThe ESP-01 is somehow limited for MicroPython development due to memory available, so you need at least the version with 1MB version which is probably the one with a black soldermask PCB. Other ESP8266 modules can be used but this one is very cheap (around 4$) and works well once you set it up correctly. It's 0.1" header is also convenient for prototyping. Another problem that need to be solve to make the ESP-01 work with MicroPython is that there is only one serial port which is by default used by the REPL prompt used to interact with MicroPython engine. So you need to disable it, the problem is that you need to patch original source code to open the serial port in a python script unavailable for the master version as far as I know. Don't worry I have attached the binary file already patched and ready to flash in your ESP-01, in order to flash it just make sure you connect a serial port (3.3V levels) to your ESP-01 and install esptool to your PC. Then execute the following commands.
esptool.py --port /dev/ttyUSB2 erase_flashesptool.py --port /dev/ttyUSB2 write_flash -fm dio --flash_size 8m 0 ./build/firmware-combined.bin
After that you should be able to open a serial terminal emulator which allow you to access the MicroPython REPL and configure network as explained here.The trick is to enable the webREPL as explained here or here. Then disable the serial REPL is done by replacing the boot.py file with this content.
# This file is executed on every boot (including wake-boot from deepsleep)
import gc
import webrepl
import esp
import time
esp.uart_nostdio(1)
webrepl.start()
The actual instruction which disable the serial REPL is esp.uart_nostdio(1), which is the function that adds the aforementioned patch.
Development Under MicroPython
The best way I found to interact with MicroPython webREPL is using mpfshell utility, it's convenient for developing stage since it allow to upload files and also interact with REPL.
Firmware
The micropython firmware do a couple of things. It runs a web socket server listening for commands from a host. Parse commands and send them to Arduino in json format using the serial port. I could have implemented an MQTT broker directly here but I really think to extend this project further, for example with the addition of a home controller with more processing power.The complete code (including the Arduino code) can be found here. Just want to showcase here how it works, first make sure you have all the sources are in place, then
The function that should be called once you import the code under REPL is called run(), using mpfshell invoke repl and proceed as follows
Then you can use any websocket tool to test. I am using web socket chrome extension.
The first part shows the command, after that it take some minutes to reach setpoint and if you not get bored or asleep first you can see how it keeps temperature within +-2 C of setpoint value, the notify command prints also the actual output of the PID controller, so you can verify it's working as expected.
Stylized 3D DesignThe unit is equipped with LCD and sensor done by means of a 3D printer.
The LCD mount assy is show below.
It looks in a good angle for user as shown below.
The sensor is mounted in a custom shell replacing the original setup. The part is printed in ABS for temperature withstanding,
The assembled unit shown below.
Comments