Una buena herramienta para trabajar con la lógica digital es el analizador lógico. Como necesito uno y ahora no es un buen momento para gastar dinero he decidido construir uno casero con Arduino.
Lo primero que necesito es saber cuántas lecturas puedo llegar a hacer en un determinado tiempo. Lo más fácil sería leer el estado de un pin de entrada y enviarlo por el puerto serie y luego controlar el tiempo de lectura desde un software de PC, pero esto sería demasiado lento y podríamos perder muchos datos. Lo óptimo sería leer un determinado número de veces, guardarlo en la memoria SRAM de Arduino y luego volcarlo todo al puerto serie. Sabiendo el tiempo empleado para leer una vez, podemos calcular la gráfica a pintar en pantalla. La forma más precisa es trabajar con instrucciones de ensamblador. Para ello hay que echar un vistazo al set de instrucciones de los chips ATmega y determinar que instrucciones usar y los ciclos de reloj que consume cada una de estas instrucciones. Además, si queremos leer varios pines a la vez, debemos acceder a ellos mediante los puertos internos del microcontrolador. Si utilizamos un Arduino UNO o un Arduino Nano (como en mi caso) tendremos el siguiente mapeado de puertos:
Lo primero que necesito es saber cuántas lecturas puedo llegar a hacer en un determinado tiempo. Lo más fácil sería leer el estado de un pin de entrada y enviarlo por el puerto serie y luego controlar el tiempo de lectura desde un software de PC, pero esto sería demasiado lento y podríamos perder muchos datos. Lo óptimo sería leer un determinado número de veces, guardarlo en la memoria SRAM de Arduino y luego volcarlo todo al puerto serie. Sabiendo el tiempo empleado para leer una vez, podemos calcular la gráfica a pintar en pantalla. La forma más precisa es trabajar con instrucciones de ensamblador. Para ello hay que echar un vistazo al set de instrucciones de los chips ATmega y determinar que instrucciones usar y los ciclos de reloj que consume cada una de estas instrucciones. Además, si queremos leer varios pines a la vez, debemos acceder a ellos mediante los puertos internos del microcontrolador. Si utilizamos un Arduino UNO o un Arduino Nano (como en mi caso) tendremos el siguiente mapeado de puertos:
A good tool for working with digital logic is the logic analyzer. As I need one and now is not a good time to spend money I decided to build a homemade one using Arduino.
The first thing I need to know is how many readings I can get in a determinate time. The easiest way would be to read the state of an input pin and send it via the serial port and then control the reading time from a PC software, but this would be too slow and I could lose a lot of data. The optimum would be to read a certain number of times, save it in Arduino SRAM and then bounce everything to the serial port. Knowing the time used to read one time, we can calculate the graph displayed on the screen. The most accurate way is to work with assember instructions. This requires taking a look at the instruction set of the ATmega chip and determining which instructions to use and how many clock cycles consume each of these instructions. Furthermore, if we read several pins at once, we have to access to the internal ports of the microcontroller. If we use an Arduino UNO or Arduino Nano (as in my case) we have the following port mapping:
The first thing I need to know is how many readings I can get in a determinate time. The easiest way would be to read the state of an input pin and send it via the serial port and then control the reading time from a PC software, but this would be too slow and I could lose a lot of data. The optimum would be to read a certain number of times, save it in Arduino SRAM and then bounce everything to the serial port. Knowing the time used to read one time, we can calculate the graph displayed on the screen. The most accurate way is to work with assember instructions. This requires taking a look at the instruction set of the ATmega chip and determining which instructions to use and how many clock cycles consume each of these instructions. Furthermore, if we read several pins at once, we have to access to the internal ports of the microcontroller. If we use an Arduino UNO or Arduino Nano (as in my case) we have the following port mapping:
BIT | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
PORTB | XTAL | XTAL | 13 | 12 | 11 | 10 | 9 | 8 |
PORTC | NC | RST | A5 | A4 | A3 | A2 | A1 | A0 |
PORTD | 7 | 6 | 5 | 4 | 3 | 2 | TX 1 | RX 0 |
Si utilizamos el puerto B para leer las entradas, debemos utilizar los bits 0..5 ya que los bits 6 y 7 están conectados al cristal. Si utilizamos el puerto C, usaremos los bits 0..5 ya que en el bit 6 está conectado el reset y el bit 7 no está conectado. Y si usamos el puerto D, usaremos los bits 2..7 porque los bits 0 y 1 se utilizan para la comunicación por el puerto serie.
Yo, concretamente, he decidido utilizar el puerto B para hacer las lecturas de lógica, así que podré leer 6 canales.
Para leer los 8 bits de un puerto con Arduino utilizamos la siguiente instrucción:
Para leerlos con instrucciones a bajo nivel (ASM) lo leemos de la siguiente forma:
Dónde Rd es el registro de memoria de destino y A es el puerto de entrada. Esta instrucción consume un ciclo de reloj (Pag 13).
Para guardar la lectura en memoria y pasar a la siguiente posición de memoria usaremos la siguiente instrucción:
Dónde z es la posición de memoria donde vamos a guardar el dato y Rr es el registro donde previamente hemos leído el puerto de entrada. ST consume un ciclo de reloj y el incremento de posición de memoria otro ciclo más.
Así, tenemos el siguiente bloque de código ensamblador:
A parte de esto tenemos que saber de cuanta memoria SRAM disponemos para almacenar lecturas. ATmega 328 dispone de 2Kb (2048 bytes), de los cuales una cantidad la consume el bootloader de Arduino. Haciendo unas pruebas he visto que puedo utilizar un buffer de 1800 bytes. Si sabemos que estamos consumiendo 3 ciclos por cada lectura podemos deducir lo siguiente:
Yo, concretamente, he decidido utilizar el puerto B para hacer las lecturas de lógica, así que podré leer 6 canales.
Para leer los 8 bits de un puerto con Arduino utilizamos la siguiente instrucción:
- byte b = PINB;
Para leerlos con instrucciones a bajo nivel (ASM) lo leemos de la siguiente forma:
- IN Rd, A
Dónde Rd es el registro de memoria de destino y A es el puerto de entrada. Esta instrucción consume un ciclo de reloj (Pag 13).
Para guardar la lectura en memoria y pasar a la siguiente posición de memoria usaremos la siguiente instrucción:
- ST z+, Rr
Dónde z es la posición de memoria donde vamos a guardar el dato y Rr es el registro donde previamente hemos leído el puerto de entrada. ST consume un ciclo de reloj y el incremento de posición de memoria otro ciclo más.
Así, tenemos el siguiente bloque de código ensamblador:
-
in r0, A
st z+,r0
A parte de esto tenemos que saber de cuanta memoria SRAM disponemos para almacenar lecturas. ATmega 328 dispone de 2Kb (2048 bytes), de los cuales una cantidad la consume el bootloader de Arduino. Haciendo unas pruebas he visto que puedo utilizar un buffer de 1800 bytes. Si sabemos que estamos consumiendo 3 ciclos por cada lectura podemos deducir lo siguiente:
If we use the port B to read inputs, we use the bits 0..5 as bits 6 and 7 are connected to the xtal. If we use the port C, we will use the bits 0..5 as in the bit 6 is connected to the reset and bit 7 isn't connected. And if we use the port D, we use bits 2..7 because bits 0 and 1 are used for serial port communication.
I, specifically, I decided to use port B to make logical readings, so I can read 6 channels.
To read the 8 bits of a port with Arduino we use the following statement:
To read with low-level instructions (ASM) we read as follows:
Where Rd is the destination memory register and A is the input port. This instruction consumes one clock cycle (Pag 13).
To save the reading in memory and move to the next memory location we will use the following instruction:
Where z is the memory location where we will store the data and Rr is the register where we have already read the input port. ST consumes one clock cycle and increasing memory location another cycle.
Thus, we have the following assembler code block:
Apart from this we have to know how much SRAM have to store readings. ATmega328 has 2K (2048 bytes), of which consumes an amount Arduino Bootloader. After doing some tests I have seen that I can use a buffer of 1800 bytes. If we know that we are consuming 3 cycles for each reading we can deduce the following:
I, specifically, I decided to use port B to make logical readings, so I can read 6 channels.
To read the 8 bits of a port with Arduino we use the following statement:
- byte b = PINB;
To read with low-level instructions (ASM) we read as follows:
- IN Rd, A
Where Rd is the destination memory register and A is the input port. This instruction consumes one clock cycle (Pag 13).
To save the reading in memory and move to the next memory location we will use the following instruction:
- ST z+, Rr
Where z is the memory location where we will store the data and Rr is the register where we have already read the input port. ST consumes one clock cycle and increasing memory location another cycle.
Thus, we have the following assembler code block:
-
in r0, A
st z+,r0
Apart from this we have to know how much SRAM have to store readings. ATmega328 has 2K (2048 bytes), of which consumes an amount Arduino Bootloader. After doing some tests I have seen that I can use a buffer of 1800 bytes. If we know that we are consuming 3 cycles for each reading we can deduce the following:
-
16 MHz / 3 Cycles = 5.33 MHz
3 Cycles * 1800 Times = 5400 Cycles
16 MHz = 62.5 ns per cycle
5400 Cycles * 62.5 ns = 337.5 us
Tenemos una frecuencia de lectura de 5.33 Mhz. El tiempo total de lectura es de 337.5 us.
Para hacer las 1800 lecturas he decidido no utilizar un bucle porque perdería tiempo incrementando variables y saltando al inicio del bucle en cada vuelta.
Para hacer las 1800 lecturas he decidido no utilizar un bucle porque perdería tiempo incrementando variables y saltando al inicio del bucle en cada vuelta.
We have a 5.33 MHz reading frequency. The total reading time is 337.5 us.
To make 1800 readings I decided not to use a loop, because it can make lose time increasing the index and looping back.
To make 1800 readings I decided not to use a loop, because it can make lose time increasing the index and looping back.
#define DATA 1800 byte _data[DATA]; void ReadDigital() { __asm__ __volatile( "in r0,%[port]\n\t" "st z+,r0\n\t" // 1 "in r0,%[port]\n\t" "st z+,r0\n\t" // 2 . . . "in r0,%[port]\n\t" "st z+,r0\n\t" // 1800 : : [port] "i" (_SFR_IO_ADDR(PINB)), "z" (_data) : ); }
PRUEBAS:
TESTS:
He programado también una aplicación en VB.Net 4 para simular por pantalla los datos recogidos por el analizador. Más adelante, cuando esté perfeccionado, subiré el código, por ahora he subido el ejecutable.
Para probar el funcionamiento del analizador, he programado otra placa Arduino como un generador de señales PWM, de forma que cada una de las salidas genera una señal PWM del 50% a su máxima frecuencia. Pins 3, 9, 10 y 11 a 31250 Hz y pins 5 y 6 a 62500 Hz. He conectado estas salidas a los pins de entrada del analizador y este ha sido el resultado:
Para probar el funcionamiento del analizador, he programado otra placa Arduino como un generador de señales PWM, de forma que cada una de las salidas genera una señal PWM del 50% a su máxima frecuencia. Pins 3, 9, 10 y 11 a 31250 Hz y pins 5 y 6 a 62500 Hz. He conectado estas salidas a los pins de entrada del analizador y este ha sido el resultado:
I have also programmed an application in VB.Net 4 to simulate on screen the data collected by the analyzer. Later, when it is perfected, I'll upload the code, now I uploaded the executable.
To test the performance of the analyzer, I have programmed another Arduino board as a PWM signal generator, so that each of the outputs generates a PWM signal of 50% at maximum frequency. Pins 3, 9, 10 and 11 are 31250 Hz and pins 5 and 6 are 62500 Hz. I connected these to the analyzer input pins and this is the result:
To test the performance of the analyzer, I have programmed another Arduino board as a PWM signal generator, so that each of the outputs generates a PWM signal of 50% at maximum frequency. Pins 3, 9, 10 and 11 are 31250 Hz and pins 5 and 6 are 62500 Hz. I connected these to the analyzer input pins and this is the result:
Ejemplo de conexiones
Example of connections
|
Esquema
Schematic
|
6 Señales PWM
6 PWM signals
|
#include "PwmFrequency.h" int pins[] = {3, 5, 6, 9, 10, 11}; void setup() { for(int i=0; i<6; i++) { setPwmFrequency(pins[i], 1); pinMode(pins[i], OUTPUT); analogWrite(pins[i], 127); } } void loop() {}
Descargas:
Downloads:
LogicAnalyzer.zip
Enlaces:
Links:
Analizador lógico (ES): http://es.wikipedia.org/wiki/Analizador_lógico
Logic analyzer (EN): http://en.wikipedia.org/wiki/Logic_analyzer
8-bit AVR® Instruction Set (EN): http://www.atmel.com/Images/doc0856.pdf
Arduino registro PORT (ES): http://arduino.cc/es/Reference/PortManipulation
Arduino port registers (EN): http://arduino.cc/en/Reference/PortManipulation
ATmega port manipulation (EN): http://playground.arduino.cc/Learning/PortManipulation