前面已经提到了GPIO的输入检测以及输出控制,现在记录一下关于树莓派GPIO的通信功能,在嵌入式中I2C、SPI、UART是最常用的通信协议,通过这几种协议我们可以实现与很多主从设备的通讯,今天以使用I2C的0.96寸OLED屏幕为例,利用树莓派自带的I2C接口来驱动OLED屏幕显示我们想要显示的东西。
在这里先补充一点协议相关的知识点:
全双工:可以收数据也可以发数据 可以同时进行 -----------两个数据线
半双工:可以收数据也可以发数据 不可以同时进行------- 一根数据线
单工 :只能收数据或者只能发数据 ----------------------------一根数据线
串行 :数据一位一位的传输 ---------一根线传输数据
例如我们即将提到的I2C、UART。
并行 :数据一次全部传输 ---------多根线传输数据
例如智能车竞赛中的摄像头就是采用的并行通信。(以上图片来自此文)
现场总线:可以远距离传输 can (10km) 485(1km) //差模信号
工业控制中常用。
板级总线:芯片之间通信----近距离 IIC SPI //共模信号
一般的元器件间的通信方式。
同步通信:通信双方使用同一个时钟源(时钟频率)
异步通信:通信双方使用自己的时钟源
有关通信的具体分类见此博文。
I2C是利浦公司推出的双向二线制总线,SCL时钟线和SDA数据线,用于数据传输,按照上面提到的知识点分类I2C是串行半双工板级同步有线传输总线。
一条总线挂载多个IIC接口器件-----并行连接在IIC总线上。
有关一组I2C总线最多可以挂接多少个I2C器件以及I2C的详细介绍大家参考此博文
有关I2C的传输流程,之前看见过以为博主把整个流程和踢球做了个类比,我觉得很形象,这里链接分享给大家
有关I2C的详细知识大家参考上述博文了解就好,笔者在此不做分析了,下面进入主题:使用树莓派的I2C。
打开终端命令,输入gpio readall,回车,在返回的IO表中可以看见有SDA1、SCL1;SDA0,SCL0两组I2C接口,我们使用SCL1与SDA1这一组进行。
接线方式如下:
树莓派 | OLED |
---|---|
5V | VCC |
GND | GND |
3脚(SDA1) | SDA |
5脚(SCL1) | SCL |
有界面的或者不习惯命令行的建议直接跳过下面这节,参照有界面的进行。
使用putty或者Xshell登录树莓派,在终端命令输入sudo raspi-config,回车
进入如下界面。
用键盘上下选择3.Interface Option,回车,利用键盘上下选择I5 I2C 回车;
继续回车;
再继续回车;
利用鼠标右键选择finish,回车,返回命令窗口;
输入sudo reboot,重启树莓派。
打开Raspberry Pi Configuration
按下图打开I2C.
然后选择注销;Reboot等待重启。
跟之前使用IO的输入输出一样,也需要安装对应的库才能实现功能。有关wiringPiI2c库的函数与STM32 I2C的常用函数对比如下:
wiringPiI2c | STM32 |
---|---|
wiringPiI2CSetup (const int devId) ; | 外设nit(void);初始化外设 |
wiringPiI2CReadReg8 (int fd, int reg) | iic_read_byte(void);读取一个字节的数据 |
wiringPiI2CWriteReg8 (int fd, int reg, int data) | iic_send_byte(u8 data)//放送一个字节的数据 |
有关此库的详细描述参考此链接。
可以发现wiringPiI2CSetup (const int devId)中有一个参数,结合上面的踢球类比和I2C的理论知识,我们知道这个参数就是从机的地址,在树莓派中可以通过i2detect命令查询到外设的地址,要使用该命令还需要先安装i2c-tool。
打开终端命令输入:sudo apt-get install i2c-tools 回车,等待重新返回命令行即可。(需要保证自己的树莓派有网)
在命令行输入: sudo i2cdetect -l 回车,出现如下显示,说明使用的是I2C1接口。
然后输入: sudo i2cdetect -y 1,回车,会出现下面的显示,这里的3C就是OLED的地址(此时树莓派I2C不挂接其他的器件),一般的0.96寸OLED屏幕的地址都是0x30。
通过上面的对比,我们知道整个I2C的函数只是被替换成了树莓派的函数接口,我们将平时使用的I2C屏幕驱动程序中的对应函数替换即可实现效果了,具体的替换过程可以参考此文。
打开Geany,输入以下代码(取模部分代码太长了,需要的去笔者资源下载0积分或者点赞收藏私信博主获取。)
// An highlighted block
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<assert.h>
#include<termios.h>
#include<string.h>
#include<sys/time.h>
#include<time.h>
#include<sys/types.h>
#include<errno.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <wiringPiI2C.h>
#include <unistd.h>
typedef unsigned char uint8_t;
typedef uint8_t u8;
#define OLED_CMD 0x00
#define OLED_DAT 0x01
int fd;
unsigned char yi[16]={"Angle of beam:"};
unsigned char er[16]={"ming"};
unsigned char san[16]={"Distance:"};
unsigned char si[16]={"okok"};
const unsigned char zi[];
const unsigned char picture1[];
const unsigned char picture2[];
const unsigned char picture3[];
const unsigned char picture4[];
const unsigned char picture5[];
const unsigned char picture6[];
const unsigned char picture7[];
//画点函数
void OLED_SetPos(unsigned char x, unsigned char y)
{
WriteCmd(0xb0+y);
WriteCmd(((x&0xf0)>>4)|0x10);
WriteCmd((x&0x0f)|0x01);
}
//BMP位图显示
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y%8==0)
y = 0;
else
y += 1;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
wiringPiI2CWriteReg8(fd,0x40,BMP[j++]);
}
}
}
void init(void)//OLED初始化
{
wiringPiSetup();
fd=wiringPiI2CSetup(0x3c);//i2c?
wiringPiI2CWriteReg8(fd,0x00,0xa1);//t?í??90xa0
wiringPiI2CWriteReg8(fd,0x00,0xc8);//L?úí??90xc0
wiringPiI2CWriteReg8(fd,0x00,0x8d);//A?5w?
wiringPiI2CWriteReg8(fd,0x00,0x14);
wiringPiI2CWriteReg8(fd,0x00,0xa6);//óí?>:90xa7
wiringPiI2CWriteReg8(fd,0x00,0xaf);// >:
}
void qingping(void)//清屏
{
char zt1,zt2;
for(zt1=0;zt1<8;zt1++)
{
wiringPiI2CWriteReg8(fd,0x00,0xb0+zt1);
for(zt2=0;zt2<128;zt2++) wiringPiI2CWriteReg8(fd,0x40,0x00);
}
}
void ascii(float Angle,float distance)//字符显示函数
{
sprintf(er,"%f",Angle); // float 0 char
sprintf(si,"%f",distance); // double 0 char
int zt;
char zt3,zt4;
for(zt3=0;zt3<4;zt3++)
{
wiringPiI2CWriteReg8(fd,0x00,0xb0+(zt3*2));
for(zt4=0;zt4<16;zt4++)
{
for(zt=0;zt<8;zt++)
{
if(zt3==0) wiringPiI2CWriteReg8(fd,0x40,zi[yi[zt4]*16+zt]);
else if(zt3==1) wiringPiI2CWriteReg8(fd,0x40,zi[er[zt4]*16+zt]);
else if(zt3==2) wiringPiI2CWriteReg8(fd,0x40,zi[san[zt4]*16+zt]);
else if(zt3==3) wiringPiI2CWriteReg8(fd,0x40,zi[si[zt4]*16+zt]);
}
}
wiringPiI2CWriteReg8(fd,0x00,0xb0+(zt3*2)+1);
for(zt4=0;zt4<16;zt4++)
{
for(zt=0;zt<8;zt++)
{
if(zt3==0) wiringPiI2CWriteReg8(fd,0x40,zi[yi[zt4]*16+zt+8]);
else if(zt3==1) wiringPiI2CWriteReg8(fd,0x40,zi[er[zt4]*16+zt+8]);
else if(zt3==2) wiringPiI2CWriteReg8(fd,0x40,zi[san[zt4]*16+zt+8]);
else if(zt3==3) wiringPiI2CWriteReg8(fd,0x40,zi[si[zt4]*16+zt+8]);
}
}
}
}
int main()
{
float Angle = 2.98754546;
float distance = 5.754644545;
init();
delay(10);
qingping();//清屏
delay(10);
ascii(Angle,distance);//字符显示
delay(10);
while(1)
{
//qingping();
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture1);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture2);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture3);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture4);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture5);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture6);
delay(1);
OLED_DrawBMP(0,0,128,8,(unsigned char *)picture7);
delay(1);
}
}
然后命名保存,笔者觉得之前的代码一直放桌面太凌乱了,所以自己新建了文件夹,把代码放在了文件夹内,大家根据自己需求来就行。
由于我修改了目录所以编译的时候也就变了
输入是:cd Desktop/my_program/c(切换到我保存文件的目录)回车
然后输入gcc -o display display.c -lwiringPi 编译和生成执行文件 回车
最后 sudo ./display 回车 运行就可以看见如下效果了 。
可能是由于I2C的速度不够,导致看起来很不舒服,后面打算换个SPI的屏幕再试试,有关取模方法大家参考这篇博文就行,也可以私聊笔者要文档教程。
参考此文。
笔者是想放冰墩墩的,但是官方不让发和冰墩墩有关的,想看效果的可以去B站查看。
树莓派4B学习笔记——系统烧录及初次开机
树莓派4B学习笔记——点亮你的LED
树莓派4B学习笔记——IO输入检测
树莓派4B学习笔记——IO通信篇(I2C)
树莓派4B学习笔记——IO通信篇(SPI)
树莓派4B学习笔记——IO通信篇(1-Wire)
树莓派4B学习笔记——IO通信篇(UART)