jueves, 30 de mayo de 2013

PWM síncrono | Synchronous PWM

Ya sabemos cómo controlar la velocidad y el sentido de la marcha de un tren con Arduino, ahora vamos a ver cómo aplicarlo a una maqueta analógica. El principal problema que se nos presenta es el de las transiciones entre cantones de vía alimentados con diferentes fuentes PWM. No voy a extenderme explicándolo ya que hay dos blogs que lo hacen estupendamente, mejor de lo que yo lo haría.

El primero es “AGP SchwarzWaldBahn 220” de A. G. Pino, dónde plantea el problema que ocurre cuando un tren toma la corriente de dos tramos de vía alimentados cada uno por una fuente PWM diferente:

El segundo es “Mi Maqueta Marklin Z”de Ignacio de la Fuente, donde explica una posible solución al problema planteado:
    PWM (y III): Solución bidireccional.
    Cab Control: Varios reguladores que pueden asignarse a cualquier cantón aislado del circuito.

Bueno, pues yo propongo otra solución. Una solución que sólo funciona en teoría pero no en la práctica ya que el ancho de los pulsos PWM dependerá de la tolerancia de los componentes utilizados.

Mi idea es la de sincronizar un número n de generadores PWM alineados a la izquierda. ¿Y cómo se consigue esto? Pues muy sencillo, con una señal de reloj y n generadores de un único pulso de una duración menor o igual que la frecuencia de dicho reloj. Veamos la siguiente imagen:
We know how to control the speed and direction of a train with Arduino, now let's see how to apply it to an analog layout. The main issue we have to overcome are transitions between isolated blocks of track using different PWM sources. I will not dwell explaining it as there are two blogs that do this great, better than I would do (Sorry, these blogs are only in spanish)

The first is “AGP Schwarzwaldbahn 220” by A. G. Pino, where the problem happens when a train is powered from two different sections of track, each with a different PWM source:

The second one is “Mi Maqueta Marklin Z” by Ignacio de la Fuente, explaining a possible solution to the problem:

Well, I propose another solution. A solution which works only in theory but not in practice since the PWM pulse widths depend on the tolerances of the components used.

My idea is to synchronize n left-aligned PWM sources. And how is this achieved? Very simple, with a clock signal and n single pulse sources with a pulse duration less or equal than clock frequency.
Consider the following image:


Teoría de PWM síncrono
Synchronous PWM theory

Cada una de las fuentes de pulso genera un único pulso cada vez que el reloj pasa de estado alto a bajo. Si el reloj es constante, se generarán pulsos constantes. Para construir estos circuitos se emplea el circuito integrado 555 en configuración monoestable y para el reloj una salida PWM de Arduino con un valor de entre 240 y 250 (sobre 255). Para evitar disparos erróneos se coloca al final del reloj una resistencia pullup de 10K.
Each of the pulse sources produces a single pulse each time clock goes from high to low. If clock is constant, constant pulses are generated. To make these circuits we have to use 555 ic in monostable configuration and an Arduino PWM output for generating clock, with a PWM value of between 240 and 250 (about 255). To prevent erroneous triggering, a 10K pullup resistor must be placed at the end of the clock line.

555 monoestable
Monostable 555
La frecuencia que he elegido para el reloj es de 62500Hz, o lo que es lo mismo un pulso cada 16us. En Arduino UNO, por ejemplo, esta frecuencia sólo se puede conseguir con los pins 5 y 6 que son los que están conectados al TIMER0.

Para calcular los valores de los componentes en configuración monoestable se utiliza la siguiente función:
I've chosen 62500Hz as clock frequency, or a pulse every 16us. In Arduino UNO, for example, this frequency can only be achieved using pins 5 and 6, which are currently connected to TIMER0.

Use the following function in order to calculate the values for the components in monostable configuration:
    T = 1.1 * R * C
Dónde los valores son en unidades:
    T en segundos. R en ohmios. C en faradios.
Where the values are in units:
    T in seconds. R in ohms. C in farads.
Para obtener 16us usando un potenciómetro de 10K tenemos que:
To obtain a 16us pulse using a 10K potentiometer we have:
    16us = 1.1 * 10K * C C = 16us / (1.1 * 10K) C = 1.45e-9 C ~ 1500pF
Resolviendo T con C = 1500pF:
Solving T with C = 1500pF:
    T = 1.1 * 10K * 1500pF T = 0.0000165s T = 16.5us
Obtenemos un tiempo superior al del reloj, por lo tanto el valor máximo que usaremos en el potenciómetro será:
We get a time higher than the clock time, therefore the maximum value that we will use in the pot will be:
    R = T / (1.1 * C) R = 16us / (1.1 * 1500pF) R ~ 9K70
A partir del 97% del potenciómetro tendremos una señal PWM del 100%.

Para la primera prueba he utilizado 3 circuitos monoestables con resistencias fijas de 4K7 para obtener aproximadamente la mitad del ciclo PWM en las tres salidas:
With a pot value upper than 97% we will have a 100% PWM signal.

For the first test I used three monostable circuit with 4K7 fixed resistors, to get about half of the PWM cycle at all three outputs:





Se puede observar como el inicio del pulso de las tres señales está perfectamente sincronizado, no ocurre lo mismo con el final, ya que ni las tres resistencias ni los tres condensadores son exactamente iguales entre sí. Las resistencias tienen una tolerancia de 5%, por lo tanto sus valores pueden variar entre 4465 y 4935 ohmios. Los condensadores que he utilizado también tienen una tolerancia del 5%, por lo tanto sus valores oscilan entre 1425 y 1757 picofaradios.

Para la segunda prueba he sustituido las resistencias fijas de 4K7 por un potenciómetro digital de 10K de Microchip, el MCP4131.
It can be seen that the pulse start of all three signals are perfectly synchronized, not so with the signal end, as neither the three resistors and three capacitors are exactly equal. The resistors have a tolerance of 5%, so their values may vary between 4465 and 4935 ohms. The capacitors I've used also have a tolerance of 5%, so their values range are between 1425 and 1757 pF.

For the second test I replaced the 4K7 fixed resistors by a digital 10K potentiometer from Microchip, the MCP4131.






Se puede apreciar una mayor diferencia en la salida ya que estos potenciómetros tienen una tolerancia del 20%.

Como ya he dicho antes, esta es una solución teórica, una solución que sólo funcionaría si los componentes tuviesen tolerancia 0%.

Para finalizar, me gustaría recomendar la lectura de los dos blogs a los que he hecho referencia al inicio de este artículo:

We can see a major difference at the outputs, as these potentiometers have a tolerance of 20%.

As I said before, this is a theoretical solution, a solution that would only work if all of the components had a tolerance of 0%.

Finally, I'd recommend reading the two blogs to which I referred at the beginning of this article:






Descargas:
Downloads:

TestFixRes.zip

TestMCP4131.zip





Enlaces:
Links:

Cantón ferroviario (ES): http://es.wikipedia.org/wiki/Cantón_ferroviario

Model RR blocks (EN): http://modeltrains.about.com/od/bmodelrailroadterms/g/block.htm

Circuito integrado 555 (ES): http://es.wikipedia.org/wiki/Circuito_integrado_555

555 integrated circuit (EN): http://en.wikipedia.org/wiki/555_IC

NE555 (EN): http://www.ti.com/lit/ds/symlink/ne555.pdf

MCP4131 (EN): http://ww1.microchip.com/downloads/en/DeviceDoc/22060b.pdf


AGP SchwarzWaldBahn 220 (ES): http://agp-schwarzwaldbahn.blogspot.com.es
Mi Maqueta Marklin Z (ES): http://mimaquetaz.blogspot.com.es




domingo, 19 de mayo de 2013

Mejorando la lectura analógica | Improving analog reading

En la anterior versión del analizador lógico cometí un error, deshabilitaba las interrupciones mientras leía el puerto analógico, esto producía errores en el reloj encargado de calcular el tiempo total.
In the previous version of the logic analyzer I made a mistake, I disabled the interrupts while reading the analog port, it causes errors in the clock that calculates the total time.


...

Serial.end();
noInterrupts();  
BeginRead();
interrupts();
Serial.begin(115200);
Serial.write(_data, DATA);

...

void ReadAnalog()
{

  t = micros();
  for(i=0; i<DATA; i+=2) // 2 cycles
  {

    /*** From wiring_analog.c ***/

    // start the conversion
    sbi(ADCSRA, ADSC); // 2 Cycles

    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC)); // 13 * 16 Cycles

    // we have to read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    _data[i+1] = ADCL; // 2 cycles
    _data[i]   = ADCH; // 1 cycle

    /****************************/
    
  }
  
  t = micros() - t;
  _data[DATA-4] = (byte)(t >> 24);
  _data[DATA-3] = (byte)(t >> 16);
  _data[DATA-2] = (byte)(t >> 8);
  _data[DATA-1] = (byte)(t);


}


Se puede calcular el error de la siguiente manera:

Tiempo de cada iteración:

    for: 2 ciclos
    sbi: 2 ciclos
    leer analógico: 208 ciclos (13 ciclos ADC * 16 prescaler)
    leer ADCL : 2 ciclos (1 para incrementar el índice y 1 para asignar al array)
    leer ADCH: 1 ciclo
    Total: 215 ciclos

Teníamos 898 iteraciones:


    898 iteraciones * 215 ciclos = 193070 ciclos
    193070 ciclos * 62.5 ns = 12.07 ms


Al deshabilitar las interrupciones y calcular el tiempo con la función "micros()" obteníamos aproximadamente 1200 us, unas mil veces menos de lo esperado.

Así que al final, he decidido no calcular el tiempo y leer 900 veces (1800 bytes), y actuar de la misma forma que en la lectura digital.
Error can be calculated as follows:

Time per iteration:

    for: 2 cycles
    sbi: 2 cycles
    analog read: 208 cycles (13 ADC cycles * 16 prescaler)
    read ADCL: 2 cycles (1 for increasing index and 1 to assign the array)
    read ADCH: 1 cycle
    Total: 215 cycles


We had 898 iterations:

    898 iterations * 215 cycles = 193070 cycles
    193070 cycles * 62.5 ns = 12.07 ms


By disabling interrupts and calculate the time using "micros()" we're getting about 1200 us, a thousand times less than expected.

So in the end, I decided not calculate the time and read 900 times (1800 bytes), and act at the same way as digital reading.





Descargas:
Downloads:

LogicAnalyzer.zip



miércoles, 8 de mayo de 2013

Analizador lógico (analógico) | Logic analyzer (analog)

Para leer un canal analógico la cosa cambia. No podemos leer el puerto completo, tenemos que leer un pin analógico y esperar a que el microcontrolador haga la conversión del voltaje de entrada a un valor numérico legible. Por defecto, Arduino utiliza el prescaler del reloj del conversor analógico-digital con un valor de 128, que significa que el reloj ADC trabajará a una frecuencia de 125KHz.

    16 MHz / 128 = 125 KHz

Esto lo podemos ver en el archivo "Arduino \hardware\arduino\cores\arduino\wiring.c"
To read an analog channel things change. We can't read the full port, we have to read an analog pin and wait for the microcontroller do the conversion of the input voltage to a numerical value. By default, Arduino uses ADC clock prescaler with a value of 128, which means that the ADC clock work at a frequency of 125KHz.

    16 MHz / 128 = 125 KHz

This can be seen in the "Arduino\hardware\arduino\cores\arduino\wiring.c"

#if defined(ADCSRA)
 // set a2d prescale factor to 128
 // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
 // XXX: this will not work properly for other clock speeds, and
 // this code should use F_CPU to determine the prescale factor.
 sbi(ADCSRA, ADPS2);
 sbi(ADCSRA, ADPS1);
 sbi(ADCSRA, ADPS0);

 // enable a2d conversions
 sbi(ADCSRA, ADEN);
#endif


Si miramos el datasheet de ATmega328, en la página 263, en el punto 23.9.2 podemos ver la siguiente tabla:
Looking at ATmega328 datasheet, on page 263, in section 23.9.2 we can see the following table:


7 6 5 4 3 2 1 0
ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0


Donde ADPSx son los 3 bits que corresponden al valor del prescaler del reloj. En la página siguiente (264), en la tabla 23-5, podemos ver qué valor debemos asignar a cada bit para establecer el factor de división:
Where ADPSx are 3 bits corresponding to the clock prescaler value. On the next page (264), Table 23-5, we can see what value we have to assign each bit to set the division factor:


ADPS2 ADPS1 ADPS0 Division factor
0 0 0 2
0 0 1 2
0 1 0 4
0 1 1 8
1 0 0 16
1 0 1 32
1 1 0 64
1 1 1 128


Una lectura ADC tarda aproximadamente 13 ciclos (pag 263), necesitamos un valor de prescaler que nos asegure velocidad a la vez que fiabilidad en el resultado. Si usamos un prescaler de 16, tenemos 76923 lecturas por segundo:
ADC read takes about 13 cycles (p 263), we need a prescaler value that assures us speed and reliability in the result. If we use a prescaler of 16, we have 76923 scans per second:


    16000000 / 16 = 1000000
    1000000 / 13 = 76923

 sbi(ADCSRA, ADPS2);
 cbi(ADCSRA, ADPS1);
 cbi(ADCSRA, ADPS0);


El tiempo total de la ejecución de lectura puede ser variable, así que lo mejor es leer el tiempo total y enviarlo también por puerto serie. Como en el modo digital hemos utilizado un buffer de 1800 bytes y sabiendo que la lectura analógica retorna un valor entero de 2 bytes y que el tiempo en microsegundos es un entero largo sin signo, sabemos que podremos llegar a leer 898 valores:
The total execution time for reading can be variable, so the best is to read the total spend time and send it also via serial port. As digital mode we used a buffer of 1800 bytes and knowing that analog reading returns a 2-byte integer value and the time in microseconds is an unsigned long, we know we can get to read 898 values:


    1800 - 4 = 1796
    1796 / 2 = 898


#define DATA 1800
#define DATAA 1796

byte _data[DATA];
unsigned long t;

void setup()
{

  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  cbi(ADCSRA, ADPS0);

  // DEFAULT Analog reference + A0 input pin
  ADMUX = (DEFAULT << 6); // | (pin & 0x07);

}

void ReadAnalog()
{

  t = micros();

  for(i=0; i<DATAA; i+=2)
  {

    /*** From wiring_analog.c ***/

    // start the conversion
    sbi(ADCSRA, ADSC);

    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC));

    // we have to read ADCL first; doing so locks both ADCL
    // and ADCH until ADCH is read.  reading ADCL second would
    // cause the results of each conversion to be discarded,
    // as ADCL and ADCH would be locked when it completed.
    _data[i+1] = ADCL;
    _data[i]   = ADCH;

    /****************************/
    
  }

  t = micros() - t;
  _data[DATA-4] = (byte)(t >> 24);
  _data[DATA-3] = (byte)(t >> 16);
  _data[DATA-2] = (byte)(t >> 8);
  _data[DATA-1] = (byte)(t);

}







PRUEBAS:
TESTS:


Al igual que en el artículo anterior, he programado otra placa Arduino para que genere los datos a leer. Se trata de una onda sinusoidal que oscila entre 0 y 5V.

Si miramos el enlace de la Wikipedia podemos ver la siguiente expresión. Tomamos la fase inicial como 0 (φ). Sabemos que si utilizamos una salida PWM para generar la onda podemos representar 256 valores de 0V a 5V. Así que la amplitud (A) corresponderá a la mitad, 128 (2.5V) y el periodo (T) a 255:
As in the previous article, I have programed another Arduino to generate the data to read. It is a sine wave ranging between 0 and 5V.

If we look at the Wikipedia link we can see the following expression. We take the initial phase as 0 (φ). We know that if we use a PWM output to generate the wave we can represent 256 values from 0V to 5V. So that the amplitude (A) correspond to half, 128 (2.5V) and the period (T) of 255:





Ahora tenemos un valor entre -2.5V y 2.5V, para obtener un valor entre 0V y 5V debemos sumar 2.5V al resultado final. Sabemos que el valor numérico en bytes para 2.5V es 128.
Now we have a value between -2.5V and 2.5V, to obtain a value between 0V and 5V, must add 2.5V to the final result. We know that the numeric value in bytes is 128 for 2.5V.




Para convertir la señal PWM en una señal analógica se utiliza un filtro RC, yo he elegido unos valores un poco a ojo, 1K y 0.47uF.
To convert the PWM signal into an analog signal, must use an RC filter, I have chosen values a little by eye, 1K and 0.47uF.


Ejemplo de conexiones
Example of connections

Esquema
Schematic

Onda sinusoidal
Sene wave
Cambiando 1K por 10K
Changing 1K for 10K





Descargas:
Downloads:

LogicAnalyzer.zip




Enlaces:
Links:

8-bit AVR® Instruction Set (EN): http://www.atmel.com/Images/doc0856.pdf
ATmega 328 (EN): http://www.atmel.com/Images/doc8161.pdf
Sinusoide (ES): https://es.wikipedia.org/wiki/Sinusoide
Sine wave (EN): http://en.wikipedia.org/wiki/Sine_wave