Author Topic: LDO Circuit Design  (Read 2209 times)

ChemE

  • Sr. Member
  • ****
  • Posts: 402
  • Country: us
LDO Circuit Design
« on: July 12, 2016, 07:11:06 PM »
I'm thinking this is one of only a few places on the internet I can ask my question and not got an asinine answer.  Purely for the sake of fun I'm trying to design a power source for my Moteinos that can be left unattended for 5+ years.  I plan on using some remotely placed Moteinos to wake periodically and gather temperature, humidity, and light levels and then broadcast the data back to a gateway (very very standard I know).  I've read that some folks just use 2 AA cells (it would seem that 4th generation Eneloops have the absolute lowest self discharge rate) but this setup will start life at 2.8V and quickly drop to 2.4V before eventually going down to around 1.8V.  I know the 328 can run down to 1.8V but the DS18B20s need 3.3V right?  Wouldn't they never get enough current to work?

Assuming I'm not overlooking anything and sensors do need a 3.3V or greater supply, it seems the longest lived source would then be 4 AA cells.  They should start life at 5.6V and even just before total death still be putting out 3.6V.  This seems tailor made for pairing with the MCP1700-3302E which has a lower quiescent current than than 1702 at the expense of not handling input voltage higher than 6V.  Now, knowing what I plan to do with my uC, does it matter which capacitors I use on the input and output of my LDO?  I've found some threads where people call for caps much larger than 1uF for stability and aging purposes but I'm not an electrical engineer so I'm pretty out of my depth there.  I want this to be stable but consume a bare minimum of power.  Would the capacitor type and capacity affect power dissipation in this circuit and if so what should I select to minimize it?

Thanks!
ChemE (a chemical engineer)
« Last Edit: July 12, 2016, 10:43:25 PM by ChemE »

TomWS

  • Hero Member
  • *****
  • Posts: 1877
Re: LDO Circuit Design
« Reply #1 on: July 13, 2016, 07:33:19 AM »
In my opinion and experience, LithiumFE (Li-FeS2) primary cells are the longest lasting batteries, over the widest temperature range, for this kind of application.  Their initial charge is about 1.75V, it stablizes to about 1.7, and maintains this voltage until very close to end of life, dropping off the cliff at about 1.5 volts.   Hence, two of these, either AAA or AA sized will give you useful voltage for their entire life and their shelf life is 20 years (if you believe Eveready - I personally haven't experienced this - yet).  No need for an LDO and it's HIGH quiescent current  ;)

Also, once you have sufficient voltage to operate, throwing more batteries at the problem actually reduces battery life.  It's not about volts, it's about mAH and the mA load will increase with increased voltage while the mAH of each battery remains the same.  Net, 4 batteries forcing you to use an LDO will die sooner than the same 2 batteries where you don't need an LDO.

Regarding sensors, processors, and radios, most these days will operate well into 2V, quite a few down to 1.8V reliably.  The processor and RFM69 will operate down to 1.8V, I know this from personal experience and data from JoeLucid.  However, the ESR of the power source must be able to maintain voltage no less than 1.8V throughout operation.

If you use an external timer/WDT chip such as TPL5110 or TPL5010 to control sample sequencing, you can easily get your 'sleeping' current into sub-100nA region.

Given this, your goal of 5+ years is certainly achievable, with careful design practices...   Your greatest risk of failure is not from design currents draining your batteries, but corrosion causing leakage and unpredicted failures.

Tom

Update: added comment about extra batteries.
« Last Edit: July 13, 2016, 08:16:55 AM by TomWS »

ChemE

  • Sr. Member
  • ****
  • Posts: 402
  • Country: us
Re: LDO Circuit Design
« Reply #2 on: July 13, 2016, 11:15:48 AM »
Thank you Tom, you've given me a good deal to look into with that.  Not sure if there is a ton of interest in this community but I have some insanely compact DS18B20 code that I'm happy to share.  It uses the 328p's internal pull-up resistors instead of the clunky 4.7kOhm resistor everyone uses and the code itself with serial output is 1,054 bytes of sketch size with 74 bytes of SRAM all this without knowing the laser ROM on sensor but finding it and outputting it.  Just shove the sensor into ground/pin 13/pin 12 and fire up your serial monitor.  Dallas and OneWire are massive and slow by comparison.

gregcope

  • Full Member
  • ***
  • Posts: 158
  • Country: gb
Re: LDO Circuit Design
« Reply #3 on: July 13, 2016, 01:39:22 PM »
I would be interested in that code.

Can you alter precision for faster reads?

perky

  • Hero Member
  • *****
  • Posts: 871
  • Country: gb
Re: LDO Circuit Design
« Reply #4 on: July 13, 2016, 06:02:05 PM »
It's possible to use UARTs as well for one wire access, this is my prefereed way because it's fast and timing is accurate although you may require an external open drain buffer:
https://www.maximintegrated.com/en/app-notes/index.mvp/id/214
Mark.

TomWS

  • Hero Member
  • *****
  • Posts: 1877
Re: LDO Circuit Design
« Reply #5 on: July 13, 2016, 09:02:46 PM »
It's possible to use UARTs as well for one wire access, this is my prefereed way because it's fast and timing is accurate although you may require an external open drain buffer:
https://www.maximintegrated.com/en/app-notes/index.mvp/id/214
Mark.
Do you have a library you can post, Mark?  This is different than I've ever used and I think others would get some use out of it.

Tom

ChemE

  • Sr. Member
  • ****
  • Posts: 402
  • Country: us
Re: LDO Circuit Design
« Reply #6 on: July 13, 2016, 10:21:58 PM »
All,

Below is my current code for handling a DS18B20 via an Arduino Uno.  I admit that this is pretty low-level code; I really enjoy taking common chunks of code and distilling them down to their bare essentials and getting the sketch size uber compact so they can run on a much smaller uC.  For some reason I can't get millis to return a non-zero value to me right now (been over a year since I coded Arduino so I'm probably screwing something simple up) but I know that a 12-bit temperature conversion takes WAY less than 750ms; it is more like 200ish ms.  I plan on adding a bit of code so I can change the resolution of temperature measurements downward if less precision is needed.

Sketch size: 1,054 bytes || SRAM usage: 74 bytes
Code: [Select]
// Solder-free method of detecting the ROM of a DS18B20 - spread the 3 legs of the sensor wide enough to fit into GND, 13, and 12 
// and place the sensor in these pins with the flat side facing the LED on the Uno and the round side facing away from the Uno. 
// Then upload and open a serial monitor with a baud rate of 9600. No resistor is needed using this method.
#include <util/delay.h>

// ====================================================== Pre-Compiler Definitions ======================================================
// Direct port manipulation needed to conduct the OneWire bus
#define   PowerPin              PB4                       // Pin 12 - we will be using this pin to supply Vcc to the DS18B20
#define   POWER_TEMP_PROBE      PORTB |= _BV(PowerPin)    // Define method for powering the DB18B20
#define   DEPOWER_TEMP_PROBE    PORTB &= ~_BV(PowerPin)   // Define method for depowering the DB18B20
#define   Pin                   PB5                       // Set up pin 13 as the data pin
#define   DIRECT_MODE_OUTPUT    DDRB |= _BV(Pin)          // Much faster and smaller version of pinMode(Pin, OUTPUT)
#define   DIRECT_MODE_INPUT     DDRB &= ~_BV(Pin)         // Much faster and smaller version of pinMode(Pin, INPUT)
#define   DIRECT_WRITE_HIGH     PORTB |= _BV(Pin)         // Much faster and smaller version of digitalWrite(Pin, HIGH)
#define   DIRECT_WRITE_LOW      PORTB &= ~_BV(Pin)        // Much faster and smaller version of digitalWrite(Pin, LOW)
#define   DIRECT_READ           PINB & _BV(Pin) ? 1 : 0   // One line if else statement using the format [test ? true return : false return]

// Delay values needed for conducting a OneWire bus
#define   clk_div               1                         // This code assumes a processor frequency of 16MHz but this can be lowered as long as clk_div is updated
#define   DELAY_A               6/clk_div                 // Delay values obtained from http://www.maximintegrated.com/app-notes/index.mvp/id/126
#define   DELAY_B               64/clk_div
#define   DELAY_C               60/clk_div
#define   DELAY_D               10/clk_div
#define   DELAY_E               9/clk_div
#define   DELAY_F               55/clk_div
#define   DELAY_G               0/clk_div
#define   DELAY_H               480/clk_div
#define   DELAY_I               72/clk_div
#define   DELAY_J               410/clk_div

// DS18B20 command codes
#define   READROM               0x33                      // Read the ROM of a OneWire device; there must only be one OneWire device on the bus!
#define   STARTCONVO            0x44                      // Tells device to take a temperature reading and put it on the scratchpad
#define   READSCRATCH           0xBE                      // Read EEPROM
#define   SKIPROM               0xCC                      // Tells all OneWire sensors on the bus that the next command applies to them
#define   MATCHROM              0x55                      // Tells all OneWire sensors on the bus to listen for a specific ROM next

#define   myubbr                (F_CPU/clk_div/16/9600-1) // Baud rate for UART

int main() { 
  uint8_t ROM[8] = { 0x28, 0xA, 0xA3, 0x83, 0x4, 0x0, 0x0, 0x63 };
 
  // Initialize the UART
  UBRR0H = (unsigned char)(myubbr>>8);
  UBRR0L = (unsigned char)myubbr;
  UCSR0A = 0;//Disable U2X mode
  UCSR0B = (1<<TXEN0);//Enable transmitter
  UCSR0C = (3<<UCSZ00);//N81
  _delay_ms(100);
 
  DDRB = B00010000;      // Set Pin 12 as an output
 
  for(;;) {  // Loop forever
    // Perform a OneWire reset pulse
    POWER_TEMP_PROBE;      // This isn't neccesary but I show it to demonstrate how to make the project conserve energy
   
    simpletx("Presence pulse: ");
    if(reset()) {
      simpletx("Detected\t");
    } else {
      simpletx("Not Detected\t");
    }
   
    // Attempt to read the ROM, if nothing is present this will return 0x00 for each of the eight bytes
    simpletx("ROM is: ");
    write(READROM);
    for(uint8_t i=0;i<8;i++) {
      ROM[i]=read();
      simpletx("0x");
      txByteAsHex(ROM[i]);
      if (i!=7) simpletx(",");
    }
    simpletx("\t\t");
   
    // If we detected a Dallas family sensor, let's go ahead and take a temperature reading
    if (ROM[0]=0x28) {  // The first byte of all dallas sensors is always 0x28
      reset();
      write(SKIPROM);
      write(STARTCONVO);
      while(!read());  //_delay_ms(750);    // Can either wait 750 ms for the conversion to be done or else read until we get a 1 back from the DS18B20 meaning it is signaling complete
      reset();
      write(MATCHROM);
      for(uint8_t i = 0; i < 8; i++) write(ROM[i]);
      write(READSCRATCH);
     
      uint8_t tempLSB = read(); 
      simpletx("Temperature: ");
      txRawTempAsFloat( read()<<8 | tempLSB );
      simpletx("F\n");           
    }
    DEPOWER_TEMP_PROBE;  // Rather than perform a reset to tell the probe to stop sending data, just cut the power and it will get the message!
    _delay_ms(5000);
  }  // End for
}  // End main

static inline uint8_t read() {
  uint8_t r=0;
     
  noInterrupts();
  for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
    DIRECT_MODE_OUTPUT;
    DIRECT_WRITE_LOW;
    _delay_us(DELAY_A);
    DIRECT_MODE_INPUT;
    DIRECT_WRITE_HIGH;  // New line for no resistor modification / enable pull-up resistor
    _delay_us(DELAY_E);
    if (DIRECT_READ) r |= bitMask;
    _delay_us(DELAY_F);
  }
  interrupts();
  return r;
}

static inline void write(uint8_t v) {
  noInterrupts();
  for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
    DIRECT_WRITE_LOW;
    DIRECT_MODE_OUTPUT;
    if (bitMask & v) {
      _delay_us(DELAY_A);
      DIRECT_WRITE_HIGH;
      _delay_us(DELAY_B);
    } else {
      _delay_us(DELAY_C);
      DIRECT_WRITE_HIGH;
      _delay_us(DELAY_D);
    }
  }
  DIRECT_MODE_INPUT;
  interrupts();
}

static inline uint8_t reset(void) {
  noInterrupts();
  DIRECT_MODE_INPUT;     
  DIRECT_WRITE_LOW;
  DIRECT_MODE_OUTPUT;
  _delay_us(DELAY_H);
  DIRECT_MODE_INPUT;
  DIRECT_WRITE_HIGH;  // New line for no resistor modification / enable pull-up resistor
  _delay_us(DELAY_I);
  uint8_t ret = !(DIRECT_READ);
  interrupts();
  _delay_us(DELAY_J);
  return ret;
}

static inline void simpletx( char * string ) {
  /*if (UCSR0B != (1<<TXEN0)) { //do we need to init the uart?
    UBRR0H = (unsigned char)(myubbr>>8);
    UBRR0L = (unsigned char)myubbr;
    UCSR0A = 0;//Disable U2X mode
    UCSR0B = (1<<TXEN0);//Enable transmitter
    UCSR0C = (3<<UCSZ00);//N81
    _delay_ms(30);
  }*/
  while (*string) {
    while ( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = *string++; //send the data
  }
}

static inline void txByteAsHex(uint8_t inp) {
  char snd[3];
  uint8_t tmp = inp>>4;
 
  if (tmp<10) {
    snd[0]=48+tmp;
  } else {
    snd[0]=55+tmp;
  }
 
  tmp=inp%16;
  if (tmp<10) {
    snd[1]=48+tmp;
  } else {
    snd[1]=55+tmp;
  }
  snd[2]='\0';
  simpletx(snd);
}

// Converts the raw temperature from a DS18B20 directly to a string containing the temperature in F with 1 decimal place
// avoids unnecessary floating point math, float variables, and casts, and 32-bit math
// TODO: May not work properly with temperatures below 32F
static inline void txRawTempAsFloat(uint16_t raw) { 
  char buffer[6];

  uint8_t decimalPos = 2;  // default case of a temp between 0 and 99.9
  uint8_t nullPos = 4;     // default case of a temp between 0 and 99.9
  uint16_t temp;
 
  // Check to see if the temperature passed in is negative
  if (raw>>11) {
    // Can't get here unless one of the 5 most-significant bits are ones which means we have a negative number, convert it
    raw = ~(raw-1);    // Convert the two's compliment number back into one's compliment
   
    if (raw > 284) {  // This temperature is far enough negative in the celcius scale that it is also negative on the farhenheit scale
      decimalPos += 1;   // Account for the negative sign's place in the string
      nullPos += 1;      // Account for the negative sign's place in the string
      buffer[0] = '-';   // Write the negative sign in the string
    }
    temp = (9*raw)/8-320;                   // Keeps only 1 decimal place but uses 16-bit math
  } else {
    temp = (9*raw)/8+320;                   // Keeps only 1 decimal place but uses 16-bit math
  }


  // Convert the raw temperature into the temperature in Fx10 so that one decimal place is kept
  //uint32_t temp = (raw*1125ul+320000ul)/1000ul;  // Keeps all 4 decimal places but uses 32-bit math 
  if(temp>=1000) {  // We're looking at a positive number with three digits
    decimalPos += 1;
    nullPos += 1;
  }
 
  buffer[nullPos--] = '\0';
  do {
    if (nullPos==decimalPos) buffer[nullPos--] = '.';
    buffer[nullPos--] = temp % 10 + '0';
    temp /= 10;
  } while (temp); 
 
  simpletx(buffer);
}

EDIT:  Just recalled that to get millis() and micros() to work using my program structure I have to call the init() routine which is usually called by setup using the default arduino structure of setup() and loop().
« Last Edit: July 13, 2016, 10:42:11 PM by ChemE »

perky

  • Hero Member
  • *****
  • Posts: 871
  • Country: gb
Re: LDO Circuit Design
« Reply #7 on: July 14, 2016, 05:07:49 AM »
It's possible to use UARTs as well for one wire access, this is my prefereed way because it's fast and timing is accurate although you may require an external open drain buffer:
https://www.maximintegrated.com/en/app-notes/index.mvp/id/214
Mark.
Do you have a library you can post, Mark?  This is different than I've ever used and I think others would get some use out of it.
Tom
The code I have is unfortunately under NDA. Using a UART is a clever solution to this problem though.
Mark.