Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

>OOP in keyboard firmware?

I could do it with FP if someone made a Haskell to AVR compiler :)

>Am I the only one who finds this quite a lot more complex than it needs to be?

What functionality would you propose stripping?

>The obvious solution for this is to synchronise the I2C clock to the matrix scanning loop, so you receive/send one bit each time through the loop.

That's the wrong solution. The main loop takes some milliseconds. Communication at that rate would be intolerably slow. And how is that any better than natively clocked I2C? I'd have to re-implement the entire protocol in software to get that kind of control.

>do away with the I2C overhead completely and use a continuous serial protocol between the two halves.

What overhead? Any overhead introduced by I2C is very minimal.

What benefit would a "continuous" (do you mean asynchronous?) serial protocol have? There are also a number of downsides: 1. It wouldn't work well with the symmetric halves design. The cable would have to switch the TX and RX lines, and no common cable does that. 2. It wouldn't support more than two keyboards at once, unless I implemented a shared-line serial protocol from scratch, and then why not just use I2C? And using an asynchronous serial protocol doesn't solve the board synchronization problem.

The new version of the board also breaks out the UART header for people who want to do exactly this, but the UART is not a better solution.



> What functionality would you propose stripping?

The complexity here is not in the functionality. It's in the design and implementation.

> The main loop takes some milliseconds.

The MCU has a 16MHz single-cycle core. That means a 62.5ns instruction cycle, or 16,000 instructions in 1ms. What makes it require >16,000 instructions every time through the loop?

> What benefit would a "continuous" (do you mean asynchronous?) serial protocol have?

I do mean a continuous one, with the master driving the clock at the frequency of the main loop, and the slave(s) synchronising their transmissions with it. One of the biggest advantages I see is that you can make use of unused positions in the key matrix, so that they can be scanned at the same time as all the others. You just need to know which one, and instead of applying debouncing/up/down, accumulate the bits of scancode that are sent in and pass them through.

> 1. It wouldn't work well with the symmetric halves design. The cable would have to switch the TX and RX lines, and no common cable does that.

GPIOs are bidirectional. You can do the crossover configuration entirely in software, since the current design already detects whether it's plugged in from USB.

> 2. It wouldn't support more than two keyboards at once

How would you do this with the current design? They're symmetrical, but I don't see any way to chain or hub them together (unless you use a USB hub to connect as many independently as you want...), so I think that's a bit of a moot point. But if you are thinking of a star topology for this "keyboard area network"(!), using another unused key position in the matrix of the master for more extension boards would work. Each board could act as a hub for more, but this quickly gets into "why would you ever want to do that" territory.

> unless I implemented a shared-line serial protocol from scratch, and then why not just use I2C?

Because I2C was defined with different assumptions in mind.


>The complexity here is not in the functionality. It's in the design and implementation.

I'd love to hear some proposals for simplifying the implementation.

>What makes it require >16,000 instructions every time through the loop?

Mostly USB library stuff, I think. Been a while since I profiled it.

>I do mean a continuous one...

Can you further explain this paragraph? I don't know what you mean by "unused positions in the key matrix".

>You can do the crossover configuration entirely in software

Like I said: I'd have no implement the protocol from scratch, in software. The hardware UART does not support this functionality.

>How would you do this with the current design?

You can splice a TRRS cable or buy a TRRS splitter. The new version of the board actually has a dual-jack USB-A connector to make daisy-chaining even easier.

Again, not sure what you mean by "unused key position in the matrix".

>Because I2C was defined with different assumptions in mind.

Which were?


> I'd love to hear some proposals for simplifying the implementation.

The one thing that stands out is that many of the classes only have one instance created, and they're classes that don't have much functionality in them. Returning structures from functions requires a copy, in this case the array of button states, and copies of them are also created inside of functions like ButtonDebouncer::update. Only one of these state arrays is ever needed, so make only one...

The actual matrix-scan loop in HardwareController could also be simplified greatly; this usually just involves shifting a 1 bit through the row/column driver output port register(s) and reading the input port(s). There's no need to turn all the rows/columns off each time, since getting the next col/row is only a matter of changing which one is being driven. Getting the button number shouldn't require a multiplication (I'll admit I was pretty shocked when I saw that!) either, since each iteration of the inner loop is the button after the previous one, so it can be done with one counter.

> Mostly USB library stuff, I think. Been a while since I profiled it.

I'm more familiar with PS/2 keyboard protocol which is much simpler, but USB shouldn't be that much more complex to send/receive info once everything has been initialised (and as I understand from working with USB controllers in non-keyboard devices, the SIE does much of the actual transfer work in hardware.)

> Can you further explain this paragraph? I don't know what you mean by "unused positions in the key matrix".

There can be row/column combinations which don't have keyswitches at their intersection, but are scanned anyway. You can connect other units so that they act like keyswitches when they send information.

> Like I said: I'd have no implement the protocol from scratch, in software. The hardware UART does not support this functionality.

Of course.

> Which were?

A non-continuous clock. Transfers that occur in small bursts instead of one continuous serial stream.

(I have studied the IBM keyboard firmware, and worked with one for an 8051-based controller before.)


>The one thing that stands out is that many of the classes only have one instance created,

To maintain proper levels of abstraction.

>Returning structures from functions requires a copy,

Which is extremely fast, simple, side-effect-free, and likely optimized away by RVO.

>The actual matrix-scan loop in HardwareController could also be simplified greatly;

Nothing you described would actually be an optimization. Turning off all the rows/columns takes 1 cycle.

>Getting the button number shouldn't require a multiplication

Nit-picking. This is fast and easy to read. I'm not going to worry about the few nanoseconds required for a multiplication.

That's also not an issue of "simplification"; it's just premature optimization.

>but USB shouldn't be that much more complex

PJRC wrote that library. Not sure what they do.

>There can be row/column combinations which don't have keyswitches at their intersection, but are scanned anyway. You can connect other units so that they act like keyswitches when they send information.

Why would I do that? Part of the design is that each individual board is responsible for translating button presses to USB keycode deltas. This allows for better distribution of responsibility, and also the boards can be connected in any configuration/order and maintain the same behavior.

>Transfers that occur in small bursts instead of one continuous serial stream.

What's the downside to this? The bursts occur way faster than human perception could hope to notice.

Besides, if you want to be pedantic, any serial protocol communicates in "bursts".


> To maintain proper levels of abstraction.

Abstraction is only a tool, a means to an end; not the end in itself.

> Which is extremely fast, simple, side-effect-free, and likely optimized away by RVO.

Copying is one of the fastest things a processor can do, but that's not a reason to do it unnecessarily, even if the compiler optimiser could simplify it.

> That's also not an issue of "simplification"; it's just premature optimization.

Really? I suppose that if you consider abstraction the ultimate goal, you might think that shorter, simpler code would be "premature optimization"...

> Part of the design is that each individual board is responsible for translating button presses to USB keycode deltas.

That functionality remains the same; the receiving board just collects each bit of the scancode and passes it through directly to the USB. The sending board is still the one doing the matrix scan and position-to-keycode translation. The receiving board is just a passthrough.

> What's the downside to this? The bursts occur way faster than human perception could hope to notice.

It adds complexity to the software, because it has to deal with start/stop conditions, etc.

> Besides, if you want to be pedantic, any serial protocol communicates in "bursts".

Not if the clock is continuous; once the receiver and sender are synchronised, the sender can e.g. keep transmitting 0 bytes when there is nothing to send, and keycodes (which are all nonzero) otherwise.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: