4线SPI实现OLED显示(基于STM32F103ZET6)

2023-10-25 01:40

本文主要是介绍4线SPI实现OLED显示(基于STM32F103ZET6),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

硬件设备:
(1):0.96寸的oled显示屏
(2):stm32开发板,不带接口也可以可,可以用杜邦线引出来插上即可

目的:

我们将利用精英 STM32 开发板上的 OLED 模块接口来点亮OLED,并实现 ASCII 字符的显示。

原理

LCD 都需要背光,而 OLED 不需要,因为它是自发光的。
OLED有多种点亮方式,包括:
(1):6800并行接口方式
(2):8080并行接口方式
(3):三线spi接口方式
(4):四线spi接口方式
(5):IIC 接口方式(只需要 2 根线就可以控制 OLED 了!)
这五种模式怎们设置呢?就是在模块的背面有一些电阻,分别BS0与BS1在控制模式,想要改变模式,就在相应位置焊上电阻
在这里插入图片描述
当然了,有的小伙伴并不是这样的oled,我的模块就不是正点原子的0.96oled,我是自己定义的引脚来控制的!可以根据厂家提供的数据手册资料来修改模式,原理差不多!
在这里插入图片描述

重点来了(四线spi)

想要写好OLED程序,了解模块的原理,流程,数据写入的方向与方式等等都是很重要的,而不是拿着标准的程序死记硬背!下面就对我使用的OLED进行我的理解说明:
引脚说明
CS:OLED 片选信号。
RST(RES):硬复位 OLED。
DC:命令/数据标志(0,读写命令;1,读写数据)。
SCLK:串行时钟线,D0 信号线作为串行时钟线
SDIN:串行数据线,D1 信号线作为串行数据线
VCC与GND也是必须要有的,这么算下来就是7针的OLED模块
IO口的配置比较简单:配置的引脚也能看到

void oled_init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE);GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3|GPIO_Pin_6;GPIO_Init(GPIOD,&GPIO_InitStructure);GPIO_SetBits(GPIOD,GPIO_Pin_3|GPIO_Pin_6);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_0;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_SetBits(GPIOC,GPIO_Pin_1|GPIO_Pin_0);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;GPIO_Init(GPIOG,&GPIO_InitStructure);GPIO_SetBits(GPIOG,GPIO_Pin_15);//oled¸´Î»OLED_RST=0;delay_ms(100);OLED_RST=1; //oled³õʼ»¯oled_writebyte(0xAE,OLED_CMD); oled_writebyte(0xD5,OLED_CMD); oled_writebyte(0x80,OLED_CMD);  oled_writebyte(0xA8,OLED_CMD); oled_writebyte(0X3F,OLED_CMD); oled_writebyte(0xD3,OLED_CMD);oled_writebyte(0X00,OLED_CMD); oled_writebyte(0x40,OLED_CMD); 										    oled_writebyte(0x8D,OLED_CMD); oled_writebyte(0x14,OLED_CMD); oled_writebyte(0x20,OLED_CMD); oled_writebyte(0x02,OLED_CMD); oled_writebyte(0xC8,OLED_CMD);oled_writebyte(0xA1,OLED_CMD); oled_writebyte(0xDA,OLED_CMD); oled_writebyte(0x12,OLED_CMD);oled_writebyte(0x81,OLED_CMD);oled_writebyte(0xEF,OLED_CMD); oled_writebyte(0xD9,OLED_CMD);oled_writebyte(0xf1,OLED_CMD); oled_writebyte(0xDB,OLED_CMD);oled_writebyte(0x30,OLED_CMD); oled_writebyte(0xA4,OLED_CMD); oled_writebyte(0xA6,OLED_CMD); oled_writebyte(0xAF,OLED_CMD); 	OLED_Clear();
}

如图:
在这里插入图片描述
在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到oled模块的SSD1306,并且是高位在前的。DC 线还是用作命令/数据的标志线。
在这里插入图片描述
口述一下从32传输一个字节到模块SSD1306的时序:
写入的数据我们分为了命令与数据,将片选位拉低,在传输数据的的开始,将时钟拉低,取出传输字节的最高位,拉高时钟,这时,1bit数据成功写入SSD1306,循环八次,方可完成一个字节的写入,此时拉高片选。

void oled_writebyte(unsigned char data,unsigned char oled_RS)
{int i;OLED_CS=0;//片选拉低OLED_RS=oled_RS;//发送的数据还是命令for(i=0;i<8;i++){OLED_SCLK=0;if(data&0x80)OLED_SDIN=1;//数据位else OLED_SDIN=0;//数据位data<<=1;//将次高位移动到最高位OLED_SCLK=1;	//时钟拉高}OLED_CS=1;//片选拉高OLED_RS=1;
}

写入SSD1306的data分为数据和命令,写入的命令是来设置OLED的显示参数,写入的数据用来显示,所以:
(1):了解要写入的命令,初始化oled模块
在这里插入图片描述
可根据此流程校验初始化流程!
(2):清除显示的数据是如何写入的,在显示屏中如何显示,现实的方向,大小等等,都很关键
第二点开始:
一个字节的数据写入了,
如果是命令:我们可以不用管如何让排列在模块里,因为这不会影响我们的显示,
如果是数据:
在这里插入图片描述
这是我们写入的数据对应屏幕的关系表,PAGE0包含128个字节,也就是128x8个像素点,从PAGE0~PAGE7共有八个,也就是128x8x8个像素点,我们取出PAGE0,
在这里插入图片描述
上面的数据只是代表个数,不代表真实数据,数字相同的八位构成一个字节,这个字节,就是我们写入的一个字节的显示数据,那这个遵守什么规律呢?
在这里插入图片描述
由写时序可知,先写高字节,再写低字节,所以数据写入的方向如图,总的方向为:从下到上,从左至右
在这里插入图片描述
写入字节的内部结构清楚了,在此之前我们设置显示这个字节的位置(一个字节控制从上到下的八个像素点我们应该是知道的就不详细说明了),上图只是我们的PAGE0,所以我们要确定在哪个PAGE上写数据,其次每页从左到右也有一个位置,所以写入数据,这是两个必须写入的参数,每一页,只需要配置一次,如何更好的控制写入的数据点呢?
可以本地(自己)定义一个屏幕像素点大小的二维数组,eg:char a[128][8]

unsigned char GRAM[128][8];//128*8个字节=128*8*8个像素点

当我们数据点配置(本地的二维数组)好后,一起发送(refresh)到SSD1306里,想要什么图形自己都可以用一个一个的像素点拼凑出来!

void GRAM_REFRESH(void)
{int i;int j;for(i=0;i<8;i++)//8页循环8次{oled_writebyte(0xb0+i,OLED_CMD);//页地址,每次增1oled_writebyte(0x00,OLED_CMD);//显示时的起始列地址低四位oled_writebyte(0x10,OLED_CMD);//显示时的起始列地址高四位for(j=0;j<128;j++)//循环128次,每次从下到上写一个字节{oled_writebyte(GRAM[j][i],OLED_DATA);	}}
}

基本达成oled点亮

小进阶(显示单个字符)

还是一个原理:任何显示的信息,都可以先修改本地(自己定义的二维数组),修改结束后一起(refresh)写入SSD1306,那么怎们来显示字符呢?
我们现在要用到字符集点阵,这个字符集点阵有大有小也就是控制字体的大小,在这个固定大小的区域内显示某个字符,这个字符点阵集可以是软件合成的,也可以在您周围的小伙伴哪里copy一下都可以,并且是const类型的常量
在这里插入图片描述
上面就是1206大小的点阵字符集,代码太多,不便上码。
举例
如果我们要用这个大小的字体显示’!'号,也就是上图的第二行,1206的意思是指高12,宽为6的像素点组成的大小的显示平面,一个字节一个字节的写入。
现在将一个字节看为整体,方向是从上到下,从左到右,如果把某个字节的1bit看为整体,就是我们前面描述到的。
在这里插入图片描述
虚线部分4bit为没写入的位,丢弃掉,这个大小总共占用12个字节,正好对应点阵字符集上的12个数据,

非常重要的函数

void draw_char(unsigned char x,unsigned char y,unsigned char chr,unsigned char size)
{unsigned char csize;unsigned char num1;unsigned char num2;unsigned char data;unsigned char num3=y;unsigned char chr1;csize= (size/8+((size%8)?1:0))*(size/2);//确定字体所占字节数chr1=chr-' ';	//得到偏移后的值,因为点阵字符集的第一个字符为空''for(num1=0;num1<csize;num1++){if(size==12){data=asc2_1206[chr1][num1];}//1206字体else if(size==16){data=asc2_1608[chr1][num1];}//1608的字体else if(size==24){data=asc2_2412[chr1][num1];}//2412的字体else return ;for(num2=0;num2<8;num2++){if(data&0x80){draw_point(x,y);}else {clean_point(x,y);}data<<=1;y++;if((y-num3)==size){y=num3;x++;break;}}			}
}

这个函数将本地的二维数组已经根据字符布置好了,如果想在(20,20)的位置上用12字体显示字符0,可以这样用函数

draw_char(20,20,'0',12)

当然,想要用此函数显示字符串,除了人为的大间隔法(离旁边的单个字符很远处再写个字符),拼凑出来在显示屏上显示的字符串,当然还有显示字符串的方法:

进阶1(显示字符串)

此时我们用到了大家喜欢的指针,也会用到上面的draw_char();函数,在这里我提供给大家两种算法:
(1):

void draw_string(unsigned char x,unsigned char y, char* a,unsigned char size)
{unsigned char X,i;X=x;for(;*a!='\0';a++){draw_char(x,y,*a,size);x+=size/2;if(x+size>128) //此行已经不能容纳更多字符,换行{x=X;//与上一排字符串同x起始位y+=size;//y显示上增加一个字体高度}}
}

draw_char参数里面传入的是字符,在这里我们应该注意,我们可以循环判断传入的字符串是否一字符串结束符’\0’结束作为标志位
(2):利用95个字符判断,处于/" ",0/<(a+i)</"~",94*/之间就为存在字符

void draw_string(unsigned char x,unsigned char y, char* a,unsigned char size)
{while((*a>=' ')&&(*a<='~'))//判断字符是否为这95个字符中的{draw_char(x,y,*a,size);x+=size/2if(x+size>127)  {x=X;y+=size;}a++;}
}

如果说想要显示文字,可以通过点阵字符制作软件生成,自行探索!
效果如图
在这里插入图片描述

进阶2(显示直线)

相信能做完前面的流程,显示直线也应该能够完成,直接上码,大家应该能看得懂:

void draw_line(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2 )
{unsigned  char k1,k2,i,k;if(x1==x2)//画竖线{if(y1>y2){for(y2;y2<=y1;y2++){draw_point(x2,y2);}}else {for(y1;y1<=y2;y1++){draw_point(x1,y1);}}}else if(y1==y2)   //画横线{for(i=0;i<=(x2-x1);i++){draw_point(x1+i,y1);}}else if(x1!=x2&&y1!=y2)//画斜线{k1=y2-y1;k2=x2-x1;k=k1*10/k2;for(i=0;i<(x2-x1);i++){draw_point(x1+i,y1+i*k/10);}}
}

参数x1,y1,x2,y2是直线两点的坐标;

一定在最后不要忘记refresh函数,否则无显示

在此,我只是提供了算法与思路,代码太多,希望对读者有用,懂原理,参考数据手册,其他的oled都不是问题,务必清楚原理后再看代码学习,否则事倍功半!
还有显示圈,图片等等,原理都是一样,希望一定要动手实践操作!
此博客只写了作为写入,没涉及到读出,还有很大的空间可以拓展,谢谢大家,也希望读者有更好的建议给我,互相学习

这篇关于4线SPI实现OLED显示(基于STM32F103ZET6)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/279181

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、