当前课程知识点:ARM微控制器与嵌入式系统 > 第五章 ARM微控制器的各种外设 > 5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动 > 5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家继续回到我们
ARM微控制器与嵌入式系统MOOC课堂
这个单元我们进行
我们SPI同步串行通讯外设的
最后一个单元
带着大家来实操和编程
那么在讲PWM的时候
我们通过实操编程
把这个板子上的音乐给播放了出来
我当时说这是里头最最好玩的一个模块
其实这个单元我要带着大家做的
我认为是比最最好玩
还要最最好玩的一个模块
就是把我们上面的OLED的
这个点阵显示屏给点亮
这个学期课的最初给大家展示
很多同学在这个板上能做的作品的时候
最最酷炫的莫过于一些很有意思的小游戏
或者是一些科学仪器
有一个屏幕显示了丰富的信息乃至图像
那么我们怎么才能把这样一个屏幕
酷炫爆的给点起来呢
就是这节课带着大家做的
我们所使用OLED这个点阵屏
它可以工作在并行的模式
但是我们最最简单在这个板上的电路里头
是使用SPI与它连接
我们来读一读它的电路图
是左边这个看着凌凌乱乱的这张图
那么里头有很多电源
有很多地
大家慢慢的学电子学要学会读图对吧
我们抛开这些复杂的电阻电容
提炼出里头有用信息的干货
我们会发现什么呢
我们会发现最最重要的是
几个信号引脚的接法
我们的PortD的第二个引脚
一直到PortD的第三个引脚
第一个引脚这些引脚
都被分别用来接了这个屏幕的
DC、Rest、Clock和MOSI
那么我们会发现这个屏幕
使用SPI的接口来进行通讯
而且使用的是半个SPI接口 为什么
因为有很多同学刚才听的很认真
就说SPI它的数据引脚是两根
一根是MOSI
一根是MISO是吧
我们这里只出现了其中一根
为什么呢
因为我们的屏幕基本上是一个只写设备
就只是主机给我们的屏幕写数据
让屏幕更新显示内容
在我们所使用的这块屏上
它没有要反馈给CPU的数据
所以为了节约引脚
它实际上只留了单向通讯
也就是在时钟的驱动下
主机通过Master Output Slave Input MOSI引脚
向屏幕发送数据
就实现了这个屏幕的显示
然后在这个屏幕里头
片选的SS引脚
是一直接地的待会会看到的
然后有一个Reset引脚
是控制这个屏幕上电的时候
完成一次由高到低电平控制的复位
然后DC引脚是控制我们主机
通过SPI接口向这个屏幕所写的
是对屏幕显示功能的控制命令
还是屏幕上要所要显示内容的像素数据
由这个DC引脚的高低电平
来切换Data or Comand 就这么一个接口
那么与之相关的
我们去看看PTD4一直到PTD3
PTD1 一直到PTD21这些引脚
在我们的芯片手册上
它都有没有SPI的功能
或者是有没有我们所要用的功能
那么很显然
我们查一查芯片手册就会发现
DC引脚、Rest引脚其实就是当IO用
给高电平 给低电平
把它配置为IO就可以了
PTD1和PTD3这两个引脚
一个给SPI接口当时钟用
一个SPI接口用做MOSI
我们就应该把它配置成
它所对应的那个功能来当SPI接口用
读完图获得的信息其实就是这些
剩下的问题就是我们如何正确的配置
所对应的SPI模块
然后与我们这个显示屏的
这个芯片显示屏能够通讯起来 对不对
那么在这个部分我不是一次实验
为什么是课呢
因为我要带着大家
逐渐学会若干个技巧和领会若干个思想
其中第一件事情
就是我们做嵌入式系统的时候
我们经常会外扩
今天是曾老师给了你一个屏幕
说已经画成电路是这样的
如果你自己希望设计一个系统
从市场上买到一个屏幕
买到一个SPI接口的芯片
你想把它跟你的微控制器用起来
你应该注意什么呢
第一件事情就是你找厂商
一定在买的同时要它的芯片手册
没有要到你就用谷歌 用百度
去网上搜到芯片手册 注意不要搜错了
去认真读芯片手册
把你Interface接口这个部分的电路
一定要找出来看清楚
那么这个信息往往很晦涩
我来带着大家如何从芯片手册
提取有用信息来编程
比如我们使用这样一个
128乘以64的点阵的小小的OLED很便宜
10块钱 20块钱你找到了以后你会发现
他就有一个比较简略的芯片手册
翻翻翻翻如果大家翻到第16页
就能看到我屏幕上
我给大家的这样一个时序图
和这样一个时序的信息表
那么这个图很多同学不会看
特别初学者就晕了
觉得这个手册这么厚
我怎么找到这个信息呢
首先找图表
找到了以后一定会有个时序
会有一个电器特性这样一张总表
这是非常有用的
那么大家来看这张图 这张表
你从里头能看到什么信息
很多同学什么信息都看不出来
叫一脸蒙逼是吧
不是的
我们要结合刚才讲的SPI的知识来看它
你会发现这是一个什么通讯啊
这是一个同步串行通讯
它有时钟信号
底下有切换的数据信号
然后这个数据的切换在这张图上
没有像我那样打箭头
但是从示意图你看出来它跟谁对齐了
它是跟时钟沿对齐了 对不对
然后我们每一个数据的菱形
这个六边形的切换的前沿
都跟这个时钟信号的第奇数个沿对齐
然后时钟信号的第偶数个沿
都切在这个六边形块的正中间
也就是说在这个位置
适合用来查看数据的值
这是不是信息啊
然后片选信号在前面远远的地方
变成了一个下降沿
但它并没有跟下面的时钟和数据发生关联
也就是说这个数据
是在时钟的驱动下开始工作的
是不是这都是信息
如果大家看的再仔细
你会发现这个数据的块块里是有字的
它是MSB在前面
还是LSB在前面呢
你去看它第一块写的是D0还是写的是D7
最后一个块是写的D7还是写的是D0
是不是这都是信息
再比方我们的时钟
我们的时钟在这张图里
平时是高电平还是低电平
第一个沿是由低变高还是由高变低
这是个时钟的极性
那么这些信息我们看完了
自己要有个概念和归纳
再看这张表
这张表有很多电子学的特性
但是我们只看它的第一项
就能找到我们要的信息是什么呢
是我们的minimum的这个Cycle time
就是时钟周期的最小值 是多少
是100个纳秒
那么100个纳秒意味着什么
时钟周期的最小值
也就上面这个时钟信号由高变低
再由低变高
再由高变低这样一个周期
最少最少不得比100纳秒更短
那就意味着频率
不能比100纳秒分之一更高 对不对
也就是说我们这个SPI接口做通讯的时候
速率不能超过10兆
大家想明白了吗
这些信息加起来是什么
是上节课最后
我要大家写那个Init函数的时候
你那三个两比特的配置信息该怎么写
你的波特率寄存器的时钟该怎么设
大家想明白了吗
来我们对到这张图里看
我们看看我们相位CPHA
相位为1这样一种时钟模式
它是不是能跟我们刚才看到
这个时序图能对应起来啊
我们把我们的
微控制器芯片手册里的图放在上面
把我们要通讯的SPI的
这个液晶屏的手册的图放在下面
两者一比
大家看看我们的时钟的极性
是不是应该设成1
变成由一变零的这种负极性的时钟啊
我们的相位CPHA是不是应该设为1
使用这样一种相位模式啊
不由片选信号驱动通讯
而由第一个时钟沿使通讯开始 对吧
然后我们的LSBFE应该设成零 对不对
我们的数据是D7在前 D0在后
这个跟液晶屏的芯片手册
应该一致 对不对
然后我们片选信号并不直接驱动通信
所以可以持续为低
在我们的液晶屏的电路上
它就接地了直接
这是不是都是关联的
那么这些事情关联了
特别这几个比特的值
应该为1为0确定了
是不是我们对于那个SPIx的那个
Command C1控制寄存器
后面那几个比特应该赋什么值
心里就清楚了
我们这个通讯如果这么写
再加上刚才说的那个波特率
不能大于10兆
我们要分频
要分到一个小于10兆
我们保守一点比如分到5兆
是不是我们通讯
就确保能够通讯起来
给屏幕写的东西就是对的
这是我要给大家讲的第一个关键点
第二个关键点呢
就是这个代码如何实现
我们如何配置引脚的时钟打开
如何配置引脚给SPI用
如何配置SPI工作在正确的状态和相位
还有波特率下
刚才基本都讲了
这个代码我会打包放在网上大家也能看到
我就不一句一句的讲了
大家也可以在这里仔细的看
那么我们怎么来写我们的SPI通讯的函数
实际上在这个上一节课的最后单元
我给大家提示了
SPI的通讯是双向的
我们应该是一个Read and Write函数
带一个字节的参数
有一个字节的返回值
那么就像我们现在代码里头
实现的红色部分
我们每次应该查询读取状态寄存器
check这个发送位是否置位
前一次通讯是否发送完
发送寄存器是否为空
当它为空的时候
我们向它写入一个新的数据
开始一次新的通讯
那么这个通讯SPI
就会自动开始一个比特一个比特发
这是要花一段时间的
所以我们应该持续的查询
接收寄存器的那一位是否置位
那一位置位既表示发送结束了
也表示那个8个Clock走完
接收到了一个新的字节
然后我们再从数据寄存器读一次
此时读到的不是刚才写的值
而是从电路上接收到的字节 对不对
这是一个SPI的完整的读写控制
红色的代码部分的具体实现
也是上节课给大家思考题
那么放到我们这个液晶屏里头
我们其实因为还有一个DC的这个IO引脚
我们对他置0或者置1
分别代表告诉我们的液晶屏
此时给你写的是一个字节的命令
还是一个字节的屏幕显示的值
所以我们在前面各加了一句话
把它封装成两个独立的函数
对液晶屏写一个命令
或者对液晶屏写一个数据
写一个像素值
而我们的液晶屏其实是单向通讯的
我们的MISO对这个主机的输入引脚
其实是没有接的
所以我们从程序的完整性上读了这个字节
为了保证这个SPI模块正确的工作
但实际上这个读回来的字节值
我们是扔掉的
并没有保留和返回
使用了void的返回值
这都是可以理解的
所以大家根据自己编程的目标
灵活的修改
你所写出来的函数
从它的原型 到它的代码实现
要恰如其分的描述你要的那个功能
大家还是记住C语言这句话
函数就是function
function也是功能
把这些概念给打清楚
这是我要给大家说的第二个点
那么如果我们实现了第二个点
我们能写入一系列的命令
我们上电启动流程呢
这一大段代码其实没有什么秘诀
芯片手册告诉我们这个屏幕要正常工作
要有一系列的配置指令
依次写入这些值
这些芯片这个液晶屏
就能进入一个基本的正常工作状态
所以我们依次让reset引脚由高变低
持续一段时间再由低变高
然后依次写入这些命令完成初始化
这个是完全按照芯片手册来
没有什么特殊的
那么第三个点是什么呢
第三个点是我们对于OLED的点阵屏
写入的数据
如何与我们看到的显示内容发生关联
我们究竟如何对它编程
来控制我们要显示内容
这是大家很关心的对吧
我们这个显示屏虽然小
分辨率其实不算太低
它是128乘以64的点阵
它是一个黑白的OLED
所以在屏幕上显示的值就是0或者1
这样两个状态
所以一个比特对应一个像素非常好理解
那么整个屏幕的信息
我考考大家是多少个比特呢
那就128乘以64个比特 对不对
那么我们对于这样一个屏幕
进行编程的时候 为了提高效率
这个屏幕在设计的时候
以纵向排列的8个比特构成一个读写单元
也就是一个字节
所以纵向的一列一列这么排下来128个条
就构成了八行128列的像素
是由若干128个字节的依次写入
可以实现控制的
我们写入的每一个字节
对应这下面这一绺的8个像素点
每一个比特的1或者0
对应着一个像素点的亮或者灭
我们就能控制这一个像素点上
显示的是什么内容
所以大家算一算
一共有128乘以64个比特的像素点
要被控制和显示
我们显示这样一整个屏幕
需要多少个字节呢
其实是128乘以64再除以8 对吧
一共1024个字节
就能够完成整个屏幕上的读写
所以我们一个字节一个字节的读写
从左上角写到右下角
大家脑袋想一想
就要读写1024次
1024个字节
如果更有心的同学
你后面还可以算一算
我如果用一个5兆
或者是不超过10兆SPI接口
来依次向一个屏幕写入这些值
我从左上角写到右下角
把整个屏幕刷一遍
最少要多少时间呢
你可以算一算 对吧
那么这是一个概念
另外一个概念
我们这么一个个的像素
如何使屏幕上看到我们想要的
那些丰富的多彩的图案和功能呢
这是一个很好的问题
我们把这样一绺像素的八个点拿下来
看成了一个条
那么这八个点我可以给它一个字节
这一个字节的比特7最高位
对应最下面那个点
比特0最低位对应最上面那个点
所以每个字节对应一条八个点
当我连续写入8个字节的时候
是不是我就控制了屏幕上
8乘以8这样一个小方块的
一个区域显示的内容
所以曾老师给大家的例程
包括大家以后在网上
找这种点阵屏的显示例程
都会有一个数组
或者有一个单元我们称为字模
里头存的都是一些神奇的字符串
0X00 0X08 0Xxx等等这么下来
然后在我这个数组后面
我标识一系列特征的字符
那么它怎么来控制我们在屏幕上的显示呢
如果我们看我们右边的代码
我们可以通过两个控制指令
指定当前把坐标移到屏幕上
虚拟看不见的坐标
移到屏幕上
第几行第几列的位置开始写入数据
然后接着我们可以连续
不再有控制命令的向屏幕
写入一系列的字符
一系列的字节
来完成一个区域的更新
而我们到了第几行第几列
来更新这个区域的时候
写入我们这样一个字库的这样一个数据串
这个串它怎么构成我们要的字符
我举个例子
比如说我们在屏幕上要看到字母A
我们去看看我们这个字库表里头的这个串
我们会发现第一行
第一个字符是0X00
第二个字符是0X80
第三个字符拆成二进制是0b1100 0000
那么我们看我们A这个字母
第一列是不是全是空白的
第二列全是空白
最下面一个点是红的
第三列全是空白
最下面两个点是红的
你如果有耐心把我们每一个数组里头
这个数对应写成二进制
你会发现它就是我们这一列一列的像素点
所以这是笨办法
我们用一系列的字符数组
记录了人类所想看到的
每一个字符的形状存在数组里
然后让我们的程序
依次把它绘制到屏幕上我们就看到了A
类似的我们可以看到桃心儿 对吧
那么有很多同学说
这个桃心儿看上不像桃心儿啊
这个A看上去很丑啊
同学请你把屏幕暂停后退五步
再来看这个字符
或者看一看
我现在显示的这个小字符
你会发现这字母看上去挺好看的
这就是8乘8的字库
那么实际上
我们当然可以生成不同的字符
乃至中文的字库对应不同的字体
这都是可以做的
如何提取字符 字模 字体
这是一个更加专业的领域
大家可以通过计算机的编程
从现有的字库里提取
然后也有一些很有意思的网上的小工具
可以帮你生成这样一个
直接生成这种C语言的数组也是有的
那么大家领会的是这么一种概念
所以我们通过编程对应这个SPI的接口
在屏幕上指定到任意坐标
连续输入八个字节的信息
控制屏幕上8乘8的矩阵
每一个点是亮是灭
就完成了一个字符的显示
而这些事情毫不夸张的讲
完成在电光火石之间
所以我们就看到屏幕上多了一个字
如果我们对于屏幕整体的刷新
大家刚才说过
我说过让大家算一算对不对
都能控制在微秒 毫秒量级完成刷新
我们周而复始
我们就可以看到屏幕上有清晰的画面
有连续的动画
有我们想要的任何效果
这就完成了对于这样一个屏幕
最最基础的控制
所以我给出了一些参考的代码
大家可以非常容易的实现
在屏幕任意坐标显示字符
这样一个功能
但它是不够的
我希望大家第一融会贯通SPI通讯
学会使用这样一个屏幕
能够在这门课
这样一个最最好玩的内容里
把这个屏幕灵活的用起来
但是更重要的是
往下接着编程领会我刚才讲的
这两三个方面的关键知识 函数的封装
芯片手册的阅读
自己独立的去编程实现这样一个接口
然后把这样一个屏幕玩起来
做一个小游戏
或者做个小应用
实现你想要的那个设计
那么有些高阶段的同学
我在这里给大家拓展一个脑洞部分
屏幕有了
128乘64我们怎么把它用好呢
从刚才问的那几个问题开始
128乘以64的点阵
需要多少个比特来存储啊
我们刚才说过需要128乘以64个比特
那它是多少个字节呢
我们说它是1024个字节
那么这样1024个字节
我是每次算出来屏幕上要显示什么内容
还是把它用一个数据结构存储起来合适呢
那我用什么数据结构
有很多同学想象那我生成一个数组呗
1024个元的unsigned char数组
1024个字节
那有了这个数组
我从数组的第一个元到最后一个元
从左上角刷新到右下角
是不是就更新了一个屏幕的全屏的内容呢
那我在固定的时钟
比如8兆或者5兆下你能不能算一算
你更新一屏的内容要多长时间呢
那么这个更新这个屏幕的内容
这个时间快不快 慢不慢
跟你测一测
算的 跟你算的一不一样
测一测跟你算的一不一样
它够不够快
够不够视觉暂留够不够你用来刷一个
速度很快的游戏 对吧
然后第二呢
我们如果做了一个更复杂的程序
一方面要不停的改变显示的内容
一方面要保证这个显示的内容
持续的稳定的更新到屏幕上
怎么让这个程序不打架写的漂亮呢
然后有的同学会说我用for循环里头
再弄一个循环专门用来定期
每次循环去刷一下屏幕
还有的同学说不对
更好的方法跟音乐一样
我把它放在一个时钟中断里头
每隔比如说20毫秒 10毫秒
我就刷一次屏幕
我算了 刷完这一次屏幕
从左上角到右下角只用5毫秒
所以在时钟中断里肯定能够完成
这都是好主意大家可以试一试
然后我怎么让这样一个
前台对于显示内容的不断更新
和后台对于显示内容的不断的
往屏幕上读写这两个事情不冲突呢
有了中断这个机制
我是不是还得有
刚才提到这个数组这个机制
这个数组开一个占掉一K字节
我能够开多于一个吗
比如说我乒乓一下
一个数组在前面算一个数组就往后面显示
来回切换
那么如果大家有了这些思想的雏形
我就不再把脑洞继续展开了
大家可以理解我们真正在
有操作系统或者没有操作系统
高性能的一些游戏或者是应用上
Frame buffer是什么机制
然后在对于这些数组
我能不能再进一步封装一些独特的函数
在坐标上画点画线
或者根据一个巨大的屏幕
我这一次只更新了其中一个部分的显示
比如一个大的背景图里
只有一个局部发生了变化
我能不能只更新这个局部呢
那么这样就会产生图形引擎的概念
大家就可以尝试
那么在我们过去的大作业
这些同学里很多同学非常聪明
他没有做图形引擎
他做了类似的思想
他把曾老师给的代码往屏幕上显示字符
灵活的使用
举个简单例子
比如说有同学
做了一个天上掉金币去接的小游戏
他实际上的做法
在这里告诉大家
是不断的在屏幕随机的位置显示金币
而下一次这个金币往下掉一个坐标
算出来以后
它只更新上一次坐标位置显示一个空格
把这个金币抹掉
在新的位置显示金币
所以它每次更新的量并不是刷整个屏幕
而是刷若干个局部点
速度就会快很多
类似这样的想法希望大家脑洞大开
做出自己更有意思的应用
把这样一个好玩的屏幕用起来
跟我们前面的按键 音乐
以及以后的各种物理量的测量
关联起来
做出自己有意思的应用
-1.1 课程概览
--1.1 课程概览
-1.2 如何学好嵌入式系统
-2.1 计算机的基本概念、发展历史
-2.2 从晶体管到CPU
-2.3 概念CPU、微控制器MCU和嵌入式系统
-2.4 八卦计算机史
-2.5 不同领域、不同系列的嵌入式系统
-2.6 ARM历史与MKL25Z128 MCU
--2.6 ARM历史与MKL25Z128 MCU【习题】
-3.1 CPU的基本结构和运行机制
-3.2.1 堆栈的概念
--3.2.1 堆栈的概念【习题】
-3.2.2 堆栈的概念-头脑体操
-3.3.1 ARM的体系结构
--3.3.1 ARM的体系结构【习题】
-3.3.2 ARM的体系结构-头脑体操
-3.4.1 中断的概念和机制
-3.4.2 中断子程的概念和编程
-3.5 复位、时钟、存储器和总线
--3.5 复位、时钟、存储器和总线【习题】
-3.6 小结:MCU的总体结构和程序运行机制
--3.6 小结:MCU的总体结构和程序运行机制【习题】
-4.1 第一种外设:IO
-4.2 IO外设的编程实操-点亮LED
-4.3 IO外设的进阶知识
-4.4 嵌入式开发的基本概念与工具链
-4.5 嵌入式开发的进阶知识
-4.6 嵌入式开发中的C语言(上)
--4.6 嵌入式开发中的C语言(上)【习题】
-4.7 嵌入式开发中的C语言(下)
--4.7 嵌入式开发中的C语言(下)【习题】
-E0.1 实验零 开发板的初步认识与工具链的安装
-E0.2 实验零 体验一个例程的编译与下载
-E0.3 实验零 编写第一个程序:点亮核心板LED
-E1 实验一 点灯秘籍
-5.1 ARM微控制器外设学习概述
-5.2.1 ARM微控制器外设:通讯
-5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)【习题】
-5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器的外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)【习题】
-5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
--5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
-5.2.5 ARM微控制器外设:UART的寄存器编程(上)
--5.2.5 ARM微控制器外设:UART的寄存器编程(上)
-5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)【习题】
-E2 实验二 UART编程实操
-5.3.1 ARM微控制器外设:IO的中断编程(上)
-5.3.2 ARM微控制器外设:IO的中断编程(下)
-5.4.1 ARM微控制器外设:定时器的原理
-5.4.2 ARM微控制器外设:定时器的编程
--5.4.2 ARM微控制器外设:定时器的编程【习题】
-E3 实验三 定时器中断编程实操
-5.5.1 ARM微控制器外设:PWM的原理
-5.5.2 ARM微控制器外设:PWM寄存器与编程
-5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
--5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
-E4 实验四 数码管显示编程实操
-5.6.1 ARM微控制器外设:SPI通讯简介
--5.6.1 ARM微控制器外设:SPI通讯简介【习题】
-5.6.2 ARM微控制器外设:SPI寄存器与编程
-5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
--5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
-5.7.1 ARM微控制器外设:I2C通讯简介
-5.7.2 ARM微控制器外设:I2C的通讯协议
-5.7.3 ARM微控制器外设:I2C寄存器与编程
--5.7.3 ARM微控制器外设:I2C寄存器与编程【习题】
-5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
--5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
-5.8.1 ARM微控制器外设:ADC简介
-5.8.2 ARM微控制器外设:ADC基础
-5.8.3 ARM微控制器外设:ADC寄存器与编程
-E5 实验五 ADC编程实操
-E6 挑战实验
--E6 挑战实验
-6.1 嵌入式系统的接口与设计
-6.2 嵌入式系统的实例