Skip to content

Contributed by putin, organized by Ai-Thinker

AI-WB2 Software-Simulated SPI to Light Up an LCD Screen

  1. Foreword    Recently I was working with 沈工 on using the WB2 to play games, which requires a display. Initially I wanted to use hardware SPI communication, but for some reason calling hosal_spi_init(hosal_spi_dev_t *spi) would get stuck inside the function itself, so software SPI was the only option.
  2. Implementation principle SPI communication has a master-slave mode, so for the master and slave devices, the communication clock (SCLK) must stay consistent. This is why the concepts of clock polarity and clock phase are introduced.

Clock polarity and clock phase refer to the characteristics of SCLK. By setting these two values, the clock characteristics of the master and slave devices are kept consistent, which ensures that SPI can communicate properly.

  • CPHA: clock phase. It indicates the edge of SCLK. When CPHA=0, it means the first edge; when CPHA=1, it means the second edge. If that is hard to follow, think of CPHA=0 as the rising edge (the 0-to-1 transition), and CPHA=1 as the next one. (If the idle clock signal is high, CPHA=0 means the falling edge (the 1-to-0 transition), and so on for CPHA=1.)
  • CPOL: clock polarity. It indicates whether SCLK is low during the idle period. When CPOL=0, idle is low; when CPOL=1, idle is high. In short, it is about sampling on high level (0) or sampling on low level (1).

With these, four modes can be formed. With this concept in mind, the principle of the four modes is easy to reason out. Here are two examples:

**Mode 0 (CPHA=0, CPOL=0): idle at low level, samples on the rising edge (high level), changes data at low level. This mode can basically be said to be the same as I2C (if this mode is used, I2C communication should also work).

Mode 3 (CPHA=1, CPOL=1): idle at high level, samples on the rising edge (high level), changes data at low level.

  1. Code implementation principle

3.1 Define macros to determine the communication pins

  1. //The related code is in spi_software.h
Click to expand full code
c
//spi通讯引脚
#define SPI_CS 17
#define SPI_MOSI 12
#define SPI_MISO  4
#define SPI_CLK 5

3.2 Implement the control timing and pin initialization functions

Click to expand full code
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 电平
}

Based on the principle above, pick a working mode and directly write the control level functions for the corresponding pins. These can also be rewritten as macros, e.g.: #define SPI_W_MOSI(x) SPI_WritePin(SPI_MOSI, BitValue)

3.3 Implement SPI communication

Click to expand full code
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; // 返回接收到的字节
}

The core principle of SPI communication is exchange. With the function above we can complete data reception and transmission directly. If you need to receive data, just use a variable to receive the corresponding return value (because the core of SPI communication is exchange — to receive, you have to give something in exchange; it is recommended to exchange with 0xFF or 0x00).

3.4 TFT driver

See the source code; this part was ported from other code found on the internet.

  1. Result

Usage notes:

  1. There is a #define TFT_DC 14 macro definition in the tft_drive.h file; remember to change it to your own pin.

  2. Normally the RST and BLK pins should be wired up; the code keeps the corresponding macro definitions, so modify them yourself if needed (if you don't plan to use the display's RST pin, connect it directly to high level).

  3. Special thanks

Thanks to 沈工 for helping me remotely fix the WB2 environment issue that prevented compilation.

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