From b87e2f291df05073d192fc1ba71c9b6de3cc6793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=83=D0=B7=D0=BD=D0=B5=D1=86=D0=BE=D0=B2=20=D0=9D?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B9?= Date: Mon, 4 May 2026 21:49:51 +0300 Subject: [PATCH 1/2] Add Keyboard I/O peripheral with tests and example --- examples/C/keyboardLedMatrix.c | 110 ++++++++++++++++++++ examples/examples.qrc | 1 + src/io/iokeyboard.cpp | 177 +++++++++++++++++++++++++++++++++ src/io/iokeyboard.h | 58 +++++++++++ src/io/ioregistry.h | 9 +- test/CMakeLists.txt | 1 + test/tst_io_keyboard.cpp | 96 ++++++++++++++++++ 7 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 examples/C/keyboardLedMatrix.c create mode 100644 src/io/iokeyboard.cpp create mode 100644 src/io/iokeyboard.h create mode 100644 test/tst_io_keyboard.cpp diff --git a/examples/C/keyboardLedMatrix.c b/examples/C/keyboardLedMatrix.c new file mode 100644 index 000000000..607691830 --- /dev/null +++ b/examples/C/keyboardLedMatrix.c @@ -0,0 +1,110 @@ +#include "ripes_system.h" + +/* + * keyboardLedMatrix.c + * Reads characters from the keyboard peripheral and displays the most recent + * one in the center of the LED matrix using a 5x7 bitmap font. + */ + +#define MATRIX_W LED_MATRIX_0_WIDTH +#define MATRIX_H LED_MATRIX_0_HEIGHT +#define CHAR_W 5 +#define CHAR_H 7 +#define COLOR_ON 0x00FF00 +#define COLOR_OFF 0x000000 + +// 5x7 bitmap font for 0-9 and a-z (case-insensitive). +static const unsigned char font[36][CHAR_H] = { + {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, // 0 + {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1 + {0x0E, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1F}, // 2 + {0x1F, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0E}, // 3 + {0x11, 0x11, 0x11, 0x1F, 0x01, 0x01, 0x01}, // 4 + {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E}, // 5 + {0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E}, // 6 + {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10}, // 7 + {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, // 8 + {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}, // 9 + {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x00}, // a + {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // b + {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}, // c + {0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E}, // d + {0x1F, 0x10, 0x1E, 0x10, 0x10, 0x10, 0x1F}, // e + {0x1F, 0x10, 0x1E, 0x10, 0x10, 0x10, 0x10}, // f + {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F}, // g + {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, // h + {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, // i + {0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0E}, // j + {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}, // k + {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}, // l + {0x11, 0x1B, 0x15, 0x11, 0x11, 0x11, 0x11}, // m + {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11}, // n + {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // o + {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, // p + {0x0E, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x01}, // q + {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11}, // r + {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E}, // s + {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, // t + {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, // u + {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, // v + {0x11, 0x11, 0x15, 0x15, 0x15, 0x15, 0x0A}, // w + {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}, // x + {0x11, 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04}, // y + {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F} // z +}; + +static inline void set_pixel(volatile unsigned int *led, int x, int y, unsigned int color) { + if (x >= 0 && x < MATRIX_W && y >= 0 && y < MATRIX_H) + *(led + y * MATRIX_W + x) = color; +} + +static void draw_char(volatile unsigned int *led, int x, int y, int idx, unsigned int color) { + const unsigned char *bitmap = font[idx]; + for (int dy = 0; dy < CHAR_H; dy++) { + unsigned char row = bitmap[dy]; + for (int dx = 0; dx < CHAR_W; dx++) + if (row & (1 << (CHAR_W - 1 - dx))) + set_pixel(led, x + dx, y + dy, color); + } +} + +static void clear_area(volatile unsigned int *led, int x, int y) { + for (int dy = 0; dy < CHAR_H; dy++) + for (int dx = 0; dx < CHAR_W; dx++) + set_pixel(led, x + dx, y + dy, COLOR_OFF); +} + +static int char_to_index(unsigned char ch) { + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'A' && ch <= 'Z') return 10 + (ch - 'A'); + if (ch >= 'a' && ch <= 'z') return 10 + (ch - 'a'); + return -1; +} + +int main() { + volatile unsigned int *led = (volatile unsigned int *)LED_MATRIX_0_BASE; + volatile unsigned int *kbd = (volatile unsigned int *)KEYBOARD_0_BASE; + + const int center_x = (MATRIX_W - CHAR_W) / 2; + const int center_y = (MATRIX_H - CHAR_H) / 2; + int prev_idx = -1; + + for (int i = 0; i < MATRIX_W * MATRIX_H; i++) + *(led + i) = COLOR_OFF; + + while (1) { + if (*(kbd + 1) == 0) + continue; + + unsigned char key = (unsigned char)(*kbd); + int idx = char_to_index(key); + if (idx < 0) + continue; + + if (prev_idx >= 0) + clear_area(led, center_x, center_y); + draw_char(led, center_x, center_y, idx, COLOR_ON); + prev_idx = idx; + } + return 0; +} diff --git a/examples/examples.qrc b/examples/examples.qrc index 1a76a3512..60c635d2f 100644 --- a/examples/examples.qrc +++ b/examples/examples.qrc @@ -8,6 +8,7 @@ assembly/consolePrinting.s C/leds.c C/switchesAndLeds.c + C/keyboardLedMatrix.c assembly/leds.s ELF/RanPi-RV32 ELF/RanPi-RV64 diff --git a/src/io/iokeyboard.cpp b/src/io/iokeyboard.cpp new file mode 100644 index 000000000..8c3300f4c --- /dev/null +++ b/src/io/iokeyboard.cpp @@ -0,0 +1,177 @@ +#include "iokeyboard.h" +#include "ioregistry.h" + +#include +#include +#include +#include +#include +#include + +namespace Ripes { + +IOKeyboard::IOKeyboard(QWidget *parent) : IOBase(IOType::KEYBOARD, parent) { + m_parameters[BUFSIZE] = IOParam(BUFSIZE, "Buffer size", 16, true, 1, 256); + + setFocusPolicy(Qt::StrongFocus); + buildLayout(); + refreshRegMap(); +} + +QString IOKeyboard::description() const { + QStringList desc; + desc << "Memory-mapped keyboard with configurable FIFO buffer."; + desc << "KEY_DATA (offset 0x00, R): dequeue next ASCII code (0 if empty)."; + desc << "KEY_STATUS (offset 0x04, R/W): read returns buffer count, " + "write non-zero to clear."; + return desc.join('\n'); +} + +void IOKeyboard::buildLayout() { + auto *root = new QVBoxLayout(this); + root->setSpacing(4); + root->setContentsMargins(6, 6, 6, 6); + + auto addKeyButton = [&](QHBoxLayout *row, char ch, int w = 32) { + auto *btn = new QPushButton(QString(QChar(ch)), this); + btn->setFixedSize(w, 32); + btn->setFocusPolicy(Qt::NoFocus); + const uint8_t ascii = static_cast(ch); + connect(btn, &QPushButton::clicked, this, + [this, ascii]() { enqueueKey(ascii); }); + row->addWidget(btn); + }; + + auto *numRow = new QHBoxLayout(); + numRow->setAlignment(Qt::AlignCenter); + for (int i = 1; i <= 10; ++i) + addKeyButton(numRow, '0' + (i % 10)); + root->addLayout(numRow); + + const char *rows[] = {"QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"}; + for (const char *row : rows) { + auto *rowLayout = new QHBoxLayout(); + rowLayout->setAlignment(Qt::AlignCenter); + for (int i = 0; row[i]; ++i) + addKeyButton(rowLayout, row[i]); + root->addLayout(rowLayout); + } + + auto *spaceRow = new QHBoxLayout(); + spaceRow->setAlignment(Qt::AlignCenter); + addKeyButton(spaceRow, ' ', 200); + root->addLayout(spaceRow); + + m_statusLabel = new QLabel(this); + root->addWidget(m_statusLabel); + refreshStatusLabel(); +} + +void IOKeyboard::refreshRegMap() { + const unsigned bufSize = m_parameters.at(BUFSIZE).value.toUInt(); + + m_regDescs.clear(); + m_regDescs.push_back(RegDesc{"KEY_DATA", RegDesc::RW::R, 8, 0, true}); + m_regDescs.push_back(RegDesc{"KEY_STATUS", RegDesc::RW::RW, 32, 4, true}); + + m_extraSymbols.clear(); + m_extraSymbols.push_back(IOSymbol{"BUF_SIZE", bufSize}); + + emit regMapChanged(); +} + +void IOKeyboard::enqueueKey(uint8_t ascii) { + { + QMutexLocker lock(&m_bufMutex); + const unsigned maxSize = m_parameters.at(BUFSIZE).value.toUInt(); + if (static_cast(m_keyBuffer.size()) < maxSize) + m_keyBuffer.enqueue(ascii); + m_lastKey = ascii; + } + refreshStatusLabel(); +} + +void IOKeyboard::refreshStatusLabel() { + if (!m_statusLabel) + return; + + QMutexLocker lock(&m_bufMutex); + const int count = m_keyBuffer.size(); + const uint8_t ch = m_lastKey; + lock.unlock(); + + const unsigned bufSize = m_parameters.at(BUFSIZE).value.toUInt(); + const QString charStr = + (ch >= 0x20 && ch < 0x7F) ? QString(QChar(ch)) : QString("--"); + m_statusLabel->setText(QString("Last: %1 (0x%2) | Buffer: %3/%4") + .arg(charStr) + .arg(ch, 2, 16, QChar('0')) + .arg(count) + .arg(bufSize)); +} + +void IOKeyboard::keyPressEvent(QKeyEvent *event) { + if (event->isAutoRepeat()) { + event->ignore(); + return; + } + + const int key = event->key(); + uint8_t ascii = 0; + + if (key >= Qt::Key_A && key <= Qt::Key_Z) + ascii = static_cast('A' + (key - Qt::Key_A)); + else if (key >= Qt::Key_0 && key <= Qt::Key_9) + ascii = static_cast('0' + (key - Qt::Key_0)); + else if (key == Qt::Key_Space) + ascii = static_cast(' '); + + if (ascii != 0) { + enqueueKey(ascii); + event->accept(); + } else { + event->ignore(); + } +} + +void IOKeyboard::parameterChanged(unsigned) { + refreshRegMap(); + refreshStatusLabel(); +} + +VInt IOKeyboard::ioRead(AInt offset, unsigned) { + if (offset == 0) { + QMutexLocker lock(&m_bufMutex); + const uint8_t val = m_keyBuffer.isEmpty() ? 0 : m_keyBuffer.dequeue(); + lock.unlock(); + QMetaObject::invokeMethod( + this, [this]() { refreshStatusLabel(); }, Qt::QueuedConnection); + return val; + } + if (offset == 4) { + QMutexLocker lock(&m_bufMutex); + return static_cast(m_keyBuffer.size()); + } + return 0; +} + +void IOKeyboard::ioWrite(AInt offset, VInt value, unsigned) { + if (offset == 4 && value != 0) { + QMutexLocker lock(&m_bufMutex); + m_keyBuffer.clear(); + lock.unlock(); + QMetaObject::invokeMethod( + this, [this]() { refreshStatusLabel(); }, Qt::QueuedConnection); + } +} + +void IOKeyboard::reset() { + { + QMutexLocker lock(&m_bufMutex); + m_keyBuffer.clear(); + m_lastKey = 0; + } + refreshStatusLabel(); +} + +} // namespace Ripes diff --git a/src/io/iokeyboard.h b/src/io/iokeyboard.h new file mode 100644 index 000000000..c328468ae --- /dev/null +++ b/src/io/iokeyboard.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +#include "iobase.h" + +QT_FORWARD_DECLARE_CLASS(QLabel); +QT_FORWARD_DECLARE_CLASS(QPushButton); + +namespace Ripes { + +class IOKeyboard : public IOBase { + Q_OBJECT + + enum Parameters { BUFSIZE }; + +public: + IOKeyboard(QWidget *parent); + ~IOKeyboard() { unregister(); }; + + virtual unsigned byteSize() const override { return 8; } + virtual QString description() const override; + virtual QString baseName() const override { return "Keyboard"; } + + virtual const std::vector ®isters() const override { + return m_regDescs; + }; + virtual const std::vector *extraSymbols() const override { + return &m_extraSymbols; + } + + virtual VInt ioRead(AInt offset, unsigned size) override; + virtual void ioWrite(AInt offset, VInt value, unsigned size) override; + virtual void reset() override; + +protected: + virtual void parameterChanged(unsigned) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + void buildLayout(); + void refreshRegMap(); + void refreshStatusLabel(); + void enqueueKey(uint8_t ascii); + + QQueue m_keyBuffer; + mutable QMutex m_bufMutex; + uint8_t m_lastKey = 0; + + QLabel *m_statusLabel = nullptr; + + std::vector m_regDescs; + std::vector m_extraSymbols; +}; + +} // namespace Ripes diff --git a/src/io/ioregistry.h b/src/io/ioregistry.h index edf95a142..ad73c5c3e 100644 --- a/src/io/ioregistry.h +++ b/src/io/ioregistry.h @@ -4,6 +4,7 @@ #include #include "iodpad.h" +#include "iokeyboard.h" #include "ioledmatrix.h" #include "ioswitches.h" @@ -18,7 +19,7 @@ namespace Ripes { -enum IOType { LED_MATRIX, SWITCHES, DPAD, NPERIPHERALS }; +enum IOType { LED_MATRIX, SWITCHES, DPAD, KEYBOARD, NPERIPHERALS }; template IOBase *createIO(QWidget *parent) { @@ -31,11 +32,13 @@ using IOFactory = std::function; const static std::map IOTypeTitles = { {IOType::LED_MATRIX, "LED Matrix"}, {IOType::SWITCHES, "Switches"}, - {IOType::DPAD, "D-Pad"}}; + {IOType::DPAD, "D-Pad"}, + {IOType::KEYBOARD, "Keyboard"}}; const static std::map IOFactories = { {IOType::LED_MATRIX, createIO}, {IOType::SWITCHES, createIO}, - {IOType::DPAD, createIO}}; + {IOType::DPAD, createIO}, + {IOType::KEYBOARD, createIO}}; } // namespace Ripes diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4f2c85aff..27e3734db 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,3 +25,4 @@ create_qtest(tst_assembler) create_qtest(tst_expreval) create_qtest(tst_cosimulate) create_qtest(tst_reverse) +create_qtest(tst_io_keyboard) diff --git a/test/tst_io_keyboard.cpp b/test/tst_io_keyboard.cpp new file mode 100644 index 000000000..d6f825a63 --- /dev/null +++ b/test/tst_io_keyboard.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +#include "io/iobase.h" +#include "io/iokeyboard.h" + +using namespace Ripes; + +namespace { +IOKeyboard *makeKeyboard() { + auto *p = new IOKeyboard(nullptr); + QObject::connect(p, &IOBase::aboutToDelete, p, + [](std::atomic &ok) { ok = 1; }); + return p; +} +} // namespace + +class tst_io_keyboard : public QObject { + Q_OBJECT + +private slots: + // Verifies FIFO enqueue/dequeue behaviour and KEY_STATUS accounting: + // - empty buffer: KEY_STATUS == 0, KEY_DATA read returns 0 + // - real QKeyEvent presses are forwarded and counted in KEY_STATUS + // - KEY_DATA reads dequeue keys in FIFO order + // - after the buffer is drained, KEY_DATA returns 0 + // - unmapped keys (e.g. F1) are not enqueued + void keyboard_keyPressEnqueuesAndStatusReflectsBuffer() { + auto *kb = makeKeyboard(); + constexpr AInt KEY_DATA = 0; + constexpr AInt KEY_STATUS = 4; + + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + QCOMPARE(kb->ioRead(KEY_DATA, 1), VInt{0}); + + const QList> keys = { + {Qt::Key_A, 'A'}, {Qt::Key_B, 'B'}, {Qt::Key_5, '5'}}; + for (const auto &k : keys) { + QKeyEvent ev(QEvent::KeyPress, k.first, Qt::NoModifier); + QCoreApplication::sendEvent(kb, &ev); + } + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{3}); + + for (const auto &k : keys) + QCOMPARE(static_cast(kb->ioRead(KEY_DATA, 1)), k.second); + QCOMPARE(kb->ioRead(KEY_DATA, 1), VInt{0}); + + QKeyEvent ignored(QEvent::KeyPress, Qt::Key_F1, Qt::NoModifier); + QCoreApplication::sendEvent(kb, &ignored); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + + delete kb; + } + + // Verifies that the key buffer can be cleared in two ways: + // - a non-zero write to KEY_STATUS flushes the FIFO + // - reset() also flushes the FIFO + void keyboard_writeStatusClearsBuffer() { + auto *kb = makeKeyboard(); + constexpr AInt KEY_STATUS = 4; + + QKeyEvent ev(QEvent::KeyPress, Qt::Key_Q, Qt::NoModifier); + QCoreApplication::sendEvent(kb, &ev); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{1}); + + kb->ioWrite(KEY_STATUS, 1, 4); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + + QCoreApplication::sendEvent(kb, &ev); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{1}); + kb->reset(); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + + delete kb; + } + + // Verifies that the FIFO is bounded by the BUFSIZE parameter: + // - excess events beyond the configured capacity are silently dropped + // - KEY_STATUS never exceeds BUFSIZE regardless of how many events arrive + void keyboard_bufferOverflowIsBounded() { + auto *kb = makeKeyboard(); + constexpr AInt KEY_STATUS = 4; + kb->setParameter(0, 4); + + QKeyEvent ev(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier); + for (int i = 0; i < 10; ++i) + QCoreApplication::sendEvent(kb, &ev); + QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{4}); + + delete kb; + } +}; + +QTEST_MAIN(tst_io_keyboard) +#include "tst_io_keyboard.moc" \ No newline at end of file From 032db0f30bd1feb9ebe7731678269cb7fd00ddca Mon Sep 17 00:00:00 2001 From: Petr Yakushev <144445892+PetrYakushev@users.noreply.github.com> Date: Thu, 14 May 2026 23:51:48 +0300 Subject: [PATCH 2/2] Add files via upload --- examples/C/keyboardLedMatrix.c | 112 +++++++++++++++++++++++++-------- src/io/iokeyboard.cpp | 36 +++++++---- src/io/iokeyboard.h | 11 +++- test/tst_io_keyboard.cpp | 57 ++++++++--------- 4 files changed, 144 insertions(+), 72 deletions(-) diff --git a/examples/C/keyboardLedMatrix.c b/examples/C/keyboardLedMatrix.c index 607691830..5e4d081fe 100644 --- a/examples/C/keyboardLedMatrix.c +++ b/examples/C/keyboardLedMatrix.c @@ -1,20 +1,44 @@ #include "ripes_system.h" /* - * keyboardLedMatrix.c - * Reads characters from the keyboard peripheral and displays the most recent - * one in the center of the LED matrix using a 5x7 bitmap font. + * keyboardDualDisplay.c + * Keyboard input demo with dual display output + * + * ========================================================= + * PREREQUISITE (IMPORTANT): + * Before running this program, the user MUST configure the + * Ripes I/O table and add the following peripherals: + * + * 1) LED Matrix: + * - ID: LED_MATRIX_0 + * - Required symbols: + * LED_MATRIX_0_BASE + * LED_MATRIX_0_WIDTH + * LED_MATRIX_0_HEIGHT + * + * 2) Keyboard: + * - ID: KEYBOARD_0 + * - Required symbols: + * KEYBOARD_0_BASE + * + * Without these peripherals properly added and mapped, + * the program will either: + * - read invalid memory + * - or produce undefined behavior in I/O access + * ========================================================= */ -#define MATRIX_W LED_MATRIX_0_WIDTH -#define MATRIX_H LED_MATRIX_0_HEIGHT -#define CHAR_W 5 -#define CHAR_H 7 -#define COLOR_ON 0x00FF00 -#define COLOR_OFF 0x000000 +#define MATRIX_W LED_MATRIX_0_WIDTH +#define MATRIX_H LED_MATRIX_0_HEIGHT +#define CHAR_W 5 +#define CHAR_H 7 +#define COLOR_ON 0x00FF00 +#define COLOR_OFF 0x000000 +#define SEG_COUNT SEVEN_SEGMENT_0_N_DIGITS -// 5x7 bitmap font for 0-9 and a-z (case-insensitive). +// LED Matrix Font: 0-9, a-z (5x7 bitmap, 1 bit = 1 pixel) static const unsigned char font[36][CHAR_H] = { + // 0-9 {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, // 0 {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1 {0x0E, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1F}, // 2 @@ -25,6 +49,7 @@ static const unsigned char font[36][CHAR_H] = { {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10}, // 7 {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, // 8 {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}, // 9 + // A-Z {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x00}, // a {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, // b {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}, // c @@ -53,27 +78,51 @@ static const unsigned char font[36][CHAR_H] = { {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F} // z }; +// 7-Segment Display Font: common-cathode encoding +static const unsigned char seg_digit[10] = { + 0x3F, 0x06, 0x5B, 0x4F, 0x66, + 0x6D, 0x7D, 0x07, 0x7F, 0x6F +}; + +static const unsigned char seg_alpha[26] = { + 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x3D, 0x76, 0x06, 0x1E, + 0x76, 0x38, 0x37, 0x54, 0x3F, 0x73, 0x67, 0x50, 0x6D, 0x78, + 0x3E, 0x1C, 0x3E, 0x76, 0x6E, 0x5B +}; + +// Set single pixel on LED matrix static inline void set_pixel(volatile unsigned int *led, int x, int y, unsigned int color) { if (x >= 0 && x < MATRIX_W && y >= 0 && y < MATRIX_H) *(led + y * MATRIX_W + x) = color; } -static void draw_char(volatile unsigned int *led, int x, int y, int idx, unsigned int color) { - const unsigned char *bitmap = font[idx]; +// Draw character from font bitmap +static void draw_char_matrix(volatile unsigned int *led, int start_x, int start_y, int char_idx, unsigned int color) { + const unsigned char *bitmap = font[char_idx]; for (int dy = 0; dy < CHAR_H; dy++) { unsigned char row = bitmap[dy]; for (int dx = 0; dx < CHAR_W; dx++) if (row & (1 << (CHAR_W - 1 - dx))) - set_pixel(led, x + dx, y + dy, color); + set_pixel(led, start_x + dx, start_y + dy, color); } } -static void clear_area(volatile unsigned int *led, int x, int y) { +// Clear character area (5x7) +static void clear_char_area(volatile unsigned int *led, int start_x, int start_y) { for (int dy = 0; dy < CHAR_H; dy++) for (int dx = 0; dx < CHAR_W; dx++) - set_pixel(led, x + dx, y + dy, COLOR_OFF); + set_pixel(led, start_x + dx, start_y + dy, COLOR_OFF); } +// Convert ASCII to 7-segment code +static unsigned char encode_seg(unsigned char ch) { + if (ch >= '0' && ch <= '9') return seg_digit[ch - '0']; + if (ch >= 'A' && ch <= 'Z') return seg_alpha[ch - 'A']; + if (ch >= 'a' && ch <= 'z') return seg_alpha[ch - 'a']; + return 0; +} + +// Convert ASCII to font index (0-35), -1 if unsupported static int char_to_index(unsigned char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'Z') return 10 + (ch - 'A'); @@ -83,28 +132,39 @@ static int char_to_index(unsigned char ch) { int main() { volatile unsigned int *led = (volatile unsigned int *)LED_MATRIX_0_BASE; + volatile unsigned int *seg = (volatile unsigned int *)SEVEN_SEGMENT_0_BASE; volatile unsigned int *kbd = (volatile unsigned int *)KEYBOARD_0_BASE; - const int center_x = (MATRIX_W - CHAR_W) / 2; - const int center_y = (MATRIX_H - CHAR_H) / 2; - int prev_idx = -1; + int prev_char_idx = -1; + int center_x = (MATRIX_W - CHAR_W) / 2; + int center_y = (MATRIX_H - CHAR_H) / 2; + int seg_pos = 0; for (int i = 0; i < MATRIX_W * MATRIX_H; i++) *(led + i) = COLOR_OFF; + for (int i = 0; i < SEG_COUNT; i++) + *(seg + i) = 0; + while (1) { if (*(kbd + 1) == 0) continue; unsigned char key = (unsigned char)(*kbd); - int idx = char_to_index(key); - if (idx < 0) - continue; + int char_idx = char_to_index(key); + unsigned char seg_code = encode_seg(key); - if (prev_idx >= 0) - clear_area(led, center_x, center_y); - draw_char(led, center_x, center_y, idx, COLOR_ON); - prev_idx = idx; + if (char_idx >= 0 && char_idx < 36 && seg_code != 0) { + if (prev_char_idx >= 0) + clear_char_area(led, center_x, center_y); + draw_char_matrix(led, center_x, center_y, char_idx, COLOR_ON); + prev_char_idx = char_idx; + + *(seg + seg_pos) = seg_code; + seg_pos++; + if (seg_pos >= SEG_COUNT) + seg_pos = 0; + } } return 0; -} +} \ No newline at end of file diff --git a/src/io/iokeyboard.cpp b/src/io/iokeyboard.cpp index 8c3300f4c..cf709dc73 100644 --- a/src/io/iokeyboard.cpp +++ b/src/io/iokeyboard.cpp @@ -95,14 +95,19 @@ void IOKeyboard::refreshStatusLabel() { if (!m_statusLabel) return; - QMutexLocker lock(&m_bufMutex); - const int count = m_keyBuffer.size(); - const uint8_t ch = m_lastKey; - lock.unlock(); + int count; + uint8_t ch; + + { + QMutexLocker lock(&m_bufMutex); + count = m_keyBuffer.size(); + ch = m_lastKey; + } const unsigned bufSize = m_parameters.at(BUFSIZE).value.toUInt(); const QString charStr = (ch >= 0x20 && ch < 0x7F) ? QString(QChar(ch)) : QString("--"); + m_statusLabel->setText(QString("Last: %1 (0x%2) | Buffer: %3/%4") .arg(charStr) .arg(ch, 2, 16, QChar('0')) @@ -141,25 +146,34 @@ void IOKeyboard::parameterChanged(unsigned) { VInt IOKeyboard::ioRead(AInt offset, unsigned) { if (offset == 0) { - QMutexLocker lock(&m_bufMutex); - const uint8_t val = m_keyBuffer.isEmpty() ? 0 : m_keyBuffer.dequeue(); - lock.unlock(); + uint8_t val; + + { + QMutexLocker lock(&m_bufMutex); + val = m_keyBuffer.isEmpty() ? 0 : m_keyBuffer.dequeue(); + } + QMetaObject::invokeMethod( this, [this]() { refreshStatusLabel(); }, Qt::QueuedConnection); + return val; } + if (offset == 4) { QMutexLocker lock(&m_bufMutex); return static_cast(m_keyBuffer.size()); } + return 0; } void IOKeyboard::ioWrite(AInt offset, VInt value, unsigned) { if (offset == 4 && value != 0) { - QMutexLocker lock(&m_bufMutex); - m_keyBuffer.clear(); - lock.unlock(); + { + QMutexLocker lock(&m_bufMutex); + m_keyBuffer.clear(); + } + QMetaObject::invokeMethod( this, [this]() { refreshStatusLabel(); }, Qt::QueuedConnection); } @@ -174,4 +188,4 @@ void IOKeyboard::reset() { refreshStatusLabel(); } -} // namespace Ripes +} // namespace Ripes \ No newline at end of file diff --git a/src/io/iokeyboard.h b/src/io/iokeyboard.h index c328468ae..1d16f602f 100644 --- a/src/io/iokeyboard.h +++ b/src/io/iokeyboard.h @@ -3,7 +3,7 @@ #include #include #include - +#include #include "iobase.h" QT_FORWARD_DECLARE_CLASS(QLabel); @@ -16,6 +16,11 @@ class IOKeyboard : public IOBase { enum Parameters { BUFSIZE }; + enum RegMap : AInt { + KEY_DATA = 0x0, + KEY_STATUS = 0x4 + }; + public: IOKeyboard(QWidget *parent); ~IOKeyboard() { unregister(); }; @@ -47,7 +52,7 @@ class IOKeyboard : public IOBase { QQueue m_keyBuffer; mutable QMutex m_bufMutex; - uint8_t m_lastKey = 0; + std::optional m_lastKey; QLabel *m_statusLabel = nullptr; @@ -55,4 +60,4 @@ class IOKeyboard : public IOBase { std::vector m_extraSymbols; }; -} // namespace Ripes +} // namespace Ripes \ No newline at end of file diff --git a/test/tst_io_keyboard.cpp b/test/tst_io_keyboard.cpp index d6f825a63..758c3524f 100644 --- a/test/tst_io_keyboard.cpp +++ b/test/tst_io_keyboard.cpp @@ -20,73 +20,66 @@ class tst_io_keyboard : public QObject { Q_OBJECT private slots: - // Verifies FIFO enqueue/dequeue behaviour and KEY_STATUS accounting: - // - empty buffer: KEY_STATUS == 0, KEY_DATA read returns 0 - // - real QKeyEvent presses are forwarded and counted in KEY_STATUS - // - KEY_DATA reads dequeue keys in FIFO order - // - after the buffer is drained, KEY_DATA returns 0 - // - unmapped keys (e.g. F1) are not enqueued void keyboard_keyPressEnqueuesAndStatusReflectsBuffer() { auto *kb = makeKeyboard(); - constexpr AInt KEY_DATA = 0; - constexpr AInt KEY_STATUS = 4; - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); - QCOMPARE(kb->ioRead(KEY_DATA, 1), VInt{0}); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{0}); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_DATA, 1), VInt{0}); const QList> keys = { {Qt::Key_A, 'A'}, {Qt::Key_B, 'B'}, {Qt::Key_5, '5'}}; + for (const auto &k : keys) { - QKeyEvent ev(QEvent::KeyPress, k.first, Qt::NoModifier); + QKeyEvent ev(QEvent::KeyPress, k.first, Qt::NoModifier, QString(k.second)); QCoreApplication::sendEvent(kb, &ev); } - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{3}); + + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{3}); for (const auto &k : keys) - QCOMPARE(static_cast(kb->ioRead(KEY_DATA, 1)), k.second); - QCOMPARE(kb->ioRead(KEY_DATA, 1), VInt{0}); + QCOMPARE(static_cast(kb->ioRead(IOKeyboard::RegMap::KEY_DATA, 1)), k.second); + + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_DATA, 1), VInt{0}); - QKeyEvent ignored(QEvent::KeyPress, Qt::Key_F1, Qt::NoModifier); + QKeyEvent ignored(QEvent::KeyPress, Qt::Key_F1, Qt::NoModifier, QString()); QCoreApplication::sendEvent(kb, &ignored); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{0}); delete kb; } - // Verifies that the key buffer can be cleared in two ways: - // - a non-zero write to KEY_STATUS flushes the FIFO - // - reset() also flushes the FIFO void keyboard_writeStatusClearsBuffer() { auto *kb = makeKeyboard(); - constexpr AInt KEY_STATUS = 4; - QKeyEvent ev(QEvent::KeyPress, Qt::Key_Q, Qt::NoModifier); + QKeyEvent ev(QEvent::KeyPress, Qt::Key_Q, Qt::NoModifier, "Q"); + QCoreApplication::sendEvent(kb, &ev); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{1}); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{1}); - kb->ioWrite(KEY_STATUS, 1, 4); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + kb->ioWrite(IOKeyboard::RegMap::KEY_STATUS, 1, 4); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{0}); QCoreApplication::sendEvent(kb, &ev); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{1}); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{1}); + kb->reset(); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{0}); + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{0}); delete kb; } - // Verifies that the FIFO is bounded by the BUFSIZE parameter: - // - excess events beyond the configured capacity are silently dropped - // - KEY_STATUS never exceeds BUFSIZE regardless of how many events arrive void keyboard_bufferOverflowIsBounded() { auto *kb = makeKeyboard(); - constexpr AInt KEY_STATUS = 4; + kb->setParameter(0, 4); - QKeyEvent ev(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier); + QKeyEvent ev(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X"); + for (int i = 0; i < 10; ++i) QCoreApplication::sendEvent(kb, &ev); - QCOMPARE(kb->ioRead(KEY_STATUS, 4), VInt{4}); + + QCOMPARE(kb->ioRead(IOKeyboard::RegMap::KEY_STATUS, 4), VInt{4}); delete kb; }