Paso 5: Bitbanging una onda de pulso en un microcontrolador ATMega328p
Desde la Junta de desarrollo de Arduino Uno R3 mantiene una señal de reloj externa de 16MHz en el ATMega328p a bordo, el microcontrolador ejecuta una ciclo de reloj 1 instrucción en exactamente 62.5ns (1/16 MHz = 62.5ns). Ya que podemos averiguar cuántos ciclos de reloj cada instrucción toma, precisamente podemos controlar cómo muchas instrucciones que necesitamos para generan nuestra señal.
Como vimos anteriormente, con el fin de transmitir un 1 en el chip WS281X debemos transmitir una señal de que se mantiene en un valor máximo (HIGH) para 0.8μs y luego se queda en un valor mínimo (LOW) para 0.45μs. Por lo tanto, queremos escribir una lista de instrucciones que:
-Set pin digital a alta
-Esperar 0.8μs
-Pin digital se establece en bajo
-Espera 0.45μs
En lenguaje ensamblador, esto puede lograrse por el siguiente código:
(volátiles) ASM
Instrucciones reloj Descripción fase bits transmitidos
"OSE %0, %1\n\t" / / 2 PIN alto (T = 2)
"rjmp. + 0\n\t" / / 2 nop nop (T = 4)
"rjmp. + 0\n\t" / / 2 nop nop (T = 6)
"rjmp. + 0\n\t" / / 2 nop nop (T = 8)
"rjmp. + 0\n\t" / / 2 nop nop (T = 10)
"rjmp. + 0\n\t" / / 2 nop nop (T = 12)
"nop\n\t" // 1 nop (T = 13)
"cbi %0, %1\n\t" / / 2 PIN bajo (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"rjmp. + 0\n\t" / / 2 nop nop (T = 19)
"nop\n\t" // 1 nop (T = 20) 1
::
Entrada operandos
"I" (_SFR_IO_ADDR(PORT)), //%0
"I" (PORT_PIN) //%1
);
Instrucción
La primera columna incluye las instrucciones de montaje seguida por un salto de línea y ficha caracteres, que el ensamblador final listado generado por el compilador más legible.
Reloj
La segunda columna muestra el número de ciclos de reloj que toma cada instrucción. Para este conjunto de instrucciones simple hay sólo un valor posible, que veremos más adelante cómo algunas instrucciones (por ejemplo, condicional) pueden tener 1, 2 o 3 valores posibles. Recuerde que cada ciclo de reloj en los 16 MHz Arduino Uno toma 62.5ns.
Descripción
La tercera columna muestra una muy breve descripción de lo que hace cada operación.
Fase
Usar el término un poco libremente, lo usamos para indicar la suma acumulativa de ciclos de reloj por las instrucciones que han sido ejecutadas hasta el momento.
Para enviar un único valor de 255: 11111111 en binario, a la WS281X tenemos que repetir este conjunto de instrucciones 8 veces. Además, si insertamos un 50μs (o mayor) entre las transmisiones de la secuencia de 8 bits, la WS281X engancha los datos transmitidos a su registro de salida. Una vez que los datos están bien cerrados, el primer LED (verde) de la WS281X debe encender a un nivel de brillo máximo. El sketch de Arduino dentro de bitbang_255.zip muestra esta operación.
Para enviar un 0 que necesitamos cambiar el código que produce un 1 al disminuir el tiempo durante el cual la señal tiene un alto valor (máximo) y aumentar el tiempo durante el cual la señal es baja (como mínimo). Además, debemos señalar que los valores de cada LED deben siempre especificarse usando 8 bits. Por ejemplo, si quisiéramos enviar un valor de 105 — 1101001 en binario, tenemos que enviar los 8 bits 01101001 incluyendo el 0 inicial. El código que produce un 0 se parece:
(volátiles) ASM
Instrucciones reloj Descripción fase bits transmitidos
"OSE %0, %1\n\t" / / 2 PIN alto (T = 2)
"rjmp. + 0\n\t" / / 2 nop nop (T = 4)
"rjmp. + 0\n\t" / / 2 nop nop (T = 6)
"cbi %0, %1\n\t" / / 2 PIN bajo (T = 8)
"rjmp. + 0\n\t" / / 2 nop nop (T = 10)
"rjmp. + 0\n\t" / / 2 nop nop (T = 12)
"rjmp. + 0\n\t" / / 2 nop nop (T = 14)
"rjmp. + 0\n\t" / / 2 nop nop (T = 16)
"rjmp. + 0\n\t" / / 2 nop nop (T = 18)
"rjmp. + 0\n\t" / / 2 nop nop (T = 20) 0
::
Entrada operandos
"I" (_SFR_IO_ADDR(PORT)), //%0
"I" (PORT_PIN) //%1
);
Podemos usar el sketch de Arduino dentro de bitbang_105.zip para generar la señal cuya imagen se puede ver en las capturas de pantalla de osciloscopio que se unen a este paso.
Ahora, para que el WS281X mostrar el color blanquecino que queremos, tenemos que enviar 255 valores no uno, sino tres, en cuyo caso la señal consiste en 24 unos — sin esperar los 50μs para los datos de cierre. Podríamos hacer esto copiando-pegando las once instrucciones sobre 23 veces (usted puede probar modificar el bosquejo del bitbang_255.ino). Pero el código sería poco práctico para el envío de valores a más de uno WS281X chips. Una mejor solución sería escribir un bucle que se recorra en iteración los valores de 8 bits hasta las tres de ellos han sido enviadas.
El bosquejo dentro de bitbang_whitish.zip incluye una descripción clara de las medidas adoptadas para lograr el resultado deseado. La sección principal, escrita en conjunto siguiendo la lógica descrita anteriormente, se ve como sigue:
(volátiles) ASM
Fase de descripción de reloj de instrucciones
"nextbit:\n\t" // - label (T = 0)
"OSE %0, %1\n\t «/ 2 señal alta (T = 2)
"sbrc %4, 7\n\t" / / 1-2 si MSB establecida (T =?)
"mov 6 %, %3\n\t" / / 0-1 tmp te fije señal alta (T = 4)
«Dic %5\n\t» / / 1 disminución bitcount (T = 5)
"nop\n\t" / / 1 nop (ciclo de 1 reloj inactivo) (T = 6)
"st % a2, %6\n\t" / / 2 puerto para tmp (T = 8)
"mov 6 %, %7\n\t" / / 1 reset tmp para bajo (predeterminado) (T = 9)
"breq nextbyte\n\t" / / 1-2 si bitcount == 0 -> nextbyte (T =?)
"rol %4\n\t" / / 1 MSB de cambio hacia la izquierda (T = 11)
"rjmp. + 0\n\t" / / 2 nop nop (T = 13)
"cbi %0, %1\n\t" / / 2 señal baja (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"nop\n\t" // 1 nop (T = 18)
"rjmp nextbit\n\t" / / 2 bitcount! = 0 -> nextbit (T = 20)
"nextbyte:\n\t" // - label -
"ldi %5, 8\n\t" / / 1 reset bitcount (T = 11)
"ld %4, %a8+\n\t" // 2 val = *p++ (T = 13)
"cbi %0, %1\n\t" / / 2 señal baja (T = 15)
"rjmp. + 0\n\t" / / 2 nop nop (T = 17)
"nop\n\t" // 1 nop (T = 18)
«Dic %9\n\t» / / 1 disminución bytecount (T = 19)
"brne nextbit\n\t" / / 2 si bytecount! = 0 -> nextbit (T = 20)
::
);
La mejor manera de entender el funcionamiento de esta sección es considerar escenarios de caso y sigue el código línea por línea de la Asamblea. Por ejemplo, sabemos que para enviar un valor de 255, necesitamos enviar 8 bits con un tiempo que corresponde a 1. En otras palabras, el Pin Digital conectado a la WS281X debe permanecer alta para 13 ciclos (0.8125μs) y baja para 7 (0.4375μs). ¿El código anterior lograr esto? Vamos a ver lo que sucede cuando empieza a transmitir:
(volátiles) ASM
"nextbit:\n\t" / / Esto es sólo una etiqueta para dirigir los saltos más abajo.
"OSE %0, %1\n\t" / / la señal está establecida en alto, instrucción utiliza 2 ciclos.
"sbrc %4, 7\n\t" / / verdadero. Envío de 255 implica MSB actual es 'set' (= 1).
"mov 6 %, %3\n\t" / / Esto es ejecutado. "tmp" está establecido en alto.
«Dic %5\n\t» / / se transmite poco, disminuir el contador de bit.
"nop\n\t" / / necesidad a ralentí para llegar a los ciclos de 13 reloj.
"st % a2, %6\n\t" / / escribir el valor de "tmp" en el puerto (pin aún alta).
"mov 6 %, %7\n\t" / "Tmp" ajuste a la baja para el próximo paso a través del lazo.
"breq nextbyte\n\t" / / falso. Contador de bit no es 0, use 1 ciclo y continuar.
"rol %4\n\t" / / cambio el valor del byte MSB hacia la izquierda.
"rjmp. + 0\n\t" / / Idle durante 2 ciclos de reloj. Fase alcanzada T = 13.
"cbi %0, %1\n\t" / / señal para bajo.
"rjmp. + 0\n\t" / / Idle durante 2 ciclos de reloj.
"nop\n\t" / / Idle por ciclo de 1 reloj.
"rjmp nextbit\n\t" / / contador de Bit no salto así que 0 en el bit siguiente. T = 20.
);
Para que las instrucciones que se ejecutan realmente generan una señal en el pin de datos es 13 ciclos alto (0.8125μs) y 7 bajo (0.4375μs), así envío un poco con un valor de 1 para el WS281X. Si seguimos a estudiar el código de lo que hace cuando el resto de los bits se envían y que lo hace cuando se utilizan los valores que no sean de 255, obtendrá una comprensión más profunda de esta aplicación particular de bitbanging.
Personalmente espero que encuentran este tutorial útil para comenzar con bitbanging su propia comunicación protocolos cuando sea necesario.