ESP32-CAM Low Power Trail Camera

I’ve been spending a lot of time lately working with the ESP32-CAM module. It doesn’t produce the best pictures I’ve seen, but for the cost (I’ve found them for $9, including the OV2640 camera!) and the number of features and horsepower, they’re tough to beat.

One of the things I want to do with them is put a couple outside and get pictures of the different kinds of animals that wander through the yard and leave footprints in the snow. When I mentioned this to a buddy of mine, he immediately wanted to know if they could be used to watch for motion and take pictures in case someone was trying to break into his shed or cabin. Sure, I told him – I didn’t see any reason why not. His response was to immediately ask me how many I could make for him and how quickly I could do it.

Unfortunately, it was just an idea at the time and I hadn’t actually tried to do it. I figured it wouldn’t be too tough – after all, the ESP32-CAM AI-Thinker modules I use have several GPIO pins broken out. I was wrong.

Turns out some of the GPIO pins are used by the camera, and the rest are used by the uSD card slot that’s on the board. One of them (D4) seems to be used by BOTH the camera (camera flash) and the uSD card slot (data line).

I tried a bunch of things and didn’t have much luck, and when I looked around for information, there were lots of links but I couldn’t find any information that quite fit what I was doing.

Finally, I found a link to some ESP documentation, which got me started. Looking into the various ESP libraries for the SD card, using a FAT32 filesystem, the camera, and the on-board EEPROM took a while but after I figured one or two of them out, the others were easier.

After going through my various parts bins, I cobbled together a circuit that seems to reliably work. Here’s the schematic of the whole thing:

Trailcam v1 schematic

+V on the schematic is the power supply you want to use. Power for the ESP32 first goes to an AMS1117-3.3 regulator. According to the AMS1117 datasheet, it will run to where the input is only 1V higher than the output. The output is 3.3V, so it should be able to run down to 4.3V. The absolute maximum input voltage is 15V, so powering it from 4xAA/4xC/4xD alkaline batteries (6V) is fine. Even 9 or 12V should be OK, but check the regulator on your board first to make sure.

Remember that if you want to power it from rechargeable NiCd or NiMH batteries, those are 1.2V, not 1.5V, so you’d probably want to use five of them, instead of four alkalines. Same with those long-life lithium batteries – they’re 1.2V too.

The block labelled “OPTIONAL” is there if you want a switch that will keep the MOSFET (and ESP) turned on while programming. You can also just move the ESP GND pin from the MOSFET drain to ground while programming. Or… if your PIR will remain on while motion is present, you can just wave your hand above the sensor until programming is done. That’s what I do.

R1 and R2 are necessary, particularly if you have a MOSFET with a high input capacitance. They keep the MOSFET gate from momentarily pulling a large amount of current which could damage the PIR or ESP.

Why the 4N37? Because the ESP is not connected to the ground rail when the MOSFET is turned off, so GPIO13 does a very noisy “high-ish” float which leads to unpredictable results. Note that the 4N37 diode side circuit goes from GPIO13 to the anode, the cathode goes to R3, and R3 goes back to the GND pin on the ESP – NOT the ground rail from the power supply!

This circuit works best with low-Vf Schottky diodes that can tolerate a reverse voltage at least 2x the highest possible voltage in your circuit. Tested and works with BAT41 and 11DQ06. Tested and works with old-school 1N34 germanium diodes (but I probably wouldn’t use them for real-world applications). It seems to mostly work but not as reliably with typical 1N914 and 1N4001 diodes. It does NOT work with anything with a Vf larger than about a volt, like LEDs.

This circuit also works best with MOSFETs that are fully on at a low voltage, like 2.5 to 4.5V, and have a very low drain-source resistance when on (tens to a couple hundred milliohms). Tested and works with IRLI640G and DMN1019USN-7.

Normally I’d put bypass capacitors across the 5V and GND pins of the ESP, but here’s the ESP32-CAM power circuit:

ESP32-CAM power supply circuit
From: https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf

Note the abundance of capacitors, which is pretty great. A 0.1uF capacitor probably wouldn’t hurt, but I don’t think it’s necessary unless you’re using a very noisy power supply.

So that’s the schematic. Breadboarded up, this is how it looks:

Trailcam v1
That loose wire is connected to GPIO0 and is connected to GND to program the ESP32
Trailcam v1
That little chunk of PCB with a blue wire on it is a nasty but functional tiny-surface-mount-to-breadboard converter

Here’s how it works:

  • When power is applied to the circuit, the gate of the MOSFET is low and doesn’t conduct, so the ESP is disconnected from the ground rail. The circuit pulls about 16uA at this time.
  • When the PIR sensor detects motion, its trigger pin goes high for two seconds. This signal is sent through a diode and 47k resistor to the gate of the MOSFET, which turns it on.
  • With the MOSFET turned on, the ESP now has power and boots. As soon as it can, it sets GPIO13 high. This turns on the input of a 4N37 optoisolator, which turns on its output transistor. The output transistor is also connected to the gate of the MOSFET through a diode and a 47k resistor. This keeps the MOSFET turned on even after the PIR trigger line goes low.
  • The ESP goes through the paces of checking for and mounting the uSD card, starting up the camera, and checking the EEPROM and reading the number from it that indicates the last picture that was taken (PIC_COUNT) prior to the previous shutdown.
  • If there is a problem at any point in the startup, the ESP will set GPIO13 low, which will turn it off and wait for the next trigger to boot again. I know it’s 2020, but sometimes a reboot still fixes things.
  • If there are no problems, it gets to the main loop, which takes the number of pictures specified by a while loop that increments the variable COUNTUP (in the program here it takes five pictures). Each time through, the picture counter (PIC_COUNT) is increased by 1. The circuit pulls about 130-140mA at this time.
  • Once COUNTUP reaches the maximum set in the while loop, the ESP saves the current value of PIC_COUNT to the EEPROM and then sets GPIO13 low. This should turn off the MOSFET and remove power from the ESP.
  • If there is still power, the ESP waits for 500ms after it tried to shut itself down. If it’s still awake, then that means the PIR has either re-triggered or is still triggered so it’s a good idea to get more pictures. The ESP sets GPIO13 high again and loops back to take another five pictures.

Here’s what it looks like when it’s running. Note the camera flash LED on the board glowing faintly instead of blazing like a million suns like it usually does. When the LED is on, the ESP is communicating with (and hopefully writing an image to) the uSD card.

You may be wondering why it works, though. After all, there don’t seem to be any usable free GPIOs when using both the camera and the uSD card, so what’s with GPIO13? Well, it comes down to changing this line:

SD_MMC.begin("/sdcard")

to this:

SD_MMC.begin("/sdcard",true)

Selecting “true” tells the ESP to talk to the uSD card in 1-bit mode instead of the usual 4-bit mode. This frees up a couple of GPIO pins, one of which is GPIO13. The disadvantage to using 1-bit mode is that it’s slower, but I’m pretty sure the ESP itself is going to be the bottleneck here. Plus, the OV2640 and lens aren’t super-high quality so setting the JPEG image quality high and making huge files isn’t necessary (or useful).

Here’s the program in its entirety (it looks kind of mangled but if you copy and paste it into a text document or the Arduino IDE it comes out properly):

/* ESP32-CAM Low Power Trail Camera v1
 * Mark's Bench (http://marksbench.com)
 * Uses ESP32-Cam AI-Thinker with OV2640 camera to take pictures and save to SD card when triggered by PIR
 * ESP32 is connected to power rail via MOSFET. MOSFET is initially turned on by PIR trigger and kept on by setting pin D13
 * on the ESP32 high as soon as ESP starts up.
 *  
 * ESP then takes 5 pictures and saves them to the SD card, after which it sets D13 low, which turns the MOSFET off,
 * cutting the ESP off from the GND rail.
 * 
 * If the PIR trigger remains high or goes high again during when the ESP32 would shut down, then take another five
 * pictures and try shutting down again.
 * 
 * Advantage to this scheme is a power savings - power draw is less than 20uA when the MOSFET
 * is off and the ESP32 is shut down. Power draw is around 130-140mA when pictures are being taken and saved.
 * 
 * Disadvantage is that when using both the camera and the SD card, there are no easily usable GPIO pins available.
 * To get around this, use 1-bit SD card access instead of the usual 4-bit. 
 * It slows things down but you can still get an image with ok quality about once a second
 * at full resolution (1600x1200) on the OV2640.
 * 
 * 
 * Information on and code examples for using the ESP32-CAM library:
 * https://github.com/yoursunny/esp32cam
 * https://github.com/espressif/esp32-camera
 * https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer
 * 
 * Information on and code examples for using the ESP32 SD_MMC library:
 * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD_MMC
 * 
 * Information on using the ESP32 EEPROM (the Preferences library):
 * https://github.com/espressif/arduino-esp32/tree/master/libraries/Preferences
 * 
 * VERY useful ESP32 documentation:
 * https://www.espressif.com/en/support/download/documents
 * In particular, the "ESP32-WROOM-32 Datasheet", "ESP32 Datasheet", and "ESP32 Hardware Design Guidelines".
 * Also, the schematic at:
 * https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf
 * And the specification page (which has some errors) at:
 * https://github.com/raphaelbs/esp32-cam-ai-thinker/blob/master/assets/ESP32-CAM_Product_Specification.pdf
 * 
 * 
 * ***TO PROGRAM: Set Board to "AI Thinker ESP32-CAM"***
 */

 
#include <esp_camera.h>
#include <FS.h>
#include <SPI.h>
#include <SD_MMC.h>
#include <Preferences.h>

// The ESP32 EEPROM library is deprecated. Use the Preferences library instead.
Preferences preferences;

// The following defines are for the ESP32-Cam AI-THINKER module only. I haven't tried any others.
#define CAM_PIN_PWDN    32
#define CAM_PIN_RESET   -1
#define CAM_PIN_XCLK    0
#define CAM_PIN_SIOD    26
#define CAM_PIN_SIOC    27
#define CAM_PIN_D7      35
#define CAM_PIN_D6      34
#define CAM_PIN_D5      39
#define CAM_PIN_D4      36
#define CAM_PIN_D3      21
#define CAM_PIN_D2      19
#define CAM_PIN_D1      18
#define CAM_PIN_D0       5
#define CAM_PIN_VSYNC   25
#define CAM_PIN_HREF    23
#define CAM_PIN_PCLK    22


// Create a variable to hold the picture number. Since the SD card is formatted FAT32, the maximum number of files
// there can be is 65534, so a 16-bit unsigned number will be fine.
uint16_t PIC_COUNT = 0;

void setup(){
  pinMode(13, OUTPUT);    // GPIO13 available when using SD_MMC.begin("/sdcard",true) for 1-bit mode (set below)
  digitalWrite(13, HIGH); // Hold the gate of the MOSFET high as soon as possible after boot to keep the power on
                          // after the PIR is done triggering.
                          
  //Serial.begin(115200); // Uncomment for troubleshooting

  preferences.begin("trailcam", false); // Open nonvolatile storage (EEPROM) on the ESP in RW mode
  PIC_COUNT = preferences.getUShort("PIC_COUNT", 0);  // Get the stored picture count from the EEPROM.
                                                      // Return 0 if it doesn't exist.
                                                      // getUShort() fetches a 16-bit unsigned value

  // Now, configure the camera with the pins defined above and recommended settings for xclk, led_c, and format.
  camera_config_t config;
  config.pin_d0 = CAM_PIN_D0;
  config.pin_d1 = CAM_PIN_D1;
  config.pin_d2 = CAM_PIN_D2;
  config.pin_d3 = CAM_PIN_D3;
  config.pin_d4 = CAM_PIN_D4;
  config.pin_d5 = CAM_PIN_D5;
  config.pin_d6 = CAM_PIN_D6;
  config.pin_d7 = CAM_PIN_D7;
  config.pin_xclk = CAM_PIN_XCLK;
  config.pin_pclk = CAM_PIN_PCLK;
  config.pin_vsync = CAM_PIN_VSYNC;
  config.pin_href = CAM_PIN_HREF;
  config.pin_sscb_sda = CAM_PIN_SIOD;
  config.pin_sscb_scl = CAM_PIN_SIOC;
  config.pin_pwdn = CAM_PIN_PWDN;
  config.pin_reset = CAM_PIN_RESET;
  config.xclk_freq_hz = 20000000;
  config.ledc_timer = LEDC_TIMER_0;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.pixel_format = PIXFORMAT_JPEG;
  
  // Make sure there is PSRAM available (the AI-Thinker module has PSRAM). Otherwise, don't go any further.
  if(psramFound()){
    config.frame_size = FRAMESIZE_SXGA; // If there's PSRAM then there's enough memory to capture up to 1600x1200
                                        // The following resolutions are available:
                                        // 96x96 (96x96)
                                        // QQVGA (160x120)
                                        // QQVGA2 (128x160)
                                        // QCIF (176x144)
                                        // HQVGA (240x176)
                                        // 240x240 (240x240)
                                        // QVGA (320x240)
                                        // CIF (400x296)
                                        // VGA (640x480)
                                        // SVGA (800x600)
                                        // XGA (1024x768)
                                        // SXGA (1280x1024)
                                        // UXGA (1600x1200) **Full-resolution for OV2640
                                        
    config.jpeg_quality = 10; // Valid: 0-63, with 0 being highest quality and largest file size.
                              // Anything lower than 8 creates large file sizes that take a long time 
                              // to save to the SD card. 
                              // The camera and lens aren't the best quality, so huge files
                              // won't get you a better picture beyond a certain point.
    config.fb_count = 2;  // With the PSRAM, there's enough memory for two framebuffers, which speeds captures.
  }
  else{
    while(true){
      // The AI-Thinker module has PSRAM. I haven't tried any module without PSRAM.
      //Serial.println("NO PSRAM FOUND"); // Uncomment for troubleshooting
      delay(500);
    }
  }

  // Start up the camera with the configuration settings made earlier in the "config." statements.
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK){
    // If we're here, there's a problem communicating with the camera.
    // Turn the ESP off and wait for the next trigger.
    digitalWrite(13, LOW);
    //Serial.println("CAM FAIL"); // Uncomment for troubleshooting
    while (true){
      // Need this loop to wait in case the PIR is keeping the power on.
    }
  }

  // Start up the SD card, using 1-bit xfers instead of 4-bit (set the "true" option). Frees up GPIO13.
  if(!SD_MMC.begin("/sdcard",true)){
    // If we're here, there's a problem with the SD card.
    // Turn the ESP off and wait for the next trigger.
    digitalWrite(13, LOW);
    //Serial.println("SD FAIL 1");  // Uncomment for troubleshooting
    while (true){
      // Need this loop to wait in case the PIR is keeping the power on.
    }
  }

  // Query the card to make sure it's OK
  uint8_t SD_CARD = SD_MMC.cardType();
  if(SD_CARD == CARD_NONE){
    // If we're here, there's a problem with the SD card.
    // Turn the ESP off and wait for the next trigger.
    digitalWrite(13, LOW);
    Serial.println("SD FAIL 2");  // Uncomment for troubleshooting
    while(true){
      // Need this loop to wait in case the PIR is keeping the power on.
    }
  }  
}


// We are now done the setup and should be ready to take pictures in the main loop() function.

void loop(){
  uint8_t COUNTUP = 0;  // Create variable to take multiple pictures.
  while (COUNTUP <=4){  // Take 5 pictures before shutting down.

    // Take picture and read the frame buffer
    camera_fb_t * fb = esp_camera_fb_get();

    if (!fb){
      // If we're here, there's something wrong with the data in the frame buffer.
      // Turn the ESP off and wait for the next trigger.
      digitalWrite(13, LOW);
      while(true){
        // Need this loop to wait in case the PIR is keeping the power on.
      }
    }

    // If we're here, the image was captured. Begin the process to save it to the SD card.
    // First, create the file name and path. Currently set to make files like /pic123.jpg
    String path = "/pic" + String(PIC_COUNT) + ".jpg";
    
    fs::FS &fs = SD_MMC;

    // Now, create a new file using the path and name set above.
    File file = fs.open(path.c_str(), FILE_WRITE);
    if(!file){
      // If we're here, there's a problem creating a new file on the SD card. 
      // Turn off the ESP and wait for the next trigger.
      digitalWrite(13, LOW);
      while(true){
        // Need this loop to wait in case the PIR is keeping the power on.
      }
    } 
    else 
    {
      // If we're here, the file was created. Now write the captured image to the file.
      file.write(fb->buf, fb->len); 
      PIC_COUNT = PIC_COUNT + 1;  // Increment the picture count number each time there's a successful write.
      if(PIC_COUNT >=65500){
        PIC_COUNT = 0;  // FAT32 has a limit of 65534 files in a folder
      }
    }
    file.close(); // Done writing the file so close it.

    // Free the memory used by the framebuffer so it's available for another picture
    esp_camera_fb_return(fb);

    COUNTUP = COUNTUP + 1;  // We are done an image capture cycle. Increment the count.
  }
  

  // If we're here then we've taken the pictures and we are ready to shut down. Write the current file number to
  // the EEPROM, then set D13 low.
  preferences.putUShort("PIC_COUNT", PIC_COUNT);  // Store the picture count number in the EEPROM
  // Normally you'd want to do a preferences.end() to properly close the EEPROM but since the intent is to
  // shut the ESP down, it's not needed, and not having to open and close it every capture cycle speeds things
  // up and saves some wear on the EEPROM.

  //Serial.println("Shutting down."); // Uncomment for troubleshooting
  digitalWrite(13, LOW);
  delay(500);
  
  // The ESP should be shut down at this point. If the PIR is still triggered or has re-triggered and is keeping
  // the MOSFET on, then set D13 high and allow the program to loop again to take another five pictures.
  digitalWrite(13, HIGH);
  //Serial.println("Looping back.");  // Uncomment for troubleshooting


}

Hopefully there are enough comments to make heads or tails of it. A few notes:

  • This is for the AI-Thinker module with the OV2640 camera only. It’s the only one I’ve tried. It will probably work with other models of ESP32-CAM, but you will need to check the #define for each pin, see how much of what kinds of storage are available, and what settings the camera you’re using requires.
  • The ESP32 Arduino-compatible “EEPROM” library is deprecated; the new way to do things is with the “Preferences” library.
  • Before programming, set the board type to “AI Thinker ESP32-CAM”. Again, this is for the AI-Thinker module only.

If you’re looking for documentation on the ESP32-CAM or the libraries I used to get this working, it’s all in the following links. For the libraries, be sure to check out the examples and .h files for options and how various things work:

I haven’t put it outside yet, but as a test of the ESP and circuit, I changed the program so the ESP would take and save as many 1600×1200 JPEGs at compression level 9 as quickly as possible. The circuit was powered by four grocery-store branded AA alkaline batteries that were unused but of unknown age. It ran for 16 hours 21 minutes and took 23475 pictures before the batteries died. Not bad!

I know there is a lot of room for improvement – both in the circuit and in the program. When building this, I was limited to what I had on hand – a P-channel MOSFET might be a better choice, and I’m sure there’s a better way to do things than with the 4N37. For now, though, I’m pretty happy that it works reliably. Now I need to put it in a box and get some pictures of those critters in the yard.

If you made it this far, congratulations! If you build this or have a better way to do this, I’d be curious how it turned out! Feel free to drop a comment here or send me a message using the contact form!

Ah, The Good Old Days…

TTL data book

Anybody remember these? Anyone else use wire as bookmarks? Back in the day (when low-power Schottky was actually low power) one of these were worth their weight in gold. For the longest time I was stuck sharing a very worn one (no cover, lots of missing pages, etc) with a group of other people until I saved up enough to get my own.

With the internet, looking up a datasheet is easier and faster than with this old book, and I’ve been tempted to throw it out a couple of times… but last night I was stuck on a problem and found myself leafing through it for ideas. I’m glad I kept it!

Getting Clone / 3rd Party Arduino 32U4 Boards Working On The Raspberry Pi / Linux

Ever since Ms Geek introduced me to the Arduino last April, I’ve been having a blast tinkering with them and building things. I’ve bought quite a few Arduino boards now, most of which are clones or third-party boards. A couple of them have been duds out of the package, but when I can buy four cheap boards for the cost of one “real” board, it’s tough to rationalize paying the extra money. Plus, a bit of time with a magnifier and a soldering iron fixed all but one of the boards that were DOA.

Up until two days ago, I’ve used the Arduino IDE exclusively under Windows and things have been working well. Two days ago, I installed the IDE (1.18.10) on a Raspberry Pi 4 running Raspbian Linux (Buster), and had no problems connecting to and uploading programs to MEGA328p boards like the Nano and Pro Mini.

A nice thing about Arduinos is that from a programming perspective, they’re pretty tough to permanently kill. As always, though, I make no guarantees that any of the following (or anything on this site, for that matter) will work for you. It’s worked for me so far, but it may not work for you. Who knows – it may not even work for me tomorrow. Anyway…

When I tried to do anything with a board that had the MEGA32U4 chip, though, the IDE couldn’t find it. There were no serial ports listed that pointed to the board:

No Serial in Arduino IDE
No Arduino board to be found…

The same thing happened with every single Pro Micro clone I tested (four of them from two different orders months apart) and a Keyestudio Leonardo clone. After hours of research and tinkering, I wasn’t any closer to fixing the problem, although I did disable modemmanager, which is something I discovered you should do if you want to do Arduino stuff under Linux anyway.

Just before I gave up, I extricated an authentic Leonardo from the project it was wired to and plugged it in. Immediately, a new serial port (ttyACM0) appeared in the IDE and showed a device connected to it:

Authentic Leonardo Showing Serial Port
I guess this is why buying the real deal is a good idea…

Finally, I had a starting point. Here’s an assortment of my 32U4-based Arduino boards (the Pro Micro is basically a little Leonardo):

32U4 boards
Top left: Authentic Leonardo. Top right: Clone Leonardo. Bottom: Clone Pro Micro

An easy thing to check was the 32U4 chip. They all looked pretty much the same, other than the authentic Leonardo being quite a bit older than the other boards:

Real Leonardo
Real Leonardo
Clone Leonardo
Clone Leonardo (I put the marker on there)
Pro Micro Clone
Pro Micro Clone

Since the boards all worked under Windows, I was pretty sure that despite some different components (regulators, crystals, etc) on the clone boards, all were electrically sound. So, I took a look at what the Pi said when I plugged the boards in. It turned out that Linux was seeing the clones when I plugged them in but they disconnected almost immediately:

32U4 Disconnecting
Output of dmesg after plugging in a clone board

Then I plugged the genuine Leonardo in. It showed up in dmesg and stayed connected:

Auth Leo
Output of dmesg with the authentic Leonardo

Aside from not disconnecting, the genuine Leonardo showed up a little different in dmesg. Take a look at the “New USB device found” and “New USB device strings” lines.

On the genuine Arduino: idVendor=2341, idProduct=8036, bcdDevice=1.00, Mfr=1, Product=2, SerialNumber=3

On the clones: idVendor=2341, idProduct=0036, bcdDevice=0.01, Mfr=2, Product=1, SerialNumber=0

Everything except the idVendor is different. According to the USB ID Repository, the vendor ID of 2341 belongs to Arduino. The Product ID of 8036 is associated with the Leonardo, while the Product ID of 0036 is associated with… a blank spot:

USB ID Repository
From the USB ID Repository (https://usb-ids.gowdy.us/)

I still remember going through USB hell when the original Raspberry Pi came out, so to make sure the Pi or Raspbian weren’t causing the problem, I connected the boards to an Ubuntu VM. I got the exact same results, which took the Pi off the list of suspects. I did some more research and discovered that the vendor, product, and other information presented by the MEGA32U4 isn’t hard-coded in the chip or selected by OTP fuses – it’s firmware. As in, the bootloader. Which can be replaced.

I found some instructions for replacing the bootloader on an Arduino here and used a Nano (which works fine in Linux) as the programmer. I hooked one of the 32U4-based clones to the Nano, set the IDE to use the Nano as a programmer (Tools -> Programmer -> ArduinoISP), then burned the new bootloader (Tools -> Board -> Arduino Leonardo, then Tools -> Burn Bootloader).

I hooked the clone Leonardo up to the Raspberry Pi and took a look at the output of dmesg:

32U4 after new bootloader
That looks a little more like it…

After burning the new bootloader, all of the clones showed the same values as the genuine Leonardo for idVendor, idProduct, bcdDevice, Mfr, Product, and SerialNumber… and all of the clones now show up as /dev/ttyACM0 or /dev/ttyACM1, and they are all visible and programmable in the Arduino IDE on a Pi 4 running Raspbian.

So, to sum up… if you can’t program a 32U4-based board with the Arduino IDE in Linux but it works fine in Windows, you can try the following:

  • Check dmesg and confirm that the board is connecting and disconnecting. If so, then…
  • Disable modemmanager (good idea to do this anyway), then…
  • Check dmesg and see if the board has idVendor=2341 and idProduct=0036. If so, then…
  • Buy or build an Arduino ISP. Once you’ve got it, then…
  • Hook up the board in question to the ISP and burn the correct bootloader to the board. Once that’s done, you can…
  • Check dmesg again and see if the board now has idProduct=8036. If it does, then…
  • Give it a try (don’t forget to change the IDE settings (i.e. Tools -> Programmer -> AVRISP mkII) back to program your board directly instead of using the IDE!)

Good luck!

Removing IR Filter From ESP32-CAM

I’ve been playing around with the ESP32-CAM modules a lot lately, and after spending some time searching through pages of code, I think I’ve finally got a handle on configuring the camera and getting it to start up cleanly.

With its boot time being about a second and the price being as low as it is, I think there are tons of applications for it. Everything from an always-on security camera to an occasionally used trail/wildlife camera, eye/brain/wifi for a remote-controlled car or robot… all kinds of things.

I want to put some cameras outside to catch the various animals that are leaving tracks in the snow, and these things are so inexpensive that they’re aalllllmost disposable. That’s not to say that I plan on putting them in situations where they’ll be ruined, but if one gets gnawed on or water gets to it, it won’t be the end of the world.

One thing that the stock camera can’t do, though, is see well in the dark. Even with bright IR illumination, the IR filter in the camera does a good job of keeping the picture stubbornly dark. Back when the Raspberry Pi camera module first came out, I fought with the IR filters in a couple of them, so I figured I’d take a shot at removing the filter from one of the OV2640s that came with the ESP32-CAM. Turns out it wasn’t too tough, although it took some patience.

PLEASE NOTE THAT THIS IS ONLY FOR THE CAMERA SHOWN IN THE PICTURES BELOW. I DON’T KNOW IF THIS PROCEDURE WORKS FOR OTHER CAMERAS.

OH, AND ALSO PLEASE NOTE THAT I’M NOT RESPONSIBLE FOR ANYTHING YOU DO, SO IF YOU WRECK YOUR CAMERA OR CUT YOURSELF OR BURN YOUR HOUSE DOWN OR WHATEVER, THAT’S ON YOU. BE CAREFUL!

First, remove the camera from the ESP32-CAM module by lifting the latch that’s holding the connector in place. Take note of where the glue is on the lens – it shines a little more under a light than the plastic does:

ESP32-CAM lens

After the camera is loose from the module and you’ve located the glue, very carefully and gently run a sharp knife in the seam between the lens barrel and the camera casing:

ESP32-CAM IR lens removal

Once you’ve cut the glue, grab the lens barrel with one finger and thumb and the casing with the other and gently turn the barrel counterclockwise. If it doesn’t turn, go back and run the knife through the glue again. If it does turn, you’ll be rewarded with a view of the camera sensor…

ESP32-CAM IR lens removal

… and you’ll be able to see the IR cut filter too:

ESP32-CAM IR lens removal

The filter in this particular camera is held on by a very thin ring of plastic and possibly some glue. I slowly shaved the plastic away until I got right down to the filter, then very carefully pried the filter off with the knife:

ESP32-CAM IR lens removal
Still broke the filter, though…

After that, it’s just a matter of making sure there’s no dust on the inside of the lens or on the camera sensor, then very carefully threading the barrel back into the casing. It is really easy to cross-thread it, so take it slow and be careful. If all goes well, you should end up with a put-back-together camera:

ESP32-CAM reassembled

One of the other advantages of cutting the glue and being able to thread the barren in and out is that you can adjust the focus on the camera now. Want to look at something nearby? Back the barrel out a bit. Something far away you want to see? Spin it in a bit.

Anyway, put the camera back in the ESP32-CAM and flip the lever down on the connector to secure it all. Voila, you now have an IR-sensitive camera!

Oh, one of the things that sometimes goes unmentioned when talking about making your camera “see in the dark” is that after you remove the IR-cut filter, your camera will no longer work very well in colour, particularly where there is a lot of extra IR light bouncing around (i.e. daytime). The colours will not look normal and it will seem like the picture is out of focus. You can fix that by putting an IR-cut filter back into or in front of the camera, but I just want to watch animals wander around in the dark so B&W is just fine for me.

Here’s the difference removing the filter makes. Same remote, same button (3), and same camera settings. Here’s before I removed the filter:

Before IR filter removed

And here’s after:

After IR filter removed

A bit of a difference! I’m looking forward to trying this out and seeing which wavelength of IR LEDs it will be the most sensitive to.

ESP32-Cam Antenna Workaround

A while ago I got my hands on a pair of ESP32-Cam modules. They were crazy cheap and it looked like I had a great little device that would work well as a security or wildlife cam:

It’s tiny!

It was easy to program using the Arduino IDE and the example program, but no matter what I tried changing in the program it wouldn’t connect to my home wireless network.

There’s a lot of documentation out there on the ESP32 devices, but there are parts that seem to be missing. I wish I’d bookmarked the link, but I came across the comment section of one article that mentioned a 0R surface-mount resistor that selected the external antenna jack or the on-board PCB antenna. It didn’t take too long to find, and it was set to use the external antenna jack:

That explained why it wasn’t talking to the wireless – no signal. I could fix the problem in two ways: plug in an external antenna, or move the tiny little resistor so that it connected the ESP32 to the PCB antenna. I don’t have any connector that will fit that jack, so I decided to move the resistor.

**NOTE** Doing any combination or number of the following steps will definitely, POSITIVELY void the warranty of the device. It may also increase the electrical noise produced by the unit and/or its susceptibility to other sources of electrical noise. It may also cause operational or stability problems. It may also cause the device to produce wifi signals that are more powerful than allowed by law. Proceed at your own risk.

Unfortunately, between not being able to see it very well (even with a magnifier lamp), having a soldering iron with too large a tip, and having tingly fingers and shaking hands, I ended up with this:

Not only did I not manage to solder the resistor in place, I also ruined the PCB pad that connected back to the ESP32.

By this point I was getting pretty frustrated. Actually, I don’t think I’ve been that frustrated in quite a while. Fortunately, I had another ESP32-Cam that I could apply the knowledge that I gained on this one and not make the same mistake twi-bahahahaha… yeah, I ruined that one, too.

The two of them together cost me less than $15, but I hate throwing something out when it’s still “alive” (they both still booted and tried to connect to the wifi), and I was looking forward to playing around with them. Seeing as how they were pretty much garbage at this point anyway, I figured I’d see if there was anything I could do to get them working.

**NOTE** See previous note. Seriously.

I could see the PCB trace leading under the metal can, so I figured that if I was careful, I might be able to remove the can and find another soldering point to attach an antenna.

To remove the can, you don’t need a hacksaw or a Dremel… just a pair of very fine-tipped pliers or snippers. there is a small hole in the can in the inside corner right by where the antenna selection resistor pads are. Carefully grab it with the pliers/snips and pull straight upwards to make some room:

Slide the pliers in a bit more and lift up and away from the PCB antenna (towards the top of this picture). It may take a bit but the can end by the PCB antenna should break away:

Reposition and pull the can up and to the left – the right side and top should come free easily as well:

Reposition again and the last bit should break away with very little force:

The antenna connection point is – very nicely – right by where the antenna selection pads are, right between two surface-mount components with big pads and lots of solder:

So now to make the antenna. I used a piece of 26ga solid wire-wrap wire, but anything nice and thin will do 22ga is too big.

Cut a piece about 6.5cm long, then strip about 1-1.5mm and bend the stripped part at a right angle. Tin the wire, make sure it’s got a good coat of solder on it:

Bend the insulated part of the wire to whatever shape and angle helps you hold the stripped part in place at the new attachment point. Use the iron sparingly – if you’re heating and heating and it doesn’t seem to be melting, back off, give it a minute, and change your angle before trying again.

If you’re careful and lucky, you’ll end up with something like (or much better than) this:

Gently bend the antenna into the orientation you prefer, and you’re done!

Reminds me of one of those facehuggers from Alien

The next thing to try is powering it up (unless you haven’t programmed it, in which case you should probably do that now). With no can and a crappy detuned wire antenna, the ESP32-Cam was able to finally connect to my home wireless network:

FINALLY

And the camera’s video streaming works like a charm:

I suppose that instead of hanging a wire antenna off that point, I could’ve run a jumper from the new attachment point to the pad that connected to the PCB antenna (or the antenna jack, for that matter), but a) I just thought of it, and b) I don’t mind the crappy antenna look.

Portable Power For The GQRX Pi 4

I’ve been enjoying finding and listening to all kinds of stuff with the SDR, and since I got it working with the Pi 4 I’ve wanted to use it without needing an extension cord.

I had a lot of trouble finding a battery supply that would do the trick. I have a few of those USB power packs at home and tried them but the Pi kept reporting low voltage.

I then turned to putting a regulator on a 12V SLA battery. Unfortunately, even with capacitors and shielding, the switching regulator I tried put out a lot of noise that the SDR picked up. I knew a linear regulator would be quieter but as I suspected, it only took about a minute before the biggest heatsink I had was too hot to touch.

So… I went back to the USB battery packs.

There are a few problems with those packs. They can also be noisy (there’s switching circuitry in them too), they can sag under load, and most of the USB cables out there are cheap crap that use very thin wire.

I put a load (actually, a Pi 2) on each pack and watched what happened to the noise and voltage on the oscilloscope. The best of the lot turned out to be an older Anker Astro E4 13000mAh unit that held a pretty constant 4.92V and wasn’t too noisy. So I started there.

I don’t know how many USB cables (or pieces of USB cables) I have sitting around. Some came with phones, tablets, or other devices… some were bought separately… some looked good… some looked cheap. I started going through the cables to see what kind of voltage drop there was when there was a Pi 4 on the end (with a micro-USB to USB-C adapter).

None of them ran the Pi without triggering the low voltage warning, and some of them couldn’t run the Pi without triggering the voltage warning even when idle. Two of them were so bad that the Pi couldn’t finish booting. The voltage drop across the cables was as much as 0.62V!

With those results, I decided to make my own cable. Unfortunately, when I looked in my USB parts drawer, I only had micro-USB plugs and USB type A jacks.

Out came the snips and I started chopping up the cables, starting with the ones that looked the best. Turns out that how a cable looks doesn’t mean much when it comes to how heavy the wire inside it is.

Eventually I found one that had considerably heavier wire than what I’d seen up to that point, so I decided to use it instead of chopping up the rest of the cables. I cut it to 60cm, soldered on the plug end, and gave it a try.

It was a lot better, but the Pi was still reporting that there’d been a low voltage condition at some point. I cut the cable to 50cm.

Then 40cm.

Then 30cm.

Then 20cm.

20cm did the trick, and I couldn’t trigger the low voltage warning anymore, even with the SDR plugged in and running and the CPU pinned to 100% (I usually use cat /dev/urandom > gzip > /dev/null for that).

Here it is, the beautiful and reliable USB cable of portable GQRXing:

Yeah, that’s hot glue. Works well and it’s strong but doesn’t look all that great…

Since I only had the micro-USB plugs on hand, I still have to use the adapter, which could also be wasting a bit of power. I need to order some other stuff sometime soon so I may grab a couple of parts to make another good cable or two.

To test the cable and battery pack, I hooked it up to my SDR Pi, fired up GQRX, told it to record the audio, and checked in on it every half hour. It ran for six hours before the battery LEDs showed it was at less than 25% capacity. I don’t like running those packs flat so I stopped the test there.

Before I shut down the Pi, I hopped onto it (using VNC on my phone, heh) and checked whether any of the warning conditions had been triggered (voltage, temperature, etc). Here’s what I saw:

0x0, or no problems at all… after running for six hours straight. Not too shabby!

GQRX On The Raspberry Pi 4

I’ve been playing with SDRs on the Raspberry Pi 4 for almost a month now and I am happy to say that the new Pi (at least the 2GB model) has enough oomph to run GQRX with a Great Scott Gadgets HackRF One (at 4MSPS max), and a NooElec NESDR SMArt. I was SO happy to say it, I spent several hours writing up a howto, just for everything to fall apart at the second last step.

So… here’s a short version. If I get enough questions about it, I’ll look at salvaging what I still have and writing it up in detail again.

To get GQRX working on the Raspberry Pi 4B (the 2GB is the only version I have), you will need the following:

  • A Raspberry Pi 4B (I only tested with the 2GB version but I expect it will work fine with both the 1GB and 4GB versions)
  • An 8GB Class 10 (or larger/faster) microSD card
  • Internet connectivity through a wired Ethernet port
  • An Ethernet cable
  • An SDR (tested with the HackRF One and the NESDR SMArt)
  • An antenna that fits your SDR
  • A USB cable that fits your SDR (if necessary)
  • Headphones or a set of speakers with a 3.5mm plug
  • A heatsink and/or fan for the Pi (it requires so little cooling that you can just point a desk fan in its general direction and it should be okay)

So, here’s how to do it:

  • Image the microSD card with the official Raspbian version with desktop but without the extra software. Get it from here.
    Get this version of Raspbian
  • Connect it to an Ethernet port and power it up, then find it on your network and do the usual password/configuration/update steps.
  • Set the resolution to 800×600.
  • Force the audio output to the headphone jack.
  • Enable VNC.
  • Install GQRX from the respository (sudo apt install gqrx-sdr).
  • Connect to the Pi via VNC and turn the system volume down.
  • Connect your SDR (with antenna)
  • Run GQRX. If you’re having trouble or for information on settings, etc, check here for information.
  • In the “FFT” tab, change the
  • Turn up the system volume and/or software or hardware amplifiers in GQRX and your SDR until you can comfortably hear a signal.

And that’s about it.

I found, though, that I wanted to make the Pi a little more portable, and when using the Ethernet jack there seems to be a bit more noise in the GQRX display. Here’s what I did:

  • Install RaspAP, information and installation instructions are here.
  • Set up the Pi to be a wireless access point on a separate network from your Ethernet port.
  • Once the AP is running, disconnect the Pi from the Ethernet port and, using your computer, phone, or tablet, look for the SSID of the AP you set up and connect to it.
  • Once connected, run a VNC client (I use VNC Viewer by RealVNC but others should work as well).
  • I lowered the output power of the Pi’s wifi transmitter because I wanted to save as much power as possible, lower the amount of electrical noise the Pi generated, and keep the AP’s range as small as possible so as few people as possible can see it. To do that, add the following line to /etc/rc.local just before the “exit 0” line:
    iwconfig wlan0 txpower 5
    Change the value for txpower to whatever suits your needs (lower = lower transmitter power).
  • If you find you can’t connect to the AP or you want to change or update the Pi, you can always plug it back into the Ethernet port and connect to it that way.

Here’s what you’ll end up with (except with sound – my PC’s microphone isn’t working):

You can also ignore that last block of instructions and just hook the Pi up to an internal or HDMI-attached display. Some people find that using displays with resolutions of 800×600 or less cramps the GQRX display and makes navigation difficult. If you’re running VNC, you can set the resolution to whatever you want – just keep in mind that the Pi will start to sweat and lag if the display is too large.

If the Pi seems to be having trouble keeping up, try the following:

  • Make sure your Pi is adequately cooled and powered. Run vcgencmd get_throttled and if it shows any number other than 0x0, you have a temperature/power problem.
  • As mentioned above, lower the display resolution.
  • Remove the Raspbian desktop picture and replace it with a single colour.
  • In GQRX, one of the biggest CPU hogs is the main display. Go into the “FFT Settings” tab and lower the FFT Size and Rate. I use 8192 and 10fps on my Pi and that seems to work reasonably well.
  • Once you find the frequency you want to listen to, minimize GQRX. That should speed things up noticeably.
  • Lower GQRX’s sample rate by going into the “Configure I/O Devices” window (the little green PCI card icon) and lower the number. The HackRF in particular needs to be set lower than its 10MSPS default. 3-4MSPS for the HackRF and 2-3MSPS for the NESDR SMArt seems to do the trick.
  • Don’t run any extra services or applications on your Pi.
  • If you’re recording audio and it’s lagging or chirping, don’t record to the SD card – mount external USB storage (stick, drive, whatever) and use that instead.

The Pi – even the 4B – has its limitations. If you’re careful, though, you can pretty easily turn it into a small, portable, and powerful little SDR machine.

It’s Here!

My order of Raspberry Pi 4s and a couple of doodads for them arrived today!

Had a bit of trouble with VNC, need to specifically enable it in raspi-config and change the resolution from the default, also in raspi-config (I’m using 1280×720). Since then, things have been going well. This board is FAST!

World’s Crappiest Oscilloscope… v1

When Ms Geek gave me an Arduino Leonardo to play with, one of the first things I did was go through the examples. After almost two decades of experience with PICs, I was amazed at how easy it was to get things like serial communication and ADC working. Don’t get me wrong – I’m still a PIC guy… but I think I’m an Arduino guy now, too.

The ReadAnalogVoltage example caught my attention because it was so simple. Here’s the setup. It’s just a potentiometer with one end terminal connected to +5v and one to GND, and the wiper connected to A0:

Not much to it, eh? That bent yellow wire in the middle just holds the Leonardo in place.

I played around with it for a while and watched the output on the Serial Plotter, but then I had a thought. You need to run the Arduino software to use the Serial Plotter, and besides, the Serial Plotter looks too nice. Half-remembered days of coaxing dusty old VT100 and TN3270 terminals back to life and running a BBS made me think – I could do the same thing, but not as good!

I dug through some of my old PIC programs and found a serial terminal that I wrote back in 2002. Between that and the ANSI sequences at http://ascii-table.com/ansi-escape-sequences.php, I stapled together a really bad looking display that I like to call the World’s Crappiest Oscilloscope, v1. Here’s the program:

// World's crappiest oscilloscope v1
// Borrows heavily from ReadAnalogVoltage example in the Arduino Examples menu
// Uses Arduino Leonardo, reads voltage on analog pin A0, then uses good old ANSI
// codes to
// draw a really bad oscilloscope in a serial terminal.
// A little amusing but very useless.
// Info about ANSI codes is at http://ascii-table.com/ansi-escape-sequences.php
// **THIS PROGRAM IS FREE TO USE AND MODIFY AS YOU SEE FIT**

void setup() {

  pinMode(A0, INPUT); // Set pin A0 to input.

  Serial.begin(9600); // initialize serial communication at 9600 bits per second.

  delay(3000);  // Should be enough time to start up a serial terminal.

  // Warm up the tubes...
  Serial.write(27); // Clear terminal screen with ESC [2J and ESC is ASCII 27
  Serial.print("[2J");
  Serial.println("Warming up the tubes, please wait...");
  delay(2000);  // This just here for dramatic effect.
  Serial.println("Starting...");
  delay(2000);  // This also just here for dramatic effect.
}


void loop() {

  // Now set up the fancy oscilloscope screen. Ah, the good old ANSI days...

  // Set Oscilloscope screen to white markers on black background.
  Serial.write(27);
  Serial.print("[0;37;40m");
  
  Serial.write(27);  // Clear Terminal screen with (esc)[2J, (esc) is ASCII 27
                    // Serial.write sends binary data to the serial port
  Serial.print("[2J");
  Serial.write(27); // ESC again.
  Serial.print("[H"); // cursor to home.

  // The following lines draw the scale up the left side of the terminal screen and
  // the bottom border.
  Serial.print("5.00V|\r\n");
  Serial.print("4.75V|\r\n");
  Serial.print("4.50V|\r\n");
  Serial.print("4.25V|\r\n");
  Serial.print("4.00V|\r\n");
  Serial.print("3.75V|\r\n");
  Serial.print("3.50V|\r\n");
  Serial.print("3.25V|\r\n");
  Serial.print("3.00V|\r\n");
  Serial.print("2.75V|\r\n");
  Serial.print("2.50V|\r\n");
  Serial.print("2.25V|\r\n");
  Serial.print("2.0V0|\r\n");
  Serial.print("1.75V|\r\n");
  Serial.print("1.50V|\r\n");
  Serial.print("1.25V|\r\n");
  Serial.print("1.00V|\r\n");
  Serial.print("0.75V|\r\n");
  Serial.print("0.50V|\r\n");
  Serial.print("0.25V|\r\n");
  Serial.print("0.00V|________________________________________________________________________________\r\n"); // 80x _
  Serial.print("                         WORLD'S CRAPPIEST OSCILLOSCOPE v1\r\n");

  byte ColumnCount = 7; // Okay, there are 50 columns to put data into, starting at column 7 and ending at 57.

  while (ColumnCount <= 87){
    int sensorValue = analogRead(A0); // read the input on analog pin 0. Need to use an int because it's a 10-bit number.
    
    float voltage = sensorValue * (5.0 / 1023.0); // Convert the reading (which goes from 0 - 1023) to a voltage (0 - 5V).

    int OscOut = (voltage * 4);  // so far so good but need to make it go the other way
 
    OscOut = 21 - OscOut;

    // now, staple everything together into one string to control the cursor
    // Control cursor position: ESC then [line;columnH
    
    String OscStr;
    OscStr = '[';
    OscStr = OscStr + OscOut;
    OscStr = OscStr + ';';
    OscStr = OscStr + ColumnCount;
    OscStr = OscStr + 'H';

    Serial.write(27);

    // Just for kicks, let's try to change the trace colour to green.
    Serial.print("[0;32;40m");

    Serial.write(27);

    Serial.print(OscStr);
    Serial.print("*");

    ColumnCount = ColumnCount +1;
  
    delay(50); // wait a bit before going back so the screen doesn't fly by too quickly.
  }

}

I really need to figure out how to widen the blocks in this theme… it kind of mangles the formatting. If you copy and paste it directly, it still works though. This is what it does (don’t start the video unless you have a strong heart – it’s THAT amazing):

I wonder if there actually were any of those old terminals set up with something like this back in the day…

Fixed The Old Weather Station

I got a nice little weather station as a gift about a decade ago. It’s been pretty accurate and the display is usually nice and bright and colourful. Over the last while, however, the backlight has gone from bright, to flickering, to flashing once in a while, to completely dead. This made the unit almost useless unless it was daytime and you were standing right in front of it.

Today I cracked it open and gave fixing it a shot. It came apart pretty easily, but I learned later that with the problem I had that I didn’t need to remove the green circuit board. Removing it led to a lot of frustration and grief as it took me many tries to get everything lined up again.

Anyway, it turned out that there were two problems. One was that one of the wires wasn’t actually soldered to the backlight – there was a glob of solder and the wire was just resting against it. I thought that’d be an easy fix but unfortunately, it didn’t make any difference.

What I ended up doing was ignoring the original LED driver circuitry altogether and connected it to the power jack (tip positive). I put a two-pin female header in series to I could easily try different resistors:

1K was a little dim, 100 ohms was better, but I finally settled on 47 ohms. That made for about 110mA, well within a regular 1/4W resistor’s capacity, and considering there are four LEDs in parallel, that’s about 28mA per LED – nice and low. I’m pretty pleased with how it’s working at this point:

Nice and bright

Still, I’m going to run it for a few days to make sure that the resistor and backlight aren’t warming up before I solder the resistor in permanently and button the whole thing back up.

Hopefully this means I’ll get at least another ten years out of it.