Whassup, guys ? I know this series started off with a pretty arid topic, but we’re now getting to the payoff. Our first peripheral driver / library is dedicated to a pretty useful device : an LCD module. Specifically, this library is dedicated to the ILI9341 SPI liquid crystal display controller and, by extension, to any cheap display module based on it.
Starting this series with a cheap LCD isn’t innocent, off course : it will provide us with an excellent feedback device as we move on to other peripherals. If anything, it’s also great for showing off or answering you mom when she asks yet again “but what exactly are you doing all day on your computer instead of going outside and dating ?”
This article will follow a different format than what I’ve written before. I simply can’t give a full documentation of such a complex library in a two thousand words blog post. In fact that wouldn’t even be enough to cover everything that can be done with this library. Instead I’m going to use this article to tell you how to get the code and how to run it on your hardware. Then I’ll discuss interesting details about it, because I’m the Q to your James Bond and I like to say “no pay attention”.
Formal documentation will be with the code, in my ESP8266 Git repository.
Why the ILI9341 ?
First and foremost, you may have noticed that I tend to write about cheap components. As in “inexpensive”, not “crap”. That’s because I don’t want you to go bankrupt. You can find ILI9341 LCD modules, some with touchscreen, for five US dollars on average. Simply consult your usual Chinese drop-shipping website. For that price, I recommend you get a few more than you need just to have them on hand. Everything is better with a display. And easier to debug.
The most common ILI9341 module on the market is probably this puppy :
It’s available with or without touchscreen though the price difference is non-measurable. It’s even got a full size SD Card socket. You can read the specs on the silk screen :
- 2.4″ diagonal (that’s 6.1 cm in reasonable people units)
- 240 x 320 pixels
What’s not on the silkscreen : those are 18-bit color pixels, meaning you can display some pretty accurate photos on it. If this were 1981. The resolution is constrained by the ILI9341, which integrates its own frame buffer and pixel array drivers for this particular resolution.
Another thing that makes the ILI9341 very interesting is its SPI interface. If you don’t know yet, SPI is simple and clean, and very fast. Fast enough you could almost play video on this display module.
Connect this LCD to your ESP8266
The ILI9341 uses all four signals of a typical SPI interface plus two discrete signals : a “command / data” selection input and a “reset” input to, what else, reset the ILI9341. Here’s how to connect them to the WeMos D1 Mini (or NodeMCU : the pin naming is the same) :
|WeMos / NodeMCU pin||ESP8266 pin||ILI9341 Display Module|
|RST||RSET#||RESET, (re)initializes the ILI9341. Active low.|
|D0||GPIO16||C/D, CD, command / data : a very common pin on display controllers|
|D5||GPIO14 / SCK||SCK : SPI data clock signal. SPI is basically a shift register, very simple|
|D6||GPIO12 / MISO||MISO : Master In / Slave Out data signal. ESP8266 is the master.|
|D7||GPIO13 / MOSI||MOSI : Master Out / Slave In data signal. ILI9341 is the slave.|
|D8||GPIO15 / SSEL||CS or SSEL : SPI Slave Select|
|3.3 V||VCC||VCC : if you module needs 5V, use 5V, duh !|
|3.3 V||VCC||LED : some modules have a backlight control input. This enables it.|
|GND||–||GND : ground. Because it’s good to stay grounded.|
The display module’s LED input is not an ILI9341 signal : the LCD on your module has an LED backlight that needs to be powered. You won’t see anything otherwise. Most modules implement a transistor circuit to control that backlight. The idea is to let you use pulse width modulation to control backlight intensity… however it’s not exactly blinding. Tie this input to 3.3V so the LED’s run at full power all the time and just forget about it.
Prototyping board (just a suggestion)
You’ve got pins on your modules, so of course you could use what our Chinese friends call “Dupont wires” to connect the ESP8266 to the LCD module. However I suggest you build something more permanent. Why, you ask ? In future articles I’ll use the LCD as a debugging tool and of course as a nice user interface. Eventually you will yearn for something more durable and easier to modify than a multicolored plastic rat-nest.
I suggest you buy some nice big prototyping boards from China. They go for about two or three dollars a pop. Some of the most useful types have stripes instead of pads and look like this :
Big is not just beautiful, it’s also practical if you’re not used to soldering circuit boards. You can even make mistakes and try again a few holes away from a disastrous previous attempt. Pro tip : that LCD module is big enough to hide freaking craters if you’re really clumsy. Pro tip #2 : use coffee a lot, and not on the board itself.
Of course you could solder the modules directly to the board, but you could also use some male and female pin strips to simplify assembly and maintenance. The best way to solder those pin strips ? Mount them on your modules’ pins and use the modules themselves to hold them in place while you solder.
Layout your modules as you like, of course do avoid short circuits if your board has stripes. It’s a good idea to place your ESP8266 module with the USB connector near an edge so you can access it easily. You can scroll down to check out my layout if you need inspiration. I’m not saying you should replicate it, but I do have plans for all that board space and I know you know I know what I’m doing.
If you have buses on your board, a great idea is to use them for your power supply rails (5V, 3.3V, GND). Those tend to fan-out a lot, so this will reduce the amount of wires on your board, making it cleaner and more reliable.
You’ve only got nine connections to solder, I’m certain you can do it. See you on the other side.
Now get the code…
You’re obviously being serious about developing code for the ESP8266, so you haven’t failed to follow the instructions I gave in the previous article. Thus you have a clone of my Git repository on your PC, complete with a template Eclipse project. Getting the code for this article means you need to update your repository from its origin (mine).
In Eclipse, select the template project. From the Git menu, choose “Fetch from Upstream“. This will download from GitHub the history of every new modification I’ve made to the template project. At that point, your local repository hasn’t been modified yet : it is simply “aware” that there’s new stuff.
In the Git repositories view, expand the ESP8266 repo. If you are edgy enough to be reading this article shortly after I published it, you should see this :
Branches are basically different versions of the template project, and I create at least one for each peripheral I write a library for. This means your local repo can be missing branches (otherwise, skip this section).
After a fetch, you’ll find any new branch(es) under “Remote Tracking“. In this instance, double click the “origin/ILI9341” branch to check it out. When asked, choose “checkout as new local branch“, which means a copy of that branch will be added to your repo.
Keep the default settings on the next dialog and you’ll not only get the new branch, it’ll also become your working branch. This means you can directly set the makefile’s COM port, build the template project and download it to your ESP8266. If you have wired your hardware correctly, you’ll see a demonstration of the library’s capability on your LCD. Much better than a blinking LED, right ? And you won’t have written a single line of code. How nice is that ?
… or update the code
If you already have the ILI9341 branch locally, you may want to update it. That process is a bit different : start with “Fetch from Upstream” again then double-click on your local ILI9341 branch to check it out. Now right-click on the Remote Tracking version of the ILI9341 branch and select merge. This will update your local branch with every additional version I’ve committed since your last update.
If you somehow messed-up and you get, for instance, a blinking LED instead of an LCD demo (or the opposite), then you’ve merged the wrong branches together. That’s easily fixed : from the Git repositories view you can delete the offending local branch(es) and checkout copies of the Remote Tracking branches again. That’s one reason I recommend you copy the template whenever you start a new project instead of working directly in your repository.
Now run the code
Double-click the ILI9341 branch of the repo to check it out (you could say “make it active”), plug in your USB cable and make the “flash” target for the template project. Once it downloads, you will see this :
Truer words were never printf’ed. They actually caused my autofocus to tremble in awe. Sorry about that, it will be severely punished of course.
Quick words on my wiring technique, because I know you’re going to ask : I use single-strand wire intended for wire-wrapping (30 AWG or roughly 0.05 mm² section). This wire is perfect for this kind of prototyping, it is cheap and is more than enough copper for 3.3 V signals. Plus the single strand gives it stiffness which is helpful during assembly. I also use some of that wire, stripped, to channel and tie together bundles of wires. The result is very compact, durable and quite maintainable. And it looks daym professional.
Interesting details about the code
Each branch in my repository is named for a particular peripheral. All branches are identical with the only exception being the user_main.c file. This file is where you start writing your own code, you’re meant to replace it with your stuff. I use it to provide you with an example targeting the device or library the branch is named for.
To see this in action, open user_main.c in Eclipse and check-out the master branch, then the ILI9341 branch again. You’ll notice the code in the source file changes automatically between the LED blink example and the LCD demo.
I’ve designed this feature so you can choose a starting point that is closest to what you want you’re trying to do. Checkout the optimal branch, copy the template project to your workspace, and you’re ready to go. You can still come back to the template and pilfer demo code from other branches, which is a good idea if your project uses several different peripherals.
Now, about the code in the ILI9341 user_main.c, there are two things worth noting :
First, it sets the ESP8266 to run at 160 MHz instead of the default 80 MHz. I thought you might want to know how to do that… and the good news it, it’s only one line of code :
system_update_cpu_freq(160); // Go to 160 MHz
Second, the ILI9341 library isn’t entirely thread-safe, meaning you can get display glitches if two or more FreeRTOS tasks use the display at the same time. It shouldn’t matter, though, because you should always confine display code to a single task dedicated to displaying.
Library documentation and future evolution
The repository contains a “documentation” folder. Rummage around, you should find what you need.
I’m not made of time (yet) and writing and documenting libraries is a fairly long process when done seriously. Make sure to check once in a while if your favorite repo has had any updates. Even if the code hasn’t changed, the documentation might have grown.
There’s also documentation in the code itself : don’t hesitate to read the library’s source files, I just love to comment on why I’m doing, and posting photos of my meals on Instagram is way too mainstream.
At this time my library focuses on displaying character strings and numbers. Because it’s helpful as I develop other projects and libraries. Eventually I plan to add graphics functions, the usual lines and circles and stuff. If you read this ten years from now, and ESP8266 is still a thing, and I haven’t done it, you are hereby entitled to send me a scathing e-mail.
You can now display text and numbers, and really fast too. What else do you want me to say ? It’s kind of a big deal.
Next time… maybe I’ll look deeper into the SPI library I rely upon to access the ILI9341. And then we could play with the touchscreen on our little display module : it also uses SPI ! What a coinkydink !
SPI is a very important interface on the ESP8266 : it’s the only one fast enough to let you exploit the full throughput of its WiFi interface. For this reason, I think it makes more sense to focus on SPI rather than on the UART or I²C. I hope you agree, but it’s not like you can change my mind. But don’t worry, I’ll get to those slow, lumbering interfaces. Eventually.
Until next time, I bid you good bye !