LED Panel 30x7: Difference between revisions
added info how to connect serial adapter to be able to send data from computer |
mNo edit summary |
||
| Line 362: | Line 362: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
You need MightyCore | You need MightyCore https://github.com/MCUdude/Mightycore in your arduino to be able to compile it, and here is the arduino configuration | ||
[[File:LED_panel_30x7_arduino_cfg.png]] | [[File:LED_panel_30x7_arduino_cfg.png]] | ||
Revision as of 01:52, 29 October 2026
What is this about
This project is about to make these LED panels working. We got them for free including power supplies and some cables, however the communication protocol is unknown.
To be able to use them we need to find out the protocol, or make our own firmware.
Hack the protocol!
The device is equipped with pair of XLR connectors connected in parallel suggesting use of DMX or RS485 protocol with daisy-chaining in mind, also the vendor (https://www.srs-group.com) is making a lot of DMX stuff, however I tried to send it DMX data on all 512 channels but no luck. I also tried to send it RS485 data in various formats, baudrates, etc - nothing worked.
I also tried to read the firmware of the main controller (atmega16) but its locked (as usual).
So I deciced to go "my firmware" way...
Make our own firmware
To be able to make our own firmware we need to know how this works - so I spent couple of hours reverse engineering the control board so we have the schematic

Then I used money of the US tax payers (free chatgpt AI) to make the firmware for atmega16 in arduino with the MightyCore atmega16 support. I told him where and how are the shift registers and row decoders connected, his first task was just to display "base48" text... He did it... Then I asked him to add very simple serial protocol that whill wait for device address and then starts to receive graphics data column by column... And that worked too.
After some polishing here is the firmware that:
- on power up it reads device address from EEPROM address 0
- shows the device address for 3 seconds on the screen
- then screen is cleared and it waits for serial data (115200, 8N1)
- it ignores all data until device address is received
- after it receives its address, it starts to receive graphics data and resets the column counter
- because each colum of display consists of 7 LEDs, I decided that all bytes with 0 in the most significant bit (0x0-0x7F) will represent graphics data (simply each bit corresponds to LED in the column) and all with 1 in this bit (0x80-0xFF) will represent device address
- therefore the device address needs to be in 0x80-0xFF range, this way we can handle up to 128 devices on the same bus
- after each received graphics byte column counter is incremented
- therefore if the device address its for example 0x80, you need to send 0x80, 0x7F, 0x7F, ... and 28 more 0x7Fs to make all LEDs on the panel lid up
- the device ignores all bytes >=0x80 if they are not its device address
- the device ignores all graphics data if end of display was reached (means all 30 columns received) until it receives its address again
#include <SPI.h>
#include <EEPROM.h>
// device address is stored in eeprom on address 0 and should be >=0x80
// to change it run "avrdude -c usbasp -p m16 -U eeprom:w:0x80:m" and replace 0x80 with your address
// graphics data = all bytes from serial < 0x80 (as we have only 7 LEDs)
// when device address is received on serial = start receiving graphics data to display buffer
// =====================================================
// Konfigurace pinů – ATmega16, MightyCore std pipnout
// =====================================================
const uint8_t PIN_LATCH = 21; // PC5
const uint8_t PIN_OE = 20; // PC4
const uint8_t PIN_S0 = 16; // PC0
const uint8_t PIN_S1 = 17; // PC1
const uint8_t PIN_S2 = 18; // PC2
const uint8_t PIN_EN = 19; // PC3
const uint32_t SPI_SPEED = 16000000; //drivery by mely fungovat do 20MHz
// =====================================================
// Parametry displeje
// =====================================================
const uint8_t WIDTH = 30;
const uint8_t HEIGHT = 7;
const uint32_t ROW_TIME_US = 1000;
const uint8_t DECODER_ENABLED = LOW;
const uint8_t DECODER_DISABLED = HIGH;
const uint8_t OUTPUTS_ENABLED = LOW;
const uint8_t OUTPUTS_DISABLED = HIGH;
// =====================================================
// Sériová komunikace
// =====================================================
const uint32_t SERIAL_BAUD = 115200;
// =====================================================
// Obrazová paměť
//
// buffer[row][column]
// =====================================================
bool buffer[HEIGHT][WIDTH];
// Index následujícího zapisovaného sloupce.
// WIDTH znamená, že je buffer plný.
uint8_t writeColumn = 0;
uint8_t devAddr = 80;
uint32_t startupDisplayTime = 0;
bool startupDisplayActive = true;
// =====================================================
// Font 3x5 pro úvodní zobrazení adresy
// =====================================================
// Každý znak má 3 sloupce, každý sloupec obsahuje 5 bitů.
// Bit 0 = horní pixel.
const uint8_t font3x5[][3] = {
{0x1F, 0x11, 0x1F}, // 0
{0x00, 0x1F, 0x00}, // 1
{0x1D, 0x15, 0x17}, // 2
{0x15, 0x15, 0x1F}, // 3
{0x07, 0x04, 0x1F}, // 4
{0x17, 0x15, 0x1D}, // 5
{0x1F, 0x15, 0x1D}, // 6
{0x01, 0x01, 0x1F}, // 7
{0x1F, 0x15, 0x1F}, // 8
{0x17, 0x15, 0x1F}, // 9
{0x1E, 0x05, 0x1E}, // A
{0x1F, 0x15, 0x0A}, // B
{0x0E, 0x11, 0x11}, // C
{0x1F, 0x11, 0x0E}, // D
{0x1F, 0x15, 0x11}, // E
{0x1F, 0x05, 0x01}, // F
{0x14, 0x08, 0x14} // x
};
// Vymazání obrazového bufferu.
void clearBuffer() {
memset(buffer, 0, sizeof(buffer));
}
// Vykreslení jednoho znaku 3x5.
void drawChar(uint8_t index, uint8_t x, uint8_t y) {
for (uint8_t col = 0; col < 3; col++) {
uint8_t bits = font3x5[index][col];
for (uint8_t row = 0; row < 5; row++) {
if (x + col < WIDTH && y + row < HEIGHT) {
buffer[y + row][x + col] = (bits >> row) & 1;
}
}
}
}
// Zobrazení adresy, například 0x83.
void showDeviceAddress() {
clearBuffer();
char text[5];
snprintf(text, sizeof(text), "%02X", devAddr);
// Text tvoří 0, x a dvě hexadecimální číslice.
// Šířka: 4 znaky * 3 pixely + 3 mezery = 15 pixelů.
// Začátek je vypočítán pro vodorovné vycentrování.
uint8_t x = (WIDTH - 15) / 2;
uint8_t y = (HEIGHT - 5) / 2;
drawChar(0, x, y); // 0
x += 4;
drawChar(16, x, y); // x
x += 4;
for (uint8_t i = 0; i < 2; i++) {
char c = text[i];
uint8_t index;
if (c >= '0' && c <= '9') {
index = c - '0';
} else {
index = c - 'A' + 10;
}
drawChar(index, x, y);
x += 4;
}
}
// =====================================================
// Příjem dat ze sériového portu
// =====================================================
void processSerial() {
while (Serial.available() > 0) {
uint8_t data = Serial.read();
// Bajty s nejvyšším bitem 1 jsou příkazy.
if (data & 0x80) {
// Jediný podporovaný příkaz: reset adresy zápisu pokud je prijat devAddr.
if (data == devAddr) {
writeColumn = 0;
}
// Ostatní ignorovat
continue;
}
// Pokud je buffer plný, další data ignorovat.
if (writeColumn >= WIDTH) {
continue;
}
// Zapsat všech 7 bitů do aktuálního sloupce.
for (uint8_t row = 0; row < HEIGHT; row++) {
buffer[row][writeColumn] = (data >> row) & 1;
}
// Připravit následující sloupec.
writeColumn++;
}
}
// =====================================================
// Výběr řádku přes 74LS138
// =====================================================
void selectRow(uint8_t row) {
digitalWrite(PIN_S0, (row & 0x01) ? HIGH : LOW);
digitalWrite(PIN_S1, (row & 0x02) ? HIGH : LOW);
digitalWrite(PIN_S2, (row & 0x04) ? HIGH : LOW);
}
// =====================================================
// Zápis sloupců do dvou A6276E
// =====================================================
void writeColumns(uint8_t row) {
uint32_t word = 0;
for (uint8_t col = 0; col < WIDTH; col++) {
if (buffer[row][col]) {
word |= (1UL << col);
}
}
digitalWrite(PIN_LATCH, LOW);
SPI.transfer((word >> 24) & 0xFF);
SPI.transfer((word >> 16) & 0xFF);
SPI.transfer((word >> 8) & 0xFF);
SPI.transfer(word & 0xFF);
digitalWrite(PIN_LATCH, HIGH);
}
// =====================================================
// Multiplexní obsluha displeje
// =====================================================
uint8_t currentRow = 0;
uint32_t lastRowChange = 0;
void refreshDisplay() {
uint32_t now = micros();
if ((uint32_t)(now - lastRowChange) < ROW_TIME_US) {
return;
}
lastRowChange = now;
// 1. Zhasnout výstupy během změny řádku.
digitalWrite(PIN_OE, OUTPUTS_DISABLED);
// 2. Zakázat dekodér.
digitalWrite(PIN_EN, DECODER_DISABLED);
// 3. Zapsat data pro aktuální řádek.
writeColumns(currentRow);
// 4. Vybrat řádek.
selectRow(currentRow);
// 5. Povolit řádek.
digitalWrite(PIN_EN, DECODER_ENABLED);
// 6. Povolit výstupy LED driverů.
digitalWrite(PIN_OE, OUTPUTS_ENABLED);
// 7. Připravit následující řádek.
currentRow++;
if (currentRow >= HEIGHT) {
currentRow = 0;
}
}
// =====================================================
// Inicializace
// =====================================================
void setup() {
// Sériová komunikace 115200 baud.
Serial.begin(SERIAL_BAUD);
// SPI pro LED drivery.
SPI.begin();
SPI.beginTransaction(
SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0)
);
// Konfigurace pinů.
pinMode(PIN_LATCH, OUTPUT);
pinMode(PIN_OE, OUTPUT);
pinMode(PIN_S0, OUTPUT);
pinMode(PIN_S1, OUTPUT);
pinMode(PIN_S2, OUTPUT);
pinMode(PIN_EN, OUTPUT);
// Displej při inicializaci zhasnout.
digitalWrite(PIN_OE, OUTPUTS_DISABLED);
digitalWrite(PIN_EN, DECODER_DISABLED);
digitalWrite(PIN_LATCH, HIGH);
digitalWrite(PIN_S0, LOW);
digitalWrite(PIN_S1, LOW);
digitalWrite(PIN_S2, LOW);
// buffer pointer je po resetu nastaven na WIDTH aby se ignorovala data dokud nedojde odpovidajici devaddr
writeColumn = WIDTH;
// Okamžitá první obnova displeje.
currentRow = 0;
lastRowChange = micros() - ROW_TIME_US;
// Adresa zarizeni z EEPROM z adresy 0
devAddr = EEPROM.read(0) | 0x80; //nacist adresu z eeprom a ujistit se ze bude >=0x80
showDeviceAddress();
startupDisplayTime = millis();
startupDisplayActive = true;
}
// =====================================================
// Hlavní smyčka
// =====================================================
void loop() {
if (!startupDisplayActive) {
processSerial();
} else if ((uint32_t)(millis() - startupDisplayTime) >= 3000) {
// Po 3 sekundách odstranit uvidni screen s adresou a zacit prijimat data
startupDisplayActive = false;
clearBuffer();
}
// obnovovat displej
refreshDisplay();
}
You need MightyCore https://github.com/MCUdude/Mightycore in your arduino to be able to compile it, and here is the arduino configuration
Also here is the compiled firmware in .hex (sorry wiki does not let me to upload it as file)...
:100000000C9465000C948D000C948D000C948D0064
:100010000C948D000C948D000C948D000C948D002C
:100020000C948D000C94A9020C948D000C94770212
:100030000C9451020C948D000C948D000C948D0046
:100040000C948D000C948D000C948D000C948D00FC
:100050000C948D0000000002000000000000000071
:100060000504000700000000000000000000000080
:100070000000000000003B003800350032000000A6
:100080003A0037003400310002020202020202028A
:100090000404040404040404030303030303030328
:1000A0000101010101010101010204081020408049
:1000B0000102040810204080010204081020408042
:1000C0000102040810204080FB0411241FBECFE56C
:1000D000D4E0DEBFCDBF10E0A0E6B0E0E6E3F0E1A3
:1000E00002C005900D92AC3AB107D9F722E0ACEA14
:1000F000B0E001C01D92AF32B207E1F710E0C5E6F3
:10010000D0E004C02197FE010E94E307C436D10766
:10011000C9F70E94F3020C9419080C940000CF93C5
:10012000DF9323E0829FC001112430E020E070E0E3
:10013000635A7E4FF901E80FF91FEC58FF4FC08159
:10014000FB01E20FF31F50E040E0D0E0DE01042E9F
:1001500002C0B595A7950A94E2F7A170A68F4F5FEC
:100160005F4F7E964530510589F72F5F3F4F233013
:10017000310501F7DF91CF91089590E0FC01E8553A
:10018000FF4F249188579F4FFC018491882399F059
:1001900090E0880F991FFC01E258FF4FA591B491A0
:1001A000FC01EC58FF4F859194918FB7F894EC9136
:1001B000E22BEC938FBF0895AF92BF92CF92DF9264
:1001C000EF92FF920F931F93CF93DF936C017B010C
:1001D0008B01040F151FEB015E01AE18BF08C0179D
:1001E000D10759F06991D601ED91FC910190F08110
:1001F000E02DC6010995892B79F7C501DF91CF91D3
:100200001F910F91FF90EF90DF90CF90BF90AF9034
:100210000895FC01538D448D252F30E0842F90E00C
:10022000821B930B541710F0CF96089501970895F1
:10023000FC01918D828D981761F0A28DAE0FBF2FBA
:10024000B11D5D968C91928D9F5F9F73928F90E010
:1002500008958FEF9FEF0895FC01918D828D98177F
:1002600031F0828DE80FF11D858D90E008958FEFBC
:100270009FEF0895FC01918D228D892F90E0805C85
:100280009F4F821B91098F739927089583EB90E00C
:100290000E943A0121E0892B09F420E0822F089581
:1002A00080E090E0892B29F00E94460181110C9496
:1002B00000000895FC01A48DA80FB92FB11DA35A09
:1002C000BF4F2C91848D90E001968F739927848F76
:1002D000A689B7892C93A089B1898C9182748C935B
:1002E000938D848D981306C00288F389E02D808158
:1002F0008F7D80830895EF92FF920F931F93CF938A
:10030000DF93EC0181E0888F9B8D8C8D981319C051
:10031000E889F989808185FF14C09FB7F894EE8938
:10032000FF896083E889F9898081827480839FBF17
:1003300081E090E0DF91CF911F910F91FF90EF90BE
:100340000895F62E0B8D10E00F5F1F4F0F731127CE
:10035000E02E8C8D8E110CC00FB607FCFACFE88909
:10036000F989808185FFF5CFCE010E945A01F1CF36
:10037000EB8DEC0FFD2FF11DE35AFF4FF0829FB77D
:10038000F8940B8FEA89FB8980818062CFCFCF936D
:10039000DF93EC01888D8823B9F0AA89BB89E889AD
:1003A000F9898C9185FD03C0808186FD0DC00FB653
:1003B00007FCF7CF8C9185FFF2CF808185FFEDCFD1
:1003C000CE010E945A01E9CFDF91CF91089590E0CC
:1003D000FC01EC5AFF4F2491FC01E855FF4F34918A
:1003E000FC01E857FF4FE491EE23C9F0222339F0D6
:1003F0002430F1F0A8F4213011F1223001F1F0E0C5
:10040000EE0FFF1FEC58FF4FA591B4918FB7F894F2
:10041000EC91611118C030953E233C938FBF089535
:10042000253051F0273059F785B58F7D85BDE7CF51
:100430008FB58F778FBDE3CF8FB58F7DFBCF83B720
:100440008F7D83BFDCCF3E2BE8CF3FB7F894809100
:10045000AD009091AE00A091AF00B091B00022B776
:1004600008B600FE05C02F3F19F00196A11DB11D71
:100470003FBFBA2FA92F982F8827BC01CD01620F4B
:10048000711D811D911D42E0660F771F881F991F06
:100490004A95D1F708958FB90000779BFECF8FB1B1
:1004A00008951F920F920FB60F9211242F933F932E
:1004B0004F935F936F937F938F939F93AF93BF936C
:1004C000EF93FF9383EB90E00E945A01FF91EF912D
:1004D000BF91AF919F918F917F916F915F914F915C
:1004E0003F912F910F900FBE0F901F9018951F9264
:1004F0000F920FB60F9211242F938F939F93EF9328
:10050000FF93E091C300F091C4008081E091C900A5
:10051000F091CA0082FD1BC090818091CC008F5F5A
:100520008F732091CD00821741F0E091CC00F0E074
:10053000ED54FF4F958F8093CC00FF91EF919F91E9
:100540008F912F910F900FBE0F901F901895808163
:10055000F4CF1F920F920FB60F9211242F933F9357
:100560008F939F93AF93BF938091500190915101CE
:10057000A0915201B09153013091AC0023E0230FC0
:100580002D3768F126E8230F0296A11DB11D209397
:10059000AC008093500190935101A0935201B0930D
:1005A00053018091AD009091AE00A091AF00B09149
:1005B000B0000196A11DB11D8093AD009093AE00D7
:1005C000A093AF00B093B000BF91AF919F918F9176
:1005D0003F912F910F900FBE0F901F90189501968D
:1005E000A11DB11DD4CFCF93DF93CDB7DEB729972F
:1005F0000FB6F894DEBF0FBECDBF789483B783608B
:1006000083BF83B7886483BF89B7816089BF92E065
:100610009EBD8EB581608EBD8FB581608FBD85B565
:10062000846085BD85B5806485BD87E886B91AB8C4
:10063000E091C300F091C4009083E091BF00F0917D
:10064000C0001082E091C100F091C20080E180837F
:100650001092CB00E091C700F091C80086E880833B
:10066000E091C500F091C600808188698083E091A7
:10067000C500F091C60080818F7D80831FB7F894FC
:100680008091B100811120C0ECE8F0E08491ECEAA7
:10069000F0E09491E82FF0E0EE0FFF1FE258FF4FDB
:1006A000A591B491EC91E92321F461E084E00E94EA
:1006B000E70184E00E94BD006C9A6E9A87E00E9478
:1006C000BD0085E00E94BD008091B1008F5F8093E6
:1006D000B1001FBF80E58DB981E08EB985E10E9430
:1006E000BD0084E10E94BD0080E10E94BD0081E167
:1006F0000E94BD0082E10E94BD0083E10E94BD0016
:1007000061E084E10E94E70161E083E10E94E7018A
:1007100061E085E10E94E70160E080E10E94E7017D
:1007200060E081E10E94E70160E082E10E94E70170
:100730008EE18093B200109254010E942502685EFF
:10074000734081099109609355017093560180931C
:1007500057019093580190E080E00E94A4078068C0
:1007600080936000EDE5F1E092EDDF011D929A9536
:10077000E9F71F928F9387EA90E09F938F931F92E0
:1007800085E08F93CE0101969F938F930E94E90796
:1007900067E080E00E948F006BE080E10E948F00A4
:1007A000998180ED890F0FB6F894DEBF0FBECDBFE3
:1007B0008A3010F089EC890F6FE00E948F009A81D7
:1007C00080ED890F8A3010F089EC890F63E10E9477
:1007D0008F002FB7F8948091500190915101A09112
:1007E0005201B09153012FBF8093590190935A0148
:1007F000A0935B01B0935C0181E0809361002DE5E3
:10080000822E21E0922E3EE1232E412C512C3201EA
:10081000439450E0A52E50E0B52E809161008111E7
:1008200033C083EB90E00E943A01181619060CF0D1
:100830004EC083EB90E00E94180187FF07C09091A3
:1008400060009813EECF1092B200EBCF6091B2002F
:100850006E3138F7E62FF0E0E35AFE4F30E020E04B
:100860009927AC01022E02C0559547950A94E2F7EC
:10087000417040832F5F3F4F7E962730310589F7C7
:100880006F5F6093B200CDCF2FB7F8948091500185
:1008900090915101A0915201B09153012FBF40910D
:1008A000590150915A0160915B0170915C01841B68
:1008B000950BA60BB70B883B9B40A105B10538F003
:1008C00010926100D401E2ED1D92EA95E9F70E94D1
:1008D0002502009155011091560120915701309148
:1008E00058016B017C01C01AD10AE20AF30A970190
:1008F0008601083E13402105310508F46DC0609360
:10090000550170935601809357019093580161E00F
:1009100084E10E94E70161E083E10E94E7013090F9
:100920005401329C9001112490E080E0C12CD12C24
:100930007601032D10E0F901E80FF91FE35AFE4F8D
:100940005081552371F0B301A201082E04C0440F59
:10095000551F661F771F0A94D2F7C42AD52AE62AA4
:10096000F72A01968E31910531F760E085E10E940A
:10097000E7018F2D0E944B028E2D0E944B028D2D80
:100980000E944B028C2D0E944B0261E085E10E9487
:10099000E701632D617080E10E94E701B801759560
:1009A0006795617081E10E94E7011595079515959E
:1009B0000795602F617082E10E94E70160E083E1AA
:1009C0000E94E70160E084E10E94E70181E0830D7D
:1009D000873070F480935401A114B10409F41DCF41
:1009E0000E944601882309F418CF0E94000015CF09
:1009F00010925401F1CFE3EBF0E01382128288EE03
:100A000093E0A0E0B0E084839583A683B78386E675
:100A100090E09183808380E490E09587848729E249
:100A200030E0378726872BE230E0318B208B2AE2BB
:100A300030E0338B228B958B848B8CE290E0978B0C
:100A4000868B118E128E138E148E0895ABE0B0E05B
:100A5000ECE2F5E00C94AC077C013B018A01FC015F
:100A600017821682838181FFC8C1CE0101965C0185
:100A7000F7019381F30193FD859193FF81913F01EC
:100A8000882309F453C1853239F493FD859193FF8E
:100A900081913F01853229F4B70190E00E94160749
:100AA000E7CF912C212C312CFFE1F31538F08B325C
:100AB00011F190F4803209F1833229F137FC3CC006
:100AC00020ED280F2A3050F536FE20C08AE0989E8F
:100AD000200D1124922E06C08D3291F0803371F7D3
:100AE000689430F8F30193FD859193FF81913F0164
:100AF0008111DACF21C0689431F8689432F8F2CFCE
:100B0000689433F8EFCF689434F8ECCFEAE02E9E87
:100B1000200D1124222E689435F8E4CF8E3229F46A
:100B200036FC04C1689436F8DDCF8C3619F468942D
:100B300037F8D8CF8836B1F2982F9F7D95549330EF
:100B4000E0F08336A1F18337C1F1833509F063C04A
:100B50002801F2E04F0E511CF801C080D180692DB0
:100B600070E036FC02C06FEF7FEFC6010E94000705
:100B70004C01689437F882010AC00C5F1F4FFFE3F5
:100B8000F98388248394912C6501E89437F833FE27
:100B90002DC0522C8114910471F5552009F468CFB1
:100BA000B70180E290E00E9416075A94F6CFF80150
:100BB000808189830E5F1F4FE4CF2801F2E04F0E42
:100BC000511CF801C080D180692D70E036FC02C054
:100BD0006FEF7FEFC6010E940B074C018201D5CF5A
:100BE000B70180E290E00E9416072A9428141904A5
:100BF00009F0B0F7CECFF60137FC859137FE819131
:100C00006F01B70190E00E94160751105A94F1E06D
:100C10008F1A9108BFCF843619F0893609F077C052
:100C2000F80137FE6BC060817181828193810C5F16
:100C30001F4FF32DFF763F2E97FF09C090958095AB
:100C4000709561957F4F8F4F9F4F689437F82AE0DA
:100C500030E0A5010E944607C82ECA188C2C432CF0
:100C600036FE0CC0E89440F8C91440F434FE05C0C8
:100C700032FC03C0F32DFE7E4F2E892C44FE95C01E
:100C8000FE01EC0DF11D8081803309F087C0242D19
:100C9000297E422E842D8870582E43FC95C040FE3C
:100CA0008FC09C2C821418F42C0C922C981844FEA3
:100CB00091C0B70180E390E00E94160742FE09C090
:100CC00088E790E041FE02C088E590E0B7010E940D
:100CD0001607C91408F48AC0CA94D12C9FEFC91A08
:100CE000D90ACA0CDB1CF60182916F01B70190E0B2
:100CF0000E941607AC14BD04B1F74FCF608171811B
:100D0000072E000C880B990B0E5F1F4F92CFD32C30
:100D1000E894D4F82AE030E0853739F1232D297F93
:100D2000D22E8F3601F148F48835A9F0F70186817B
:100D300097812B96E2E10C94C807803749F08837F9
:100D4000A9F7D4FE02C06894D2F820E130E00DC0CB
:100D50006894D4F8F6CF34FE03C0822F8660D82E74
:100D600020E132E002C028E030E0F801D7FE0FC0F9
:100D700060817181828193810C5F1F4FA5010E9468
:100D80004607C82ECA183D2CE89437F867CF608113
:100D9000718190E080E00E5F1F4FF0CF42FC02C0F7
:100DA000839478CF8394839475CF842D867809F4C7
:100DB00071CFF6CFB70180E290E00E9416078394CE
:100DC0008214C0F3512C73CF522C5818821408F49B
:100DD0006ECFF8CF842D867809F47BCF8BE241FE6D
:100DE00080E247FC8DE2B70190E071CFB70180E36C
:100DF00090E00E9416079A946CCF8FEF9FEF99CFE7
:100E0000FC010590615070400110D8F780959095D5
:100E10008E0F9F1F0895FC016150704001900110DA
:100E2000D8F7809590958E0F9F1F08950F931F936D
:100E3000CF93DF93182F092FEB018B8181FD09C020
:100E40001FEF0FEF812F902FDF91CF911F910F9107
:100E5000089582FF14C02E813F818C819D812817C7
:100E600039073CF4E881F981CF01019699838883A1
:100E700010838E819F8101969F838E83E3CFE885C7
:100E8000F985812F0995892BA1F3DACFFA01AA27D9
:100E9000283051F1203181F1E8946F936E7F6E5FBD
:100EA0007F4F8F4F9F4FAF4FB1E03ED0B4E03CD06B
:100EB000670F781F891F9A1FA11D680F791F8A1F4E
:100EC000911DA11D6A0F711D811D911DA11D20D0B5
:100ED00009F468943F912AE0269F11243019305D6F
:100EE0003193DEF6CF010895462F4770405D419360
:100EF000B3E00FD0C9F7F6CF462F4F70405D4A33AD
:100F000018F0495D31FD4052419302D0A9F7EACF74
:100F1000B4E0A6959795879577956795BA95C9F7A3
:100F200000976105710508959B01AC010A2E069496
:100F30005795479537952795BA95C9F7620F731F4F
:100F4000841F951FA01D0895E199FECF9FBB8EBB06
:100F5000E09A99278DB308952F923F924F925F9216
:100F60006F927F928F929F92AF92BF92CF92DF92B9
:100F7000EF92FF920F931F93CF93DF93CDB7DEB71E
:100F8000CA1BDB0B0FB6F894DEBF0FBECDBF0994B2
:100F90002A88398848885F846E847D848C849B8409
:100FA000AA84B984C884DF80EE80FD800C811B8117
:100FB000AA81B981CE0FD11D0FB6F894DEBF0FBE46
:100FC000CDBFED010895EE0FFF1F0590F491E02DC8
:100FD0000994AEE0B0E0EFEEF7E00C94BA070D89AB
:100FE0001E898F89988D26E02C831A83098397FFA9
:100FF00002C080E090E801979E838D83AE01455E3C
:101000005F4F698D7A8DCE0101960E9426054D8134
:101010005E8157FD0AC02F813885421753070CF4B3
:101020009A01020F131FF80110822E96E4E00C942F
:06103000D607F894FFCF83
:101036005001000000007B01DC000901C7013A01F4
:1010460018012C011F111F001F001D151715151F54
:1010560007041F17151D1F151D01011F1F151F173B
:10106600151F1E051E1F150A0E11111F110E1F1525
:0C107600111F0501140814253032580029
:00000001FF
Flash the FW into the panel
You need programmer able to program atmega16 MCUs in DIP40 packages, luckily there is ISP header on the control borad, so I used cheap USBasp to do that.
!!! THE ISP HEADER USE NON-STANDARD PINOUT - DO NOT PLUG STANDARD USBASP CABLE, YOU NEED TO PIN THESE ACCORDING TO SCHEMATIC OTHERWISE USBASP POWER WILL BE SHORTED !!!
- install avrdude
- connect USBasp and panel power supply
- verify that you can communicate with the atmega over USBasp
avrdude -c usbasp -p m16that should say "device signature = 0x1e9403 (probably m16)" - erase the locked atmega16 using
avrdude -c usbasp -p m16 -e - set the fuses (16MHz xtal, 4V BOD, jtag off, preserved EEPROM on chip erase, ISP allowed) using
avrdude -c usbasp -p m16 -U lfuse:w:0x3f:m -U hfuse:w:0xc7:m - set the device address (needs to be in range 0x80-0xFF) using
avrdude -c usbasp -p m16 -U eeprom:w:0x80:mreplace 0x80 with desired address - write and verify the firmware using
avrdude -c usbasp -p m16 -U flash:w:firmware.hex:i - done
- ... the address could be changed using
avrdude -c usbasp -p m16 -U eeprom:w:0x80:min the future without the need of reflashing the main FW again
Show something on the screen
Here are some bash examples (to be easy to understand) how to display something on the screen.
You need RS485 adapter to send data from computer to the module. Alternatively (for testing purposes with short cables) you can use cheap USB-to-Serial adapter connected this way:
- GND from USB-to-Serial to pin 1 on XLR
- TxD from USB-to-Serial to pin 3 on XLR
- put two same resistors (about 1k - 5K) in series, connect one end to Vcc+ (from USB-to-Serial), the other to GND, and the middle of them to pin 2 on XLR
- this way it will work with the differencial bus, but the range (lenght of the cable) and intereference immunity will be significantly reduced
This example shows the "base48" text on screen with address 0x80

#!/bin/bash
PORT="/dev/ttyUSB0"
# Nastavení sériového portu: 115200 baud, 8N1
stty -F "$PORT" 115200 cs8 -cstopb -parenb raw -echo
# Odeslat příkaz 0x80 a grafická data.
# Každý znak má 4 sloupce, mezi znaky je jeden prázdný sloupec.
# Bit 0 = horní LED, bit 6 = spodní LED.
printf '\x80\x7F\x48\x48\x30\x00\x20\x54\x54\x78\x00\x48\x54\x54\x20\x00\x38\x54\x54\x18\x00\x18\x14\x12\x7F\x00\x36\x49\x49\x36\x00' > "$PORT"
This example shows simple clock on screen with address 0x80

#!/bin/bash
PORT="/dev/ttyUSB0"
# Nastavení sériového portu
stty -F "$PORT" 115200 cs8 -cstopb -parenb raw -echo
# Font číslic 3x5
declare -a FONT=(
"111 101 101 101 111" # 0
"010 110 010 010 111" # 1
"111 001 111 100 111" # 2
"111 001 111 001 111" # 3
"101 101 111 001 001" # 4
"111 100 111 001 111" # 5
"111 100 111 101 111" # 6
"111 001 010 010 010" # 7
"111 101 111 101 111" # 8
"111 101 111 001 111" # 9
)
# Vykreslení číslice do sloupcového bufferu.
# Číslice má šířku 3 a výšku 5 LED.
# Začíná na řádku 1, takže je na displeji vystředěná.
draw_digit() {
local digit=$1
local x=$2
local row col bits
local -a rows
read -ra rows <<< "${FONT[$digit]}"
for ((row = 0; row < 5; row++)); do
for ((col = 0; col < 3; col++)); do
if [[ ${rows[$row]:$col:1} == 1 ]]; then
((bits = 1 << (row + 1)))
((columns[x + col] |= bits))
fi
done
done
}
# Nekonečná smyčka hodin
while true; do
# Získat aktuální čas počítače
time=$(date '+%H%M%S')
# Vymazat obrazový buffer
for ((i = 0; i < 30; i++)); do
columns[i]=0
done
# Rozložení HH:MM:SS
draw_digit "${time:0:1}" 2
draw_digit "${time:1:1}" 6
# Dvojtečka
((columns[10] |= 0x14))
draw_digit "${time:2:1}" 12
draw_digit "${time:3:1}" 16
# Dvojtečka
((columns[20] |= 0x14))
draw_digit "${time:4:1}" 22
draw_digit "${time:5:1}" 26
# Sestavit příkaz 0x83 a všech 30 sloupců.
# Data jsou zatím reprezentována jako escape sekvence,
# aby fungovaly i bajty s hodnotou 0x00.
payload='\x80'
for ((i = 0; i < 30; i++)); do
printf -v byte '\\x%02X' "${columns[i]}"
payload+="$byte"
done
# Odeslat celý obraz jedním printf
printf '%b' "$payload" > "$PORT"
# Aktualizace přibližně jednou za sekundu
sleep 1
done
Here is a simple pong game for two players controlled with keys Q, A, P, L

#!/bin/bash
PORT="/dev/ttyUSB0"
WIDTH=30
HEIGHT=7
DELAY=0.05
# Nastavení sériového portu
stty -F "$PORT" 115200 cs8 -cstopb -parenb raw -echo
# Uložení nastavení terminálu a povolení čtení kláves bez Enteru
OLD_STTY=$(stty -g) || exit 1
restore_terminal() {
stty "$OLD_STTY"
}
trap restore_terminal EXIT INT TERM
stty -echo -icanon min 0 time 0
# Počáteční pozice pálek
left_paddle=3
right_paddle=3
# Inicializace hry
new_game() {
ball_x=15
ball_y=3
# Náhodný směr míčku
if ((RANDOM % 2)); then
vx=1
else
vx=-1
fi
# Náhodný vertikální směr
if ((RANDOM % 2)); then
vy=1
else
vy=-1
fi
}
new_game
while true; do
# ---------------------------------------
# Čtení kláves
# ---------------------------------------
while IFS= read -r -s -n 1 -t 0.001 key; do
case "$key" in
q|Q)
((left_paddle > 1)) && ((left_paddle--))
;;
a|A)
((left_paddle < HEIGHT - 2)) && ((left_paddle++))
;;
p|P)
((right_paddle > 1)) && ((right_paddle--))
;;
l|L)
((right_paddle < HEIGHT - 2)) && ((right_paddle++))
;;
$'\003')
exit 0
;;
esac
done
# ---------------------------------------
# Výpočet nové pozice míčku
# ---------------------------------------
next_x=$((ball_x + vx))
next_y=$((ball_y + vy))
# Odraz od horní a dolní hrany
if ((next_y < 0)); then
next_y=1
vy=1
elif ((next_y >= HEIGHT)); then
next_y=$((HEIGHT - 2))
vy=-1
fi
# ---------------------------------------
# Levá pálka
# ---------------------------------------
if ((vx < 0 && next_x <= 0)); then
if ((next_y >= left_paddle - 1 &&
next_y <= left_paddle + 1)); then
# Míček zasáhl levou pálku
next_x=1
vx=1
if ((next_y < left_paddle)); then
vy=-1
elif ((next_y > left_paddle)); then
vy=1
else
# Zásah doprostřed: náhodný vertikální směr
if ((RANDOM % 2)); then
vy=1
else
vy=-1
fi
fi
else
# Minutí levé pálky: okamžitý restart
new_game
continue
fi
fi
# ---------------------------------------
# Pravá pálka
# ---------------------------------------
if ((vx > 0 && next_x >= WIDTH - 1)); then
if ((next_y >= right_paddle - 1 &&
next_y <= right_paddle + 1)); then
# Míček zasáhl pravou pálku
next_x=$((WIDTH - 2))
vx=-1
if ((next_y < right_paddle)); then
vy=-1
elif ((next_y > right_paddle)); then
vy=1
else
if ((RANDOM % 2)); then
vy=1
else
vy=-1
fi
fi
else
# Minutí pravé pálky: okamžitý restart
new_game
continue
fi
fi
# Uložení pozice míčku
ball_x=$next_x
ball_y=$next_y
# ---------------------------------------
# Vykreslení snímku
# ---------------------------------------
for ((x = 0; x < WIDTH; x++)); do
columns[x]=0
done
# Levá pálka
for ((y = left_paddle - 1; y <= left_paddle + 1; y++)); do
((columns[0] |= 1 << y))
done
# Pravá pálka
for ((y = right_paddle - 1; y <= right_paddle + 1; y++)); do
((columns[WIDTH - 1] |= 1 << y))
done
# Míček
((columns[ball_x] |= 1 << ball_y))
# ---------------------------------------
# Odeslání snímku displeji
# ---------------------------------------
payload='\x80'
for ((x = 0; x < WIDTH; x++)); do
printf -v byte '\\x%02X' "${columns[x]}"
payload+="$byte"
done
printf '%b' "$payload" > "$PORT"
sleep "$DELAY"
done
Stay 420, have fun!
