stm32学习记录之0.96寸OLED显示屏配置

0.96寸oled屏概述

本次实验所用oled显示屏为黄蓝屏,即屏上1/4 部分为黄光,下3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改。分辨率为128*64,采用IIC 接口方式进行通讯(默认地址0x78)。

IIC 接口模块

接口定义

  1. GND:电源地
  2. VCC:电源正(3.3~5V)
  3. SCL:OLED 的D0 脚,在IIC 通信中为时钟管脚
  4. SDA:OLED 的D1 脚,在IIC 通信中为数据管脚

0.96 寸OLED 驱动IC
0.96’OLED(4Pin)模块采用SSD1306 为驱动芯片,模块带有稳压芯片,支持软件模拟IIC 通讯与硬件IIC 通讯,上电自动复位,功耗低,自发光自由视角。

SSD1306 有3 中寻址模式:页寻址模式、水平寻址模式、垂直寻址模式。寻址方式决定了写入数据的方式。

工程实现

本实验程序基于普中stm32f103zet6核心板:
在这里插入图片描述

SCL时钟管脚  接 PD6
SDA数据管脚  接 PD7
VCC  接 3.3v电源
GND  电源地

GPIO初始化

为了实际应用,对IO口进行了进一步封装。需要改变管脚的时候,修改程序更方便。
文件oled.h

/* 若要改变管脚 直接在此配置 */
#define GPIO	GPIOD
#define RCC_IO	RCC_APB2Periph_GPIOD	//gpio时钟
#define SCL  	GPIO_Pin_6	//时钟管脚
#define SDA	 	GPIO_Pin_7	//数据管脚

文件oled.c

static void gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	//gpio结构体变量

	RCC_APB2PeriphClockCmd(RCC_IO, ENABLE);	//使能GPIOD时钟
	
	GPIO_InitStructure.GPIO_Pin = SCL | SDA;	//PD6、PD7
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
	GPIO_Init(GPIO, &GPIO_InitStructure); 	   /* 初始化GPIOD */
	
	GPIO_SetBits(GPIO, SCL | SDA);	//拉高电平
}

SSD1306 初始化

文件oled.c

/*******************************************************************************
* 函 数 名         : OLED_Init
* 函数功能		   : 0.96寸oled初始化函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void OLED_Init(void)
{
	gpio_init();	//数据与时钟的gpio管脚初始化

	delay_ms(200);	//等待oled复位完成	

	OLED_WR_Byte(0xAE, OLED_CMD); 	//关闭显示
	
	OLED_WR_Byte(0xD5, OLED_CMD); 	//设置时钟分频因子,震荡频率
	OLED_WR_Byte(0x80, OLED_CMD);   	//[3:0],分频因子;[7:4],震荡频率
	
	OLED_WR_Byte(0xA8, OLED_CMD); 	//设置驱动路数
	OLED_WR_Byte(0X3F, OLED_CMD); 	//默认0X3F(1/64) 
	
	OLED_WR_Byte(0xD3, OLED_CMD); 	//设置显示偏移
	OLED_WR_Byte(0X00, OLED_CMD); 	//默认为0

	OLED_WR_Byte(0x00, OLED_CMD);	//设置显示位置—列低地址
	OLED_WR_Byte(0x10, OLED_CMD);	//设置显示位置—列高地址
	OLED_WR_Byte(0x40, OLED_CMD); 	//设置显示开始行 [5:0],行数.
													    
	OLED_WR_Byte(0x8D, OLED_CMD); 	//电荷泵设置
	OLED_WR_Byte(0x14, OLED_CMD); 	//bit2,开启/关闭
	
	OLED_WR_Byte(0x20, OLED_CMD); 	//设置内存地址模式
	OLED_WR_Byte(0x02, OLED_CMD); 	//[1:0],00,列地址模式; 01,行地址模式; 10,页地址模式;默认10;
	
	OLED_WR_Byte(0xA1, OLED_CMD); 	//段重定义设置,bit0: 0,0->0; 1,0->127;
	
	OLED_WR_Byte(0xC0, OLED_CMD); 	//设置COM扫描方向;bit3: 0,普通模式; 1,重定义模式 COM[N-1]->COM0; N:驱动路数
	//OLED_WR_Byte(0xC8,OLED_CMD); 	//设置COM扫描方向
	
	OLED_WR_Byte(0xDA, OLED_CMD); 	//设置COM硬件引脚配置
	OLED_WR_Byte(0x12, OLED_CMD); 	//[5:4]配置
		 
	OLED_WR_Byte(0x81, OLED_CMD); 	//对比度设置
	OLED_WR_Byte(0xEF, OLED_CMD); 	//1~255;默认0X7F (亮度设置,越大越亮)
	
	OLED_WR_Byte(0xD9, OLED_CMD); 	//设置预充电周期
	OLED_WR_Byte(0xf1, OLED_CMD); 	//[3:0],PHASE 1;[7:4],PHASE 2;
	
	OLED_WR_Byte(0xDB, OLED_CMD); 	//设置VCOMH 电压倍率
	OLED_WR_Byte(0x30, OLED_CMD); 	//[6:4] 000,0.65*vcc; 001,0.77*vcc; 011,0.83*vcc;

	OLED_WR_Byte(0xA4, OLED_CMD); 	//全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
	OLED_WR_Byte(0xA6, OLED_CMD); 	//设置显示方式;bit0:1,反相显示;0,正常显示	    						   
	OLED_WR_Byte(0xAF, OLED_CMD); 	//开启显示	
	
	OLED_Clear();	//清屏函数

以上这些初始化命令我参考了不同淘宝商家和教程给的程序,其中配置方案大致相同,个别参数配置不同:

  1. 对比度设置:
    OLED_WR_Byte (0x81, OLED_CMD);
    //对比度设置
    OLED_WR_Byte (0xEF, OLED_CMD);
    //1~255;默认0X7F (亮度设置,越大越亮)
    这个很好理解,配置的参数不同只影响屏幕亮度。

  2. 而有的配置方案中多了这么两个指令:
    OLED_WR_Byte (0x00, OLED_CMD);
    //设置显示位置—列低地址
    OLED_WR_Byte (0x10, OLED_CMD);
    //设置显示位置—列高地址

经查看技术手册发现:

页地址模式设置列低半字节的开始地址(00h~0Fh)
这个命令专门为8位列地址的低半字节设置以通过页地址模式显示RAM中的数据。而每一个数据使用后列地址会自动增加。请参考表格9-1的部分以及10.1.3的部分来了解详细情况。

页地址模式设置列高半字节的开始地址(10h~1Fh)
这个命令专门为8位列地址的高半字节设置以通过页地址模式显示RAM中的数据。而每一个数据使用后列地址会自动增加。请参考表格9-1的部分以及10.1.3的部分来了解详细情况。

在这里插入图片描述
在这里插入图片描述

使用X[3:0]作为数据位,将列起始地址寄存器的低位半字节设置为页面寻址模式。复位后,初始显示行寄存器复位至0000b。
此命令仅适用于页面寻址模式。

看到这已经大概明白什么意思了,但是不知道具体有什么用,继续向下挖…

在这里插入图片描述

页面寻址模式(A[1:0]=10xb)。
在页寻址模式下,在读/写显示RAM之后,列地址指针自动加1。如果列地址指针到达列结束地址,则列地址指针重置为列起始地址,页地址指针不变。用户必须设置新的页面和列地址才能访问下一页RAM内容。页面寻址模式的页面和列地址点的移动顺序如图10-1所示。

在正常显示数据RAM读取或写入和页面寻址模式下,需要执行以下步骤。
定义起始RAM访问指针位置:
·通过命令B0h至B7h设置目标显示位置的页面起始地址。
·通过命令00h~0Fh设置指针的低位起始列地址。
·通过命令10h~1Fh设置指针的上起始列地址。

看到这儿,已经大概知道页寻址模式下如何向oled模块写入数据。这也是后面的清屏函数更新显示函数的大概逻辑结构。

  1. 在我具体调试过程中,第一次显示字符的时候,字符的形状是上下颠倒的,我就怀疑显示字符函数哪里弄反了,或者是因为ASCII字符点阵数组不匹配。
    经过检查和修改,也没有好转。这时候想到之前使用过的TFT显示屏能旋转,于是猜想这个显示屏会不会也有类似的功能。
    又对比了几个例程发现有一个COM扫描方向的指令有出入:
    OLED_WR_Byte (0xC0, OLED_CMD);
    //设置COM扫描方向;bit3: 0普通模式; 1重定义模式COM[N-1]->COM0; N:驱动路数
    查了查技术手册,看不懂(=_=),不管怎样先试试再说。最后字符显示形状正确了,但是字符从左下角变到右上角了,这就说明坐标零点由左下变为左上了。由于不影响使用,也没有过多纠结,当然知道其原因及调试方法更好。

启动与停止函数

oled.h

/* IIC端口定义 */
#define OLED_SCLK_Clr() 	GPIO_ResetBits(GPIO,SCL)	//SDA IIC接口的时钟信号
#define OLED_SCLK_Set() 	GPIO_SetBits(GPIO,SCL)

#define OLED_SDIN_Clr() 	GPIO_ResetBits(GPIO,SDA)	//SCL IIC接口的数据信号
#define OLED_SDIN_Set() 	GPIO_SetBits(GPIO,SDA)

#define OLED_CMD  0	//写命令
#define OLED_DATA 1	//写数据

oled.c

/* 模拟IIC启动信号 (时钟和数据管脚都由高到低)*/
void IIC_Start(void)
{

	OLED_SCLK_Set();	//时钟脚置1
	OLED_SDIN_Set();	//数据脚置1
	OLED_SDIN_Clr();	//数据脚置0
	OLED_SCLK_Clr();	//时钟脚置0
	
}

/* 模拟IIC停止信号 (时钟置高、数据管脚都由低到高)*/
void IIC_Stop(void)
{

	OLED_SCLK_Set();	//时钟脚置1
	
	OLED_SDIN_Clr();	//数据脚置0
	OLED_SDIN_Set();	//数据脚置1
}

之所以把这么两个简单的函数单独列出来,是因为我在配置模拟IIC启动信号函数时,由于是第一次写模拟IIC通信协议的函数,没有严格按照时序图来写,启动函数没有起到它应有的作用,导致显示屏没有收到启动信号。

起始条件与停止条件时序图
在这里插入图片描述
最初写启动函数时,先是把SDA时钟线与SCL数据线拉高,然后产生下降沿时,先拉低的是时钟线,再拉低的数据线。而启动条件是通过将SCL数据线从高拉到低,同时SDA时钟线保持高来建立的;停止条件是通过将SDA时钟线从低到高拉入来建立的。刚开始配置的时候没注意这些细节,参照例程比着葫芦画瓢,想着拉高再拉低就行,直到改正过来也不知道为什么是这儿错了,知道后来参照使用手册和时序图才明白过来。(说到这不得不说数据手册真是个好东西)

每次SSD1306 收到数据后都会将SDA 信号线拉低,发送一个应答信号,单片机通过检测应答信号来判断SSD1306 是否有接受到数据。
应答时序图
在这里插入图片描述
时钟线拉高再拉低,不需要真的确定它接收到了本次数据再发送下个数据。

/* 模拟IIC读取从机应答信号 */
static void IIC_Wait_Ack(void)
{	
	OLED_SCLK_Set();
	OLED_SCLK_Clr();
}

各个功能函数

数据的发送是高位在前,也就是先发送字节的高位。数据的传输是由SDA 信号线与SCL 时钟线通过一定的规范进行传输:
在SCL 时钟线处于高电平期间SDA 的电平必须保持稳定(不允许改变电平状态);
在SCL时钟线处于低电平期间SDA 的电平允许发生改变(可以改变电平状态)。

IIC写入一个字节函数:

/* IIC写入一个字节 */
void Write_IIC_Byte(u8 IIC_Byte)
{
	u8 i = 0;
	u8 j;
	
	for(i = 0; i < 8; i++)
	{
		OLED_SCLK_Clr();	//时钟脚置低,为数据传输做准备
		if(IIC_Byte & 0x80)
			j=1;
		else j=0;
		PDout(7) = j;	//提取bit7 的数据并发送到PD7端口 (PDout(n)函数是直接操作ODR寄存器控制IO口)
		IIC_Byte <<= 1;	//把bit6 移到 bit7,为下一次传输数据做准备
		
		OLED_SCLK_Set();	//时钟脚置高,发送数据				
	}
	OLED_SCLK_Clr();	//时钟脚置低(应答)
}

根据通讯协议,在写入数据前要发送一个控制字节(Control byte)来通知模块接下来发送的一个字节是数据还是命令。
如果接受到的是命令,SSD1306 就会把接收到的下一个字节当作命令转移到命令寄存器中;
如果接受到的是数据,SSD1306 就会把接收到的下一个字节当作数据存放到图形显示数据RAM 中。

0x00:接下来发送的是命令;
0x40:接下来发送的是数据。
/* IIC写入命令 */
void Write_IIC_Command(u8 IIC_Command)
{
	IIC_Start();	//开始	
	
	Write_IIC_Byte(0x78);           //写入从机地址,SA0=0
	IIC_Wait_Ack();			
	Write_IIC_Byte(0x00);			//写入命令
	IIC_Wait_Ack();		
	
	Write_IIC_Byte(IIC_Command); 	//数据
	IIC_Wait_Ack();	
	
	IIC_Stop();	//停止
}

/* IIC写入数据 */
void Write_IIC_Date(u8 IIC_Date)
{
	IIC_Start();	//开始
	
	Write_IIC_Byte(0x78);           //写入从机地址,SA0=0	
	IIC_Wait_Ack();		
	Write_IIC_Byte(0x40);			//写入数据
	IIC_Wait_Ack();		
	
	Write_IIC_Byte(IIC_Date); 		//数据	
	IIC_Wait_Ack();		
	
	IIC_Stop();	//停止
}

//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令; 1,表示数据;
void OLED_WR_Byte(u8 date, u8 cmd)
{
	if(cmd)
		Write_IIC_Date(date);
	else 
		Write_IIC_Command(date);
}

更新显示函数 & 清屏函数

u8 OLED_GRAM[128][8];	//8页  屏幕大小64*128 

//更新图像到LCD		 
void OLED_Refresh_Gram(void)
{
	u8 i, n;		    
	for(i = 0; i < 8; i++)  
	{  
		OLED_WR_Byte(0xb0 + i, OLED_CMD);	//设置页地址(0~7)
		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++)  
	{  
		OLED_WR_Byte(0xb0 + i, OLED_CMD);	//设置页地址(0~7)
		OLED_WR_Byte(0x00, OLED_CMD);		//设置显示位置—列低地址
		OLED_WR_Byte(0x10, OLED_CMD);		//设置显示位置—列高地址
		
		for(n=0;n<128;n++)	
			OLED_WR_Byte(0, OLED_DATA); 
	} //更新显示
}

画点函数 & 画方块函数

//画点 
//	x:	0~127
//	y:	0~63
//bit:	1填充; 0清空				   
void OLED_DrawPoint(u8 x, u8 y, u8 bit)
{
	u8 Y, bx, date = 0;
	if(x > 127 || y > 63)
		return;		//超出范围 结束函数
	Y = 7 - y/8;	//判断该点在哪一页
	bx = y % 8;		//定位该点所在行数
	date = 1<<(7 - bx);	
	if(bit)
		OLED_GRAM[x][Y] |= date;	//填充该位
	else 
		OLED_GRAM[x][Y] &= ~date;	//清除
		    
}

//填充(x1,y1)与(x2,y2)对角坐标的区域	 	 
//bit: 0清空; 1填充	  
void OLED_Fill(u8 x1, u8 y1, u8 x2, u8 y2, u8 bit)  
{  
	u8 x, y, tra_x, tra_y;
	tra_x = tra_x;
	tra_y = tra_y;	//避免编译器警告,并无实际用处

	if(x1>127||x2>127||y1>63||y2>63)
		return;	//超界则退出函数
	if(x1 > x2)
	{
		tra_x = x1;
		x1 = x2;
		x2 = x1;
	}
	if(y1 > y2)
	{
		tra_y = y1;
		y1 = y2;
		y2 = y1;
	}
	
	for(x = x1; x <= x2; x++)	
		for(y = y1; y <= y2; y++)		 	
			OLED_DrawPoint(x, y, bit);		
														    
	OLED_Refresh_Gram();//更新显示
}

显示ASCII字符函数

//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 size, u8 mode)
{      			    
	u8 temp, t, t1;
	u8 y0 = y;
	u8 csize = (size/8 + ((size%8)?1:0)) * (size/2);	//得到字体一个字符对应点阵集所占的字节数
	chr = chr - ' ';	//把输入的字符转化为在字体数组所在位置 (字符减字符等于数字)		 
    for(t = 0; t < csize; t++)
    {   
		if(size == 12)		temp = ascii_1206[chr][t]; 	//调用1206字体
		else if(size == 16)	temp = ascii_1608[chr][t];	//调用1608字体
		else if(size == 24)	temp = ascii_2412[chr][t];	//调用2412字体
		else return;								//没有的字库
		
        for(t1 = 0; t1 < 8; t1++)
		{
			if(temp & 0x80)	OLED_DrawPoint(x, y, mode);
			else OLED_DrawPoint(x, y, !mode);
			
			temp <<= 1;
			y++;
			if((y - y0) == size)
			{
				y = y0;
				x++;
				break;
			}
		}  	 
    }          
}

//显示一个字符串
//x,y:起点坐标  
//size:字体大小 
//*p:字符串起始地址 
void OLED_ShowString(u8 x, u8 y, const u8 *p, u8 size)
{	
    while((*p <= '~') && (*p >= ' '))//判断字符是否正确
    {       
        if(x>(128-(size/2)))
		{
			x = 0;
			y += size;
		}
        if(y > (64-size))
		{
			y = x = 0;
			OLED_Clear();
		}
        OLED_ShowChar(x, y, *p, size, 1);	 
        x += size/2;
        p++;
    }  	
}

显示数字函数

//m^n函数 为显示多位数字提供方便
static u32 oled_pow(u8 m,u8 n)
{
	u32 result = 1;	 
	while(n--)
		result *= m;    
	return result;
}

//显示len个数字
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//num :数值(0~2^32-1);	 		  
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size)
{
	u8 t, temp;
	u8 enshow = 0;	//判断高位是否为0 的标志						   
	for(t = 0; t < len; t++)
	{
		temp = (num / oled_pow(10, len-t-1)) % 10;	//从高到低 依次取出对应位上的数值
		if(enshow == 0 && t < (len-1))
		{
			if(temp == 0)	//判断最高位上的数是否有效(若最高位上的数为0 则不显示数字)
			{
				OLED_ShowChar(x + (size/2)*t, y,' ', size, 1);
				continue;	//跳出本次循环 且标志位不清除,直到最高位有效
			}
			else enshow = 1;	//最高位有效,清除此标志位		 	 
		}
	 	OLED_ShowChar(x + (size/2)*t, y, temp+'0', size, 1);	//显示数字 
	}
}

显示汉字函数

//显示汉字
//x,y:起点坐标  
//pos:数组位置汉字显示
//size:字体大小 
//mode:0,反白显示;1,正常显示
void OLED_ShowFontHZ(u8 x, u8 y, u8 pos, u8 size, u8 mode)
{
	u8 temp, t, t1;
	u8 y0 = y;  
	u8 csize = (size/8+((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数	 
	
	if(size!=12 && size!=16 && size!=24)
		return;	//不支持的size
	 
	for(t = 0; t < csize; t++)
	{   												   
		if(size == 12)		temp = FontHzk[pos][t]; //调用1206字体
		else if(size == 16)	temp = FontHzk[pos][t];	//调用1608字体
		else if(size == 24)	temp = FontHzk[pos][t];	//调用2412字体
		else return;								//没有的字库
        for(t1 = 0; t1 < 8; t1++)
		{
			if(temp & 0x80)
				OLED_DrawPoint(x, y, mode);
			else OLED_DrawPoint(x, y, !mode);
			temp <<= 1;
			y++;
			if((y - y0) == size)
			{
				y = y0;
				x++;
				break;
			}
		}  	 
	}  	
} 

取模软件生成的汉字数组

//16*16汉字字体(宋体)
const unsigned char FontHzk[][32]={
	
{0x00,0x00,0x00,0x00,0x1F,0xF8,0x11,0x10,0x11,0x10,0x11,0x10,0x11,0x10,0xFF,0xFE,
0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x1F,0xF9,0x00,0x01,0x00,0x0F,0x00,0x00},/*"电",0*/

{0x01,0x00,0x41,0x00,0x41,0x00,0x41,0x00,0x41,0x00,0x41,0x02,0x41,0x01,0x47,0xFE,
0x45,0x00,0x49,0x00,0x51,0x00,0x61,0x00,0x41,0x00,0x01,0x00,0x01,0x00,0x00,0x00},/*"子",1*/

{0x00,0x80,0x01,0x00,0x06,0x00,0x1F,0xFF,0xE0,0x00,0x00,0x00,0x20,0x00,0x24,0x9F,
0x24,0x92,0xA4,0x92,0x64,0x92,0x24,0x92,0x24,0x92,0x24,0x9F,0x20,0x00,0x00,0x00},/*"信",2*/

{0x00,0x02,0x00,0x0C,0x00,0x00,0x3F,0xC0,0x2A,0x9C,0x2A,0x82,0x6A,0x82,0xAA,0xA2,
0x2A,0x9A,0x2A,0x82,0x2A,0x82,0x3F,0xCE,0x00,0x00,0x00,0x10,0x00,0x0C,0x00,0x00},/*"息",3*/
};

显示效果:
在这里插入图片描述
对比各个显示字符函数,它们的大概思路都一样,只要把数据正确的写入OLED_GRAM数组里对应位置,再配合更新显示函数,就能准确的显示字符或汉字。

从这次配置oled模块的过程来看,学会查找技术手册、掌握外设模块与单片机通讯方法是非常必要的,这也是我有待提高的部分。

此外,我发现在程序运行的时候,原本每0.5s转换一次状态的灯变成了大约每2s转换一次。换算过后估计运行一次更新显示函数需要用时30ms。
经测试,发现更新显示函数运行用时>21ms。不知道这个时长对于64*128的显示屏来说正不正常。但大体配置已完成,已经能够正常使用,后续遇到具体问题再来改进。

//主函数程序
int main()
{
	RCC_HSE_Config(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);   // (8/1)*9 = 72M
					//外部晶振1分频		  //PLL锁相环9倍频
	SysTick_Init(72);	//滴答定时器初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//中断优先级组合方式2
	USART1_Init(9600);	//串口1初始化 波特率9600
	LED_Init();	//LED0 LED1初始化
	OLED_Init();
	
	while(1)
	{	
		
		i++;
		if(i % 50 == 0)
		{
			LED2_green = !LED2_green;
			i=0;			
			
		}
		delay_ms(10);
		
		OLED_ShowFontHZ(16*0,0,0,16,1);	 //电子信息
		OLED_ShowFontHZ(16*1,0,1,16,1);
		OLED_ShowFontHZ(16*2,0,2,16,1);
		OLED_ShowFontHZ(16*3,0,3,16,1);
				
		OLED_Refresh_Gram();//更新显示
	}
}

这是个人在初次学习oled配置过程中的一些理解,如有不妥或错误之处敬请见谅并麻烦各位大佬指正,非常感谢!

版权声明:本文为CSDN博主「askasz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45655978/article/details/114505535

生成海报
点赞 0

askasz

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

OV7670摄像头模块资料

OV7670摄像头模块资料 一、实物图和原理图 二、模块简介 OV7670 是 OV( OmniVision)公司生产的 CMOS VGA 图像传感器。该传感器体积小、工作电压低,提供单片 VGA

pwm电机调速的原理介绍与代码实现

1、pwm实现调速的原理与介绍 PWM(Pulse Width Modulation)脉冲宽度调制。 1)占空比 pwm占空比就是一个脉冲周期内有效电平在整个周期所占的比例。 通过调节PWM的占空比就能调节IO口上电压的持续

【STM32Cube笔记】2-STM32Cube安装教程

【STM32Cube笔记】系列文章目录 1-基于STM32的VSCode入门级教程前言 2-STM32Cube安装教程 3-STM32CubeIDE汉化 4-STM32Cube配置时钟设置 5-跑马灯引脚配置 6-Cortex-M7内核基本配