Tag Archives: Arduino

Atmel ICE, JTAG and the ATmega32U4

Having spent a lot of time working with AVR microcontrollers I decided a number of years ago to invest in a dedicated In Circuit Emulator (ICE). At the time Atmel had just released their new Atmel ICE device. This seemed to fit the bill perfectly. atmel-ice_angle1024Not only did it support  traditional JTAG debugging but also debugWIRE. Atmels own propriety debug protocol aimed at low resource devices such as the ATMega88/168/328 family. I purchased the Atmel ICE basic kit. The kit consists of the Atmel ICE and a 10 pin ribbon cable to 10 to pin JTAG (0.05″ pitch) and 6 pin (0.1″ pitch) SPI connector. The full kit available at a much greater cost shipped with a number of different cables and adaptors. As debugWIRE uses the RESET line for debugging I deemed the 6 pin SPI cable would be all I needed. Or so I thought.

Moving on a few years I find myself now working with the ATmega32U4 again (I needed USB functionality) and to my surprise this device does not support debugWIRE only JTAG. What!!! How can this be?

I was wondering if Atmel sold those additional debug cables separately? Turns out they do but they are almost £50. After shelling out just shy of £100 in the first place I wasn’t prepared to spend another £50 on cables.
adaptor
So I set about building my own adaptor. The main problem I faced was the Atmel have used 0.05″ (1.27mm) pitch connectors rather than the more readily available standard 0.1″ (2.54mm) pitch connectors. What I needed was some form of breakout board or even something to convert from the 0.05″ pitch connector to something more manageable. After trawling the net it turns out Adafruit have a Cortex SWD adaptor which in essence is a 0.05″ pitch to 0.1″ breakout board. The pin mapping is one to one so assuming you ignore the silk screen it should be easy to interface to the ATmega32U4. The best thing about this solution is the price, I managed to get two of these boards from a local supplier for just £3.

The development board I am currently using is an OLIMEXINO-32U4 an Arduino Leonardo type design from Olimex. The table below shows the Cortex SWD to AVR JTAG pin mapping. Also shown is the JTAG connections of the 32U4 (or Arduino Leonardo if you prefer) development board.

# SWD Pin JTAG Pin Olimex 32u4 Pin
1 Vin TCK A3 (PF4)
2 SWIO GND GND
3 GND TDO A1 (PF6)
4 CLK VCC VCC
5 GND TMS A2 (PF5)
6 SWO RESET RESET
7 KEY N/C N/C
8 N/C N/C N/C
9 GND TDI A0 (PF7)
10 /RST GND GND

Below you can see a picture of my setup. I am not sure if they are needed but I did add 10K pull ups to all of the control lines TCK, TDO, TMS and TDI.

board1

One thing that had not dawned on me was the fact JTAG is not enabled by default. The pins designated to the JTAG interface PF4-PF7 are used as analog inputs. So in order for JTAG to work you need to ensure the JTAGEN fuse is programmed. This can be done through the ISP interface in the normal manner. Once I had that programmed I could successfully download and debug my code on target. Result.
Advertisements

GPS Data Logging

Over the last few weeks I have been playing with a couple of U-Blox NEO-6 Global Positioning System (GPS) receivers I purchased from eBay. What I love about these receivers is they are extremely easy to use. Once powered is applied the receiver starts outputting positional information. The two receivers I have both use a UART interface. However I believe the chip set does support other interfaces including SPI and USB.

Support for these receivers from U-Blox is second to none. As well as a comprehensive manual U-Blox also provide free evaluation software known as U-Center which allows you to evaluate and test these modules in real time. The receivers may also be configured using U-Center.

By the time I received the receivers I already had a couple of projects I wanted to use them for. First off I wanted to build a standalone GPS display device similar to the Quanum GPS Logger V2. The main use for these appears to be for carrying out speed runs of radio controlled cars and planes. Although not massively expensive I figured it would be much more fun to build something similar rather than going out and buying one.

Secondly I wanted a dedicated GPS logging device. As I keen cyclist I am regularly out and about on my mountain bike and often find myself off the beaten track in the middle of nowhere. What would be nice would be the ability to record these routes and import them into Google Earth when I return home. Of course I can track these routes on my phone using Strava or My Tracks but I wanted something a bit more robust. Something I wasn’t overly worried about getting damaged.

Reinventing the Wheel

As I have already mentioned the NEO-6 receivers output positional information via a serial interface. They support two protocols a propriety binary protocol and the National Marine Electronics Association (NMEA) standard. The NMEA standard uses a simple ASCII protocol to send sentences to listening devices. The typical baud rate for this protocol being 4800 baud however my receivers came preconfigured to use 9600 baud. The default update rate for these receivers is every second which is more than adequate for logging purposes.

When it comes to importing this information fortunately Google Earth is now able to import NEMA logs directly without the need for conversion. In the past NEMA logs would have to have been converted to Keyhole Markup Language (KML) format in order to use them with Google Earth. Thankfully this is no longer the case.

So with the receiver continually outputting NMEA messages all that was required was the ability to capture these messages and save them to external media. Now there is no point reinventing the wheel and I figured there must be a whole host of data loggers out there cable of logging serial data. Sparkfuns OpenLog seemed like the ideal solution. OpenLog is an open source Arduino based data logger. Running on an ATMega328 micro controller OpenLog stores received data to an external microSD card. Cards up to 64GB are supposedly supported.

Rather than buying an OpenLog module I built one using a Arduino Nano and a microSD card breakout board. Worked out a lot cheaper in the end. The serial output from the GPS receiver was then fed directly into the Arduino Nano UART RX pin. I did make one minor change to the original OpenLog sketch. By default OpenLog creates new files with the “.TXT” extension. I changed this to “.LOG” which is file type Google Earth is looking for when importing logs. The device is powered via the USB connector, I have it attached to a portable USB power bank at the moment.

logger

I have done a couple of test runs with it and it works great I have logged a couple of bikes rides as well as a 300 miles round trip in the car. All of which imported into Google Earth perfectly. All that remains is to get it into a suitable enclosure.

 

Driving OLED Displays

In a recent project I used a small 128×64 pixel OLED display module. These modules are great because the provide a clear and vivid display while requiring no back lighting. The display I used had a Systech SSD1306 controller fitted. The internet is rife with examples of code for driving these displays so I had it up and running with fairly minimal effort.

Having decided to use these displays on another project I am currently working on I found them on the R/C model site HobbyKing. Turns out the MultiWii flight controller (Arduino based flight controller originally using gyroscopes and accelerometers from the Wii controllers) uses an add-on OLED display module which no surprise features a 128×64 OLED display driven but the SSD1306 controller. As I was already ordering from Hobby King I decided to bundle one in with my order.

When the display arrived I assumed since both modules used the same display drivers the code I had already written would work out of the box. Wrong! Come on things are never that simple. Time to start investigating. First thing was to look at the two displays see how they compare. One thing that strikes you straight away is the lack of components on the new display (yellow PCB) compared with the old display (blue PCB).

Working_labelledWorking display module.
Not_Working_labelled

Not working display module.

Next step was to start reading the data sheet to see how this controller is configured. The pin out for the connections to the display can be seen below. I have also labelled them on the pictures above.

Pin Connection Description
1 N/C No connection. (GND)
2 C2P Charge pump capacitor.
3 C2N Charge pump capacitor.
4 C1P Charge pump capacitor.
5 C1N Charge pump capacitor.
6 VBAT DC/DC converter supply.
7 N/C No connection.
8 VSS Logic ground.
9 VDD Logic power supply.
10 BS0 Protocol select.
11 BS1 Protocol select.
12 BS2 Protocol select.
13 CS Chip select.
14 RESET Driver reset.
15 D/C Data/Command select. In I2C mode, this pin acts as SA0 for slave address selection.
16 R/W Read/Write.
17 E/RD Enable Read/Write.
18 D0 Input/output. When I2Cmode is selected, D0 is theserial clock input SCL.
19 D1 Input/output. When I2Cmode is selected, D2 & D1 should be tired together andserve as SDAout & SDAin.
20 D2 Input/output.
21 D3 Input/output.
22 D4 Input/output.
23 D5 Input/output.
24 D6 Input/output.
25 D7 Input/output.
26 IREF Brightness current reference.
27 VCOMH COM signal high voltage. A capacitor should be connected between this pin and VSS.
28 VCC OEL panel power supply. A stabilization capacitor should be connected between this pin and VSS when the converter is used.
29 VLSS Analog ground.
30 N/C No connection. (GND)

The controller has an internal charge pump regulator circuit for generating the 7.5V required by the display. Two external capacitors are required. These are connected between C1P/C1N and C2P/C2N and can be seen on both displays.

Both VCC and VCOMH have decoupling capacitors down to GND as outlined in the data sheet. The brightness current is set by the resistor between IREF and GND. The working display using 910K while the non working display opting to use 560K. The 3.3V regulator provides the required logic voltage.

Interestingly it turns out the controller supports communication over I2C, SPI (3 and 4 wire) and parallel. The protocol selection pins BS0-BS2 allow different protocols to be selected. Both displays have BS0 and BS2 are tied to GND while BS2 is tied to the positive supply which as expected sets the mode to I2C.

When configured for I2C mode D0 acts as the serial clock input. The data sheet stipulates that D1 and D2 should then be connected together to act as the serial data line. On closer inspection of both displays it becomes apparent this is the case the working display (blue PCB) but not with the non working display (yellow). Another thing the working display appears to have pull up resistors connected to SCL and SDA. Something you would expect with I2C comms. The non working display has no pull ups fitted.

Having said that the non working display appears to have three unpopulated foot prints on the PCB allowing for pull resistors to be fitted and for D1 and D2 to be connected together. So the first I did was to add and a zero ohm link between D1 and D2 joining them together. I didn’t bother with any pull up resistors. After fitting the display back into my development board and powering up to my surprise it worked!!

I can only assume when configured for I2C operation D1 acts as the serial input to the controller while D2 acts as the output. Joining the two must allow the acknowledge bit set by the controller to be read by the driver. The driver could have been modified to remove the need for the acknowledgement but this would have meant changing the code to be device specific which I didn’t want to do.

One nice feature on the old display is the ability to change the slave address. In I2C operation the Data/Command pin can be configured to set the lowest bit of the slave address SA0. Allowing the slave address to be either 0x78 or 0x7A. Meaning more than one display could fitted on the same bus.

Another slight gripe is the lack of power on reset circuitry on the new display. The working display has a simple reset circuit comprising R1, C1 and D9. The RC network ensures the reset pulse is present while the supply voltage rises keeping the controller in reset while the supply stabilises. D9 allows C1 to quickly discharge on power down in order to generate a reset pulse on power up in the case of short power downs or spikes. Having the reset pin tied directly to the supply, in the case of the new display, means the reset pin will rise of the same rate as the supply which is not ideal. The track could be cut and a reset circuit added but since it worked I wasn’t going to start modifying it.

 

TM1638 Seven Segment Display Driver with Key Scan Interface

While looking for a new display on eBay recently I stumbled across a seven segment display module. The module (shown below) features 8 seven segment displays, 8 push button switches as well as 8 LEDs. All of which are controlled by one single driver IC the TM1638. I have never come across the TM1638 before, I have used similar display driver ICs like the MAX7219, but never the TM1638.

led_key

After some googling I discovered the TM1638 is manufactured by a Chinese outfit known as Titan Micro Electronics. After downloading the data sheet I begun looking to see how this driver is used. At this point I must state the data sheet, which appears to have been translated from chinese to english, badly, leaves a lot to be desired. Fortunately for me the Arduino fanboys seem to love these devices and there is no shortage of information scattered across the internet. So armed with this information I decided to purchase one.

Once it had arrived getting it working was fairly straight forward. The module does not come with schematic and I wasn’t going to bother reverse engineering the board to create one (not unless I ran into any issues I could not resolve) so it was a bit trial and error in the early stages. In the rest of the post I will demonstrate how to use this device in more detail.

Background

The TM1638 is a LED driver controller with integrated key-scan interface. Communication with the device is via a 3 wire serial interface. The device is capable to of driving up to 8 x 10 LED segments. As well as reading 24 individual key inputs. The display intensity of the LED segments can also be dynamically configured.

Connections

The supply voltage for the device is quoted as 5V ±10% however have seen evidence of people using a 3V3 supply and not having issues.

The LED segments are connected to the device via the segment output pins SEG1-SEG10 and grid output pins GRID1-GRID8. The module I purchased has 8 seven segment displays and a further 8 LEDs. These seven segment displays connect via segment output pins SEG1-SEG8 and grid output pins GRID1-GRID8. The 8 LEDs connect via segment output pin SEG9 and grid output pins GRID1-GRID8.

Image17

The push button switches are multiplexed together and read via key state output pins KS1-KS8 and key scan data input pins K1-K3. Note key state output pins KS1-KS8 share the same physical pins as the segment output pins SEG1-SEG8. The device simply alternates between driving the segment outputs and scanning the key input states.

Communication with the device is via a 3 wire serial interface comprising of a strobe (STB), data input/output (DIO) and a clock (CLK) pin. The strobe pin is used to initialise the serial interface prior to sending data. The state of the DIO pin is clocked into the device on the rising edge of the clock signal. Similarly when reading from the device the state of DIO pin may be read on the rising edge of the clock signal.

Image31

Operation

The protocol used to communicate with the TM1638 is a fairly simple one. The transfer is initialised by first pulling the strobe line low, a command byte is then sent followed by a number of optional arguments. The transfer is then terminated by returning the strobe pin high. Each command sent must begin with the strobe pin being pulled low and finish with the strobe pin being pulled high.

The device supports three command types. Determined by bits B7 and B6 of the command byte. Data instruction set 0b01 (0x4X) writes or reads data to or from the device. Display control instruction set 0b10 (0x8X) configures the device and Address instruction set 0b11 (0xCX) sets the display register address.

Image23

The first thing we want to do post power up is initialise the display. To do so we need to send the display control instruction set command 0x8X.

Image13

The table above shows the individual bit settings for this command and their relevant function. Bits B7 and B6 set the command type, fixed at 0b10 (0x8X). Bits B5 and B4 are irrelevant and set to 0b00. Bit B3 is used to turn the display ON (1) or OFF (0). The remaining bits B2, B1 and B0 configure the display intensity. They actually set the pulse width which determines the display intensity. 1/16 (0x00) being the dullest and 14/16 (0x07) being the brightest.

For instance if we wanted to turn the display ON with the intensity set to maximum we would send the command 0b10001111 (0x8F). For minimum intensity we would send the command 0b10001000 (0x88).

The data instruction set command can perform a number of functions. We can set the data write mode to either write data to a data register or read the key scan data. We can set the address add mode to either automatically increment the destination address while writing data or write to a fixed destination address. There is also a test mode set function which we will not cover here.

Image24

Bits B7 and B6 set the command type and are fixed at 0b01 (0x4X). Bits B5 and B4 are irrelevant and set to 0b00. Bit B3 is used to set the test mode which we will keep set to 0 normal mode. Bit B2 sets the address add mode (0 automatically increments the address and 1 sets a fixed address). Bits B1 and B0 set the data write mode to be either a write to a data register (0b00) or read from the key scan data register (0b10).

The address instruction set command configures the destination address/register we wish to write. There are 16 display registers in total. Bits B7 and B6 set the command type and are fixed at 0b11 (0xCX). Bits B5 and B4 are irrelevant and set to 0b00. The remaining bits B3, B2, B1 and B0 set the display address (0x00 through 0x0F). The data stored in these registers is used to drive the display segments. Which we will cover in more detail soon.

Image27

This may all look very confusing so let’s cover a couple of examples which should make things a little clearer. Let’s say we want to send the value 0x45 to display register 0x02. The address mode will be fixed since we are only writing one register. So we send the command 0x44 (0b01000100) followed by command 0xC2 (0b11000010) followed by 0x45. If we wanted to send the values 0x01, 0x02 and 0x03 to display registers 0x00, 0x01 and 0x02. The address mode will be set to auto increment since we are writing consecutive registers. We would then send the command 0x40 (0b01000000) followed by command 0xC0 followed by 0x01, 0x02 and 0x03.

To read the state of the keys we need to send the command 0x42 (0b01000010) before reading back 4 bytes containing the key states. The details of which we will cover in more detail further on.

As already mentioned the display registers allow us to set all of the individual segment states. The mapping of each segment in the display registers can be seen in the table below.

Image6

For instance segment output pins SEG1-SEG8 which control the seven segments (A-G) plus the decimal point (DP) on grid output pin GRID1 are mapped to display register 00HL (the lower 4 bits of the display register 0) and 00HU (the upper 4 bits of the display register 0). The remaining two segment output pins SEG9 and SEG10 are mapped to the lower two bits of display register 01HL. The remaining bits of display register 1 have no function. This is then repeated for digit 2 using registers 02HL/02HU and 03HL/03HU etc.

The module I have has single LEDs all connected to segment output pin SEG9 and no connections to segment output pin SEG10. Some of the modules available on eBay have bi-colour LEDs on which I assume SEG9 is used to drive one colour and SEG10 to drive the other.

Again this may appear somewhat confusing so let’s have another example. Let’s say we wanted to show ‘0’ on display digit 1. In order to show ‘0’ we need to set segments SEG1 (a), SEG2 (b), SEG3 (c), SEG4 (d), SEG5 (e) and SEG6 (f) while segments SEG7 (g) and SEG8 (DP) remain unset. We do this by loading display register 0 with the value 0b00111111 (0x3F). For display digit 2 we load display register 2 with 0b00111111 (0x3F) etc.

digits

Similarly if we wanted to energise the LED1 we would need to set SEG9 by loading display register 1 with 0b0000001 (0x01). For the LED2 we would load display register 3 with 0b0000001 (0x01) etc.

When it comes to reading the input keys. A maximum of 24 keys may be read by the device. These are all arranged in a 3×8 matrix format. The key states returned from the read key scan data command as four bytes encoded as follows.

Image10

BYTE1 contains the key input states K3 (B0), K2 (B1) and K1 (B2) for key state output KS1 and K3 (B4), K2 (B5) and K1 (B6) for key state output KS2. BYTE2 corresponds to key state outputs KS3 and KS4. BYTE 3 corresponds to key state outputs KS5 and KS6 and BYTE4 corresponds to key state outputs KS7 and KS8. Bits B3 and B7 in all four bytes are irrelevant and are ignored.

For example to read the input state of the key corresponding to input state K2 and key state output KS8 we read bit B5 of BYTE4. To read the input state of the key corresponding to input state K3 and key state output KS1 we read bit B0 of BYTE1.

After a bit of trial and error I discovered the eight keys on my board are all mapped to input state K3. So by checking bits B0 and B4 of each of the 4 bytes read I was able to determine the state of all of the 8 keys.

I have created a basic driver for the TM1638 (written in C) using Atmel Studio. The driver has been tested on a ATMega328 (Arduino Nano) development board. However the code could easily be ported to any other platform if required. All of the project files and an example implementation are all available on my GitHub account.

Binary Clock

For a while now I have fancied building a binary clock. A clock capable of showing the current time in binary format. Not only do they look great but you have the added bonus that most people probably wont have any idea what an earth they are looking at. For those who have no idea what a binary clock is I would highly recommend this Wikipedia page for more information.

To keep things simple I decided on a Binary Coded Decimal (BCD) format rather than pure binary or grey scale. With BCD the hours, minutes and seconds are all represented by individual binary digits. That way you simply add each digit together to determine the number of hours followed by the number of minutes and finally the number seconds.

I wanted something fairly portable, preferably USB powered and reasonably cheap. I ended up using an Arduino Nano clone, a 0.96″ 128×64 OLED display module and a DS3231 real time clock module. This configuration fitted the bill perfectly.

I love these little OLED displays. They are extremely bright and easy to use. The majority if not all of the ones I found on eBay use the SSD1306 display driver chip. The SSD1306 supports a number of interfaces formats including parallel, SPI  and I²C. The module I purchased uses an I²C bus.

The DS3231 I have used on lots of projects before. These devices are extremely accurate with minimal drift over time. Not a lot to say about these really the device maintains seconds, minutes, hours, day, date, month, and year information. Has two configurable alarms and even has an internal temperature sensor (which is used for temperature compensation of the oscillator) but can also be read via one of the internal registers. Data is transferred serially through an I²C bus.

The OLED display supports two colours. The very top of the display uses yellow on black while the remainder of the display uses blue on black. I decided to take advantage of this and alternate between the current time, date and temperature along the top while showing the current time in binary format below using rectangles to represent each binary bit. I am really pleased with the way it turned out.

binary_clock

The design was implemented on breadboard and all of the code written in C using the Atmel Studio development environment. Rather than continually reading the time from the DS3231 its square wave output pin was configured to output a 1Hz square wave which in turn was connected to one of the external interrupts pins on the ATMega328. Upon interrupt the interrupt handler simply sets a flag to indicate to the main exec to read the time and refresh the display. The clock has been running fine now for around four months with no issues at all. All of the project files and source code will be available in my GitHub account.

Having fun with TFT displays

Over the last couple of days I have been somewhat struggling to get a TFT display module I recently purchased from eBay working. There seems to be an abundance of posts littered all over the internet regarding these cheap Chinese TFT displays. The problem seems to be that sellers are miss stating the controller these modules are fitted with.

tft

The module I purchased allegedly had an SPFD5408 controller fitted. The reason I went for this module was that it has a Arduino Uno foot print so should be compatible with the Nucleo development board. Another bonus is it also comes with a 4 wire resistive touch screen and micro SD card socket.

Display Connections

TFT Arduino nucLEO
LCD_RST A4 PC_1
LCD_CS A3 PB_0
LCD_RS A2 PA_4
LCD_WR A1 PA_1
LCD_RD A0 PA_0
LCD_D2 D2 PA_10
LCD_D3 D3 PB_3
LCD_D4 D4 PB_5
LCD_D5 D5 PB_4
LCD_D6 D6 PB_10
LCD_D7 D7 PA_8
LCD_D0 D8 PA_9
LCD_D1 D9 PC_7
SD_CS D10 PB_6
SD_DI D11 PA_7
SD_DO D12 PA_6
SD_SCK D13 PA_5

As you can see from the pin out the controller uses an 8 bit parallel interface with a further five dedicated control pins. So using my Nucleo development board I started writing a basic driver, something to simply initialise the display and maybe draw some simple test patterns just so I could confirm it was working.

 void MW_TFT_Init(void)  
 {       
      GPIO_InitTypeDef GPIO_InitDef;  
      uint16_t id;  
      /* Enable LCD control pin clocks */  
      RCC_AHB1PeriphClockCmd(MW_TFT_RST_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_CS_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_RS_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_WR_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_RD_CLK, ENABLE);  
      /* LCD_RST */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_RST_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_RST_PORT, &GPIO_InitDef);  
      /* LCD_CS */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_CS_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_CS_PORT, &GPIO_InitDef);  
      /* LCD_RS */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_RS_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_RS_PORT, &GPIO_InitDef);  
      /* LCD_WR */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_WR_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_WR_PORT, &GPIO_InitDef);  
      /* LCD_RD */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_RD_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_RD_PORT, &GPIO_InitDef);  
      /* Enable LCD data pin clocks */  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD0_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD1_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD2_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD3_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD4_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD5_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD6_CLK, ENABLE);  
      RCC_AHB1PeriphClockCmd(MW_TFT_LCD7_CLK, ENABLE);  
      /* Set LCD data pins outputs */  
      MW_TFT_SetWriteDirection();  
      /* Set all control lines high */  
      MW_TFT_RST_HIGH;  
      MW_TFT_CS_HIGH;  
      MW_TFT_RS_HIGH;  
      MW_TFT_WR_HIGH;  
      MW_TFT_RD_HIGH;  
      /* Reset TFT */  
      MW_TFT_RST_LOW;  
      Delayms(2);  
      MW_TFT_RST_HIGH;  
      /* Resync */  
      MW_TFT_WriteData(0);  
      MW_TFT_WriteData(0);  
      MW_TFT_WriteData(0);  
      MW_TFT_WriteData(0);  
 }  
 void MW_TFT_WriteData(uint16_t data)  
 {  
      MW_TFT_CS_LOW;  
      MW_TFT_RS_HIGH;  
      MW_TFT_RD_HIGH;  
      MW_TFT_WR_HIGH;  
      /* Send high byte */  
      MW_TFT_Write8(data>>8);  
      MW_TFT_WR_LOW;  
      __asm("nop");  
      MW_TFT_WR_HIGH;  
      /* Send low byte */  
      MW_TFT_Write8(data);  
      MW_TFT_WR_LOW;  
      __asm("nop");  
      MW_TFT_WR_HIGH;  
      MW_TFT_CS_HIGH;  
 }  
 void MW_TFT_WriteCommand(uint16_t cmd)  
 {  
      MW_TFT_CS_LOW;  
      MW_TFT_RS_LOW;  
      MW_TFT_RD_HIGH;  
      MW_TFT_WR_HIGH;  
      /* Send high byte */  
      MW_TFT_Write8(cmd>>8);  
      MW_TFT_WR_LOW;  
      __asm("nop");  
      MW_TFT_WR_HIGH;  
      /* Send low byte */  
      MW_TFT_Write8(cmd);  
      MW_TFT_WR_LOW;  
      __asm("nop");  
      MW_TFT_WR_HIGH;  
      MW_TFT_CS_HIGH;  
 }  
 uint16_t MW_TFT_ReadData(void)  
 {  
      uint16_t data = 0;  
      MW_TFT_CS_LOW;  
      MW_TFT_RS_HIGH;  
      MW_TFT_RD_HIGH;  
      MW_TFT_WR_HIGH;  
      /* Read high byte */  
      MW_TFT_RD_LOW;  
      Delay(10);  
      data = MW_TFT_Read8();  
      data <<= 8;  
      /* Read low byte */  
      MW_TFT_RD_HIGH;  
      MW_TFT_RD_LOW;  
      Delay(10);  
      data |= MW_TFT_Read8();  
      MW_TFT_RD_HIGH;  
      MW_TFT_CS_HIGH;  
      return data;  
 }  
 uint16_t MW_TFT_ReadRegister(uint16_t addr)  
 {  
      MW_TFT_WriteCommand(addr);  
      return MW_TFT_ReadData();  
 }  
 void MW_TFT_WriteRegister(uint16_t addr, uint16_t data)  
 {  
      MW_TFT_WriteCommand(addr);  
      MW_TFT_WriteData(data);  
 }  
 void MW_TFT_SetWriteDirection(void)  
 {  
      GPIO_InitTypeDef GPIO_InitDef;  
      /* LCD_D0 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD0_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD0_PORT, &GPIO_InitDef);  
      /* LCD_D1 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD1_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD1_PORT, &GPIO_InitDef);  
      /* LCD_D2 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD2_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD2_PORT, &GPIO_InitDef);  
      /* LCD_D3 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD3_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD3_PORT, &GPIO_InitDef);  
      /* LCD_D4 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD4_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD4_PORT, &GPIO_InitDef);  
      /* LCD_D5 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD5_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD5_PORT, &GPIO_InitDef);  
      /* LCD_D6 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD6_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD6_PORT, &GPIO_InitDef);  
      /* LCD_D7 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD7_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_OUT;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD7_PORT, &GPIO_InitDef);  
 }  
 void MW_TFT_SetReadDirection(void)  
 {  
      GPIO_InitTypeDef GPIO_InitDef;  
      /* LCD_D0 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD0_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD0_PORT, &GPIO_InitDef);  
      /* LCD_D1 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD1_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD1_PORT, &GPIO_InitDef);  
      /* LCD_D2 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD2_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD2_PORT, &GPIO_InitDef);  
      /* LCD_D3 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD3_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD3_PORT, &GPIO_InitDef);  
      /* LCD_D4 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD4_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD4_PORT, &GPIO_InitDef);  
      /* LCD_D5 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD5_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD5_PORT, &GPIO_InitDef);  
      /* LCD_D6 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD6_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD6_PORT, &GPIO_InitDef);  
      /* LCD_D7 */  
      GPIO_InitDef.GPIO_Pin = MW_TFT_LCD7_PIN;  
      GPIO_InitDef.GPIO_Speed = GPIO_Speed_50MHz;  
      GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;  
      GPIO_InitDef.GPIO_OType = GPIO_OType_PP;  
      GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;  
      GPIO_Init(MW_TFT_LCD7_PORT, &GPIO_InitDef);  
 }  
 void MW_TFT_Write8(uint8_t data)  
 {  
      if( data & 0x80 ) MW_TFT_LCD7_HIGH;  
      else MW_TFT_LCD7_LOW;  
      if( data & 0x40 ) MW_TFT_LCD6_HIGH;  
      else MW_TFT_LCD6_LOW;  
      if( data & 0x20 ) MW_TFT_LCD5_HIGH;  
      else MW_TFT_LCD5_LOW;  
      if( data & 0x10 ) MW_TFT_LCD4_HIGH;  
      else MW_TFT_LCD4_LOW;  
      if( data & 0x08 ) MW_TFT_LCD3_HIGH;  
      else MW_TFT_LCD3_LOW;  
      if( data & 0x04 ) MW_TFT_LCD2_HIGH;  
      else MW_TFT_LCD2_LOW;  
      if( data & 0x02 ) MW_TFT_LCD1_HIGH;  
      else MW_TFT_LCD1_LOW;  
      if( data & 0x01 ) MW_TFT_LCD0_HIGH;  
      else MW_TFT_LCD0_LOW;  
 }  
 uint8_t MW_TFT_Read8(void)  
 {  
      uint8_t data = 0;  
      if( MW_TFT_LCD7_STATE ) data |= 0x80;  
      if( MW_TFT_LCD6_STATE ) data |= 0x40;  
      if( MW_TFT_LCD5_STATE ) data |= 0x20;  
      if( MW_TFT_LCD4_STATE ) data |= 0x10;  
      if( MW_TFT_LCD3_STATE ) data |= 0x08;  
      if( MW_TFT_LCD2_STATE ) data |= 0x04;  
      if( MW_TFT_LCD1_STATE ) data |= 0x02;  
      if( MW_TFT_LCD0_STATE ) data |= 0x01;  
      return data;  
 }  

Display Initialisation

I find the biggest issue with any TFT display module is initialisation. There are normally a whole host of registers within the controller which need setting up before you can start writing to the display. Now I am not a big fan of datasheets. They are a necessary evil in my opinion. Some times you have no choice. But if I can figure things out without having to refer to a couple of thousand pages a broken English then all the better.

With that in mind I did a quick search for the controller supposedly fitted to the module, hoping to find example code or maybe an Arduino sketch which I could extract the initialisation sequence from. To cut a long story short I ported sequences from numerous sources trying to get this module working to no avail. Rather worryingly it was looking like I may have to start referring to the datasheet for help after all.

Then by chance I stumbled upon an Arduino sketch which had a simple driver check built in. It turns out the first register (index 0) in these controllers often contains a controller ID. Reading this register resulted in the ID 0x7783 being returned. Now from previous hunting around I know a common controller used on these cheap TFT display modules is the ST7783. Surely it couldn’t be that simple right? Well turns out it was that simple. Armed with this information I found a sample initialisation sequence and surprise surprise the display now initialises perfectly.

 void MW_TFT_SendInitSequence2(void)  
 {  
      MW_TFT_WriteRegister(0x00FF, 0x0001);  
      MW_TFT_WriteRegister(0x00F3, 0x0008);  
      MW_TFT_WriteCommand(0x00F3);  
      MW_TFT_WriteRegister(0x0001, 0x0100);  
      MW_TFT_WriteRegister(0x0002, 0x0700);  
      MW_TFT_WriteRegister(0x0003, 0x1030);  
      MW_TFT_WriteRegister(0x0008, 0x0302);  
      MW_TFT_WriteRegister(0x0009, 0x0000);  
      MW_TFT_WriteRegister(0x0010, 0x0000);  
      MW_TFT_WriteRegister(0x0011, 0x0007);  
      MW_TFT_WriteRegister(0x0012, 0x0000);  
      MW_TFT_WriteRegister(0x0013, 0x0000);  
      Delay(1000);  
      MW_TFT_WriteRegister(0x0010, 0x14B0);  
      Delay(500);  
      MW_TFT_WriteRegister(0x0011, 0x0007);  
      Delay(500);  
      MW_TFT_WriteRegister(0x0012, 0x008E);  
      MW_TFT_WriteRegister(0x0013, 0x0C00);  
      MW_TFT_WriteRegister(0x0029, 0x0015);  
      Delay(500);  
      MW_TFT_WriteRegister(0x0030, 0x0000);  
      MW_TFT_WriteRegister(0x0031, 0x0107);  
      MW_TFT_WriteRegister(0x0032, 0x0000);  
      MW_TFT_WriteRegister(0x0035, 0x0203);  
      MW_TFT_WriteRegister(0x0036, 0x0402);  
      MW_TFT_WriteRegister(0x0037, 0x0000);  
      MW_TFT_WriteRegister(0x0038, 0x0207);  
      MW_TFT_WriteRegister(0x0039, 0x0000);  
      MW_TFT_WriteRegister(0x003C, 0x0203);  
      MW_TFT_WriteRegister(0x003D, 0x0403);  
      MW_TFT_WriteRegister(0x0050, 0x0000);  
      MW_TFT_WriteRegister(0x0051, st7783Properties.width - 1);  
      MW_TFT_WriteRegister(0x0052, 0X0000);  
      MW_TFT_WriteRegister(0x0053, st7783Properties.height - 1);  
      MW_TFT_WriteRegister(0x0060, 0xa700);  
      MW_TFT_WriteRegister(0x0061, 0x0001);  
      MW_TFT_WriteRegister(0x0090, 0X0029);  
      MW_TFT_WriteRegister(0x0007, 0x0133);  
      Delay(500);  
      MW_TFT_WriteCommand(0x0022);  
 }  

So the moral of this story don’t always believe everything you read especially on eBay.