Teensy 4.1 Browning Out - USB Battery Pack

Status
Not open for further replies.

grinch

Well-known member
Hi, I am working on a battery powered wearables project where I need to drive 4 LED strips and a 4 5V fairy lights (via darlington transistors plus PWM) using a motherboard PCB I've designed for the Teensy 4.1. I have my system working well, except that when I try to use it with a USB battery back the Teensy 4.1 browns out at a certain point, and gets stuck at a point in the program loop that's extremely hard to track down since it also shuts down serial communication. When I have the system running off a brick or wall wart power supply the loop runs cleanly, and has made it through a 12 hour overnight test.

All the elements of the project are a bit complex to post here all at once, but I wanted to see if y'all could answer some questions that might help me in troubleshooting.

1. What is minimum voltage the Teensy 4.1 can accept at its input before browning out? The product page marks the VIN pin as taking 3.6V to 6V, which seemed like it would be a safe margin for a 5V nominal output. My understanding is that this VIN pin connects to a 3.3V LDO regulator which powers the onboard electronics. When looking at the voltage out of my USB battery with a volt meter I see it fluctuating between 5.5 and 6V with brightness, which doesn't seem like it would cause any issues. I have never seen the voltage duck to anywhere close to 3.6V. Wondering if something else is going on here.

2. Does using the 4.1 SD card reader change voltage requirements at all? Does using a Timer Interrupt change voltage requirements? My assumption is that they wouldn't but I figured I'd check.\

3. Would putting another regulator between the battery voltage and the Teensy input help anything? I have been looking at 4V LDOs which could potentially provide a steady 4V from the 5V nominal input, but my understanding is that the onboard regulator should already be doing this, so I don't know if this would make any difference.

4. Is there anything else y'all can think of that I should check in this type of situation?

Here is the usb battery I am working with: https://www.amazon.com/gp/product/B01LYVF137/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1


Here is the schematic for my driver board:

Screen Shot 2020-11-24 at 10.52.43 PM.jpg

Here is the code I am running to test with:

Code:
#include <SD.h>
#include <Adafruit_NeoPixel.h>
#include "CopyRgbSD.h"

#define LED_PIN     12
#define LED_COUNT  96
#define BRIGHTNESS 127
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2(LED_COUNT, 11, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip3(LED_COUNT, 10, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip4(LED_COUNT, 9, NEO_GRB + NEO_KHZ800);

#define PWM1 2
#define PWM2 3
#define PWM3 4
#define PWM4 5

IntervalTimer myTimer;

uint32_t color;
int pwmVal = 0;
int dir = 8;

uint32_t last2 = 0;

CopyRgbSD copySD;

void doOutputSD(){
  __disable_irq();
  if(millis() - last2 > 10000){
    Serial.println("Running Interrupt");
    last2 = millis();
  }
  for(int i=0; i<strip.numPixels(); i++) {
    copySD.readFromBuffers(&color);
    strip.setPixelColor(i, color);
    strip2.setPixelColor(i, color);
    strip3.setPixelColor(i, color);
    strip4.setPixelColor(i, color);
  }
  strip.show();
  strip2.show();
  strip3.show();
  strip4.show();
  pwmVal += dir;
  if(pwmVal == 256){
    dir = -8;
  }else if(pwmVal == 0){
    dir = 8;
  }

  analogWrite(PWM1, pwmVal);
  analogWrite(PWM2, pwmVal);
  analogWrite(PWM3, pwmVal);
  analogWrite(PWM4, pwmVal);
    
  __enable_irq();
}

void setup() {
  Serial.begin(9600);
  delay(3000);
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(255);
  strip2.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip2.show();            // Turn OFF all pixels ASAP
  strip2.setBrightness(255);
  strip3.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip3.show();            // Turn OFF all pixels ASAP
  strip3.setBrightness(255);
  strip4.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip4.show();            // Turn OFF all pixels ASAP
  strip4.setBrightness(255);

  pinMode(PWM1, OUTPUT);
  pinMode(PWM2, OUTPUT);
  pinMode(PWM3, OUTPUT);
  pinMode(PWM4, OUTPUT);

  analogWrite(PWM1, 0);
  analogWrite(PWM2, 0);
  analogWrite(PWM3, 0);
  analogWrite(PWM4, 0);

  Serial.println("starting");

  copySD.begin("RGB11.TXT");
  // put your setup code here, to run once:
  myTimer.priority(0);

  float outputRate = 1000000.0f / 30.0f;

  Serial.println(outputRate);
  
  myTimer.begin(doOutputSD, outputRate);
  
}

uint32_t last = 0;

void loop() {

  if(copySD.checkOverrunAndClear()){
    Serial.println("Buffer Overrun!");
  }
  if(millis() - last > 10000){
    Serial.print("Time Running:");
    Serial.println(millis() / 1000);
    last = millis();
  }
  copySD.copyToBuffers();
//  strip.setBrightness(analogRead(A14));
//  Serial.println("Main loop");
  // put your main code here, to run repeatedly:

}

Code:
//modified from play_sd_wav.cpp by Emmett Palaima

#include <Arduino.h>
#include "CopyRgbSD.h"
#include "spi_interrupt.h"

bool CopyRgbSD::begin(const char *filename)
{
	write = read = copy = 0;
	isRepeating = true;
  if (!(SD.begin(BUILTIN_SDCARD))) {
    // stop here, but print a message repetitively
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
	if(!open(filename)){
    return false;
	}
  for(int i = 0; i < COPY_BUFFER_COUNT; ++i){
    ready[i] = false;
  }
  copyToBuffers();
  return true;
}

void CopyRgbSD::readFromBuffers(uint32_t *color)
{
	if(ready[read] && rgbFile){
		*color = copyBuffer[read][index];
		index++;
		if(index >= FRAME_SIZE){
			index = 0;
			ready[read++] = false;
			if(read >= COPY_BUFFER_COUNT){ read = 0; }
		}
	}else{
		if(!underrun && !underrunInternal && rgbFile){ overrun = true; }
	}
}

void CopyRgbSD::copyToBuffers(void){
	while(rgbFile && !ready[copy]){
		unsigned int avail = rgbFile.available();
		if(avail >= MULTI_BUFFER_SIZE){
			rgbFile.read(buffer, MULTI_BUFFER_SIZE);
		}else{
      Serial.println("Rollover");
			unsigned int rollover = MULTI_BUFFER_SIZE - avail;
			rgbFile.read(buffer, avail);
			rgbFile.seek(0);
			rgbFile.read(&buffer[avail], rollover);
		}
		unsigned int count = 0;

    uint8_t r, g, b, a;
		write = 0;
		while(count < MULTI_BUFFER_SIZE){
      r = buffer[count++];
      g = buffer[count++];
      b = buffer[count++];
      a = buffer[count++];
      copyBuffer[copy][write++] = (a << 24) | (r << 16) | (g << 8) | b;
		}
    ready[copy++] = true;
    if(copy >= COPY_BUFFER_COUNT){ copy = 0; }
	}
}


bool CopyRgbSD::open(const char *filename)
{
	__disable_irq();
	rgbFile = SD.open(filename);
  Serial.println(filename);
	Serial.println(rgbFile.available());
	if(!rgbFile) Serial.println("File not found!");
	__enable_irq();
	if (!rgbFile) {
		return false;
	}
  rgbFile.seek(0);
	return true;
}

Code:
//modified from play_sd_wav.h by Emmett Palaima

#ifndef copy_rgb_sd
#define copy_rgb_sd

#define COPY_BUFFER_COUNT 16
#define FRAME_SIZE 96
#define MULTI_BUFFER_SIZE (FRAME_SIZE * 4)

#include "Arduino.h"
#include "AudioStream.h"
#include "SD.h"

class CopyRgbSD
{
public:
	CopyRgbSD(void){};
	bool begin(const char *filename);
	bool open(const char *filename);
	bool setRepeating(bool onOff){ isRepeating = onOff; }
	void readFromBuffers(uint32_t *color);
	void copyToBuffers(void);
	bool checkOverrunAndClear(){
		bool check = overrun;
		overrun = false;
		return check;
	};
  	bool checkUnderrunAndClear(){
	    bool check = underrun;
	    underrun = false;
	    return check;
  	};
  	unsigned long getOverrunInterval(){
	    if(!overrun){ return 0; }
	    else{
		unsigned long count = overrunTime;
		overrunTime = 0;
		overrun = false;
		return count;
    }
  }
private:
	uint32_t copyBuffer[COPY_BUFFER_COUNT][FRAME_SIZE];
	bool ready[COPY_BUFFER_COUNT];
	int16_t write, read, copy;
	int16_t index = 0;
	bool error = false;
	bool underrun = false;
	bool underrunInternal = false;
	bool overrun = false;
	unsigned long overrunCounter = 0;
	unsigned long overrunTime = 0;
	File rgbFile;
	uint8_t numChannels;
	uint8_t buffer[MULTI_BUFFER_SIZE];		// buffer one block of data
	bool isRepeating;
};

#endif
 
Hi, I am working on a battery powered wearables project where I need to drive 4 LED strips and a 4 5V fairy lights (via darlington transistors plus PWM) using a motherboard PCB I've designed for the Teensy 4.1. I have my system working well, except that when I try to use it with a USB battery back the Teensy 4.1 browns out at a certain point, and gets stuck at a point in the program loop that's extremely hard to track down since it also shuts down serial communication. When I have the system running off a brick or wall wart power supply the loop runs cleanly, and has made it through a 12 hour overnight test.

All the elements of the project are a bit complex to post here all at once, but I wanted to see if y'all could answer some questions that might help me in troubleshooting.

1. What is minimum voltage the Teensy 4.1 can accept at its input before browning out? The product page marks the VIN pin as taking 3.6V to 6V, which seemed like it would be a safe margin for a 5V nominal output. My understanding is that this VIN pin connects to a 3.3V LDO regulator which powers the onboard electronics. When looking at the voltage out of my USB battery with a volt meter I see it fluctuating between 5.5 and 6V with brightness, which doesn't seem like it would cause any issues. I have never seen the voltage duck to anywhere close to 3.6V. Wondering if something else is going on here.

(Just to be clear, I am a software guy, if somebody with real EE knowledge posts, believe them and not me).

Note the product page says the limit is 5.5v, not 6v. IIRC, Paul has said that it can tolerate some voltages above 5.5v, but it isn't designed for it.

And depending on who manufactured the LED strips, these strips can vary in terms of whether they support voltages under or over 5v. Sometimes sellers 'forget' to update datasheets when changing suppliers and/or manufacturers 'forget' to update datasheets when coming out with a new modification.

As I recall from Paul's postings, there are two limits for power from the Teensy:
  • If you are drawing power from 3.3v, the limit is around 0.25A (250mA);
  • If you are drawing power from the VIN pin, there is fuse that limits the power to roughly 0.5A (500mA) from the USB pin.

If you need to draw more than 0.5A of power, you need to add a connection before the USB cable gets to the Teensy.

I have seen the brownout condition on the Teensy 4.1 when the LEDs consumed too much power (i.e. the brightness was ramped up to the max).

I tend to think if If your battery charger is delivering 5.5 - 6v constantly, it is out of spec, and you may potentially fry anything you connect to it. Generally it should be something like 4.8 to 5.2 volts. I would suggest thinking about getting a different USB battery.

Teensys do not identify themselves that they need higher voltages and such, so typically a lot of smart chargers will limit the power to 5 volts and 0.5A (500 mA). This is the spec that the USB 2.0 standard says should be provided. Some chargers will give more amps to devices that do not identify themselves, but you would need to see what a particular charger gives you.

You mentioned 'voltmeter', you probably should be thinking instead of a voltmeter to use a combined voltmeter and ammeter to measure both the voltage and amps being delivered. One combined voltmeter and ammeter that I like is the UM34C meter. It can take input from a USB 3.0 A male plug, USB 2.0 male micro-B plug, or USB 3.1 male C plug and deliver output to a USB 3.0 A female plug that you can plug in a normal USB micro-B cable. In addition to displaying both the volts and amps on the screen, it can send out the information via bluetooth to an appropriate smartphone. It has a screen to show the volts/amps over time, and it can also export a table of volts/amps to the phone that you can upload to a computer. There are some related meters (UM25C, UM24C) that have slightly different inputs and outputs:


Note, with Android phones, you will need to enable GPS in order to connect to the bluetooth device.

In terms of battery, I recently picked up a Romoss 20000mAh battery that I like. I needed a battery that supported USB C-PD (power delivery) that could deliver 9v and at least 1A of power. It has a digitial display, and it can show either the volts or amps being delivered (the above meters deliver more information, but it can be useful to get a quick sense of the amps being delivered):
 
Last edited:
4. Is there anything else y'all can think of that I should check in this type of situation?

I see you're using IntervalTimer to run a *lot* of stuff which generally isn't considered safe to run from an interrupt. Looks like you're also completely disabling interrupts, but while interrupts are off you're using APIs like millis() and Serial.print() which depend indirectly on interrupts, and might unexpectedly reenable interrupts.

I would highly recommend avoiding IntervalTimer. Interrupts are almost impossible to use correctly & reliably in the way you're crafting this code.

Use elapsedMillis() or elapsedMicros() inside loop() to decide when you need to update the LEDs.

You might also consider using the non-blocking WS2812Serial instead of Adafruit_NeoPixel, which blocks all interrupts while the LEDs update. This will allow your program to have much better performance, since you can read the next data from the SD card while WS2812Serial busy doing the work of sending to the LEDs.


Now, to quickly answer your many power-related questions.....


1. What is minimum voltage the Teensy 4.1 can accept at its input before browning out?

Typically about 3.4V.

Brown out should be detected when the 3.3V power drops to around 2.9V and shut down the board. It should automatically reboot when the 3.3V power recovers to about 3.0V to 3.1V.

If the Teensy is locking up and remaining unresponsive, that's almost certainly a software crash, which is incredibly common when interrupts are used in risky ways (or even in seemingly safe ways ... interrupts are very hard to use 100% reliably). The MKL02 chip provides hardware detection of brown-out and automatically reboots the main processor when voltage is restored to a usable level, so brown-out problems should manifest as unexpected reboots, not stalls or lockups.


Wondering if something else is going on here.

Yes, that's why I started with answering your last question. ;)


2. Does using the 4.1 SD card reader change voltage requirements at all?

No, other than the small increase in current used by the SD card.


Does using a Timer Interrupt change voltage requirements?

No.


3. Would putting another regulator between the battery voltage and the Teensy input help anything?

From everything you've said so far, probably not.

One caveat is we can't see how you've wired up this project. A photo might help. Generally the best way is to run the LED power and ground as directly as possible to the power source using the shortest practical wire length.

Just keep in mind this answer is based only on the text you're written and info you've given, which doesn't include a photo to see the actual wiring.




Here is the usb battery I am working with

Specs on these sorts of products usually fall into 2 categories: missing info or outright lies. It's almost certainly not a 30AH battery!

While I suspect your problems are software based, I would recommend using a proper 5V power supply for the sake of testing.
 
I see you're using IntervalTimer to run a *lot* of stuff which generally isn't considered safe to run from an interrupt. Looks like you're also completely disabling interrupts, but while interrupts are off you're using APIs like millis() and Serial.print() which depend indirectly on interrupts, and might unexpectedly reenable interrupts.

I would highly recommend avoiding IntervalTimer. Interrupts are almost impossible to use correctly & reliably in the way you're crafting this code.

Use elapsedMillis() or elapsedMicros() inside loop() to decide when you need to update the LEDs.

You might also consider using the non-blocking WS2812Serial instead of Adafruit_NeoPixel, which blocks all interrupts while the LEDs update. This will allow your program to have much better performance, since you can read the next data from the SD card while WS2812Serial busy doing the work of sending to the LEDs.

Okay, that makes sense, thank you for the feedback. I have used this exact interrupt configuration (interrupt handling output, loop filling a data buffer from an SD card) before on an audio related project to good effect, so I figured it might work here as well. The function of the interrupt in the previous project was a simple SPI write, rather than getting into whatever the neopixel library is doing under the hood, so it makes sense that it might not work as well in this situation. I'll try doing everything in the main loop and see if that makes a difference.

The output pins on the PCB I've designed are designed are already set, and I'd like to avoid doing a PCB redesign so I probably can't use the library you recommended. Would switching to FastLED see any improvement? My understanding is that there isn't much difference between FastLED and NeoPixel, but I'd be willing to try that out.

Would the Neopixel interrupt blocking cause any problems with micros() or elapsedMicros() being used for timing, or would using the two together be alright? I get that there is some loss in efficiency doing it this way, but as long as the timing remains relatively accurate I think I have enough overhead for this to be fine (I am only doing LED writes 30 times a second).
 
The output pins on the PCB I've designed are designed are already set, and I'd like to avoid doing a PCB redesign so I probably can't use the library you recommended. Would switching to FastLED see any improvement? My understanding is that there isn't much difference between FastLED and NeoPixel, but I'd be willing to try that out.

While it maybe more complicated to use, the OctoWS2811 LED library on the Teensy 4.0 and 4.1 can use any arbitrary pins:

The WS2812Serial library is limited to pins that are the TX lines for serial UARTs. On the Teensy 4.1, this would be pins 1, 8, 14/A0, 17/A3, 20/A6, 24/A10, 28, and 35 (plus pin 47 inside the micro-SD card reader, and pin 53 on the solder pads underneath the 4.1 for adding memory).
 
Status
Not open for further replies.
Back
Top