DIY Charging a Craftsman C3 Lithium Ion battery pack

C3 Battery
To charge DieHard / Craftsman C3 lithium-ion battery packs apply 21V across the + and – terminals. (rounded end = +, opposite = -)

C3 Connector C3 batteries come in several variations and each one has different requirements to allow charging.   Most variants will need a 22K resistor between the left terminal (when looking with the curve away from you) and the + terminal.

Be sure to use a current limited supply to avoid the possibility of damaging your pack or starting a fire should the internal protection circuit fail.

My model was a 315.PP2025 (130211023) rated 19.2V, 48watt hours purchased in mid 2016 and required the 22K resistor between the left terminal and the + terminal to charge without cutting off after 30 seconds.

22K is red red black black brown for 1% 5 band resistors, and red red orange gold for 5% 4 band resistors.

C3 Circuit For charging in the house, I’ve got a 24V 0.5A wall wart (more of a brick) power supply and an LM317 regulator in a TO220 package.  I used three 10 ohm resistors in parallel to spread the heat out so that I could use 1/4 watt resistors without getting them too hot.  (The LM317 will get too hot to touch, be careful.)  The circuit is to the left and limits current to 375mA which, for my 48W pack, is a nice gentle 6 hours charge from empty and generates no heat in the pack that my IR thermometer can detect.  The LM317 will go up to 1.5A with a big heat sink and seven 5.6 ohm 1/4 watt resistors in parallel, but be careful to not charge packs that are below 5V at this high rate.  The 1.5A charger will fill the big 48W pack in 1 hour 40 minutes.

The 375mA charge rate is safe for even deeply discharged batteries that won’t charge in the Craftsman charger.

Speeding up the ADC on an Arduino ATMega 328P

ADC steps
courtesy Spinningspark at Wikipedia

This is a continuation of the performance enhancements for reading the ADC on Arduino.  Similar steps apply to many non Arduino/ATMega processors.

That other article is here: Arduino Library Functions & Macros and introduces startSample(), sampleDone(), and getSampleResult() as a way of avoiding blocking when doing an analogRead.  Here we will update the second two functions and introduce a new function: startFreeRunningADC().

This time we delve a little deeper into the ATMega328 and 168, we implement the free running mode and increase the clock rate to achieve 38,462 samples per second compared to the 8,966 samples per second that analogRead() can do.   Other than the example, none of this is Arduino dependent.  It works on any microcontroller in the same series as the ATMega328 and similar steps can be done on just about any microcontroller with a successive approximation ADC.

The first step to improve performance is to have the ADC operate in Free Running mode, constantly sampling and updating the ADC results registers.  The first conversion is started by calling startFreeRunningADC() from setup().  In this mode the ADC will perform successive conversions every 13 ADC clocks indefinitely instead of doing one conversion in 25 ADC clocks and then waiting to be restarted.  Note, the sampleDone() and getSampleResult() are also slightly updated.  This alone more than doubles performance.

The remaining step is to increase the ADC clock speed from Arduino’s default of 125KHz to 500KHz.  This has a side effect of reducing the sample time which reduces resolution.  To minimize the impact of the reduced sample time, avoid high value resistor dividers and use the lowest value that your signal source can accept, or buffer the signal with an opamp.  There is a compromise speed commented out if you’d prefer the accuracy of remaining close to the 200KHz required for best resolution.  With Free Running mode and a 500KHz ADC clock, the ADC runs at 38,462 samples per second.

Finally, not implemented here, you can trigger an interrupt on conversion complete to avoid polling for results as is implemented here.  In many interrupt driven programs, the ADC results are simply stored and then the stored result is polled, leading to no real improvements in speed.

Here are the functions:

#include "wiring_private.h"
#include "pins_arduino.h"

void startFreeRunningADC(uint8_t pin){
 ADMUX=(1<<REFS0)|((pin-14)&0x07); //select AVCC as reference and set MUX to pin
 ADCSRB = 0; //set free running mode
ADCSRA=(1<<ADEN)|(1<< ADATE)|(1<<ADPS2)|(1<<ADPS0); //set adc clock to 500Khz, enable, free running mode
//ADCSRA=(1<<ADEN)|(1<< ADATE)|(1<<ADPS2)|(1<<ADPS1); //set adc clock to 250Khz, enable, free running mode
 sbi(ADCSRA, ADSC); //start the ADC
}

inline uint8_t sampleDone(){
 // ADIF is set when the conversion finishes
 return bit_is_set(ADCSRA, ADIF);
}

inline uint16_t getSampleResult(){
 uint8_t low, high;
 low = ADCL; //make sure to read L value first
 high = ADCH;
 sbi(ADCSRA, ADIF); //clear conversion complete flag
 return (high << 8) | low;
}

Here is a sample program using these functions:

#define ADCport A4
void setup() {
  Serial.begin(230400); //the ADC is so fast that we need faster serial!
  startFreeRunningADC(ADCport); //free running mode, only needs to start once.
}

void loop() {
  if( sampleDone()){
    Serial.println(getSampleResult());
  }
}

OpenOCD for programming nRF51822 via nothing but wires and a Raspberry Pi

nRF51822 BoardThe Hardware here is simple and cheap, it can be done with any Pi, though I used a Pi Zero.  For me, it was free because I had the Pi Zero and the wire.

The default pins are:

Ground                        to Ground
3.3V power              to 3.3V power
Pi header pin 22  to swclk
Pi header pin 18   to swdio

At the time this was written, OpenOCD didn’t have bcm2835gpio in the main release so we git clone it.
The debugger/programmer install is:

cd ~/Downloads
sudo bash
apt-get install libtool libusb-dev libusb-1.0 autoconf automake texinfo
git clone git://git.code.sf.net/p/openocd/code openocd-code
cd openocd-code/
./bootstrap
./configure --enable-sysfsgpio --enable-bcm2835gpio
make
make install

The raspberrypi-native.cfg didn’t have SWD support, so add them from raspberrypi2-native.cfg.  I needed to comment out:

#bcm2835gpio_jtag_nums 11 25 10 9

and to add:

# Each of the SWD lines need a gpio number set: swclk swdio
# Header pin numbers: 22 18
bcm2835gpio_swd_nums 25 24

The peripheral base address is different on some Pi models.  You can check yours by grabbing the middle 8 digits from:

hexdump -s4 -n4 -e '"0x" 4/1 "%02X""\n"" "' /proc/device-tree/soc/ranges

Edit it with:

vi /usr/local/share/openocd/scripts/interface/raspberrypi-native.cfg

Check to be sure your default speed is 700Mhz, or adjust bcm2835gpio_speed_coeffs:

cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq

To run openocd:

openocd -f interface/raspberrypi-native.cfg -c "transport select swd; set WORKAREASIZE 0" -f target/nrf51.cfg

If it worked correctly, the last line should read (nothing before this indicates you successfully connected):

Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints

To connect from your development platform:

telnet pi0 4444

To burn the code, we need two files, in my case both are on a different machine (host) so I use sshfs to mount that.

mkdir ~/host
sshfs user@host:/ ~/host

Since the path is rather long, I break it in two and store into variables. This is the proximity example from the SDK:

set BASEPATH ~/host/home/user/Downloads/nRF5_SDK_11.0.0_89a8197
set SOFTDEV $BASEPATH/components/softdevice/s130/hex/s130_nrf51_2.0.0_softdevice.hex
set HEXFILE $BASEPATH/examples/ble_peripheral/ble_app_proximity/pca10028/s130/armgcc/_build/nrf51422_xxac_s130.hex
#halt, erase, write softdevice and binary, verify both, run it.
halt; nrf51 mass_erase; sleep 500; flash write_image $SOFTDEV 0; flash write_image $HEXFILE 0; verify_image $SOFTDEV 0; verify_image $HEXFILE 0; reset run

At this point, your sniffer or phone or whatever should see the Nordic_Prox device.

Arduino Library Functions & Macros

Logo

I think the one consensus about Arduino is that the build environment and the library suck.  That said, I use it.  This is some code I have written to try to make it a bit better for at least the limited things that I do with it.

For this post, I’m assuming you know how to use the Arduino IDE and how to program in it.  I make no effort to teach programming here and just present the code.

Diving right in with one of the worst: digitalWrite()

http://pastebin.com/dSRSxgax is a friend’s example of just how bad it is compared to harder to read but faster and smaller code.

My solution:  A macro that compiles down to an sbi or cbi.  All ATmega328 series boards should work, but I only tested on the Nano.  Comments with patches for other boards will be considered.

A quick look at the schematic shows the following ports:

  D0-D7:PORTD
  D8-D13:PORTB
  A0-A7:PORTC

For example:

  #define OutputPin A5
  digitalWrite(OutputPin, HIGH);

is far more efficient as:

  #define OutputPin A5
  PORTC |= (1<<OutputPin-A0);

The following are macros for both read and write that do the above but work for any pin and also include a pinMode() replacement: (not my code)

#define digitalPinToPortReg(P) (((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC))
#define digitalPinToPINReg(P) (((P) >= 0 && (P) <= 7) ? &PIND : (((P) >= 8 && (P) <= 13) ? &PINB : &PINC))
#define digitalPinToBit(P) (((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14))
#define digitalPinToDDRReg(P) (((P) >= 0 && (P) <= 7) ? &DDRD : (((P) >= 8 && (P) <= 13) ? &DDRB : &DDRC))
#define pinModeFast(P, V) bitWrite(*digitalPinToDDRReg(P), digitalPinToBit(P), (V))
#define digitalWriteFast(P, V) bitWrite(*digitalPinToPortReg(P), digitalPinToBit(P), (V))
#define digitalReadFast(P) bitRead(*digitalPinToPINReg(P), digitalPinToBit(P))

This simple to use set of macros assumes PWM and timers are not in use on the related pins and it will fail to compile if the pin (P) isn’t a constant. Variables of type const will not work, #define does work.
These are all things the programmer can keep in mind while writing their code and are hardly a burden.  If not all of the above apply, use the bigger, slower digitalWrite() instead.


Simplified Level Shifting is also possible.  The usual voltage divider or transistor to write to a 3.3V device requires at least two components and in the case of the voltage divider, will create a poor quality signal.  The method described here only requires a single resistor.
There is a caveat, you must burn the code before connecting the hardware and if you make a mistake in your code, you can damage your hardware.
The trick here is that to output a HIGH to a 3.3V device we actually set the port to INPUT and use an external 4.7K pull-up resistor to 3.3V.  To output a low, the pin is set to LOW and the pinMode is set to OUTPUT.

DO NOT use pinModeFast(pin, OUTPUT) or pinMode(pin, OUTPUT) with this function.

Assuming D2 is and output and D3 is an input, the hardware would be as shown:
The code to handle simplified level shifting is:
Schematic

#define digitalWriteLevelShift(P, V) (V) ? bitClear(*digitalPinToDDRReg(P), digitalPinToBit(P)) : (bitClear(*digitalPinToPortReg(P),digitalPinToBit(P));bitSet(*digitalPinToDDRReg(P), digitalPinToBit(P)))

This macro depends on the macros from the section above.  To use it, do nothing in setup(), do not use the usual pinMode(pin, OUTPUT); call.  Simply use digitalWriteLevelShift(pin,HIGH); or digitalWriteLevelShift(pin,LOW);.  As with the other functions, V can be a variable but pin (P) must meet all the restrictions for the other macros above.  For P, variables of type const will not work, #define does work.


An even bigger speedup can come from improving the way the ADC is read.  With default settings, the Arduino library sits doing nothing for about 110uS while waiting for the ADC to convert, that’s a very large chunk of time that could be used to do other things.

Courtesy Spinningspark at Wikipedia
Courtesy Spinningspark at Wikipedia

This one isn’t just limited to the Arduino library, the majority of code I’ve seen for all chips is done the same way.  There is another option:

Split the ADC read into 3 parts, Start, Poll, and Read.  To use it, you start a conversion, then continue doing whatever else you want, and poll whenever convenient to see if it’s done.  When the polling says it’s done, you read the result.  Usually you’ll start the first conversion in setup() and after each result, you’ll start the next reading.  The overall improvement is a bit hard to quantify but the startSample() call takes 2.1uS, the sampleDone() check takes 1.7uS and the getSampleResult() call takes 1.8uS leaving your code more than 100uS more time per ADC read to do calculations or to speed it’s main loop.  This might allow you to get your response latency for other things down to an acceptable level without resorting to interrupts and thereby simplify your system.  If you actually need the ADC to do more conversions per second, see part 2 of this article (referenced below the code.)

#include "wiring_private.h"
#include "pins_arduino.h"

void startSample(uint8_t pin){
  uint8_t analog_reference = DEFAULT;
  #if defined(analogPinToChannel)
    #if defined(__AVR_ATmega32U4__)
      if (pin >= 18) pin -= 18; // allow for channel or pin numbers
    #endif
    pin = analogPinToChannel(pin);
  #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    if (pin >= 54) pin -= 54; // allow for channel or pin numbers
    #elif defined(__AVR_ATmega32U4__)
      if (pin >= 18) pin -= 18; // allow for channel or pin numbers
    #elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
      if (pin >= 24) pin -= 24; // allow for channel or pin numbers
    #else
      if (pin >= 14) pin -= 14; // allow for channel or pin numbers
  #endif
  #if defined(ADCSRB) && defined(MUX5)
    // the MUX5 bit of ADCSRB selects whether we're reading from channels
    // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
    ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
  #endif
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits). this also sets ADLAR (left-adjust result)
  // to 0 (the default).
  #if defined(ADMUX)
    ADMUX = (analog_reference << 6) | (pin & 0x07);
  #endif
  // without a delay, we seem to read from the wrong channel
  //delay(1);
  #if defined(ADCSRA) && defined(ADCL)
    // start the conversion
    sbi(ADCSRA, ADSC);
  #endif
}
uint8_t sampleDone(){
  // ADSC is cleared when the conversion finishes
  return bit_is_clear(ADCSRA, ADSC);
}
uint16_t getSampleResult(){
  uint8_t low, high;
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read. reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  #if defined(ADCSRA) && defined(ADCL)
    low = ADCL;
    high = ADCH;
  #else
    // we dont have an ADC, return 0
    low = 0;
    high = 0;
  #endif
  // combine the two bytes
  return (high << 8) | low;
}

Further improvements to ADC performance are in my post: Speeding up the ADC on an Arduino ATMega 328P


Next up is code for doing linear conversions and scaling.

This is similar to Arduino’s:

long map(long x, long in_min, long in_max, long out_min, long out_max)

The version presented here  doesn’t simply throw longer integers at the problem and it avoids doing everything with slower 32 bit math.

scale(): Effectively this is the same as a units conversion equation with support for different zero points such as the celsius conversion described at http://oakroadsystems.com/math/convert.htm
The equation is arranged as below to reduce lost precision from using integers by doing the division as late as possible.  The simple form should be familiar from 9th grade chemistry class.

The simple form of this equation is:

or solved for returnValue:

EXAMPLE 1: Fahrenheit to Celsius in integer

#define Extrapolate
int16_t c = scale(f,32,212,0,100);

//c=((70-212)*(0-100))/(32-212)+100 //70F randomly chosen
//c=(-142*-100)/-180+100
//c=14200/-180+100 //notice 14200 is quite large, that is the reason for the 32bit cast.
//c=-78+100
//c=22 //notice the error due to integer math, 21.111… is more accurate but to get that would require slower math.

EXAMPLE 2: Fahrenheit to Celsius in fixed point math (100ths of F -> 100ths of C)

#define Extrapolate
int16_t c = scale(f,3200,21200,0,10000);

//c=((7000-21200)*(0-10000))/(3200-21200)+10000 //70.00F randomly chosen
//c=(-14200*-10000)/-18000+10000
//c=142000000/-18000+10000 //notice 142,000,000 is quite large, that is the reason for the 32bit cast.
//c=-7888+10000
//c=2112 //21.12C is still a bit off but is much closer and runs without using much slower floating point math.

EXAMPLE 3:  Time correcting with linear iterpolation

GraphWhen correlating two sample sets, you often find that the offset time indexes don’t line up correctly.  If the data is compatible, often you can graph the samples on Y with time on X and draw a line between the closest two points, then find the intersection with the offset time of the sample in the other data set.  The image to the right shows an example group of data points from two sample sets graphed against time.  The 3 black points are the ones we’re using.

The process could also be used to take virtual samples at specific offset times, creating approximate data with regular intervals out of measured data at irregular intervals.

redVirtualSample=scale(blueSampleTime[2],redSampleTime[2],redSampleTime[3],redSample[2],redSample[3]);

If the samples are:

Red[1] 19,72  Blue[1] 33,79
Red[2] 52,83  Blue[2] 66,90
Red[3] 89,93  Blue[3] 96,91
Red[3] 118,89

Then the above becomes:

redVirtualSample=scale(66,52,89,83,93);
redVirtualSample=((66-89)*(83-93))/(52-89)+93
redVirtualSample=(-23*-10)/-37+93
redVirtualSample=230/-37+93
redVirtualSample=86

Looking at the graph, we can visually observe that this is about right.

Without further ado, here is the code:

int16_t scale( int16_t inputValue,
               int16_t inputRangeOne,
               int16_t inputRangeTwo,
               int16_t outputRangeOne,
               int16_t outputRangeTwo){
//#define Extrapolate //define if values outside of inputRangeOne to inputRangeTwo are permitted
#ifndef Extrapolate
//special case for input at end of range or out of range.
// equation supports inverting (higher inputs result in lower outputs) so 
// detect that first to allow propper out/end of range detection
  if(inputRangeOne>inputRangeTwo){ //not input inverted
    if(inputValue>=inputRangeOne) return outputRangeOne;
    if(inputValue<=inputRangeTwo) return outputRangeTwo;
  }else{ //input inverted
    if(inputValue<=inputRangeOne) return outputRangeOne;
    if(inputValue>=inputRangeTwo) return outputRangeTwo;
  }
#endif
  //main equation as described above
  return ((int32_t)((int16_t)inputValue-inputRangeTwo)*((int16_t)outputRangeOne-outputRangeTwo))/(inputRangeOne-inputRangeTwo)+outputRangeTwo;
}

More functions and macros will be added to this post over time.

“the breadboard image above was created with Fritzing”

ESP8266-01 Programming with an Arduino Nano

ESP8266-01

Programming the ESP8266-01 can have a bit of a steep learning curve.  If you have a Nano and an -01, this just might be your blog.

The approach presented here does away with the need to buy an extra  FTDI, CP2102, or CH340G serial adapter just to program your ESP8266.

When you’re done, you’ll have the ability to use the Nano as a programmer and the example program will give you a transparent wifi to serial gateway that you can use to communicate with your future Nano projects.

I’m going to assume some fairly minimal experience.  You need to be able to read resistor color codes or at least be willing to look them up.  I’ll assume you understand voltage dividers.  You need to be able to wire a breadboard and use a meter.

The tools you’ll need:
Arduino 1.6.4 IDE (or later)
Arduino Core for ESP8266
A Breadboard & a few jumper wires;  M-M and M-F types
A voltage meter that is accurate to 3 digits
An assortment of resistors
A single capacitor between 1uF and 10uF
An Arduino Nano clone
An LDO regulator like an LM317t or a DC-DC buck converter
A DC power source between 6.5V and 40V
If your supply is over about 15V, you’ll need a heat sink or the DC-DC buck converter

I picked a CH340G variant of the Nano because they’re cheap, it’s not FTDI and the drivers have been built into Linux for a long time.  Windows users might have more pain between incompatible FTDI clones, Russian sourced CH340G drivers, or scarce cp2102 based Nano boards.

Get your Nano working in the IDE first, if it doesn’t work then neither will this.  The code in your Nano doesn’t matter much as long as it works, I used the Blink example so that I could see visually if the Nano was running or being held in reset.

[Soothing music plays while you Google for another blog to help you get your Nano programmed for the first time]

LM317t pinout Next, we tackle power for the ESP8266-01.  It needs 3.0V to 3.6V and about 250mA of current.
We’ll be using an external power source because the Nano receives power from USB through a diode and the diodes used by cheap Nano clones like the one I have are 0.7V forward voltage leaving only 4.3V for the ATmega328p and the “+5V” pin.  My buck converter and my LDO couldn’t handle that low a voltage.
I picked an LM318t and a 12v battery that I had sitting here and I chose 3.40V in hopes of minimizing the level conversion headaches.  Wire your power supply at the far right of your breadboard.  The circuit is 12V to Vin, Vout to the +rail, a 2.2K resistor between Vout and Adj, and a 3.9K resistor to the ground rail.  The schematic is: LM317t schematic I took my handy multi-meter and measured the output…  3.65V, way too high, so some adjustments needed.  The LM317 formula said it should have been 3.47V so I was about .2V high.  .2V works out to about .4K lower on the 3.9K resistor so a parallel 33K resistor comes up about right.  I added the resistor and took another measurement, 3.40V dead on.  I picked the whole thing up and brought it to my nice bench meter, 3.3999V, not too shabby.
LM317t schematic The adjusted schematic is to the left.  Bottom line, each approximately 15k in parallel with the resistor to ground will reduce the voltage by .1V.  Try to get the voltage within .05V.  Note, some regulators or supplies don’t work well with no load, put 220 ohms across the output while taking this measurement and most are happy.  Add the 1uF to 10uF capacitor across the output and ground to keep things stable.

Now that there is power, the next thing to deal with is the level shift beteewn 5V the Nano and the ESP which is 3.3V.  The solution is easy, http://jamesreubenknowles.com/level-shifting-stragety-experments-1741 says there are lots of options but programming is a rare task so power loss doesn’t matter, a good strong signal and simplicity are the priorities.  A resistor divider with low value resistors meets the goal.  If you achieved 3.4V in the prior step and have a cheap Nano with 4.3V on the “5V” pin, then the resistor divider values work out to 330 ohms between the Nano’s RX pin and the -01’s RX pin and 1200 ohms from the -01’s RX to ground and results in 3.37V on the -01’s RX pin.  If you didn’t achieve 3.4V on the power supply, you may need to adjust the resistors here instead.   Note, we really are connecting the RX pin to the RX pin and not crossing them like you usually would.  That’s because we’re not talking to the Nano, but actually talking to the CH340G.

The TX pin on the Nano goes directly to the TX pin on the -01.

Connect a wire from the Nano’s RST pin to ground.  This will permanently hold the ATmega328p in reset and prevent it from bothering the RX and TX pins what we’re using to talk with the CH340G.

Breadboard Image

Lastly, we need to wire up the rest of the pins on the -01.  The main points are to connect power, connect GPIO0 to ground to request programming mode, CHPD to 3.4V, and reset to 3.4V through a small resistor.  This resistor should be over 300 ohms and under 33K ohms, but is not critical otherwise, use 2.2K if convenient.  The complete breadboard that I used is to the right.

Now that we have hardware, install the ESP8266 Core using the instructions at https://github.com/esp8266/Arduino#installing-with-boards-manager

The promised serial gateway program is basically just a slightly patched version of the example WiFiTelnetToSerial.  I adjusted the sbuf allocation and the port but other than that I only removed all the serial messages.  Given that not much was done, I’ll go straight to the code:

/*
  WifiSerialGateway
  Based entirely on the WiFiTelnetToSerial Example 

  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
  This file is part of the ESP8266WiFi library for Arduino environment.
 
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include <ESP8266WiFi.h> 

//how many clients should be able to telnet to this ESP8266
#define MAX_SRV_CLIENTS 1

#define TCP_PORT 2167 //raw-serial
#define MAX_BUFF 32
const char* ssid = "********";
const char* password = "************";

WiFiServer server(TCP_PORT);
WiFiClient serverClients[MAX_SRV_CLIENTS];

void setup() {
  WiFi.begin(ssid, password);
  uint8_t i = 0;
  while (WiFi.status() != WL_CONNECTED && i++ < 20) delay(500);
  if(i == 21){
    while(1) delay(500);
  }
  //start UART and the server
  Serial.begin(115200);
  server.begin();
  server.setNoDelay(true);
}

void loop() {
  uint8_t i;
  size_t bufsize = MAX_BUFF;
  uint8_t sbuf[bufsize];
  //check if there are any new clients
  if (server.hasClient()){
    for(i = 0; i < MAX_SRV_CLIENTS; i++){
      //find free/disconnected spot
      if (!serverClients[i] || !serverClients[i].connected()){
        if(serverClients[i]) serverClients[i].stop();
        serverClients[i] = server.available();
        continue;
      }
    }
    //no free/disconnected spot so reject
    WiFiClient serverClient = server.available();
    serverClient.stop();
  }
  //check clients for data
  for(i = 0; i < MAX_SRV_CLIENTS; i++){
    if (serverClients[i] && serverClients[i].connected()){
      if(serverClients[i].available()&&Serial.availableForWrite()){
        //get data from the telnet client and push it to the UART
        while(serverClients[i].available()) Serial.write(serverClients[i].read());
      }
    }
  }
  //check UART for data
  if(Serial.available()){
    size_t len = Serial.available();
    if(len>MAX_BUFF) len=MAX_BUFF;
    Serial.readBytes(sbuf, len);
    //push UART data to all connected telnet clients
    for(i = 0; i < MAX_SRV_CLIENTS; i++){
      if (serverClients[i] && serverClients[i].connected()){
        serverClients[i].write(sbuf, len);
        delay(1);
      }
    }
  }
}

To use this code, paste it into the IDE, edit the SSID and Password, choose board type “Generic ESP8266 Module”, plug in your Nano’s USB cable, and choose the appropriate USB port.

To enter programming mode, we add just one more jumper wire from reset to ground.  To program, you connect this wire, then press the “Upload” Button Image button in the Arduino IDE, wait for the white text to stop scrolling, then pull the wire.  The timing is tricky, for me I wait about 1 second after the white text stops but try different delays until you find one that works for you.  I still only succeed about half the time so don’t feel bad if it takes some trial and error to get the timing right.  Once you have something programmed, remove the GPIO0 wire and reset again to let it run.

Using your new Serial Gateway with your Nano project, you may have to adjust the voltage divider on the -01’s RX pin to match the voltage on the “+5V” pin when installed in your project.  Use the Raltron.com voltage divider calculator (link below) to find small resistors that provide just a hair under the -01’s voltage when the input voltage is whatever you measure on the Nano’s “+5V” pin.  The sum of the two resistors added together should be between about 1K and 2K so that you get a nice clean signal.  If you’re using a battery or are power sensitive, consider one of the other level shift options over at jamesreubenknowles.com or the links in his Addendum, this one is power hungry.  Make sure the level shifter or divider stays on the -01’s RX pin.  Since we’ll be connecting to the Nano and not the CH340G, you now cross the RX and TX pins like usual and since we’re using those pins, nothing can be connected to the USB data pins.  Note most chargers short the data pins and that may activate the USB chip which could blow up either the USB chip or the -01.  If you do decide to try a USB charger, at least put a 1K resistor on the -01’s TX line to give it a chance should your USB chip be activated.  Talking to it is as simple as opening a terminal and entering:

nc ipaddress 2167

 

References:

http://www.reuk.co.uk/LM317-Voltage-Calculator.htm
http://www.1728.org/resistrs.htm
http://www.raltron.com/cust/tools/voltage_divider.asp
http://lushprojects.com/circuitjs/circuitjs.html
http://www.digikey.com/en/resources/conversion-calculators/conversion-calculator-resistor-color-code-5-band
“the breadboard image above was created with Fritzing”
http://jamesreubenknowles.com/level-shifting-stragety-experments-1741
https://github.com/esp8266/Arduino#installing-with-boards-manager