Protótipo 262.144 combinações de cores de LED e 64 pixels

    Quero compartilhar a experiência de criar um display LED de 8x8 pixels, 262k combinações de cores (18 bits), uma taxa de quadros de 180 FPS e uma conexão USB. Também estou pronto para ouvir sugestões de otimização e refinamento. No futuro, pretendo usar as melhores práticas para criar uma exibição da estação meteorológica doméstica.

Prefácio


    Tudo começou com o esquema de controle mais simples para uma linha de 8 LEDs através de uma porta LPT. A versão seguinte era uma placa 5x8 de LEDs de três cores, que também era conectada ao LPT e, de fato, era uma matriz de quinze buffers de 8 bits com um decodificador para o endereçamento.
    Mais tarde, depois de conhecer os microcontroladores, decidi criar uma tela semelhante, mas com uma conexão USB. Inicialmente esperado usar apenas 8 cores. Posteriormente, ele encontrou uma maneira de controlar o brilho de cada diodo usando um temporizador por analogia com o PWM e, após finalizar a parte do software, o dispositivo atual foi desligado. Teoricamente, você pode trabalhar com 16 milhões de cores, mas os LEDs convencionais não são adequados para esse modo em termos de reprodução e repetibilidade de cores. Além disso, problemas com a cor de diferentes diodos já são visíveis na configuração atual.

Descrição do trabalho


    O dispositivo é baseado no microcontrolador PIC18F4550, operando a uma frequência de 48 MHz. Um controlador USB embutido e uma biblioteca pronta para trabalhar com ele são usados, Timer0 no modo de 8 bits, que implementa a indicação dinâmica. Para armazenar três cores em uma coluna, três gatilhos de 8 bits no 74F374 foram usados. O uso desse buffer permite reduzir o tempo de exibição de um quadro em 3 vezes. Nota: Quando selecionei o buffer 74F374, não prestei atenção à fiação de suas pernas, mas entendi isso apenas no suporte de montagem, portanto tive que complicar significativamente a placa. É melhor usar análogos mais convenientes. Por exemplo, 74HC574.
    Os LEDs são conectados através das teclas ULN2803 e UDN2982. As resistências limitadoras de corrente estão apenas no canal vermelho, porque a tensão de alimentação está abaixo de azul e verde. Para azul e verde, a resistência não está instalada, porque queda de tensão suficiente nas teclas. Nota: Para uma reprodução de cores mais precisa, é melhor selecionar resistências limitadoras de corrente mais precisas em cada canal.
    O microcontrolador em um ciclo interminável pesquisa o status do USB e, quando um pacote de dados chega, dependendo do comando, inicia / para a exibição ou prepara os dados para exibição. Devido à limitação do tamanho de um pacote para 64 bytes, os dados de cada cor são transmitidos em um pacote separado de 48 bytes - 6 bytes para cada uma das 8 colunas, codificando o brilho de cada LED na coluna. Após receber cada pacote, ele é copiado da memória USB para uma matriz de sua cor.
    Após o recebimento do comando de indicação, o MK ativa o timer no modo de 8 bits e um divisor em 128. O timer usa a frequência de operação do microcontrolador como pulsos de clock. Um aumento no contador do timer ocorre a cada 4 compassos. O período mínimo do timer é de 10,6 μs (1/48 * 4 * 128), que é aproximadamente 2,8 vezes o tempo de processamento da interrupção (46 operações, contra 128 amostras do timer).
    Quando o temporizador transborda, uma alta interrupção do vetor é realizada. O manipulador de interrupção desativa a indicação, atualiza os dados nos buffers, transferindo 1 byte de cada matriz de cores de acordo com o cursor e, em seguida, ativa a indicação. Insere um novo valor no timer a partir do buffer temporário, diminui o cursor, muda o buffer temporário para o timer. Se o buffer do temporizador excedeu o valor máximo, ou seja, deslocado mais de 5 vezes, o buffer do timer é redefinido para o valor mínimo e o ponteiro da coluna selecionada é deslocado.
Como resultado, é obtido o seguinte algoritmo de indicação dinâmica:
  1. Pegamos o primeiro grupo de 3 bytes de três matrizes e colocamos os buffers de cada cor na coluna.
  2. Ativamos o timer com um tempo de atraso mínimo de 128 ticks.
  3. Pegamos o próximo grupo de 3 bytes de três matrizes e colocamos os buffers de cada cor na coluna.
  4. Ativamos o timer com um atraso duplo em relação ao passo anterior.
  5. Repita a amostra mais 4 vezes e duplique o tempo de atraso de cada vez.
  6. Redefinimos o cronômetro e começamos a processar a próxima coluna da etapa 1.

    Assim, podemos definir 2 ^ 6 = 64 opções de brilho para cada diodo na coluna. Combinando o brilho de cada uma das três cores básicas, obtemos 64 * 64 * 64 = 262144 cores. O tempo de processamento para uma coluna é (2 ^ 6-1) * 10,6 μs = 672 μs. O tempo por quadro de 8 colunas é 672 * 8 = 5.4ms, o que corresponde aproximadamente a 186 quadros por segundo.

Componentes Utilizados


  • PIC18F4550 - Microcontrolador
  • 74F374 - Disparador para manter os valores atuais da coluna
  • ULN2803 - Chave para controle de cátodo
  • UDN2982 - Chave para controle de ânodos
  • LEDs RGB de 4 pinos com um cátodo comum (qualquer LED pode ser usado)

Esquema


Esquema no formato dsn - download
Gráficos


Taxa


Lay6 desenhos - baixar
Gráficos
1


2


( , , )





Firmware


Origens e HEX montado no MPLABX X IDE v2.30 - download
Código principal
#ifndef MAIN_C
#define MAIN_C

// Local includes
#include "config.h"
#include "usb.h"
#include "HardwareProfile.h"
#include "usb_function_hid.h"
#include "genericHID.h"

#define UdnOn           LATA&=0b11111110
#define UdnOff          LATA|=0b00000001

#define UlnOn           LATD
#define UlnOff          LATD =0b00000000

#define LineBufer       LATB

#define WriteR          LATE|=0b00000001
#define WriteG          LATE|=0b00000010
#define WriteB          LATE|=0b00000100
#define WriteRst        LATE =0b00000000

#define Columns         8
#define BrightLevels    6
#define BlockSize       (Columns*BrightLevels)

#define MinBright       0b11111111

unsigned char cursor;
unsigned char bright;
unsigned char column;
unsigned char dataR[BlockSize];
unsigned char dataG[BlockSize];
unsigned char dataB[BlockSize];

void ProcessIO(void) {
    unsigned char temp = BlockSize + 1;

    // If we are not in the configured state just return
    if ((USBDeviceState < CONFIGURED_STATE) || (USBSuspendControl == 1)) return;

    //Check if data was received from the host.
    if (!HIDRxHandleBusy(USBOutHandle))
    {
        switch (ReceivedDataBuffer[0])
        {
            case 0x80: // get red packet
                while (--temp) dataR[temp-1] = ReceivedDataBuffer[temp];
                break;

            case 0x81: // get green packet
                while (--temp) dataG[temp-1] = ReceivedDataBuffer[temp];
                break;

            case 0x82: // get blue packet
                while (--temp) dataB[temp-1] = ReceivedDataBuffer[temp];
                break;

            case 0x90: // start
                column = 0b00000001;
                cursor = BlockSize;
                bright = MinBright;
                TMR0ON = 1;
                SWDTEN = 0;
                break;

            case 0x91: // stop
                UdnOff;
                UlnOff;
                TMR0ON = 0;
                SWDTEN = 0;
                break;

            case 0x92: // power off
                UdnOff;
                UlnOff;
                TMR0ON = 0;
                SWDTEN = 0;
                SLEEP();
                break;
        }

        // Re-arm the OUT endpoint for the next packet
        USBOutHandle = HIDRxPacket(HID_EP, (BYTE*) & ReceivedDataBuffer, 64);
    }
}

void main(void)
{
    // Set all port as digital input/output
    PCFG3   = 1;

    // Clear all ports
    //          76543210
    PORTA   = 0b00000000;
    PORTB   = 0b00000000;
    PORTC   = 0b00000000;
    PORTD   = 0b00000000;
    PORTE   = 0b00000000;

    // Configure ports (1 - inputs; 0 - outputs)
    //          76543210
    TRISA   = 0b00000000;
    TRISB   = 0b00000000;
    TRISC   = 0b00000000;
    TRISD   = 0b00000000;
    TRISE   = 0b00000000;

    // Configure interrupts for Timer0
    //          76543210
    INTCON  = 0b10100000;

    // Configure Timer0 as 8bit and 128 prescaler
    //          76543210
    T0CON   = 0b01000110;

    USBDeviceInit();

    while(1)
    {
        // Check bus status and service USB interrupts.
        USBDeviceTasks();

        // Application-specific tasks.
        ProcessIO();
    };
}

void interrupt tc_int() // High priority interrupt
{
    UdnOff;
    UlnOff;
    LineBufer = dataR[cursor-1]; WriteR;
    LineBufer = dataG[cursor-1]; WriteG;
    LineBufer = dataB[cursor-1]; WriteB;
    UdnOn;
    UlnOn = column;
    WriteRst;
    TMR0L = bright;

    if (!--cursor) cursor = BlockSize;

    bright <<= 1;
    asm("BTFSS _bright, 5, 0"); asm("RLNCF _column, 1, 0");
    asm("BTFSS _bright, 5, 0"); bright = MinBright;

    TMR0IF = 0;
}
#endif



Dispositivo em operação


    Para controle, uso um reprodutor de rádio da Internet escrito em C, baseado na biblioteca BASS.DLL. Uma demonstração de gradiente em toda a paleta de cores disponível funciona durante uma pausa, a taxa de atualização de quadros (pacotes transmitidos para o dispositivo) é de 20 Hz. Ao tocar música, um visualizador funciona usando a matriz FFT obtida pelo BASS.DLL, a taxa de atualização de quadros (pacotes transmitidos para o dispositivo) nesse modo é 29 Hz.

Gradiente


Visualizer

: Tape Five — Soulsalicious

: ( ) ( ). .. , .







  • ( UDN)
  • USB
  • smd
  • 74F374 74HC574,
  • 74F374
  • 74HC138,
  • 3 ULN, UDN

Source: https://habr.com/ru/post/pt392815/


All Articles