Quectel GPS module set-up tips

Probably you have seen one of those Quectel GPS modules working with Arduino. I have two of them, L96-M33 that I have bought for a GPS watch project and LG77L which is included in the GPS version of the LilyGO e-paper watch module.

In their Protocol Specification documents, you can find all the available commands to set up or control the module for your projects. Note that depending on what module you are using, not all the commands are available, but for those modules I have pretty similar sets of commands are used and I wanted to share some key commands here for those who are using these modules.

Back-up mode

The back-up mode is useful to save power for a portable device with batteries. In order to go into back-up mode by software, you need to pull the wakeup pin low first. Then send a command “$PMTK225,4*2F” via the serial port you opened for the GPS module. It looks like this in Arduino sketch.

digitalWrite(GPS_WAKEUP, LOW);
gpsPort.println("$PMTK225,4*2F"); // GPS backup mode

To wake the GPS module up, simply pull the wakeup pin high.

digitalWrite(GPS_WAKEUP, HIGH);

Stand-by mode

The stand-by mode draws a little bit more current than the back-up mode, but still draws much lower current than the continuous mode and can fix quickly. For the stand-by mode, you don’t need to pull the wakeup pin low. Just send this command.

gpsPort.println("$PMTK161,0*28"); // stand by mode

To go back to the continuous mode, send a single byte (any character). Here is an example.

gpsPort.println("a"); // "a" can be any character

Note that you need to wait at least 120ms before waking up the GPS from the stand-by mode.

Baud rate change

The default baud rate is 9600 bps, but you can change it to higher rate with PQBAUD command. Here are some example. For other baud rates, change the value and get the right checksum. To calculate the checksum, there are websites that calculates it for you. Here is a link that I’m using.

gpsPort.println("$PQBAUD,W,115200*43"); // set baud rate to 115200 bps
gpsPort.println("$PQBAUD,W,9600*4B"); // set baud rate to 9600 bps

Update rate change

The default update rate is 1Hz, but you can change it up to 10Hz. Here are a couple of example.

gpsPort.println("$PMTK220,100*2F"); // set position fix interval to 100ms (10Hz)
gpsPort.println("$PMTK220,200*2C"); // set position fix interval to 200ms (5Hz)
gpsPort.println("$PMTK220,1000*1F"); // 1Hz

Note that you will need faster baud rate for higher frequency, obviously.

Sharp memory LCD review

As I mentioned in my previous post, the e-paper displays have a problem under UV light. So I wanted to try another display and found Sharp memory LCD which draws very low current and is supposed to provide good contrast under ambient light. It’s not transparent, so backlight doesn’t work.

I bought a 1.26 inch LS013B7DH05 from Aliexpress and tested with a test sketch included in the Adafruit_SharpMem library. I was expecting to see brilliant contrast images and texts, but the result was quite disappointing. See below pictures. Only when the illuminated angle of the light source is the same as your viewing angle, you can see good contrast. In other words, the viewing angle is very limited. I tested outdoor as well and the result was not improved. As you can see, as you tilt the display, it gets dark quickly.

If you rotate the screen 90 degrees either CW or CCW, the viewing angle gets even narrower. Not good.

It requires 3kbytes of buffer, so ATmega328 based Arduino cannot drive this display. I used ESP32 and found another downside of this display. At high CPU frequency (default frequency of the ESP32 is 240MHz), say about 40MHz, the display shows noises. In my experiments, only at 10 and 20 MHz, it works fine. See below pictures.

There are many other models which may be better than this model. Actually there are many websites that introduce these memory LCDs as good display. Another downside of these LCDs is the price. It’s quite expensive, so I don’t want to buy another model and try.

So, I have resumed my research on finding a (simple) way to block the UV for the e-paper display. Any idea?

E-paper display problem under the sunlight

I already knew the e-paper (or e-ink) displays were not suitable under sunlight. So for those outdoor products, I think they attach protective a film that can block the UV light. Even on a cloudy day, there is still significant amount of UV light outdoor.

So, my plan was to attach a UV-stabilized acrylic sheet. However, it turned out that’s not enough. See below picture.

The black text got faded and it’s difficult to read. Disappointed.

I have ordered a screen protector film that was advertised as it can block 99% of UV light and still waiting for it. Unless it can actually block the UV light properly, I will need to find another solution for the display.

GPS watch: LilyGO E-paper watch GPS – hardware customization

The GPS version of the LilyGO E-paper watch (hereinafter referred to “watch”) has two switches, a vibration motor, an external GPS antenna connector, and an LED for the PPS indicator for the GPS.

Here is a summary of what I need and what I don’t need of those features.

  • Swtiches: From the past experiences, I know I need at least two switches for the menu and the selection inputs. The watch has two switches, but one of them is not connected to a GPIO pin, but is connected to the EN pin which is used to reset the ESP32 micro controller.
  • Vibration motor: It can be useful if there is enough space. As I want to make it as small and thin as possible, I have removed it.
  • GPS antenna connector: This is for an external antenna, but the antenna should be hidden inside the watch or wrist strap. I don’t want any bulky antenna dangling outside the watch.
  • LED indicator: The watch has an LED (on the back side of the PCB from the display, which is a weird location for an indicator) for PPS (pulse per second) of GPS: I don’t need it. I’d rather have an LED connected to a GPIO pin that I can control and on the same side as the display.

First, I took a look at the schematic, and noticed the PPS from the GPS is connected to the GPIO 19. As mentioned above, I don’t need the PPS, so decided to cut copper traces (red arrows in pictures below) on the PCB and connect the GPIO 19 to the switch. There are a few GPIO pins that are not connected to anything, but there is no copper traces for those pins so it’s more difficult to solder.

If you look at the schematic around the GPS module, Quectel LG77L, and compared to the GPS hardware manual, you can see they used an active antenna circuit which got power to the antenna to get amplified signal. However, with the antenna that came with the watch, I couldn’t get any fixed GPS signal even outside the house. So I contacted the seller on Aliexpress where I bought the watch, but they couldn’t give me a solution. I did some research and decided to try the passive mode. That is the RF-IN pin on the GPS module is connected to the antenna directly without any powered amplifier assistance. I had experienced with my previous watch with just a simple copper strip attached to the RF-IN pin actually work better than ceramic chip antenna.

I wanted to use an inverted-F antenna inside the watch housing and tried multiple locations and shape of the antenna and different location of the feed line and ground plane, but couldn’t get good signal

The picture below shows the latest design of the watch and the antenna. I have tested dozens of different shape and location (tried to put inside the watch, but couldn’t get good signal) of the antenna and this one is the best so far. I’m planning to put this strip between the NATO watch strip layers.

As you can see in the picture above, with the simple antenna, the GPS could see 18 satellites (max 20 of them I have ever seen) in view and good hdop inside my house. The watch was sitting on my coffee table, so the speed is 0m/s.

Finally, the LED modification. This watch has a connector for the optional backlight illumination, but I don’t need it, so I could use the backlight pin (GPIO 33 through an N-channel MOSFET) to control my added LED on the same side of the display. I noticed there is a circular translucent area around the edge of the display, so I placed the LED just under that spot.

GPS watches

Since 2018, I have made a few different versions of the GPS watches. I started making these watches for golfing at first, but later, I thought to myself, “Why not use this watch for a regular watch?” But the problem was the battery life and bulky form for the 1st version I made in 2018. See below picture for those different versions. From left, the first version in 2018, 2020 (never posted in my blog), and 2022 version that I’m currently working on.

The first and the second version uses ATmega328p and the 3rd one is that I bought from Aliexpress, Lilygo e-paper watch GPS version (u-controller: ESP32) and modified (there are so many things to talk about this one. I will post few more posts for this particular watch).

As you can see, for the first version, I used a prototype enclosure (35mm x 50mm x 15mm) and PA6C GPS module and 0.9″ OLED i2c display. Without using any sleep mode with 150mAh battery, it can work barely enough for 18 hole round if there is no delay. The display is tiny and it’s hard to read small numbers without reading glasses which I need, but can’t wear during golfing. The golf course data (coordinates and par) was stored in the flash memory, but ATmega328p has limited memory (32kbytes), so I could load only 7 or 8 course data. So, I started thinking to improve the watch: 1) bigger display; 2) SD card; 3) smaller GPS module; 4) bigger battery and 5) smaller package.

The version 2 made in 2020 (middle on in above picture) has most of these improvements except for the battery life during play. I used a 1.3″ IPS TFT display, micro-SD card slot, u-blox CAM-M8C GPS module, 200mAh battery, ATmega328p, and 3D printed housing (thanks to my son, Yuchan). It’s pretty small (39mm x 32mm x 10mm) for a regular wrist watch. With the low power deep sleep mode of the ATmega328p micro-controller and GPS module turned off (by MOSFET) and the display in sleep mode most of the time and waking up by pressing a button (interrupt) to show time for a few seconds, I could use the watch for over 3 weeks with one full charge of the battery. Pretty good, huh? I didn’t use a separate time module, but used the RTC in the GPS module (V_backup is directly powered by the battery) which worked pretty well in terms of accuracy for 3 weeks. However, for golfing, with the full load for the GPS, the 200mAh battery could last only 4 -5 hours. In order to save battery, the screen is off most of the time. When I push a button, it wakes up and show the yardage data for a few seconds. There is always some delay for waking up and processing the GPS data. Except for the short battery life, it worked pretty well with customized antenna (I will see if I get a chance to post a story about the antenna.) for fast fixing and good accuracy, so I have used it for 2 seasons until last fall. During long winter (I can still see snow in my backyard as of today.), I thought about upgrading the watch: 1) using e-paper for low current consumption and continuous display without sleeping; 2) better (larger flash memory and faster processing) micro-controller; and 3) even bigger battery.

I started collecting information on the micro-controller and the e-paper and also searching for existing diy (or open-source) watches and found open-source watch, watchy, and Lilygo ttgo T-watch. None of these looked satisfying. But I got an idea and bought a Lilygo ttgo T-watch v1 (v2 has the GPS but is more expensive :-), and I thought I could use the BLE (bluetooth low energy) in the watch (ESP32) and my phone to provide the GPS data over BLE to the watch. But as I posted before (HERE), the current consumption by the BLE was very disappointing. It is supposed to consume a lot less than normal bluetooth, but it drains the battery quite quickly. And then I found the same company Lilygo has e-paper version with optional GPS module which is perfect combination for me. I already purchased an e-paper (Gooddisplay, 1.54″, BW), so I bought only the assembled PCB through Aliexpress. It took some time to modify the circuit a little bit and to figure out the antenna issue, but I think I figured out all the hardware configuration and settings now. It will take some more time to port my code from previous version, but that’s fun part, right? My son printed the housing for me and it fits pretty well. The size of the new watch is 37mm x 44mm x 11mm. I have ordered NATO wrist strap and am still waiting for it. You might notice there is a short wire coming out of the watch with a metal strip attached to it. That’s the GPS antenna. Figuring out the antenna part will need another post, but that was very interesting experiments to tell.

That’s all for today. Leave your comments/questions below.

Thanks.

ESP-01 programmer with a modified FTDI breakout

In this post, I’m going to show a simple ESP-01 programmer which requires only 2 resistors and header sockets. Another part you need is an FTDI breakout board with a little modification. Let’s start with the FTDI modification. There is a pin called CTS among 6 pins on the FTDI breakout board, but this pin is not used for Arduino based programming. For ESP-01 programming, we need the RTS pin instead. The RTS pin is not on the 6 pin connector, but it’s accessible on the side of the board. So I cut the trace for the CTS pin from the back of the board and soldered a short solid copper wire from the RTS pin to the connector. See below picture.

Here is the ESP-01 programmer schematic.

And the assembled ESP-programmer look like these pictures.

In the Arduino IDE, simply choose a board, Generic ESP8266 Module, and upload your sketch.

Sump pump monitor using ESP-01, ATtiny85, piezo transducer, and Gmail sender [Updated]

I have used the sump pump monitor with the ultrasonic sensor (HC-SR04) and ESP-01 for years (LINK). It measures the water level in the sump pit and sends an email to my Gmail account regularly. It has worked fine.

Recently, I have installed a battery powered backup pump in cases of a main pump failure or power outages which had happened this summer a couple of times. Now the sump pit was quite crowded by the pipes so it was difficult to place the ultrasonic sensor. So, I started thinking a way to detect when the pump runs so that I can monitor whether the pump system is working fine or not at anytime, anywhere by checking the email.

I happen to have a small (10mm in diameter) piezo transducer which is often used as a buzzer. If you apply pressure, vibration, or any force on it, it generates electric potential and that’s how you can detect vibration. There are many applications using it, but in this case, the sensor is to detect the vibration on the drain pipe of the sump pump. When the pump is on and especially when the pump stops, the check valve suddenly blocks the water flow and it generates quite noticeable vibration with noise.

The sensor is attached on the drain pipe with a piece of tape right above the check valve.

In order to send an email, an ESP-01 module is used. It has the wifi capability but one downside of it is a poor ADC. So, I used an ATtiny85v to measure the analog signal from the piezo transducer and generate a pulse on one of the digital pin so that the ESP-01 can read the digital output from the ATtiny85v. I don’t have a wall adapter for 3.3V, but only for 5V, so I used LD1117V33 to get 3.3V.

Here is the schematic diagram of entire system.

I have modified the assembly from the previously used module, so it doesn’t look pretty, but here is a picture of the assembly mounted on a wooden frame close to the sump pit.

Now the software part. As you can see, both ESP-01 and ATtiny85v need to be programmed separately. First, the program for the ATtiny85v is shown as below. It’s pretty straight forward. It keeps monitoring the analog input from the piezo transducer and it pulls the digital pin 1 to HIGH when the analog signal goes over the threshold which you need to find a proper value.

int sensorPin = A2;    // pin 3, piezon input
int latchPin = 1;      // pin 6, latch output to ESP-01
int threshold = 28;

void setup() {
  pinMode(latchPin, OUTPUT);
  digitalWrite(latchPin, LOW);
}

void loop() {
  if (analogRead(sensorPin) >= threshold) {
    digitalWrite(latchPin, HIGH);
    delay(10000);
    digitalWrite(latchPin, LOW);
  }
}

The program for the ESP-01 is as follows. The Gmail sender library was taken from this LINK.

// Gmail sender: https://www.mischianti.org/2019/09/10/send-email-with-esp8266-and-arduino/

#include "Arduino.h"
#include <EMailSender.h>
#include <ESP8266WiFi.h>

uint8_t connection_state = 0;
uint16_t reconnect_interval = 10000;
const char* ssid = "your wifi SSID";
const char* password = "your wifi password";

EMailSender emailSend("your Gmail address", "your Gmail password");

int sensorPin = 2;
const long milsec_to_min = 60000;
int pumpIntervalMin;
unsigned long lastPumpOnMillis;
String subject, message, data;

uint8_t WiFiConnect(const char* nSSID = nullptr, const char* nPassword = nullptr)
{
  static uint16_t attempt = 0;
  if (nSSID) {
    WiFi.begin(nSSID, nPassword);
  }
  uint8_t i = 0;
  while (WiFi.status() != WL_CONNECTED && i++ < 50)
  {
    delay(200);
  }
  ++attempt;
  if (i == 51) {
    if (attempt % 2 == 0)
      return false;
  }
  return true;
}

void Awaits()
{
  uint32_t ts = millis();
  while (!connection_state)
  {
    delay(50);
    if (millis() > (ts + reconnect_interval) && !connection_state) {
      connection_state = WiFiConnect();
      ts = millis();
    }
  }
}

void setup()
{
  pinMode(sensorPin, INPUT);

  connection_state = WiFiConnect(ssid, password);
  if (!connection_state) // if not connected to WIFI
    Awaits();          // constantly trying to connect

  EMailSender::EMailMessage message;
  message.subject = "Sump pump monitor started";
  message.message = ""; // "1st line<br>2nd line";

  EMailSender::Response resp = emailSend.send("your Gmail address", message);
}

void loop() {
  EMailSender::EMailMessage message;
  message.message = "";
  if (digitalRead(sensorPin) == HIGH) {
    pumpIntervalMin = int((millis() - lastPumpOnMillis) / milsec_to_min);
    if (pumpIntervalMin != 0)
      message.subject = "===== Sump pump ON (" + String(pumpIntervalMin) + "min)";
    else
      message.subject = "======= Sump pump ON";
    EMailSender::Response resp = emailSend.send("your Gmail address", message);
    lastPumpOnMillis = millis();
    delay(60000);
  }
  delay(10);
}

Here is the captured image from my Gmail inbox. It shows when the pump runs and how long it took from the last run.

[Update, 11/13/2021]

I found that it either misses the pump run or detects falsely sometimes. Apparently, the reason why it misses the pump run is due to irregular pump vibration. The amplitude of the vibration by the pump cannot be the same every time. So I tried to lower the threshold but it increased the false detection cases. Since the piezo transducer picks up any vibration, it could response to anything that shakes the pipe through wall, floor, or some loud noise through vent.

So, I thought about better way to detect the pump run. Of course there are many other sensors to use, but I wanted to stick with the piezo transducer and improve the program. I did some experiments to find what can distinguish the pump run from other noise or vibration and found the other noise/vibration duration is relatively short. So, I changed the pulse width from ATtiny85v shorter and lowered the threshold a little bit. And then have the ESP-01 count the number of pulses within 1 minute. Usually the sump pump runs for about 30 seconds in my case. It depends on the pump capacity and sump pit size and the float switch height settings. After some trial, I found the pump generated over 150 counts of pulses (range of 150~300). It’s been working fine with better accuracy. I said ‘better’ because it’s only been a day since I changed the program and found no error yet.

Here is the updated ESP-01 program.

// https://www.mischianti.org/2019/09/10/send-email-with-esp8266-and-arduino/

#include "Arduino.h"
#include <EMailSender.h>
#include <ESP8266WiFi.h>

uint8_t connection_state = 0;
uint16_t reconnect_interval = 10000;
const char* ssid = "your wifi SSID";
const char* password = "your wifi password";

EMailSender emailSend("your Gmail address", "your Gmail password");

int sensorPin = 2;
const long milsec_to_min = 60000;
int pumpIntervalMin;
unsigned long lastPumpOnMillis, startMillis;
int count = 0;
String subj, data;
boolean trigger = false;

uint8_t WiFiConnect(const char* nSSID = nullptr, const char* nPassword = nullptr)
{
  static uint16_t attempt = 0;
  if (nSSID) {
    WiFi.begin(nSSID, nPassword);
  }
  uint8_t i = 0;
  while (WiFi.status() != WL_CONNECTED && i++ < 50)
  {
    delay(200);
  }
  ++attempt;
  if (i == 51) {
    if (attempt % 2 == 0)
      return false;
  }
  return true;
}

void Awaits()
{
  uint32_t ts = millis();
  while (!connection_state)
  {
    delay(50);
    if (millis() > (ts + reconnect_interval) && !connection_state) {
      connection_state = WiFiConnect();
      ts = millis();
    }
  }
}

void setup()
{
  pinMode(sensorPin, INPUT);

  connection_state = WiFiConnect(ssid, password);
  if (!connection_state) // if not connected to WIFI
    Awaits();          // constantly trying to connect

  EMailSender::EMailMessage message;
  message.subject = "Sump pump monitor started";
  message.message = "";

  EMailSender::Response resp = emailSend.send("your Gmail address", message);
}

void loop() {
  if (digitalRead(sensorPin) == HIGH) {
    if (!trigger) {
      startMillis = millis();
      trigger = true;
    }
    count++;
  }
  if (millis() - startMillis > 60000) {
    if (count > 150) {
      pumpIntervalMin = int((startMillis - lastPumpOnMillis) / milsec_to_min);
      lastPumpOnMillis = startMillis;
      subj = "===== Sump pump ON (" + String(pumpIntervalMin) + "min)";
      data = String(count);
      sendEmail(subj, data);
      data = "";
    }
    count = 0;
    trigger = false;
  }
  delay(10);
}

void sendEmail(String emailSubject, String emailMessage) {
  EMailSender::EMailMessage message;
  message.subject = emailSubject;
  message.message = emailMessage;
  EMailSender::Response resp = emailSend.send("your Gmail address", message);
}

And the program for the ATtiny85v.

int sensorPin = A2;    // pin 3, piezon input
int latchPin = 1;      // pin 6, latch output to ESP-01
int threshold = 20;    

void setup() {
  pinMode(latchPin, OUTPUT);
  digitalWrite(latchPin, LOW);
}

void loop() {
  if (analogRead(sensorPin) >= threshold) {
    digitalWrite(latchPin, HIGH);
    delay(100);    
    digitalWrite(latchPin, LOW);
  }
}

LilyGo T-watch 2020 v1 battery life with BLE

As I mentioned in previous post, I was planning to use this watch to communicate with my smartphone to get the GPS data. The microprocessor in the LilyGo T-watch is ESP32 which has not only wifi module but also BLE (bluetooth low energy). The BLE is supposed to consume about 100 times less than traditional bluetooth. That’s why I purchased it in the first place.

I tried those examples, BLE server and BLE client, with the watch and an ESP32 dev. board to see how long the battery lasts when they are connected to each other. In order to maximize the battery life, I changed the CPU frequency from the default 240MHz to 80MHz (the lowest frequency for wifi and bluetooth) and turned off wifi and put the screen in sleep mode.

It was very disappointing. The battery lasted only about 4.5 hours. If I turned on the screen and show some data on it, it would be much shorter than that.

I have searched internet how to fix this issue, but found so many people were frustrated by this non-sense.

Well, I don’t know yet what to do with this toy.

LilyGO T-Watch 2020 V1 LED replacement

Since I made the first golf GPS watch 3 years ago, I have made another one with Ublox CAM-M8C GPS module which is smaller than PA6C and 1.3″ IPS color LCD as shown below. But I haven’t posted the detail about it yet. It has a micro-SD slot, so I could load golf course coordinate data from my computer without re-flashing.

It’s pretty light with a 3D printed case and 200mA LiPO battery. It has two modes: watch and golf gps modes. With a deep sleep mode most of the time for the watch mode, the battery lasts almost 3 weeks. I have used this watch for the last two years daily as a regular watch and golf GPS. This one has 2 problems: 1) in golf GPS mode, the battery lasts barely enough for a 18 hole rounding, 4-5 hours; 2) The GPS response is slow and accuracy is not very good. This particular GPS module doesn’t work very well close to the human body. I tried to put it on my golf bag and it worked better, but I wanted to wear it on my wrist.

I was thinking to make another version with Bluetooth instead of a GPS module so that it can get the GPS data from my smart phone. And then I found this watch on internet, LilyGO TTGO T-Watch 2020. It has ESP32 with 1.54″ color LCD with touch screen. The ESP32 is amazing microcontroller which has a dual-core processor, huge memory compared to ATmega328p, fast clock speed, and equipped with a WiFi and a Bluetooth. This watch seemed a perfect solution for me. I can program it with the Arduino IDE. Also, it’s pretty cheap, less than $40. They also have another version, V2, with a GPS module, but it’s a little more expensive and I know how fast the battery will drain. So, I bought V1.

I’m still learning how to deal with the Bluetooth LE (low energy) and how to connect to the smart phone. But I’d like to show what I did with the IR LED which I found not very useful for me. I opened the watch and removed 4 screws and removed the wrist strap. The antenna was in one side of the straps.

And then I disconnected 2 flex cable connectors.

Now I could pull out the PCB and flipped over to desolder the IR LED and replaced it with a 0805 red LED. I showed the polarity of the LED in the picture below.

The IR (now red) LED is connected to the pin 13, so you can use digitalWrite commend to turn it on or off. Below picture is an example I tried to show the battery charge status. 1ms per every second is good enough to show the blinks.

Currently I’m trying various sleep options with regular wake-up to test the battery life. With following settings, the battery seemed to last more than 2days. I stopped the test after 21hours to try another setting when the battery level showed 60%. So, if the discharge rate is linear, then it may last more than 48 hours, I think.

  • CPU frequency: 10MHz
  • Screen brightness: 5
  • Light sleep with touch screen, timer and button interrupts enabled
  • Timer wake-up: every 15 seconds
  • When it’s woken up it shows current time, date, and battery status for 5 seconds

I will post updates for the future developments.