1
0
mirror of synced 2024-11-23 23:50:56 +01:00

添加 vfd 相关文件

This commit is contained in:
Sucareto 2024-10-20 15:26:29 +08:00
parent 588df7567a
commit 579a17c5c5
3 changed files with 591 additions and 0 deletions

140
doc/vfd_log.txt Normal file
View File

@ -0,0 +1,140 @@
1b 0b // boot
1b 21 01 // PowerOn
1b 32 02 // Set Language
1b 0c // Clear
1b 52 // Stop Scroll
1b 40 00 00 00 00 9f 02 // Set Option
1b 41 00 // Set Speed
1b 50 1e 83 4a 81 5b 83 68 82 f0 82 a9 82 b4 82 b5 82 c4 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // カードをかざしてください
1b 51 // Start Scroll
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 20 83 4a 81 5b 83 68 82 f0 97 a3 82 b3 82 c8 82 a2 82 c5 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // カードを離さないでください
1b 51
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 1c 82 b5 82 ce 82 e7 82 ad 82 a8 91 d2 82 bf 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // しばらくお待ちください
1b 51
1b 0c
1b 52
1b 30 00 00 00 8f 80 94 f5 92 86 82 c5 82 b7 81 63 // 準備中です…
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 1c 82 b5 82 ce 82 e7 82 ad 82 a8 91 d2 82 bf 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // しばらくお待ちください
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b9 82 f1 // 電子決済できません
1b 0c
1b 52
1b 40 00 00 02 00 9f 02
1b 41 00
1b 50 2e 6e 61 6e 61 63 6f 20 20 20 20 20 20 8c f0 92 ca 8c 6e 20 20 20 20 20 20 57 41 4f 4e 20 20 20 20 20 20 50 41 53 45 4c 49 20 20 20 20 20 20 // nanaco 交通系 WAON PASELI
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b9 82 f1 // 電子決済できません
1b 0c
1b 52
1b 40 00 00 02 00 9f 02
1b 41 00
1b 50 2e 6e 61 6e 61 63 6f 20 20 20 20 20 20 8c f0 92 ca 8c 6e 20 20 20 20 20 20 57 41 4f 4e 20 20 20 20 20 20 50 41 53 45 4c 49 20 20 20 20 20 20 // nanaco 交通系 WAON PASELI
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 2c 50 41 53 45 4c 49 8e 78 95 a5 0d 0a 83 4a 81 5b 83 68 82 f0 83 5e 83 62 83 60 82 b5 82 c4 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // PASELI支払 カードをタッチしてください
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 02 00 9f 02
1b 41 00
1b 50 2e 6e 61 6e 61 63 6f 20 20 20 20 20 20 8c f0 92 ca 8c 6e 20 20 20 20 20 20 57 41 4f 4e 20 20 20 20 20 20 50 41 53 45 4c 49 20 20 20 20 20 20 // nanaco 交通系 WAON PASELI
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 2c 50 41 53 45 4c 49 8e 78 95 a5 0d 0a 83 4a 81 5b 83 68 82 f0 83 5e 83 62 83 60 82 b5 82 c4 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // PASELI支払 カードをタッチしてください
1b 30 00 00 02 6e 61 6e 61 63 6f 8e 78 95 a5 20 5c 31 30 30 nanaco支払 \100
1b 51
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 2c 50 41 53 45 4c 49 8e 78 95 a5 0d 0a 83 4a 81 5b 83 68 82 f0 83 5e 83 62 83 60 82 b5 82 c4 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // PASELI支払 カードをタッチしてください
1b 30 00 00 02 6e 61 6e 61 63 6f 8e 63 8d 82 20 5c 31 30 31 nanaco残高 \101
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 02 00 9f 02
1b 41 00
1b 50 2e 6e 61 6e 61 63 6f 20 20 20 20 20 20 8c f0 92 ca 8c 6e 20 20 20 20 20 20 57 41 4f 4e 20 20 20 20 20 20 50 41 53 45 4c 49 20 20 20 20 20 20 // nanaco 交通系 WAON PASELI
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 00 00 9f 02
1b 41 00
1b 50 2c 50 41 53 45 4c 49 8e 78 95 a5 0d 0a 83 4a 81 5b 83 68 82 f0 83 5e 83 62 83 60 82 b5 82 c4 82 ad 82 be 82 b3 82 a2 20 20 20 20 20 20 // PASELI支払 カードをタッチしてください
1b 51
1b 0c
1b 52
1b 0c
1b 52
1b 40 00 00 02 00 9f 02
1b 41 00
1b 50 2e 6e 61 6e 61 63 6f 20 20 20 20 20 20 8c f0 92 ca 8c 6e 20 20 20 20 20 20 57 41 4f 4e 20 20 20 20 20 20 50 41 53 45 4c 49 20 20 20 20 20 20 // nanaco 交通系 WAON PASELI
1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
1b 51
1b 0c
1b 52

195
tools/segatools-edit/vfd.c Normal file
View File

@ -0,0 +1,195 @@
/* This is some sort of LCD display found on various cabinets. It is driven
directly by amdaemon, and it has something to do with displaying the status
of electronic payments.
Part number in schematics is "VFD GP1232A02A FUTABA".
Little else about this board is known. Black-holing the RS232 comms that it
receives seems to be sufficient for the time being. */
#include <windows.h>
#include <assert.h>
#include <stdint.h>
#include "board/vfd.h"
#include "hook/iohook.h"
#include "hooklib/uart.h"
#include "util/dprintf.h"
#include "util/dump.h"
static HRESULT vfd_handle_irp(struct irp *irp);
static struct uart vfd_uart;
static uint8_t vfd_written[512];
static uint8_t vfd_readable[512];
static uint8_t vfd_enable, vfd_redirect;
static HANDLE com_device;
UINT codepage;
HRESULT vfd_hook_init(unsigned int port_no)
{
vfd_enable = GetPrivateProfileIntW(L"vfd", L"enable", 1, L".\\segatools.ini");
vfd_redirect = GetPrivateProfileIntW(L"vfd", L"redirect", 0, L".\\segatools.ini");
if (!vfd_enable)
{
return S_FALSE;
}
char com_name[10];
// sprintf(com_name, "\\\\.\\COM%d", port_no);
// com_device = CreateFile(com_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
// if (com_device != INVALID_HANDLE_VALUE)
// {
// CloseHandle(com_device);
// if (!vfd_redirect)
// {
// return S_FALSE;
// }
// }
uart_init(&vfd_uart, port_no);
vfd_uart.written.bytes = vfd_written;
vfd_uart.written.nbytes = sizeof(vfd_written);
vfd_uart.readable.bytes = vfd_readable;
vfd_uart.readable.nbytes = sizeof(vfd_readable);
if (vfd_redirect) // 将 vfd 串口数据重写到另一个 com用于调试
{
dprintf("VFD: redirect enable, target COM%d.\n", vfd_redirect);
sprintf(com_name, "\\\\.\\COM%d", vfd_redirect);
com_device = CreateFile(com_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (com_device != INVALID_HANDLE_VALUE)
{
dprintf("VFD: CreateFile OK.\n");
DCB dcbSerialParams = {0};
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
GetCommState(com_device, &dcbSerialParams);
dcbSerialParams.BaudRate = 115200;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
SetCommState(com_device, &dcbSerialParams);
// COMMTIMEOUTS timeouts = {0};
// timeouts.ReadIntervalTimeout = 1;
// timeouts.ReadTotalTimeoutConstant = 1;
// timeouts.ReadTotalTimeoutMultiplier = 1;
// timeouts.WriteTotalTimeoutConstant = 1;
// timeouts.WriteTotalTimeoutMultiplier = 1;
// SetCommTimeouts(com_device, &timeouts);
EscapeCommFunction(com_device, SETDTR);
EscapeCommFunction(com_device, SETRTS);
}
else
{
dprintf("VFD: CreateFile error.\n");
}
}
codepage = GetACP(); // 获取当前系统的 Code page用于后续转码显示
dprintf("VFD: hook enabled.\n");
return iohook_push_handler(vfd_handle_irp);
}
static HRESULT vfd_handle_irp(struct irp *irp)
{
HRESULT hr;
assert(irp != NULL);
if (!uart_match_irp(&vfd_uart, irp))
{
return iohook_invoke_next(irp);
}
hr = uart_handle_irp(&vfd_uart, irp);
if (FAILED(hr) || irp->op != IRP_OP_WRITE)
{
return hr;
}
if (vfd_redirect && com_device != INVALID_HANDLE_VALUE)
{
DWORD bytesWritten;
if (WriteFile(com_device, vfd_uart.written.bytes, vfd_uart.written.pos, &bytesWritten, NULL))
{
dprintf("VFD: WriteFile %lu.\n", bytesWritten);
}
}
else
{
uint8_t cmd = 0;
uint8_t str_1[512]; // 两行数据的缓冲区
uint8_t str_2[512];
uint8_t str_1_len = 0;
uint8_t str_2_len = 0;
for (size_t i = 0; i < vfd_uart.written.pos; i++)
{
if (vfd_uart.written.bytes[i] == 0x1B)
{
i++;
cmd = vfd_uart.written.bytes[i];
if (cmd == 0x30)
{
i += 3;
}
else if (cmd == 0x50)
{
i++;
}
continue;
}
if (cmd == 0x30)
{
str_1[str_1_len++] = vfd_uart.written.bytes[i];
}
else if (cmd == 0x50)
{
str_2[str_2_len++] = vfd_uart.written.bytes[i];
}
}
if (str_1_len)
{
str_1[str_1_len++] = '\0';
if (codepage != 932)
{ // 如果非日文系统,则转码后输出 https://en.wikipedia.org/wiki/Code_page_932_(Microsoft_Windows)
WCHAR buffer[512];
MultiByteToWideChar(932, 0, (LPCSTR)str_1, str_1_len, buffer, str_1_len);
char str_recode[str_1_len * 3];
WideCharToMultiByte(codepage, 0, buffer, str_1_len, str_recode, str_1_len * 3, NULL, NULL);
dprintf("VFD: %s\n", str_recode);
}
else
{
dprintf("VFD: %s\n", str_1);
}
}
if (str_2_len)
{
str_2[str_2_len++] = '\0';
if (codepage != 932)
{
WCHAR buffer[512];
MultiByteToWideChar(932, 0, (LPCSTR)str_2, str_2_len, buffer, str_2_len);
char str_recode[str_2_len * 3];
WideCharToMultiByte(codepage, 0, buffer, str_2_len, str_recode, str_2_len * 3, NULL, NULL);
dprintf("VFD: %s\n", str_recode);
}
else
{
dprintf("VFD: %s\n", str_2);
}
}
}
vfd_uart.written.pos = 0;
return hr;
}

256
tools/vfd_test/vfd_test.ino Normal file
View File

@ -0,0 +1,256 @@
// 本程序已于 2023/07/09 停止开发
// 展示信息的小屏幕,波特率 115200只会出现在支持 電子決済 的框体,设备仅接收数据,不需要回复
// 在启用 emoney 时,显示支持的卡片种类和付款情况
// com 序号在 config_common.json > emoney > display_port 定义
// https://github.com/djhackersdev/segatools/blob/master/board/vfd.c
// vfd 串口数据没有固定格式,没有长度位
// 测试程序显示文字用的是 u8g2 库,默认用的是 utf8 编码,无法直接使用 vfd 串口的 shift-jis 编码数据
// 字库相关的信息:
// https://github.com/olikraus/u8g2/discussions/1508
// https://github.com/larryli/u8g2_wqy
// https://github.com/breakstring/u8g2_fontmaker
// https://github.com/createskyblue/Easy-u8g2-font-generate-tools/blob/master/main.py
#define SerialDevice Serial
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
static uint8_t state = 0;
static char textBuffer[];
static uint8_t text_len = 0;
// https://r12a.github.io/app-encodings/
// https://www.mgo-tec.com/blog-entry-utf8-sjis-wroom-arduino-lib.html
// https://github.com/mgo-tec/UTF8SJIS_for_ESP8266/blob/master/src/UTF8toSJIS.cpp
// https://github.com/mgo-tec/ESP32_SD_UTF8toSJIS
void setup() {
SerialDevice.begin(115200);
SerialDevice.setTimeout(0);
Wire.setClock(800000);
u8g2.begin();
u8g2.setFont(u8g2_font_6x12_mf);
u8g2.clearBuffer();
u8g2.sendBuffer();
}
void loop() {
while (SerialDevice.available()) {
uint8_t r = SerialDevice.read();
if (r == 0x1B) {
r = SerialDevice.read();
state = 0;
continue;
}
if (!state) {
state = r;
continue;
}
switch (state) {
case 0x0B: // 仅开机出现过
// 1b 0b
break;
case 0x0C:
// 1b 0c
text_len = 0;
break;
case 0x21: // 仅开机出现过,在 0b 后面
// 1b 21 01
break;
case 0x30:
textBuffer[text_len++] = r;
continue;
// 1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b7 // 電子決済できます
// 1b 30 00 00 00 93 64 8e 71 8c 88 8d cf 82 c5 82 ab 82 dc 82 b9 82 f1 // 電子決済できません
// 1b 30 00 00 02 6e 61 6e 61 63 6f 8e 78 95 a5 20 5c 35 30 30 // anaco支払 \500
case 0x32: // 仅开机出现过,在 21 后面
// 1b 32 02
break;
case 0x40:
// 1b 40 00 00 (00|02) 00 9f 02
uint8_t prm1 = SerialDevice.read();
uint8_t prm2 = SerialDevice.read();
uint8_t Line = SerialDevice.read();
uint8_t BoxSize_x = SerialDevice.read();
uint8_t BoxSize_y = SerialDevice.read();
// 缺少一位
break;
case 0x41: // 在 40 后面speed 只发现是 00用途未知
// 1b 41 00
uint8_t speed = SerialDevice.read();
break;
case 0x50: // 长度后面是字符内容
uint8_t len = SerialDevice.read();
textBuffer[text_len++] = r;
continue;
case 0x51: // 仅在有输出时,作为最后一条指令发送
// 1b 51
break;
case 0x52: // 跟在 0c 后面
// 1b 52
break;
}
}
}
/*
// oled 显示测试
#include <Wire.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);
// https://github.com/olikraus/u8g2/wiki/fntlistallplain
// https://github.com/olikraus/u8g2/wiki/u8g2reference
char text1[] = {
0xe9, 0x9b, 0xbb,
0xe5, 0xad, 0x90,
0xe6, 0xb1, 0xba,
0xe6, 0xb8, 0x88,
0xe3, 0x81, 0xa7,
0xe3, 0x81, 0x8d,
0xe3, 0x81, 0xbe,
0xe3, 0x81, 0x99,
0
};
char text2[] = {
0x6e, 0x61, 0x6e, 0x61, 0x63, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe4, 0xba, 0xa4, 0xe9, 0x80, 0x9a, 0xe7, 0xb3, 0xbb, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x57, 0x41, 0x4f, 0x4e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x50, 0x41, 0x53, 0x45, 0x4c, 0x49, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0
};
u8g2_uint_t titleW, msgW, DisplayW; // 字符、屏幕宽度
u8g2_uint_t title_offset, msg_offset; // 字符的偏移位置
bool is_display = false;
uint8_t state, title_len, msg_len;
char title_buffer[256];
char msg_buffer[256];
void setup() {
SerialDevice.begin(115200);
SerialDevice.setTimeout(0);
Wire.setClock(800000);
u8g2.begin();
u8g2.setFont(u8g2_font_unifont_t_japanese3);
u8g2.enableUTF8Print();
Serial.begin(115200);
DisplayW = u8g2.getDisplayWidth();
// Serial.println(titleW);
// Serial.println(msgW);
// Serial.println(DisplayW);
}
void displayTask() {
if (!is_display) {
return;
}
u8g2.clearBuffer();
if (titleW) {
// SerialDevice.println("titleW");
if (titleW < DisplayW) {
u8g2.drawUTF8(0, 14, title_buffer);
// u8g2.setCursor(0, 14);
// u8g2.print(text1);
} else {
u8g2_uint_t x = title_offset;
do {
u8g2.drawUTF8(x, 14, title_buffer);
x += titleW;
} while (x < DisplayW);
title_offset -= 1;
if ((u8g2_uint_t)title_offset < (u8g2_uint_t)-titleW) {
title_offset = 0;
}
}
}
if (msgW) {
// SerialDevice.println("msgW");
if (msgW < DisplayW) {
u8g2.drawUTF8(0, 30, msg_buffer);
} else {
u8g2_uint_t x = msg_offset;
do {
u8g2.drawUTF8(x, 30, msg_buffer);
x += msgW;
} while (x < DisplayW);
msg_offset -= 1;
if ((u8g2_uint_t)msg_offset < (u8g2_uint_t)-msgW) {
msg_offset = 0;
}
}
}
u8g2.sendBuffer();
// delay(500);
}
void loop() {
msg_len = 0;
// memset(title_buffer, 0, 256);
// memset(msg_buffer, 0, 256);
// bool pre_display = false;
while (SerialDevice.available()) {
msg_buffer[msg_len++] = SerialDevice.read();
// uint8_t r = SerialDevice.read();
// if (r == 0x1B) {
// state = 0;
// continue;
// }
// if (!state) {
// state = SerialDevice.read();
// }
// switch (state) {
// case 0x0C:
// title_len = 0;
// msg_len = 0;
// titleW = 0;
// msgW = 0;
// is_display = false;
// // memset(title_buffer, 0, 256);
// // memset(msg_buffer, 0, 256);
// u8g2.clearBuffer();
// u8g2.sendBuffer();
// SerialDevice.println("clear");
// break;
// case 0x30:
// title_buffer[title_len++] = r;
// pre_display = true;
// continue;
// case 0x50:
// msg_buffer[msg_len++] = r;
// pre_display = true;
// continue;
// default:
// break;
// }
}
// if (pre_display) {
// if (title_len) {
// title_buffer[title_len++] = 0;
// titleW = u8g2.getUTF8Width(title_buffer);
// title_offset = 0;
// is_display = true;
// SerialDevice.println("pre_display title_len");
// }
if (msg_len) {
msg_buffer[msg_len++] = 0;
msgW = u8g2.getUTF8Width(msg_buffer);
SerialDevice.println(msgW);
msg_offset = 0;
is_display = true;
SerialDevice.println("pre_display msg_len");
}
// pre_display = false;
// }
displayTask();
}
*/