Skip to content

由 putin 贡献,Ai-Thinker 进行整理

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

一、前言    最近在和沈工研究使用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
📜 点击展开完整代码
c
//spi通讯引脚
#define SPI_CS 17
#define SPI_MOSI 12
#define SPI_MISO  4
#define SPI_CLK 5

3.2完成控制时序及引脚初始化函数

📜 点击展开完整代码
c
/**
* 函数:SPI写引脚电平
* 说明:通用 SPI 引脚控制函数
* 参数:pin 引脚编号,BitValue 电平值 (0 或 1)
* 返回值:无
*/
static void SPI_WritePin(uint8_t pin, uint8_t BitValue)
{
bl_gpio_output_set(pin, BitValue); //设置引脚电平
}
void SPI_Init(void)
{
// 初始化 SPI 引脚
bl_gpio_enable_output(SPI_CS, 0, 0);
bl_gpio_enable_output(SPI_MOSI, 0, 0);
bl_gpio_enable_input(SPI_MISO, 1, 0); // MISO 为输入
bl_gpio_enable_output(SPI_CLK, 0, 0);
// 设置默认电平
SPI_WritePin(SPI_CS, 1);   // 默认 CS 高电平
SPI_WritePin(SPI_CLK, 0);  // 默认 CLK 低电平
}
void SPI_W_CS(uint8_t BitValue)
{
SPI_WritePin(SPI_CS, BitValue); // 通过通用函数设置 CS 引脚
}
void SPI_W_CLK(uint8_t BitValue)
{
SPI_WritePin(SPI_CLK, BitValue); // 通过通用函数设置 CLK 引脚
}
void SPI_W_MOSI(uint8_t BitValue)
{
SPI_WritePin(SPI_MOSI, BitValue); // 通过通用函数设置 MOSI 引脚
}
uint8_t SPI_R_MISO(void)
{
return bl_gpio_input_get_value(SPI_MISO); // 读取 MISO 电平
}

由二上面原理写的挑选一个工作模式直接编写对应引脚的控制电平函数,这里也可以将其改写为宏定义如:#define SPI_W_MOSI(x) SPI_WritePin(SPI_MOSI, BitValue)

3.3实现SPI通讯

📜 点击展开完整代码
c
/**
* 函数: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; // 返回接收到的字节
}

SPI通讯的核心原理就是交换,我们可以通过上面的函数直接就可以完成数据的接收与发送,如果你需要接收数据就可以通过一个变量去接收对应的返回值(因为SPI通讯的核心就是交换,要完成接收就得给一个东西去交换,建议用0xFF或0x00这两个数去换)

3.4TFT驱动

见源码,该部分代码移植于互联网的其他代码。

四、现象

使用注意事项:

1、tft_drive.h文件有一个#define TFT_DC 14 宏定义注意修改为自己的

2、正常应该RST、BLK要接线,代码里留了对应的宏定义,需要使用请自行修改(不打算使用显示屏RST引脚直接接高电平)

五、特别鸣谢

感谢沈工帮我远程搞定WB2环境问题导致不能编译。

Released under the MIT License. Build Time 2026-09-11 14:52:23