基于STM32的SPI协议和OLED显示数据

一、SPI接口简介

SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是3线式或4线式。

1、接口

image-20231116145012558

4线SPI器件有四个信号:

  • 时钟(SPI CLK, SCLK)
  • 片选(CS)
  • 主机输出、从机输入(MOSI)
  • 主机输入、从机输出(MISO)

产生时钟信号的器件称为主机。主机和从机之间传输的数据与主机产生的时钟同步。同I2C接口相比,SPI器件支持更高的时钟频率。用户应查阅产品数据手册以了解SPI接口的时钟频率规格。

SPI接口只能有一个主机,但可以有一个或多个从机。图1显示了主机和从机之间的SPI连接。

来自主机的片选信号用于选择从机。这通常是一个低电平有效信号,拉高时从机与SPI总线断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。本文中的片选信号始终是低电平有效信号。

MOSI和MISO是数据线。MOSI将数据从主机发送到从机,MISO将数据从从机发送到主机。

2、数据传输

要开始SPI通信,主机必须发送时钟信号,并通过使能CS信号选择从机。片选通常是低电平有效信号。因此,主机必须在该信号上发送逻辑0以选择从机。SPI是全双工接口,主机和从机可以分别通过MOSI和MISO线路同时发送数据。在SPI通信期间,数据的发送(串行移出到MOSI/SDO总线上)和接收(采样或读入总线(MISO/SDI)上的数据)同时进行。串行时钟沿同步数据的移位和采样。SPI接口允许用户灵活选择时钟的上升沿或下降沿来采样和/或移位数据。

3、时钟极性和时钟相位

在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。表1显示了这4种SPI模式。

image-20231116145116234

图2至图5显示了四种SPI模式下的通信示例。在这些示例中,数据显示在MOSI和MISO线上。传输的开始和结束用绿色虚线表示,采样边沿用橙色虚线表示,移位边沿用蓝色虚线表示。

image-20231116145206271

图3给出了SPI模式1的时序图。在此模式下,时钟极性为0,表示时钟信号的空闲状态为低电平。此模式下的时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

image-20231116145229748

图4给出了SPI模式2的时序图。在此模式下,时钟极性为1,表示时钟信号的空闲状态为高电平。此模式下的时钟相位为1,表示数据在下降沿采样(由橙色虚线显示),并且数据在时钟信号的上升沿移出(由蓝色虚线显示)。

image-20231116145309508

图5给出了SPI模式3的时序图。在此模式下,时钟极性为1,表示时钟信号的空闲状态为高电平。此模式下的时钟相位为0,表示数据在上升沿采样(由橙色虚线显示),并且数据在时钟信号的下降沿移出(由蓝色虚线显示)。

image-20231116145336867

4、多从机配置

多个从机可与单个SPI主机一起使用。从机可以采用常规模式连接,或采用菊花链模式连接。

常规SPI模式:

image-20231116145420637

在常规模式下,主机需要为每个从机提供单独的片选信号。一旦主机使能(拉低)片选信号,MOSI/MISO线上的时钟和数据便可用于所选的从机。如果使能多个片选信号,则MISO线上的数据会被破坏,因为主机无法识别哪个从机正在传输数据。

从图6可以看出,随着从机数量的增加,来自主机的片选线的数量也增加。这会快速增加主机需要提供的输入和输出数量,并限制可以使用的从机数量。可以使用其他技术来增加常规模式下的从机数量,例如使用多路复用器产生片选信号。

菊花链模式:

image-20231116145501973

在菊花链模式下,所有从机的片选信号连接在一起,数据从一个从机传播到下一个从机。在此配置中,所有从机同时接收同一SPI时钟。来自主机的数据直接送到第一个从机,该从机将数据提供给下一个从机,依此类推。

使用该方法时,由于数据是从一个从机传播到下一个从机,所以传输数据所需的时钟周期数与菊花链中的从机位置成比例。例如在图7所示的8位系统中,为使第3个从机能够获得数据,需要24个时钟脉冲,而常规SPI模式下只需8个时钟脉冲。图8显示了时钟周期和通过菊花链的数据传播。并非所有SPI器件都支持菊花链模式。请参阅产品数据手册以确认菊花链是否可用。

image-20231116145529385

二、OLED介绍

OLED,即有机发光二极管(Organic Light Emitting Diode)。OLED 由于同时具备自发光,不需背 光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。LCD 都需要背光,而 OLED不需要,因为它是自发光的。这样同样的显示OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。

1、OLED

  • 本次实验采用的0.96寸7pinOLED显示屏,该屏有以下特点:

(1)0.96寸OLED有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上1/4部分为黄光,下3/4为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。

(2)分辨率为128*64

(3)多种接口方式;OLED裸屏总共种接口包括:6800、8080 两种并行接口方式、3线或4线的串行SPI接口方式、IIC 接口方式(只需要 2根线就可以控制 OLED了!),这五种接口是通过屏上的BSO~BS2来配置的。

image-20231116170147014

2、模块接口定义

image-20231116170457084
接口序号接口定义
1GND 电源地
2VCC电源正(3~5.5V)
3SCK OLED的DO脚,在SPI和IIC通信中为时钟管脚00
4SDA OLED的D1脚,在SPI和IIC通信中为数据管脚
5RES OLED的RES#脚,用来复位(低电平复位)
6DC OLED的D/C#E脚,数据和命令控制管脚
7CS OLED的CS#脚,也就是片选管脚

3、0.96寸OLED显示屏模块原理图

image-20231116160354438

4、接线

  • 参考芯片手册引脚说明

image-20231117095519398

  • SCK一般对应的是 SPI 的 SCK 时钟线, 所以我们找到芯片对应的 PA5
  • SDA一般对应的是数据线, 因为我们使用OLED是板子向屏幕发送数据,所以是主机向从机发送数据,这个时候我们使用 MOSI(主出从入)这条线就可以,对应 PA7
  • RES 和 DC 引脚都是需要控制IO口的高低电平,所以我们找两个空IO口接上就行,我这里选择 RES – PB0 , DC - - PB1 .
  • CS是片选引脚,不过也叫NSS,我们找到对应的引脚 PA4
OLEDSTM32
GNDGND
VCCVCC(3.3V~5V)
SCKPA5
SDAPA7
RESPB0
DCPB1
CSPA4
  • 用面包板跳线连接STM32的供电和OLED的供电,使STM32和OLED共地

三、汉字取模

理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI接口实现以下功能:

(1)显示自己的学号和姓名;

(2)显示AHT20的温度和湿度;

(3)上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。

1、PCtoLCD汉字取模

(1)打开取模软件PCtoLCD,然后模式选择字符模式,然后点击选项

img

(2)汉字取模设置如下,点击确定。

img

(3)在红框内输入要取模的汉字,点击生成字模

image-20231116172312685

资料和工具下载

  • 可以到电子器件购买的店铺询问模块资料

2、zimo221汉字取模

(1)打开取模软件zimo221,参数设置->其它选项->进行取模设置->确定。

image-20231116201100300

(2)在红框内输入要取模的汉字,Ctrl+Enter结束输入文字

(3)生成字模

image-20231116201620475

四、显示姓名学号

1、OLED.c

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

u8 OLED_GRAM[144][8];

//反显函数
void OLED_ColorTurn(u8 i)
{
	if(i==0)
		{
			OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
		}
	if(i==1)
		{
			OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
		}
}

//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
	if(i==0)
		{
			OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
			OLED_WR_Byte(0xA1,OLED_CMD);
		}
	if(i==1)
		{
			OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
			OLED_WR_Byte(0xA0,OLED_CMD);
		}
}


void OLED_WR_Byte(u8 dat,u8 cmd)
{	
	u8 i;			  
	if(cmd)
	  OLED_DC_Set();
	else
	  OLED_DC_Clr();
	OLED_CS_Clr();
	for(i=0;i<8;i++)
	{
		OLED_SCLK_Clr();
		if(dat&0x80)
		   OLED_SDIN_Set();
		else 
		   OLED_SDIN_Clr();
		OLED_SCLK_Set();
		dat<<=1;   
	}				 		  
	OLED_CS_Set();
	OLED_DC_Set();   	  
}

//开启OLED显示 
void OLED_DisPlay_On(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x14,OLED_CMD);//开启电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//点亮屏幕
}

//关闭OLED显示 
void OLED_DisPlay_Off(void)
{
	OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
	OLED_WR_Byte(0x10,OLED_CMD);//关闭电荷泵
	OLED_WR_Byte(0xAF,OLED_CMD);//关闭屏幕
}

//更新显存到OLED	
void OLED_Refresh(void)
{
	u8 i,n;
	for(i=0;i<8;i++)
	{
	   OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址
	   OLED_WR_Byte(0x00,OLED_CMD);   //设置低列起始地址
	   OLED_WR_Byte(0x10,OLED_CMD);   //设置高列起始地址
	   for(n=0;n<128;n++)
		 OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
  }
}
//清屏函数
void OLED_Clear(void)
{
	u8 i,n;
	for(i=0;i<8;i++)
	{
	   for(n=0;n<128;n++)
			{
			 OLED_GRAM[n][i]=0;//清除所有数据
			}
  }
	OLED_Refresh();//更新显示
}

//画点 
//x:0~127
//y:0~63
void OLED_DrawPoint(u8 x,u8 y)
{
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	OLED_GRAM[x][i]|=n;
}

//清除一个点
//x:0~127
//y:0~63
void OLED_ClearPoint(u8 x,u8 y)
{
	u8 i,m,n;
	i=y/8;
	m=y%8;
	n=1<<m;
	OLED_GRAM[x][i]=~OLED_GRAM[x][i];
	OLED_GRAM[x][i]|=n;
	OLED_GRAM[x][i]=~OLED_GRAM[x][i];
}


//画线
//x:0~128
//y:0~64
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2)
{
	u8 i,k,k1,k2,y0;
	if((x1<0)||(x2>128)||(y1<0)||(y2>64)||(x1>x2)||(y1>y2))return;
	if(x1==x2)    //画竖线
	{
			for(i=0;i<(y2-y1);i++)
			{
				OLED_DrawPoint(x1,y1+i);
			}
  }
	else if(y1==y2)   //画横线
	{
			for(i=0;i<(x2-x1);i++)
			{
				OLED_DrawPoint(x1+i,y1);
			}
  }
	else      //画斜线
	{
		k1=y2-y1;
		k2=x2-x1;
		k=k1*10/k2;
		for(i=0;i<(x2-x1);i++)
			{
			  OLED_DrawPoint(x1+i,y1+i*k/10);
			}
	}
}
//x,y:圆心坐标
//r:圆的半径
void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
	int a, b,num;
    a = 0;
    b = r;
    while(2 * b * b >= r * r)      
    {
        OLED_DrawPoint(x + a, y - b);
        OLED_DrawPoint(x - a, y - b);
        OLED_DrawPoint(x - a, y + b);
        OLED_DrawPoint(x + a, y + b);
 
        OLED_DrawPoint(x + b, y + a);
        OLED_DrawPoint(x + b, y - a);
        OLED_DrawPoint(x - b, y - a);
        OLED_DrawPoint(x - b, y + a);
        
        a++;
        num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
        if(num > 0)
        {
            b--;
            a--;
        }
    }
}



//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size:选择字体 12/16/24
//取模方式 逐列式
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1)
{
	u8 i,m,temp,size2,chr1;
	u8 y0=y;
	size2=(size1/8+((size1%8)?1:0))*(size1/2);  //得到字体一个字符对应点阵集所占的字节数
	chr1=chr-' ';  //计算偏移后的值
	for(i=0;i<size2;i++)
	{
		if(size1==12)
        {temp=asc2_1206[chr1][i];} //调用1206字体
		else if(size1==16)
        {temp=asc2_1608[chr1][i];} //调用1608字体
		else if(size1==24)
        {temp=asc2_2412[chr1][i];} //调用2412字体
		else return;
				for(m=0;m<8;m++)           //写入数据
				{
					if(temp&0x80)OLED_DrawPoint(x,y);
					else OLED_ClearPoint(x,y);
					temp<<=1;
					y++;
					if((y-y0)==size1)
					{
						y=y0;
						x++;
						break;
          }
				}
  }
}


//显示字符串
//x,y:起点坐标  
//size1:字体大小 
//*chr:字符串起始地址 
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 size1)
{
	while((*chr>=' ')&&(*chr<='~'))//判断是不是非法字符!
	{
		OLED_ShowChar(x,y,*chr,size1);
		x+=size1/2;
		if(x>128-size1)  //换行
		{
			x=0;
			y+=2;
    }
		chr++;
  }
}

//m^n
u32 OLED_Pow(u8 m,u8 n)
{
	u32 result=1;
	while(n--)
	{
	  result*=m;
	}
	return result;
}

显示2个数字
x,y :起点坐标	 
len :数字的位数
size:字体大小
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1)
{
	u8 t,temp;
	for(t=0;t<len;t++)
	{
		temp=(num/OLED_Pow(10,len-t-1))%10;
			if(temp==0)
			{
				OLED_ShowChar(x+(size1/2)*t,y,'0',size1);
      }
			else 
			{
			  OLED_ShowChar(x+(size1/2)*t,y,temp+'0',size1);
			}
  }
}

//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
//取模方式 列行式
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1)
{
	u8 i,m,n=0,temp,chr1;
	u8 x0=x,y0=y;
	u8 size3=size1/8;
	while(size3--)
	{
		chr1=num*size1/8+n;
		n++;
			for(i=0;i<size1;i++)
			{
				if(size1==16)
						{temp=Hzk1[chr1][i];}//调用16*16字体
				else if(size1==24)
						{temp=Hzk2[chr1][i];}//调用24*24字体
				else if(size1==32)       
						{temp=Hzk3[chr1][i];}//调用32*32字体
				else if(size1==64)
						{temp=Hzk4[chr1][i];}//调用64*64字体
				else return;
							
						for(m=0;m<8;m++)
							{
								if(temp&0x01)OLED_DrawPoint(x,y);
								else OLED_ClearPoint(x,y);
								temp>>=1;
								y++;
							}
							x++;
							if((x-x0)==size1)
							{x=x0;y0=y0+8;}
							y=y0;
			 }
	}
}

//配置写入数据的起始位置
void OLED_WR_BP(u8 x,u8 y)
{
	OLED_WR_Byte(0xb0+y,OLED_CMD);//设置行起始地址
	OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
	OLED_WR_Byte((x&0x0f),OLED_CMD);
}

//x0,y0:起点坐标
//x1,y1:终点坐标
//BMP[]:要写入的图片数组
void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[])
{
	u32 j=0;
	u8 x=0,y=0;
	if(y%8==0)y=0;
	else y+=1;
	for(y=y0;y<y1;y++)
	 {
		 OLED_WR_BP(x0,y);
		 for(x=x0;x<x1;x++)
		 {
			 OLED_WR_Byte(BMP[j],OLED_DATA);
			 j++;
     }
	 }
}
//OLED的初始化
void OLED_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	 //使能A端口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;	 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOA, &GPIO_InitStructure);	  //初始化GPIOD3,6
 	GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_4);	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能A端口时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
 	GPIO_Init(GPIOB, &GPIO_InitStructure);	  //初始化GPIOD3,6
 	GPIO_SetBits(GPIOB,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8);
	
	OLED_RST_Set();
	delay_ms(100);
	OLED_RST_Clr();//复位
	delay_ms(200);
	OLED_RST_Set();
	
	OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
	OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
	OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
	OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WR_Byte(0x00,OLED_CMD);//-not offset
	OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
	OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
	OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
	OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
	OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
	OLED_WR_Byte(0xAF,OLED_CMD);
	OLED_Clear();
}

2、mian.c

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"

int main(void)
{
	u8 t;
	delay_init();
	OLED_Init();
	while(1)
	{

		delay_ms(500);
		OLED_ShowChinese(0,0,0,16);//白
		OLED_ShowChinese(18,0,1,16);//雪
		OLED_ShowChinese(36,0,2,16);//松
		OLED_ShowString(8,16,"632107060607",16);
		OLED_Refresh();
		delay_ms(500);
		OLED_ShowChinese(0,36,0,16);//白
		OLED_ShowChinese(18,36,1,16);//雪
		OLED_ShowChinese(36,36,2,16);//松
		OLED_Refresh();
	}
}

  • OLED_Font.h
#ifndef __OLEDFONT_H
#define __OLEDFONT_H
...
unsigned char Hzk1[6][16]={
{0x00,0x00,0xF8,0x08,0x08,0x0C,0x0A,0x09,0x08,0x08,0x08,0x08,0xF8,0x00,0x00,0x00},
{0x00,0x00,0xFF,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0xFF,0x00,0x00,0x00},/*"白",0*/
/* (16 X 16 , 宋体 )*/

{0x10,0x0C,0x05,0x55,0x55,0x55,0x05,0x7F,0x05,0x55,0x55,0x55,0x05,0x14,0x0C,0x00},
{0x00,0x00,0x41,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,0x00},/*"雪",1*/
/* (16 X 16 , 宋体 )*/

{0x10,0x10,0xD0,0xFF,0x90,0x10,0x80,0x60,0x1E,0x00,0xE0,0x07,0x18,0x60,0x80,0x00},
{0x04,0x03,0x00,0xFF,0x00,0x03,0x20,0x70,0x2C,0x23,0x20,0x20,0x24,0x38,0x60,0x00},/*"松",2*/
/* (16 X 16 , 宋体 )*/
};
...
#endif

3、编译下载

IMG_20231116_222747

五、显示AHT20的温度和湿度

1、接线

image-20231111170633994
AHT20STM32
VDD2.0~5.5V,推荐电压为3.3V
SDAPB7
GNDGND
SCLPB6
  • 注意:建议给AHT20独立供电(5V)。AHT20传感器较为灵敏,电压不稳会使其出现数据读取不到或发送错误数据。

电气特性,如功耗、输入和输出的高、低电平电压等,依赖于电源供电电压。为了使传感器通讯顺畅,很重要的一点是,确保信号设计严格限制在表所给出的范围内)。

2、AHT20.c

#include "stm32f10x.h" 
#include "AHT20.h" 

void Delay_N10us(uint32_t t)//延时函数
{
	  uint32_t k;

	   while(t--)
	  {
	    for (k = 0; k < 2; k++);//110
	  }
}

void SensorDelay_us(uint32_t t)//延时函数
{
		
	for(t = t-2; t>0; t--)
	{
		Delay_N10us(1);
	}
}

void Delay_4us(void)		//延时函数
{	
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
}
void Delay_5us(void)		//延时函数
{	
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);

}

void Delay_1ms(uint32_t t)		//延时函数
{
   while(t--)
  {
    SensorDelay_us(1000);//延时1ms
  }
}


void AHT20_Clock_Init(void)		//延时函数
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
}

void SDA_Pin_Output_High(void)   //将PB7配置为输出 , 并设置为高电平, PB7作为I2C的SDA
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_7);
}

void SDA_Pin_Output_Low(void)  //将PB7配置为输出  并设置为低电平
{

	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_ResetBits(GPIOB,GPIO_Pin_7);
}

void SDA_Pin_IN_FLOATING(void)  //SDA配置为浮空输入
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOB,&GPIO_InitStruct);
}

void SCL_Pin_Output_High(void) //SCL输出高电平,PB6作为I2C的SCL
{
	GPIO_SetBits(GPIOB,GPIO_Pin_6);
}

void SCL_Pin_Output_Low(void) //SCL输出低电平
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
}

void Init_I2C_Sensor_Port(void) //初始化I2C接口,输出为高电平
{	
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	AHT20_Clock_Init();
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_6);//输出高电平
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_7);//输出高电平
}
void I2C_Start(void)		 //I2C主机发送START信号
{
	SDA_Pin_Output_High();
	SensorDelay_us(8);
	SCL_Pin_Output_High();
	SensorDelay_us(8);
	SDA_Pin_Output_Low();
	SensorDelay_us(8);
	SCL_Pin_Output_Low();
	SensorDelay_us(8);   
}

void AHT20_WR_Byte(uint8_t Byte) //往AHT20写一个字节
{
	uint8_t Data,N,i;	
	Data=Byte;
	i = 0x80;
	for(N=0;N<8;N++)
	{
		SCL_Pin_Output_Low(); 
		Delay_4us();	
		if(i&Data)
		{
			SDA_Pin_Output_High();
		}
		else
		{
			SDA_Pin_Output_Low();
		}	
			
    SCL_Pin_Output_High();
		Delay_4us();
		Data <<= 1;
		 
	}
	SCL_Pin_Output_Low();
	SensorDelay_us(8);   
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);
}	

uint8_t AHT20_RD_Byte(void)//从AHT20读取一个字节
{
	uint8_t Byte,i,a;
	Byte = 0;
	SCL_Pin_Output_Low();
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	for(i=0;i<8;i++)
	{
		SCL_Pin_Output_High();		
		Delay_5us();
		a=0;
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)) a=1;
		Byte = (Byte<<1)|a;
		SCL_Pin_Output_Low();
		Delay_5us();
	}
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	return Byte;
}


uint8_t Receive_ACK(void)   //看AHT20是否有回复ACK
{
	uint16_t CNT;
	CNT = 0;
	SCL_Pin_Output_Low();	
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	SCL_Pin_Output_High();	
	SensorDelay_us(8);	
	while((GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))  && CNT < 100) 
	CNT++;
	if(CNT == 100)
	{
		return 0;
	}
 	SCL_Pin_Output_Low();	
	SensorDelay_us(8);	
	return 1;
}

void Send_ACK(void)		  //主机回复ACK信号
{
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);	
	SDA_Pin_Output_Low();
	SensorDelay_us(8);	
	SCL_Pin_Output_High();	
	SensorDelay_us(8);
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);
}

void Send_NOT_ACK(void)	//主机不回复ACK
{
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
	SDA_Pin_Output_High();
	SensorDelay_us(8);
	SCL_Pin_Output_High();	
	SensorDelay_us(8);		
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
	SDA_Pin_Output_Low();
	SensorDelay_us(8);
}

void Stop_I2C(void)	  //一条协议结束
{
	SDA_Pin_Output_Low();
	SensorDelay_us(8);
	SCL_Pin_Output_High();	
	SensorDelay_us(8);
	SDA_Pin_Output_High();
	SensorDelay_us(8);
}

uint8_t AHT20_Read_Status(void)//读取AHT20的状态寄存器
{
	uint8_t Byte_first;	
	I2C_Start();
	AHT20_WR_Byte(0x71);
	Receive_ACK();
	Byte_first = AHT20_RD_Byte();
	Send_NOT_ACK();
	Stop_I2C();
	return Byte_first;
}

void AHT20_SendAC(void) //向AHT20发送AC命令
{
	I2C_Start();
	AHT20_WR_Byte(0x70);
	Receive_ACK();
	AHT20_WR_Byte(0xac);//0xAC采集命令
	Receive_ACK();
	AHT20_WR_Byte(0x33);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();
}

//CRC校验类型:CRC8/MAXIM
//多项式:X8+X5+X4+1
//Poly:0011 0001  0x31
//高位放到后面就变成 1000 1100 0x8c
//C现实代码:
uint8_t Calc_CRC8(uint8_t *message,uint8_t Num)
{
        uint8_t i;
        uint8_t byte;
        uint8_t crc=0xFF;
	for(byte=0; byte<Num; byte++)
	{
		crc^=(message[byte]);
		for(i=8;i>0;--i)
		{
			if(crc&0x80) crc=(crc<<1)^0x31;
			else crc=(crc<<1);
		}
	}
	return crc;
}

void AHT20_Read_CTdata(uint32_t *ct) //没有CRC校验,直接读取AHT20的温度和湿度数据
{
	volatile uint8_t  Byte_1th=0;
	volatile uint8_t  Byte_2th=0;
	volatile uint8_t  Byte_3th=0;
	volatile uint8_t  Byte_4th=0;
	volatile uint8_t  Byte_5th=0;
	volatile uint8_t  Byte_6th=0;
	 uint32_t RetuData = 0;
	uint16_t cnt = 0;
	AHT20_SendAC();//向AHT20发送AC命令
	Delay_1ms(80);//延时80ms左右	
	cnt = 0;
	while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
	{
		SensorDelay_us(1508);
		if(cnt++>=100)
		{
		 break;
		 }
	}
	I2C_Start();
	AHT20_WR_Byte(0x71);
	Receive_ACK();
	Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
	Send_ACK();
	Byte_2th = AHT20_RD_Byte();//湿度
	Send_ACK();
	Byte_3th = AHT20_RD_Byte();//湿度
	Send_ACK();
	Byte_4th = AHT20_RD_Byte();//湿度/温度
	Send_ACK();
	Byte_5th = AHT20_RD_Byte();//温度
	Send_ACK();
	Byte_6th = AHT20_RD_Byte();//温度
	Send_NOT_ACK();
	Stop_I2C();

	RetuData = (RetuData|Byte_2th)<<8;
	RetuData = (RetuData|Byte_3th)<<8;
	RetuData = (RetuData|Byte_4th);
	RetuData =RetuData >>4;
	ct[0] = RetuData;//湿度
	RetuData = 0;
	RetuData = (RetuData|Byte_4th)<<8;
	RetuData = (RetuData|Byte_5th)<<8;
	RetuData = (RetuData|Byte_6th);
	RetuData = RetuData&0xfffff;
	ct[1] =RetuData; //温度
}


void AHT20_Read_CTdata_crc(uint32_t *ct) //CRC校验后,读取AHT20的温度和湿度数据
{
	volatile uint8_t  Byte_1th=0;
	volatile uint8_t  Byte_2th=0;
	volatile uint8_t  Byte_3th=0;
	volatile uint8_t  Byte_4th=0;
	volatile uint8_t  Byte_5th=0;
	volatile uint8_t  Byte_6th=0;
	volatile uint8_t  Byte_7th=0;
	 uint32_t RetuData = 0;
	 uint16_t cnt = 0;
	// uint8_t  CRCDATA=0;
	 uint8_t  CTDATA[6]={0};//用于CRC传递数组
	
	AHT20_SendAC();//向AHT20发送AC命令
	Delay_1ms(80);//延时80ms左右	
	cnt = 0;
	while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
	{
		SensorDelay_us(1508);
		if(cnt++>=100)
		{
		 break;
		}
	}
	
	I2C_Start();

	AHT20_WR_Byte(0x71);
	Receive_ACK();
	CTDATA[0]=Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
	Send_ACK();
	CTDATA[1]=Byte_2th = AHT20_RD_Byte();//湿度
	Send_ACK();
	CTDATA[2]=Byte_3th = AHT20_RD_Byte();//湿度
	Send_ACK();
	CTDATA[3]=Byte_4th = AHT20_RD_Byte();//湿度/温度
	Send_ACK();
	CTDATA[4]=Byte_5th = AHT20_RD_Byte();//温度
	Send_ACK();
	CTDATA[5]=Byte_6th = AHT20_RD_Byte();//温度
	Send_ACK();
	Byte_7th = AHT20_RD_Byte();//CRC数据
	Send_NOT_ACK();                           //注意: 最后是发送NAK
	Stop_I2C();
	
	if(Calc_CRC8(CTDATA,6)==Byte_7th)
	{
	RetuData = (RetuData|Byte_2th)<<8;
	RetuData = (RetuData|Byte_3th)<<8;
	RetuData = (RetuData|Byte_4th);
	RetuData =RetuData >>4;
	ct[0] = RetuData;//湿度
	RetuData = 0;
	RetuData = (RetuData|Byte_4th)<<8;
	RetuData = (RetuData|Byte_5th)<<8;
	RetuData = (RetuData|Byte_6th);
	RetuData = RetuData&0xfffff;
	ct[1] =RetuData; //温度
	}
	else
	{
		ct[0]=0x00;
		ct[1]=0x00;//校验错误返回值,客户可以根据自己需要更改
	}//CRC数据
}


void JH_Reset_REG(uint8_t addr)
{
	
	uint8_t Byte_first,Byte_second,Byte_third,Byte_fourth;
	I2C_Start();
	AHT20_WR_Byte(0x70);//原来是0x70
	Receive_ACK();
	AHT20_WR_Byte(addr);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();

	Delay_1ms(5);//延时5ms左右
	I2C_Start();
	AHT20_WR_Byte(0x71);//
	Receive_ACK();
	Byte_first = AHT20_RD_Byte();
	Send_ACK();
	Byte_second = AHT20_RD_Byte();
	Send_ACK();
	Byte_third = AHT20_RD_Byte();
	Send_NOT_ACK();
	Stop_I2C();
	
    Delay_1ms(10);//延时10ms左右
	I2C_Start();
	AHT20_WR_Byte(0x70);///
	Receive_ACK();
	AHT20_WR_Byte(0xB0|addr);寄存器命令
	Receive_ACK();
	AHT20_WR_Byte(Byte_second);
	Receive_ACK();
	AHT20_WR_Byte(Byte_third);
	Receive_ACK();
	Stop_I2C();
	
	Byte_second=0x00;
	Byte_third =0x00;
}

void AHT20_Start_Init(void)
{
	JH_Reset_REG(0x1b);
	JH_Reset_REG(0x1c);
	JH_Reset_REG(0x1e);
}

3、AHT20.h

#ifndef _AHT20_H_
#define _AHT20_H_

#include "stm32f10x.h"  

void Delay_N10us(uint32_t t);//延时函数
void SensorDelay_us(uint32_t t);//延时函数
void Delay_4us(void);		//延时函数
void Delay_5us(void);		//延时函数
void Delay_1ms(uint32_t t);	
void AHT20_Clock_Init(void);		//延时函数
void SDA_Pin_Output_High(void)  ; //将PB15配置为输出 , 并设置为高电平, PB15作为I2C的SDA
void SDA_Pin_Output_Low(void);  //将P15配置为输出  并设置为低电平
void SDA_Pin_IN_FLOATING(void);  //SDA配置为浮空输入
void SCL_Pin_Output_High(void); //SCL输出高电平,P14作为I2C的SCL
void SCL_Pin_Output_Low(void); //SCL输出低电平
void Init_I2C_Sensor_Port(void); //初始化I2C接口,输出为高电平
void I2C_Start(void);		 //I2C主机发送START信号
void AHT20_WR_Byte(uint8_t Byte); //往AHT20写一个字节
uint8_t AHT20_RD_Byte(void);//从AHT20读取一个字节
uint8_t Receive_ACK(void);   //看AHT20是否有回复ACK
void Send_ACK(void)	;	  //主机回复ACK信号
void Send_NOT_ACK(void);	//主机不回复ACK
void Stop_I2C(void);	  //一条协议结束
uint8_t AHT20_Read_Status(void);//读取AHT20的状态寄存器
void AHT20_SendAC(void); //向AHT20发送AC命令
uint8_t Calc_CRC8(uint8_t *message,uint8_t Num);
void AHT20_Read_CTdata(uint32_t *ct); //没有CRC校验,直接读取AHT20的温度和湿度数据
void AHT20_Read_CTdata_crc(uint32_t *ct); //CRC校验后,读取AHT20的温度和湿度数据
void JH_Reset_REG(uint8_t addr);///重置寄存器
void AHT20_Start_Init(void);///上电初始化进入正常测量状态

#endif

4、main.c

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"
#include  "AHT20.h"

int main(void)
{
	u8 t;
	delay_init();
	OLED_Init();
	uint32_t CT_data[2];
	volatile int  c1=0,t1=0,C1=0,T1=0,C2=0,T2=0;
	Init_I2C_Sensor_Port();//初始化SDA,SCL的IO口
	OLED_Init();
	Delay_1ms(500);
	if((AHT20_Read_Status()&0x18)!=0x18)
	{
		AHT20_Start_Init(); //重新初始化寄存器,一般不需要此初始化,只有当读回的状态字节不正确时才初始化AHT20
		Delay_1ms(10);
	}
	while(1)
	{
		delay_ms(500);
		OLED_DrawLine(0,0,128,0);
		OLED_DrawLine(0,0,0,64);
		OLED_DrawLine(127,0,127,64);
		OLED_DrawLine(0,63,128,63);
		OLED_DrawLine(64,0,64,64);
		OLED_DrawLine(0,16,128,16);
		OLED_ShowChinese(16,0,0,16);//温
		OLED_ShowChinese(32,0,1,16);//度
		OLED_ShowChinese(80,0,2,16);//湿
		OLED_ShowChinese(96,0,1,16);//度
		OLED_ShowChinese(24,48,3,16);//℃
		OLED_ShowString(84,48,"%RH",16);//RH
		AHT20_Read_CTdata(CT_data);  //读取温度和湿度 , 可间隔1.5S读一次
		c1 = CT_data[0]*1000/1024/1024;  //计算得到湿度值(放大了10倍,如果c1=523,表示现在湿度为52.3%)
		t1 = CT_data[1] *200*10/1024/1024-500;//计算得到温度值(放大了10倍,如果t1=245,表示现在温度为24.5℃)
		C1=c1/10;
		T1=t1/10;
		C2=C1%10;
		T2=t1%10;
		OLED_ShowNum(18,26,T1,2,16);
		OLED_ShowString(34,26,".",16);
		OLED_ShowNum(39,26,T2,1,16);	
		OLED_ShowNum(88,26,C1,2,16);
		OLED_ShowString(104,26,".",16);//显示ASCII字符
		OLED_ShowNum(108,26,C2,1,16);
		Delay_1ms(1500); //延时1.5S
		OLED_Refresh();
	}
}

5、编译下载

基于stm32+OLED+AHT20的温湿度检测

六、汉字滚动显示

1、OLED.c

//num 显示汉字的个数
//space 每一遍显示的间隔
void OLED_ScrollDisplay(u8 num,u8 space)
{
	u8 i,n,t=0,m=0,r;
	while(1)
	{
		if(m==0)
		{
	    OLED_ShowChinese(128,24,t,16); //写入一个汉字保存在OLED_GRAM[][]数组中
			t++;
		}
		if(t==num)
			{
				for(r=0;r<16*space;r++)      //显示间隔
				 {
					for(i=0;i<144;i++)
						{
							for(n=0;n<8;n++)
							{
								OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
							}
						}
           OLED_Refresh();
				 }
        t=0;
      }
		m++;
		if(m==16){m=0;}
		for(i=0;i<144;i++)   //实现左移
		{
			for(n=0;n<8;n++)
			{
				OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
			}
		}
		OLED_Refresh();
	}
}

OLED显示屏幕的缓存数组OLED_GRAM[][],通过每次左移一列的方式实现文字的滚动显示。具体操作如下:

  1. 从第一列开始,每次显示一个汉字,并把这个汉字保存在OLED_GRAM[][]数组中。
  2. 当显示完num个汉字后,开始启动滚动显示。每次左移一列,然后刷新OLED显示屏幕。
  3. 为了实现滚动间隔,每次滚动完毕之后,延时一段时间再开始下一次滚动。
  4. 为了保证滚动的连续性,当移动到最后一列时,需要回到第一列重新开始滚动显示。

通过不断的重复上述操作,就可以实现文字在OLED上的滚动显示。

2、编译下载

基于stm32+OLED的汉字滚动显示

七、总结

  • 在接口选择方面,如果系统有更多的空闲 GPIO 引脚,建议使用SPI进行通信,因为它的速度比I2C快,且并不需要读取从设备的操作。如果GPIO资源有限,或者局限在2针的插头,最好使用I2C。

  • 在进行OLED显示的编程时,需要注意以下几点:

  1. 注意自己使用芯片的引脚定义包括I/O口电平、默认复用功能、重定义功能。
  2. 需要熟悉OLED的控制寄存器和数据寄存器,通过SPI或IIC接口向OLED发送指令和数据以设置其工作模式、显示内容和位置等。懂得查找和翻阅手册。
  3. 如需显示AHT20的温度和湿度,建议使用软件IIC读取AHT20模块的数据并进行格式化处理,然后再进行OLED显示。注意在初始化AHT20模块时需要发送启动命令并等待一段时间后再读取数据(不同厂家的AHT20上电等待时间不同,建议查找相应的产品手册),否则读取的数据可能不准确导致显示不准确。
  4. 注意相关外设的电气特性和工作条件,保证其能正常工作。

八、参考文章

SPI接口简介 | 亚德诺半导体 (analog.com)

【精选】基于STM32+0.96寸OLED - - 7脚SPI接线显示+代码解析_屏幕排线 oled 针脚_-木槿昔年-的博客-CSDN博客

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于STM32F103和OLED的波形显示是一种通过STM32F103微控制器驱动OLED屏幕显示波形的应用。STM32F103是一款基于ARM Cortex-M3内核的单片机,具有丰富的外设资源,包括GPIO、定时器、SPI等。OLED(有机发光二极管)是一种具有自发光和高对比度特点的显示技术。 首先,我们需要将STM32F103与OLED连接起来。通过SPI接口或I2C接口连接STM32F103和OLED屏幕。然后,在STM32F103的程序中,我们需要配置SPI或I2C的相关寄存器,设置通信参数和时钟频率,以确保STM32F103能够正确地与OLED通信。 接下来,我们需要准备波形数据。可以通过STM32F103的ADC模块进行模拟信号采样,或者通过其他方式获取数字化的波形数据。将获取的波形数据存储在STM32F103的存储器中,例如数组或缓冲区。 然后,在STM32F103的程序中,我们需要编写相应的代码来读取波形数据,并将其发送给OLED屏幕进行显示。根据OLED的驱动程序,我们可以通过SPI或I2C发送命令和数据来控制OLED显示。通过逐点方式,将波形数据发送给OLED,以在屏幕显示出波形。 为了实时显示波形,我们可以使用定时器中断来定时刷新屏幕上的波形数据。在每个定时器中断中,我们可以更新屏幕显示,将新的波形数据发送给OLED进行更新。 此外,为了更好地显示波形,我们可以添加一些图形化的界面元素,例如坐标轴、刻度线等,以增强用户的可视化体验。 总而言之,基于STM32F103和OLED的波形显示是一种通过STM32F103微控制器驱动OLED屏幕显示波形的应用。通过合理的硬件连接和程序设计,我们可以实现波形的实时显示,并提供更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值