When I need to find out how to control a piece of hardware, my sure-fire method of “research” is … to ask Google. But in this particular instance, getting the information I needed has been surprisingly difficult!
If you Google “ILI9341 Raspberry-Pi” you’ll get lots of tutorials … but these all concentrate on how to add and configure a pre-made driver so that the screen becomes the primary display device (via the framebuffer). And this is perfectly understandable – most people who are looking at connecting screens to their Pi want to then run X-Windows on it, or run an emulator and build a mini arcade machine.
I didn’t want to go this route – I wanted to write the SPI code myself, controlling the display directly so as to avoid unnecessary dependencies and keep things minimal. It would also make it nice and easy to leave the primary display free as a place to send printfs for debugging and profiling.
I already had code examples of hardware-SPI access on the Raspberry Pi (like the Raspberry-Pi version of my binary clock) so I was just missing the information on what data to send to the screen, particularly around initial setup. Usually I find that once I’ve got something initialised properly (you can see the screen do something) then iterating on that becomes quite straightforward – but the setup work before that is often the hardest!
The missing information came from cross-referencing the ILI9341 datasheet, multiple threads and forums, and examples for the screen intended for the Arduino and PIC platforms. These are much simpler beasts, so the code tends to be more “lean”.
I present my notes here (and working example code) so that hopefully, any weary developer who finds their way here will have a much easier time of it than I did. Although I’m writing this as part of my Raspberry-Pi Desk Clock project, I will make no other mention of it in this post – this is just about the mechanics of how to control a screen. If this blog saves you any time and stops you yanking out your (remaining) hair, flipping your keyboard and kicking the cat … please show your appreciation with the “Donate” button on the right. Thank you!
The SPI Protocol
The “Serial Peripheral Interface” protocol is intended for the microcontroller in a product to control peripherals, usually on the same PCB. For example: it’s a common way for a microcontroller to communicate with displays, touchscreens, memory cards, accelerometers, joysticks, and so on. Ben Eater’s rather excellent video on Youtube is worth a watch if you haven’t yet seen it.
You might have seen the phrases “software SPI” and “hardware SPI”. In hardware SPI, the microcontroller has dedicated circuitry for communicating over SPI. This makes our life easier as programmers, because we just ask the microcontroller to send some data, and then don’t worry about it any further. We also tend to get faster data rates this way.
“Software SPI” is when the microcontroller doesn’t have any dedicated hardware for the job, so we must write the code to implement the protocol ourselves … pulse pins at the right rate, listen for data coming back, and so on. You’ll most often see software SPI implemented on retro systems that are utilising newer peripherals. For example: many projects that use SD-cards to replace floppy drives or cassettes on 8-bit computers must implement software SPI, as that’s the standard way of accessing SD-cards.
Thankfully, the Raspberry-Pi supports hardware SPI: the older models (with 26-pin connectors) have one interface, and the newer models (40-pin connectors) have two. In this blogpost, I’ve chosen to use the hardware-SPI interface that’s common to all models.
Wiring between screen and Pi
I bought a 26-way (13×2) DuPont housing, and an eight-way F-F DuPont pre-made cable. Then I removed the eight-way housing from one end of the cable (using a tiny flat-head screwdriver to lever-up the eight clips) and inserted the eight wires into the 13×2 housing instead. (Actually, I bought a big bag of DuPont housings, and a big bag of cables – just in case I ever get to the point of selling kits for this project. Give me a shout if you need any.)
I didn’t have to make this connecting cable – I could have just found eight individual F-F DuPont wires and connected them individually. But I’ve found that as I have a number of different sized screens to hand, switching between them with this cable is now really convenient, and the connections feel stronger. I strongly recommend that you do this if you’re attempting a similar project.
I don’t expect you to work out the wiring from the above photo. Here it is:
The DC/RS and RESET pins are two extra connections that the screen requires in addition to the four SPI pins. For this reason, they can be connected to any spare GPIO pins on the Pi … I’ve chosen 24 and 25.
Software
I’ve tried to keep the example code as simple and commented as possible – it’s for illustrative purposes, rather than production-quality code!
- ili9340.cpp and ili9340.h is a class to represent the screen in your code. It contains all the BCM2835 API calls (to communicate over SPI) plus the data and commands to send initialisation calls, setting any clipping rectangle, and so on.
- main.cpp is the code that creates an instance of this class, initialises it, and generates a pattern for display. When experimenting, I’d say this is the place to start: make a more interesting pattern than mine!
- wiring.h is my typical way of describing which pins I use in a project, plus comments about wiring the screen.
To try this for yourself, download my archive to your Pi:
Unpack it with:
tar xzvf spiscreentest.tar.gz
Have a quick read of the readme.txt about installing the BCM2835 library and building the executable. At the end of it, you should see something like this:
And that should be enough to get you started!