May 30, 2020

SSD1306: OLED Screen for Motor Control!!


Posted on May 30, 2020 by admin

A while ago I bought a bunch of cheap 128×64 OLED screens off eBay to attach to random things. They were cool but I never really progressed to using them in actual projects. But in this time of Corona quarantine, I decided to give the screens a go again.

It is pretty easy to find arduino libraries for these screens. I wasted no time porting one of the simpler libraries for use on an STM32F401, mostly just changing it to use the mbed I2C calls. This proved to be actually somewhat annoying, but eventually with some aggressive scoping and reading on Wikipedia it was eventually convinced to work.

Some unforeseen problems showed up pretty immediately. The most significant problem was that it takes a whopping 30 ms to write the entire screen, an eternity in microcontroller land. This is primarily due to just how horribly slow I2C is, plodding along at only 400 kb/sec. Additionally, this screen just has lots of pixels, which must all be sent over said slow I2C connection. Luckily, each I2C packet is eight pixels, but still, just sending the packets would take 20 ms even without any overhead. In order to attach a screen to a motor controller, any function would likely need to take less than 20uS to run, requiring three orders of magnitude of speed increase.

The easy way out of this problem was to attach an arduino as a middle man. I tried this scheme on the gas generator. Data was streamed out of the CAN hub over serial. I configured it to read power, RPM, and bus voltage. This scheme actually worked out pretty well.

This was cool but not ideal. I really wanted the screen to hook directly to the motor controller, and driven off the same micro.

Driving everything off the same micro is a noble goal, but requires a lot of work to make everything non-blocking. With infinite time in corona, I decided to give it a go despite my better judgement. It took a few weeks to really get this working. Here was what it took to make everything work, with some interesting notes:

-Using the I2C with registers instead of the mbed libraries. Getting the I2C to work with registers is important for future steps. I2C is a little different than SPI or UART because it requires an address byte. This is annoying because you have to wait for the address byte to be sent before sending the rest of the data. Additionally, data transmission must be ended with a specific stop condition. Configuring all this to work correctly was annoying but was eventually accomplished.

-Using the DMA to run the I2C. This is critical to make the I2C non-blocking, and why the previous step was necessary. It was slightly more challenging than expected, because it turns out that the I2C can only be configured to send a maximum of 255 bytes at once, which is only two rows of pixels. Additionally, each row needs some initialization bytes which tell it which row is being written, so effectively only one row can be written at once. This is not a huge problem, but it means that all code will write to a single row at once.

-Using a frame buffer. It is possible to write individual characters to the screen, one at a time. However, this is very slow because each data command needs a number of command bytes on top of the actual data, and those must be sent every time a new transmission is started. A much faster strategy is to write all the character bitmaps to a big buffer and then send the whole buffer at once. Because of the aforementioned I2C limitations, the buffer only stores a single row at once. Therefore, it requires eight writes to write the whole screen, but it also means the buffer can be significantly smaller, which is vaguely important for my weeny F303K8.

-Getting some different fonts! It turns out information which is only 8 pixels tall (about 2.5 mm) is really hard to see. So, I made a 16 pixel font. But, remember I can only write one 8-pixel row at once? I actually made two “fonts” which were just the upper and lower half of certain characters. A little hacky, but it worked great. But how to actually make the fonts? I made a python script which converted a bitmap image of a whole font into an array of bitmap characters! This was annoying but eventually worked.

-Shielding the wires. I was super happy when I got everything working on the bench, it felt super good! I made the fonts look nice and got everything talking over CAN. I downloaded the firmware to the electric bike and it worked! But as soon as I gave it some throttle, the screen instantly died. Not even a second, it died instantaneously as it was cremated by 160V of switching noise. I had forgotten that I2C is extremely sensitive to noise, as there are only weak pullups. It turns out that the pullups installed on the screen are on the order of 100 kohms (!) which is way too big. I should have installed some noise suppression caps, but didn’t have any on hand. The hacky solution was just to wrap another wire around the bunch and connect it to the screen ground. Surprisingly, this worked well. The I2C would still glitch out every few mins though, so I had to use the next step.

-Using a timeout on the I2C. After every few minutes the I2C would randomly glitch out, most likely due to noise. The simple solution was just to reset the I2C if it remained busy for over a second. With this, the screen FINALLY worked. And work it did- it’s actually super nice to have a lot of information while driving.

Finally, with everything working, I attached a screen to the electric bike. The eventual plan is to attach a screen to the motor controller directly, but the micro on the current board doesn’t have enough pins. For now, I attached a screen to the dashboard nucleo. This meant I had to have the motor controller send all the information back over CAN, but luckily this wasn’t too bad. The screens come in white, blue, and combo orange/blue. I found the white text to be most easily viewable.

I had to decide what to display! I ultimately decided to have speed, battery voltage, and modulation depth shown in big text. Watt-hours consumed, distance traveled, motor rotations, and any errors present are shown in small text. I also put some blinking # signs in the upper right to show if the motor controller is alive.

Errors are labeled, in this case HV dead and current loop tracking error. Inverter over-temperature warnings can also be displayed.

It is hard to take photos while driving, but here is the photo after driving. In this case I used 366 Wh or about 2/3 of the battery in this drive. The motor did 384163 rotations, but the display rolls over at 100,000 rotations. The motor does about 20,000 rotations per mile. I should probably change it to not roll over.

The I2C shielding bonus wire. Somehow, this just worked, which was surprising. I only soldered it at one end, to the screen, because the screen wire was more exposed.

Here was the little arduino screen I made for the gas generator. It was ugly but did work.

Anyway, screens are cool!! My library can be found here. Happy screening!


0