电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
通过SPI接口完成OLED显示控制——OrangePi Zero2测试
分 享
扫描二维码分享
通过SPI接口完成OLED显示控制——OrangePi Zero2测试
orangePi
opizero2
我爱下载
关注
发布时间: 2021-01-18
丨
阅读: 2604
在OrangPi Zero2上通过26针接口完成OLED的显示控制,其实就是在linux用户态完成对于SPI接口和GPIO接口控制的过程。目前的处理手段有两种,一种比较直接的方式,一种是经过封装的间接方式。 本次测试OLED设备为SPI接口,128x64单色屏。 OLED的硬件引脚连接关系图: ![](https://cf05.ickimg.com/bbsimages/202101/35dcadeac4dcd12743594501884780d7.jpg) # 方式一,直接方式 这里的直接方式是指,利用linux系统中提供的设备文件和/sys/class/gpio中的控制方式完成的。先总结一下linux下gpio外设和spi设备的使用方法。 ## GPIO的控制方式 Linux系统在用户态完成GPIO控制的方法之一是导出gpio引脚的方法。 ### 1) gpio引脚编号规则 GPIO引脚分为端口和在端口内的序号,例如PORTB端口的第一个引脚,我们记为PB1。 linux系统中,将端口号按照字母顺序排列,分别为PORTA:0,PORTB:1… … 端口内的引脚按照数字顺序号排列,分别为pin0:0,pin1:1…… ,pin31:31 gpio的编号规则为gpiox = 端口号*32+端口内引脚序号。 例如PB1的gpio编号为:gpiox = 1*32+1 = 33 PC5 的gpio编号为:gpiox = 2*32+5 = 69 依次类推 ![](https://cf05.ickimg.com/bbsimages/202101/16f2e56967578671d9fd0c6db1ece5f8.jpg) 图 OPIZ2的端子图 OPIZ2的gpio引脚导出对应表 ![](https://cf05.ickimg.com/bbsimages/202101/2b2fd90354112bb30b1dec984bb27960.jpg) ### 2) gpio导出方法 在/sys/class/gpio目录下,存在export、unexport和若干个gpiochipx文件。 ![](https://cf05.ickimg.com/bbsimages/202101/0233484f64249d2f19bde6c327bb98c6.png) 其中: export:用于将指定编号的引脚导出作为gpio使用; unexport:用于将导出的gpio删除; gpiochipx:当前处理器包含的gpio控制器; 例如操作gpio编号69的引脚PC5 添加gpio设备接口 echo 69 > export 删除gpio设备接口 echo 69 > unexport ### 3) gpio使用方法 输入输出方向控制 设置为输入:echo “in” >direction 设置为输出:echo “out” >direction 输入输出电平 输出时,控制高低电平;输入时,获取高低电平 高电平:echo 1 > value 低电平:echo 0 > value 边沿触发 控制中断触发模式,引脚被配置为中断后可以使用poll() 函数监听引脚 非中断引脚: echo “none” > edge 上升沿触发:echo “rising” > edge 下降沿触发:echo “falling” > edge 边沿触发:echo “both” > edge ![](https://cf05.ickimg.com/bbsimages/202101/2c08a303081dbb1514b5833b39ec949c.png) ### 4) 方法举例 比如我想监听PC5上的电平变化(也就是边沿触发),那么应该先向“/sys/class/gpio/gpio69/direction”写入“in”,然后向“/sys/class/gpio/gpio69/edge”写入“both”,然后对”/sys/class/gpio/gpio69/value”执行select/poll操作。 程序代码如下: ```c #include
#include
#include
#include
int main() { int fd=open("/sys/class/gpio/gpio69/value",O_RDONLY); if(fd<0) { perror("open '/sys/class/gpio/gpio69/value' failed!\n"); return -1; } struct pollfd fds[1]; fds[0].fd=fd; fds[0].events=POLLPRI; while(1) { if(poll(fds,1,0)==-1) { perror("poll failed!\n"); return -1; } if(fds[0].revents&POLLPRI) { if(lseek(fd,0,SEEK_SET)==-1) { perror("lseek failed!\n"); return -1; } char buffer[16]; int len; if((len=read(fd,buffer,sizeof(buffer)))==-1) { perror("read failed!\n"); return -1; } buffer[len]=0; printf("%s",buffer); } } return 0; } ``` ## SPI设备的使用方法 OrangePi Zero2已经在26针端子上集成了一个SPI接口,内部设备为/dev/spidev1.1 。spi接口的操作方法。 ### 1) SPI的初始化: 首先需要打开/dev/spidev1.1设备,并通过ioctl对spi设备进行配置。 打开设备: ```c fd = open(“/dev/spidev1.1”, O_RDWR); ``` 模式配置: SPI_MODE_0 //SCLK空闲时为低电平,第一个时间延采样 SPI_MODE_1 //SCLK空闲时为高电平,第一个时间延采样 SPI_MODE_2 //SCLK空闲时为低电平,第二个时间延采样 SPI_MODE_3 //SCLK空闲时为高电平,第二个时间延采样 写模式初始化 ```c ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); ``` 读模式初始化 ```c ret = ioctl(fd, SPI_IOC_RD_MODE, &mode); ``` 我们这里使用SPI_MODE_0。 读写传输位数配置: ```c ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits); ``` 这里设置为8位。 时钟速度配置: ```c ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed); ``` 这里根据实际情况设定,我配置位12MHz。 ### 2) SPI读写函数 ```c /@@** * 功 能:同步数据传输 * 入口参数 : * TxBuf -> 发送数据首地址 * len -> 交换数据的长度 * 出口参数: * RxBuf-> 接收数据缓冲区 * 返回值: 0 成功 其它值 操作失败 **/ int SPI_Transfer(const uint8_t *TxBuf, uint8_t *RxBuf, int len) { int ret; int fd = g_SPI_Fd; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long) TxBuf, .rx_buf = (unsigned long) RxBuf, .len = len, .delay_usecs = delay, }; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 1) pr_err("can't send spi message\n"); return ret; } ``` ## OLED的显示控制方式 通过对于GPIO控制方法和SPI设备的使用方法说明后,接下来就是利用这些方式对OLED的使用控制了。 前面介绍了GPIO的使用方法,通过这种控制方式将OLED需要的RST和RS两个引脚分别加载到PC7和PC10引脚。 这里最重要的移植就是对于gpio引脚和通过spi数据写入的移植了。 ### 1) 指令数据写入接口函数 ```c //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat, u8 cmd) { u8 i; uint8_t tx[2], rx[2]; gpiopin_out(OLED_RS, cmd); //写命令 tx[0] = dat; SPI_Transfer(tx, rx, 1); gpiopin_out(OLED_RS, 1); } ``` ### 2) 显示缓冲区刷新函数移植 ```c //OLED的显存 //存放格式如下. //[0]0 1 2 3 ... 127 //[1]0 1 2 3 ... 127 //[2]0 1 2 3 ... 127 //[3]0 1 2 3 ... 127 //[4]0 1 2 3 ... 127 //[5]0 1 2 3 ... 127 //[6]0 1 2 3 ... 127 //[7]0 1 2 3 ... 127 u8 OLED_GRAM[128][8]; //更新显存到LCD void OLED_Refresh_Gram(void) { u8 i, n; u8 tx[128], rx[128]; for (i = 0; i < 8; i++) { OLED_WR_Byte (0xb0 + i, OLED_CMD); //设置页地址(0~7) OLED_WR_Byte (0x00, OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10, OLED_CMD); //设置显示位置—列高地址 gpiopin_out(OLED_RS, 1); for (n = 0; n < 128; n++) tx[n] = OLED_GRAM[n][i]; SPI_Transfer(tx, rx, 128); } } ``` ### 3) 测试代码 ```c static int count; int main( int argc, char *argv[]) { int ret; char str[100] = {0}; OLED_Init(); //初始化液晶 OLED_Refresh_Gram(); OLED_ShowString(20, 1, "OLED TEST"); OLED_ShowString(4, 20, "OrangePi Zero2"); OLED_Refresh_Gram(); while (1) { count++; sprintf(str, "%d", count); OLED_ShowString(4, 40, str); OLED_Refresh_Gram(); usleep(5 * 1000); } return ret; } ``` # 方式二,间接方式 著名的树莓派上面有个WiringPi库函数,用来完成端子上外部设备和gpio引脚的使用。香橙派同样有这样的东西,叫做WiringOP库函数。 ## 源码获取 下载地址:https://github.com/orangepi-xunlong/WiringOP 将源码克隆到本地。 ## 编译和安装 源码克隆后,通过如下命令完成wiringOP的编译和安装。 ```bash cd wiringOP ./build clean ./build ``` 官方安装提示中有板卡选择的部分,但是我在编译的时候并没有出现板卡选择。原因是在编译脚本build中有如下的描述: ```bash 在select_boards()函数中存在如下几条判断, if [[ -f /etc/orangepi-release ]]; then source /etc/orangepi-release elif [[ -f /etc/armbian-release ]]; then source /etc/armbian-release ``` 我们在/etc目录下发现了orangepi-release文件,打开文件的内容如下: ```bash #PLEASE DO NOT EDIT THIS FILE BOARD=orangepizero2 BOARD_NAME="Orange Pi Zero 2" BOARDFAMILY=sun50iw9 BUILD_REPOSITORY_URL=https://github.com/orangepi-xunlong/orangepi-build.git BUILD_REPOSITORY_COMMIT=8d94f1b-dirty DISTRIBUTION_CODENAME=buster VERSION=2.1.0 LINUXFAMILY=sun50iw9 BRANCH=legacy ARCH=arm64 IMAGE_TYPE=user-built BOARD_TYPE=conf INITRD_ARCH=arm KERNEL_IMAGE_TYPE=Image ``` 从文件内容中,我们已经看到了 BOARD=orangepizero2 这样一条定义,所以,select_boards使用这个文件的定义,直接返回了。 我这里编译和安装非常顺利,一次通过了。 ## 测试 执行 gpio readall 命令 ![](https://cf05.ickimg.com/bbsimages/202101/1ef251098f953f0601dff49085a43b43.png) 可以看到当前端子的输出状态,其中PC7和PC10我配置为输出模式,这里都正确的显示出来了。说明wiringOP正确安装,运行正常。 ## OLED的显示控制方式 通过wiringop库方式操作OLED的使用方法和直接操作差不多,由于wiringOP的封装,给我们的感觉使用起来更加的简洁和方便了。 OLED的引脚连接关系如前述,仍然使用RST和RS两个引脚分别连接到PC7和PC10引脚。其它引脚连接到spi设备引脚上 ### 1) 设备初始化 ```c #define OLED_RST 22 #define OLED_RS 26 wiringPiSetup () ; //环境初始化 pinMode (OLED_RST, OUTPUT) ; //GPIO引脚初始化 pinMode (OLED_RS, OUTPUT) ; //GPIO引脚初始化 //SPI外设初始化,spidev1.1 5MHz,mode=0 wiringPiSPISetupMode (1, 1, 5000000, 0); ``` ### 2) 指令数据写入接口函数 ```c //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat, u8 cmd) { u8 i; uint8_t tx[2]; digitalWrite (OLED_RS, ((cmd == 0)?LOW, HIGH)); //写命令 tx[0] = dat; wiringPiSPIDataRW (1, tx, 1); digitalWrite (OLED_RS, HIGH); } ``` ### 3) 显示缓冲区刷新函数移植 ```c //OLED的显存 //存放格式如下. //[0]0 1 2 3 ... 127 //[1]0 1 2 3 ... 127 //[2]0 1 2 3 ... 127 //[3]0 1 2 3 ... 127 //[4]0 1 2 3 ... 127 //[5]0 1 2 3 ... 127 //[6]0 1 2 3 ... 127 //[7]0 1 2 3 ... 127 u8 OLED_GRAM[128][8]; //更新显存到LCD void OLED_Refresh_Gram(void) { u8 i, n; u8 tx[128]; for (i = 0; i < 8; i++) { OLED_WR_Byte (0xb0 + i, OLED_CMD); //设置页地址(0~7) OLED_WR_Byte (0x00, OLED_CMD); //设置显示位置—列低地址 OLED_WR_Byte (0x10, OLED_CMD); //设置显示位置—列高地址 digitalWrite (OLED_RS, HIGH); for (n = 0; n < 128; n++) tx[n] = OLED_GRAM[n][i]; wiringPiSPIDataRW (1, tx, 128); } } ``` ### 4) 测试代码 和直接控制方式采用相同的代码。 # 实测效果展示 将编译,并执行观察效果。 ![](https://cf05.ickimg.com/bbsimages/202101/389c7d6453e6be925bfa55214843ca9c.gif)
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
我爱下载
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交