TDSO112A

GUI para DSO112A

Posted by jm on Sunday, April 26, 2026

TDSO112A - Una GUI para el DSO112A

Desde hace ya algún tiempo, estoy desarrollando una interfaz para el control y manejo del pequeño DSO112A.

La verdad que estoy muy contento porque la arquitectura tanto del programa de sobremesa, desarrollado en Tcl/Tk, como del firmware del microcontrolador (un Arduino UNO), me han posibilitado desarrollar en mis pocos tiempos libres, una aplicación que uso casi a diario y colma mis espectativas. Es muy posible que la elección de un lenguaje interpretado como Tcl me haya ayudado, de modo inconsciente, a cumplir los objetivos en tan poco tiempo.

Sigrok

Aunque en la versión 1.2 ya tenía implementados el protocolo del sensor de humedad y temperatura DHT11 y UART, estaba claro que el proyecto no podría crecer rápido si tenía que escribir todos los protocolos que quería utilizar, así que busqué una solución que pudiera integrar, y encontré Sigrok.

Al tener implementada una arquitectura cliente/servidor no me costó mucho integrarla, y pasé de poder analizar solo señales UART a 9600 8N1, a poder usar cualquier tipo de configuración que se pueda usar en Sigrok en la versión 1.3.

UART

Una cosa que me resultó muy interesante, es que se pueden apilar las anotaciones que quieres visualizar, por ejemplo, poder ver los bits del bus, pero también el equivalente en ASCII, por ejemplo. Además, ya no estaba solo limitado a 9600bps sino que podía utilizar una cornucopia de velocidades y parámetros.

DHTX / AM230X

Le tocaba ahora el turno a mi querido sensor de humedad y temperatura DHT11, una especie de desperdicio electrónico, por sus prestaciones, pero que me gusta utilizar desde que escribí un protocolo para usarlo por puro entretenimiento hará años mil.

Una vez realizada la integración de Sigrok esta integración no tenía ningún misterio y era cuestión de minutos… pero la cosa se complicó un poco.

El primer problema con que me encontré es que el decodificador de Sigrok exigía 200ksps, pero con los 1024 puntos del DSO112A y teniendo en cuenta que los primeros 18ms de la línea de datos son solo para ver la línea a nivel bajo, era imposible que funcionara, pero si yo lo había conseguido con 125ksps alguna posibilidad tenía.

El protocolo, grosso modo, es el siguiente:

Protocolo DHT11

DHT11 MCU

En mi decodificador, simplemente esperaba un flanco positivo, Pull up & wait for sensor response de la primera imagen, para saber que se estaban enviando los datos, y así evitaba los 18ms iniciales que nada aprotaban, así que de alguna manera estaba obligado a hacerlo así.

Datos del DHT11

Ahora bien, por alguna razón, que veremos más tarde, el decodificador no funcionaba (la imagen corresponde al programa ya funcionando de forma correcta).

No se me ocurría qué podría estar pasando, así que ejecuté la versión 1.2 y… ¡¡tampoco funcionaba!! Esto no podía ser, siempre me había funcionado y no había cambiado nada en el sensor… Había algo raro que no entendía, hasta que se me ocurrió hacer zoom en la parte en la que la MCU pone la línea a nivel bajo:

Pulso de la discordia

¿Qué hacía ese pulso ahí? Ahora estaba claro porqué la versión 1.2 tampoco funcionaba: ¡ahí está el flanco positivo que yo esperaba, pero no en esa posción! pero lo que me parece extrañísimo es que uso ese sensor casi a diario y nunca me había fallado mi decodificador. Descarto que el sensor se haya estropeado y releyendo la documentación no veo por ninguna parte que ese pulso debiera existir porque pone claramente que se la MCU deja el bus a nivel bajo 18ms y después sí que envía un pequeño pulso a nivel alto para liberar el bus. No estoy seguro al 100% pero puede ser que sea normal, por tanto entiendo que con una base de tiempos de 0.2ms simplemente he tenido muchísima suerte y no he detectado ese pulso durante este tiempo (y de hecho con 0.2ms la verdad que cuesta mucho verlo: o no se da o es casi inperceptible).

Esto, evidentemente, crea un grave problema: ya no puedo usar pulse->positive (al menos ya no…) para saber cuándo empiezan los datos. De alguna forma tengo que obviar los primeros milisegundos para captar solo los datos, aunque no descarto que haya modelos en los que no haga falta usar el trigger externo y simplemente podamos utilizar simplemente el trigger interno y esperemos a un flanco positivo.

Trigger externo

Afortunadamente el DSO112A tiene un trigger externo, así que solo tengo que crear un pulso pasados unos 16ms desde que la MCU indica al sensor que quiere los datos. Así que modificamos nuestro programa de Arduino para hacer justamente eso:

/*
  keyestudio ESP32 Inventor Learning Kit 
  Project 23.1 Smart Cup
  Generar trigger externo para DSO112A e intentar
  decodificar el protocolo del DHT11
*/
#define DHT_PIN      2
#define TRIGGER_PIN  8
#define TRIGGER_US   100

#include <xht11.h>
xht11 xht(DHT_PIN);
//Define an array to store temperature and humidity data
unsigned char dat[] = {0,0,0,0};

volatile bool timerArmed = false;
volatile unsigned long lastStartUs = 0;

// ----------------------------------------------
void dhtStartFallingISR() {
  unsigned long now = micros();

  // Bloqueo de 30 ms para ignorar flancos posteriores
  if ((now - lastStartUs) < 30000) return;
  if (timerArmed) return;

  lastStartUs = now;
  timerArmed = true;

  noInterrupts();

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  // T = (OCR1A + 1) · prescaler / F_CPU
  // T = 3750 · 64 / 16000000
  // 15 ms
  //OCR1A = 3749;

  // 16 ms
  OCR1A = 3999;
  TCCR1B |= (1 << WGM12);              // CTC
  TCCR1B |= (1 << CS11) | (1 << CS10); // /64
  TIMSK1 |= (1 << OCIE1A);

  interrupts();
}

// ----------------------------------------------
ISR(TIMER1_COMPA_vect) {
  TCCR1B = 0;
  TIMSK1 &= ~(1 << OCIE1A);

  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(TRIGGER_US);
  digitalWrite(TRIGGER_PIN, LOW);

  timerArmed = false;
}

// ----------------------------------------------
// ----------------------------------------------
void setup() {
  Serial.begin(9600);

  pinMode(TRIGGER_PIN, OUTPUT);
  digitalWrite(TRIGGER_PIN, LOW);

  pinMode(DHT_PIN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(DHT_PIN), dhtStartFallingISR, FALLING);
}

// ----------------------------------------------
void loop() {
  //Check correct return to true
  if (xht.receive(dat)) {
    //The integral part of humidity,dht[1] is the decimal part
    Serial.print("RH:");
    Serial.print(dat[0]);
    Serial.print("%  ");
    //The integer part of the temperature,dht[3] is the decimal part
    Serial.print("Temp:");
    Serial.print(dat[2]);
    Serial.println("C");
  } else {
    Serial.println("sensor error");
  }
  delay(1500);
}

Aquí lo único que está añadido, respecto a un programa ordinario que use el DHT11, es lo relativo al pulso:

#define TRIGGER_PIN  8
#define TRIGGER_US   100
...
volatile bool timerArmed = false;
volatile unsigned long lastStartUs = 0;

// ----------------------------------------------
void dhtStartFallingISR() {
  unsigned long now = micros();
  
  // Bloqueo de 30 ms para ignorar flancos posteriores
  if ((now - lastStartUs) < 30000) return;
  if (timerArmed) return;
  
  lastStartUs = now;
  timerArmed = true;
  
  noInterrupts();
  
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  
  // T = (OCR1A + 1) · prescaler / F_CPU
  // T = 3750 · 64 / 16000000
  
  // 16 ms
  OCR1A = 3999;
  TCCR1B |= (1 << WGM12);              // CTC
  TCCR1B |= (1 << CS11) | (1 << CS10); // /64
  TIMSK1 |= (1 << OCIE1A);
  
  interrupts();
}

// ----------------------------------------------
ISR(TIMER1_COMPA_vect) {
  TCCR1B = 0;
  TIMSK1 &= ~(1 << OCIE1A);

  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(TRIGGER_US);
  digitalWrite(TRIGGER_PIN, LOW);

  timerArmed = false;
}

// ----------------------------------------------
// ----------------------------------------------
void setup() {
  ...
  pinMode(TRIGGER_PIN, OUTPUT);
  digitalWrite(TRIGGER_PIN, LOW);
  ...
  attachInterrupt(digitalPinToInterrupt(DHT_PIN), dhtStartFallingISR, FALLING);
}

Donde básicamente, detectamos cuando el DHT_PIN pasa a nivel bajo y entonces contamos 16ms (aquí tal vez se podría haber usado delay() pero quería algo más preciso) mediante dhtStartFallingISR() y una vez contados la ISR(TIMER1_COMPA_vect) crea un pulso de 100us.

Sigrok - AM230X

Ahora ya solo falta modificar ligeramente el decoder de Sigrok para aceptar sampleados de 125ksps y empezar en el estado en que esperamos los datos.

# 125kps
timing = {
    'START LOW'     : {'min': 750, 'max': 25000},
    'START HIGH'    : {'min': 8,   'max': 10000},
    'RESPONSE LOW'  : {'min': 48,  'max': 96},
    'RESPONSE HIGH' : {'min': 48,  'max': 96},
    'BIT LOW'       : {'min': 48,  'max': 96},
    'BIT 0 HIGH'    : {'min': 16,  'max': 40},
    'BIT 1 HIGH'    : {'min': 56,  'max': 88},
}
...
def reset_variables(self):
        #self.state = 'WAIT FOR START LOW'
        self.state = 'WAIT FOR START HIGH'
...

Sourceforge

Ejecutable y más información en Sourceforge.

Youtube

Vídeo explicativo Youtube.