Trade storage for code - stretching a font in real time
I've published several Arduino libraries for controlling inexpensive OLED and LCD displays, for example, my oled_96 library drives SSD1306/SH1106 OLEDs. It's gone through several iterations with additional functionality added. Recently I was using it on a new ATmega32u4 (Arduino Leonardo) project and ran out of FLASH space. I wanted to keep using the large fixed font for the project, but the font data takes up 6K of the 28K available. My first idea was to just #ifdef the font out of existence on targets with small amounts of FLASH memory.
I couldn't move forward with my project until I solved the space problem and then decided that a slightly blocky looking large font would be better than no large font. The code to stretch the pixels isn't very complicated, so I added the logic to draw the large font as a 2x2 stretch of the 8x8 font. AVR target MCUs tend to have very little FLASH space, so I singled them out as the target which would get this font stretching. Luckily there's a handy preprocessor macro defined for knowing when the target is AVR:
__AVR__
Here's the new font stretching code (this is in the inner loop for drawing each character):
This compromise works well for my project and seemed like a good feature to share on Github. The additional MCU clock cycles needed to stretch the font in real time are not noticeable. This even allows the 3 font sizes to work on something as small as an ATtiny85 (one of my favorite AVRs due to its power and constraints).
Theory of operation:
The font data is arranged the same as the pixels on the display. The bytes are oriented vertically with the LSB as the top and the MSB as the bottom. To stretch the font, I loop through horizontally as the outer loop and double the output (making it wider) of each byte generated in the inner loop. The inner loop looks at the top and bottom halves of each font byte (lower and upper 4 bits) and stretches them double tall by using a mask with 2 bits set (starts at the value 3). Each bit of the font is tested and the mask is OR'd with the output if set. The mask is then shifted left 2 places at the bottom of the inner loop. The upper and lower halves of the output (2 byte rows) are generated together, then written to the display individually. If there were more RAM available and output speed was a priority, it would be faster to create and then write the entire line of characters in a single shot as a top/bottom operation. The code to change the 'cursor' position on the display between writing the top and bottom halves slows things down not only because of the additional data to transmit, but also because of having to start/top the I2C transactions.
I couldn't move forward with my project until I solved the space problem and then decided that a slightly blocky looking large font would be better than no large font. The code to stretch the pixels isn't very complicated, so I added the logic to draw the large font as a 2x2 stretch of the 8x8 font. AVR target MCUs tend to have very little FLASH space, so I singled them out as the target which would get this font stretching. Luckily there's a handy preprocessor macro defined for knowing when the target is AVR:
__AVR__
Here's the new font stretching code (this is in the inner loop for drawing each character):
This compromise works well for my project and seemed like a good feature to share on Github. The additional MCU clock cycles needed to stretch the font in real time are not noticeable. This even allows the 3 font sizes to work on something as small as an ATtiny85 (one of my favorite AVRs due to its power and constraints).
Theory of operation:
The font data is arranged the same as the pixels on the display. The bytes are oriented vertically with the LSB as the top and the MSB as the bottom. To stretch the font, I loop through horizontally as the outer loop and double the output (making it wider) of each byte generated in the inner loop. The inner loop looks at the top and bottom halves of each font byte (lower and upper 4 bits) and stretches them double tall by using a mask with 2 bits set (starts at the value 3). Each bit of the font is tested and the mask is OR'd with the output if set. The mask is then shifted left 2 places at the bottom of the inner loop. The upper and lower halves of the output (2 byte rows) are generated together, then written to the display individually. If there were more RAM available and output speed was a priority, it would be faster to create and then write the entire line of characters in a single shot as a top/bottom operation. The code to change the 'cursor' position on the display between writing the top and bottom halves slows things down not only because of the additional data to transmit, but also because of having to start/top the I2C transactions.
Comments
Post a Comment