Firmware2/Marlin/src/lcd/extui/dgus_reloaded/DGUSDisplay.cpp
2021-10-02 22:31:15 -05:00

408 lines
11 KiB
C++

/**
* Marlin 3D Printer Firmware
* Copyright (c) 2021 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* DGUS implementation written by coldtobi in 2019 for Marlin */
#include "../../../inc/MarlinConfigPre.h"
#if ENABLED(DGUS_LCD_UI_RELOADED)
#include "DGUSDisplay.h"
#include "config/DGUS_Addr.h"
#include "config/DGUS_Constants.h"
#include "definition/DGUS_VPList.h"
#include "../ui_api.h"
#include "../../../gcode/gcode.h"
long map_precise(float x, long in_min, long in_max, long out_min, long out_max) {
return LROUND((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min);
}
uint8_t DGUSDisplay::gui_version = 0;
uint8_t DGUSDisplay::os_version = 0;
uint8_t DGUSDisplay::volume = 255;
uint8_t DGUSDisplay::brightness = 100;
DGUSDisplay::rx_datagram_state_t DGUSDisplay::rx_datagram_state = DGUS_IDLE;
uint8_t DGUSDisplay::rx_datagram_len = 0;
bool DGUSDisplay::initialized = false;
void DGUSDisplay::Loop() {
ProcessRx();
}
void DGUSDisplay::Init() {
LCD_SERIAL.begin(LCD_BAUDRATE);
Read(DGUS_VERSION, 1);
}
void DGUSDisplay::Read(uint16_t addr, uint8_t size) {
WriteHeader(addr, DGUS_READVAR, size);
LCD_SERIAL.write(size);
}
void DGUSDisplay::Write(uint16_t addr, const void* data_ptr, uint8_t size) {
if (!data_ptr) return;
WriteHeader(addr, DGUS_WRITEVAR, size);
const char* data = static_cast<const char*>(data_ptr);
while (size--) {
LCD_SERIAL.write(*data++);
}
}
void DGUSDisplay::WriteString(uint16_t addr, const void* data_ptr, uint8_t size, bool left, bool right, bool use_space) {
if (!data_ptr) return;
WriteHeader(addr, DGUS_WRITEVAR, size);
const char* data = static_cast<const char*>(data_ptr);
size_t len = strlen(data);
uint8_t left_spaces = 0;
uint8_t right_spaces = 0;
if (len < size) {
if (!len) {
right_spaces = size;
}
else if ((left && right) || (!left && !right)) {
left_spaces = (size - len) / 2;
right_spaces = size - len - left_spaces;
}
else if (left) {
right_spaces = size - len;
}
else {
left_spaces = size - len;
}
}
else {
len = size;
}
while (left_spaces--) {
LCD_SERIAL.write(' ');
}
while (len--) {
LCD_SERIAL.write(*data++);
}
while (right_spaces--) {
LCD_SERIAL.write(use_space ? ' ' : '\0');
}
}
void DGUSDisplay::WriteStringPGM(uint16_t addr, const void* data_ptr, uint8_t size, bool left, bool right, bool use_space) {
if (!data_ptr) return;
WriteHeader(addr, DGUS_WRITEVAR, size);
const char* data = static_cast<const char*>(data_ptr);
size_t len = strlen_P(data);
uint8_t left_spaces = 0;
uint8_t right_spaces = 0;
if (len < size) {
if (!len) {
right_spaces = size;
}
else if ((left && right) || (!left && !right)) {
left_spaces = (size - len) / 2;
right_spaces = size - len - left_spaces;
}
else if (left) {
right_spaces = size - len;
}
else {
left_spaces = size - len;
}
}
else {
len = size;
}
while (left_spaces--) {
LCD_SERIAL.write(' ');
}
while (len--) {
LCD_SERIAL.write(pgm_read_byte(data++));
}
while (right_spaces--) {
LCD_SERIAL.write(use_space ? ' ' : '\0');
}
}
void DGUSDisplay::SwitchScreen(DGUS_Screen screen) {
DEBUG_ECHOLNPAIR("SwitchScreen ", (uint8_t)screen);
const uint8_t command[] = { 0x5A, 0x01, 0x00, (uint8_t)screen };
Write(0x84, command, sizeof(command));
}
void DGUSDisplay::PlaySound(uint8_t start, uint8_t len, uint8_t volume) {
if (volume == 0) volume = DGUSDisplay::volume;
if (volume == 0) return;
DEBUG_ECHOLNPAIR("PlaySound ", start, ":", len, "\nVolume ", volume);
const uint8_t command[] = { start, len, volume, 0x00 };
Write(0xA0, command, sizeof(command));
}
void DGUSDisplay::EnableControl(DGUS_Screen screen, DGUS_ControlType type, DGUS_Control control) {
DEBUG_ECHOLNPAIR("EnableControl ", (uint8_t)control, "\nScreen ", (uint8_t)screen, "\nType ", (uint8_t)type);
const uint8_t command[] = { 0x5A, 0xA5, 0x00, (uint8_t)screen, (uint8_t)control, type, 0x00, 0x01 };
Write(0xB0, command, sizeof(command));
FlushTx();
delay(50);
}
void DGUSDisplay::DisableControl(DGUS_Screen screen, DGUS_ControlType type, DGUS_Control control) {
DEBUG_ECHOLNPAIR("DisableControl ", (uint8_t)control, "\nScreen ", (uint8_t)screen, "\nType ", (uint8_t)type);
const uint8_t command[] = { 0x5A, 0xA5, 0x00, (uint8_t)screen, (uint8_t)control, type, 0x00, 0x00 };
Write(0xB0, command, sizeof(command));
FlushTx();
delay(50);
}
uint8_t DGUSDisplay::GetBrightness() {
return brightness;
}
uint8_t DGUSDisplay::GetVolume() {
return map_precise(volume, 0, 255, 0, 100);
}
void DGUSDisplay::SetBrightness(uint8_t new_brightness) {
brightness = constrain(new_brightness, 0, 100);
new_brightness = map_precise(brightness, 0, 100, 5, 100);
DEBUG_ECHOLNPAIR("SetBrightness ", new_brightness);
const uint8_t command[] = { new_brightness, new_brightness };
Write(0x82, command, sizeof(command));
}
void DGUSDisplay::SetVolume(uint8_t new_volume) {
volume = map_precise(constrain(new_volume, 0, 100), 0, 100, 0, 255);
DEBUG_ECHOLNPAIR("SetVolume ", volume);
const uint8_t command[] = { volume, 0x00 };
Write(0xA1, command, sizeof(command));
}
void DGUSDisplay::ProcessRx() {
#if ENABLED(LCD_SERIAL_STATS_RX_BUFFER_OVERRUNS)
if (!LCD_SERIAL.available() && LCD_SERIAL.buffer_overruns()) {
// Overrun, but reset the flag only when the buffer is empty
// We want to extract as many as valid datagrams possible...
DEBUG_ECHOPGM("OVFL");
rx_datagram_state = DGUS_IDLE;
//LCD_SERIAL.reset_rx_overun();
LCD_SERIAL.flush();
}
#endif
uint8_t receivedbyte;
while (LCD_SERIAL.available()) {
switch (rx_datagram_state) {
case DGUS_IDLE: // Waiting for the first header byte
receivedbyte = LCD_SERIAL.read();
DEBUG_ECHOPAIR("< ", receivedbyte);
if (DGUS_HEADER1 == receivedbyte) rx_datagram_state = DGUS_HEADER1_SEEN;
break;
case DGUS_HEADER1_SEEN: // Waiting for the second header byte
receivedbyte = LCD_SERIAL.read();
DEBUG_ECHOPAIR(" ", receivedbyte);
rx_datagram_state = (DGUS_HEADER2 == receivedbyte) ? DGUS_HEADER2_SEEN : DGUS_IDLE;
break;
case DGUS_HEADER2_SEEN: // Waiting for the length byte
rx_datagram_len = LCD_SERIAL.read();
DEBUG_ECHOPAIR(" (", rx_datagram_len, ") ");
// Telegram min len is 3 (command and one word of payload)
rx_datagram_state = WITHIN(rx_datagram_len, 3, DGUS_RX_BUFFER_SIZE) ? DGUS_WAIT_TELEGRAM : DGUS_IDLE;
break;
case DGUS_WAIT_TELEGRAM: // wait for complete datagram to arrive.
if (LCD_SERIAL.available() < rx_datagram_len) return;
initialized = true; // We've talked to it, so we defined it as initialized.
uint8_t command = LCD_SERIAL.read();
DEBUG_ECHOPAIR("# ", command);
uint8_t readlen = rx_datagram_len - 1; // command is part of len.
unsigned char tmp[rx_datagram_len - 1];
unsigned char *ptmp = tmp;
while (readlen--) {
receivedbyte = LCD_SERIAL.read();
DEBUG_ECHOPAIR(" ", receivedbyte);
*ptmp++ = receivedbyte;
}
DEBUG_ECHOPGM(" # ");
// mostly we'll get this: 5A A5 03 82 4F 4B -- ACK on 0x82, so discard it.
if (command == DGUS_WRITEVAR && 'O' == tmp[0] && 'K' == tmp[1]) {
DEBUG_ECHOLNPGM(">");
rx_datagram_state = DGUS_IDLE;
break;
}
/* AutoUpload, (and answer to) Command 0x83 :
| tmp[0 1 2 3 4 ... ]
| Example 5A A5 06 83 20 01 01 78 01 ……
| / / | | \ / | \ \
| Header | | | | \_____\_ DATA (Words!)
| DatagramLen / VPAdr |
| Command DataLen (in Words) */
if (command == DGUS_READVAR) {
const uint16_t addr = tmp[0] << 8 | tmp[1];
const uint8_t dlen = tmp[2] << 1; // Convert to Bytes. (Display works with words)
DEBUG_ECHOPAIR("addr=", addr, " dlen=", dlen, "> ");
if (addr == DGUS_VERSION && dlen == 2) {
DEBUG_ECHOLNPGM("VERSIONS");
gui_version = tmp[3];
os_version = tmp[4];
rx_datagram_state = DGUS_IDLE;
break;
}
DGUS_VP vp;
if (!DGUS_PopulateVP((DGUS_Addr)addr, &vp)) {
DEBUG_ECHOLNPGM("VP not found");
rx_datagram_state = DGUS_IDLE;
break;
}
if (!vp.rx_handler) {
DEBUG_ECHOLNPGM("VP found, no handler.");
rx_datagram_state = DGUS_IDLE;
break;
}
gcode.reset_stepper_timeout();
if (!vp.size) {
DEBUG_ECHOLN();
vp.rx_handler(vp, nullptr);
rx_datagram_state = DGUS_IDLE;
break;
}
if (vp.flags & VPFLAG_RXSTRING) {
unsigned char buffer[vp.size];
memset(buffer, 0, vp.size);
for (uint8_t i = 0; i < dlen; i++) {
if (i >= vp.size) {
break;
}
if (i + 1 < dlen && tmp[i + 3] == 0xFF && tmp[i + 4] == 0xFF) {
break;
}
buffer[i] = tmp[i + 3];
}
DEBUG_ECHOLN();
vp.rx_handler(vp, buffer);
rx_datagram_state = DGUS_IDLE;
break;
}
if (dlen != vp.size) {
DEBUG_ECHOLNPGM("VP found, size mismatch.");
rx_datagram_state = DGUS_IDLE;
break;
}
DEBUG_ECHOLN();
vp.rx_handler(vp, &tmp[3]);
rx_datagram_state = DGUS_IDLE;
break;
}
DEBUG_ECHOLNPGM(">");
rx_datagram_state = DGUS_IDLE;
break;
}
}
}
size_t DGUSDisplay::GetFreeTxBuffer() {
#ifdef LCD_SERIAL_GET_TX_BUFFER_FREE
return LCD_SERIAL_GET_TX_BUFFER_FREE();
#else
return SIZE_MAX;
#endif
}
void DGUSDisplay::FlushTx() {
#ifdef ARDUINO_ARCH_STM32
LCD_SERIAL.flush();
#else
LCD_SERIAL.flushTX();
#endif
}
void DGUSDisplay::WriteHeader(uint16_t addr, uint8_t command, uint8_t len) {
LCD_SERIAL.write(DGUS_HEADER1);
LCD_SERIAL.write(DGUS_HEADER2);
LCD_SERIAL.write(len + 3);
LCD_SERIAL.write(command);
LCD_SERIAL.write(addr >> 8);
LCD_SERIAL.write(addr & 0xFF);
}
bool DGUS_PopulateVP(const DGUS_Addr addr, DGUS_VP * const buffer) {
const DGUS_VP *ret = vp_list;
do {
const uint16_t *paddr = (uint16_t *)(&ret->addr);
const uint16_t addrcheck = pgm_read_word(paddr);
if (addrcheck == 0) break;
if ((DGUS_Addr)addrcheck == addr) {
memcpy_P(buffer, ret, sizeof(*ret));
return true;
}
} while (++ret);
DEBUG_ECHOLNPAIR("VP not found: ", (uint16_t)addr);
return false;
}
#endif // DGUS_LCD_UI_RELOADED