IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> [ESP32]学习笔记07 -> 正文阅读

[嵌入式][ESP32]学习笔记07

使用ESP32的硬件SPI驱动中景园1.3寸Lcd

接上次我们使用IO模拟的方式驱动LCD屏幕,由于IO模拟刷屏速度太慢,本次我们使用ESP32的硬件SPI来驱动LCD,在测试过程中遇到了很多坑,所以会详细的讲一下ESP32的SPI外设。



ESP32的SPI外设

ESP32-S2 系列芯片共有4 个SPI(SPI0,SPI1,SPI2 和SPI3)。SPI0 和SPI1 只可以配置成SPI 存储器模式,
SPI2 既可以配置成SPI 存储器模式又可以配置成通用SPI 模式;SPI3 只可以配置成通用SPI 模式。
? SPI 存储器(SPI Memory) 模式
SPI 存储器模式(SPI0, SPI1 和SPI2)用于连接SPI 接口的外部存储器。SPI 存储器模式下数据传输长度
以字节为单位,最高支持 8 线 STR/DDR 读写操作。时钟频率可配置, STR 模式下支持的最高时钟频率为
80 MHz,DDR 模式下支持的最高时钟频率为40 MHz。
? SPI2 通用SPI (GP-SPI) 模式
SPI2 作为通用 SPI 时,既可以配置成主机模式,又可以配置成从机模式。主机模式支持 2 线全双工和
1/2/4/8 线半双工通信;从机模式支持 2 线全双工和 1/2/4 线半双工通信。通用 SPI 的主机时钟频率可配
置;数据传输长度以字节为单位;时钟极性(CPOL) 和相位(CPHA) 可配置;可连接DMA 通道。
– 在 2 线全双工通信模式下, 主机的时钟最高频率为 80 MHz,从机的时钟最高频率为 40 MHz。支持
SPI 传输的4 种时钟模式。
– 在主机1/2/4/8 线半双工通信模式下,时钟频率最高为80 MHz,支持SPI 传输的4 种时钟模式。
– 在从机1/2/4 线半双工通信模式下,时钟频率最高为40 MHz,也支持SPI 传输的4 种时钟模式。
? SPI3 通用SPI (GP-SPI) 模式
SPI3 只能作为通用SPI,既可以配置成主机模式,又可以配置成从机模式,具有2 线全双工和1 线半双工
通信功能。通用SPI 的主机时钟频率可配置;数据传输长度以字节为单位;时钟极性(CPOL) 和相位(CPHA)
可配置;可连接DMA 通道。
– 在 2 线全双工通信模式下, 主机的时钟频率最高为 80 MHz,从机的时钟频率最高为 40 MHz。支持
SPI 传输的4 种时钟模式。
– 在1 线半双工通信模式下,主机的时钟频率最高为80 MHz,支持SPI 传输的4 种时钟模式;从机的
时钟频率最高为40 MHz,也支持SPI 传输的4 种时钟模式。

在频率这一部分我查阅了很多资料,对于ESP32我看到的大多数描述是如果使用IO_MUX,那么最高频率能到80MHz,如果使用GPIO矩阵,那么最高频率只能到26.6MHz,否则ESP_LOG将会报错;我手上的是ESP32-S2,翻阅了乐鑫官方的数据手册,并没有看到描述必须使用IO_MUX才能将频率配置到80MHz,同时ESP32-S2的IO_MUX列表中我并没有找到SPI3的标注,之有SPI和FSPI。所以对于能不呢配置成80MHz,我们只需要直接配置成80MHz然后打开串口终端,观察是否打印错误信息即可。但是对于实际的频率,并不能确定是80MHz。


一、SPI-Master

我们用SPI主机模式来驱动LCD

ESP32共有4个SPI,与我们熟悉的STM32来说,并不是叫做SPI1,SPI2,SPI3,在ESP32的SPI中,SPI0和SPI1是不提供给用户使用的,在模组中,SPI0/1直接连接外挂的FLASH,当然有一部分ESP32芯片也是带有片上FLASH的,但同样不能将SPI0/1用来驱动其他模块,提供的功能也仅是与外挂的FLASH进行连接。剩余的两个SPI也不叫做SPI2/3,而是叫做FSPI和HSPI,对于这个命名我一开始认为是Fast SPI和High-Speed SPI的缩写,但实际上并不能这样理解,暂且认为它和SPI2/3相同。

二、使用SPI-Master驱动

1.首先配置两个结构体:

通过调用 spi_bus_initialize 初始化 SPI 总线. 确保在 bus_config 结构中设置正确的 IO 引脚. 注意将不需要的信号设置为 -1.
通过调用 spi_bus_add_device 告诉驱动程序连接到总线的 SPI 从设备. 确保在 dev_config 结构中配置设备具有的任何时序要求. 您现在应该拥有该设备的句柄,以便在发送事务时使用.
要与设备交互,请使用您需要的任何事务参数填充一个或多个 spi_transaction_t 结构. 然后以轮询方式或中断方式发送它们:

  1. 中断方式:通过调用 spi_device_queue_trans 将事务添加到队列中,之后使用
    spi_device_get_trans_result 查询结果,或者通过将它们提供给 spi_device_transmit
    来处理所有请求.
  2. 轮询方式:调用 spi_device_polling_transmit 发送轮询事务。或者,如果要在它们之间插入内容,可以通过
    spi_device_polling_start 和 spi_device_polling_end 发送轮询事务。

可选:要对设备执行事务,请在事务之前调用 spi_device_acquire_bus,并在事务之后调用 spi_device_release_bus。
可选:要卸载设备的驱动程序,请以设备句柄作为参数调用 spi_bus_remove_device
可选:要删除总线的驱动程序,请确保没有连接更多驱动程序并调用 spi_bus_free.

下面我们先看看LCD的原理图:

在这里插入图片描述这里的SCL引脚对应SPI的SCLK引脚,SDA引脚对应SPI的MOSI引脚,这里并不需要使用MISO,所以我们将其配置为-1,RES是LCD的复位引脚,在初始化时先要将LCD复位,CS引脚是模块的片选引脚,对于SPI外设,最多可以有三个CS来选择是给所连接的三个外设中的哪一个进行数据传输,CS低电平有效。这里LCD直接连接到了地,所以我们并不需要使用ESP32的硬件SPI的CS来控制。DC是写寄存器/写数据的选择引脚,这里我们直接用软件控制的方式,BLK是LCD的背光选择引脚,记得打开背光,LCD才能正常显示。

下面我们直接来进行配置:

首先我么使用宏来定义使用的管脚:

/* LCD-SPI Pins */
#define LCD_SCLK_PIN 3
#define LCD_MOSI_PIN 4
#define LCD_MISO_PIN -1
#define LCD_CS_PIN -1

#define LCD_HOST FSPI_HOST
#define DMA_CHAN SPI_DMA_CH_AUTO

这里的DMA_CHAN 在官方例程中,是直接配置为LCD_HOST,也就是说我们的LCD_HOST是配置的FSPI_HOST的宏,FSPI_HOST是2,那么DMA通道也就配置成了2;
但是对于ESP32-S2略有不同;我们先看看SDK中的预编译内容:

/**
 * @brief SPI DMA channels
 */
typedef enum {
  SPI_DMA_DISABLED = 0,     ///< Do not enable DMA for SPI
#if CONFIG_IDF_TARGET_ESP32
  SPI_DMA_CH1      = 1,     ///< Enable DMA, select DMA Channel 1
  SPI_DMA_CH2      = 2,     ///< Enable DMA, select DMA Channel 2
#endif
  SPI_DMA_CH_AUTO  = 3,     ///< Enable DMA, channel is automatically selected by driver
} spi_common_dma_t;

这里对DMA通道进行了枚举,我们使用的是ESP32-S2所以将DMA通道配置为SPI_DMA_CH_AUTO
对于最大传输的Size我们直接配置为默认的最大4094即可;
记得首先要创建SPI句柄:

static spi_device_handle_t spi; //创建spi

spi_bus_config_t buscfg = {
    .miso_io_num = LCD_MISO_PIN,
    .mosi_io_num = LCD_MOSI_PIN,
    .sclk_io_num = LCD_SCLK_PIN,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 4094};

spi_device_interface_config_t devcfg = {
    .clock_speed_hz = SPI_MASTER_FREQ_80M, //Clock out at 80 MHz
    .mode = 2,                             //SPI mode 0
    .spics_io_num = LCD_CS_PIN,            //CS pin
    .queue_size = 7 //We want to be able to queue 7 transactions at a time
};

然后我们使用刚刚声明的两个结构体变量和spi句柄进行初始化和安装spi驱动:

void Lcd_SpiInit(void)
{
    spi_bus_initialize(LCD_HOST, &buscfg, DMA_CHAN);
    spi_bus_add_device(LCD_HOST, &devcfg, &spi);
}

2.改写LCD发送数据的函数

void Lcd_Cmd(const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));                   //Zero out the transaction
    t.length = 8;                               //Command is 8 bits
    t.tx_buffer = &cmd;                         //The data is the cmd itself
    t.user = (void *)0;                         //D/C needs to be set to 0
    ret = spi_device_polling_transmit(spi, &t); //Transmit!
    assert(ret == ESP_OK);                      //Should have had no issues.
}

这里是通过spi_transaction_t这个结构体来进行传输数据的写入和发送;
对于小于8bit的数据,我们需要使用rx_data这个结构体成员进行数据写入,并且要配置flag。我们这里使用的最小数据为8bit,直接使用tx_buffer成员即可。

我们再编写一个写任意长度数据的函数,将其用于刷屏的函数,用于提高速度:

IRAM_ATTR void Lcd_Datax(uint16_t *dat, uint32_t len)
{
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));                             //Zero out the transaction
    t.length = len;                                       //Command is 8 bits
    t.tx_buffer = dat;                                    //The data is the cmd itself
    t.user = (void *)0;                                   //D/C needs to be set to 0
    esp_err_t ret = spi_device_polling_transmit(spi, &t); //Transmit!
    assert(ret == ESP_OK);                                //Should have had no issues.
}

然后我们将LCD写8位数据和写16位数据的函数中IO模拟发送数据的部分改位硬件SPI发送数据:

/**
  * @name   Lcd_WriteByte.
  * @brief  Lcd写8位数据
  * @param  dat: 要写入的数据
  * @retval None
  */
void Lcd_WriteByte(uint8_t dat)
{
    Lcd_SetDCLevel(1);
    //Lcd_WriteBuffer(dat);
    Lcd_Cmd(dat);
}

/**
  * @name   Lcd_WriteData.
  * @brief  Lcd写16位数据
  * @param  dat: 要写入的数据
  * @retval None
  */
void Lcd_WriteData(uint16_t dat)
{
    Lcd_SetDCLevel(1);
    //Lcd_WriteBuffer(dat >> 8);
    //Lcd_WriteBuffer(dat);
    Lcd_Cmd(dat >> 8);
    Lcd_Cmd(dat);
}

改写写命令函数:

/**
  * @name   Lcd_WriteReg.
  * @brief  Lcd写命令
  * @param  cmd: 要写入的命令
  * @retval None
  */
void Lcd_WriteReg(uint8_t cmd)
{
    Lcd_SetDCLevel(0);
    //Lcd_WriteBuffer(cmd);
    Lcd_Cmd(cmd);
}

改写清屏函数:

/**
  * @name   Lcd_Clear.
  * @brief  Lcd清屏
  * @param  color: 填充的颜色
  * @retval None
  */
void Lcd_Clear(uint16_t color)
{
    /*uint16_t i, j;
    Lcd_SetAddress(0, 0, LCD_Width - 1, LCD_Height - 1);
    for (i = 0; i < LCD_Width; i++)
    {
        for (j = 0; j < LCD_Height; j++)
            Lcd_WriteData(color);
    }*/
    Lcd_SetAddress(0, 0, LCD_Width - 1, LCD_Height - 1);
    Lcd_SetDCLevel(1);
    uint16_t color_temp[240 * 2];
    memset(color_temp, color, sizeof(color_temp));
    for (uint16_t i = 0; i < LCD_Width / 2; i++)
    {
        Lcd_Datax(color_temp, 240 * 16 * 2);
    }
}

最后记得在初始化中删除掉初始化MOSI引脚和SCLK引脚的部分,初始化SPI后就不需要把它们设置为普通IO了,记得在LCD初始化中调用SPI初始化函数:

	Lcd_SpiInit();
    /*gpio_config_t gpio_configstructure;
    gpio_configstructure.pin_bit_mask = (1ULL << LCD_SCLK_PIN) | (1ULL << LCD_SDIN_PIN) | (1ULL << LCD_RES_PIN) | (1ULL << LCD_DC_PIN) | (1ULL << LCD_BLK_PIN);
    gpio_configstructure.mode = GPIO_MODE_OUTPUT;
    gpio_configstructure.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_configstructure.pull_down_en = GPIO_PULLDOWN_DISABLE;
    gpio_configstructure.intr_type = GPIO_INTR_DISABLE;
    gpio_config(&gpio_configstructure);*/
    gpio_pad_select_gpio(LCD_RES_PIN);
    gpio_pad_select_gpio(LCD_DC_PIN);
    gpio_pad_select_gpio(LCD_BLK_PIN);
    gpio_set_direction(LCD_RES_PIN, GPIO_MODE_OUTPUT); 
    gpio_set_direction(LCD_DC_PIN, GPIO_MODE_OUTPUT); 
    gpio_set_direction(LCD_BLK_PIN, GPIO_MODE_OUTPUT);

刷屏测试我们还是使用上次创建的任务:

void Lcd_RefreshTask(void *arg)
{
    Lcd_Init();
    while (1)
    {
        BACK_COLOR = WHITE;
        Lcd_Clear(WHITE);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(GREEN);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(RED);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(YELLOW);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(BLUE);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(GRAY);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(BROWN);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(MAGENTA);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(CYAN);
        vTaskDelay(200 / portTICK_PERIOD_MS);
        Lcd_Clear(WHITE);
        Lcd_ShowString(0, 0, (uint8_t *)"Test", 12, BLACK);
        Lcd_ShowNum(0, 12, 999, 3, 12, BLACK);
        Lcd_ShowfloatNum(0, 24, 99.9, 4, 12, BLACK);
        vTaskDelay(100 / portTICK_PERIOD_MS);
        Lcd_ShowString(40, 40, (uint8_t *)"Test", 16, BLUE);
        Lcd_ShowNum(40, 56, 999, 3, 16, BLUE);
        Lcd_ShowfloatNum(40, 72, 99.9, 4, 16, BLUE);
        vTaskDelay(100 / portTICK_PERIOD_MS);
        Lcd_ShowString(80, 80, (uint8_t *)"Test", 24, GREEN);
        Lcd_ShowNum(80, 104, 999, 3, 24, GREEN);
        Lcd_ShowfloatNum(80, 128, 99.9, 4, 24, GREEN);
        vTaskDelay(100 / portTICK_PERIOD_MS);
        Lcd_ShowString(140, 120, (uint8_t *)"Test", 32, RED);
        Lcd_ShowNum(140, 152, 999, 3, 32, RED);
        Lcd_ShowfloatNum(140, 184, 99.9, 4, 32, RED);
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

void app_main(void)
{
	ESP_LOGI(TAG, "APP Start......");
    ESP_ERROR_CHECK(nvs_flash_init());
    xTaskCreate(Lcd_RefreshTask, "Lcd_Refresh", 2048, NULL, 4, NULL);
}	

这里使用延时是为了看清刷屏的内容,实际硬件SPI非常快;


烧录测试

将编译好的代码烧录进开发板中,观察刷屏显示内容是否和代码中设置相同:
在这里插入图片描述
完整工程代码.

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-04 17:42:22  更:2021-09-04 17:44:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 9:11:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计