This post continues after Interrupts squred.
Since all I do is playing with things I feel like, and designing the bits to make this a usable keyboard sounds like work, we got another intermediate step to have some fun with.
There’s an LCD display connected to the board as I bought them. So let’s get that up an running. Can’t be hard, right?
During some earlier research, I had found an lcd library on github that claims to support DMA, which apparently the vendor lcd example does not. So I grabbed the code from github, and got started.
There’s even a commit for it. The font table is from the vendor example. And not very interesting for now I guess.
Killing the GPIO
I put up the commits in the repository in an order that should work out, trying to reproduce my work. But the way I got to them is in the reverse order for a couple commits, and the post follows what I did, not what I commited.
So, I just added a quick example (taken from the library) to test lcd stuff. The commit is rather simple. Set up lcd, generate a test imgage (well, grey with red border) and scan it out to display. Easy.
And it actually is that easy. Even the print char later on works quite well. I don’t have a picture of that state, but I’ll put some up of later steps.
So far so good, now does the other stuff work? No. We are in trouble. The mcp23017 GPIO expanders don’t trigger the interrupt anymore. And the values we do read out of them look, well, wrong. Great. Add one feature, break the core. Sooo, what’s going on? It’s not in software this time. Shorting the interrupt pin to ground, I still get the interrupt triggered on the CPU.
Finding the Issue
Step one of fixing an issue, find it! This one was a bit annoying, but luckily we know we just added the lcd support code. So it should be somewhere in there.
So, what do we see here? The LCD code sets up GPIO Pin 0 on pad B and then uses it to switch data/cmd mdoe.
If you recall the schematic of the current system, GPIOB Pin0 is already used. It’s the reset pin for the mcp23017(s). Whelp, found out why they don’t behave as expected anymore.
Fixing the Issue
Sadly we can’t just work around the issue in software. So there’s a hardware patch and a software patch required. Luckily, it’s pretty easy. We just move the reset pin. The code change explains all of it.
I hope I remember to update the schematic as well, the next time I actually touch it. It’s almost the same, but the RST wire needs to be moved to another pin on the Longan Nano.
Interrupts for Sync
While the lcd library I found is nice with the framebuffer, the other functions aren’t quite up to what I need. The main example even mentions that synch between drawing and scanout is done by mostly luck.
Since I want to avoid the giant framebuffer taking up roughly 20KiB of the available 32KiB of ram, some adjustments were required.
Add Basic DMA Feedback
As previoously in the interrupts series, we’ll want to leverage the interrupts provided by the DMA controller.
The first step adds a global feedback function from DMA interrupt handler and exposes it for the lcd_write_u16_cb
function.
I also had to split out lcd_enable_interrupt
to actually enable a user to tell the CPU to trigger it.
On the main side (probably going to move into a library at some point), void lcd_print_string(const char *str, uint32_t x, uint32_t y, uint16_t color, void (*cb)(void))
was created.
The prototype is an almost copy of the vendor example. It only has an additional function pointer to call after it’s done.
If you prefer functional programming over this kinda stuff, just think of it as concurrent continuation passing style programming.
The implementation of lcd_print_char
was only extended by a continuation function pointer passed into the lcd library as well. The more interesting part is lcd_print_string_step
(well, and the body of lcd_print_string
I guess).
We set up a small local store for our arguments, i.e. string, colour, position and callback. The state machine that allows us to advance over the string is done in the same variables. Every step we increase the string by one (i.e. go one character further) and increase the x
position by 8, which is the font width.
If we are at the end of the string str[0] == '\0'
we stop this and call the user callback. Easy
Adding more DMA Feedback
The following commit is largely the same, but this time in the library.
void lcd_rect_cb(int x, int y, int w, int h, uint16_t color, void(*cb)(void))
uses the same kind of static struct state machine callback driven approach.
Looking at it now, it might make more sense to switch it from a single entry step function + switch/case
to each step being in a dedicated function, and keeping all state via the instruction pointer. But ehh