Nokia 3410 LCD on the STM32
The Nokia LCD displays are among my favourite toys. Generally, I use a monochrome display intended for the Nokia 3410 'phone. This a display size of 96 x 48 pixels and can display bitmaps as well as text in 6 rows of 16 characters. It is smaller than the more common 16x2 text-only displays, easier to drive, cheaper, uses fewer connections, much more flexible and is readily available. And now, I have connected one up to my STM32F103 Cortex-M3 processor. As a first go with the SPI peripheral on these processors, it has been quite instructive...
There are plenty of other places to go and find out about he Nokia displays SPI in general. There is a short list at the end of this post. Here I just want to look at the business of getting the SPI going on the STM32. As with the other examples here, I have used the ST Peripheral Driver Library and its functions relating to the SPI peripheral. No attempt has been made to bypass these to talk to the hardware as they are fast enough for my purposes and give the promise of some portability.

For this example, the LCD is connected to the SPI1 port which uses pins on Port A. These are:
| Pin name | SPI Function | Nokia Function | ||
|---|---|---|---|---|
| PA4 | SPI1_NSS | Display Select | ||
| PA5 | SPI1_SCK | Serial Clock | ||
| PA6 | SPI1_MISO | Data/Command | ||
| PA7 | SPI1_MOSI | Serial Data | ||
| PA3 | none | Display Reset |
The SPI functions of these pins are in the alternate function set. that is, after a reset, the pins work as plain GPIO pins so the appropriate settings need to be made to the port. the Nokia display is a write-only device so there is no use for the MISO pin and it can be used as a GPIO to drive the Data/Command select pin of the display. Similarly, the Slave Select pin, SPI1_NSS, will be set in software and so can be left as a GPIO. That leaves only two of the dedicated SPI pins used for their SPI function. First then, the ordinary GPIO pins are configured:
SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* Enable SPI1 and GPIO clocks */ RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI_NOKIA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_NOKIA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_CS | GPIO_Pin_DC | GPIO_Pin_RST; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIO_LED, &GPIO_InitStructure);
Then the Alternate function is setup for the SCK and MOSI pins:
/* Configure SPI1 pins: SCK and MOSI only to their Alternative (SPI) function */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SCK | GPIO_Pin_MOSI; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIO_NOKIA, &GPIO_InitStructure);
At this point, the display can be initialised by holding down the reset line for at least 10us. The Chip select is normally left high in case anoher peripheral is sharing the SPI port.
/* Deselect the display Chip Select high */ SPI_NOKIA_CS_HIGH(); SPI_NOKIA_RST_LOW(); delay(100000); SPI_NOKIA_RST_HIGH();
Finally, the SPI port itself is configured:
/* SPI1 configuration */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); /* Enable SPI1 */ SPI_Cmd(SPI1, ENABLE);
This code is taken straight out of one of the ST example programs. Notice that the SPI runs as a master device with a clock divisor of 16 which gives a clock rate of (72/16) = 4.5MHz. Also, the NSS line is set for software control. I didn’t check the SPI mode used by the port but it is correct for a Nokia 3410 display. Also lifted from an ST example was the code needed to send an 8 bit value to the display. However, this seemed to have a bit of a problem. This is similar to the issue I have had with the SPI peripheral on the dsPIC. The routine started by waiting until the transmit buffer was empty before trying to dump the byte into it. That is a precondition but, with a slow SPI clock, it was possible to load up the buffer before the preceding transfer was finished. That would cause the final test to return prematurely and the caller would release the slave select line before the slave had got all the data. What is really needed is a way to tell if the transmit shift register is empty. On the face of it, waiting for the receive buffer to fill should do the trick but if it has not been read after a previous transfer, the RXNE flag will still be set, the test succeeds and the previous value is read while the current one carries on shifting in. As a temporary fix, I added a single line to the beginning to perform a dummy read from the receive buffer and reset the RXNE flag:
/**
* @brief Sends a byte through the SPI interface and return the byte
* received from the SPI bus.
* @param byte : byte to send.
* @retval : The value of the received byte.
*/
u8 SPI_NOKIA_SendByte(u8 byte)
{
SPI_I2S_ReceiveData(SPI1);
/* Loop while DR register in not emplty */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/* Send byte through the SPI1 peripheral */
SPI_I2S_SendData(SPI1, byte);
/* Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(SPI1);
}
This works although I am not happy about it. It seems like a terrible kludge and I shall have to find a more elegant way to do this. The return value will be ignored as nothing gets sent back from the display but it is kept here as a reminder for any other use at a later date. Data sent to the display will be either a data byte or a command byte. Here are the two functions that do that:
void nokiaCmd( int c )
{
SPI_NOKIA_CS_LOW(); // Select the device
SPI_NOKIA_DC_LOW(); // sending data
(void)SPI_NOKIA_SendByte( c );
SPI_NOKIA_CS_HIGH(); // Deselect the device
}
void nokiaData( int c )
{
SPI_NOKIA_CS_LOW(); // Select the device
SPI_NOKIA_DC_HIGH(); // sending data
(void)SPI_NOKIA_SendByte( c );
SPI_NOKIA_CS_HIGH(); // Deselect the device
}
the control line toggling is done with a set of macros:
#define SPI_NOKIA_CS_LOW() GPIO_ResetBits(GPIO_NOKIA_CS, GPIO_Pin_CS) #define SPI_NOKIA_CS_HIGH() GPIO_SetBits(GPIO_NOKIA_CS, GPIO_Pin_CS) #define SPI_NOKIA_DC_LOW() GPIO_ResetBits(GPIO_NOKIA_DC, GPIO_Pin_DC) #define SPI_NOKIA_DC_HIGH() GPIO_SetBits(GPIO_NOKIA_DC, GPIO_Pin_DC) #define SPI_NOKIA_RST_LOW() GPIO_ResetBits(GPIO_NOKIA_RST, GPIO_Pin_RST) #define SPI_NOKIA_RST_HIGH() GPIO_SetBits(GPIO_NOKIA_RST, GPIO_Pin_RST)
The rest of the display code is all well proven and I have been using t for some time. it is based on code originally written, I think by Sylvain Bissonnette. Try these references for more information about the Nokia 3310/3410 displays:
- SPI data transfers
- LCD screen for Decimus
- Adding a timer and the graphical LCD
- Nokia LCD Lib
- Nokia 3310 LCD
- Nokia 3310 LCD, 84x48 pixels

Comments
Updated 27th May 2009 to
I have a fix for your dummy
/* SPI configuration */SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
.
.
.
/* Loop while BUSY in communication or TX buffer not emplty
* before selecting the AD5621
*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
csLOW();
/* Send 16-bit word through the SPI1 peripheral */
SPI_I2S_SendData(SPI1, data);
/* Loop while BUSY in communication or TX buffer not emplty
* before deselecting the AD5621
*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
csHIGH();
Thank you. I will have a
before i start let me tell
I cant offer much help on
hello peteh, I have read ur
u8 SPI_NOKIA_SendByte(u8 byte){
SPI_I2S_ReceiveData(SPI1);
/* Loop while DR register in not emplty */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
/* Send byte through the SPI1 peripheral */
SPI_I2S_SendData(SPI1, byte);
/* Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
/* Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(SPI1);
}
SPI_I2S_ReceiveData(SPI1);SPI_NOKIA_SendByte
Hi - sorry for the delay...
Nothing is returned from the nokia display. The code is simply copied from some other SendByte function in the library or an example - I forget where.
I have not looked at this for a while but you can probably leave that out. However, the first read simply makes sure that the SPI peripheral is in a suitable state to send. It too could probably be left out but I would want to have a good look at what is going on first.
I use the software slave select because I have had configurations with more than one SPI slave. Also, in a previous version of the code on a Microchip Processor, the hardware slave select did not function correctly so I ended up doing it in software.
The state of the MOSI pin should only be read by the display of edges of the SPI clock signal. It should not matter what the state of the MOSI line is at any other times.
Thanks + Broken Links
Thank you
Thank for your comments. I have repaired the broken links.
Post new comment