Operácie

Maticový displej 8x8 s driverom MAX7219

Zo stránky SensorWiki

Záverečný projekt predmetu MIPS / LS2024 - Ladislav Nagy


Zadanie

Zostrojte animáciu pohyblivéj šípky, na spôsob smerovky. Pri stlačení tlačidla do prava, alebo do ľava sa vytvorí animácia ukazujúca smer.

Vývojová doska Arduino Nano.

Literatúra:


Analýza a opis riešenia

V projekte som využil maticový diplej so 64 LED diódami usporiadanými do matice 8x8, na displej je pripojený radič MAX7219, ktorý zjednodušuje komunikáciu s Arduinom. Celý modul sa pripájal pomocu 5-tich pinov:

  • VCC - napájanie 5V
  • GND - uzemnenie
  • DIN(data in, pin 11) - MOSI(master out slave in) pin použtý na odosielanie dát z mastera (Arduino Nano) do slave zariadenia ( displeja s radicom MAX7219)
  • CS(chip select, pin 10) - slúži na vybratie konkrétneho slave zariadenia, s ktorým má master (Arduino Nano) komunikovať
  • CLK(alebo SCK - serial clock, pin 13) - je signál generovaný masterom, ktorý určuje synchronizačnú frekvenciu pre prenos dát medzi masterom a slave zariadením

Displej funguje na princípe multiplexingu, teda sa prepína jeden riadok displeja v ktorom rozsvedsuje len tie diódy, ktoré chceme aby svietili. Potom prejdem na ďalší riadok a urobí to isté. Tento proces sa opakuje pre všetky riadky na displeji. Ak to robí dostatočne rýchlo (viac ako 100 krát za sekundu), naše oči to vnímajú ako stabilné svetlo, aj keď sa v skutočnosti každý riadok zapína a vypína postupne. Týmto spôsobom môžeme teda zobraziť rôzne informácie na displeji.

  1. Prvým krokom v projekte bolo pripojenie displeja s radičom ku Arduinu, pomocou SPI(Serial Peripheral Interface) zbernice.
SPI zbernica - je sériová synchrónna komunikačná zbernica, ktorá sa používa na prenos dát medzi mikrokontrolérmi a rôznymi zariadeniami.
Princíp fungovania SPI komunikácie:
  • Synchrónnu komunikáciu zabezpečuje synchronizačný signál, ktorý určuje kedy majú byť odosielané a prijímané dáta.
  • Využíva Master-Slave komunikáciu, čo znamená, že v tejto komunikácií existuje zvyčajne jeden master(nadriadený) a viacero slave(podriadený) prvkov. Master teda zodpovedná za riadenie komunikácie a slaves reagujú na jeho príkazy.
  • Uplatňuje sa full duplex komunikácia, teda umožňuje plný obojsmerný prenos dát, čo znamená, že master môže posielať dáta slave zariadeniu a súčasne prijímať dáta od neho.
  1. Druhým krokom bolo vytvorenie programovéj časti projektu, v ktoréj bolo potrebné inicializovať komunikáciu, vyvoriť funkciu na odosieľanie dát z Arduina, nastavenie displeja pomocou registrov a vytvorenie funkcií, ktoré zobrazovali animáciu šípok.
8x8 maticový displej s radičom MAX7219.

Schéma zapojenia

Schéma zapojenia LCD displeja.


Algoritmus a program

Program pre displej je tvorený z hlavného programu main.c a dvoch pomocných knižníc SPIinit.c a uart.c. Obsahuje aj dva hlavičkové súbory pre komunikáciu UART (urat.h) a SPI komunikáciu (SPIinit.h).

  • V knižnici SPIinit.c je prevažne definovaná inicializácia displeja pomocou funkcií, sú tu definované piny, ktorými je modul pripojený k Arduinu a taktiež sa tu nachádzajú animácie, ktoré sa následne volajú v hlavnom programe.
  • Knižnica uart.c slúži na inicializáciu sériovej komunikácie pomocou UART, teda dokážeme zobrazovať napríklad stav tlačítok v programe Putty.
  • Na začiatku hlavného programu main.c sú definované potrebné makrá a enumy pre určenie stavu tlačidiel. Nastavujú sa tu piny PD2 a PD3(tlačidlá) ako vstupy, teda sú povolené interné pull-up rezistory. V cykle while sa nachádza časť pre ošetrenie kmitania pri stlačení tlačítok a program číta stav tlačítok, podľa ních mení stav enumov. Je tu nastavená podmienka, pre stlačenie pravého, alebo ľavého tlačidla a podľa toho sa spustí príslušná animácia.

Pri spustení programu sa na displeji zobrazí animácia smajlíka, po ktoréj je možné nastavovať smer šípok, tento smer sa následne zobrazuje v programe Putty.

#define F_CPU 16000000UL
#define BAUDRATE       9600
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"
#include "SPIinit.h"

#define switchL   PD3
#define switchR   PD2

FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE);

enum states { Off, Down, On, Up }; // Stavy tlačítok
volatile enum states buttonL = Off;
volatile enum states buttonR = Off;


int main(void) {
	uart_init();
	stdout = &mystdout; // printf() funkcia je zapnutá
    SPI_init(); // Inicializácia SPI
    MAX7219_init(); // Inicializácia MAX7219
	
    // Nastavenie tlačidiel ako vstupov a povolenie interných pull-up rezistorov
    DDRD &= ~((1 << switchL) | (1 << switchR));
    PORTD |= (1 << switchL) | (1 << switchR);
	
	clearDisp();
	smileAnimation();
	clearDisp();
	printf("Smer sipok sa da menit tlacitkami na nepajivom poli.\n");
    
    while (1) {
		if ( (buttonL == Off) &&  bit_is_clear(PIND, switchL)  )
	  {
		 delay(15);                 
		 if ( bit_is_clear(PIND, switchL) ) 
		 buttonL = Down;	            
	  }
	  else if ( (buttonL == Down) &&  bit_is_clear(PIND, switchL)  )
	  { 
  		 buttonL = On;
	  }
	  else if  ( (buttonL == On) &&  bit_is_clear(PIND, switchL) ) 	  
	  {  
		 buttonL = Up;
	  }
	  else if (  (buttonL == Up) &&  bit_is_clear(PIND, switchL) )
	  {
		 delay(15);                 
		 if ( bit_is_clear(PIND, switchL) ) 
		 buttonL = Off;
	  }
	  
	  
	  if ( (buttonR == Off) &&  bit_is_clear(PIND, switchR)  )
	  {
		 delay(15);                 
		 if ( bit_is_clear(PIND, switchR) ) 
		 buttonR = Down;	            
	  }
	  else if ( (buttonR == Down) &&  bit_is_clear(PIND, switchR)  )
	  { 
  		 buttonR = On;
	  }
	  else if  ( (buttonR == On) &&  bit_is_clear(PIND, switchR) ) 	  
	  {  
		 buttonR = Up;
	  }
	  else if (  (buttonR == Up) &&  bit_is_clear(PIND, switchR) )
	  {
		 delay(15);                 
		 if ( bit_is_clear(PIND, switchR) ) 
		 buttonR = Off;
	  }
	  
        if (buttonL == Down) {  
			printf("Smer sipok: <--\r");
			vlavo(); 
			buttonL = Up; // Nastav stav tlačidlo L na Up po animácii
		}
        if (buttonR == Down) {
			printf("Smer sipok: -->\r");  
			vpravo(); 
			buttonR = Up; // Nastav stav tlačidlo R na Up po animácii
		}
    }
}
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"

// Definícia pinov
#define DIN_PIN     3    // Pripojenie Arduina k pinu DIN(data in -  odosielanie dát z mastera) na MAX7219  (pin 11)
#define CLK_PIN     5    // Pripojenie Arduina k pinu CLK(alebo SCK - určuje synchronizačnú frekvenciu pre prenos dát) na MAX7219  (pin 13)
#define CS_PIN      2    // Pripojenie Arduina k pinu CS(chip select - slúži na vybratie konkrétneho slave zariadenia) na MAX7219   (pin 10)
#define switchL   PD3
#define switchR   PD2

void delay(int delay)      
{
  for (int i=1; i<=delay; i++)
  _delay_ms(1);
}

// Inicializácia SPI komunikácie
void SPI_init() {
    DDRB |= (1 << DIN_PIN) | (1 << CLK_PIN) | (1 << CS_PIN); // Nastavenie pinov DIN, CLK, CS ako výstupy
    PORTB |= (1 << CS_PIN); // Nastavenie CS pinu na HIGH (deaktivovaný stav)
    SPCR |= (1 << SPE) | (1 << MSTR) | (1 << SPR0); // Zapnutie SPI, Master módu a nastavenie hodinového deliča na Fosc/16
}

// Funkcia pre odoslanie dát na MAX7219
void MAX7219_send(unsigned char reg, unsigned char data) {
    PORTB &= ~(1 << CS_PIN); // Aktivácia čipu, nastavenie CS na LOW
    SPDR = reg; // Odoslanie adresy registra
    while (!(SPSR & (1 << SPIF))); // Čakanie na dokončenie odosielania
    SPDR = data; // Odoslanie dát
    while (!(SPSR & (1 << SPIF))); // Čakanie na dokončenie odosielania
    PORTB |= (1 << CS_PIN); // Deaktivácia čipu, nastavenie CS na HIGH
}

// Funkcia na inicializáciu MAX7219
void MAX7219_init() {
    MAX7219_send(0x09, 0x00); // Nastavenie Decode Mode na "no decode"
    MAX7219_send(0x0a, 0x01); // Intenzita svetla (0x00 - 0x0f)
    MAX7219_send(0x0b, 0x07); // Nastavenie Scan Limit (0x07 = zobrazenie 8 riadkov)
    MAX7219_send(0x0c, 0x01); // Nastavenie Shutdown režimu na "normal operation"
    MAX7219_send(0x0f, 0x00); // Test mode off
}

// Funkcia na zobrazenie sipky do prava
void vpravo() {
    unsigned char sipkaVpravo[] = {
        0x00, 0x10, 0x18, 0xfc, 0xfe, 0xfc, 0x18, 0x10
    };

    for (int repeat = 0; repeat < 10; repeat++) { // Vonkajší cyklus pre opakovanie animácie 10-krát
        for (int posun = 0; posun <= 12; posun++) { // "ako daleko sipka prejde"
            for (int i = 1; i <= 8; i++) {  // Prechod všetkých 8 riadkov
                MAX7219_send(i, sipkaVpravo[i - 1]>>posun);
                _delay_ms(9); // Oneskor prí v každom kroku, aby sa zobrazil pohyb
				if (bit_is_clear(PIND, switchL)) {
					printf("Smer sipok: <--\r");
					return 0;
				}
            }
        }
    }
}

// Funkcia na zobrazenie sipky do ľava
void vlavo() {
    unsigned char sipkaVlavo[] = {
        0x00,0x08,0x18,0x3f,0x7f,0x3f,0x18,0x08
    };
	for (int repeat = 0; repeat < 10; repeat++) { // Vonkajší cyklus pre opakovanie animácie 10-krát
		for (int posun = 0; posun <= 12; posun++) {
			for (int i = 1; i <= 8; i++) {
				MAX7219_send(i, sipkaVlavo[i - 1]<<posun);
				delay(9);
				if (bit_is_clear(PIND, switchR)) {
					printf("Smer sipok: -->\r");  
					return 0;
				}					
			}
		}
	}	
}
// Funkcia na vyčistenie displeja
void clearDisp() {
    for (int i = 1; i <= 8; i++) { // Prechod cez všetky riadky displeja
        MAX7219_send(i, 0x00); // Nastavenie všetkých bitov v riadku na 0 (vymazanie)
    }
}

// Funkcia na počiatočnú animáciu
void smileAnimation() {
    unsigned char happy[] = {
        0x3c,0x42,0xa5,0x81,0xa5,0x99,0x42,0x3c
    };
	unsigned char normal[] = {
        0x3c,0x42,0xa5,0x81,0x81,0xbd,0x42,0x3c
    };
	unsigned char sad[] = {
        0x3c,0x42,0xa5,0x81,0x99,0xa5,0x42,0x3c
    };
	
	for (int repeat = 0; repeat < 3; repeat++) { // Vonkajší cyklus pre opakovanie animácie 3-krát
			for (int i = 1; i <= 8; i++) {
				MAX7219_send(i, happy[i - 1]);
				delay(9);
			}
			delay(700);
			for (int i = 1; i <= 8; i++) {
				MAX7219_send(i, normal[i - 1]);
				delay(9);
			}
			delay(700);
			for (int i = 1; i <= 8; i++) {
				MAX7219_send(i, sad[i - 1]);
				delay(9);
			}
			delay(700);
	}
}
#ifndef SPIinit_H_
#define SPIinit_H_

#include <stdio.h>

#define BAUD_PRESCALE  (((F_CPU / (BAUDRATE * 16UL))) - 1)  // vzorček z datasheetu
void delay(int delay); 
void SPI_init();
void MAX7219_send(unsigned char reg, unsigned char data);
void MAX7219_init();
void clearDisp();
void vpravo();
void vlavo();
void smileAnimation();
#endif /* SPIinit_H_ */
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"

void uart_init( void ) 
{
//  for different BAUD rate change the project settings, or uncomment 
//  following two lines:	
//	#undef  BAUD           // avoid compiler warning
//  #define BAUD 115200
	
   #include <util/setbaud.h>  // requires defined BAUD
   
   UBRR0H = UBRRH_VALUE;
   UBRR0L = UBRRL_VALUE;
   #if USE_2X                 // defined in setbaud.h 
    UCSR0A |= (1 << U2X0);
   #else
    UCSR0A &= ~(1 << U2X0);
   #endif


    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);   /* Enable RX and TX */	
}


int uart_putc( char c, FILE *stream )
{
   if (c == '\n') 
      uart_putc('\r',stream);
   
   loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
   UDR0 = c;
   return 0;
}


void uart_puts(const char *s)
{
  /* toto je vasa uloha */
}

char uart_getc(void) 
{
    loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
    return UDR0;
}
#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))

#ifndef UART_H_
#define UART_H_

#include <stdio.h>

#define BAUD_PRESCALE  (((F_CPU / (BAUDRATE * 16UL))) - 1)  // vzorček z datasheetu

void hw_init( void );
void uart_init( void );
     
int uart_putc( char c, FILE *stream );
void uart_puts( const char *s );

char uart_getc( void );

void delay(int delay); 

#endif /* UART_H_ */

Zdrojový kód: projektNagyL.zip

Overenie

Na používanie našej aplikácie stačia dve tlačítka a postup používania je opísaný v sekcii popis riešenia. Na konci uvádzame fotku záverečnej obrazovky pred resetom. Vypísaný je tu priemerný čas a najlepší čas.

Aplikácia.

Video: