Transforming the NumWorks Calculator with Raspberry Pi

Transforming the NumWorks Calculator with Raspberry PiMAKERZardam/ Translated byEndless Fun

This time we will use the Raspberry Pi Zero to study STM32, focusing mainly on DMA and interrupt aspects, to modify the NumWorks calculator.Since the NumWorks calculator is powered by the STM32F412, why not try to combine them and see what happens?
If you want to experience the features of NumWorks immediately, here is an online NumWorks simulator:https://www.numworks.com/simulator/

Working Principle

1. Add an application to the calculator that will display the output from the Raspberry Pi and the key data sent from the keyboard to it.2. Drive the display on the Raspberry Pi through SPI using fbtft, and some exposed pads of NumWorks contain the SPI bus.

Transforming the NumWorks Calculator with Raspberry Pi

The video effect is as follows:

Displaying Data from SPI

The calculator firmware has completed all the hard work of the lower level (initializing the display) and provides an API for controlling the display.In fact, it is driven by FSMC (Flexible Static Memory Controller), so from the CPU’s perspective, the display can be accessed through two static addresses, one for commands and one for data, with a width of 16 bits.

For this application, the only command that needs to be set is the command for the pixel display area, which has already been implemented in the firmware.Pushing pixels is as simple as writing each pixel sequentially to the data address, and they will display on the standard monitor from left to right and top to bottom.

Therefore, it is only necessary to copy the pixels from the SPI controller to the display address.For the DMA engine, copying pixels is very simple.

The window setup is done before each frame, using the unused MISO pin in the SPI bus as the software chip select.Thus, when MISO goes low, it triggers an interrupt, sets the software chip select on the SPI controller to accept incoming data, and configures the window in the display controller to cover the entire screen.

Setting the window takes about 3µs, so the first word of DMA will be delayed.On the Raspberry Pi side, there is about a 10 µs delay between chip select and the first input byte.

Setting GPIO

The SPI pins must be set to alternate function mode to connect to the SPI controller internally.The MISO pin is kept as a normal GPIO because it is used to trigger interrupts.

GPIOA.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction);
GPIOA.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF5);
GPIOA.MODER()->setMode(6, GPIO::MODER::Mode::Input);
GPIOA.MODER()->setMode(7, GPIO::MODER::Mode::AlternateFunction);
GPIOA.AFR()->setAlternateFunction(7, GPIO::AFR::AlternateFunction::AF5);

Setting the SPI Controller

The configuration of the SPI controller is very simple.It is set to 16-bit mode, RXONLY, and software chip select.The MISO pin is repurposed for chip select.

SPI1.CR1()->setRXONLY(true);
SPI1.CR1()->setSSI(true); // Software chip select
SPI1.CR1()->setSSM(true); // Software chip select mode
SPI1.CR1()->setDFF(true); // 16 bits
SPI1.CR1()->setSPE(true); // enable

Setting the DMA Controller

To make the DMA work, you need to select the correct DMA controller, stream, and channel.Then configure the source address (here it is the SPI1 data register), the destination address (display controller data address), source and destination data width (16 bits), mode (circular), and the number of items to transfer (here it is 1, as we are in circular mode).No need to increment the source/destination address, it is always the same.

DMAEngine.SPAR(DMAStream)->set((uint32_t)SPI1.DR()); // Source
DMAEngine.SM0AR(DMAStream)->set((uint32_t)Ion::Display::Device::DataAddress); // Destination
DMAEngine.SNDTR(DMAStream)->set(1); // Number of items
DMAEngine.SCR(DMAStream)->setCHSEL(3); // SPI Channel
DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::PeripheralToMemory);
DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord);
DMAEngine.SCR(DMAStream)->setCIRC(true); // Circular
DMAEngine.SCR(DMAStream)->setEN(true); // Enable

Setting the SPI Controller to Issue DMA Requests

Enabling the RXDMAEN bit in the SPI control register completes this.

SPI1.CR2()->setRXDMAEN(true); // enable DMA requests

Setting Interrupts via the MISO Pin

This part is relatively complex to set up.There are multiple levels of abstraction between the pin and the actual interrupt handler.1. Configure the EXTI (External Interrupt/Event Controller) to trigger an interrupt line in the NVIC (Nested Vector Interrupt Controller).2. Enable the NVIC line and define the corresponding interrupt handler.Don’t forget to acknowledge the interrupt in the handler.

SYSCFG.EXTICR2()->setEXTI(Ion::Rpi::Device::ChipSelectPin, Ion::Rpi::Device::ChipSelectGPIO);
EXTI.RTSR()->set(Ion::Rpi::Device::ChipSelectPin, true);
EXTI.FTSR()->set(Ion::Rpi::Device::ChipSelectPin, true);
NVIC.NVIC_ISER0()->set(23, true);

Interrupt HandlerThere are two cases here:When CS goes low, it activates the software chip select for the SPI controller and triggers the configuration of the window in the display controller.

When CS goes high, it disables the software chip select for the SPI controller.Any data received on SPI will be discarded.

void rpi_isr() {
EXTI.PR()->set(Ion::Rpi::Device::ChipSelectPin, true);

if(GPIOA.IDR()->get(6)) {
SPI1.CR1()->setSSI(true);
} else {
Ion::Display::Device::setDrawingArea(KDRect(0,0,320,240), Ion::Display::Device::Orientation::Landscape);
*Ion::Display::Device::CommandAddress = Ion::Display::Device::Command::MemoryWrite;
SPI1.CR1()->setSSI(false);
}
}

Note:If there are issues and no error handling is done, the whole chain can get blocked.This mainly happens on the SPI controller.If the DMA reads data too slowly, it will get stuck waiting for an error acknowledgment.

Sending Raspberry Pi Display via SPI Bus

Initially, I planned to use the original fbtft, but after studying the code, I found it couldn’t be used directly because it accesses the display controller directly to optimize pixel pushing (by limiting to the areas of the screen that have changed).But I didn’t want to implement this functionality on the calculator, so I decided to write the code myself.https://github.com/notro/fbtft

Using the concepts and code from fbtft, which is another driver written by Sprite_tm and is also the vfb driver in the kernel, I assembled a “quick and dirty” Linux module that meets my needs:Push the entire framebuffer to the SPI bus.http://spritesmods.com/?art=spitfthttps://github.com/torvalds/linux/blob/ffefb181728f7b97df49ceba18cacfb6c5ee19f2/drivers/video/fbdev/vfb.c

The display is 320×240 pixels, 16bpp, so each frame is 1228800 bits.The maximum SPI frequency for the STM32F412 is 50MHz, but the Raspberry Pi cannot produce it accurately.After testing with 62.5 MHz, it works well, so theoretically the maximum frame rate is (1228800/(62.5×106))−1 ≈ 50 fps.https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md

Setting Up the Keyboard

This is the most “flexible” part of the application on the calculator (SPI and DMA run in the background).The calculator simply sends the results of the firmware’s keyboard scanning routine (64-bit bitfield) to the Raspberry Pi via UART.On the Raspberry Pi side, the daemon listens on the UART and generates kernel key codes using uinput.https://www.kernel.org/doc/html/v4.12/input/uinput.html

Using custom key mapping on the Linux side.I didn’t take this route as external Bluetooth keyboards can still be used (and I don’t know if different key mappings can be used across multiple keyboards).You can try it out.

The calculator keyboard has only 46 keys, so to map enough keys, the buttons “x, n, t” and “var” are used to switch between standard keys and numbers.Not all keys of a standard keyboard are mapped.This is worth emphasizing.

The mouse relies solely on X.Org’s mouse emulation.Pressing the power button triggers it.https://wiki.archlinux.org/index.php/Xorg/Keyboard_configuration#Enabling_mouse_keys

Here is a large chunk of source codeTo avoid affecting the reading experience, please click the original text at the end for viewing

Powering the Raspberry Pi

The board I initially used was the Raspberry Pi Zero, non-WiFi version.When powered at 2.8 V (the internal regulated voltage on the calculator), it worked well at first, and the board was equipped with an SD card reader and a transistor to control its power.I decided to reuse the SD power board to control the power of the Raspberry Pi.

Transforming the NumWorks Calculator with Raspberry PiBut later I found that not having WiFi was inconvenient, so I ultimately opted for the WiFi version.Note that the 2.8V power supply cannot run stably, the WiFi version requires a 3V power supply.Disabling the WiFi chip (“dtoverlay=pi3-disable-wifi” in config.txt) allows the Raspberry Pi to work normally at 2.8 V.https://www.cypress.com/file/298756/download

Finally, I chose to power the Raspberry Pi directly with a battery.Since I could no longer use the space occupied by the SD card, I soldered a transistor to the unconnected pins of the SD cart slot and pulled up the resistor in a “free-form” manner.

I used an NTR1P02LT1 and a 10kΩ resistor, but any P-channel MOSFET that can handle at least 100 mA current should work fine.

Transforming the NumWorks Calculator with Raspberry PiThe voltage levels are fine since all the pins used on the STM32 can withstand 5V voltage.The Raspberry Pi will start when entering the application and shut down when the calculator turns off the power.Thus, it can leave or enter the Raspberry Pi application as needed.

Installing the Calculator

The calculator fits easily into the Raspberry Pi.There are no components where the connector of the Raspberry Pi is located.I fixed it in place with double-sided tape on the HDMI interface and the display interface of the calculator.But it is a bit thick, so the original back cover cannot be used (the vertical protrusion was cut off), but it can be left in place.

Transforming the NumWorks Calculator with Raspberry PiTransforming the NumWorks Calculator with Raspberry PiTransforming the NumWorks Calculator with Raspberry Pi

Software Configuration of the Raspberry Pi

GitHub repository:https://github.com/zardam/spifbSimply install the kernel header files, compile, install, and auto-load the module.

sudo apt-get install raspberrypi-kernel-headers build-essential
git clone https://github.com/zardam/spifb.git
cd spifb
make -C /lib/modules/$(uname -r)/build M=$PWD
sudo make -C /lib/modules/$(uname -r)/build M=$PWD modules_install
sudo depmod -a

/etc/modules

spi-bcm2835
spifb
uinput

/boot/config.txt

dtparam=spi=on

# Disable HDMI output, saves some power
hdmi_blanking=2

# Enable the mini uart (/dev/ttyS0 on a PI Zero W)
enable_uart=1

# Disable LED, saves some power
dtparam=act_led_trigger=none
dtparam=act_led_activelow=on

There are two possibilities here:Directly using the framebuffer.This is the simplest method, but hardware acceleration from the Raspberry Pi GPU will not be available.Using fbcp to copy the normal framebuffer (fb0) to the SPI framebuffer (spi1).This copy incurs some CPU overhead, but allows hardware acceleration and can scale the framebuffer since the 320×240 resolution is almost unusable.

Directly using the framebufferConfiguration is the same as using fbtft./boot/cmdline.txt

fbcon=map:10

X Server

sudo apt-get install xserver-xorg-video-fbdev

/usr/share/X11/xorg.conf.d/99-fbdev.conf

Section "Device"
  Identifier "myfb"
  Driver "fbdev"
  Option "fbdev" "/dev/fb1"
EndSection

fbcpI used a branch that builds with CMake.https://github.com/Oper8or/rpi-fbcp

sudo apt-get install cmake
git clone https://github.com/Oper8or/rpi-fbcp.git
cd rpi-fbcp
mkdir build
cd build
cmake ..
make

/boot/config.txt

hdmi_force_hotplug=1
hdmi_cvt=640 480 60 1 0 0 0
hdmi_group=2
hdmi_mode=87

/etc/systemd/system/fbcp.service

[Unit]
Description=NumWorks input device
After=systemd-modules-load.service

[Service]
Type=simple
WorkingDirectory=/home/pi/rpi-fbcp/build
ExecStart=/home/pi/rpi-fbcp/build/fbcp
User=root
Group=root
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable and start the service

sudo systemctl daemon-reload
sudo systemctl enable fbcp
sudo systemctl start fbcp

Keyboard

GitHub repository:https://github.com/zardam/uinput-serial-keyboard

git clone https://github.com/zardam/uinput-serial-keyboard
cd uinput-serial-keyboard
gcc uinput.c -o uinput

Need to disable lxkeymap in lxde session configuration (only use GUI tools).Must disable the Linux serial console.In /boot/cmdline.txt, remove:

console=serial0,115200

/etc/systemd/system/nwinput.service

[Unit]
Description=NumWorks input device

[Service]
Type=simple
WorkingDirectory=/home/pi/uinput-serial-keyboard/
ExecStart=/home/pi/uinput-serial-keyboard/uinput
User=root
Group=root
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then, enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable nwinput
sudo systemctl start nwinput

Calculator

GitHub repository:https://github.com/zardam/epsilon/tree/rpiOn the computer, after installing the NumWorks SDK:

git clone -b rpi https://github.com/zardam/epsilon.git
cd epsilon
make epsilon_flash

Then connect and reset the calculator to refresh the custom firmware.

Completion

Run the NumWorks simulator in the calculator’s browser.

Transforming the NumWorks Calculator with Raspberry PiNow, the entire project is complete, and I hope you enjoy this project.

This project repository address:
http://make.quwj.com/project/185
via zardam.github.io/post/raspberrypi-numworks/
Links in the text can be clicked to view the original text at the end
Transforming the NumWorks Calculator with Raspberry Pi
More exciting content
3D Printed Arduino Color Picker
3D Printed OLED Indoor Thermometer

BrachioGraph: Raspberry Pi Drawing Machine

micro:bit + LoRa for Balloon Tracking
Deep Learning:Owl “Zombie” Detector
Making Electric Wheelchair Rearview Mirror with Raspberry Pi Zero
Arduino Projects:Ultrasonic Digital Display Ruler
Raspberry Pi DIY Real CS Equipment:Infrared Laser Gun
Transforming the NumWorks Calculator with Raspberry Pi

Leave a Comment

×