发帖
15 0 0

AI-WB2软件模拟SPI 点亮LCD屏

putin
论坛元老

23

主题

134

回帖

4008

积分

论坛元老

积分
4008
Ai-WB2系列 6960 15 2024-11-24 13:25:44
本帖最后由 putin 于 2024-11-24 13:25 编辑

一、前言
   最近在和沈工研究使用WB2玩游戏,需要使用先显示屏,最开始想用硬件SPI通讯的,但是不知道为什么只要调用hosal_spi_init(hosal_spi_dev_t *spi)这个函数直接就会卡死在这个函数,只能使用软件SPI了
二、实现原理
SPI 通讯是有主从模式,所以对于主从两个设备来说,通信时钟(SCLK)必须要保持一致,所以引入时钟极性和时钟相位的概念。
所谓时钟极性和时钟相位所指的就是 SCLK 的特性,通过设置这两个值保证主从设备时钟的特性一致,这样才能保证 SPI 能够正常通信。
  • CPHA:时钟相位。表示 SCLK 的边沿,当 CPHA=0,表示第一个边沿,CPHA=1,表示第二个边沿,看不懂可以理解为CPHA=0就是上升沿(0到1的跳变),CPHA=1以此类推。(如果空闲时钟信号为高电平CPHA=0就是下降沿(1到0的跳变),CPHA=1以此类推
  • CPOL:时钟极性。表示 SCLK 在空闲时段(IDLE)是是低电平。当 CPOL=0,idle 是低电平,CPOL=1,idle 是高电平,说白了就是高电平采样(0)还是低电平采样(1)。

所以就能够组成四种模式,有这个概念四种模式原理也很容易推理出来,这边举两个例子说明一下:
模式0(CPHA=0,CPOL=0):空闲时为低电平,上升沿(高电平)采样,低电平改变数据这个模式可以说就是和I2C一样了(如果是用这个模式应该可以用I2C通讯)。
模式3(CPHA=1,CPOL=1):空闲时为高电平,上升沿(高电平)采样,低电平改变数据
三、代码实现原理
3.1定义宏定义来确定通讯引脚
  1. //相关代码在spi_software.h
复制代码
//spi通讯引脚
#define SPI_CS 17
#define SPI_MOSI 12
#define SPI_MISO  4
#define SPI_CLK 5


3.2完成控制时序及引脚初始化函数
  1. /**
  2.   * 函数:SPI写引脚电平
  3.   * 说明:通用 SPI 引脚控制函数
  4.   * 参数:pin 引脚编号,BitValue 电平值 (0 或 1)
  5.   * 返回值:无
  6.   */
  7. static void SPI_WritePin(uint8_t pin, uint8_t BitValue)
  8. {
  9.     bl_gpio_output_set(pin, BitValue); //设置引脚电平
  10. }


  11. void SPI_Init(void)
  12. {
  13.     // 初始化 SPI 引脚
  14.     bl_gpio_enable_output(SPI_CS, 0, 0);
  15.     bl_gpio_enable_output(SPI_MOSI, 0, 0);
  16.     bl_gpio_enable_input(SPI_MISO, 1, 0); // MISO 为输入
  17.     bl_gpio_enable_output(SPI_CLK, 0, 0);

  18.     // 设置默认电平
  19.     SPI_WritePin(SPI_CS, 1);   // 默认 CS 高电平
  20.     SPI_WritePin(SPI_CLK, 0);  // 默认 CLK 低电平
  21. }


  22. void SPI_W_CS(uint8_t BitValue)
  23. {
  24.     SPI_WritePin(SPI_CS, BitValue); // 通过通用函数设置 CS 引脚
  25. }


  26. void SPI_W_CLK(uint8_t BitValue)
  27. {
  28.     SPI_WritePin(SPI_CLK, BitValue); // 通过通用函数设置 CLK 引脚
  29. }


  30. void SPI_W_MOSI(uint8_t BitValue)
  31. {
  32.     SPI_WritePin(SPI_MOSI, BitValue); // 通过通用函数设置 MOSI 引脚
  33. }

  34. uint8_t SPI_R_MISO(void)
  35. {
  36.     return bl_gpio_input_get_value(SPI_MISO); // 读取 MISO 电平
  37. }
复制代码
由二上面原理写的挑选一个工作模式直接编写对应引脚的控制电平函数,这里也可以将其改写为宏定义如:#define SPI_W_MOSI(x) SPI_WritePin(SPI_MOSI, BitValue)
3.3实现SPI通讯
  1. /**
  2.   * 函数:SPI交换传输一个字节,使用 SPI 模式0
  3.   * 说明:发送一个字节并接收一个字节
  4.   * 参数:ByteSend 要发送的字节
  5.   * 返回值:接收到的字节
  6.   */
  7. uint8_t SPI_SwapByte(uint8_t ByteSend)
  8. {
  9.     uint8_t i, ByteReceive = 0x00; // 接收到的数据,初始值为0

  10.     for (i = 0; i < 8; i++) {
  11.         // 发送当前位的数据
  12.         SPI_W_MOSI(ByteSend & (0x80 >> i));  // 获取 ByteSend 的第 i 位数据
  13.         SPI_W_CLK(1);  // 拉高 SCK

  14.         // 读取接收到的数据
  15.         if (SPI_R_MISO()) {
  16.             ByteReceive |= (0x80 >> i);  // 如果 MISO 为 1,设置接收字节的第 i 位
  17.         }

  18.         SPI_W_CLK(0);  // 拉低 SCK
  19.     }

  20.     return ByteReceive; // 返回接收到的字节
  21. }
复制代码
SPI通讯的核心原理就是交换,我们可以通过上面的函数直接就可以完成数据的接收与发送,如果你需要接收数据就可以通过一个变量去接收对应的返回值(因为SPI通讯的核心就是交换,要完成接收就得给一个东西去交换,建议用0xFF或0x00这两个数去换)
3.4TFT驱动
见源码,该部分代码移植于互联网的其他代码。
四、现象
使用注意事项:
1、tft_drive.h文件有一个#define TFT_DC 14 宏定义注意修改为自己的
2、正常应该RST、BLK要接线,代码里留了对应的宏定义,需要使用请自行修改(不打算使用显示屏RST引脚直接接高电平)
五、特别鸣谢
感谢沈工帮我远程搞定WB2环境问题导致不能编译。











──── 0人觉得很赞 ────

TFT_WB2.zip

15.71 KB, 下载次数: 5

使用道具 举报

2025-1-23 11:37:06
KevinLi 发表于 2025-1-22 20:02
这个软件spi,【SPI_SwapByte】这个函数是按照bit 每clk依次写入/读取,
您的项目中,函数满足ST7735的时序 ...

理论上来说翻转速率就是gpio的速度,0.2ms的话你这个太慢了得加延时可以自己用死循环函数冲当延时
2025-1-22 20:02:52
本帖最后由 KevinLi 于 2025-2-3 21:10 编辑

这个软件spi,【SPI_SwapByte】这个函数是按照bit 每clk依次写入/读取,
您的项目中,此方法满足ST7735的时序要求,

请问您是否知道这个间隔大概是多少?是否是按照GPIO的翻转间隔计算呢?

(我发现BL602没有参数可以计算这个参数,只知道cpu主频和XTAL频率。XTAL频率似乎只需在烧录时指定)
我在尝试驱动ws2812,需要每bit间隔/分度值 是0.2ms/200ns左右,
我使用您这个软件方法驱动 失败了(尝试加入延时但vtaskdelay最小值似乎是1ms,插入cpu的nop方法我还没试因为不清楚io翻转速度)。

------
0203更新:

我用逻辑分析仪 发现这个 波形/时间间隔是稳定的,周期是1063ns.
(8Mhz/125ns 和 1063ns差的还是挺多的)



/**
  * 函数:SPI交换传输一个字节,使用 SPI 模式0
  * 说明:发送一个字节并接收一个字节
  * 参数:ByteSend 要发送的字节
  * 返回值:接收到的字节
  */
uint8_t SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00; // 接收到的数据,初始值为0

    for (i = 0; i < 8; i++) {
        // 发送当前位的数据
        SPI_W_MOSI(ByteSend & (0x80 >> i));  // 获取 ByteSend 的第 i 位数据
        SPI_W_CLK(1);  // 拉高 SCK

        // 读取接收到的数据
        if (SPI_R_MISO()) {
            ByteReceive |= (0x80 >> i);  // 如果 MISO 为 1,设置接收字节的第 i 位
        }

        SPI_W_CLK(0);  // 拉低 SCK
    }

    return ByteReceive; // 返回接收到的字节
}


2024-12-4 13:22:59
King6688 发表于 2024-12-3 14:16
看你上面说的貌似板上每个引脚都要接线,代码定义的2个头文件中算上电源线得有9个引脚,而板上一共才8个引 ...

miso是从机发送主机接受引脚,我是单独封装spi通讯函数为了方便需要用到接受和发送的情况我可以直接用(如外部flash)。tft驱动是对spi通讯函数的再次封装只不过tft不需要向主机发送数据。你只要接你外设有的鹰角
2024-12-3 14:16:02
putin 发表于 2024-12-3 11:34
VCC接板子VCC或者3.3
gnd接板子gnd

看你上面说的貌似板上每个引脚都要接线,代码定义的2个头文件中算上电源线得有9个引脚,而板上一共才8个引脚,板上只有SCLK跟SDA,并没有MISO
2024-12-3 11:34:40
King6688 发表于 2024-12-2 20:30
老哥,显示屏板子才8个引脚,代码定义了7个,那VCC、GND接哪里?是否有一个引脚不接的? ...

VCC接板子VCC或者3.3
gnd接板子gnd
2024-12-2 20:30:49
老哥,显示屏板子才8个引脚,代码定义了7个,那VCC、GND接哪里?是否有一个引脚不接的?
2024-12-1 12:44:10
支持支持
牛哇
2024-11-28 09:04:15
分辨率不高呢
2024-11-28 08:53:25
您需要登录后才可以回帖 立即登录
高级模式
12下一页
统计信息
  • 会员数: 31254 个
  • 话题数: 42377 篇