一、适用场景
适用场景:小型信息显示(传感器数值、状态、菜单)、嵌入式调试界面、简易 UI(图标 + 文本)、时钟/计时器显示、低功耗便携设备显示、课程演示与入门练习(I²C/SPI 与显存管理)。

二、器材清单

0.96OLED 模块(接口:I²C 或 SPI) ×1

STM32F103VET6 ×1

若干杜邦线(母对母/公对公,根据引脚)

5V/3.3V电源

三、工作原理(要点)

OLED 控制器(如 SSD1306)内部维护显存(例如 128×64 → 1024 字节),MCU 更新显存缓冲区后通过 I²C/SPI 将显存写给显示器并触发刷新。

接口:I²C(7-bit)易接线,节省引脚;SPI(4 线)速度更快,适合频繁刷新与动画。·  

四、接线示意

VCC → +5V

GND → GND

标准库-IIC

PA6 →SCL

PA7 →SDA

标准库-SPI

PA5 →SCL

PA7 →SDA

PA0 →CS

PA1 →DC

PA2 →RS

五、示例代码

标准库-iic

#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "bsp_SysTick.h"

int n2=0;
void i2c_startup()
{
	sda_h;
	Delay_us(1);
	scl_h;
	Delay_us(1);
	sda_l;
	Delay_us(1);
	scl_l;
	Delay_us(1);
}

void i2c_stopup()
{
	sda_l;
	Delay_us(1);
	scl_h;
	Delay_us(1);
	sda_h;
	Delay_us(1);
	scl_l;
	Delay_us(1);

}

void ack_state(int ack_mask)
{
	if(ack_mask==0)
	{
		sda_l;
	}
	else
		sda_h;
	Delay_us(1);
	scl_h;
	Delay_us(1);
	scl_l;
	Delay_us(1);
}

int get_ack()
{
	int ack;
	sda_h;
	Delay_us(1);
	scl_h;
	Delay_us(1);
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7) == RESET)
	{
		ack=0;
	}else
		ack=1;
	scl_l;
	Delay_us(1);

	return ack;
}


int send_num(u8 num)
{
	int i;
	char ack;
	for(i=0;i<8;i++)
	{
		if(num&0x80)
		{
			sda_h;
		}else
			sda_l;
		num<<=1;
		scl_h;
		Delay_us(1);
		scl_l;
		Delay_us(1);
		
	}
	ack=get_ack();
   	 return ack;
}

u8 get_num(int state)
{
	int i;
	u8 num=0;
	for(i=0;i<8;i++)
	{
		sda_h;
		Delay_us(1);
		num <<= 1;
		scl_h;
		Delay_us(1);
		if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_3) == SET)
		{
			num|=0x01;
		}
		scl_l;
		Delay_us(1);
	}
	ack_state(state);
	scl_l;
	Delay_us(1);
	return num;
}

//写入数据
void Write_Data(unsigned char dat)
{
	i2c_startup();
	send_num(0x78);      //写入oled地址
	get_ack();
	send_num(0x40);			 //写入命令地址
	get_ack();
	send_num(dat);			 //发送命令
	get_ack();
	i2c_stopup();
}

void Write_Command(unsigned char dat)
{
   i2c_startup();
   send_num(0x78);            //Slave address,SA0=0
	 get_ack();
   send_num(0x00);			//write command
	 get_ack();
   send_num(dat);
	 get_ack();
	 i2c_stopup();
}

void OLED_writemode(unsigned dat,unsigned cmd)
{
	if(cmd)
   Write_Data(dat);
	else
   Write_Command(dat);
}



//坐标设置
void OLED_Set_Pos(unsigned char x, unsigned char y) 
{
 	OLED_writemode(0xb0+y,OLED_CMD);
	OLED_writemode((x&0x0f),OLED_CMD); 							 //计算出设置显示
	OLED_writemode(((x&0xf0)>>4)|0x10,OLED_CMD);     //计算出设置显示位置—每一组中每一列的高地址位置—每一组中每一列的低地址
	
	
}   	  

		 
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  
	char i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_writemode(0xb0+i,OLED_CMD);    //设置页地址(0~7),默认0页(0组)地址是0xb0
		OLED_writemode(0x00,OLED_CMD);      //设置显示位置—每一组中每一列的低地址
		OLED_writemode(0x10,OLED_CMD);      //设置显示位置—每一组中每一列的高地址   
		for(n=0;n<128;n++)OLED_writemode(0,OLED_DATA);  //循环写入128列数据(都是0,相当于清屏)
	} //更新显示
}


//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 16/12 
void OLED_ShowChar(char x,char y,char chr)
{      	
	  unsigned char c=0,i=0;	
		c=chr-' ';																//得到偏移后的值,该值是根据头文件中符号对应的规律计算,空格是的ascii是32		
		if(x>Max_Column-1){x=0;y=y+2;}						//+2是因为数据显示时每行数据实际上占两行,为了间距看起来正常需要将越界的数据偏移两行
		OLED_Set_Pos(x,y);												//设置要写入的组号以及列的高低地址
		for(i=0;i<8;i++)													//循环8次写入数据上半段
		OLED_writemode(F8X16[c*16+i],OLED_DATA);
		OLED_Set_Pos(x,y+1);											//下移一组
		for(i=0;i<8;i++)												  //循环8次写入数据下半段
		OLED_writemode(F8X16[c*16+i+8],OLED_DATA);
			
}


//显示一个字符号串
void OLED_ShowString(char x,char y,char *chr)
{
	unsigned char j=0;
	while (chr[j]!='\0')
	{		OLED_ShowChar(x,y,chr[j]); //写数据
			x+=8;											 //+8是因为在x坐标写一次数据就占8个列,因此要写入下一个数据前必须进行位移
		if(x>120){x=0;y+=2;}				 //+2是因为数据显示时每行数据实际上占两行,为了间距看起来正常需要将越界的数据偏移两行
			j++;											 //数据下标后移
	}
}

//初始化SSD1306					    
void OLED_Init(void)
{ 	

	GPIO_InitTypeDef PA;
	
	//使能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	PA.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
	PA.GPIO_Mode = GPIO_Mode_Out_PP;
	PA.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&PA);
//	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7);

	Delay_us(800000);
  OLED_writemode(0xAE,OLED_CMD);//--display off
	OLED_writemode(0x00,OLED_CMD);//---set low column address
	OLED_writemode(0x10,OLED_CMD);//---set high column address
	OLED_writemode(0x40,OLED_CMD);//--set start line address  
	OLED_writemode(0xB0,OLED_CMD);//--set page address
	OLED_writemode(0x81,OLED_CMD); // contract control
	OLED_writemode(0xFF,OLED_CMD);//--128   
	OLED_writemode(0xA1,OLED_CMD);//set segment remap 
	OLED_writemode(0xA6,OLED_CMD);//--normal / reverse
	OLED_writemode(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_writemode(0x3F,OLED_CMD);//--1/32 duty
	OLED_writemode(0xC8,OLED_CMD);//Com scan direction
	OLED_writemode(0xD3,OLED_CMD);//-set display offset
	OLED_writemode(0x00,OLED_CMD);//
	
	OLED_writemode(0xD5,OLED_CMD);//set osc division
	OLED_writemode(0x80,OLED_CMD);//
	
	OLED_writemode(0xD8,OLED_CMD);//set area color mode off
	OLED_writemode(0x05,OLED_CMD);//
	
	OLED_writemode(0xD9,OLED_CMD);//Set Pre-Charge Period
	OLED_writemode(0xF1,OLED_CMD);//
	
	OLED_writemode(0xDA,OLED_CMD);//set com pin configuartion
	OLED_writemode(0x12,OLED_CMD);//
	
	OLED_writemode(0xDB,OLED_CMD);//set Vcomh
	OLED_writemode(0x30,OLED_CMD);//
	
	OLED_writemode(0x8D,OLED_CMD);//set charge pump enable
	OLED_writemode(0x14,OLED_CMD);//
	
	OLED_writemode(0xAF,OLED_CMD);//--turn on oled panel
}  



void OLED_Clearport(int a,int b)
{
	OLED_writemode(0xb0+a,OLED_CMD);    //设置页地址(0~7)
	OLED_writemode(0x00,OLED_CMD);      //设置显示位置—列低地址
	OLED_writemode(0x10,OLED_CMD);      //设置显示位置—列高地址   
	for(n2=0;n2<128;n2++)OLED_writemode(0,OLED_DATA);
	OLED_writemode(0xb0+b,OLED_CMD);    //设置页地址(0~7)
	OLED_writemode(0x00,OLED_CMD);      //设置显示位置—列低地址
	OLED_writemode(0x10,OLED_CMD);      //设置显示位置—列高地址   
	for(n2=0;n2<128;n2++)OLED_writemode(0,OLED_DATA);	
}


#include "stm32f10x.h"
#include "stdio.h"
#include "bsp_SysTick.h"
#include "bsp_usart.h"
#include "oled.h"

int TEMP,NUM;
int cnt=0;
char show[20];

int main()
{
	SysTick_Init();	    	 //延时函数初始化
	OLED_Init();
  OLED_Clear();
	while(1)
	{
		OLED_ShowString(15,0,"open!");	
		OLED_ShowString(15,3,"hellow world!");	
		Delay_us(5000000);
		OLED_Clear();
		OLED_ShowString(15,0,"close!");	
		OLED_ShowString(15,3,"hellow world!");	
		Delay_us(5000000);
		OLED_Clear();
//		OLED_Clearport(3,4);
	}
}

标准库-spi(这里面函数名还是iic是忘改了,如果建议直接修改函数名即可)

//标准库-spi
#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "bsp_SysTick.h"

int n2=0;
void i2c_startup()
{
	cs_l;
	Delay_us(1);
}

void i2c_stopup()
{
	cs_h;
	Delay_us(1);
}

void send_num(u8 num)
{
	int i;
	scl_l;
	for(i=0;i<8;i++)
	{
		if(num&0x80)
		{
			sda_h;
		}else
			sda_l;
		scl_h;
		Delay_us(1);
		num<<=1;
		scl_l;
		Delay_us(1);
	}
	
	

}



void OLED_writemode(unsigned dat,unsigned cmd)
{
	if(cmd)
	{
   dc_h;
	}
	else
   dc_l;
	Delay_us(1);
	i2c_startup();
	send_num(dat);  
	i2c_stopup();
	dc_h;
}



//坐标设置
void OLED_Set_Pos(unsigned char x, unsigned char y) 
{
 	OLED_writemode(0xb0+y,OLED_CMD);
	OLED_writemode(((x&0xf0)>>4)|0x10,OLED_CMD);     //计算出设置显示位置—每一组中每一列的高地址位置—每一组中每一列的低地址
	OLED_writemode((x&0x0f),OLED_CMD); 							 //计算出设置显示
}   	  

		 
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  
	char i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_writemode(0xb0+i,OLED_CMD);    //设置页地址(0~7),默认0页(0组)地址是0xb0
		OLED_writemode(0x00,OLED_CMD);      //设置显示位置—每一组中每一列的低地址
		OLED_writemode(0x10,OLED_CMD);      //设置显示位置—每一组中每一列的高地址   
		for(n=0;n<128;n++)OLED_writemode(0,OLED_DATA);  //循环写入128列数据(都是0,相当于清屏)
	} //更新显示
}


//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 16/12 
void OLED_ShowChar(char x,char y,char chr)
{      	
	  unsigned char c=0,i=0;	
		c=chr-' ';																//得到偏移后的值,该值是根据头文件中符号对应的规律计算,空格是的ascii是32		
		if(x>Max_Column-1){x=0;y=y+2;}						//+2是因为数据显示时每行数据实际上占两行,为了间距看起来正常需要将越界的数据偏移两行
		OLED_Set_Pos(x,y);												//设置要写入的组号以及列的高低地址
		for(i=0;i<8;i++)													//循环8次写入数据上半段
		OLED_writemode(F8X16[c*16+i],OLED_DATA);
		OLED_Set_Pos(x,y+1);											//下移一组
		for(i=0;i<8;i++)												  //循环8次写入数据下半段
		OLED_writemode(F8X16[c*16+i+8],OLED_DATA);
			
}


//显示一个字符号串
void OLED_ShowString(char x,char y,char *chr)
{
	unsigned char j=0;
	while (chr[j]!='\0')
	{		OLED_ShowChar(x,y,chr[j]); //写数据
			x+=8;											 //+8是因为在x坐标写一次数据就占8个列,因此要写入下一个数据前必须进行位移
		if(x>120){x=0;y+=2;}				 //+2是因为数据显示时每行数据实际上占两行,为了间距看起来正常需要将越界的数据偏移两行
			j++;											 //数据下标后移
	}
}

//初始化SSD1306					    
void OLED_Init(void)
{ 	
	GPIO_InitTypeDef PA;
	//使能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	PA.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_5|GPIO_Pin_7;
	PA.GPIO_Mode = GPIO_Mode_Out_PP;
	PA.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&PA);
	
	rs_h;
	Delay_us(100000);
	rs_l;
	Delay_us(100000);
	rs_h; 
	cs_h;
	scl_l;
	dc_h;

	Delay_us(800000);
  OLED_writemode(0xAE,OLED_CMD);//--display off
	OLED_writemode(0x00,OLED_CMD);//---set low column address
	OLED_writemode(0x10,OLED_CMD);//---set high column address
	OLED_writemode(0x40,OLED_CMD);//--set start line address  
	OLED_writemode(0xB0,OLED_CMD);//--set page address
	OLED_writemode(0x81,OLED_CMD); // contract control
	OLED_writemode(0xFF,OLED_CMD);//--128   
	OLED_writemode(0xA1,OLED_CMD);//set segment remap 
	OLED_writemode(0xA6,OLED_CMD);//--normal / reverse
	OLED_writemode(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_writemode(0x3F,OLED_CMD);//--1/32 duty
	OLED_writemode(0xC8,OLED_CMD);//Com scan direction
	OLED_writemode(0xD3,OLED_CMD);//-set display offset
	OLED_writemode(0x00,OLED_CMD);//
	
	OLED_writemode(0xD5,OLED_CMD);//set osc division
	OLED_writemode(0x80,OLED_CMD);//
	
	OLED_writemode(0xD8,OLED_CMD);//set area color mode off
	OLED_writemode(0x05,OLED_CMD);//
	
	OLED_writemode(0xD9,OLED_CMD);//Set Pre-Charge Period
	OLED_writemode(0xF1,OLED_CMD);//
	
	OLED_writemode(0xDA,OLED_CMD);//set com pin configuartion
	OLED_writemode(0x12,OLED_CMD);//
	
	OLED_writemode(0xDB,OLED_CMD);//set Vcomh
	OLED_writemode(0x30,OLED_CMD);//
	
	OLED_writemode(0x8D,OLED_CMD);//set charge pump enable
	OLED_writemode(0x14,OLED_CMD);//
	
	OLED_writemode(0xAF,OLED_CMD);//--turn on oled panel


//
	
}  



void OLED_Clearport(int a,int b)
{
	OLED_writemode(0xb0+a,OLED_CMD);    //设置页地址(0~7)
	OLED_writemode(0x00,OLED_CMD);      //设置显示位置—列低地址
	OLED_writemode(0x10,OLED_CMD);      //设置显示位置—列高地址   
	for(n2=0;n2<128;n2++)OLED_writemode(0,OLED_DATA);
	OLED_writemode(0xb0+b,OLED_CMD);    //设置页地址(0~7)
	OLED_writemode(0x00,OLED_CMD);      //设置显示位置—列低地址
	OLED_writemode(0x10,OLED_CMD);      //设置显示位置—列高地址   
	for(n2=0;n2<128;n2++)OLED_writemode(0,OLED_DATA);	
}

#include "stm32f10x.h"
#include "stdio.h"
#include "bsp_SysTick.h"
#include "bsp_usart.h"
#include "oled.h"


int main()
{
	SysTick_Init();	    	 //延时函数初始化
	OLED_Init();
  OLED_Clear();
	while(1)
	{
		OLED_ShowString(15,0,"open!");	
		OLED_ShowString(15,3,"hellow world!");	
		Delay_us(5000000);
		OLED_Clear();
		OLED_ShowString(15,0,"close!");	
		OLED_ShowString(15,3,"hellow world!");	
		Delay_us(5000000);
		OLED_Clear();
//		OLED_Clearport(3,4);
	}
}

//HAL库-iic和spi

一样只要将标准库工程里面延时修改成HAL_Delay,修改宏定义里面引脚上下拉函数即可

 六、讲解视频

https://www.bilibili.com/video/BV13snEzrEV4/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719
https://www.bilibili.com/video/BV1YKnHznEyB/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719
https://www.bilibili.com/video/BV19KnHznEcF/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

https://www.bilibili.com/video/BV1QunHzEEkk/?spm_id_from=333.1387.homepage.video_card.click&vd_source=f7dfe1b14f260b9cc3a146d2dbfd0719

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐