IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 0.96寸OLED屏幕-stm32f1点亮【寄存器版本】 -> 正文阅读

[嵌入式]0.96寸OLED屏幕-stm32f1点亮【寄存器版本】

	(题外话)为什么选择寄存器来实现,对于初学者而言我非常建议从寄存器配置开始,主要是因为搞单片机本来就是一项接近于底层硬件的工作,不要嫌麻烦。了解硬件外设工作原理和配置过程会对以后的调试有很大帮助。更容易理解库函数开发。

1.硬件资源描述

主控STM32F103RC
通讯方式硬件SPI1 +DMA1(DMA可选)
屏幕0.96寸蓝色OLED屏幕

下面是屏幕图片
在这里插入图片描述
在这里插入图片描述
2.OLED屏幕驱动方法说明
屏幕的话可以在那啥宝上买到大概10块钱,现在应该还没涨价吧。至于买IIC协议的还是SPI协议的就看单片机使用习惯了,个人还是喜欢SPI的,相比IIC讲SPI传输速度更快点。至于NSS是接地还是硬件控制,看个人习惯,我选择的是硬件控制。

	屏幕与单片机接线如下:
或者
D0
SCK : PA5
D1
MOSI : PA7
DC
PA1
RES
PA3
NSS
PA4
GND

注意:VDD和GND千万不要搞反了不然10块钱白给(我就烧坏了一个)😂

2.1:怎么和屏幕通讯并配置单片机的通讯

  • 下面这张图是OLED屏幕驱动芯片手册提供的SPI时序List itemCS# :通讯时一定要保持低电平。
    D/C# :就是命令模式选择信号低电平时SDIN(D1)的数据就是命令控制字节。
    SCLK :空闲电平高低无关,从这条时钟线可以看出屏幕是在上升沿锁存数据,下降沿允许数据变化。(这点很重要!)
    SDIN(D1) :8为数据高位在前。

(第三行的时序就是最后两行的组合)

  • 单片机通讯配置
    在这里插入图片描述
    这张图来自STM32F1参考手册
    stm32f1可以将SPI配置成4种通讯时序。对比我们的屏幕,只能选择上升沿采样的两种方式。

如下表:

序号控制位描述
1CPHA=1 CPOL=1空闲时高电平,第二个边沿采样(上升沿)
2CPHA=0 CPOL=0空闲时低电平,第一个边沿采样(上升沿)

下面的例程使用的是上表中序号2。
stm32SPI1寄存器配置如下:

void SPI1_Init(void)
{
	u16 spitest= 0;
	
	RCC->APB2ENR			|= 1<<2;				//IO端口A时钟开启
	RCC->APB2ENR			|= 1<<0;				//辅助功能IO时钟开启
	RCC->APB2ENR			|= 1<<12;				//SPI1时钟开启
	GPIOA->CRL				&= 0x0F00FFFF;			//PA4配置清除(NSS)		PA5配置清除(SCK)		PA7配置清除(MOSI)
	GPIOA->CRL				|= 0xB0BB0000;			//PA4复用功能推挽输出模式,输出模式,最大速度10MHz		PA5 & PA7复用功能推挽输出模式,输出模式,最大速度50MHz
	
	spitest						|= 1<<2;			//配置为主设备
	spitest						|= 1<<9;			//软件从设备管理
	spitest						|= 1<<8;			//NSS输出
	spitest						|= 1<<15;			//选择“单线双向”模式
	spitest						|= 1<<14;			//输出使能(只发模式)
	spitest						&=~(1<<1);			//空闲状态时,SCK保持低电平
	spitest						&=~(1<<0);			//数据采样从第一个时钟边沿开始
	spitest						&=~(1<<11);			//使用8位数据帧格式进行发送/接收
	spitest						&= 0xFFFFFFC7;		//波特率36Mbs= 4.5M/S
	spitest						&=~(1<<7);			//先发送MSB
	SPI1->CR1				   = spitest;
	SPI1->CR2					|= 1<<1;			//TXDMAEN:发送缓冲区DMA使能
	SPI1->CR1					|=1<<6;				//打开SPI设备
}

2.2:DMA
stm32的DMA可是32的一大特色,优点在于它不需要CPU干预,只要DMA被触发就能直接对数据进行搬运。所以使用它可以节省CPU的工作时间用来处理其他任务,在一些大项目中这点尤为突出。详细的配置介绍请参考STM32F1参考手册,这块我就不多言了。直接上代码。

(不想使用DMA的话可以将主函数注释掉的部分取消注释,并在屏幕初始化函数子函数内倒数第二行取消调用DMA函数)

inline static void DMA_OLED_Init(void)
{
	RCC->AHBENR				 |=	1<<0;		//DMA1时钟开启
	DMA1_Channel3->CPAR = (u32)(&(SPI1->DR));//CPAR:外设数据寄存器的基地址
	DMA1_Channel3->CMAR = (u32)(OLED_SRAM);	//CMAR:存储器的基地址
	DMA1_Channel3->CCR |= 0<<14;			//MEM2MEM:非存储器到存储器模式
	DMA1_Channel3->CCR |= 0<<6;				//MINC:不执行外设地址增量操作
	DMA1_Channel3->CCR |= 1<<7;				//MINC:存储器地址增量模式
	DMA1_Channel3->CCR |= 1<<5;				//CIRC:循环模式
	DMA1_Channel3->CCR |= 1<<4;				//DIR :从存储器读
	DMA1_Channel3->CNDTR= 0x400;			//1024个字节
	DMA1_Channel3->CCR |= 1<<0;				//EN  :通道开启

}

2.3:OLED屏幕的初始化及设置
相关的控制命令在屏幕手册中都有,下面要注意的一点就是DC控制要注意时间问题。DC是由GPIO控制的,所以反转速度很快,数据发送相比DC要慢得多。倒数第四行的 Wait_us(10); 就是在调试过程中遇到的问题,从代码上看数据已经发送完成了然后再改变DC命令选择,貌似没问题。但是屏幕一直没反应,用逻辑分析仪抓取时序图后发现该问题,下面附图。例程代码已纠正放心复制。
在这里插入图片描述
图中时序自上而下分别是SCK,MISO,DC。明显看出数据还未发送结束DC已经改变。

分析原因:子函数OLED_SendCmd(unsigned char)是判断发送区是否为空,然后条件式的装入。而硬件SPI则需等待上一个数据发送完成才会再处理刚送进来的数据,同样也需要时间。

代码部分如下

void OLED_Init(void) //初始化函数
{
	#define OLED_DC  	PA1
	#define OLED_RES 	PA3
	#define OLED_NSS 	PA4
	#define OLED_D0  	PA5
	#define OLED_D1  	PA7
	
	GPIOA->CRL				&= 0xFFFF0F0F;	//rs,dc
	GPIOA->CRL				|= 0x00003030;	//
	
	OLED_RES = 0;			 //低电平复位
	Wait_us(100);
	OLED_RES = 1;			 //复位结束
	OLED_DC	 = 0;			 //命令模式
	Wait_us(100);

	OLED_SendCmd(0xAE);//关闭显示
	OLED_SendCmd(0xD5);//设置时钟分频因子,震荡频率
	OLED_SendCmd(0xF0);//[3:0],分频因子;[7:4],震荡频率
	OLED_SendCmd(0x81);//设置对比度
	OLED_SendCmd(0x7F);//128
	OLED_SendCmd(0x8D);//设置电荷泵开关
	OLED_SendCmd(0x14);//开
	OLED_SendCmd(0x20);//设置模式
	OLED_SendCmd(0x00);//设置为水平地址模式
	OLED_SendCmd(0xD3);//行偏移命令
	OLED_SendCmd(0x2A);//校正参数
  	OLED_SendCmd(0x21);//设置列地址的起始和结束的位置
  	OLED_SendCmd(0x00);//0
  	OLED_SendCmd(0x7F);//127   
  	OLED_SendCmd(0x22);//设置页地址的起始和结束的位置
  	OLED_SendCmd(0x00);//0
 	OLED_SendCmd(0x07);//7
	OLED_SendCmd(0xC8);//0xc9上下反置 0xc8正常
 	OLED_SendCmd(0xA1);//0xa0左右反置 0xa1正常
	OLED_SendCmd(0xA4);//全局显示开启;0xa4正常,0xa5无视命令点亮全屏
	OLED_SendCmd(0xA6);//设置显示方式;A7,反相显示;A6,正常显示	
	OLED_SendCmd(0xAF);//开启显示
 	OLED_SendCmd(0x56);
	Wait_us(10);//
	OLED_DC	 = 1;			 //显示数据模式
	DMA_OLED_Init();
	Wait_us(10);//

}

OLED_SendCmd(0xA4);//全局显示开启;0xa4正常,0xa5无视命令点亮全屏
这行代码在程序调试的时候可以将参数改为0xa5判断通讯是否正常。

这个是命令发送函数
OLED屏幕刷新一帧需要128*8个字节,每个字节的每一位控制屏幕的一个像素点(位的0或1表示亮灭),刚好是128 *64个位。

static void OLED_SendCmd(unsigned char ctrl_data)
{
	unsigned char t=200;
	while(! (SPI1->SR & 1<<1) )			//SPI1->SR & 1<<1=1:发送缓冲为空。
	{
		t--;
		if(t<=0)
			break;
	}

	SPI1->DR	= ctrl_data;
}




void OLED_Write(unsigned char ASII,unsigned char ye,unsigned char lie)
{
	char i;
	for(i=0;i<6;i++)
	{
		OLED_SRAM[ye][lie+i]=F6X8[(ASII-32)*6+i];
	}
}

这个是ASII字库,网上能找到太多了。

const unsigned char F6X8[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ,   // sp
    0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 ,   // !
    0x00, 0x00, 0x07, 0x00, 0x07, 0x00 ,   // "
    0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 ,   // #
    0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 ,   // $
    0x00, 0x62, 0x64, 0x08, 0x13, 0x23 ,   // %
    0x00, 0x36, 0x49, 0x55, 0x22, 0x50 ,   // &
    0x00, 0x00, 0x05, 0x03, 0x00, 0x00 ,   // '
    0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 ,   // (
    0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 ,   // )
    0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 ,   // *
    0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 ,   // +
    0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 ,   // ,
    0x00, 0x08, 0x08, 0x08, 0x08, 0x08 ,   // -
    0x00, 0x00, 0x60, 0x60, 0x00, 0x00 ,   // .
    0x00, 0x20, 0x10, 0x08, 0x04, 0x02 ,   // /
    0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E ,   // 0				//30[16]48
    0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 ,   // 1
    0x00, 0x42, 0x61, 0x51, 0x49, 0x46 ,   // 2
    0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 ,   // 3
    0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 ,   // 4
    0x00, 0x27, 0x45, 0x45, 0x45, 0x39 ,   // 5
    0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 ,   // 6
    0x00, 0x01, 0x71, 0x09, 0x05, 0x03 ,   // 7
    0x00, 0x36, 0x49, 0x49, 0x49, 0x36 ,   // 8
    0x00, 0x06, 0x49, 0x49, 0x29, 0x1E ,   // 9
    0x00, 0x00, 0x36, 0x36, 0x00, 0x00 ,   // :
    0x00, 0x00, 0x56, 0x36, 0x00, 0x00 ,   // ;
    0x00, 0x08, 0x14, 0x22, 0x41, 0x00 ,   // <
    0x00, 0x14, 0x14, 0x14, 0x14, 0x14 ,   // =
    0x00, 0x00, 0x41, 0x22, 0x14, 0x08 ,   // >
    0x00, 0x02, 0x01, 0x51, 0x09, 0x06 ,   // ?
    0x00, 0x32, 0x49, 0x59, 0x51, 0x3E ,   // @
    0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C ,   // A
    0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 ,   // B
    0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 ,   // C
    0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C ,   // D
    0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 ,   // E
    0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 ,   // F
    0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A ,   // G
    0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F ,   // H
    0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 ,   // I
    0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 ,   // J
    0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 ,   // K
    0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 ,   // L
    0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F ,   // M
    0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F ,   // N
    0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E ,   // O
    0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 ,   // P
    0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E ,   // Q
    0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 ,   // R
    0x00, 0x46, 0x49, 0x49, 0x49, 0x31 ,   // S
    0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 ,   // T
    0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F ,   // U
    0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F ,   // V
    0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F ,   // W
    0x00, 0x63, 0x14, 0x08, 0x14, 0x63 ,   // X
    0x00, 0x07, 0x08, 0x70, 0x08, 0x07 ,   // Y
    0x00, 0x61, 0x51, 0x49, 0x45, 0x43 ,   // Z
    0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 ,   // [
    0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 ,   // 55
    0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 ,   // ]
    0x00, 0x04, 0x02, 0x01, 0x02, 0x04 ,   // ^
    0x00, 0x40, 0x40, 0x40, 0x40, 0x40 ,   // _
    0x00, 0x00, 0x01, 0x02, 0x04, 0x00 ,   // '
    0x00, 0x20, 0x54, 0x54, 0x54, 0x78 ,   // a
    0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 ,   // b
    0x00, 0x38, 0x44, 0x44, 0x44, 0x20 ,   // c
    0x00, 0x38, 0x44, 0x44, 0x48, 0x7F ,   // d
    0x00, 0x38, 0x54, 0x54, 0x54, 0x18 ,   // e
    0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 ,   // f
    0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C ,   // g
    0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 ,   // h
    0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 ,   // i
    0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 ,   // j
    0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 ,   // k
    0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 ,   // l
    0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 ,   // m
    0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 ,   // n
    0x00, 0x38, 0x44, 0x44, 0x44, 0x38 ,   // o
    0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 ,   // p
    0x00, 0x18, 0x24, 0x24, 0x18, 0xFC ,   // q
    0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 ,   // r
    0x00, 0x48, 0x54, 0x54, 0x54, 0x20 ,   // s
    0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 ,   // t
    0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C ,   // u
    0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C ,   // v
    0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C ,   // w
    0x00, 0x44, 0x28, 0x10, 0x28, 0x44 ,   // x
    0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C ,   // y
    0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 ,   // z
    0x14, 0x14, 0x14, 0x14, 0x14, 0x14     // horiz lines
};

这个是用到的延时初始化函数和ms级延时函数

inline void Wait_Init(void)
{
	SysTick->CTRL			&= (unsigned int)(~(1<<0));			//关闭SysTick定时器
	SysTick->CTRL			&= (unsigned int)(~(1<<2));			//9MHz
	SysTick->CTRL			&= (unsigned int)(~(1<<1));			//不产生下溢中断
}
void Wait_ms(unsigned int t)
{
#define ms_t 9000
	SysTick->LOAD			 = ms_t;				//1ms定时
	SysTick->VAL			 = 0;						//当前值清零
	SysTick->CTRL			|= 1<<0;				//打开SysTick定时器
	while(t)
	{
		if(SysTick->CTRL & 1<<16)				//判断下溢
		{t--;}
	}
}

下面是主函数
注意:前面用到的子函数请做好声明

unsigned char OLED_SRAM[8][128]; //图像储存
extern const unsigned char F6X8[];

int main()
{
	Wait_Init();//这里的延时函数我用的是SYSTICK
	SPI1_Init();
	OLED_Init();
	OLED_Write('L',0,0);
	OLED_Write('i',0,6);
	OLED_Write('a',0,12);
	OLED_Write('n',0,18);
	OLED_Write('g',0,24);
	while(1)
	{
//		for(j=0;j<8;j++)
//		{
//				for(i=0;i<128;i++)
//					OLED_Write(OLED_SRAM[j][i]);
//		}
		
	}
}

结果





在这里插入图片描述

如果有问题欢迎指正和讨论

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 23:07:51  更:2021-07-14 23:08:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 6:31:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码