Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions examples/C/keyboardLedMatrix.c
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be obvious, but i like to make things super obvious - can you write here that, as a prerequisite, users should instantiate a matrix and keyboard peripheral before running this program?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve updated the header comment to explicitly state the prerequisite.
It now mentions that, before running the program, the user must add both the LED Matrix and Keyboard peripherals to the Ripes I/O tab, using the default identifiers (LED_MATRIX_0_* and KEYBOARD_0_*).

*/

#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;
}
1 change: 1 addition & 0 deletions examples/examples.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<file>assembly/consolePrinting.s</file>
<file>C/leds.c</file>
<file>C/switchesAndLeds.c</file>
<file>C/keyboardLedMatrix.c</file>
<file>assembly/leds.s</file>
<file>ELF/RanPi-RV32</file>
<file>ELF/RanPi-RV64</file>
Expand Down
177 changes: 177 additions & 0 deletions src/io/iokeyboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include "iokeyboard.h"
#include "ioregistry.h"

#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QMutexLocker>
#include <QPushButton>
#include <QVBoxLayout>

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<uint8_t>(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<unsigned>(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();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a scoped lock instead of a lock/unlock pattern.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced the manual lock.unlock() pattern with a scoped block, so the mutex is now released automatically when the block ends.


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<uint8_t>('A' + (key - Qt::Key_A));
else if (key >= Qt::Key_0 && key <= Qt::Key_9)
ascii = static_cast<uint8_t>('0' + (key - Qt::Key_0));
else if (key == Qt::Key_Space)
ascii = static_cast<uint8_t>(' ');
Comment on lines +124 to +132

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh - is there really no built-in Qt function for this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qt provides QKeyEvent::text(), which gives the actual character generated by the key press while taking the current keyboard layout and modifiers into account. That turned out to be exactly what we needed here, so I replaced the manual Qt::Key_* to ASCII mapping with event->text().toLatin1().


if (ascii != 0) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zero checks aren't the best. If you insist on using this pattern, do std::optional<uint8_t> ascii.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I removed the sentinel 0 check and switched to std::optional<uint8_t>, so the presence or absence of a decoded character is now represented explicitly.

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<VInt>(m_keyBuffer.size());
}
return 0;
Comment on lines +148 to +167

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Although it's small, please add an enum for the register map, and do a switch statement here. Magic constants are bad.
  2. To keep the code a bit dry, i'd just lock the m_bufMutex at the start of this function. Again, use scoped locks instead of lock/unlocks.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a RegMap enum for the register offsets, replaced the if chain with a switch over KEY_DATA and KEY_STATUS, and moved to a single scoped QMutexLocker at the start of the function to avoid both magic constants and repeated locking logic.

}

void IOKeyboard::ioWrite(AInt offset, VInt value, unsigned) {
if (offset == 4 && value != 0) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the enum register map value that you are going to define.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After introducing the RegMap enum, I updated the code to use RegMap::KEY_DATA and RegMap::KEY_STATUS everywhere instead of the raw offset values.

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
58 changes: 58 additions & 0 deletions src/io/iokeyboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <QMutex>
#include <QQueue>
#include <QWidget>

#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<RegDesc> &registers() const override {
return m_regDescs;
};
virtual const std::vector<IOSymbol> *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<uint8_t> m_keyBuffer;
mutable QMutex m_bufMutex;
uint8_t m_lastKey = 0;

QLabel *m_statusLabel = nullptr;

std::vector<RegDesc> m_regDescs;
std::vector<IOSymbol> m_extraSymbols;
};

} // namespace Ripes
9 changes: 6 additions & 3 deletions src/io/ioregistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <QWidget>

#include "iodpad.h"
#include "iokeyboard.h"
#include "ioledmatrix.h"
#include "ioswitches.h"

Expand All @@ -18,7 +19,7 @@

namespace Ripes {

enum IOType { LED_MATRIX, SWITCHES, DPAD, NPERIPHERALS };
enum IOType { LED_MATRIX, SWITCHES, DPAD, KEYBOARD, NPERIPHERALS };

template <typename T>
IOBase *createIO(QWidget *parent) {
Expand All @@ -31,11 +32,13 @@ using IOFactory = std::function<IOBase *(QWidget *parent)>;
const static std::map<IOType, QString> IOTypeTitles = {
{IOType::LED_MATRIX, "LED Matrix"},
{IOType::SWITCHES, "Switches"},
{IOType::DPAD, "D-Pad"}};
{IOType::DPAD, "D-Pad"},
{IOType::KEYBOARD, "Keyboard"}};
const static std::map<IOType, IOFactory> IOFactories = {
{IOType::LED_MATRIX, createIO<IOLedMatrix>},
{IOType::SWITCHES, createIO<IOSwitches>},
{IOType::DPAD, createIO<IODPad>}};
{IOType::DPAD, createIO<IODPad>},
{IOType::KEYBOARD, createIO<IOKeyboard>}};

} // namespace Ripes

Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading