--- /dev/null
+; vim: set filetype=asx8051 et sw=4 sts=4 :
+
+; Módulo y opciones
+.module leds
+.optsdcc -mmcs51 --model-small
+
+; Variables globales
+.globl _leds_matrix
+.globl _leds_matrix_len
+.globl _leds_delay
+
+; Funciones globales
+.globl _leds_init
+.globl _leds_write
+.globl _leds_write0
+.globl _leds_write1
+.globl _leds_delay_update
+
+; Constantes
+; UN CICLO DE MAQUINA SON 1.6666 useg (clock 8MHz)
+INTERVAL = 66 ; 0.1ms (por el clock de 8MHz)
+LEDS0 = 0x0080
+LEDS1 = 0x00c0
+MAX_COLS = 64
+DELAY_BASE = 28
+
+; Área de bancos de registros
+.area REG_BANK_0 (REL,OVR,DATA)
+ .ds 8
+ ; Usamos siempre banco 0
+ ar0 = 0x00
+ ar1 = 0x01
+ ar2 = 0x02
+ ar3 = 0x03
+ ar4 = 0x04
+ ar5 = 0x05
+ ar6 = 0x06
+ ar7 = 0x07
+
+; Variables es memoria RAM común
+.area DSEG (DATA)
+_leds_matrix_len::
+ .ds 1
+_leds_delay::
+ .ds 1
+delay:
+ .ds 1
+curr_col:
+__stack: ; XXX
+ .ds 1
+
+; Variables en memoria RAM extendida indirecta (8052)
+.area ISEG (DATA)
+_leds_matrix::
+ .ds MAX_COLS * 2 ; 2 bytes por columna
+
+
+; Configuramos el vector de interrupciones para atender el timer2
+.area INTV (ABS, CODE)
+ .org 0x0000 ; XXX
+ ljmp _leds_init ; XXX
+
+ .org 0x002b
+ clr tf2 ; limpio bit de interrupción porque para el timer2 no es autom.
+ ljmp timer2_isr
+
+
+; Área de código del programa
+.area CSEG (CODE)
+
+; Inicializa leds.
+; Primitiva de C:
+; void leds_init();
+;
+; C se encarga de hacer push y pop del dptr, a y psw si lo necesita.
+_leds_init::
+ mov sp, #__stack ; XXX
+ ; guardo registros que uso
+ push ar0
+ push ar1
+ push ar2
+
+ ; leo de la ROM el tamaño por default
+ mov dptr, #MAT_D_LEN
+ clr a
+ movc a, @a+dptr
+ mov _leds_matrix_len, a
+ clr c
+ rlc a ; multiplicamos por 2 porque hay 2 bytes por columna
+ mov r2, a ; tamaño en bytes de la matriz
+
+ ; Cargo milisegundos
+ acall _leds_delay_update
+ mov delay, _leds_delay
+
+ ; copio imagen por default de la ROM a la RAM
+ mov dptr, #MAT_D
+ mov r0, #0 ; indice del "array" en la ROM
+ mov r1, #_leds_matrix ; dirección de memoria de la RAM
+ mov a, r0
+seguir$:
+ movc a, @a+dptr ; leo de la ROM con el índice
+ mov @r1, a ; escribo en el puntero a la RAM
+ inc r1 ; incremento puntero
+ inc r0 ; incremento índice
+ mov a, r0 ; para comparar
+ cjne a, ar2, seguir$ ; veo si quedan más bytes por leer
+
+ ; cargo los capture registers
+ mov rcap2l, #<(-INTERVAL) ; low byte del intervalo
+ mov rcap2h, #>(-INTERVAL) ; high byte del intervalo
+
+ mov ie, #0b10100000 ; habilito interrupcion timer 2
+ ; bits de IE (interrupt enable) en 1:
+ ; IE.7 (Global enable/disable)
+ ; IE.5 (Enable timer 2 interrupt)
+ mov t2con, #0b00000100 ; setup timer 2 (auto-reload y start)
+
+ mov curr_col, #0 ; inicializo el contador de columna en 0
+
+ ; Limpiamos stack
+ push ar2
+ push ar1
+ push ar0
+
+ ret
+
+
+; Escribe en los leds.
+; Primitiva de C:
+; void leds_write(unsigned int);
+;
+; C se encarga de hacer push y pop del dptr, a y psw si lo necesita.
+_leds_write::
+ ; parte baja
+ mov a, dpl ; de C me viene la parte baja del argumento en el dpl
+ mov dptr, #LEDS0
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+ ; parte alta
+ mov a, dph ; de C me viene la parte alta del argumento en el dph
+ mov dptr, #LEDS1
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+ ret
+
+
+; Escribe en los leds del primer latch.
+; Primitiva de C:
+; void leds_write0(unsigned char);
+;
+; C se encarga de hacer push y pop del dptr, a y psw si lo necesita.
+_leds_write0::
+ ; parte baja
+ mov a, dpl ; de C me viene el argumento en el dpl
+ mov dptr, #LEDS0
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+ ret
+
+
+; Escribe en los leds del segundo latch.
+; Primitiva de C:
+; void leds_write1(unsigned char);
+;
+; C se encarga de hacer push y pop del dptr, a y psw si lo necesita.
+_leds_write1::
+ ; parte baja
+ mov a, dpl ; de C me viene el argumento en el dpl
+ mov dptr, #LEDS1
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+ ret
+
+
+; Actualiza el retardo de la matriz según la cantidad de columnas
+; Primitiva de C:
+; void leds_delay_update();
+;
+; La fórmula utilizada es: (DELAY_BASE - (leds_matrix_len / 2)) * 0.1 ms
+; C se encarga de hacer push y pop del dptr, a y psw si lo necesita.
+_leds_delay_update::
+ mov a, _leds_matrix_len
+ clr c
+ rrc a ; divido por 2
+ mov dpl, a
+ mov a, #DELAY_BASE
+ subb a, dpl
+ mov _leds_matrix_len, a
+ ret
+
+
+; Manejador de la interrupción del timer2
+timer2_isr:
+ ; vemos si todavía hay que seguir esperando o si ya tenemos que leer
+ djnz delay, fin$
+
+ ; comenzamos realmente a leer la próxima columna
+ mov delay, _leds_delay
+
+ ; guardamos en el stack el estado actual de los registros que vamos a usar
+ push acc
+ push psw
+ push ar0
+ push dpl
+ push dph
+
+ ; vemos si hay que empezar a leer por la 1ra columna de nuevo
+ mov a, curr_col
+ cjne a, _leds_matrix_len, continua$
+
+ ; hay que empezar de nuevo
+ mov curr_col, #0
+ mov a, curr_col ; dejamos en a la columna actual
+
+continua$:
+ ; multiplicamos por 2 porque hay 2 bytes por columna
+ clr c
+ rlc a
+
+ ; uso r0 como puntero al comienzo de la matriz
+ mov r0, #_leds_matrix
+ add a, r0 ; le sumo al puntero el offset actual segun la columna
+ mov r0, a
+
+ ; imprimo en LEDS1
+ mov a, @r0 ; leo el contenido de la matriz
+ mov dptr, #LEDS1
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+
+ ; imprimo en LEDS0
+ inc r0 ; busco proximo byte de la columna
+ mov a, @r0 ; leo el contenido de la matriz
+ mov dptr, #LEDS0
+ cpl a ; complemento para ver encendidos los "1"
+ movx @dptr, a
+
+ ; avanzamos a la proxima columna
+ mov a, curr_col
+ inc a
+ mov curr_col, a
+
+ ; sacamos nuestra basura del stack
+ pop dph
+ pop dpl
+ pop ar0
+ pop psw
+ pop acc
+
+fin$:
+ reti ; listo! seguimos viaje...
+
+
+; Matriz por default
+MAT_D_LEN:
+ .db 16
+ ;.db 32
+
+MAT_D:
+ .dw 0b0000111111110000 ; columna 0
+ .dw 0b0011111111111100 ; columna 1
+ .dw 0b0111000000001110 ; columna 2
+ .dw 0b0110000000000110 ; columna 3
+ .dw 0b1100001100000011 ; columna 4
+ .dw 0b1100011000110011 ; columna 5
+ .dw 0b1100110000110011 ; columna 6
+ .dw 0b1100110000000011 ; columna 7
+ .dw 0b1100110000000011 ; columna 8
+ .dw 0b1100110000110011 ; columna 9
+ .dw 0b1100011000110011 ; columna 10
+ .dw 0b1100001100000011 ; columna 11
+ .dw 0b0110000000000110 ; columna 12
+ .dw 0b0111000000001110 ; columna 13
+ .dw 0b0011111111111100 ; columna 14
+ .dw 0b0000111111110000 ; columna 15
+
+; .dw 0b0000111111110000 ; columna 0
+; .dw 0b0011111111111100 ; columna 1
+; .dw 0b0111000000001110 ; columna 2
+; .dw 0b0110000000000110 ; columna 3
+; .dw 0b1100110000000011 ; columna 4
+; .dw 0b1100011000110011 ; columna 5
+; .dw 0b1100001100110011 ; columna 6
+; .dw 0b1100001100000011 ; columna 7
+; .dw 0b1100001100000011 ; columna 8
+; .dw 0b1100001100110011 ; columna 9
+; .dw 0b1100011000110011 ; columna 10
+; .dw 0b1100110000000011 ; columna 11
+; .dw 0b0110000000000110 ; columna 12
+; .dw 0b0111000000001110 ; columna 13
+; .dw 0b0011111111111100 ; columna 14
+; .dw 0b0000111111110000 ; columna 15
+
+;MAT_D:
+; .dw 0b0000001111100000 ; columna 0
+; .dw 0b0000111110000000 ; columna 1
+; .dw 0b0111111000000000 ; columna 2
+; .dw 0b1111000000000000 ; columna 3
+; .dw 0b0111100000000000 ; columna 4
+; .dw 0b0011110000000000 ; columna 5
+; .dw 0b0001111000000000 ; columna 6
+; .dw 0b0000111100000000 ; columna 7
+; .dw 0b0000011110000000 ; columna 8
+; .dw 0b0000001111000000 ; columna 9
+; .dw 0b0000000111100000 ; columna 10
+; .dw 0b0000000011110000 ; columna 11
+; .dw 0b0000000001111000 ; columna 12
+; .dw 0b0000000000111100 ; columna 13
+; .dw 0b0000000000011110 ; columna 14
+; .dw 0b0000000000001111 ; columna 15
+
+;end