More progress in porting Loser Corps to the ESP32 (see all posts about the port).
Even though there's nothing really exciting to report, this is a
very long post because there's a lot of small changes:
- Wiimote support
- New VGA DACs
- Rewriting the VGA output code
- New font
The game now supports the Wiimote controller (via Bluetooth) using
takeru's Wiimote library. I had
to make a very small change to use it: I added a void *
parameter to
the callback function, so it can update the state of the controller
object without forcing it to refer to the global binding.
The library works perfectly, the only slightly annoying thing is that
I haven't figured out how to permanently pair the controller with the
ESP32, so every time the ESP32 restarts you have to press the Wiimote
buttons 1 and 2 at the same time to make the Bluetooth connection. Not
a big deal, but it could be better.
I made a new VGA connector with new digital-to-analog converters
(DACs). The old ones are slightly wonky because I didn't have
resistors with good values when I made it, so I finally got some new
resistors and made a new one:
New VGA connector with DACs
For comparison, here's the old one:
Old VGA connector with DACs
To see why the new DACs are better than the old ones, we have to talk
about how the VGA color signal is generated. The monitor wants to
receive each color component (Red, Green, Blue) in their own wire,
with a voltage between 0V and 0.7V indicating its intensity. To
produce an image, the voltage has to be changed as the monitor sweeps
the screen to draw the pixels: each pixel is drawn with the RGB
intensity corresponding to the voltage of the red, green and blue
wires at the time it's being drawn.
The ESP32 only has 2 internal DAC outputs, so we can't use them to
create the 3 voltages we need for the RGB wires (even if it had 3, its
DACs aren't fast enough to generate the signal we need at over
12MHz). So we make the ESP32 output each color component as multiple
digital pins: in our case, we use 2 pins per component. Since a
digital pin has two logical values (0 and 1), each component has 4
possible combinations (00, 01, 10, 11), and the DAC has to convert
these to 4 voltages between 0V and 0.7V. Ideally, we'd like to map
them like this:
The ESP32 output pins give 3.3V when high (logical 1) and 0V when low
(logical 0), so we can build a small circuit connecting them to
resistors to generate the desired output voltages:
DAC schematic (for each color component)
That's the DAC. We just have to choose values for R1 and R2 so that
when Pin1 and Pin2 are high/low we have the desired voltage in output
as shown in the table above. (By the way, the 75Ω resistor is there
for impedance matching with the monitor -- I don't think it's really
right, but I don't know how to match the impedance properly).
The old DAC had resistor values of 1KΩ and 560Ω, which is what I had
available at the time. The new DAC has 1KΩ and 470Ω, which outputs
voltages closer to what we want:
DAC output voltages (dotted lines show theoretical best)
The dotted lines show the theoretical best values for each pin value
except for 00
, which has the trivial-to-generate voltage level of
0V. The new DAC is overall closer to the theoretical best, but most
importantly, it has more consistent spacing between the levels, while
the old DAC has the interval between 01 and 10 smaller then the other
ones.
I'm not sure the image result is that different, but for the record
here's a photo of the game running with the new DACs:
Game screen with the new DACs
I ended up rewriting the VGA output code. I was using bitluni's
excellent ESP32Lib, but that
library does a lot of stuff we don't need. That makes the code larger
and also more complicated: when reading the code, I had trouble
following exactly how things are done with the code written in C++
with multiple inheritance.
So to make sure I understood every detail, I ended up rewriting the
code in plain C style. My code supports only double-buffered output,
and changing the resolution requires altering the appropriate
constants by hand (I might make it easier to change it if I end up
using this code for something else). It also doesn't have any drawing
API, all drawing must be done by writing directly to the framebuffer
-- which suits the game just fine, because I was already doing that
with ESP32Lib.
My only improvement over ESP32Lib was making the I2S system trigger an
interrupt at the end of each frame (the interrupy simply increments a
frame counter). With that, the main game loop can now wait for the end
of the frame to swap the framebuffers and start drawing again
(basically a vsync). That removes the annoying horizontal line from
the screen which happens when the image is updated while it's being
drawn, and also forces a constant framerate (that's why that photo
above shows 60fps).
It's still possible to disable the vsync (returning to the old
variable framerate and annoying horizontal line), which I do
occasionally to check the performance of the game.
Since I'm not using ESP32Lib anymore, I had to make my own font and
font-rendering code (which is pretty unoptimized, but for now it
doesn't matter since I'm only using text for debugging). I used
Krita to draw the font because it's easier to
draw pixel-by-pixel with it than with Gimp.
Here's an image of the 6x8 font:
The new font
(The yellow/greenish background is what I used when drawing the font.)
I'm not sure what I'll do next: probably either sound or network. For
network, I haven't even decided if I'll use ESP32's "ESP-NOW" protocol
or normal WiFi/IP, but it will be fun to investigate the pros and cons
of each one.