跳转至

06 SPI实验

实验说明

我们这里会使用SPI去驱动一个0.96的OLED,首先需要打开SPI

sudo raspi-config
Interfacing Options------>SPI------>Yes------->OK------->finsh

然后将屏幕接到树莓派上,接mosi和sclk的脚,DC接28,RST接29,CE接CE0

这个实验有一个坑我还没找到,就是每次我使用了BCM库来控制屏幕后,就不能使用Python和WringPi来控制了,这时候把SPI关一次再开一次才可以使用,但是如果一个时间内只掉用他们中的单独一个,是不会有问题的。

image-20240817201702470

代码编写

wiringPi

主要说一下怎么初始化和发送数据,具体的实现,参考开源链接。

1、选择SPI的通道和速度。

channel只有0和1这两个值,表示初始化哪个SPI通道;

speed的范围是500000 到32000000,这个是SPI通讯的速度范围,单位是Hz;

int wiringPiSPISetup     (int channel, int speed) ;

2、开始数据传输

channel只有0和1这两个值,表示初始化哪个SPI通道; data要传输的字节,十六进制; len传输的字节长度。

int wiringPiSPIDataRW    (int channel, unsigned char *data, int len) ;

下面是一个初始化SPI的代码,然后使用的OLED.c就是中景元开源的代码,将SPI部分做了修改。

这是main.c的代码

#include <wiringPi.h>
#include <stdio.h>
#include <wiringPiI2C.h>
#include "oled.h"
#include <wiringPiSPI.h>

#define LCD_DC  28     // 模式选择 1:写数据 0:写命令
#define LCD_DIN 12     // 串行数据线
#define LCD_CLK 14     // 串行时钟线
#define LCD_RST 29     // 复位信号  低电平有效
#define LCD_CE  10     // 芯片使能  低电平有效

#define SPI_CHANNEL 0

void oled_write_one_byte(uint8_t dat,uint8_t mode)
{ 
    digitalWrite(LCD_CE,LOW);
    if (mode == OLED_DATA) { // 写数据
        digitalWrite(LCD_DC,HIGH);
    }
    else { // 写命令
        digitalWrite(LCD_DC,LOW);
    }
    uint8_t *tempData = &dat;
    wiringPiSPIDataRW(SPI_CHANNEL, tempData, 1);
    digitalWrite (LCD_CE, HIGH);
}

void set_rst_level(uint8_t level)
{
    if(level == 1)
    {
        digitalWrite(LCD_RST,HIGH);
    }else
    {
        digitalWrite(LCD_RST,LOW);
    }
}

void delay_ms(uint8_t ms)
{
    delay(ms);
}

int main(void)
{ 
    if(wiringPiSetup() < 0) //当使用这个函数初始化树莓派引脚时,程序使用的是wiringPi 引脚编号表。
        return 1;

    pinMode(LCD_DC,OUTPUT);
    pinMode(LCD_RST,OUTPUT);
    pinMode(LCD_CE,OUTPUT);

    int isOK = wiringPiSPISetup(SPI_CHANNEL, 1000000);
    if (isOK == -1) {
        printf("SPI设置失败\n");
    }
    else {
        printf("SPI设置OK == %d\n",isOK);
    }

    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0,0,(char*)"HELLO2",16,1);
    OLED_Refresh();//更新显示

    return 0;
}

这是oled.c主要修改的部分

extern void oled_write_one_byte(uint8_t dat,uint8_t mode);
extern void set_rst_level(uint8_t level);
extern void delay_ms(uint8_t ms);

void OLED_WR_Byte(uint8_t dat,uint8_t mode)
{
    oled_write_one_byte(dat,mode);
}

void OLED_RST_SET(uint8_t level)
{
    set_rst_level(level);
}

void LCD_DELAY_MS(uint8_t ms)
{
    delay_ms(ms);
}

//OLED的初始化
void OLED_Init(void)
{
    OLED_RST_SET(0); // 液晶复位
    LCD_DELAY_MS(20);
    OLED_RST_SET(1);
    LCD_DELAY_MS(20);
    ...
}

然后编译这段代码

cc -Wall -o main main.c oled.c -lwiringPi

-Wall 表示编译时显示所有警告,-lwiringPi 表示编译时动态加载 wiringPi 库

编译完成后调用生成的main文件

sudo ./main

想要停止这个程序,Ctrl+c即可。

bcm2835

主要说一下怎么初始化和发送数据,具体的实现,参考开源链接。

1、开始SPI操作

int bcm2835_i2c_begin(void);

2、设置高位在前还是低位在前

void bcm2835_spi_setBitOrder(uint8_t order);

3、设置工作模式

void bcm2835_spi_setDataMode(uint8_t mode);

4、设置分频系数

void bcm2835_spi_setClockDivider(uint16_t divider);

5、选择控制哪个CS

void bcm2835_spi_chipSelect(uint8_t cs);

6、选择激活时的CS电平,就是工作时候的CS电平

void bcm2835_spi_setChipSelectPolarity(uint8_t cs, uint8_t active);

7、结束SPI的操作

void bcm2835_spi_end(void);

注意,下面是一个实际例子,oled部分的处理和wiringPi一样这里就不写了。注意两种库的引脚编号不一样

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <bcm2835.h>
#include "oled.h"

#define LCD_DC  20     // 模式选择 1:写数据 0:写命令
#define LCD_DIN 10     // 串行数据线
#define LCD_CLK 11     // 串行时钟线
#define LCD_RST 21     // 复位信号  低电平有效
#define LCD_CE  8     // 芯片使能  低电平有效

void oled_write_one_byte(uint8_t dat,uint8_t mode)
{ 
    if (mode == OLED_DATA) { // 写数据
        bcm2835_gpio_write(LCD_DC,HIGH);
    }
    else { // 写命令
        bcm2835_gpio_write(LCD_DC,LOW);
    }
    bcm2835_spi_transfer(dat);
}

void set_rst_level(uint8_t level)
{
    if(level == 1)
    {
        bcm2835_gpio_write(LCD_RST,HIGH);
    }else
    {
        bcm2835_gpio_write(LCD_RST,LOW);
    }
}

void delay_ms(uint8_t ms)
{
    bcm2835_delay(ms);
}

int main(int argc, char **argv)
{
    if (!bcm2835_init())//所有外围io引脚初始化,之前已经分析过了
    {
        printf("bcm2835_init failed. Are you running as root??\n");
        return 1;
    }

    bcm2835_gpio_fsel(LCD_DC, BCM2835_GPIO_FSEL_OUTP); //设置为输出模式
    bcm2835_gpio_fsel(LCD_RST,BCM2835_GPIO_FSEL_OUTP); //设置为输出模式

    if (!bcm2835_spi_begin())    {
        printf("bcm2835_spi_begin failed. Are you running as root??\n");
        return 1;
    }

    bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);  
    bcm2835_spi_setDataMode(BCM2835_SPI_MODE0); 
    bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_32);  
    bcm2835_spi_chipSelect(BCM2835_SPI_CS0);
    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW); 

    OLED_Init();
    OLED_Clear();
    OLED_ShowString(0,0,(char*)"HELLO3 world",16,1);
    OLED_Refresh();//更新显示


    bcm2835_spi_end();  
    bcm2835_close();

    return 0;
}

然后编译这段代码

gcc -Wall main.c oled.c -o main -lbcm2835

-Wall 表示编译时显示所有警告,-lbcm2835 表示编译时动态加载bcm2835 库

编译完成后调用生成的main文件

sudo ./main

想要停止这个程序,Ctrl+c即可。

RPi.GPIO

python控制iic的话,是有专门的别人写好的库。有两个库可以选择luma和Adafruit,但是我们下面的使用只使用luma。

pip install luma.oled

我们使用python安装的时候,应该会报错error: externally-managed-environment,网上有几种方法解决,可以参考这篇博文,我使用的是他说的创建venv的方式。

python -m venv ~/myenv        #创建虚拟环境,myenv就是环境名
source ~/myenv/bin/activate   #使能我们创建的虚拟环境
pip install luma.oled         #再次Pip安装

其他env操作

deactivate #退出环境

直接给代码了,python应该比较好理解

from luma.core.interface.serial import i2c, spi
from luma.core.render import canvas
from luma.oled.device import ssd1306

# 创建 SPI 设备,使用 SPI-0
serial = spi(device=0, port=0, gpio_RST=21, gpio_DC=20)

# 创建屏幕的驱动实例
device = ssd1306(serial)

# 开始往屏幕上绘图。draw 是 Pillow 的实例,它里面还有非常多的绘图 API。
with canvas(device) as draw:
  draw.rectangle(device.bounding_box, outline="white", fill="black")
  draw.text((30, 40), "Hello World!", fill="white")

# 这行是为了阻止程序退出,因为退出的时候会调用析构函数,清空屏幕。防止一闪而过,什么也看不到。
while (True):
  pass

更多资料可查看https://github.com/rm-hull/luma.examples