tornamesero: electrónica detrás de tortilla stories

publicado el 2018-10-20

parte del proyecto: tortilla-stories


¡viene una nueva función de tortilla stories!

buena oportunidad para mejorar el diseño electrónico de las tornamesas, más aún porque el proyecto viajó y deberá funcionar sin mi presencia física.

en esta entrada comparto los archivos que las hacen funcionar, con licencia gnu gpl v3 y posteriores, y documento parte del proceso por el que pasé para construir todo este sistema.

foto de una tarjeta electrónica con múltiples cables y componentes

vista previa al cerebrito de las tornamesas

descargas

primero que nada, aquí están los recursos de los que hablo más adelante: el esquemático o diagrama del circuito, todo el proyecto de kicad con el esquemático y pcb en proceso, y el programa de arduino que hace funcionar todo:

rata panchis con un cable en las patas que viene de un microcontrolador

¡feliz de compartir mi trabajo!

circuito

diagrama de circuito

así luce el esquemático final

lista de componentes

  • 1 microcontrolador atmega328
  • 1 cristal de 16 MHz
  • 3 arreglos de transistores uln2003
  • 3 motores a pasos 28byj-48
  • 2 capacitores de 10uF
  • 2 capacitores de 22pF
  • 1 regulador de voltaje L7805
  • 6 potenciómetros
  • 3 LEDs blancos (tornamesas)
  • 1 LED amarillo (parpadeo de vida)
  • 1 LED verde (conectado a corriente)
  • 3 diodos
  • 12 resistores de 1kOhm (para uln2003)
  • 5 resistores pequeños para LEDs
  • 1 resistor 10k para pin de reset
  • 1 cable FTDI u otro convertidor de “usb a serial”
  • 1 fuente de 5V DC o más
  • 1 arduino uno para programar al microcontrolador (u otro medio para programarlo… después investigaré más al respecto)

proceso

en el circuito y función inicial, estuve usando una tarjeta arduino due para controlar todo.

además de que ya lo tenía, parte de por qué lo usé fue por el número de pines digitales a utilizar: 3 de los LEDs de las tornamesas, 2 para la comunicación serial, y 12 para los motores de pasos, pues con la configuración que tenía necesitaba cuatro para cada uno. en total 17 pines digitales, que no cualquier arduino tiene.

para esta segunda iteración, me había quedado con la duda de controlar los motores con menos pines. reencontré esta página de referencia del blog de tom igoe, en donde se describe la conexión de dos pines de control.

casi simultáneamente seguí dos referencias (ArduinoToBreadBoard y Standalone Arduino), en conjunto con el esquemático del arduino uno, contemplando la idea de no usar para el proyecto ni la tarjeta del arduino due ni la del uno, sino el microcontrolador por sí solo.

eventualmente pude juntar ambas investigaciones; la reducción de los pines de control de cuatro a dos por motor resultaba en un total de 11 pines digitales, y el arduino uno tiene 14. antes de la reducción el sistema no cabría en el arduino uno, pero ahora hasta me pude dar el lujo de usar un pin 12 para mostrar el estatus de vida del programa.

foto de un microcontrolador conectado en una tarjeta de prototipado, conectado a su vez a otra tarjeta donde está conectado un motor

el momento de prueba del 'arduino' en la _breadboard_, controlando un motor de pasos con dos pines, y además con comunicación serial via un chip FTDI

además, para esta segunda iteración decidimos agregar controladores físicos del volumen en las tornamesas mismas.

para la primera función habíamos preparado un control con osc, pero desde los días anteriores en pruebas la red inalámbrica a la que conectábamos una tablet con los controles no nos funcionó muy bien. (concluyo que no confío en usar sistemas inalámbricos para artes escénicas)

el arduino uno / atmega328 tiene 6 adcs (convertidores de analógico a digital), el número exacto para 3 potenciómetros de control (activar y desactivar tornamesas) y 3 potenciómetros de volumen. tal vez quedaría pendiente uno más para el volumen máster… pero ese lo podemos dejar en la configuración de todo el sistema del sonido.

todo el sistema queda muy bien usando casi al máximo la capacidad de pines del microcontrolador :)

pcb

ya con ese conocimiento y pruebas resueltas, me pareció interesante explorar hacer una tarjeta impresa (pcb), pues nunca me da tiempo de hacerlas. el primer paso era hacer el esquemático; algo que de cualquier forma me serviría para fines de documentación y de orden a la hora de armar el circuito.

me apoyé en este tutorial de kicad, pero haciendo mi propio circuito en vez del del ejemplo.

hice un par de pruebas e incluso vi que se pueden hacer renders de los circuitos.

imagen de una representación 3D del circuito y sus componentes

render de la primera prueba

eventualmente vi que por practicidad convenía hacer todo en una capa, así que rehice el diseño.

imagen de la plantilla de una tarjeta impresa

aspecto final del diseño de la pcb, todo en una capa

imagen de una representación 3D del circuito y sus componentes

render de la segunda prueba

por cuestión de tiempo (jeje) y de materiales, su producción sigue pendiente. tuve que resolver usando una tarjeta perforada.

tarjeta perforada

el diseñar previamente la pcb me ayudó mucho a familiarizarme con el circuito, con el microcontrolador, y con un ordenamiento “óptimo”.

con una tarjeta perforada que encontré, con algunos canales en la parte posterior, implementé todo el circuito.

foto de un circuito sobre una tarjeta perforada

después de las primeras horas de trabajo, los componentes soldados y los transistores en proceso de conexión

originalmente había pensado en hacer y usar conectores para los diferentes elementos de las tornamesas: los motores ya tienen los suyos, pero pensé en hacer también unos para los potenciómetros y los LEDs.

como sea, por practicidad y robustez, decidí por el momento soldarlos a la tarjeta.

foto de un circuito sobre una tarjeta perforada, unos cables largos salen de ella con potenciómetros en sus extremos

acabando de soldar los potenciómetros

foto de un circuito sobre una tarjeta perforada, cables largos salen de ella, y en sus extremos tienen o potenciómetros o LEDs

ya con los LEDs

después de muchas horas de soldar, y de diversas pruebas de continuidad en las conexiones, lo conecté a la corriente y…

foto de una tarjeta electrónica con múltiples cables y componentes

¡se encendió!

tuve que hacer varios ajustes ya revisando cada componente - supuse que si funcionaba todo a la primera iba a ser muy extraño, y tuve razón. algunos pines no estaban bien conectados, y por equivocación soldé un LED que no servía.

¡pero eventualmente quedó!

código

un poco en paralelo al proceso anterior, modifiqué el programa que tenía previamente para 1) usar el control de motores de pasos con dos pines, 2) usar los potenciómetros de volumen e implementar el protocolo de volumen, y 3) parametrizar los pines y asegurarme que concordaran con los del esquemático.

además agregué un LED de estado que parpadea una vez por segundo para asegurarme de que el microcontrolador estuviera funcionando al conectarlo a la corriente.

protocolo final

en el programa tengo las siguientes definiciones del protocolo de comunicación:

#define C_ENCIENDE 0x20
#define C_MOTORON 0x30
#define C_MOTOROFF 0x40
#define C_ACTIVA 0x50
#define C_APAGA 0x60
#define C_VOL 0xA0

la idea es que los primeros cinco mensajes llevan el número de la tornamesa correspondiente (0, 1 o 2) en el nibble de la derecha.

en el mensaje de volumen, el número de tornamesa se suma al nibble de la izquierda, y en el nibble de la derecha se pone el valor convertido a 4 bits del potenciómetro correspondiente.

los primeros tres mensajes los recibe el microcontrolador, y los últimos tres los envía.

por ejemplo el mensaje 0x20 (el número 20 en hexadecimal, 32 en decimal, o un espacio en ascii) le indica al sistema que hay que encender el LED de la tornamesa 0 (o “1”, desde el punto de vista exterior). 0x21 encendería el de la tornamesa 1 (o “2”).

el mensaje 0xAF desde el arduino indica que el volumen de la tornamesa 0 fue llevado al máximo, mientras que el mensaje 0xC0 indica que el volumen de la tornamesa 2 fue llevado al mínimo.

el programa

aquí copio el código del programa, pero también se puede descargar del enlace casi hasta arriba de esta página.

/*
 tornamesero Motorizado 2
 copyright 2018 rata panchis

 este programa es software libre
 bajo la licencia GPL v3 y posteriores

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

// pines para leds de tornamesas
#define LED1 10
#define LED2 11
#define LED3 12
#define LEDSTATUS 13

// pines de control para motores
#define M1C1 2
#define M1C2 3
#define M2C1 4
#define M2C2 5
#define M3C1 6
#define M3C2 7

// pines de adc
#define POT1 A0
#define VOL1 A1
#define POT2 A2
#define VOL2 A3
#define POT3 A4
#define VOL3 A5

#include <Stepper.h>
#define NUM_STEPS 2
#define MOTOR_SPEED 60
const int stepsPerRevolution = 512;  
Stepper motor2 = Stepper(stepsPerRevolution, M3C1, M3C2);
Stepper motor1 = Stepper(stepsPerRevolution, M2C1, M2C2);
Stepper motor0 = Stepper(stepsPerRevolution, M1C1, M1C2);

#define NUM_TORNAMESAS 3
#define TIMER_DELAY 50
#define THRESHOLD_POT 800
#define TH_TIMEOUT 100
#define LEDSTATUS_DELAY 1000

// protocolo
#define C_ENCIENDE 0x20
#define C_MOTORON 0x30
#define C_MOTOROFF 0x40
#define C_ACTIVA 0x50
#define C_APAGA 0x60
#define C_VOL 0xA0

// estado de cada tornamesa
bool estado [NUM_TORNAMESAS]; 
// estado de cada motor 
bool estadoMotor [NUM_TORNAMESAS];

int timer[NUM_TORNAMESAS];
int led[NUM_TORNAMESAS];
int pinpots[NUM_TORNAMESAS];
int pinvols[NUM_TORNAMESAS]; 
int vols[NUM_TORNAMESAS];
unsigned long timeout[NUM_TORNAMESAS];


void setup() {
  Serial.begin(115200);

  led[0] = LED1;
  led[1] = LED2;
  led[2] = LED3;

  pinpots[0] = POT1;
  pinpots[1] = POT2;
  pinpots[2] = POT3;
  
  pinvols[0] = VOL1;
  pinvols[1] = VOL2;
  pinvols[2] = VOL3;

  motor0.setSpeed(MOTOR_SPEED);
  motor1.setSpeed(MOTOR_SPEED);
  motor2.setSpeed(MOTOR_SPEED);

  for(int i=0; i<NUM_TORNAMESAS; i++){
	  pinMode(led[i],OUTPUT);
	  timeout[i] = millis();
	  vols[i] = 0;
	  enciende(i);
  }
}

void loop() {

  int valorPot = 0;
  int valorVol = 0;

  for(int i=0; i<NUM_TORNAMESAS; i++){
	  if(timer[i] > 0){
		timer[i]--;
	  }
	  else{
		digitalWrite(led[i],LOW);	
	  }

	  // **** pot

	  valorPot = analogRead(pinpots[i]);
          // si está apagado y el sensor supera el threshold
	  // activa
          if(!estado[i] && 
	  	valorPot >= THRESHOLD_POT && 
		esTimeout(i)){
            estado[i] = true;
            activa(i);
          }
          // si está encendido y el sensor está por debajo
	  // apaga
          else if(estado[i] && 
	  	valorPot < THRESHOLD_POT && 
		esTimeout(i)){
            estado[i] = false;
	    apaga(i);
          }

	  // **** vol
	  // reduce la resolución de 10 bits del adc a 4
	  valorVol = analogRead(pinvols[i]) >> 6;

	  // si el volumen cambió, actualízalo
	  if(valorVol != vols[i]){
		vols[i] = valorVol;
		actualizaVol(i);
	  }

  }

  int cuentaMotores = 0;
  if(estadoMotor[0]){
    motor0.step(NUM_STEPS);
    cuentaMotores++;
  }
  if(estadoMotor[1]){
    motor1.step(NUM_STEPS);
    cuentaMotores++;
  }
  if(estadoMotor[2]){
    motor2.step(NUM_STEPS);
    cuentaMotores++;
  }

  recibeMensajes();

  digitalWrite(LEDSTATUS, ((millis()/LEDSTATUS_DELAY)%2==0));

  if(cuentaMotores < 1){
    delay(10);        
  }
}

boolean esTimeout(int i){
	if(millis()-timeout[i] > TH_TIMEOUT){
		timeout[i] = millis();
		return true;
	}
	return false;
}

//void serialEvent(){
void recibeMensajes(){
  char ch, cod, i;
  while(Serial.available()>0){
	ch = (char)Serial.read();
	cod = ch & 0xF0;
	i = ch & 0x0F;

	switch(cod){
		case C_ENCIENDE:
		  enciende(i);
		break;
		case C_MOTORON:
		  motorOn(i);
		break;
		case C_MOTOROFF:
		  motorOff(i);
		break;
	}
  }
}

void enciende(int i){
  timer[i] = TIMER_DELAY;
  digitalWrite(led[i],HIGH);
}

void motorOn(int i){
	estadoMotor[i] = true;
}

void motorOff(int i){
	estadoMotor[i] = false;

}

// envía la señal de leer y encender la tornamesa
void activa(int i){
  Serial.write(C_ACTIVA | i);
}

// envia la señal de apagar la tornamesa
void apaga(int i){
  Serial.write(C_APAGA | i);
}

// el volumen ha cambiado, envía la actualización
void actualizaVol(int i){
  Serial.write( (C_VOL + (i<<4)) | vols[i]);
}

prueba serial

normalmente estaba probando la comunicación serial y el funcionamiento de los componentes abriendo minicom y probando uno por uno los caracteres que podía enviar desde la computadora, y luego leyendo los caracteres enviados al activar y desactivar las tornamesas.

al agregar al protocolo los mensajes de volumen, y por la codificación que elegí, minicom no los muestra como caracteres.

encontré que hay forma de usarlo con notación hexadecimal:

$ minicom -H -D /dev/ttyUSB0

como sea, para automatizar relativamente las pruebas, y ayudar al equipo de trabajo que preparará el sistema en la próxima función, decidí hacer un script.

tal vez lo pude haber escrito en processing pero me pareció interesante investigar cómo hacerlo con bash.

tuve que aprender sobre cómo hacer funciones en bash, sobre od, y también sobre stty para configurar el puerto. tal vez luego hago un pequeño post de notas de viaje para hablar al respecto.

a continuación presento el script resultante. primero prueba los mensajes que van al arduino (enciende LED, enciende motor, apaga motor) en cada tornamesa, y luego pide que se pruebe cada potenciómetro, pidiendo confirmación visual por la usuaria.

esta segunda parte tiene algunas áreas de mejora, pero para nuestros fines está bien.


#!/bin/bash
#script para probar la comunicación serial
# y componentes del tornameseroMotorizado
puerto=/dev/ttyUSB0

# parametros texto mensaje, caracter, sleep
escribe_puerto(){
	echo $1
	echo -e $2 > $puerto
	sleep $3
}

# parametros: texto, numero de caracteres
lee_puerto(){
	echo $1
	od -A n -t x1 -v -w1 -N $2 < $puerto
}

echo "iniciando la rutina de prueba de las tornamesas"
stty -F $puerto speed 115200 raw cs8 -cstopb
echo "probando leds..."
sleep 1
escribe_puerto "encendiendo led 1" "\x20" 1
escribe_puerto "encendiendo led 2" "\x21" 1
escribe_puerto "encendiendo led 3" "\x22" 1

sleep 1
echo "probando motores..."
escribe_puerto "encendiendo motor 1" "\x30" 3
escribe_puerto "encendiendo motor 2" "\x31" 3
escribe_puerto "encendiendo motor 3" "\x32" 3
escribe_puerto "apagando motor 1" "\x40" 3
escribe_puerto "apagando motor 2" "\x41" 3
escribe_puerto "apagando motor 3" "\x42" 3
sleep 1

echo "probemos potenciómetros..."

lee_puerto "activa y desactiva la tornamesa 1 \
  un par de veces. debes ver el número 50 y 60" 4
lee_puerto "activa y desactiva la tornamesa 2 \
  un par de veces. debes ver el número 51 y 61" 4
lee_puerto "activa y desactiva la tornamesa 3 \
  un par de veces. debes ver el número 52 y 62" 4

echo "probemos volumenes..."

lee_puerto "sube y baja el volumen 1 completamente.\
  debes ver el rango entre a0 y af" 31 
lee_puerto "sube y baja el volumen 2 completamente.\
  debes ver el rango entre b0 y bf" 31 
lee_puerto "sube y baja el volumen 3 completamente.\
  debes ver el rango entre c0 y cf" 31 

echo "¡prueba finalizada!"

conclusión

muchas lecciones y disfrute en esta segunda iteración. ya “capacité” a mel con cómo instalar y usar el sistema, y este post también servirá como documentación por si se necesita.

queda pendiente hacer la pcb, ya sin tanta prisa pero con miras de seguir explorando ese medio.

por último, este proceso me motivó a seguir explorando el uso de microcontroladores fuera de tarjetas de arduino… ya iré compartiendo :)