Draw Phase
Draw commands defines where and what to draw on the screen. The SmolPhone supports the following draw commands:
Border: transparent rectangle with solid borders.Fill: rectangle filled with a given color and without borders.FillCircle: circle filled with a given color and without borders.BinaryImage: an image.Text: a line of text without breaking.
A draw command has a bounding box that determines where to draw on the screen.
In contrast to most render phase, the SmolPhone delays the rendering of text into glyphs to the draw phase. This saves memory by avoiding the insertion of each glyph into the display list. This comes at the cost of redundant computations, because the glyphs of a line of text are rendered for each lines of pixels. For example, if the font height is 16 pixels, then the glyphs of a line of text have to be rendered 16 times.
Sending to the display
The SmolPhone prototype uses the Sharp Memory-in-pixel LCD LS027B7DH01.
It is a 400x240 monochrome display.
The display is connected to the microcontroller board via the Serial Peripheral Interface (SPI) protocol.
The display supports an SPI clock speed of up to 2 MHz, enabling 20 frames per second. In practice, it can often be overclocked. For example, the Playdate uses the same display and supports from 30 to 50 frames per second. Its SPI connection is thus overclocked to at least 5 MHz.
The display is able to update multiple lines within a single SPI transaction.
Each line consists of 400 bits with a prefix of 16 bits that contains the line number.
The transaction ends with a 16 bits trailer of dummy data.
Thus, to update n lines in a single SPI transaction, we transmit L(n) bits.
\( L(n) = \begin{cases} 0 & \text{if } n = 0\\ 416n + 16 & \text{otherwise} \end{cases} \)
This takes LT(n) seconds and allows sending LPS(n) transactions per second.
\( \text{LT}(n) = \frac{L(n)}{F} \)
\( \text{LPS}(n) = \frac{F}{L(n)} \)
To update the entire display with n-lines transactions (\( 0 < n <= 240 \)),
U(n) bits are transmitted.
\( U(n) = \frac{240}{n}L(n) + L(240 \mod n) \)
Given a SPI clock speed of F hertz, the SPI connection is able to transmit F bits per second.
The theoretical time to update the entire display is FT(n) and the number of frames per second is FPS}(n).
\( FT(n) = \frac{U(n)}{F} \)
\( \text{FPS}(n) = \frac{F}{U(n)} \)
\( \text{FPS}(n) \) forms a hyperbola with an asymptote at around 20 frames per second,
as depicted in Figure 1.
See the Figure 2 for the values obtained.
To reduce the number of values we only reported the factors of 240 for n.
Figure 1: Theoretical frames per second when updating the entire display in batches of n lines with a SPI clock speed of 2 MHz.
| n | LT(n) in ms | LPS(n) | FT(n) in ms | FPS(n) |
|---|---|---|---|---|
| 1 | 0.22 | 4629.63 | 51.840 | 19.29 |
| 2 | 0.42 | 2358.49 | 50.880 | 19.65 |
| 3 | 0.63 | 1582.28 | 50.560 | 19.78 |
| 4 | 0.84 | 1190.48 | 50.400 | 19.84 |
| 5 | 1.05 | 954.20 | 50.300 | 19.88 |
| 6 | 1.26 | 796.18 | 50.240 | 19.90 |
| 8 | 1.67 | 598.09 | 50.160 | 19.94 |
| 10 | 2.09 | 478.93 | 50.112 | 19.96 |
| 12 | 2.50 | 399.36 | 50.080 | 19.97 |
| 15 | 3.13 | 319.69 | 50.048 | 19.98 |
| 16 | 3.34 | 299.76 | 50.040 | 19.98 |
| 20 | 4.17 | 239.92 | 50.016 | 19.99 |
| 24 | 5.00 | 200.00 | 50.000 | 20.00 |
| 30 | 6.25 | 160.05 | 49.984 | 20.01 |
| 40 | 8.33 | 120.08 | 49.968 | 20.01 |
| 48 | 9.99 | 100.08 | 49.960 | 20.02 |
| 60 | 12.49 | 80.08 | 49.952 | 20.02 |
| 80 | 16.65 | 60.07 | 49.944 | 20.02 |
| 120 | 24.97 | 40.05 | 49.936 | 20.03 |
| 240 | 49.93 | 20.03 | 49.928 | 20.03 |
Figure 2: Theoretical time and frames per second when updating the entire display in batches of n lines with a SPI clock speed of 2 MHz.
Only n that are factors of 240 are reported.
Figure 3 highlights the importance of updating as few lines as possible. For example, if we update 24 lines in batches of 8 lines, we can perform \( \frac{\text{LPS}(8)}{\frac{24}{8}} \approx 199 \) updates per second.
To measure the time necessary to update the entire screen in real conditions, we created a program that updates every line of the screen on each iteration. Lines are updated in order. Thus, if we update the display by batches of 120 lines, then the first transaction updates the first 120 lines. We found that the first iteration took longer than the others. We also found that excluding this first iteration caused the average to stabilize more quickly, after around five to ten iterations. Thus, we decided to treat the first ten iterations as a warmup, computing the average time taken to update the entire screen over the next twenty iterations.
The transmission of data on the SPI can be managed by the microcontroller or by the Direct Memory Access controller (DMAc). In the Rust ecosystem there exists two main Hardware Abstraction Layers (HAls) for the RP2350: rp-hal and embassy. We implemented our own DMA adapter for the rp-hal. Embassy provides a built-in support or DMA and puts the microcontroller into sleep mode when the DMA is used. So, we tested our program in four different settings, varying the HAL and the method by which the transmission is managed, i.e. by the MCU or the DMA controller.
The Figure 3 shows the obtained times for a SPI clock speed of 2 MHz.
The Figure 4 and the Figure 5 show the obtained times for a SPI clock speed of 5 MHz.
Embassy is slightly slower than rp-hal.
However, the difference narrows as n increases.
Using the DMA controller to manage the transmission is slower than using the MCU at 2 MHz.
Conversely, it is much quicker at 5 MHz.
We found than using the DMAc provides gains above 2.5 MHz.
One advantage of using the DMAc is the ability to execute other tasks while the transmission is in progress.
| n | rp-hal MCU | rp-hal DMA | embassy MCU | embassy DMA |
|---|---|---|---|---|
| 1 | 52.962 | 53.034 | 53.235 | 53.965 |
| 2 | 51.862 | 51.902 | 52.002 | 52.391 |
| 3 | 51.496 | 51.923 | 51.595 | 51.867 |
| 4 | 51.310 | 51.330 | 51.385 | 51.609 |
| 5 | 51.211 | 51.227 | 51.273 | 51.461 |
| 6 | 51.139 | 51.151 | 51.190 | 51.354 |
| 8 | 51.046 | 51.056 | 51.087 | 51.224 |
| 10 | 50.997 | 51.001 | 51.026 | 51.144 |
| 12 | 50.959 | 50.963 | 50.984 | 51.092 |
| 15 | 50.924 | 50.926 | 50.944 | 51.040 |
| 16 | 50.914 | 50.916 | 50.932 | 51.027 |
| 20 | 50.887 | 50.888 | 50.902 | 50.988 |
| 24 | 50.869 | 50.869 | 50.982 | 50.962 |
| 30 | 50.851 | 50.850 | 50.861 | 50.935 |
| 40 | 50.832 | 50.831 | 50.841 | 50.909 |
| 48 | 50.823 | 50.822 | 50.830 | 50.896 |
| 60 | 50.814 | 50.812 | 50.820 | 50.883 |
| 80 | 50.804 | 50.806 | 50.820 | 50.873 |
| 120 | 50.795 | 50.797 | 50.810 | 50.860 |
| 240 | 50.785 | 50.787 | 50.800 | 50.847 |
Figure 3: Real time to update the entire screen in batches of n lines with a SPI clock speed of 2 MHz.
| n | rp-hal MCU | rp-hal DMA | embassy MCU | embassy DMA |
|---|---|---|---|---|
| 1 | 23.741 | 21.163 | 24.014 | 22.093 |
| 2 | 23.241 | 20.655 | 23.386 | 21.149 |
| 3 | 23.074 | 20.486 | 23.176 | 20.832 |
| 4 | 22.988 | 20.399 | 23.068 | 20.676 |
| 5 | 22.949 | 20.358 | 23.015 | 20.593 |
| 6 | 22.917 | 20.324 | 22.973 | 20.528 |
| 8 | 22.874 | 20.281 | 22.919 | 20.449 |
| 10 | 22.854 | 20.258 | 22.888 | 20.402 |
| 12 | 22.838 | 20.241 | 22.866 | 20.370 |
| 15 | 22.822 | 20.225 | 22.846 | 20.338 |
| 16 | 22.817 | 20.220 | 22.840 | 20.331 |
| 20 | 22.805 | 20.207 | 22.824 | 20.307 |
| 24 | 22.797 | 20.199 | 22.814 | 20.292 |
| 30 | 22.789 | 20.191 | 22.804 | 20.276 |
| 40 | 22.781 | 20.182 | 22.793 | 20.260 |
| 48 | 22.777 | 20.178 | 22.788 | 20.253 |
| 60 | 22.772 | 20.174 | 22.783 | 20.244 |
| 80 | 22.767 | 20.173 | 22.787 | 20.240 |
| 120 | 22.763 | 20.169 | 22.782 | 20.232 |
| 240 | 22.759 | 20.165 | 22.777 | 20.224 |
Figure 4: Real time to update the entire screen in batches of n lines with a SPI clock speed of 5 MHz.
Figure 5: Real time to update the entire screen in batches of n lines with a SPI clock speed of 5 MHz.