Jump to content
david1982

ILI9341 SPI stm32f103

Recommended Posts

Hello All.

I have started to play with a new LCD ILI9341 SPI on my stm32f103.

I just cannot seam to get it to work I have connected to the correct pins as per the ILI9341 example in extras ..

Is there some thing else I should be doing when using the SPI?

My board_ILI9341.h

#ifndef _GDISP_LLD_BOARD_H
#define _GDISP_LLD_BOARD_H

#define LCD_PORT GPIOB
#define LCD_MOSI 15
#define LCD_MISO 14
#define LCD_SCK 13
#define LCD_CS 12
#define LCD_DC 11
#define LCD_RES 10

#define LCD_DC_CMD palClearPad(LCD_PORT, LCD_DC)
#define LCD_DC_DATA palSetPad(LCD_PORT, LCD_DC)
#define LCD_SCK_SET palSetPad(LCD_PORT, LCD_SCK)
#define LCD_SCK_RES palClearPad(LCD_PORT, LCD_SCK)
#define LCD_CS_RES palSetPad(LCD_PORT, LCD_CS)
#define LCD_CS_SET palClearPad(LCD_PORT, LCD_CS)

/**
* SPI configuration structure.
* Speed 12 MHz, CPHA=0, CPOL=0, 8bits frames, MSb transmitted first.
* Soft slave select.
*/
static const SPIConfig spi2cfg = {
NULL,
LCD_PORT,
LCD_CS,
(SPI_CR1_MSTR | SPI_CR1_SPE | SPI_CR1_SSM | SPI_CR1_SSI)
};

static void send_data(uint16_t data);

/**
* @brief Initialise the board for the display.
*
* @param[in] g The GDisplay structure
*
* @note Set the g->board member to whatever is appropriate. For multiple
* displays this might be a pointer to the appropriate register set.
*
* @notapi
*/
static inline void init_board(GDisplay *g) {

// As we are not using multiple displays we set g->board to NULL as we don't use it.
g->board = 0;

palSetPadMode(LCD_PORT, LCD_CS, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(LCD_PORT, LCD_DC, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(LCD_PORT, LCD_RES, PAL_MODE_OUTPUT_PUSHPULL);

spiStart(&SPID2, &spi2cfg);
spiSelectI(&SPID2);
}

/**
* @brief After the initialisation.
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline void post_init_board(GDisplay *g) {
(void) g;
}

/**
* @brief Set or clear the lcd reset pin.
*
* @param[in] g The GDisplay structure
* @param[in] state TRUE = lcd in reset, FALSE = normal operation
*
* @notapi
*/
static inline void setpin_reset(GDisplay *g, bool_t state) {
(void) g;

if (state = TRUE) {
palClearPad(LCD_PORT, LCD_RES);
} else {
palSetPad(LCD_PORT, LCD_RES);
}
}

/**
* @brief Set the lcd back-light level.
*
* @param[in] g The GDisplay structure
* @param[in] percent 0 to 100%
*
* @notapi
*/
static inline void set_backlight(GDisplay *g, uint8_t percent) {
(void) g;
(void) percent;
}

/**
* @brief Take exclusive control of the bus
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline void acquire_bus(GDisplay *g) {
(void) g;
}

/**
* @brief Release exclusive control of the bus
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline void release_bus(GDisplay *g) {
(void) g;
}

/**
* @brief Send data to the lcd.
*
* @param[in] data The data to send
*
* @notapi
*/
static inline void send_data(uint16_t data) {
// http://forum.easyelectronics.ru/viewtopic.php?p=262122#p262122
while (!(SPI2->SR & SPI_SR_TXE)); // ��� ����� �� �������� ��������� - � ������ �� SPI_DR
SPI2->DR = data; // ��������� � SPI_DR ��� �������

}

/**
* @brief Send data to the index register.
*
* @param[in] g The GDisplay structure
* @param[in] index The index register to set
*
* @notapi
*/
static inline void write_index(GDisplay *g, uint16_t index) {
(void) g;

while (SPI2->SR & SPI_SR_BSY);
LCD_CS_RES;
LCD_DC_CMD; // ��������� ������� � ����� ������
LCD_CS_SET;
send_data(index);
while (SPI2->SR & SPI_SR_BSY); // ���� ���� ���������� (==1) -- ������ SPI �����
/* ������ ���� �������� ��������� �������� ������� ��������� � ���������� �����
* ����� ������ ��� �������� �������� � ��������.
*/
LCD_DC_DATA; // ��������� ������� � ����� ������
}

/**
* @brief Send data to the lcd with DC control.
*
* @param[in] g The GDisplay structure
* @param[in] data The data to send
*
* @notapi
*/
static inline void write_data(GDisplay *g, uint16_t data) {
(void) g;

send_data(data);
}

/**
* @brief Set the bus in read mode
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline void setreadmode(GDisplay *g) {
(void) g;
}

/**
* @brief Set the bus back into write mode
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline void setwritemode(GDisplay *g) {
(void) g;
}

/**
* @brief Read data from the lcd.
* @return The data from the lcd
*
* @param[in] g The GDisplay structure
*
* @notapi
*/
static inline uint16_t read_data(GDisplay *g) {
(void) g;
return 0;
}

#endif /* _GDISP_LLD_BOARD_H */

My main.c

#include "ch.h"
#include "hal.h"
#include "gfx.h"
#include "board_ILI9341.h"

int main(void) {
coord_t width, height;

halInit();
chSysInit();
gfxInit();

// Get the screen size
width = gdispGetWidth();
height = gdispGetHeight();

// Code Here
gdispFillArc(width/2, height/2, width/4, -10, -45, White);
gdispDrawCircle(width/2+width/8, height/2-height/8, 13, Green);
gdispFillCircle (width/2+width/8, height/2-height/8, 10, Red);
gdispDrawArc(width/2+width/8, height/2-height/8, 20, 25, 115, Gray);
gdispFillEllipse (width-width/6, height-height/6, width/8, height/16, Blue);
gdispDrawEllipse (width-width/6, height-height/6, width/16, height/8, Yellow);

while(TRUE) {
gfxSleepMilliseconds(500);
}
}

Share this post


Link to post
Share on other sites

Hello david1982,

In your board file you set your SPI pins to PAL_MODE_OUTPUT_PUSHPULL mode. This way the pins really just act as normal I/Os. To use your SPI peripheral you need to set the pins to the corresponding alternating function. Furthermore the board file looks somewhat strange. You should use the high level API provided by ChibiOS/HAL to write and read from the SPI bus.

A few more things to check if it does still not work (common issues):

  • Make sure that the backlight is on
  • Start with a low SPI frequency (especially when you're using long cables

I hope that helps.

~ Tectu

Share this post


Link to post
Share on other sites

Still on good..

I have slowed down the SPI and I have redone the board file.

I also looked at the Chinese files that came with the LCD and there where some write_data differences so I corrected them in my gdisp_lld_ILI9341.c file but still nothing on the LCD.

I have connected my oscilloscope and I am defiantly getting pulses on MOSI and CS also RST.

The backlight is also differently on.

The attachment is what I got with the LCD and below are my files.

Could some one with more experience and knowledge than I please have a look.

Thank You.

board_ILI9341.h

/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
* http://ugfx.org/license.html
*/

/**
* @file boards/addons/gdisp/board_SSD1306_spi.h
* @brief GDISP Graphic Driver subsystem board interface for the SSD1306 display.
*
* @note This file contains a mix of hardware specific and operating system specific
* code. You will need to change it for your CPU and/or operating system.
*/

#ifndef _GDISP_LLD_BOARD_H
#define _GDISP_LLD_BOARD_H

// Pin & SPI setup

#define SPI_DRIVER (&SPID2)
#define SPI_PORT GPIOB
#define SCK_PAD 13
#define MISO_PAD 14
#define MOSI_PAD 15

#define CS_PORT GPIOB
#define RESET_PORT GPIOB
#define DNC_PORT GPIOB
#define CS_PAD 12 // 0 = chip selected
#define RESET_PAD 10 // 0 = reset
#define DNC_PAD 11 // control=0, data=1

static SPIConfig spi_cfg = {
NULL,
CS_PORT,
CS_PAD,
(SPI_CR1_BR_1 | SPI_CR1_CPOL | SPI_CR1_CPHA)
};

static inline void init_board(GDisplay *g) {
(void) g;
//g->board = 0;

palSetPadMode(SPI_PORT, SCK_PAD, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
palSetPadMode(SPI_PORT, MOSI_PAD, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
palSetPadMode(SPI_PORT, MISO_PAD, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
palSetPadMode(RESET_PORT, RESET_PAD, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(CS_PORT, CS_PAD, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(DNC_PORT, DNC_PAD, PAL_MODE_OUTPUT_PUSHPULL);
palSetPad(CS_PORT, CS_PAD);
palSetPad(RESET_PORT, RESET_PAD);
palClearPad(DNC_PORT, DNC_PAD);


spiStart(&SPID2, &spi_cfg);

}

static inline void post_init_board(GDisplay *g) {
(void) g;
}


static inline void setpin_reset(GDisplay *g, bool_t state) {
(void) g;
palWritePad(RESET_PORT, RESET_PAD, !state);

}
static inline void set_backlight(GDisplay *g, uint8_t percent) {
(void) g;
(void) percent;
}
static inline void acquire_bus(GDisplay *g) {
(void) g;
spiAcquireBus(SPI_DRIVER);

}

static inline void release_bus(GDisplay *g) {
(void) g;
spiReleaseBus(SPI_DRIVER);
}


static inline void write_index(GDisplay *g, uint16_t index) {
(void) g;
spiSelect(SPI_DRIVER);

static uint8_t txbuf[2] = {0};
txbuf[0] = 0x70 | 0x00 | 0x00;
spiSend(SPI_DRIVER, 1, txbuf);

//txbuf[0] = index & 0xFF;
txbuf[0] = (index >> 8);
txbuf[1] = (index & 0xFF);

spiSend(SPI_DRIVER, 2, txbuf);

spiUnselect(SPI_DRIVER);

}

static inline void write_data(GDisplay *g, uint16_t data) {
(void) g;
spiSelect(SPI_DRIVER);
static uint8_t txbuf[2] = {0};
txbuf[0] = 0x70 | 0x00 | 0x02;

spiSend(SPI_DRIVER, 1, txbuf);

txbuf[0] = (data >> 8);
txbuf[1] = (data & 0xFF);

spiSend(SPI_DRIVER, 2, txbuf);

spiUnselect(SPI_DRIVER);
}

static inline void setreadmode(GDisplay *g) {
(void) g;
}

static inline void setwritemode(GDisplay *g) {
(void) g;
}


static inline uint16_t read_data(GDisplay *g) {
(void) g;

spiStart(SPI_DRIVER, &spi_cfg);
spiSelect(SPI_DRIVER);
static uint8_t txbuf[1] = {0};
txbuf[0] = 0x70 | 0x01 | 0x02;
spiSend(SPI_DRIVER, 1, txbuf);

static uint8_t rxbuf[3] = {0};
spiReceive(SPI_DRIVER, 3, rxbuf);

spiUnselect(SPI_DRIVER);
spiStart(SPI_DRIVER, &spi_cfg);
static uint16_t value = 0;
value = rxbuf[1] << 8 | rxbuf[2];
return value;
}


#endif /* _GDISP_LLD_BOARD_H */

gdisp_lld_ILI9341.c

/*
* This file is subject to the terms of the GFX License. If a copy of
* the license was not distributed with this file, you can obtain one at:
*
* http://ugfx.org/license.html
*/

#include "gfx.h"

#if GFX_USE_GDISP

#if defined(GDISP_SCREEN_HEIGHT)
#warning "GDISP: This low level driver does not support setting a screen size. It is being ignored."
#undef GISP_SCREEN_HEIGHT
#endif
#if defined(GDISP_SCREEN_WIDTH)
#warning "GDISP: This low level driver does not support setting a screen size. It is being ignored."
#undef GDISP_SCREEN_WIDTH
#endif

#define GDISP_DRIVER_VMT GDISPVMT_ILI9341
#include "drivers/gdisp/ILI9341/gdisp_lld_config.h"
#include "src/gdisp/gdisp_driver.h"

#include "board_ILI9341.h"

/*===========================================================================*/
/* Driver local definitions. */
/*===========================================================================*/

#ifndef GDISP_SCREEN_HEIGHT
#define GDISP_SCREEN_HEIGHT 320
#endif
#ifndef GDISP_SCREEN_WIDTH
#define GDISP_SCREEN_WIDTH 240
#endif
#ifndef GDISP_INITIAL_CONTRAST
#define GDISP_INITIAL_CONTRAST 50
#endif
#ifndef GDISP_INITIAL_BACKLIGHT
#define GDISP_INITIAL_BACKLIGHT 100
#endif

#include "drivers/gdisp/ILI9341/ILI9341.h"

/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/

// Some common routines and macros
#define dummy_read(g) { volatile uint16_t dummy; dummy = read_data(g); (void) dummy; }
#define write_reg(g, reg, data) { write_index(g, reg); write_data(g, data); }
#define write_data16(g, data) { write_data(g, data >> 8); write_data(g, (uint8_t)data); }
#define delay(us) gfxSleepMicroseconds(us)
#define delayms(ms) gfxSleepMilliseconds(ms)

static void set_viewport(GDisplay *g) {
write_index(g, 0x2A);
write_data(g, (g->p.x >> 8));
write_data(g, (uint8_t) g->p.x);
write_data(g, (g->p.x + g->p.cx - 1) >> 8);
write_data(g, (uint8_t) (g->p.x + g->p.cx - 1));

write_index(g, 0x2B);
write_data(g, (g->p.y >> 8));
write_data(g, (uint8_t) g->p.y);
write_data(g, (g->p.y + g->p.cy - 1) >> 8);
write_data(g, (uint8_t) (g->p.y + g->p.cy - 1));

}

/*===========================================================================*/
/* Driver exported functions. */
/*===========================================================================*/

LLDSPEC bool_t gdisp_lld_init(GDisplay *g) {
// No private area for this controller
g->priv = 0;

// Initialise the board interface
g-> board = 0;
init_board(g);


// Hardware reset
setpin_reset(g, TRUE);
gfxSleepMilliseconds(20);
setpin_reset(g, FALSE);
gfxSleepMilliseconds(20);

// Get the bus for the following initialisation commands
acquire_bus(g);

write_index(g, 0x01); //software reset
gfxSleepMilliseconds(5);
write_index(g, 0x28);
// display off
//---------------------------------------------------------
// magic?
write_index(g, 0xCF);
write_data(g, 0x00);
write_data(g, 0xC1);
write_data(g, 0x30);

write_index(g, 0xED);
write_data(g, 0x64);
write_data(g, 0x03);
write_data(g, 0x12);
write_data(g, 0x81);
write_index(g, 0xE8);
write_data(g, 0x85);
write_data(g, 0x00);
write_data(g, 0x78);
write_index(g, 0xCB);
write_data(g, 0x39);
write_data(g, 0x2C);
write_data(g, 0x00);
write_data(g, 0x34);
write_data(g, 0x02);
write_index(g, 0xF7);
write_data(g, 0x20);
write_index(g, 0xEA);
write_data(g, 0x00);
write_data(g, 0x00);
//------------power control------------------------------
write_index(g, 0xC0); //power control
write_data(g, 0x23);
write_index(g, 0xC1); //power control
write_data(g, 0x10);
//--------------VCOM
write_index(g, 0xC5); //vcom control
write_data(g, 0x3e);//35
write_data(g, 0x28);//3E
write_index(g, 0xC7); //vcom control
write_data(g, 0x86); // 0x94
//------------memory access control------------------------
write_index(g, 0x36);
// memory access control
write_data(g, 0x48); //0048 my,mx,mv,ml,BGR,mh,0.0
write_index(g, 0x3A); // pixel format set
write_data(g, 0x55);//16bit /pixel
//----------------- frame rate------------------------------
write_index(g, 0xB1);
// frame rate
write_data(g, 0x00);
write_data(g, 0x18); //70
//----------------Gamma---------------------------------
write_index(g, 0xF2); // 3Gamma Function Disable
write_data(g, 0x00);
write_index(g, 0x26);
write_data(g, 0x01); // gamma set 4 gamma curve 01/02/04/08

write_index(g, 0xE0); //positive gamma correction
write_data(g, 0x0F);
write_data(g, 0x31);
write_data(g, 0x2B);
write_data(g, 0x0C);
write_data(g, 0x0E);
write_data(g, 0x08);
write_data(g, 0x4E);
write_data(g, 0xF1);
write_data(g, 0x37);
write_data(g, 0x07);
write_data(g, 0x10);
write_data(g, 0x03);
write_data(g, 0x0E);
write_data(g, 0x09);
write_data(g, 0x00);
write_index(g, 0xE1); //negamma correction
write_data(g, 0x00);
write_data(g, 0x0E);
write_data(g, 0x14);
write_data(g, 0x03);
write_data(g, 0x11);
write_data(g, 0x07);
write_data(g, 0x31);
write_data(g, 0xC1);
write_data(g, 0x48);
write_data(g, 0x08);
write_data(g, 0x0F);
write_data(g, 0x0C);
write_data(g, 0x31);
write_data(g, 0x36);
write_data(g, 0x0F);
//--------------ddram ---------------------
write_index(g, 0x2A);
// column set
// size = 239
write_data(g, 0x00);
write_data(g, 0x00);
write_data(g, 0x00);
write_data(g, 0xEF);
write_index(g, 0x2B);
// page address set
// size = 319
write_data(g, 0x00);
write_data(g, 0x00);
write_data(g, 0x01);
write_data(g, 0x3F);
// write_index(g, 0x34);
//write_index(g, 0x35);
// tearing effect off
// tearing effect on
// write_index(g, 0xb4); // display inversion
// write_data(g, 0x00);
write_index(g, 0xb7); //entry mode set
write_data(g, 0x07);
//-----------------display---------------------
write_index(g, 0xB6);
// display function control
write_data(g, 0x08);
write_data(g, 0x82);
write_data(g, 0x27);
write_index(g, 0x11); //sleep out
gfxSleepMilliseconds(100);
write_index(g, 0x29); // display on
write_index(g, 0x2c);

// Finish Init
post_init_board(g);

// Release the bus
release_bus(g);

/* Turn on the back-light */
set_backlight(g, GDISP_INITIAL_BACKLIGHT);

/* Initialise the GDISP structure */
g->g.Width = GDISP_SCREEN_WIDTH;
g->g.Height = GDISP_SCREEN_HEIGHT;
g->g.Orientation = GDISP_ROTATE_0;
g->g.Powermode = powerOn;
g->g.Backlight = GDISP_INITIAL_BACKLIGHT;
g->g.Contrast = GDISP_INITIAL_CONTRAST;
return TRUE;
}

#if GDISP_HARDWARE_STREAM_WRITE
LLDSPEC void gdisp_lld_write_start(GDisplay *g) {
acquire_bus(g);
set_viewport(g);
write_index(g, 0x2C);
}
LLDSPEC void gdisp_lld_write_color(GDisplay *g) {
write_data16(g, gdispColor2Native(g->p.color));
}
LLDSPEC void gdisp_lld_write_stop(GDisplay *g) {
release_bus(g);
}
#endif

#if GDISP_HARDWARE_STREAM_READ
LLDSPEC void gdisp_lld_read_start(GDisplay *g) {
acquire_bus(g);
set_viewport(g);
write_index(g, 0x2E);
setreadmode(g);
dummy_read(g);
}
LLDSPEC color_t gdisp_lld_read_color(GDisplay *g) {
uint16_t data;

data = read_data(g);
return gdispNative2Color(data);
}
LLDSPEC void gdisp_lld_read_stop(GDisplay *g) {
setwritemode(g);
release_bus(g);
}
#endif

#if GDISP_NEED_CONTROL && GDISP_HARDWARE_CONTROL
LLDSPEC void gdisp_lld_control(GDisplay *g) {
switch(g->p.x) {
case GDISP_CONTROL_POWER:
if (g->g.Powermode == (powermode_t)g->p.ptr)
return;
switch((powermode_t)g->p.ptr) {
case powerOff:
case powerSleep:
case powerDeepSleep:
acquire_bus(g);
write_reg(g, 0x0010, 0x0001); /* enter sleep mode */
release_bus(g);
break;
case powerOn:
acquire_bus(g);
write_reg(g, 0x0010, 0x0000); /* leave sleep mode */
release_bus(g);
break;
default:
return;
}
g->g.Powermode = (powermode_t)g->p.ptr;
return;

case GDISP_CONTROL_ORIENTATION:
if (g->g.Orientation == (orientation_t)g->p.ptr)
return;
switch((orientation_t)g->p.ptr) {
case GDISP_ROTATE_0:
acquire_bus(g);
write_reg(g, 0x36, 0x48); /* X and Y axes non-inverted */
release_bus(g);
g->g.Height = GDISP_SCREEN_HEIGHT;
g->g.Width = GDISP_SCREEN_WIDTH;
break;
case GDISP_ROTATE_90:
acquire_bus(g);
write_reg(g, 0x36, 0xE8); /* Invert X and Y axes */
release_bus(g);
g->g.Height = GDISP_SCREEN_WIDTH;
g->g.Width = GDISP_SCREEN_HEIGHT;
break;
case GDISP_ROTATE_180:
acquire_bus(g);
write_reg(g, 0x36, 0x88); /* X and Y axes non-inverted */
release_bus(g);
g->g.Height = GDISP_SCREEN_HEIGHT;
g->g.Width = GDISP_SCREEN_WIDTH;
break;
case GDISP_ROTATE_270:
acquire_bus(g);
write_reg(g, 0x36, 0x28); /* Invert X and Y axes */
release_bus(g);
g->g.Height = GDISP_SCREEN_WIDTH;
g->g.Width = GDISP_SCREEN_HEIGHT;
break;
default:
return;
}
g->g.Orientation = (orientation_t)g->p.ptr;
return;

case GDISP_CONTROL_BACKLIGHT:
if ((unsigned)g->p.ptr > 100)
g->p.ptr = (void *)100;
set_backlight(g, (unsigned)g->p.ptr);
g->g.Backlight = (unsigned)g->p.ptr;
return;

//case GDISP_CONTROL_CONTRAST:
default:
return;
}
}
#endif

#endif /* GFX_USE_GDISP */

LCD.zip

Share this post


Link to post
Share on other sites

A few things...

1. The spiSelect and spiUnselect can go in the aquire and release bus too. This reduces significant overhead especially when transferring data.

2. There is something wrong with the way you are talking to the controller in that you are sending that 0x73 before each transfer. That looks like i2c addressing. If your controller is setup to use i2c then you should be using the chibios i2c api not the spi api. If your controller talks via spi then it almost certainly will not want that preamble. Have a look at some of the other boards that use the spi interface to talk to their controller.

3. There seems to be a problem with the width of the data being sent for an write_index. Although the parameter type implies a 16 bit transfer, in reality I believe it is actually a byte operation (something to cleanup some day in the board file template).

Have a look at the display board file for the stm32429-idiscovery board. Although it uses a different driver and has lots of other complications at the driver level, the controller is actually an ILI9341 that is addressed using spi so it's board file should be close to what you need.

Share this post


Link to post
Share on other sites

One more thing, it is probably a good idea to leave changes to the driver as a very last resort. This driver is well tested and has not been known to need changes to get it to work.

Share this post


Link to post
Share on other sites

We are glad to hear that you got it working :)

Could you please tell us what the actual problem was and how you fixed it? This way other people who might read this thread in the future can fix their problems too.

~ Tectu

Share this post


Link to post
Share on other sites

Yes, please share :-)

I'm trying to get the same LCD working on a Nucleo board in combination with ChibiOS and uGFX too.

Via mbed, the LCD demo is working, but I don't like mbed very much.

Thanks!

Share this post


Link to post
Share on other sites

The problem was just bad setup on my end looking at examples I came up with the following board file.

Hope it helps.


#ifndef _GDISP_LLD_BOARD_H
#define _GDISP_LLD_BOARD_H
//**** ILI9341 on SPI1. TESTED on STM32F1 AND STM32F4.
// Pin & SPI setup
// SPI1
#define SPI_DRIVER (&SPID1)
#define SPI_PORT GPIOA
#define SCK_PAD 5 //PA5
#define MISO_PAD 6 //PA6
#define MOSI_PAD 7 //PA7

#define CS_PORT GPIOA
#define RESET_PORT GPIOA
#define DNC_PORT GPIOA
#define CS_PAD 4 // PA4 -- 0 = chip selected
#define RESET_PAD 1 // PA1 -- 0 = reset
#define DNC_PAD 0 // PA0 -- control=0, data=1 -- DNC or D/C

// SPI setup ajust " SPI_BaudRatePrescaler_X" to set SPI speed.
// Peripherial Clock 42MHz SPI2 SPI3
// Peripherial Clock 84MHz SPI1 SPI1 SPI2/3
#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ
#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz
#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz
#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz
#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz
#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz
#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz
#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz
static SPIConfig spi_cfg = {
NULL,
CS_PORT,
CS_PAD,
SPI_BaudRatePrescaler_4 //AJUST SPEED HERE..
};

static inline void init_board(GDisplay *g) {
(void) g;
//g->board = 0;
//Set up the pins..
palSetPadMode(SPI_PORT, SCK_PAD, PAL_MODE_ALTERNATE(5));
palSetPadMode(SPI_PORT, MOSI_PAD, PAL_MODE_ALTERNATE(5));
palSetPadMode(SPI_PORT, MISO_PAD, PAL_MODE_ALTERNATE(5));
palSetPadMode(RESET_PORT, RESET_PAD, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(CS_PORT, CS_PAD, PAL_MODE_OUTPUT_PUSHPULL);
palSetPadMode(DNC_PORT, DNC_PAD, PAL_MODE_OUTPUT_PUSHPULL);
//Set pins.
palSetPad(CS_PORT, CS_PAD);
palSetPad(RESET_PORT, RESET_PAD);
palClearPad(DNC_PORT, DNC_PAD);
//Start SPI1 with our config.
spiStart(SPI_DRIVER, &spi_cfg);

}

static inline void post_init_board(GDisplay *g) {
(void) g;
}


static inline void setpin_reset(GDisplay *g, bool_t state) {
(void) g;
palWritePad(RESET_PORT, RESET_PAD, !state);
}
static inline void set_backlight(GDisplay *g, uint8_t percent) {
(void) g;
(void) percent;
}
static inline void acquire_bus(GDisplay *g) {
(void) g;
spiSelect(SPI_DRIVER);

}

static inline void release_bus(GDisplay *g) {
(void) g;
spiUnselect(SPI_DRIVER);
}


static inline void write_index(GDisplay *g, uint8_t index) {
static uint8_t sindex;
(void) g;

palClearPad(DNC_PORT, DNC_PAD);
sindex = index;
spiSend(SPI_DRIVER, 1, &sindex);
}

static inline void write_data(GDisplay *g, uint8_t data) {
static uint8_t sdata;
(void) g;

palSetPad(DNC_PORT, DNC_PAD);
sdata = data;
spiSend(SPI_DRIVER, 1, &sdata);
}

static inline void setreadmode(GDisplay *g) {
(void) g;
}

static inline void setwritemode(GDisplay *g) {
(void) g;
}
static inline uint16_t read_data(GDisplay *g) {
(void) g;
return 0;
}

#endif /* _GDISP_LLD_BOARD_H */

Share this post


Link to post
Share on other sites

Yep, works! Seems to be a problem with the SPI speed I used.

Your default SPI_BaudPrescaler_4 wasn't working on my L152RE Nucleo board, had to use Prescaler_8, which is quite slow in my opinion.

My mbed example runs significantly faster, SPI speed is set there at 24MHz.

I'll try to figure out how to increase the speed.

Thanks!!

Share this post


Link to post
Share on other sites

Hi,

I was able to increase the SPI speed (SPI_BaudRatePrescaler_2) by changing the pad config:

static inline void init_board(GDisplay *g) {
(void) g;
g->board = 0;

//Set up the pins..
palSetPadMode(SPI_PORT, CS_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(SPI_PORT, SCK_PAD, PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(SPI_PORT, MISO_PAD, PAL_MODE_ALTERNATE(5));
palSetPadMode(SPI_PORT, MOSI_PAD, PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);

palSetPadMode(RESET_PORT, RESET_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(DNC_PORT, DNC_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);

//Set pins.
palSetPad(CS_PORT, CS_PAD);
palSetPad(RESET_PORT, RESET_PAD);
palClearPad(DNC_PORT, DNC_PAD);

//Start SPI1 with our config.
spiStart(SPI_DRIVER, &spi_cfg);
spiSelect(SPI_DRIVER); /* Slave Select assertion. */
}

And did some marginal optimalisations:

static inline void write_index(GDisplay *g, uint8_t index) {
(void) g;

palClearPad(DNC_PORT, DNC_PAD);
spiSend(SPI_DRIVER, 1, &index);
}

static inline void write_data(GDisplay *g, uint8_t data) {
(void) g;

palSetPad(DNC_PORT, DNC_PAD);
spiSend(SPI_DRIVER, 1, &data);
}

But the speed is still (visually) slower then my mbed demo. Both are compiled with µVision 5.13 if this should influence something.

Maybe a µGFX setting? DMA (but GDISP_USE_DMA = TRUE)?

Share this post


Link to post
Share on other sites

But the speed is still (visually) slower then my mbed demo. Both are compiled with µVision 5.13 if this should influence something.

Maybe a µGFX setting? DMA (but GDISP_USE_DMA = TRUE)?

Do I understand you correctly that in both cases you use uGFX, just one time with mbed stuff and one time with ChibiOS/RT itself?

~ Tectu

Share this post


Link to post
Share on other sites

No, I'm not aware of a µGFX port for mbed so far.

My (visual) comparisation is just by drawing rectangles on the screen.

The library / sample I use for mbed is from here: https://developer.mbed.org/users/gmoral ... 40Library/

For µGFX (on ChibiOS/RT), I use gdispFillArea()

For mbed, I use tft.FillRect()

The hardware setup is identical because I use the same board, LCD & connections.

Share this post


Link to post
Share on other sites

I'm currently on my phone so I can't check the actual code but beside the interface speed (eg. using DMA as you already mentioned) the most likely reason for this is that the controller itself provides a function to fill a rectangle. You just have to send the first point, width, height and color and the display controller will draw the rectangle. Most uGFX drivers have this implemented and it can be enabled through the driver configuration file.

But as I said. Right now I can not check if the ILI9341 even provides this feature and whether it has been implemented in uGFX and mbed.

~ Tectu

Share this post


Link to post
Share on other sites

The mbed routines are using simple filling as well:

////////////////////////////////////////////////////////////////////////////////////////////////
// Draws a filled rectangle
////////////////////////////////////////////////////////////////////////////////////////////////
void ILI9340_Display::FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t colour) {

// rudimentary clipping (drawChar w/big text requires this)
if((x >= _width) || (y >= _height)) return;
if((x + w - 1) >= _width) w = _width - x;
if((y + h - 1) >= _height) h = _height - y;

SetAddrWindow(x, y, x+w-1, y+h-1);

uint8_t hi = colour >> 8, lo = colour;

dc = 1;
cs = 0;

for(y=h; y>0; y--) {
for(x=w; x>0; x--) {
spi.write(hi);
spi.write(lo);
}
}
cs = 1;
}

Share this post


Link to post
Share on other sites

Thank you very much for providing the videos. This is definitely not the expected behavior. Experience says that this is exactly the hardware filling issues (assuming that the controller provides it).

I have ordered on of those modules now to test it myself should you not be able to get it done yourself. Sadly it will take up to five weeks to receive it...

~ Tectu

Share this post


Link to post
Share on other sites

Wow that is a big difference. :o

Thanks for cleaning up the code I to can run SPI_BaudRatePrescaler_2 now.

My setup is also running about the same speed as your ugfx setup.

Thank you.

Share this post


Link to post
Share on other sites

I strongly suspect the difference is the way spi is implemented. I would suggest that mbed is using bit banging versus chibios using the real spi hardware. Bit banging is usually faster but more cpu intensive.

An implementation of bit banging spi can be found under the SAMEX256 board for some of the uext peripherals (initially implemented that way due to buggy spi drivers for that cpu in chibios).

Other factors...

Make sure your board file routines are marked as "inline". This way with the right optimization settings your compiler will remove any function call overhead.

Set the DNC pin in your board_init and also at the end of the write_index call. Setting DNC can then be removed from write_data as it will already be set.

Share this post


Link to post
Share on other sites

Thanks for the tips.

- All board routines are marked "static inline"

- I changed the DNC setters as you suggested. It made a small difference, but not the one I hoped for.

Concerning "bit banging". I'm afraid I'm not advanced enough to make that change myself, but any help welcome! ;)

Next I gonna measure the SPI lines in the two situations with a scope, and verify if the SPI baudrate is actually the speed as set in the code.

With the mbed example, I need to lower the SPI baudrate to <1MHz to have the same visual speed as with the µGFX example where SPI is (supposed to be) 16Mhz (SPI_BaudRatePrescaler_2, MCU clock = 32MHz (STM32L152RET6)).

Share this post


Link to post
Share on other sites

This is the CLK of the mbed sample. Frequency looks like +/- 12MHz.

mbed_1.jpg

This is the CLK of the ChibiOS/µGFX sample. Frequency looks like +/- 16MHz. Higher! :o

ugfx_1.jpg

However, the mbed sample has more of those "bursts" in a same period of time:

mbed_2.jpg

ChibiOS/µGFX :

ugfx_2.jpg

Anybody any idea why via mbed, there is more data transmitted in the same period of time, even when the CLK is lower?

Could this be because of the bit banging method which mbed may be using?

Share this post


Link to post
Share on other sites

It is almost certainly due to the overheads in the chibios spi driver. I suspect that chibios is suspending the thread while waiting for the spi to send rather than polling the registers to see when it is done. Resuming a suspended thread is a very expensive proposition hence the big delays between bytes of data.

An even better method than polling for the transaction to complete would be to poll just before starting the next transaction for completion of the previous transaction.

Unfortunately we are talking the specifics of how chibios implements it's drivers here, not something we easily control. The only solutions are...

1. Use a bit banging method of implementing spi, or

2. Write cpu specific direct hardware register calls, or

3. Rewrite the chibios spi driver.

Method 1 was used in the SAM7EX256 uext drivers.

Method 2 was used in the SAM7EX256 lcd driver

Method 3 is probably impossible due to deficiencies in the way chibios drivers work.

Unfortunately the SAM7EX256 board uses a different cpu so method 2 is not directly transferable from that code. The bit banging code however should work on any cpu.

It has been on the agenda for a while for ugfx to write a HAL module which would overcome many of these problems. That is a very big job however so for now we have to rely on the base operating systems hardware drivers.

Share this post


Link to post
Share on other sites

Thanks for your reply. I was suspecting the same. Maybe I should make a post about this on the ChibiOS forum.

I will look into the SAM7EX256 code you suggested, and hopefully I can port it to something I can use.

Share this post


Link to post
Share on other sites

Yep, found a solution (with the help of Giovanni from ChibiOS) :)

ugfx_3.jpg

According to this, it is even faster then the mbed solution. Most likely because of the 16MHz <=> 12MHz SPI speed, and now the extra MHz are making a (visual) difference.

But the best news is that ChibiOS is not causing any extra overload, and I want to stick with ChibiOS.

What was the problem?:

We were using the spiSend() method, but this is far from optimal when sending small packages (in our case, 1 pixel at the time). The DMA setup, and waiting for an interrupt was taking too long. This intended for transferring large amounts of data, not for 1 byte or something like that. For such situations, ChibiOS has the function spiPolledExchange(). This works without DMA and interrupts, but via polling. Less efficient for the system, but a lot faster for what we need.

You can switch between the two methods via "SPI_METHOD"

Here is the code:

#ifndef _GDISP_LLD_BOARD_H
#define _GDISP_LLD_BOARD_H
//**** ILI9341 on SPI1. TESTED on STM32L1, STM32F1 AND STM32F4.
// Pin & SPI setup
#define SPI_METHOD_IRQ 1
#define SPI_METHOD_POLLING 2
#define SPI_METHOD SPI_METHOD_POLLING
// SPI1
#define SPI_DRIVER (&SPID1)
#define SPI_PORT GPIOA
#define SCK_PAD 5 //PA5
#define MISO_PAD 6 //PA6
#define MOSI_PAD 7 //PA7

#define CS_PORT GPIOA
#define RESET_PORT GPIOA
#define DNC_PORT GPIOA
#define CS_PAD 4 // PA4 -- 0 = chip selected
#define RESET_PAD 1 // PA1 -- 0 = reset
#define DNC_PAD 0 // PA0 -- control=0, data=1 -- DNC or D/C

// SPI setup ajust " SPI_BaudRatePrescaler_X" to set SPI speed.
// Peripherial Clock 42MHz SPI2 SPI3
// Peripherial Clock 84MHz SPI1 SPI1 SPI2/3
#define SPI_BaudRatePrescaler_2 ((uint16_t)0x0000) // 42 MHz 21 MHZ
#define SPI_BaudRatePrescaler_4 ((uint16_t)0x0008) // 21 MHz 10.5 MHz
#define SPI_BaudRatePrescaler_8 ((uint16_t)0x0010) // 10.5 MHz 5.25 MHz
#define SPI_BaudRatePrescaler_16 ((uint16_t)0x0018) // 5.25 MHz 2.626 MHz
#define SPI_BaudRatePrescaler_32 ((uint16_t)0x0020) // 2.626 MHz 1.3125 MHz
#define SPI_BaudRatePrescaler_64 ((uint16_t)0x0028) // 1.3125 MHz 656.25 KHz
#define SPI_BaudRatePrescaler_128 ((uint16_t)0x0030) // 656.25 KHz 328.125 KHz
#define SPI_BaudRatePrescaler_256 ((uint16_t)0x0038) // 328.125 KHz 164.06 KHz

static SPIConfig spi_cfg = {
NULL,
CS_PORT,
CS_PAD,
SPI_BaudRatePrescaler_2 //AJUST SPEED HERE..
};

static inline void init_board(GDisplay *g) {
(void) g;
g->board = 0;

//Set up the pins..
palSetPadMode(SPI_PORT, CS_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(SPI_PORT, SCK_PAD, PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(SPI_PORT, MISO_PAD, PAL_MODE_ALTERNATE(5));
palSetPadMode(SPI_PORT, MOSI_PAD, PAL_MODE_ALTERNATE(5) | PAL_STM32_OSPEED_HIGHEST);

palSetPadMode(RESET_PORT, RESET_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(DNC_PORT, DNC_PAD, PAL_MODE_OUTPUT_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);

//Set pins.
palSetPad(CS_PORT, CS_PAD);
palSetPad(RESET_PORT, RESET_PAD);
palClearPad(DNC_PORT, DNC_PAD);

//Start SPI1 with our config.
spiStart(SPI_DRIVER, &spi_cfg);
spiSelectI(SPI_DRIVER); /* Slave Select assertion. */
}

static inline void post_init_board(GDisplay *g) {
(void) g;
}

static inline void setpin_reset(GDisplay *g, bool_t state) {
(void) g;
palWritePad(RESET_PORT, RESET_PAD, !state);
}

static inline void set_backlight(GDisplay *g, uint8_t percent) {
(void) g;
(void) percent;
}

static inline void acquire_bus(GDisplay *g) {
(void) g;
spiSelectI(SPI_DRIVER);
}

static inline void release_bus(GDisplay *g) {
(void) g;
spiUnselectI(SPI_DRIVER);
}

static inline void write_index(GDisplay *g, uint8_t index) {
(void) g;

palClearPad(DNC_PORT, DNC_PAD);
#if SPI_METHOD == SPI_METHOD_IRQ
spiSend(SPI_DRIVER, 1, &index);
#elif SPI_METHOD == SPI_METHOD_POLLING
spiPolledExchange(SPI_DRIVER, index);
#endif
palSetPad(DNC_PORT, DNC_PAD);
}

static inline void write_data(GDisplay *g, uint8_t data) {
(void) g;

#if SPI_METHOD == SPI_METHOD_IRQ
spiSend(SPI_DRIVER, 1, &data);
#elif SPI_METHOD == SPI_METHOD_POLLING
spiPolledExchange(SPI_DRIVER, data);
#endif
}

static inline void setreadmode(GDisplay *g) {
(void) g;
}

static inline void setwritemode(GDisplay *g) {
(void) g;
}
static inline uint16_t read_data(GDisplay *g) {
(void) g;
return 0;
}

#endif /* _GDISP_LLD_BOARD_H */

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.

×