MQTT con ESP32 y Mongoose-OS

Mongoose-OS incorpora un cliente MQTT y un simple intérprete mJS (una versión reducida de JavaScript). Con ellos podemos rápida y fácilmente conectarnos a un broker MQTT y realizar pruebas de concepto de aplicaciones más complejas.

Configuración

Configuramos el ESP32 con Mongoose-OS para tener un cliente MQTT y conectarse como cliente a una red WiFi. Dado que debemos conectarnos a un servidor, ésta nos resulta la forma tal vez más rápida y simple de realizar las pruebas y explicar la operación. La configuración puede realizarse manualmente mediante RPC, en un archivo de configuración JSON; o definirla en el archivo YAML que describe el proyecto. Para las pruebas elegimos esta última opción.

libs:
  - origin: https://github.com/mongoose-os-libs/mqtt  # incorpora el cliente MQTT
config_schema:
  - ["mqtt.enable", true]            # Habilita el cliente MQTT
  - ["mqtt.server", "address:port"]  # Dirección IP del broker a utilizar

El puerto comúnmente utilizado es 1883. El broker puede además solicitar que utilicemos nombre de usuario y password, los detalles de la configuración completa los podemos encontrar en la página de Mongoose-OS.

Operación

Antes de compilar y ejecutar, es conveniente tener un cliente conectado al broker para poder observar el mensaje enviado al momento de conexión. Hemos utilizado un broker instalado en nuestro lab:

$ mosquitto_sub -h mqtt.lab -p 1883 -t "#" -v

También (si el tráfico habitual lo permite) es posible usar el broker abierto de Eclipse , en cuyo caso deberemos limitar un poco el tópico a suscribirnos a fin de intentar recibir sólo nuestros mensajes

$ mosquitto_sub -h mqtt.eclipseprojects.io -p 1883 -t "/this/#" -v

Luego de compilado el código (mos build) y grabado el microcontrolador (mos flash) mediante mos tool, observaremos en el log si todo funciona como debe, o los errores que se hayan producido. Recordemos que debemos configurar las credenciales para conectarnos por WiFi a nuestra red (SSID y clave) y la dirección y port del broker MQTT que vayamos a utilizar.

[Feb 11 15:19:42.353] mgos_mqtt_conn.c:435    MQTT0 connecting to mqtt.sensors.lab:1883
[Feb 11 15:19:42.370] mgos_mqtt_conn.c:188    MQTT0 TCP connect ok (0)
[Feb 11 15:19:42.380] mgos_mqtt_conn.c:235    MQTT0 CONNACK 0
[Feb 11 15:19:42.385] mgos_mqtt_conn.c:168    MQTT0 sub /this/sub @ 1
[Feb 11 15:19:42.393] init.js:38              MQTT connected
[Feb 11 15:19:42.407] init.js:26              Published:OK topic:/this/pub/esp32_807A98 msg:CONNECTED!

En este momento observaremos el mensaje en nuestro cliente:

$ mosquitto_sub -h mqtt.lab -p 1883 -t "#" -v
/this/pub/esp32_807A98 CONNECTED!

A continuación, enviamos un mensaje a nuestro dispositivo publicando uno en el tópico al que éste se halla suscripto:

$ mosquitto_pub -t /this/sub -h mqtt.lab -m "This message"

En este momento observaremos el mensaje en nuestro cliente

$ mosquitto_sub -h mqtt.lab -p 1883 -t "#" -v
/this/pub/esp32_807A98 CONNECTED!
/this/sub This message

y en el log de nuestro dispositivo

[Feb 11 15:20:02.647] init.js:32              Got a msg: This message

Finalmente, apagamos nuestro ESP32 y al cabo de unos minutos el broker detectará la desconexión; en ese momento observaremos el mensaje “de última voluntad” en nuestro cliente

$ mosquitto_sub -h mqtt.lab -p 1883 -t "#" -v
/this/pub/esp32_807A98 CONNECTED!
/this/sub This message
/this/test lost

El código mJS

Cuando queremos publicar algo, invocamos el método MQTT.pub(). No debemos esperar ni hacer handshakes, sólo llamarlo y observar el resultado. Internamente el mensaje es puesto en una cola, de donde saldrá cuando sea posible. El valor devuelto indica que ha sido posible encolar el mensaje, no que éste ha llegado a destino. En el código que acompaña a este artículo creamos una función como ejemplo de uso:

let publish = function (topic, msg) {
    let ok = MQTT.pub(topic, msg, 1, false); // QoS = 1, do not retain
    Log.print(Log.INFO, 'Published:' + (ok ? 'OK' : 'FAIL') + ' topic:' + topic + ' msg:' +  msg);
    return ok;
};

let device_id = Cfg.get('device.id');
publish('/this/pub/'+device_id,'CONNECTED!');

Si queremos que el mensaje sea retenido, el cuarto parámetro de MQTT.pub() debe ser verdadero.

Para recibir mensajes, nos suscribimos al tópico deseado usando el método MQTT.sub(). El segundo parámetro es un handler a una función callback que se ejecutará cuando recibamos un mensaje. En el código que acompaña a este artículo mostramos un ejemplo de uso:

MQTT.sub('/this/sub', function(conn, topic, msg) {
    Log.print(Log.INFO, 'Got a msg: ' + msg);
}, null);

Last will, mensajes “de última voluntad”

Si deseamos configurar una “última voluntad”, es decir, un mensaje que el broker publicará por nosotros ante la detección de una desconexión, podemos hacerlo como se ve a continuación:

  - ["mqtt.will_topic", "/this/test"]
  - ["mqtt.will_message", "lost"]

En algunas aplicaciones es necesario que este mensaje sea retenido por el broker, de modo que otros clientes que se conecten luego de nuestra desconexión y antes de nuestra reconexión puedan recibir dicho mensaje, que por lo general tiene la misión de indicar que no estamos disponibles.

  - ["mqtt.will_retain", true]

Retain, persistencia

La retención de mensajes (sean “de última voluntad” o mensajes corrientes con el flag retain seteado) requiere de un broker MQTT con capacidad de persistencia (persistence), es decir, con una base de datos que almacene los últimos mensajes marcados como persistentes y los retransmita a quien se suscribe al tópico correspondiente al momento de iniciar la suscripción.

En nuestro caso hemos utilizado como broker Eclipse Mosquitto, pero hay otras opciones.

En el caso de Mosquitto, para configurar la persistencia debemos agregar

persistence true

en el archivo de configuración (si lo buscamos lo encontramos). También es posible que además debamos agregar el nombre y ubicación de la base de datos de persistencia (paths en formato ambiente GNU/Linux):

persistence_file mosquitto.db
persistence_location /var/lib/mosquitto/

pero eso depende de la distribución que utilicemos, por lo que puede que ya esté hecho por defecto. El usuario bajo el cual se ejecuta el proceso debe tener permisos de escritura a esta base de datos.

Ejemplo

El código de ejemplo se encuentra en Github.

El extraño caso de la placa que se rehusaba a apagarse

Esto ocurrió hace un tiempo, en una galaxia no muy distante, donde muchos hombres han ido anteriormente.

Con un colega habíamos diseñado un circuito de encendido para un instrumento portátil alimentado a pilas, bastante simple: un MOSFET cierra el circuito puenteando el pulsador de encendido cuando el microcontrolador inicializa y configura el pin, como puede verse en el diagrama a continuación.

Para encender el instrumento, el usuario presiona el pulsador; el microcontrolador recibe alimentación, ejecuta su secuencia de inicialización y setea POW_CTRL en estado alto, habilitando el MOSFET, que lo mantiene alimentado luego que el usuario suelta el pulsador. Para apagarlo, el usuario presiona el pulsador nuevamente (estando encendido…), esto es leído por el programa en el pin SW_OFF, inhabilitando al MOSFET e ingresando en un loop de inactividad. Cuando el usuario suelta el pulsador, la tensión en ON_PWR comienza a descender, y el microcontrolador finalmente se apaga cuando los capacitores se descargan lo suficiente.

Esto funcionó perfectamente en el laboratorio y en las primeras unidades producidas para el cliente. Un tiempo más tarde, sin embargo, en otro lote de producción, una o dos de las nuevas unidades no se apagaban; el programa se reiniciaba siendo imposible el apagado.

Dado que yo había sido el perpetrador del hardware, fui elegido por unanimidad para hacerme cargo de resolver el misterio y encontrar la razón lógica detrás de este comportamiento esotérico. Munido de todas las unidades “con falla” y un par de unidades “funcionales”, comencé la inspección osciloscópica del circuito en cuestión.

Las unidades “funcionales” mostraban una esperada exponencial decreciente, correspondiente al descenso monotónico de la tensión de alimentación; aunque algunas porciones de la curva acusaban cambios en la pendiente.

Las unidades “con falla” mostraban cambios de pendiente más pronunciados, con una extraña tendencia a quedar horizontales momentos antes de que el microcontrolador decidiera reiniciarse. El trazo celeste corresponde a la tensión de alimentación en ON_PWR, antes de ingresar a un LDO, mientras que el trazo naranja es la tensión en POW_CTRL, con un resistor adicional conectado al positivo de las pilas (a través de ese resistor en el circuito esquemático que parece ir a ningún lado, esto es parte de un circuito más complejo pero generalmente dejado sin conectar o puesto a masa) para poder observar cuando el microcontrolador dejaba sus pines en alta impedancia (al resetearse). (Créanme que sólo lo tuve conectado el tiempo suficiente para tomar las capturas)

Finalmente, una de estas unidades “con falla” fue lo suficientemente gentil como para permitir al osciloscopio que me mostrara un pequeño incremento en la tensión de alimentación; sí, ese puntito celeste dentro del círculo rojo, justo antes del flanco ascendente del trazo anaranjado (el reset del micro). Por supuesto que esto puede ser simplemente ruido, pero se veía como si la tensión de alimentación ascendiera justo un instante antes del momento del reset. Sí, nadie había presionado el botón, y si estás pensando en echarle la culpa al LDO, a los fines prácticos llevaba visto lo suficiente esos chips como para considerarlos como un cable para todas las tensiones inferiores a la de regulación.

Mi teoría es que, una vez que el MOSFET dejaba de conducir y la alimentación del microcontrolador disminuía al descargarse el capacitor principal, los circuitos internos del microcontrolador (periféricos) se iban apagando y tomando cada vez menos corriente. Esa corriente es mayormente entregada por el capacitor electrolítico principal, y lo que yo estaba observando era su dQ/dC mientras su dQ estaba siendo diezmada por di siendo drenada con el transcurso de dt; a eso le restamos la caída di x ESR (Equivalent Series Resistance, resistencia interna serie equivalente que para este propósito sirve como paragüas para un probablemente amplio grupo de sutiles detalles químicos). Eso explicaría los cambios de pendiente…

Aparentemente, en algunas unidades esas diferencias de corriente cuando los periféricos se iban apagando cooperaban con las alinealidades de la ESR del capacitor para alterar el comportamiento monotónico de la alimentación (y tal vez verse como un aumento en la tensión), situación que dispararía el BOR (Brown-Out Reset) y el circuito interno de reset, reiniciando al microcontrolador, y habilitando al MOSFET nuevamente.

Afortunadamente, debido a la funcionalidad de apagado nos era posible observar si el botón estaba presionado o no. La condición de pulsador presionado es típica del encendido, mientras que una condición de pulsador abierto estaría indicando uno de esos despreciables y compulsivos resets ocasionados por el BOR, situación que podemos reconocer por programa e ignorar muy elegantemente, desviando el flujo de programa a un loop que espere a ver si la tensión de alimentación desaparece de una vez por todas, o el usuario realmente presiona el pulsador (porque a veces, sólo a veces, los pulsadores presentan rebotes en sus contactos; pero creo que eso ya es bien sabido, ¿no?).

GPIO handling in ARM Cortex-M

Let’s start by analyzing what flipping an I/O port really means.

The minimum number of instructions when using bit banding is three:

  • one processor register must be a pointer to the I/O register
  • another register will contain the value to be written
  • an indexed write instruction performs the operation

The following actions we may perform on that same port, can be carried using only two instructions, or even one if we are able to reuse a register containing the value to be written (0 or 1).

Operations modifying several bits at once require that we read the I/O port copying its contents to a processor register, modify that register, and then write that register back to the I/O port (except in cases where the microcontroller has purposely built hardware, like some STM32’s, but let’s focus on the Cortex-M itself).

Any operation we’d like to perform over a peripheral, will essentially abide to wait we just stated. This process, in fact, takes a very small amount of time for the processor core. Due to the internal bus structure, the timing for successive actions over an I/O port is determined by the corresponding transactions over first the AHB bus and then the APB bus, added to the time it takes for the processor to request those changes (instruction execution). Even though every microcontroller has its own peripherals, chosen by the manufacturer, we usually encounter access times in the order of several hundreds of nanoseconds, due to the fact that the minimum switching time (either low to high or high to low) in an I/O pin is determined by the maximum transfer speed in those buses, which corresponds to two clock cycles for each of them.

The processor may perform other activities during that time, but it can’t operate on that very same port; in that case the execution of the second instruction is delayed until the first one can be completely performed and the bus is freed to accept another request.

For example, in a microcontroller family like the HT32F125x from Holtek, Cortex-M3, the AHB bus clock and the APB bus clock are user configurable up to a maximum of 72MHz, which is also the default value. To this we have to add the processor instruction execution time. Thus, we can estimate the minimum possible latency time as four clock cycles (∼ 50ns), that is, once the instruction to move a pin has executed, it must go through the AHB bus to the APB bus to reach the peripheral, the I/O port itself.

As we’ve seen, it is possible for the processor to perform other duties but it can’t operate on that same port again, so the second order gets delayed until the first one can finish, what gives a period of eight cycles (∼ 100ns), as we can see on the figure, a capture taken during the development phase of a color display driver:

Another possibility is that the AHB bus internal structure allows pipelining succesive requests, this can help to reduce some cycles from the total time.

For example, for the TMPM330 from Toshiba, another Cortex-M3, the AHB bus clock and the APB bus clock are user configurable up to a maximum of 40MHz, and also the default value. This processor seems to have some form of pipelining in its AHB bus implementation, ’cause in the very same application we’ve observed a minimum period of six clock cycles (150ns), as can be seen in the following figure:

Up to here, everything is also valid for Cortex-M4 and Cortex-M0 processors.

The Cortex-M0+ processor can optionally have a Single-cycle I/O peripheral connected to its Bus Matrix, which, as its name suggests, allows performing input and output operations in just one cycle. That peripheral is memory mapped and can be accessed using regular data transfer instructions, but these are executed in one cycle instead of two cycles.

The next figure shows flipping a couple of GPIO pins on an STM32G071 running at 64MHz. The lower trace shows succesive set and reset operations in around 15ns:

Regarding the I/O management process, and given that to operate on a pin we need to copy the required content from a register to memory, the actual operation in a single cycle would be limited to those cases in which it is possible to keep each required value in its own register and then use only memory transfer instructions in a row. The next capture shows the results of executing two successive read-modify-write operations, followed by two write operations, inside a loop (so we can easily see it), on a Holtek HT32F52231, a 40MHz Cortex-M0+. The first signal reset, at trigger time, includes the time needed to setup all necessary registers; so the time the signal stays in the high state is considerably longer. The first set can then be executed in three cycles (read, modify, write). The second reset takes two cycles (register load, write), while the next set can be executed in one cycle due to the optimizer being able to reuse one of the previously loaded registers:

This post contains excerpts from the book “Desarrollo con microcontroladores ARM Cortex-M3“, taken and translated with the author’s premission.

ESP32 resets

(English below the images)

En el foro de Mongoose-OS y algunos chats se leen pedidos de ayuda por problemas al conectarse a una computadora diferente, al cambiar el cable USB, al usar una fuente de alimentación/cargador “genérico”… Evidentemente se trata de módulos y en la mayoría de estos casos tienen un conector USB con el cual se somete a ambos a tortura mutua. Veamos a qué pueden deberse estos problemas.

El ESP32 no es particularmente un micro de bajo consumo, y menos sabiendo que tenemos un radio Bluetooth (no sólo BLE) y otro WiFi. Recuerdo haber leído hace algunos años acerca de corrientes del orden de los 100mA, una rápida consulta a la hoja de datos se detiene en un parámetro extraño que estaría mejor en una guía de diseño que una hoja de datos, algo así como “mínima corriente que debe entregar la fuente de alimentación” (como si esto fuera una característica o parámetro del micro…). Su valor es de 0,5A; es decir, el fabricante sugiere que nuestra fuente de alimentación debe proveer como mínimo 500mA. Esto está en los límites del USB tradicional, pero si miramos las imágenes que siguen a continuación veremos que tanto en el arranque como al iniciar el soft-AP o conectarse a una red WiFi existente, el ESP32 toma corrientes elevadas por períodos muy breves. Esto, sumado a cables finos, y largos, es una excelente receta para el desastre. Si bien la medición es bastante “sucia”, dado que se trata de un resistor de .47 ohms en serie con un módulo ESP32-WROOM-32, podemos allí observar como pasamos de la centena de mA al medio ampere en cuestión de microsegundos; -L di/dt.

(pseudo-amarillo: fuente de alimentación, cian: alimentación del micro, magenta: resta de ambos)

At the Mongoose-OS forum and some chats there are several cries for help, people are having trouble when changing computers, swapping USB cables, or switching to a walwart adapter… These are modules, of course, and mostly have a USB connector. Let’s see what can be causing these issues.

The ESP32 is not particularly a low-power microcontroller, not if we know it has a Bluetooth radio (not just BLE) and a WiFi radio. I remember reading years ago figures about currents around 100mA; a quick browse to the datasheet pauses on a strange parameter that could belong better to a design guide, something like “minimum current delivered by the power supply” (as if this were a characteristic or parameter belonging to the microcontroller…). Its value is 0.5A; that is, the manufacturer suggest our power supply must be capable of delivering at least 500mA. This is at the limit of traditional USB, but if we take a look at the images above (right below the Spanish text… yes, those), we’ll see that at startup, soft-AP start, and WiFi network connection, the ESP32 has high current spikes, drawing high currents for brief amounts of time. This, added to thin (and long) wires, is an excellent recipe for disaster. Even though this is a quick and dirty measurement, as it was done with a .47 ohm resistor in series with an ESP32-WROOM-32 module, we can easily see how we go from around hundred milliamps to half an amp in just microseconds; -L di/dt.

(sort-of-yellow: power supply rail, cyan: device supply rail, magenta: difference between them)

Manejo de GPIOs en ARM Cortex-M

Analicemos en detalle la operatoria para mover un pin de I/O.

La cantidad mínima de instrucciones mediante bit banding es de tres:

  • un registro del micro debe apuntar al registro de I/O
  • otro contendrá el valor a escribir
  • una instrucción de escritura indexada efectúa la operación propiamente dicha

Las operaciones siguientes que realicemos sobre el mismo port pueden hacerse con dos instrucciones, o incluso una si reutilizamos el registro que contiene el valor (0 ó 1).

Las operaciones de modificación de varios bits simultáneamente requieren de una lectura del port de I/O a un registro, modificación del registro, y escritura al port de I/O (excepto que el microcontrolador específicamente disponga de un hardware ad hoc como por ejemplo algunos STM32, pero hablemos del procesador Cortex-M en sí).

Cualquier operación que deseemos realizar sobre cualquier periférico, responderá en esencia a una de estas dos consideraciones vistas. Este proceso analizado demora muy poco tiempo en lo que respecta al procesador. Debido a la estructura interna de buses, el timing para acciones sucesivas sobre I/O viene dado por las transacciones en el bus AHB y luego el APB, sumado al tiempo del procesador para ordenar el cambio (ejecución de las instrucciones). Si bien cada micro tiene sus propios periféricos determinados por su fabricante, es común encontrarnos con tiempos de acceso de varios cientos de nanosegundos, debido a que el tiempo mínimo de conmutación en uno y otro sentido de un pin de I/O corresponde a la máxima velocidad de transferencia en estos buses, que es de dos ciclos de clock en cada uno.

Es posible que el procesador realice otras actividades en este tiempo, pero no que opere sobre el mismo port, en cuyo caso la ejecución de la segunda orden se demora hasta la finalización de la primera.

Por ejemplo, en un microcontrolador como el HT32F125x de Holtek, un Cortex-M3, el clock del bus AHB y el bus APB es configurable por el usuario, 72MHz como máximo y por defecto. A esto sumamos el tiempo de ejecución del procesador. Así, deducimos que el tiempo mínimo posible de latencia es de cuatro ciclos de clock (∼ 50ns), correspondiente al pasaje de la información de mover el pin desde la finalización de la instrucción hasta que pasa por el bus AHB y luego por el bus APB.

Como analizamos, es posible que el procesador realice otras actividades en este tiempo, pero no que opere sobre el mismo port, en cuyo caso la ejecución de la segunda orden se demora hasta la finalización de la primera, obteniendo un período de ocho ciclos de clock (∼ 100ns), como podemos apreciar en la figura, que corresponde al desarrollo de un driver para un display color:

Otra posibilidad es que la implementación interna del bus AHB permita realizar pipelining de sucesivas operaciones, con lo cual se reduce en algunos ciclos el tiempo total.

Por ejemplo para el TMPM330 de Toshiba, otro Cortex-M3, el clock del bus AHB y el bus APB es configurable por el usuario, 40MHz como máximo y por defecto. Al parecer este micro incorporaría pipelining en el bus AHB, pues en la misma aplicación hemos obtenido un período mínimo de seis ciclos de clock (150ns), como podemos apreciar en la figura:

Lo visto hasta aquí es válido también para Cortex-M4 y para Cortex-M0.

El procesador Cortex-M0+ incorpora de manera opcional en su Bus Matrix la conexión a un periférico que permite acceder a operaciones de entrada y salida en un solo ciclo: Single-cycle I/O. Dicho periférico está mapeado en memoria y puede ser accedido por las instrucciones corrientes de transferencia de datos, sólo que éstas se ejecutan en sólo un ciclo, en vez de dos.

La imagen siguiente muestra el movimiento de un par de GPIOs en un STM32G071 operando a 64MHz. En el trazo inferior observamos que un set y reset sucesivos se producen en unos 15ns:

En lo que al proceso de manejo de I/O se refiere, y dado que para operar sobre un pin es necesario copiar el contenido de un registro a memoria, la operación real en sólo un ciclo se limitaría a los casos en que es posible mantener los valores en sendos registros y utilizar sólo instrucciones de transferencia a memoria una tras otra. La imagen siguiente muestra el resultado de ejecutar dos operaciones de lectura-modificación-escritura sucesivas, seguidas por dos de escritura, dentro de un loop (para poder observarlo fácilmente), en un Holtek HT32F52231, Cortex-M0+ de 40MHz. El primer reset de la señal, al momento del disparo, incluye el seteo de los registros necesarios; por eso el tiempo en estado alto es considerablemente más largo. El primer set ya puede ejecutarse en tres ciclos (lectura, modificación, escritura). El segundo reset se ejecuta a los dos ciclos (carga de registro y escritura), mientras que el set siguiente se ejecuta en un ciclo dado que el optimizador del compilador ha podido reutilizar un registro cargado con anterioridad:

Este post contiene algunos extractos del libro “Desarrollo con microcontroladores ARM Cortex-M3“, con permiso del autor.

This power supply used to power itself off

Everything started with a phone call, one of my clients had just got a complaint from one of his retail buyers, this device had been freezing and remaining unresponsive, having to be power cycled to recover. Since these appliances were directly attached to the mains power when installed, this required operating on the switch breaker for that part of the house, also affecting other appliances in that area.

At that time we already had several thousand of these controller boards in the field; since this one was the only one with such a failure, and this customer was located at a country place, life went on and I thought my customer had somehow solved it. A watchdog failure was something very unlikely, so it should have been something related to the power supply or some thermal issue, those places are either too cold or too hot….

Months later, a rather similar failure shows up again on another location, also relatively far away from a city, but this time the end user was a strategic customer living on a private neighborhood; as it always happens, hard to solve failures happen in places where damages can be most harmful, Murphy…

The company selling this appliance had sent several technicians, who had changed controller boards at will, but the problem still persisted, so we decided to pay a visit to this customer and run some measurements there. Surprisingly enough, we could not only confirm that this customer was right, but we also observed huge fluctuations on the mains voltage, sometimes getting over 250V (we have 220V mains here). The oscilloscope confirmed that the controller board built-in power supply stopped working, starting again after a power cycle; something looking like a safety protection or a self power-off feature…

After confirming there were no temperature issues involved (this chip also included some thermal protection features), I went back to my lab carrying this board with me, with the intention to reproduce this issue there and meditate on what could be causing it. The first one in a series of profound revelations I found by playing with the variable autotransformer (variac) knob. Even though the power supply was working correctly at such a high voltage, sometimes I could reproduce this issue by simulating sharp changes in mains voltage. Then, the use of a mains voltage stabilizer that has been modified for manual switching, allowed me to almost reproduce this issue at will.

The second revelation I found in the datasheet: one of the controller pins had this functionality associated to an internal finite state machine whose description was a very good match to what we’ve been observing, but that pin was correctly decoupled by a capacitor connected to ground, as the datasheet stated.

A view of the PCB design, showing the decoupling capacitor

After reading the pin descriptions for each and every controller pin dozens of times, I came to a conclusion: “something”, “somehow”, was causing voltage in that pin to raise. Something strange was happening around that capacitor in the figure…

The third revelation happened once I could observe the voltage at that pin with great detail: it barely raised at times when “mains voltage” sharply changed when above 240V. Then, the power supply stopped working until it was power cycled. We had found what was going on, now we only had to find what was causing it…

Satori came when observing the PCB design, while analyzing (once again) current loops, but this time deeply focusing on the path to ground for this decoupling capacitor.

Current loops in detail, we omitted the snubber current for simplicity

The light mustard trace depicts the power supply switching current, intentionally having a reduced area to avoid generating excessive noise that could interfere with the rest of the circuit. The dark mustard trace shows the input current coming from the mains power… shared with circuitry serving other features for the appliance operation, but also, and mainly, shared to a great extent with the light red trace, which belongs to this very decoupling capacitor’s ground return path.

Though we can trust that thick trace on both layers doubling also as a heatsink, let’s pay attention to that “unrolled inductor” running horizontally in the screen. We can see that both the switching power supply current pulses, that are stronger when higher currents come into play, and the filter capacitor charging pulses, that are stronger when there are mains instabilities, circulate on a long path that is shared with the decoupling capacitor connecting path to ground. We can think of this as if that capacitor is connected to ground via an RL series circuit (the PCB track impedance), and in that node we also connect other circuitry to carry their currents to ground… Technically speaking, we have a sensitive circuit requiring a low impedance path to ground and we are using an inadequate impedance path that also, and for worse, carries strong currents with a high di/dt, and we all know what happens when high di/dt circulates through -L, isn’t it?.

To solve it, we replaced that capacitor for a through-hole component, connecting its ground return path straight through using a different, star-shaped path, to the chip reference ground; away from the switching circuit and the input current, as good practices and datasheet state…

The modified PCB design, with the new capacitor

The figure shows this new path. Though we also share this SOT-323 double diode path to ground, its current is low enough to be dismissed.

La fuente de alimentación que se apagaba sola

Todo empezó con un llamado telefónico, mi cliente había recibido una queja de un usuario final que había comprado el producto y decía que se quedaba congelado, que no respondía a los controles, que debía desconectarlo y volverlo a conectar. Siendo un producto empotrado y directamente conectado a la red domiciliaria de 220V, esto significaba cortar el suministro eléctrico en esa zona de su casa.

Dado que había ya algunos miles de placas controladoras de este producto en servicio, ésta era la única con esa falla, y además se trataba de un lugar alejado en el interior del país, quedó como una anécdota que de algún modo resolvió mi cliente y retomé mis actividades habituales. Era raro que fallara el watchdog, debía tratarse de algo relacionado con la fuente o algún problema térmico, en esos lugares hace mucho frío o mucho calor…

Meses después, la falla se repite en un lugar también relativamente alejado, pero esta vez el usuario final era un cliente estratégico en un barrio cerrado; como siempre sucede, las fallas difíciles de encontrar se manifiestan en el lugar donde más daño causan, Murphy…

Los técnicos de la empresa que vendía el producto se cansaron de reemplazar placas y el problema persistía, a lo cual fuimos hasta el lugar e hicimos mediciones. Para nuestra sorpresa pudimos observar que el cliente tenía toda la razón y un poquito más, pero también observamos que la tensión de la red eléctrica fluctuaba considerablemente, ascendiendo por momentos a más de 250V. Colocamos un osciloscopio y pudimos confirmar que la fuente switching que alimentaba a la electrónica del aparato dejaba de funcionar, y retomaba sus actividades al retirarle la alimentación y volverla a aplicar; algo así como una auto-protección o un auto-apagado…

Luego de comprobar que no era un problema de temperatura (el chip tenía además un protección contra eso), con esa placa en la mano me retiré a mi laboratorio a reproducir el problema y meditar sobre sus posibles causas. La primera revelación la encontré ejercitando la perilla del autotransformador variable (variac). Si bien la fuente funcionaba correctamente a altas tensiones, con cambios bruscos a veces lograba reproducir la falla. Conectando un estabilizador de tensión modificado y forzándolo a cambiar repetidas veces, logré reproducirla casi a voluntad.

La segunda revelación la encontré en la hoja de datos, uno de los pines del controlador desarrollaba una función con una máquina de estados interna cuya descripción coincidía notablemente con lo que estábamos observando, pero ese pin estaba correctamente desacoplado con un capacitor a masa como indicaba la hoja de datos.

Vista del diseño de la PCB, con el capacitor de desacople

Luego de leer la descripción de todos los pines del controlador decenas de veces, llegué a la conclusión que “algo” tendría que “de algún modo” hacer que la tensión en ese pin se elevara. Algo extraño estaba sucediendo en torno a ese capacitor indicado en la figura anterior…

La tercera revelación ocurrió cuando pude observar con el osciloscopio que la tensión en dicho pin ascendía levemente durante un instante coincidente con los cambios bruscos de “la tensión de red” cuando ésta estaba en valores por encima de los 240V, momento a partir del cual la fuente de alimentación dejaba de operar hasta que se la reiniciaba. Habíamos encontrado qué era lo que sucedía, ahora sólo restaba encontrar por qué era que eso ocurría…

El satori se produjo al observar el diseño de la placa y analizar (una vez más) los lazos de corriente, pero esta vez prestando mucha atención al camino de retorno a masa del capacitor de desacople.

Detalle de los lazos de corriente, omitimos la corriente por el snubber por simplicidad

El trazo en color mostaza claro es el lazo de la corriente de conmutación de la fuente, intencionalmente de área reducida para evitar generar excesivo ruido que pudiera interferir con la operación del resto del circuito. El trazo en mostaza oscuro, corresponde al lazo de la corriente de entrada de la fuente desde la red… compartido con otras funciones de la circuitería del aparato en sí, pero además, y fundamentalmente, compartido en una extensión importante con el trazo rojo brillante, que corresponde al retorno a masa del capacitor de desacople…

Si bien podríamos confiar en la pista gruesa y en ambas caras que oficia a la vez de disipador; prestemos atención a ese “inductor desenrrollado” que corre horizontal. Es evidente que tanto los impulsos de operación de la fuente, que resultan más intensos cuando hay altas corrientes en juego, como los impulsos de carga del capacitor de filtro, que resultan más intensos cuando hay transitorios en la red, circulan por un tramo considerable compartido con la conexión del capacitor de desacople a masa. Esto lo podemos pensar como que ese capacitor tiene un circuito RL serie a masa (la impedancia de las pistas de la PCB), y en el punto de unión con ese circuito conectamos las otras corrientes para llevarlas a masa… Hablando con propiedad, tenemos un circuito sensible que requiere un camino de retorno a masa de baja impedancia y lo estamos llevando por un camino de una impedancia inadecuada que para peor transporta corrientes variables elevadas, con alto di/dt, y todos sabemos lo que sucede cuando alto di/dt circula por -L, ¿no?.

La solución fue reemplazar ese capacitor por uno de inserción, conectando el retorno a masa directamente por un camino diferente, “en estrella”, a la referencia del chip; lejos del circuito de conmutación y de la corriente de entrada, como recomiendan las buenas prácticas y la hoja de datos…

Vista del diseño corregido de PCB, con el nuevo capacitor

En la figura podemos observar el nuevo camino. Si bien tenemos además la conexión a masa de un doble diodo en SOT-323, la misma no transporta corriente significativa.

Brightness greed causes LEDs to let the fumes out

This happened more than a decade ago. At that time we had very few information on LEDs, which were becoming ubiquitous in lighting applications at a fast pace.

By then, besides what the locals call R&D, I was also in charge of the tech support team.

This customer approached us with a strong claim: the LEDs he bought were defective. He claimed to be powering them “at the exact threshold voltage”, but some LEDs will quickly become brighter and brighter, then dimming to the point of being unusable.

The first question that came to our minds was “what would be the ‘exact’ operating voltage for an LED ?”, but we thought he was assuming ‘typical’ meant “for all practical purposes: always”. A quick look at the datasheet revealed a strong spread in the expected voltage at a known current, as we can see in this typical datasheet extract:

Typical red LED datasheet extract

He had bought quite a bunch of cheap LEDs with no bin separation and he somehow believed all of them would have the same working point.

Nevertheless, we asked for the schematic, which conceptually was sort of something like the figure:

Conceptualized version of the customer’s schematic

We then advised him to search for literature regarding how to connect diodes in parallel, or if he couldn’t find that, concepts in paralleling power transistors would shine some light.

What he was experiencing was the following: when you power such a parallel circuit, voltage will be the same for all branches, but unless you carefully match all LEDs (and perhaps even so), LEDs will have slightly different operation curves, that’s what those ‘min’, ‘max’, and ‘typ’ mean. “Vled” in the schematic above will be different for every LED and can be anywhere between ‘min’ and ‘max’, with some probability of falling around ‘typ’. That is, driven with a (let’s say) 20mA source, many LEDs will exhibit a voltage drop of ‘typ’, while a number of others will measure values closer to ‘min’ or ‘max’. Conversely, the situation becomes exponential instead of logarithmic and here is where things get interesting, as we are to expect a much wider variation in LED current with small variations in the applied voltage.

A diode characteristic equation according to Shockley’s model

The branch with more LEDs whose working voltage (at the branch current) is on the “closer to ‘min'” side of the distribution curve, will add up to “a lower voltage”, but since it is connected in parallel to the other branches, it must share the same voltage. As a result, this branch will draw more current to move the operating point to the voltage imposed by the parallel circuit.

And here comes the tail-chasing game. Those LEDs in that branch that had a higher voltage (at the branch current) will heat up more, since their VxI is higher. As we know, these semiconductor junctions have a negative temperature coefficient, that is, junction voltage is inversely proportional to its temperature, so when temperature increases, voltage across the junction will decrease when current is taken constant. (Let’s pay attention to the presence of ‘T’, the absolute temperature, in the Shockley model equation above, and also the note “Ta=25ºC” in the datasheet extract and curve above)

So, as voltage decreases, the branch will take more current to cause the voltage to rise, causing the LED to heat up some more, and so on until, in the absence of anything limiting the current, something undesired happens: the LED will let the fumes out, and once that vital fluid escapes, it will no longer work… 😉

As LED brightness is proportional to current, and color depends on temperature, the LED brightness increases while color changes, up to a point where the junction no longer works as expected and so brightness is minimum; probably protecting the other LEDs but that really depends on the different failure modes.

We also told him he needed to, at least, add some negative feedback to the circuit: a series resistor inside each and every branch; as done when paralleling diodes or power transistors.

Main concept for the negative feedback stabilizer based on an ohmic current-to-voltage converter…

The operation is as follows:

When one of the branches starts taking more current, the resistor turns that into a voltage that is subtracted from the branch supply voltage, causing the LEDs in that branch to see a lower voltage and so take less current. A small value, enough to compensate the threshold voltage spread, would do. The “exact” value of that resistor will depend on the LEDs and the number of them in series, and of course we can’t expect to still be able to power the circuit at the sum of the typical voltages as this customer intended to do, and the solution would be as effective as one manages to juggle all the variables and still choose a viable value for the resistors; but though we’ll keep the LEDs alive, we’ll still have some brightness differences among the branches.

A more complex but stable thing to do, is to dedicate a current source for each branch. As we know, LED brightness depends on its current, so we control the brightness by keeping the branch current constant, and each branch has its current source, all powered from the voltage source (since we don’t have ideal current sources but transistor-based circuits that keep current relatively constant within certain limits).

Conceptual scheme for a generic solution using transistor-based constant current circuits

Current will still vary with temperature, so if we really need to keep brightness tightly constant, we’ll need to stabilize our current sources for changes in temperature.

More expensive LEDs, where the manufacturer has separated them in bins or groups with similar characteristics, also present some spread:

Extract from several pages out of a red LED datasheet, showing different groups (bins). Each group presents not only a different working voltage but also different light intensity at the same working current.

As we can easily calculate, even that 0.1V difference can cause a great difference in current when connected in parallel, but with carefully selected LEDs those differences can be minimized and more simple circuits can be built. We somehow shift circuit complexity to provisioning logistics.

On a higher detail, LEDs, as diodes, deviate from this ideal exponential response, which can be modeled via a small series equivalent resistance. This resistance can provide a certain degree of regulation, but since in general there is no available data, this would require lots of development effort and is not guaranteed to be successful.

Equation for diode voltage drop according to the added series equivalent resistor model

El extraño caso de los LEDs supernova

Esta historia de avaricia de brillo ocurrió hace más de una década. Para esas fechas no teníamos mucha información sobre los LEDs, que se estaban volviendo rápidamente moneda corriente en aplicaciones de iluminación de todo tipo.

Por ese entonces, además de ser el equipo de desarrollo (lo que en estas tierras llamamos R&D), yo tenía a cargo al equipo de soporte técnico en una conocida distribuidora local de componentes electrónicos.

Un día un cliente realiza un fuerte reclamo: los LEDs que había comprado eran defectuosos. Según él los estaba alimentando “a la tensión de funcionamiento exacta”, pero algunos LEDs rápidamente comenzaban a brillar más y más, para luego disminuir su intensidad al punto de ya no funcionar.

La primera pregunta que se nos cruzó por la cabeza fue “¿cuál será la tensión ‘exacta’ de funcionamiento de un LED?”, pero pensamos que estaba asumiendo que ‘típico’ quería decir “a todos los propósitos prácticos: siempre”. Una mirada rápida a la hoja de datos mostró una amplia dispersión en los valores de la tensión esperada a una corriente específica (si bien no recuerdo el modelo particular del LED, las hojas de datos típicas son algo como lo siguiente):

Extracto de una hoja de datos típica de un LED rojo

El cliente había comprado una gran cantidad de LEDs económicos, sin separación entre grupos (bins), y de algún modo consideraba que todos iban a tener el mismo punto de trabajo.

No obstante, le solicitamos el circuito esquemático y procedimos a examinar dicho circuito, que conceptualmente era algo como lo que se ve en la figura siguiente:

Esquema conceptual del circuito esquemático entregado por el cliente

A continuación, le aconsejamos buscar literatura sobre conexión de diodos en paralelo, o, si no encontraba mucho al respecto, las explicaciones sobre operación de transistores en paralelo le iban a resultar útiles.

Lo que el cliente estaba observando era lo siguiente: cuando se alimenta un circuito paralelo como éste, la tensión será la misma para todas las ramas, pero a menos que se seleccionen y emparejen todos los LEDs (y probablemente ni siquiera así), los LEDs van a tener ligeras diferencias en sus curvas de operación, eso es lo que ‘mínimo’, ‘típico’ y ‘máximo’ indican. La tensión “Vled” en el circuito esquemático anterior será diferente para cada LED y puede estar en cualquier valor entre el mínimo y el máximo de la distribución, con una cierta probabilidad de estar cerca del valor típico. Esto es, excitando con (digamos) 20mA, muchos LEDs presentarán una caída de tensión ‘típica’, mientras que una cantidad de los otros se distribuirá a uno y otro lado entre los valores ‘mínimo’ y ‘máximo’ respectivamente. En sentido inverso, la situación es exponencial en vez de logarítmica y aquí las cosas se ponen interesantes, ya que es de esperar observar una variación mucho mayor de la corriente que toma el LED con variaciones pequeñas de la tensión aplicada.

Ecuación del diodo según el modelo de Shockley

La rama con mayor cantidad de LEDs cuya tensión de operación (a la corriente de la rama) está del lado cercano al mínimo en la curva de distribución, va a tener una sumatoria de tensiones de operación menor que las otras ramas, pero al estar conectada en paralelo con éstas, debe necesariamente tener la misma tensión de operación que las otras. Como resultado, esta rama va a tomar mayor corriente para así mover el punto de operación de los LEDs hasta hacer coincidir la sumatoria con la tensión impuesta por el circuito paralelo.

Y aquí es donde el perro se persigue la cola. Los LEDs en la rama que tiene una sumatoria de tensiones de operación (a la corriente que circula por la rama) más alta que las otras, van a calentar más, debido a que el producto VxI en estos LEDs es mayor. Como sabemos, estas junturas semiconductoras PN tienen un coeficiente de temperatura negativo, es decir, la tensión de operación de la juntura es inversamente proporcional a su temperatura, de modo que cuando la temperatura aumenta la tensión de operación disminuye, si tomamos la corriente como una constante. (Observemos la presencia de ‘T’, la temperatura absoluta, en la ecuación del modelo de Shockley y el detalle “Ta=25ºC” en el extracto de la hoja de datos y la curva en la figura anterior)

Entonces, a medida que la tensión disminuye, la rama va a tomar mayor corriente para hacer que la tensión de operación suba e iguale a la del circuito paralelo, ocasionando que los LEDs calienten más, incentivando el círculo vicioso (conocido como embalamiento térmico) que, no habiendo nada que limite la corriente, terminará con la destrucción de algunos LEDs.

Como el brillo del LED es proporcional a la corriente y el color varía con la temperatura, mientras esto sucede observamos que el brillo aumenta a la vez que el color se hace más intenso, hasta un punto de no retorno en el que la juntura se arruina y deja de operar correctamente; disminuyendo considerablemente su capacidad de emitir luz. Si a partir de este punto el resultado de la sumatoria protege a los otros LEDs de la rama o no, depende de los diferentes modos de falla.

La sugerencia que hicimos al cliente fue que, al menos, introdujera en el circuito algo de realimentación negativa: un resistor en serie con cada rama, como se hace al conectar diodos o transistores de potencia en paralelo.

Esquema conceptual del estabilizador por realimentación negativa basado en un conversor corriente/tensión óhmico…

La operación es la siguiente:

Cuando una de las ramas comienza a tomar más corriente, el resistor presenta una mayor caída de tensión que se resta de la tensión de alimentación de la rama, ocasionando que los LEDs de esa rama tengan una menor tensión de alimentación y tomen menos corriente. Un valor pequeño, suficiente como para compensar la variación en las tensiones de umbral posibles, sería lo necesario. El valor “exacto” de ese resistor dependerá de los LEDs en sí y de la cantidad de éstos en serie, y por supuesto no será posible alimentar el circuito a la suma de las tensiones típicas de operación de los LEDs; la solución resultaría tan efectiva como cuanto podamos conjugar todas las variables en juego y a la vez elegir un valor viable para los resistores. No obstante, si bien los LEDs seguirían vivos, tendríamos diferencias de brillo entre las ramas.

Una solución más compleja pero a la vez más estable, consiste en dedicar una fuente de corriente para cada rama. Como sabemos, el brillo de un LED depende de la corriente que circula por él, de modo que controlaremos el brillo manteniendo constante la corriente por la rama, con una fuente de corriente por rama, con todas las ramas alimentadas por una fuente de tensión (debido a que no disponemos de fuentes de corriente ideales sino que las realizaremos con circuitos a base de transistores que mantendrán la corriente relativamente constante dentro de ciertos límites).

Esquema conceptual de la solución genérica en base a circuitos de corriente constante con transistores

La corriente variará con la temperatura, si esto es un problema y necesitamos mantener el brillo dentro de un rango de variación pequeño, deberemos estabilizar nuestras fuentes de corriente en temperatura.

Los LEDs más caros, ya separados en bins o grupos por el fabricante, presentan una dispersión bastante menor:

Extracto de diferentes páginas de una hoja de datos de un LED rojo con separación por grupos (bins). Cada grupo presenta no sólo diferente tensión de operación sino además diferente intensidad luminosa a la misma corriente.

Como podremos calcular, incluso esa diferencia de 100mV puede ocasionar una gran diferencia en la corriente que circulará por diferentes LEDs en paralelo. Con una cuidadosa selección, esas diferencias pueden minimizarse y es posible construir circuitos más simples. De algún modo desplazamos la complejidad del circuito a la logística de aprovisionamiento.

Hilando bien fino, los LEDs, como los diodos, se desvían de esta respuesta exponencial ideal, lo cual se modela con una pequeña resistencia serie equivalente. Esta resistencia puede proveer cierta regulación pero esto requiere mucho ensayo y dedicación dado que por lo general no existen datos confiables al respecto.

Ecuación de la caída de tensión en un diodo según el modelo con resistencia serie equivalente

The strange case of the board that refused to power down

This happened a while ago, in a galaxy not so far away, and where many men have gone before.

Me and this colleague had designed this simple power-on circuit for a portable battery-powered instrument; where a MOSFET bypassed the power-on push button after the microcontroller started, as can be seen in the following diagram.

To power on, the user presses the button, the microcontroller gets powered up, does its initialization sequence, and sets POW_CTRL high, enabling the MOSFET, what keeps the microcontroller powered on after the user releases the button. To power off, the user would press the button again (some time after the initial power up), what is read by the program at the SW_OFF pin, disabling the MOSFET and entering a harmless loop. Once the user releases the button, voltage at ON_PWR begins to decay, and the microcontroller will eventually power off as the capacitors discharge.

This worked great in the lab and in the first units we produced for our customer. But some time later, on another production lot, one or two of the new units could not be powered off; they would restart instead.

I was the one perpetrating the hardware, so I was unanimously elected to be the one in charge of solving this mistery and find the logical reason behind this esoteric behavior.

Having all “failed” units and a couple of “working” units in my workbench, I instructed Mr. O to take a look at the circuit.

The “working” units exhibited a nice exponential roll off where the supply voltage was graciously and monotonically falling down, though some parts of the curve showed changes in the slope.

The “failed” units showed a more pronounced change in slope, with a strange tendency to go horizontal just before the microcontroller would decide to restart. The cyan trace is the supply voltage at ON_PWR, just before an LDO, while the orange trace is the voltage at POW_CTRL, with a nasty weak pull-up resistor to the battery voltage (through that resistor going nowhere in the schematic, part of a more complex circuitry but normally left open or grounded otherwise) to show when the microcontroller had set its pins in Hi-Z mode (at reset time…). (I promise I only did this q&d hack to take the pictures)

Finally, one of those later units was kind enough to let Mr. O show me a small rise in the supply voltage; yes, that small cyan dot within the red circle, just before the orange trace rising edge. I know it can be just measurement noise, but, it looked like the voltage was rising just before the reset was to occur. Yes, no one had pressed the button, and if you are blaming the LDO, well, for all purposes I’ve tested those chips to behave like a wire for all voltages below the regulated one.

My (perhaps educated) guess is that, once the MOSFET was turned off and the microcontroller supply voltage was going down as the main capacitor was discharging, the microcontroller internal circuits (peripherals) took turns powering off and so draining less and less current. That current was provided mostly by the main electrolytic capacitor, and what I was observing was its dQ/dC when dQ was being decimated by di being drained as dt went by; and to that, substract di times its ESR (Equivalent Series Resistance, which for this purpose would serve as an umbrella for perhaps some group of subtle chemical issues). That would explain those changes in slope…

Apparently, in some units, those differences in current when the peripherals turned off and the nonlinearities in the capacitor ESR would cooperate to alter the monotonic behavior in the supply bus (and even perhaps be seen as a small rise in voltage), situation that would trigger the BOR (Brown-Out Reset) and the power-on reset circuitry, and so the microcontroller would restart, enabling the MOSFET again.

Fortunately, due to the power-off functionality, we could check on startup whether the button was being pressed or not. A button pressed condition was the typical power-on situation, while the button not being pressed would signal one of these nasty BOR compulsive restarts, situation we would gladly recognize and elegantly ignore, detouring to a harmless loop waiting for either the power to come definitely down or the user really press the button (’cause sometimes, only sometimes, buttons do bounce; but I guess you do know about that… don’t you ?).