En aquest cas poden jugar dues persones o una contra la màquina. La matriu de LED es fa servir de manera que les quatre columnes de l'esquerra mostren les combinacions de colors i les quatre de la dreta mostren la correcció de la tirada corresponent. Durant la partida, els polsadors tenen les següents finalitats:
| Polsador | Funció |
| 1 | Groc |
| 2 | Cian |
| 3 | Magenta |
| 4 | Blanc |
| 5 | Apagat |
| 0 | Reinici |
Hi ha dos modes de joc i el polsador 0 permet seleccionar quin dels dos es desitja. Segons el nivell de dificultat els colors de la correcció són:
| Color | Fàcil | Difícil |
| Verd | Encertat color i posició | Encertat color i posició |
| Blau | Encertat color però no posició | |
| Vermell | Color incorrecte | No encertat |
El programa és el següent:
#pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF #pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF #include <pic16f690.h> #include <xc.h> #define _XTAL_FREQ 4000000 #define Polsador6 RA3 // Li assigna un nom a l'adreça del polsador
char Tirada[8][8];
char ntirada;
// Groc (0), Cian(1), Magenta(2), Blanc(3), Verd(4), Vermell(5), Blau(6)
char bcolors[3][7] = { { 1, 0, 1, 1, 0, 1, 0},
{ 1, 1, 0, 1, 1, 0, 0},
{ 0, 1, 1, 1, 0, 0, 1}};
// Groc, Cian, Magenta, Blanc
char colors[4] = {6, 3, 5, 7};
char mode = 0;
char vsol[4];
char vtirada[4];
char win;
char Sortida[6]; // Valors a enviar al MAX7221 (48 bits)
char Sortida1[6];
char RGB[3];
char Polsador(void); // Funció de lectura dels polsadors
char Polsad;
char Actiu; // Variable que diu quin color està actiu
// Actiu = 0 Apagat
// Actiu = 1 Vermell
// Actiu = 2 Verd
// Actiu = 3 Blau
// Definició de les funcions que farem servir void Envia3max(char Val[]); // Envia un joc de valors als tres MAX7221 desactivant interrupcions void Envia_max(char Valor[]); // Envia un joc de valors als tres MAX7221 void Ini3max(void); // Inicialitza els tres MAX7221 void Apaga(void); // Apaga tots els LED void corregir(void); void tirar(void); void pintar(char pos_fila, char pos_color); void introduir_sol(void); void TocaNota(char ValPR2, char ValCCPR1L, char ValDC1B); void TocaNota2(char ValPR2, char ValCCPR1L, char ValDC1B); void TocaNota3(char ValPR2, char ValCCPR1L, char ValDC1B); void TocaNota4(char ValPR2, char ValCCPR1L, char ValDC1B); void TocaNota5(char ValPR2, char ValCCPR1L, char ValDC1B); void TocaNota6(char ValPR2, char ValCCPR1L, char ValDC1B); void EnviaL(char Caracter); // Envia un caràcter void Esborra(void); // Esborra la pantalla i posa el cursor a l'inici void Cursor(char Filera, char Columna); // Posiciona el cursor // (filera 1 a 2 i columna 1 a 32, segons pantalla)
void main(void) {
OPTION_REG = 0b10000101; // Configuració de Timer0
// Com a temporitzador basat en rellotge
// 101 - Factor d'escala de 64
// I resistències de pull-up desactivades (valor per defecte)
PORTC=0;
TRISC = 0; // Tot el port C Ès de sortida
ADCON1 = 0b00010000; // Posa el conversor a 1/8 de la freqüència
ADCON0 = 0b00001001;
TRISB = 0; // Tot el port B és de sortida
TRISA = 0xFF; // Tot el port A és d'entrada
ANSEL = 0b00000101; // Configura AN0 i AN2 com entrada analògica
CCP1CON = 0b00001100;
PIR1bits.TMR2IF = 0;
ANSELH = 0; // Desactiva les altres entrades analògiques
// Inicialitza a 0 el port C
PORTB = 0; // Inicialitza a 0 el port B
Ini3max(); // Inicialitza els tres MAX7221
Actiu = 1; // Activa el color vermell
TMR0 = 139; // Presselecció de 139, que són 117 iteracions
// Correspon a una interrupció cada 7,5 ms
INTCON = 0b10100000; // Activem GIE i T0IE
TXSTAbits.BRGH = 1; // Configuració de velocitat
BAUDCTLbits.BRG16 = 0; // Paràmetre de velocitat de 8 bits
SPBRG = 25; // Velocitat de 9600 baud
TXSTAbits.SYNC = 0; // Comunicació asíncrona
TXSTAbits.TX9 = 0; // Comunicació de 8 bits
Esborra();
Apaga();
char posar_sol = 0;
ntirada = 0;
win = 0; // Indica si es guanya la partida o no;
while (1) {
if (win == 2) {
while (Polsador6 == 1) { // Mentre el polsador no estigui premut
;
}
Apaga();
posar_sol = 0;
Esborra(); // Esborra la pantalla i posa el cursor a l'inici
ntirada = 0;
win = 0; // Indica si es guanya la partida o no;
PORTC = 0;
if (mode == 0) {
mode = 1;
PORTC = 0;
PORTC = 8;
} else {
mode = 0;
PORTC = 0;
PORTC = 4;
}
}
if (posar_sol == 0) {
introduir_sol();
PORTC=1;
EnviaL('G'); // Lletra
EnviaL('O'); // Lletra
EnviaL('O'); // Lletra
EnviaL('D'); // Lletra
EnviaL(' '); // Lletra
EnviaL('L'); // Lletra
EnviaL('U'); // Lletra
EnviaL('C'); // Lletra
EnviaL('K'); // Lletra
posar_sol = 1;
}
if ((ntirada > 7) && (win == 0)) {
ntirada = 0;
}
if (win == 0) {
tirar();
corregir();
ntirada++;
RGB[0]=0;
RGB[1]=0;
RGB[2]=0;
}
if (win==1) {
Esborra(); // Esborra la pantalla i posa el cursor a l'inici
EnviaL('Y'); // Lletra
EnviaL('O'); // Lletra
EnviaL('U'); // Lletra
EnviaL(' '); // Lletra
EnviaL('W'); // Lletra
EnviaL('I'); // Lletra
EnviaL('N'); // Lletra
while (Polsador6 == 1) { // Mentre el polsador no estigui premut
TocaNota2(133, 67, 0); // la4 Retard de 0,2 s
TocaNota2(168, 84, 2); // fa#4
TocaNota2(149, 75, 0); // sol#4
TocaNota2(133, 67, 0); // la#4
TocaNota2(126, 63, 2); // si4
TocaNota2(168, 84, 2); // fa#4
TocaNota2(149, 75, 0); // sol#4
TocaNota2(133, 67, 0); // la#4
TocaNota3(118, 59, 2); // do5 Retard de 0,1 s
TocaNota3(158, 79, 2); // sol4
TocaNota3(141, 71, 0); // la4
TocaNota3(126, 63, 2); // si4
TocaNota3(118, 59, 2); // do5
TocaNota3(158, 79, 2); // sol4
TocaNota3(141, 71, 0); // la4
TocaNota3(126, 63, 2); // si4
TocaNota4(118, 59, 2); // do5 Retard de 2,0 s
break;
}
win=2;
}
}
}
void __interrupt() temporit(void) {
INTCONbits.GIE = 0; // Desactiva les interrupcions momentàniament
if (INTCONbits.T0IF) { // Comprovem que hi ha interrupció per Timer 0
TMR0 = 139; // Preselecció de Timer0
INTCONbits.T0IF = 0; // Desactiva el bit que indica interrupció pel Timer0
if (Actiu != 0) { // Si la matriu no està apagada
Actiu--; // Passem a activar un altre color
if (Actiu == 0) { // Si hem arribat a zero
Actiu = 3; // Torna a posar el 3
}
}
// D'entrada els desactivem els tres
Sortida1[0] = 0x00; // Vermell
Sortida1[2] = 0x00; // Verd
Sortida1[4] = 0x00; // Blau
if (Actiu == 1) { // Si és vermell
Sortida1[0] = 0x01; // Vermell activat
}
if (Actiu == 2) { // Si és verd
Sortida1[2] = 0x01; // Verd activat
}
if (Actiu == 3) { // Si és blau
Sortida1[4] = 0x01; // Blau activat
}
Sortida1[1] = 0x0C; // Shutdown mode
Sortida1[3] = 0x0C; // Shutdown mode
Sortida1[5] = 0x0C; // Shutdown mode
Envia3max(Sortida1); // Ho envia al MAX7221
}
INTCONbits.GIE = 1; // Reactiva les interrupcions a l'acabar
}
void pintar(char pos_fila, char pos_color) {
char r = bcolors[0][pos_color]; // poscolor si te r, g o b
char g = bcolors[1][pos_color];
char b = bcolors[2][pos_color];
r = r << pos_fila;
g = g << pos_fila; // de cada fila el led que ha de pintar de 0 a 7
b = b << pos_fila;
RGB[0] = RGB[0] | r; // Vermells
RGB[1] = RGB[1] | g; // Verds
RGB[2] = RGB[2] | b; // Blaus
}
void corregir(void) {
int nencerts = 0;
for (int i = 0; i < 4; i++) {
if (vtirada[i] == vsol[i]) { // Posar verd
pintar (3 - i, 4);
nencerts++;
} else { // Posar vermell/Blau
char encert = 0;
for (int j = 0; j < 4; j++) {
if (vtirada[i] == vsol[j]) {
encert = 1;
}
}
if (encert == 0) { // Posar vermell
pintar (3 - i, 5);
} else { //Posar blau
if (mode == 0) { // Posar blau sent 0 facil
pintar (3 - i, 6);
} else { // Posar vermell sent dificl
pintar (3 - i, 5);
}
}
}
}
Sortida[1] = ntirada+1; // Filera a pintar
Sortida[3] = ntirada+1;
Sortida[5] = ntirada+1;
Sortida[0] = RGB[0]; // Filera a pintar
Sortida[2] = RGB[1];
Sortida[4] = RGB[2];
Envia3max(Sortida);
if (nencerts == 4) {
win = 1;
}
}
void tirar(void) {
for (int i = 0; i < 4; i++) {
char but=0;
while (but==0) {
if (Polsador6 == 0){ // Mentre el polsador estigui premut
win = 2;
PORTC = 0;
if (mode == 0) {
mode = 1;
} else {
mode = 0;
}
break;
}
but = Polsador();
}
while (but == 5) {
but = Polsador();
}
if (but < 5) {
vtirada[i] = colors[but-1];
pintar (7 - i, but-1);
Sortida[1] = ntirada+1; // Filera a pintar
Sortida[3] = ntirada+1;
Sortida[5] = ntirada+1;
Sortida[0] = RGB[0]; // Filera a pintar
Sortida[2] = RGB[1];
Sortida[4] = RGB[2];
Envia3max(Sortida);
}
__delay_ms(100);
while (but!=0) {
but = Polsador();
}
}
}
void introduir_sol(void) {
char but;
for (int i = 0; i < 4; i++) {
but = Polsador();
while (but==0) {
but = Polsador();
}
while (but == 5) {
but = Polsador();
}
if (but < 5) {
vsol[i] = colors[but-1];
}
__delay_ms(100);
while (but>0) {
but = Polsador();
}
}
}
char Polsador(void) {
char Pols = 0;
ADCON0bits.GO = 1; // Posa en marxa el conversor
while (ADCON0bits.GO == 1) // Mentre no acabi
; // ens esperem
__delay_ms(30);
if (ADRESH < 220 && ADRESH > 200) {
Pols = 1; // Comprova polsador 1
}
if (ADRESH < 194 && ADRESH > 174) {
Pols = 2; // Comprova polsador 2
}
if (ADRESH < 163 && ADRESH > 143) {
Pols = 3; // Comprova polsador 3
}
if (ADRESH < 90 && ADRESH > 70) {
Pols = 4; // Comprova polsador 4
}
if (ADRESH < 55 && ADRESH > 35) {
Pols = 5; // Comprova polsador 5
}
return Pols;
}
void Apaga(void) { // Apaga tots els LED
char Bytes[6]; // Els sis bytes que cal enviar
for (int j = 1; j <= 8; j++){ // Hem d'enviar 8 fileres
Bytes[1] = j; // Filera
Bytes[3] = j;
Bytes[5] = j;
Bytes[0] = 0x00; // Vermells
Bytes[2] = 0x00; // Verds
Bytes[4] = 0x00; // Blaus
Envia3max(Bytes); // Els envia
}
}
void Envia3max(char Val[]) { // Envia un joc de valors als tres MAX7221
INTCONbits.GIE = 0; // Desactiva les interrupcions momentàniament
Envia_max(Val);
INTCONbits.GIE = 1; // Reactiva les interrupcions a l'acabar
}
void Envia_max(char Valor[]) { // Envia un joc de valors als tres MAX7221
INTCONbits.GIE = 0; // Desactiva les interrupcions momentàniament
char Port = 0; // Variable on guardem l'estat del port B
char Temp; // Variable temporal
for (int j = 5; j >= 0; j--){ // Hem d'enviar 6 bytes
for (int k = 1; k < 9; k++){ // De 8 bits
Temp = Valor[j] & 0b10000000; // Agafa el bit de més a l'esquerra
// Temp només podrà valer 0 o 128
if (Temp == 0) { // Si val 0
Port = Port & 0b11101111; // Desactiva Data (bit 4)
} else { // Si val 128
Port = Port | 0b00010000; // Activa Data (bit 4)
}
Valor[j] = Valor[j] << 1; // Rodem els bits per situar el següent
PORTB = Port; // Ho posa al port B
Port = Port | 0b00100000; // Activa Clock (bit 5) i força lectura
PORTB = Port; // Ho posa al port B
Port = Port & 0b11011111; // Desactiva Clock (bit 5)
PORTB = Port; // Ho posa al port B
}
}
Port = Port | 0b01000000; // Activa Latch (bit 6) per copiar a les sortides
PORTB = Port; // Ho posa al port B
INTCONbits.GIE = 1; // Reactiva les interrupcions a l'acabar
}
void Ini3max(void) { // Inicialitza els tres MAX7221
char Bytes[6]; // Els sis bytes que cal enviar
Bytes[0] = 0x00; // Desactivat
Bytes[1] = 0x0C; // Shutdown mode
Bytes[2] = 0x00;
Bytes[3] = 0x0C;
Bytes[4] = 0x00;
Bytes[5] = 0x0C;
Envia_max(Bytes); // Els envia
Bytes[0] = 0x00; // No decode
Bytes[1] = 0x09; // Decode mode
Bytes[2] = 0x00;
Bytes[3] = 0x09;
Bytes[4] = 0x00;
Bytes[5] = 0x09;
Envia_max(Bytes); // Els envia
Bytes[0] = 0x07; // Vuit fileres
Bytes[1] = 0x0B; // Scan limit
Bytes[2] = 0x07;
Bytes[3] = 0x0B;
Bytes[4] = 0x07;
Bytes[5] = 0x0B;
Envia_max(Bytes); // Els envia
}
void TocaNota(char ValPR2, char ValCCPR1L, char ValDC1B) {
TRISC = 0b00100000; // Definim com volem les E/S del port C
// RC5 (sortida del PWM), de moment, com a entrada
PR2 = ValPR2; // Carrega PR2
CCP1CON = CCP1CON & 0b11001111; // Posa a zero els bits que corresponen a DC1B
ValDC1B = ValDC1B % 4; // DC1B va de 0 a 3
ValDC1B = ValDC1B * 16; // Desplaça els bits a la posició que els correspon a CCP1CON
CCP1CON = CCP1CON + ValDC1B; // Coloca DC1B al seu lloc
CCPR1L = ValCCPR1L; // Carrega CCPR1L, registre que ens dona l'amplada de tON
PIR1bits.TMR2IF = 0; // Desactiva el bit d'interrupció del Timer 2
T2CON = 0b00000111; // Configura el Timer 2
// bits T2KCPS (bits 1-0) a 11 prescalat de 16
// bit 2 (TMR2ON) a 1, Timer activat
// Postscaler TOUTPS (bits 6-3) no afecten al PWM
while (PIR1bits.TMR2IF == 0) // Espera l'activació del bit d'interrupció del Timer 2
; // Esperem
TRISC = 0b00000000; // Posem RC5 (sortida del PWM) com a sortida
__delay_ms(220); // Sona tants s
TRISC = 0b00100000; // Posem RC5 (sortida del PWM) com a entrada
// O sigui, silenci
__delay_ms(50); // Retard de mite s
}
void TocaNota2(char ValPR2, char ValCCPR1L, char ValDC1B) {
TRISC = 0b00100000; // Definim com volem les E/S del port C
// RC5 (sortida del PWM), de moment, com a entrada
PR2 = ValPR2; // Carrega PR2
CCP1CON = CCP1CON & 0b11001111; // Posa a zero els bits que corresponen a DC1B
ValDC1B = ValDC1B % 4; // DC1B va de 0 a 3
ValDC1B = ValDC1B * 16; // Desplaça els bits a la posició que els correspon a CCP1CON
CCP1CON = CCP1CON + ValDC1B; // Coloca DC1B al seu lloc
CCPR1L = ValCCPR1L; // Carrega CCPR1L, registre que ens dona l'amplada de tON
PIR1bits.TMR2IF = 0; // Desactiva el bit d'interrupció del Timer 2
T2CON = 0b00000111; // Configura el Timer 2
// bits T2KCPS (bits 1-0) a 11 prescalat de 16
// bit 2 (TMR2ON) a 1, Timer activat
// Postscaler TOUTPS (bits 6-3) no afecten al PWM
while (PIR1bits.TMR2IF == 0) // Espera l'activació del bit d'interrupció del Timer 2
; // Esperem
TRISC = 0b00000000; // Posem RC5 (sortida del PWM) com a sortida
__delay_ms(180); // Sona tants s
TRISC = 0b00100000; // Posem RC5 (sortida del PWM) com a entrada
// O sigui, silenci
__delay_ms(90); // Retard de mite s
}
void TocaNota3(char ValPR2, char ValCCPR1L, char ValDC1B) {
TRISC = 0b00100000; // Definim com volem les E/S del port C
// RC5 (sortida del PWM), de moment, com a entrada
PR2 = ValPR2; // Carrega PR2
CCP1CON = CCP1CON & 0b11001111; // Posa a zero els bits que corresponen a DC1B
ValDC1B = ValDC1B % 4; // DC1B va de 0 a 3
ValDC1B = ValDC1B * 16; // Desplaça els bits a la posició que els correspon a CCP1CON
CCP1CON = CCP1CON + ValDC1B; // Coloca DC1B al seu lloc
CCPR1L = ValCCPR1L; // Carrega CCPR1L, registre que ens dona l'amplada de tON
PIR1bits.TMR2IF = 0; // Desactiva el bit d'interrupció del Timer 2
T2CON = 0b00000111; // Configura el Timer 2
// bits T2KCPS (bits 1-0) a 11 prescalat de 16
// bit 2 (TMR2ON) a 1, Timer activat
// Postscaler TOUTPS (bits 6-3) no afecten al PWM
while (PIR1bits.TMR2IF == 0) // Espera l'activació del bit d'interrupció del Timer 2
; // Esperem
TRISC = 0b00000000; // Posem RC5 (sortida del PWM) com a sortida
__delay_ms(100); // Sona tants s
TRISC = 0b00100000; // Posem RC5 (sortida del PWM) com a entrada
// O sigui, silenci
__delay_ms(90); // Retard de mite s
}
void TocaNota4(char ValPR2, char ValCCPR1L, char ValDC1B) {
TRISC = 0b00100000; // Definim com volem les E/S del port C
// RC5 (sortida del PWM), de moment, com a entrada
PR2 = ValPR2; // Carrega PR2
CCP1CON = CCP1CON & 0b11001111; // Posa a zero els bits que corresponen a DC1B
ValDC1B = ValDC1B % 4; // DC1B va de 0 a 3
ValDC1B = ValDC1B * 16; // Desplaça els bits a la posició que els correspon a CCP1CON
CCP1CON = CCP1CON + ValDC1B; // Coloca DC1B al seu lloc
CCPR1L = ValCCPR1L; // Carrega CCPR1L, registre que ens dona l'amplada de tON
PIR1bits.TMR2IF = 0; // Desactiva el bit d'interrupció del Timer 2
T2CON = 0b00000111; // Configura el Timer 2
// bits T2KCPS (bits 1-0) a 11 prescalat de 16
// bit 2 (TMR2ON) a 1, Timer activat
// Postscaler TOUTPS (bits 6-3) no afecten al PWM
while (PIR1bits.TMR2IF == 0) // Espera l'activació del bit d'interrupció del Timer 2
; // Esperem
TRISC = 0b00000000; // Posem RC5 (sortida del PWM) com a sortida
__delay_ms(600); // Sona tants s
TRISC = 0b00100000; // Posem RC5 (sortida del PWM) com a entrada
// O sigui, silenci
__delay_ms(90); // Retard de mite s
}
void EnviaL(char Caracter) {
INTCONbits.GIE = 0; // Desactiva les interrupcions momentàniament
RCSTAbits.SPEN = 1; // Activa comunicació sèrie
TXSTAbits.TXEN = 1; // Activa comunicació
TXREG = Caracter; // Agafa el caràcter i l'envia
__delay_ms(1); // Donem temps
while (PIR1bits.TXIF == 0) // Esperem que s'acabi d'enviar
; // No fem res
RCSTAbits.SPEN = 0; // Desactiva comunicació sèrie
TXSTAbits.TXEN = 0; // Desactiva comunicació
INTCONbits.GIE = 1; // Activa les interrupcions
}
void Esborra(void) {
EnviaL(254); // Caràcter de control
EnviaL(1); // Esborra la pantalla i posa el cursor a l'inici
}
void Cursor(char Filera, char Columna) {
char Posicio = 0; // Variable per a calcular la posició
if (Filera == 2) {
Posicio = 64; // La primera columna de la segona fila ès 64;
}
if (Columna > 0 && Columna < 33) { // Comprovem que sigui un valor raonable
Posicio = Posicio + Columna; // Sumem les adreces
Posicio = Posicio - 1; // Restem 1 perquË numera des de 0
}
Posicio = Posicio + 128; // Posa el bit de posicionat a 1
EnviaL(254); // Control de la posiciÛ del cursor
EnviaL(Posicio); // Canvia el cursor de lloc
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.