使用RTT studio 和Cubemx联合开发Alios Things_第四节:使用SPI设备框架驱动ST7789 LCD屏幕。

前言

这个卡了好久,网上资料实在是太少了。其中比较迷惑的点是RTThread的SPI驱动框架底层实现了一部分hal库的功能,那么它把底层实现到了哪里?我的HAL_SPI_Transmit( )函数还应不应该使用?CubeMX帮我做到了哪里?
还有一个最关键的问题,我该如何将我的SPI设备融入RTT的框架里?

硬件

参考使用DMA+SPI驱动Aliyun Things 上的ST7789H2 LCD屏幕文章的硬件章节

CubeMX

SPI配置

这里只需要配置这两项就行,
本页面中传输字长,模式,波特率啥的已经被RTT接管。在这里插入图片描述
DMA配置,一定要配置。
在这里插入图片描述
引脚复用,一定要配置。
在这里插入图片描述
打开中断
在这里插入图片描述

工程配置

一定要生成单独的.c.h ,否则没有HAL_SPI_MspInit()
在这里插入图片描述
结束,生成工程就行了。

RTT studio

开启SPI1总线设备

按照board.h中的提示
在这里插入图片描述
step1选择完后记得ctrl+S保存
在这里插入图片描述
step2
在这里插入图片描述
step3,只要复制过来就行
HAL_SPI_MspInit主要干两件事情,设置SPI1_SCK,SPI1_MOSI输出的引脚,设置SPI1_TX DMA。
在这里插入图片描述
step3的另一种做法:修改SConscript脚本,使hal库生成的spi.c加入编译,推荐使用
在这里插入图片描述
加入之后记得更新
在这里插入图片描述
step4:不需要做,CubeMX已经帮你做好了
在这里插入图片描述
到此为止,SPI1总线已经可以使用了。
大家可能比较迷惑,
第一个问题:
还没有初始化SPI,配置极性,配置数据宽度之类的,为什么就说SPI可以用了呢?
原因在于,RTT认为SPI是总线设备,不同的外设使用一个SPI总线设备的时候,需要不同的配置参数。所以,是一个具体的外设一套参数。这个具体在代码里可以提现出来。
第二个问题:
我并没有调用HAL_SPI_MspInit,它是怎么起作用的?
这个问题在挂载SPI设备的时候会细说。

到此为止,我们通过CubeMX配置好了SPI1的硬件引脚,通过RTT studio 配置好了SPI1设备,接下来我们要将我们的lcd挂载到SPI1总线设备上。大家先将代码下载进去,应该可以看到SPI1总线设备的。
在这里插入图片描述

挂载SPI设备

首先,在driver目录下新建ST7789.c, ST7789.h,
在这里插入图片描述
C文件里,包含#include <drv_spi.h>,定义引脚,定义LCD设备名称,定义LCD设备句柄。

#include <drv_spi.h>
#include "st7789.h"
#include <rtdbg.h>

#define LCD_PWR  GET_PIN(E,7)
#define LCD_RST  GET_PIN(B,2)
#define LCD_DC   GET_PIN(A,6)

#define LCD_SPI_NAME "spi10"

static struct rt_spi_device *lcd_dev;

H文件里,加入以下代码

#include <rtdevice.h>
#include <drv_common.h>

//LCD屏幕分辨率定义
#define LCD_Width   240
#define LCD_Height  240
#define LCD_RAM_SIZE LCD_Width*LCD_Height*2     //长240 宽240 色深2bit
#define Pixel_NUM (LCD_RAM_SIZE/2)

之后,我们要讲LCD设备挂载到SPI1总线设备上。
rt_hw_spi_device_attach函数形参如下:
const char *bus_name:总线名称,一般为"spix"
const char *device_name:设备名称, 挂在SPI1上的设备一般起名为"spi10",“spi11”…
GPIO_TypeDef *cs_gpiox:片选引脚所在的组,比如GPIOA
uint16_t cs_gpio_pin:片选引脚,比如GPIO_PIN_4

    rt_err_t ret;
    /**********************将LCD挂载到SPI1上*********************/
    ret = rt_hw_spi_device_attach("spi1", LCD_SPI_NAME, GPIOA, GPIO_PIN_4);
    if(ret != RT_EOK)
    {
        LOG_E("rt_hw_spi_device_attach %s err ...\n",LCD_SPI_NAME);
        return -EBUSY;
    }

到此,我们定义一个函数将这些包含进去,并导出到shell

static int lcd_hw_init(void)
{
	...
}
MSH_CMD_EXPORT(lcd_hw_init,lcd_hw_init);

串口窗口中,可以看到执行lcd_hw_init函数后,已经挂载成功。
在这里插入图片描述
挂载成功之后,我们配置LCD设备的SPI属性。在上述代码之后,添加如下代码

    /**********************配置LCD设备需要的spi属性*********************/
    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = RT_SPI_MASTER|RT_SPI_MODE_0|RT_SPI_MSB;
    cfg.max_hz =40 * 1000 * 1000;
    lcd_dev = (struct rt_spi_device *)rt_device_find(LCD_SPI_NAME);
    if(lcd_dev == NULL){
        LOG_E("rt_device_find %s err ...\n",LCD_SPI_NAME);
        return -ENODEV;
    }
    else
    {
        ret = rt_spi_configure(lcd_dev, &cfg);
    }

这里我们就能看到, rt_spi_configure(lcd_dev, &cfg)该函数是将一套spi配置绑定到了一个设备上的。这个验证了之前我们所说的,一个设备一套参数的说法。在我们使用RTT SPI相关的API的时候,会自动判断之前占用总线的SPI设备和当前使用SPI的设备是否相同,不同的话会重新配置一次再执行收发操作。

插播一条:HAL_SPI_MspInit就是再rt_spi_configure中被调用的,调用顺序如下
rt_spi_configure()->函数指针configure,指向spi_configure()->stm32_spi_init()->HAL_SPI_Init()->HAL_SPI_MspInit();

到此为止,我们就完成了SPI的配置,之后就可以使用过RTT SPI相关的API了。
举一个例子来挖一下RTT的SPI发送函数干了啥,rt_spi_send是我们常用的SPI发送函数。

rt_inline rt_size_t rt_spi_send(struct rt_spi_device *device,
                                const void           *send_buf,
                                rt_size_t             length)
{
    return rt_spi_transfer(device, send_buf, RT_NULL, length);
}  

struct rt_spi_device *device: 指明操作SPI总线的设备
const void *send_buf:发送缓冲
rt_size_t length:发送字长

该函数内联到rt_spi_transfer,向下挖:
在这里插入图片描述
插播一条:这里就可以看到,RTT对SPI的互斥操作是通过内建互斥量实现的,当前操作SPI的不是之前的设备的话是会重新配置的。继续往下翻
在这里插入图片描述
找到了,就是这个xfer()是核心的发送语句,但这是一个函数指针,直接go to definition就到这里了
在这里插入图片描述
有一个意外发现的方法:ctrl+H全局搜索 “xfer =”
在这里插入图片描述
然后就找到了,xfer函数指针指向的是spixfer函数,继续往下挖
在这里插入图片描述
这里我们终于看到了HAL的引用,到此为止。
我当时就是到此为止哈哈哈哈,然后被大佬提醒这个函数末尾有需要注意的地方。
在这里插入图片描述
翻译翻译,就是说为了简化使用,你使不使用DMA,我都死等SPI传输结束。大家记住我们如果使用DMA的话,这里需要修改,怎么修改后面说。

编写驱动程序

完善C文件

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-01-15     12526       the first version
 */

#include <drv_spi.h>
#include "st7789.h"
#include <rtdbg.h>

#define LCD_PWR  GET_PIN(E,7)
#define LCD_RST  GET_PIN(B,2)
#define LCD_DC   GET_PIN(A,6)

#define LCD_SPI_NAME "spi10"

static struct rt_spi_device *lcd_dev;

static void LCD_Write_Cmd(uint8_t cmd)
{
    rt_pin_write(LCD_DC, PIN_LOW);
    rt_spi_send(lcd_dev, &cmd, 1);
}
static void LCD_Write_Data(uint8_t dat)
{
    rt_pin_write(LCD_DC, PIN_HIGH);
    rt_spi_send(lcd_dev, &dat, 1);
}
static void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    /* 指定X方向操作区域 */
    LCD_Write_Cmd(0x2a);
    LCD_Write_Data(x1 >> 8);
    LCD_Write_Data(x1);
    LCD_Write_Data(x2 >> 8);
    LCD_Write_Data(x2);

    /* 指定Y方向操作区域 */
    LCD_Write_Cmd(0x2b);
    LCD_Write_Data(y1 >> 8);
    LCD_Write_Data(y1);
    LCD_Write_Data(y2 >> 8);
    LCD_Write_Data(y2);

    /* 发送该命令,LCD开始等待接收显存数据 */
    LCD_Write_Cmd(0x2C);
}
static int lcd_hw_init(void)
{

    rt_err_t ret;
    /**********************将LCD挂载到SPI1上*********************/
    ret = rt_hw_spi_device_attach("spi1", LCD_SPI_NAME, GPIOA, GPIO_PIN_4);
    if(ret != RT_EOK)
    {
        LOG_E("rt_hw_spi_device_attach %s err ...\n",LCD_SPI_NAME);
        return -EBUSY;
    }
    /**********************配置LCD设备需要的spi属性*********************/
    struct rt_spi_configuration cfg;
    cfg.data_width = 8;
    cfg.mode = RT_SPI_MASTER|RT_SPI_MODE_0|RT_SPI_MSB;
    cfg.max_hz =40 * 1000 * 1000;
    lcd_dev = (struct rt_spi_device *)rt_device_find(LCD_SPI_NAME);
    if(lcd_dev == NULL){
        LOG_E("rt_device_find %s err ...\n",LCD_SPI_NAME);
        return -ENODEV;
    }
    else
    {
        ret = rt_spi_configure(lcd_dev, &cfg);
    }
    rt_pin_mode(LCD_PWR, PIN_MODE_OUTPUT);
    rt_pin_mode(LCD_RST, PIN_MODE_OUTPUT);
    rt_pin_mode(LCD_DC, PIN_MODE_OUTPUT);

    return ret;
}
MSH_CMD_EXPORT(lcd_hw_init,lcd_hw_init);
static int lcd_init(void)
{
    /*初始化SPI*/
    lcd_hw_init();

    /* 复位 */
    rt_pin_write(LCD_RST, PIN_LOW);
    rt_thread_delay(100);
    rt_pin_write(LCD_RST, PIN_HIGH);

    /* 关闭睡眠模式 */
    LCD_Write_Cmd(0x11);
    rt_thread_delay(120);

    /* 开始设置显存扫描模式,数据格式等 */
    LCD_Write_Cmd(0x36);
    LCD_Write_Data(0x00);
    /* RGB 5-6-5-bit格式  */
    LCD_Write_Cmd(0x3A);
    LCD_Write_Data(0x65);
    /* porch 设置 */
    LCD_Write_Cmd(0xB2);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x00);
    LCD_Write_Data(0x33);
    LCD_Write_Data(0x33);
    /* VGH设置 */
    LCD_Write_Cmd(0xB7);
    LCD_Write_Data(0x72);
    /* VCOM 设置 */
    LCD_Write_Cmd(0xBB);
    LCD_Write_Data(0x3D);
    /* LCM 设置 */
    LCD_Write_Cmd(0xC0);
    LCD_Write_Data(0x2C);
    /* VDV and VRH 设置 */
    LCD_Write_Cmd(0xC2);
    LCD_Write_Data(0x01);
    /* VRH 设置 */
    LCD_Write_Cmd(0xC3);
    LCD_Write_Data(0x19);
    /* VDV 设置 */
    LCD_Write_Cmd(0xC4);
    LCD_Write_Data(0x20);
    /* 普通模式下显存速率设置 60Mhz */
    LCD_Write_Cmd(0xC6);
    LCD_Write_Data(0x0F);
    /* 电源控制 */
    LCD_Write_Cmd(0xD0);
    LCD_Write_Data(0xA4);
    LCD_Write_Data(0xA1);
    /* 电压设置 */
    LCD_Write_Cmd(0xE0);
    LCD_Write_Data(0xD0);
    LCD_Write_Data(0x04);
    LCD_Write_Data(0x0D);
    LCD_Write_Data(0x11);
    LCD_Write_Data(0x13);
    LCD_Write_Data(0x2B);
    LCD_Write_Data(0x3F);
    LCD_Write_Data(0x54);
    LCD_Write_Data(0x4C);
    LCD_Write_Data(0x18);
    LCD_Write_Data(0x0D);
    LCD_Write_Data(0x0B);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x23);
    /* 电压设置 */
    LCD_Write_Cmd(0xE1);
    LCD_Write_Data(0xD0);
    LCD_Write_Data(0x04);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x11);
    LCD_Write_Data(0x13);
    LCD_Write_Data(0x2C);
    LCD_Write_Data(0x3F);
    LCD_Write_Data(0x44);
    LCD_Write_Data(0x51);
    LCD_Write_Data(0x2F);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x20);
    LCD_Write_Data(0x23);
    /* 显示开 */
    LCD_Write_Cmd(0x21);
    LCD_Write_Cmd(0x29);

    rt_pin_write(LCD_PWR, PIN_HIGH);
    return RT_EOK;
}
INIT_DEVICE_EXPORT(lcd_init);
int lcd_hw_write(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1,
                    void *buffer, uint32_t size)
{
    LCD_Address_Set(x0, y0, x1, y1);
    rt_pin_write(LCD_DC, PIN_HIGH);
    rt_spi_send(lcd_dev, (void *)buffer, size);
    return RT_EOK;
}

完善H文件

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-01-19     12526       the first version
 */
#ifndef DRIVERS_ST7789_H_
#define DRIVERS_ST7789_H_

#include <rtdevice.h>
#include <drv_common.h>

//LCD屏幕分辨率定义
#define LCD_Width   240
#define LCD_Height  240
#define LCD_RAM_SIZE LCD_Width*LCD_Height*2     //长240 宽240 色深2bit
#define Pixel_NUM (LCD_RAM_SIZE/2)

int lcd_hw_write(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1,
                    void *buffer, uint32_t size);
#endif /* DRIVERS_ST7789_H_ */

main.c里加入测试

#include <rtthread.h>
#include <drv_common.h>
#include <drivers/pin.h>
#include "st7789.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

uint8_t data[LCD_RAM_SIZE];

int main(void)
{
    static uint16_t color;
    rt_tick_t t1,t2;

    while(1)
    {
        color +=0x3333;
        for(uint16_t j = 0; j < Pixel_NUM; j++)
        {
            data[j * 2] = color >> 8;
            data[j * 2 + 1] =  color;
        }
        t1 = rt_tick_get();
        lcd_hw_write(0, 0, LCD_Width-1, LCD_Height-1, data, LCD_RAM_SIZE);
        t2 = rt_tick_get();
        rt_kprintf("tick = %d\r\n", t2-t1);
        rt_thread_delay(500);
    }
    return RT_EOK;
}

此时,屏幕应该可以正常点亮了,可以看到执行一次全屏写入需要40 ticks,这明显是CPU在写,我们要修改为DMA传输方式
在这里插入图片描述

修改驱动程序为DMA方式

board.h里添加宏BSP_SPI1_TX_USING_DMA。
在这里插入图片描述
加入同步信号量,DMA传输完成之前,发送线程都得等待。
在这里插入图片描述
之前挖的死等的语句,也用宏屏蔽掉。
在这里插入图片描述
到此为止,SPI+DMA传输,使用RTT SPI框架就全部完成了。此时下载,打开串口能看到,CPU已经被解放到其他任务里去了。写入函数只耽误CPU 2个tick。
注意:这并不意味着DMA会加速一帧的传输,提高帧率。只是意味着CPU从以前的自己搬砖,变成了指挥别人搬砖了。所以write函数只是配置一些DMA参数就退出了,消耗时间会比之前少很多
在这里插入图片描述
吐槽一下,RTT资料好少。2013年的RTT参考手册都被我扒出来用了。但同时,RTT做的确实很漂亮。

  • 25
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝地亚狂想曲

送我一瓶农夫山泉?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值