ESP32 IoT core board with flexible power and flexible communications
The PCB layout is done!
This was a hard one. In hindsight, it might have been better to go to a 4-layer board to get it done faster. But now it's done, I'm happy with the result and I think it will work well. To get all power and signal traces where they need to go all over the board, I needed to have extensive routing on both layers, so there isn't much left of a ground plane on the bottom layer. But with plenty of judicious via stitching between ground on the top and bottom layers, I think it's still a solid enough ground plane overall.
The PoE section on the bottom left is very similar to that found on the #wESP32. This time I'm using a smaller flyback transformer and smaller diode on the secondary side because we only need about 6W here, not 13 W as on the wESP32. The Ethernet PHY has moved to above the Ethernet jack. This means it's closer to the jack than on the wESP32 so performance should be as good or better.
We moved from an ESP32-WROOM-32 to a ESP32-WROVER-I module to take advantage of PSRAM for larger projects and have plenty of space for MicroPython. The positioning of the module may look bad for antenna performance, but since we use the -I version with U.FL connector and an external antenna, the on-board antenna is not used, and the module's position is fine.
Programming and serial console is provided by a wESP32-Prog submodule as on the wESP32, the footprint for this option (J2) is located central on the PCB along the bottom edge as on the wESP32.
Most of the new stuff is on the right side of the PCB. On top is a micro SD card slot, wired for 1-bit access from the ESP32. 1-bit is not the fastest but it's a compromise to preserve enough ESP32 pins for GPIO connections to external circuitry. I wanted to have at least enough pins available for simultaneous use of I2C (2 pins) and SPI (4 pins), and with the pins taken up by the Ethernet RMII bus (8 pins) and the PSRAM (2 pins), and the unfortunate number of input-only pins on the ESP32 (4 pins), 1-bit SD access was as good as we were going to get.
Central on the board is the buck-boost converter that generates the 3.3 V system power from the battery voltage, which can range from 1.71 V to 3.6 V (minimum LTO to maximum LiFePO4 voltage). The converter should be able to provide at least 1 A even at the lowest input voltage. The central location worked out well to use a star topology for system power distribution.
To the right and below this is the microcontroller based charging system. This was the hardest part to layout because the various current loops need to stay as small as possible to reduce EMI and maintain high performance. I went with the NCP81151 based gate driver to minimize cost and take advantage of the built-in zero cross detection and synchronous rectification for maximum efficiency. This came at the cost of more components for regulation and level shifting, so it was a very tight squeeze to pack it all in. I had to use resistor, capacitor and transistor arrays to make it work.
External connections are clustered on the bottom right. The reason for this is that I wanted to keep the top edge of the PCB clear of through-hole components so it is possible to install an optional on-board 18650 battery holder there on the bottom of the PCB between the BAT+ and BAT- pads. Since the battery holder makes the mounting holes along the top unreachable, a central mounting hole was added as well. The on-board battery should be a good option for development and bench-top use, but I expect that most production installations will use an external battery connected to the screw terminal connector along the bottom, along with DC/solar power input, if that is used.
The GPIO header needs to be in the bottom right as well for the same reason, to not interfere with the optional on-board 18650 battery. This made the layout harder because a lot of traces needed to be...Read more »
I got a FRDM-KL27Z development board to be able to test some things in the design before committing to them in a PCB layout. Initially I had intended to use this to build and verify the complete charge buck converter, but that was before I realized I need a proper gate driver. I am no @greg davill, you won't see me put together a dead bug prototype with 0.5 mm pitch QFNs under a microscope. So a complete charge converter prototype won't happen before the PCBs are made.
But, I decided the dev board would still be useful to do some testing of the ADC for current sensing. One of the reasons I chose the MKL17Z64VFM4 is because when I was designing the custom project this is based on, I liked the built-in ADC and reference of the MK02FN64VLF10 I was using for that project and found I could do current sensing directly with the micro using the differential 16-bit ADC inputs. I decided I should verify whether this chip worked as well for the purpose.
Battery heater decisions
Surprisingly, the need for ADC data came out of the need to settle on a particular implementation for the battery heater driver. I am driving the heater from the battery voltage in this design. The idea is to again make things more automatic and easier for the user. In the #LiFePO4wered/Solar1, I drive the heater from the incoming voltage. The downside is that no MPPT happens, the heater has to be carefully designed to not overload the solar panel and make the voltage collapse. In this board I want to power the heater from the battery (much more predictable), and supplement as much power from the external input as available. I also want to use the solar power most efficiently, so MPPT is desirable for the heater power. Instead of building a completely separate MPPT converter for this purpose, I'm using the charger MPPT converter and divert all available power to the heater up to the point where all power comes from the input.
There are two topologies I considered:
In topology 2, you can see a single current sense resistor. It's the battery current sense, but it can also measure the heater current. More correctly: it can measure whether the heater driver is diverting all power from charging the battery to heating the battery. We may not know what the current is exactly, but we know when the battery current is zero and can implement something in the MPPT control loop to maintain this.
In topology 1, you can see two current sense resistors. One is to measure the battery current, the other measures the heater current. It is necessary to add the second current sense resistor because the battery current sense provides no visibility into the heater current, we sense the current whether it flows through the battery or the heater. The upside is that we still know the absolute current value. We can build a control loop to make the heater current match the battery current. The downside is that we need another current measurement, and it is a high side current measurement (near supply voltage). This means we have to use a differential ADC channel for this.
Testing the ADC
"Remember kids, the only difference between screwing...Read more »
So in a previous project log I had been trying to figure out how to design this without a proper gate driver, and drive the charger MOSFETs directly from the micro.
I have since come to the conclusion that this is a stupid idea. :) In the log referenced above, I mentioned two purposes of a gate driver:
The latter is what made driving the gates directly from the microcontroller already look bad. But there's another function gate drivers provide that I had forgotten:
Level shifting the high side gate drive signal does not just involve shifting the high level, but the source voltage for this MOSFET (and "ground" reference for the driver) is shifted as well every cycle!
Details can be found in the Wikipedia article on buck converters, but here's the graph from it:
As you can see, the switching node voltage (Vd in the graph) continuously switches between 0V and Vi, which in my case can be up to 28 V. The gate driver ensures that the high side MOSFET gate keeps the correct voltage level relative to Vd, no matter what Vd is doing.
Proper gate drivers contain a special circuit feature called a floating well that takes care of this and a level shifter that translates the incoming PWM signal into a signal that is referenced to this node that's bouncing up and down. There's just no way that I can easily replicate this functionality without using a gate driver. Turns out people knew what they were doing when they started making these parts and they exist for a good reason. :)
My biggest objections to adding a gate driver were cost and PCB space. When I started searching for gate drivers that would work with my minimum signal voltage levels of 1.71 V and maximum input voltage of 28 V, I quickly found out that there was a good reason I had chosen the MIC4600 in the customer project this is based on: it's pretty much the only part on the market that fulfills both requirements. Unfortunately, the lowest price I've seen for them is $0.978 in qty 1000. That's a significant cost adder.
I wanted to know what else was out there. What was I missing out on with my extreme requirements? Were there any requirements I could relax just a bit so a lower cost part could work?
So I removed my search filters and went for the lowest cost suitable part i could find: the NCP81151, which is only $0.175 in qty 1000! In addition to the low cost, it has some other advantages. It's a synchronous driver which means that instead of having to control both high and low side MOSFETs from the micro, I can provide a single PWM signal and the gate driver will manage the high and low side drivers by itself. In addition, it has zero cross detection on the low side driver which makes it automatically behave correctly if the system goes into discontinuous mode (inductor current falls to zero). This is a really big deal to me because there's no good way I know to handle this from the micro other than to estimate and run in asynchronous mode for low currents and synchronous for high currents. Having the zero cross detection makes this headache go away.
What would it take to make this part work in my design? The first obvious thing: it requires 5 V, not 28 V. The floating well can take 35 V so my input voltage range is fine, but I need a voltage regulator to supply the gate driver itself. In some way this isn't any different from the MIC4600. It also runs from 5 V, but has a regulator integrated on the same chip.
I need a regulator...Read more »
So I've entered this project into the Hackaday Prize 2019 contest. Not sure if it's a good idea or not. I'm entering very late. There are many other good projects that jumped on the opportunity right away and have collected tons of followers and likes (and bootstrap money) by now, while I've just started to document my early stages of development. Some projects look like they have fully functional hardware at this point, while I have just a whiteboard sketch.
But, since it's about designing products this year, I really can't let it slide, since that's what I do. :) We'll see if I can manage to attract some attention from the judges with what I have documented by now. I'm hoping that my track record of turning the #LiFePO4wered/Pi+ and #wESP32 into successful products will be helpful. It just didn't fit into my schedule to kick off the project any earlier.
I considered letting this year's prize pass and submitting the project to next years prize, but who knows what theme it's going to be about next year? Probably nothing that would fit a product like this. Designing something that's not just a hack but a product, that's something I can do.
I hope they're not getting me on some technicality this time. Like: I have not yet detailed my license because there's nothing to license yet! When there's a schematic, it will be CC-BY-SA like most of my stuff. There isn't a detailed description yet because I'm still designing the thing so I don't know what it's going to be yet.
So, let's just see what happens. :)
Gate drive level concerns
I had unexpected difficulties in finding good power MOSFETs to implement the charge converter. My desire to have a large 5 to 28 V input voltage range turned out to be making life difficult here. It's easy enough to find MOSFETs with high Vdss breakdown ratings. It's also easy enough to find MOSFETs with low Vgs thresholds. But having the two requirements combined in a single part turned out to be harder than expected.
A cursory glance at part specs might make you think differently. There are plenty of parts that are listed as having low threshold voltage, but usually the threshold voltage is specified for silly low drain currents such as 250uA or 1mA. I need a part that is fully on, with low RdsON, at the minimum microcontroller voltage of 1.71V.
The reason is that I want to keep the charge circuit as simple as possible. In the custom project that inspired this charger's implementation, I was using a boost converter to generate 5V gate drive voltage and a MIC4600 gate driver chip to drive the MOSFETs. For the LiFePO4wered/ESP32 I want to do away with those parts and drive the converter MOSFETs directly from the microcontroller, if at all possible.
Keeping these requirements in mind, while also keeping cost and size down, and considering Safe Operating Area (SOA) so I don't have MOSFETs burst into flame at high input voltages this time, the best option I have found is the DMN3020UFDF.
With a Vdss breakdown voltage of 30V, a maximum RdsON of 40 milliohm and drain current of 10 A specified for a Vgs of 1.8 V, plus the best looking SOA graph I could find, it looks like this part should work well to implement an efficient charger without needing to add a gate voltage booster or separate gate driver chip.
Switching speed concerns
At least when it comes to gate drive voltage level that is. What I'm still worried about is the switching speed I can achieve when driving the MOSFETs directly from the micro. After all, the function of a gate driver like the MIC4600 is not only to provide the right gate voltage levels, but also to provide powerful enough drivers to quickly charge and discharge the power MOSFET's gate capacitance.
The DMN3020UFDF specifies a total gate charge of 15 nC at 4.5 V Vgs, 27 nC at 8 V Vgs. This seems to be scaling linearly (makes sense since Q = C * V), so I'm assuming about 12 nC gate charge at 3.6V and 6.6 nC at 2 V. I will be driving the gates from high drive capable pads of the microcontroller. They are specified at 20 mA (0.5V drop) for 3.6 V supply and 10 mA for supplies less than 2.7 V. This gives an estimated switching time of 600 to 666 ns. That is... slow. Compare this to the typical switching time of about 15 ns for the MIC4600 gate driver.
The problem with slow switching is twofold. First, it directly limits the switching frequency because obviously you want on and off times that are significantly longer than the transition between them. Second, during switching is when the MOSFET dissipates the most power as heat. When fully off, the voltage across a MOSFET is high but no current flows, so there's no power loss. When fully on, the current though the MOSFET is high but the voltage across it is really low (due to the low RdsON) so power loss is low. It is during a switch between on and off that significant values for both voltage across and current through the MOSFET are present at the same time, causing power loss (P = V * I) that is dissipated as heat.
Heat causes circuit problems and reduces efficiency. The solution to minimizing this problem is to switch less often (less time spent in switching transitions). But lower switching frequency demands a larger value inductor, which will either have to become physically larger and more expensive, or will have higher resistance, again reducing...Read more »
I'm using the ESP32-WROVER-I with external antenna for this project to take advantage of the PSRAM. Downsides are large physical size and losing two GPIO.
The LAN8720A works well on the #wESP32, is well supported by the ESP32 tools and is readily available at low cost in the Chinese market so I'm sticking to it for this project as well.
I have been considering moving to one of the PHY clocking modes where the ESP32 outputs the PHY clock instead of using an external crystal oscillator. Firmware support for this seems to be improving. But the most common way to do this is by using GPIO16 or GPIO17, which are not available on the ESP32-WROVER module because they are in use for the PSRAM.
Another option is using GPIO0 for clock output instead of input, but that seems to be less well supported, as can be seen in the MicroPython patch lined above, where it is disabled. Alas, I think I'll keep the oscillator for now.
Same for the PoE power chip, I'm sticking with the SI3404A used in the wESP32. I've been looking at the TPS23755 as a possible replacement that does away with the need for an opto-coupler, but considering this is a new part that I haven't tested yet and that is not as common in distribution, and considering there's enough to figure out and plenty of risk on the charger side of this project, I have decided to pick my battles and go with a proven solution for this part. :)
Charge / system controller
In the custom design that is the inspiration for this part of the project, I was using the NXP MK02FN64VLF10. This is a pretty powerful Cortex-M4F running at 100 MHz and probably overkill, but a good safe core with plenty of margin for developing something new.
The excellent voltage reference and 16-bit ADC with several differential inputs were the most important features I was after. I had added optional current sense amplifiers to make sure I would be able to measure the minute voltages across the 0.001 ohm and 0.02 ohm current sense resistors accurately enough, but it turns out they aren't needed--the ADC by itself does the job just fine!
It also has a great supply voltage range of 1.71 V to 3.6 V, which perfectly matches what we get from a LiFePO4 or LTO cell. When I designed in the part, I also assumed it would give me 100 MHz PWM clock, but the devil is in the details. What in the PWM peripheral's documentation was called the "system clock" turned out to be the controller's "bus clock", which is half the actual 100 MHz system clock. Thanks for the confusing docs, NXP. Still, the charger worked just fine at 50 MHz and I didn't need the extreme PWM accuracy I had been aiming for.
For the LiFePO4wered/ESP32, I'm scaling down this part to the lower cost MKL17Z32VFM4, a 48 MHz Cortex-M0+. It shaves about half a dollar off the 10K cost, is still plenty powerful for what I need, seems to have more low-power optimizations (it's an "L" part), has a similarly great ADC peripheral, and is in the same family so I can re-use a bunch of the code I have.
The biggest downside seems to be that the PWM signals for driving the buck converter MOSFETs will have lower resolution (PWM clock of 24 MHz), giving less accuracy in maintaining maximum power point. But I think it will still be sufficient.
To generate the regulated 3.3 V for the ESP32 and external customer circuitry, I need a buck-boost converter to generate this voltage from a possible input range of ~2 V to 3.6 V. Looking at current requirements, part cost and availability in China, the TPS63020 seems to be a good option. It should be able to deliver 1 A from even the lowest expected input voltage of ~2 V, while offering decent efficiency across the whole range.
In principle, the ESP32 can be powered directly from a LiFePO4 cell, as the cell voltage range perfectly matches the ESP32's recommended operating conditions (2.3V - 3.6V). But the customer who is driving this project requires regulated 3.3V for the ESP32 and the application-specific external circuitry he is connecting.
This means that instead of a simple load switch to prevent over-discharge of the battery, we need to have a buck-boost converter because the battery voltage can be both higher (up to 3.6V) or lower (down to 2.0V or so) than the 3.3V output voltage.
To provide true UPS functionality, I have always designed my LiFePO4wered products to have the battery permanently in the power path, instead of switching from external power to battery when the external power fails. Plenty of user complaints about system resets in other products designed with such a switching topology prove this is the right way to go for maximum reliability (and to be a proper UPS). Using LiFePO4 makes it possible to do this without affecting battery lifetime, due to the low float voltage. In my topology, no switching takes place. External power always passes the battery and when external power fails, the battery is right there to pick up the slack. Think of it as a giant decoupling capacitor.
The downside of this topology is that the battery (or a large cap) needs to be present for the power system to work. This is of course not a big problem since this is assumed for a product under the LiFePO4wered brand name. :) Also, predictable shutdowns go a long way in creating reliable embedded/IoT systems anyway. True, an ESP32 isn't a Linux system for which a clean system shutdown is much more critical to prevent corruption. But even an ESP32 or similar deeply embedded device can benefit from knowing when the power fails and having the opportunity to take some action. Think being able to report the power failure to a cloud system, or finishing a write to flash or an SD card. Or continuing normal operation for quite a long time on battery power only, and being completely unaffected by crappy input power.
So I'm sticking to the same battery-in-the-power-path topology for this project. Initially I had assumed I was going to use the tried and true CN3801 LiFePO4 charging chip used in the #LiFePO4wered/Solar1 and #LiFePO4wered/Pi+ for this project as well. After all, it has been working very well in those products. But there is another option to consider this time.
I have been working on a proprietary system for a customer that implements a microcontroller based MPPT charger. The customer has graciously allowed me to apply some of the IP to other products, as long as they don't compete in his market.
Now his system is a bit different because it uses a boost topology to charge the battery from a single solar cell with ~0.5V output voltage, while in this project, I'd need to use a buck topology to charge the battery from 5-28V input. But some of the IP could still be applied.
There's a good list of advantages in taking this approach:
Since this board is squarely aimed at professional IoT deployments, my first inclination was that this would be a bring-your-own-battery type board like the #LiFePO4wered/Solar1. Customers will most likely deploy this in the field with a sizable LiFePO4 battery such as a 10Ah or 20Ah cell, to be able to bridge many gloomy days when solar powered.
However, some users might be able to get away with much smaller cells. This isn't just a solar board--when powered by PoE for instance, it's more likely there will only be short power interruptions and power will be present 99% of the time.
Also, since LiFePO4 cells aren't as common in the market as consumer-level lithiums, and I hope to eventually launch this as a product on Crowd Supply, I have to consider market appeal, out-of-the-box experience and ease of development as well. I need to lower the threshold for adoption as much as possible.
So I'm leaning toward providing an on-board battery option too. Looking at the #wESP32 for the basic floor plan and holding an 18650 battery holder by it, this is likely going to make my life difficult. :) Considering the location of through-hole parts such as connectors, headers and Ethernet jack, the isolated PoE power island, and mounting hole options, there's really only one place where an on-board battery holder can fit. And it's going to put one of the battery terminals in the opposite corner of the board from where the rest of the power system will most likely live.
But, I think it will be doable. A long battery trace isn't going to be as big a problem on this board as it would be on a #LiFePO4wered/Pi+ for instance, because we're working with lower currents. The ESP32 isn't nearly as much a power hog as the latest Raspberry Pi's are!
What do you think? Should I go for it?
Initially, my customer who's driving this project wanted to know how he could combine the #wESP32 with my #LiFePO4wered/Solar1 to have a single LiFePO4 based supply that can be powered from DC, solar, or PoE. He sells smart city IoT products and it turns out everyone has different requirements and availability of power sources, so his products need to be able to take pretty much anything.
Unfortunately, combining the existing wESP32 and LiFePO4wered/Solar1 in a sensible way isn't really possible. The problem is that while the wESP32 power output could be used as a charging source for the LiFePO4 battery, it produces its own 3.3V to power the ESP32. It was never created to accept back-power on the 3.3V rail. So, the ESP32 would only run when PoE was present, and not from the battery, which defeats the whole idea. While talking to Crowd Supply about the concept (they had been asking if I had any new products in the pipeline), they suggested making a wESP32 variant that would make this possible. But I did not see a good way to do this, so that it would work both stand-alone and in combination with a LiFePO4wered/Solar1.
Also, I prefer to create well-designed products that work well and make things really easy for the customer, and hacking together a wESP32 and LiFePO4wered/Solar1 did not appeal as a quality product. Additionally, it would pretty much preserve the status quo, and not push technology further. Where's the fun in that? :)
So, the idea to create a new product was born.
Power only, or more than that?
I initially explored the idea that this would be a power-only board like the LiFePO4wered/Solar1. However, it became quickly obvious that this was not really possible, if one of the power sources would be Power over Ethernet. When you have this Ethernet connection running to your system, it doesn't really make sense to not use it for communications as well. One option would be to just run the TX and RX pairs to a header and let the customer deal with them if they want to use Ethernet comms, but since these are high speed signals requiring controlled impedance, this seemed like asking for trouble. It would also push the Ethernet complication to the customer, which goes against my product philosophy. I prefer to pull technical difficulties onto my board so the customer can focus on their application.
Since these things had already been taken care of on the wESP32, it makes sense to provide the same ESP32 based functionality on this board as well. The ESP32 Ethernet works great and it's just an awesome, well supported and low cost platform that many makers are already familiar with. So why change?
So, which ESP32?
Having settled on making this an ESP32 core board, the next question became: which ESP32?
I could stick to the ESP32-WROOM module used on the wESP32, but when deploying this in the field, my customer would need an external antenna for WiFi and Bluetooth. There is the ESP32-WROOM-32U with U.FL connector that would offer a size advantage. But in talking to ESP32-using customers it had also become obvious that available memory would become tight when using the BLE stack or a high-level firmware like MicroPython.
So I'm tentatively designing in the ESP32-WROVER-I module, which provides PSRAM and a U.FL connector. Unfortunately, Espressif didn't do the sensible thing they did with the ESP32-WROOM-32U and remove the antenna part of the PCB for this external antenna option. The size of this module is a big disadvantage since the unused PCB antenna is still going to take up space.
My initial goal was to keep the GPIO header the same as the one on the wESP32, but I may need to shelve that idea.
First, the ESP32-WROVER module uses the IO16 and IO17 signals internally for the PSRAM. On the wESP32 they are used for the MDIO and MDC signals to the Ethernet PHY, so these signals will have to be assigned to different...Read more »