ESP32有4组的SPI,SPI0和SPI又叫SPI,SPI2又叫HSPI,SPI3又叫VSPI,这里的HSPI和VSPI只是名字不同,并不是high-speed SPI 和Very High-speed SPI。SPI0作为cache访问外部存储接口使用,SPI1一般用作连接外部Flash,应用一般只使用SPI2和SPI3。
如果SPI采用GPIO矩阵,则最大频率会限制在26.6MHz,如果采用IOMUX,最高频率会限制在80MHz。因为显示屏是个高速刷数据的硬件,所以我使用IOMUX,如何使用,只要根据稳定要求配置指定的IO口就行,我这里使用SPI3,即VSPI。
Pin | HSPI | VSPI |
CS0* | 15 | 5 |
SCLK | 14 | 18 |
MISO | 12 | 19 |
MOSI | 13 | 23 |
QUADWP | 2 | 22 |
QUADHD | 4 | 21 |
这里我们只需要输出SPI,所以MOSI引脚不配,另外QUADWP和QUADHD也不需要,所以SPI相关的,只需要配置CS0、SCLK和MISO。
我的LCD驱动IC是ILI9341,看模块的引脚,还需要一个IO接DC,用来切换命令/数据,还有一个IO用来控制背光。这两个IO口没有特殊要求,最后看下LCD驱动的IO口定义
#define DISP_SPI_MOSI 23
#define DISP_SPI_CLK 18
#define DISP_SPI_CS 5
#define DISP_SPI_DC 12
#define DISP_BCKL 27
1、初始化
申请双缓冲区内存,用来发送到SPI控制器
#define LINE_BUFFERS (2)
#define LINE_COUNT (8)
const size_t lineSize = LCD_WIDTH * LINE_COUNT * sizeof(uint16_t);
for (int x = 0; x < LINE_BUFFERS; x++)
{
line[x] = heap_caps_malloc(lineSize, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
if (!line[x])
abort();
}
这里的缓冲区有2个,一个用来发送到SPI,另一个用来绘制,而且每次只发送8行数据。
#define SPI_CLOCK_SPEED 80000000
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[DISP_SPI_DC], PIN_FUNC_GPIO);
gpio_set_direction(DISP_SPI_DC, GPIO_MODE_OUTPUT);
esp_err_t ret;
spi_bus_config_t buscfg = {
.miso_io_num = -1,
.mosi_io_num = DISP_SPI_MOSI,
.sclk_io_num = DISP_SPI_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 8 * 320 * 2 + 8};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = SPI_CLOCK_SPEED,
.mode = 0, //SPI mode 0
.spics_io_num = DISP_SPI_CS, //CS pin
.queue_size = 7,
.pre_cb = disp_spi_pre_transfer_callback,
.post_cb = NULL,
//.flags=SPI_DEVICE_NO_DUMMY
};
//Initialize the SPI bus
ret = spi_bus_initialize(VSPI_HOST, &buscfg, 1);
assert(ret == ESP_OK);
//Attach the LCD to the SPI bus
ret = spi_bus_add_device(VSPI_HOST, &devcfg, &spi);
assert(ret == ESP_OK);
SPI总线初始化,然后添加LCD设备,这里基本就是配置相关的引脚,然后速率配置为80Mhz。
ili9341_init();
最后还需要初始化驱动IC,不同的驱动IC代码不一样,一般厂商会提供代码,这里不展开研究。
2、发送数据
void send_lines(int ypos, int width, uint16_t *linedata, int lineCount)
{
esp_err_t ret;
int x;
static spi_transaction_t trans[6];
for (x = 0; x < 6; x++)
{
memset(&trans[x], 0, sizeof(spi_transaction_t));
if ((x & 1) == 0)
{
//Even transfers are commands
trans[x].length = 8;
trans[x].user = (void *)0;
}
else
{
//Odd transfers are data
trans[x].length = 8 * 4;
trans[x].user = (void *)1;
}
trans[x].flags = SPI_TRANS_USE_TXDATA;
}
trans[0].tx_data[0] = 0x2A; //Column Address Set
trans[1].tx_data[0] = 0; //Start Col High
trans[1].tx_data[1] = 0; //Start Col Low
trans[1].tx_data[2] = (width - 1) >> 8; //End Col High
trans[1].tx_data[3] = (width - 1) & 0xff; //End Col Low
trans[2].tx_data[0] = 0x2B; //Page address set
trans[3].tx_data[0] = ypos >> 8; //Start page high
trans[3].tx_data[1] = ypos & 0xff; //start page low
trans[3].tx_data[2] = (ypos + lineCount) >> 8; //end page high
trans[3].tx_data[3] = (ypos + lineCount) & 0xff; //end page low
trans[4].tx_data[0] = 0x2C; //memory write
trans[5].tx_buffer = linedata; //finally send the line data
trans[5].length = width * 2 * 8 * lineCount; //Data length, in bits
trans[5].flags = 0; //undo SPI_TRANS_USE_TXDATA flag
//Queue all transactions.
for (x = 0; x < 6; x++)
{
ret = spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
assert(ret == ESP_OK);
}
}
每发送一次数据,需要分6个事务来做,这里还是根据I9341驱动IC来。