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.
Literatúra:
Analýza a opis riešenia
V projekte sme využil maticový diplej so 64 LED diódami usporiadanými do matice 8x8, na displej sme pripojili 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 radičom MAX7219)
- CS(chip select, pin 10) - slúži na vybratie konkrétneho slave zariadenia, s ktorým má master 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.
Pri tvorbe projektu bolo potrebné poznať aj jednotlivé registre radiča MAX7219:
- No-Op: tento register sa nepoužíva na ovládanie, slúži len ako výplň, keď chcete komunikovať s viacerými MAX7219 radičmi v sérii
- Digit Registre (od 0x01 až po 0x08): ovládajú jednotlivé riadky LED displeja, každý z nich zodpovedá jednému riadku (digit 0 až digit 7) a umožňujú nastaviť, ktoré LEDky v danom riadku budú svietiť
- Decode Mode Register (0x09): určuje, ktoré z číslic budú dekódované (pre 7-segmentové displeje) a ktoré budú priamo riadené (pre LED maticové displeje)
- Intensity Register (0x0A): ovláda jas LED 8x8 displeja, pričom hodnota môže byť od 0x00 pre najnižší jas, až po 0x0F pre najvyšší jas
- Scan-Limit Register (0x0B): určuje počet zobrazovaných číslic alebo riadkov, pre 8x8 LED displej sa nastaví na 0x07, čo znamená, že bude skenovať všetkých 8 riadkov
- Shutdown Register (0x0C): slúži na zapnutie alebo vypnutie displeja, hodnota 0x00 vypne displej a hodnota 0x01 ho zapne
- Display-Test Register (0x0F): používa sa na testovanie displeja, ak je nastavený na 0x01, všetky LEDky sa rozsvietia a ak je nastavený na 0x00, displej funguje normálne
- 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ť od neho dáta.
- Princíp fungovania SPI komunikácie:
- 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.
- 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.
Schéma zapojenia
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 pre čítanie stavu 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
Funkčnosť projektu sme overili, ako môžeme vidieť videu. Na začiatku sa zobrazí animácia smajlíka a následne sa dá šípkami prepínať smer animácie.
Video: