资讯详情

合泰单片机 | HT66F3195 | 个人库开发过程 | 【11】I2C + SSD1306 + AHT20驱动

硬件环境

OLED

在这里插入图片描述 OLED用这个,4pin的I2C驱动,尺寸为0.像素为1286英寸x64。

AHT20

自己买的传感器加上自己设计的小板。

程序配置

I2C部分

这里就不用介绍太多了,可以参考我之前写的另一个笔记: STM32F103C6T6 | 模拟IIC主机读取AHT20温湿度传感器数据 虽然这种用法只有16个M主频的HT66F3195效率不是很高,但考虑到后续的开发和可读性,首先选择这种方法,如果你必须追求效率,换回直接操作寄存器的方法。

OLED部分

所有操作都是基于模拟I2C过去发送数据给主机OLED。 OLED.c

发送数据

void OLED_Write_Data(unsigned char data) { 
             I2C_Start(&I2C_Dev[I2C_OLED]);     I2C_Write_Byte(&I2C_Dev[I2C_OLED], (( (OLED_Dev_Address)<<1 ) | (OLED_In_Rx) ));     if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)     { 
                 I2C_Stop(&I2C_Dev[I2C_OLED]);         return ;     }     GCC_DELAY(I2C_Delay);     /*------------------------------------------------------------*/     I2C_Write_Byte(&I2C_Dev[I2C_OLED], 0x40);     if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)     { 
                 I2C_Stop(&I2C_Dev[I2C_OLED]);         return ;
    }
    GCC_DELAY(I2C_Delay);
    /*------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_OLED], data);
    if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
    { 
        
        I2C_Stop(&I2C_Dev[I2C_OLED]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*------------------------------------------------------------*/
    I2C_Stop(&I2C_Dev[I2C_OLED]);
    return ;
}

发送命令

void OLED_Write_Command(unsigned char com)
{ 
        
    I2C_Start(&I2C_Dev[I2C_OLED]);
    I2C_Write_Byte(&I2C_Dev[I2C_OLED], (( (OLED_Dev_Address)<<1 ) | (OLED_In_Rx) ));
    if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
    { 
        
        I2C_Stop(&I2C_Dev[I2C_OLED]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_OLED], 0x00);
    if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
    { 
        
        I2C_Stop(&I2C_Dev[I2C_OLED]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_OLED], com);
    if(I2C_Check_ACK(&I2C_Dev[I2C_OLED]) == I2C_NACK)
    { 
        
        I2C_Stop(&I2C_Dev[I2C_OLED]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*------------------------------------------------------------*/
    I2C_Stop(&I2C_Dev[I2C_OLED]);
    return ;
}

初始化

这一段初始化程序是参考了别人的数据,但是具体是哪一篇我也不记得了,因为有很多都是一样的。

void OLED_Init()
{ 
        
    OLED_Write_Command(0xAE); //关闭显示
	OLED_Write_Command(0x00);//设置低列地址
	OLED_Write_Command(0x10);//设置高列地址
	OLED_Write_Command(0x40);//设置起始行地址,集映射RAM显示起始行(0x00~0x3F)
	OLED_Write_Command(0x81);//设置对比度控制寄存器
	OLED_Write_Command(0xCF);//设置SEG输出电流亮度
	OLED_Write_Command(0xA1);//段重定义设置,bit0:0,0->0;1,0->127; 0xa0左右反置 0xa1正常
	OLED_Write_Command(0xC8);//设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 0xc0上下反置 0xc8正常
	OLED_Write_Command(0xA6);//设置正常显示(设置显示方式;bit0:1,反相显示;0,正常显示 )
	OLED_Write_Command(0xA8);//设置驱动路数 设置多路复用比(1比64)
	OLED_Write_Command(0x3F);//1/64 duty(默认0X3F(1/64))
	OLED_Write_Command(0xD3);//设置显示偏移位移映射RAM计数器(0x00~0x3F)
	OLED_Write_Command(0x00);//-not offset
	OLED_Write_Command(0xD5);//设置显示时钟分频比/振荡器频率
	OLED_Write_Command(0x80);//设置分频比,设置时钟为100帧/秒
	OLED_Write_Command(0xD9);//设置预充电周期
	OLED_Write_Command(0xF1);//设置预充15个时钟,放电1个时钟([3:0],PHASE 1;[7:4],PHASE 2;)
	OLED_Write_Command(0xDA);//设置COM硬件引脚配置
	OLED_Write_Command(0x12);//[5:4]配置
	OLED_Write_Command(0xDB);//设置VCOMH 电压倍率
	OLED_Write_Command(0x40);//Set VCOM 释放电压([6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;)
	OLED_Write_Command(0x20);//设置页面寻址模式(0x00/0x01/0x02)
	OLED_Write_Command(0x00);//[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
	OLED_Write_Command(0x8D);//设置充电泵启用/禁用
	OLED_Write_Command(0x14);//设置(0x10禁用,0x14启用)
	OLED_Write_Command(0xA4);// 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) (0xa4/0xa5)
	OLED_Write_Command(0xA6);// 设置显示方式;bit0:1,反相显示;0,正常显示 (0xa6/a7) 
	OLED_Write_Command(0xAF);//开启显示

    OLED_Set_Position(0, 0);
    OLED_Display_All(0x00);
}

设定坐标

void OLED_Set_Position(unsigned char x, unsigned char y)
{ 
        
    OLED_Write_Command(0xB0 + y);
    OLED_Write_Command((x >> 4) + 0x10);
    OLED_Write_Command(x & 0x0F);
}

全屏显示或者熄灭

这个主要是配合自己测试用的,自己去改需要写的数据为0还是1去刷整个屏幕。

void OLED_Display_All(unsigned char data)
{ 
        
    unsigned char i, j;
    for(i = 0; i < 8; i++)
    { 
        
    	Feed_Dog();
        for(j = 0; j < 128; j++)
        { 
        
        	OLED_Write_Data(data);
        }
    }
}

清除屏幕指定区域

void OLED_Clear_Area(unsigned char x_start, unsigned char x_end, unsigned char y_start, unsigned char y_end)
{ 
        
    unsigned char i, j;
    OLED_Set_Position(x_start, y_start);
    for(i = y_start; i < y_end + 1; i++)
    { 
        
        OLED_Set_Position(x_start, i);
        for(j = x_start; j < x_end + 1; j++)
        { 
        
            OLED_Write_Data(0x00);
        }
    }
}

判断是否为特殊符号

如果返回一个0xFF证明不是符号,因为目前这个阶段也不会有这么多符号,只列举一些常见常用的,后续添加的话直接加到尾部。

const char OLED_Symbol_Table[] = 
{ 
        
	',',
	'.',
	'!',
	'~',
	':',
	';',
	'/',
	'?',
	'*',
	' ',
	'%',
};
const unsigned char OLED_Symbol_Table_Length = sizeof(OLED_Symbol_Table);

for循环会在已经列举出的符号表内遍历,如果有配对的符号则返回对应的索引号。

unsigned char OLED_Is_Symbol(char sym)
{ 
        
	unsigned char flag = 0xFF;
	unsigned char i;

	for(i = 0; i < OLED_Symbol_Table_Length; i++)
	{ 
        
		if(sym == OLED_Symbol_Table[i])
			flag = i;
	}

	return flag;
}

显示单个英文字符

因为后续有一个输出英文字符串的操作,一般是要配合标点符号输出,所以在这里面加了一个标点符号的判断和输出。

void OLED_Set_En_Char(unsigned char x, unsigned char y, char n)
{ 
        
    unsigned char i;
	unsigned char index;
    unsigned char flag = 0;
	flag = OLED_Is_Symbol(n);
	if(flag != 0xFF)	/* 如果为标点符号 */
	{ 
        
		index = flag;
	}
   	else	/* 如果不是标点符号,即为英文字符 */
    { 
        
        if(n < 90)
            index = (unsigned char)n - 65;
        else
            index = (unsigned char)n - 71;
    }

	OLED_Set_Position(x, y);
	for(i = 0; i < 8; i++)
	{ 
        
        if(flag != 0xFF)
            OLED_Write_Data(OLED_Symbol_Font[index][i]);
        else
		    OLED_Write_Data(OLED_En_Font[index][i]);
	}
	
	OLED_Set_Position(x, y + 1);
	for(i = 8; i < 16; i++)
	{ 
        
        if(flag != 0xFF)
            OLED_Write_Data(OLED_Symbol_Font[index][i]);
        else
		    OLED_Write_Data(OLED_En_Font[index][i]);
	}
	Feed_Dog();
}

显示英文字符串

这里面有一个自动换行的操作,就是先一步判断,输出下一个字符的空间,够不够放一个字符,如果空间够就放,不够的话就切换到下一行再输出,这里的一行对于屏幕来说其实是两行,因为一个字符的高度本身就要占掉屏幕的两行。

void OLED_Set_En_String(unsigned char x, unsigned char y, char *p)
{ 
        
    unsigned char x_temp = x, y_temp = y;
	char *tp = p;
    OLED_Set_Position(x, y);
	while(*tp != '\0')
	{ 
        
		OLED_Set_En_Char(x_temp, y_temp, *tp);

		if((x_temp + 8) < 120)  /* 判断是否达到行末端 */
		{ 
        
			x_temp += 8;    /* 未到达继续步进单个字符 */
		}
		else
		{ 
        
			x_temp = 0;             /* 如果到达x轴边界,进行一个字符的换行 */
			if((y_temp + 2) < 7)
				y_temp += 2;
			else                    /* y轴边界暂时不处理,理论上应该会回到左上角起点的位置 */
				y_temp += 0;
		}
		
		tp += 1;
	}
}

显示单个数字

这个跟显示单个英文字符差不多是一样的,但是因为不存在大小写映射关系,所以简化了一些。

void OLED_Set_Num_Char(unsigned char x, unsigned char y, unsigned char n)
{ 
        
	unsigned char i;
	unsigned char index;
    unsigned char flag = 0;

	flag = OLED_Is_Symbol(n);
	if(flag != 0xFF)	/* 如果为标点符号 */
	{ 
        
		index = flag;
	}
	else
	{ 
        
		index = (unsigned char)n - 48;
	}

	OLED_Set_Position(x, y);
	for(i = 0; i < 8; i++)
	{ 
        
		if(flag != 0xFF)
			OLED_Write_Data(OLED_Symbol_Font[index][i]);
		else
			OLED_Write_Data(OLED_Num_Font[index][i]);
	}

	OLED_Set_Position(x, y + 1);
	for(i = 8; i < 16; i++)
	{ 
        
		if(flag != 0xFF)
			OLED_Write_Data(OLED_Symbol_Font[index][i]);
		else
			OLED_Write_Data(OLED_Num_Font[index][i]);
	}
	Feed_Dog();
}

显示数字串

输出字符串的这个几乎也是一模一样,但是如果我要将两个合并在一起的话,我还要额外加形参输入,函数里面一大片分支判断,个人又不是很喜欢这种形式。

void OLED_Set_Num_String(unsigned char x, unsigned char y, char *p)
{ 
        
    unsigned char x_temp = x, y_temp = y;
	char *tp = p;
    OLED_Set_Position(x, y);
	while(*tp != '\0')
	{ 
        
		OLED_Set_Num_Char(x_temp, y_temp, *tp);

		if((x_temp + 8) < 120)  /* 判断是否达到行末端 */
		{ 
        
			x_temp += 8;    /* 未到达继续步进单个字符 */
		}
		else
		{ 
        
			x_temp = 0;             /* 如果到达x轴边界,进行一个字符的换行 */
			if((y_temp + 2) < 7)
				y_temp += 2;
			else                    /* y轴边界暂时不处理,理论上应该会回到左上角起点的位置 */
				y_temp += 0;
		}
		
		tp += 1;
	}
}

显示单个符号

void OLED_Set_Symbol_Char(unsigned char x, unsigned char y, unsigned char n)
{ 
        
	unsigned char i;
    OLED_Set_Position(x, y);
	for(i = 0; i < 8; i++)
	{ 
        
        OLED_Write_Data(OLED_Symbol_Font[n][i]);
	}
	
	OLED_Set_Position(x, y + 1);
	for(i = 8; i < 16; i++)
	{ 
        
        OLED_Write_Data(OLED_Symbol_Font[n][i]);
	}
}

显示汉字

void OLED_Set_Cn_Char(unsigned char x, unsigned char y, unsigned char n)
{ 
        
	unsigned char i;
	OLED_Set_Position(x, y);
	for(i = 0; i < 16; i++)
	{ 
        
		OLED_Write_Data(OLED_Cn_Font[n][i]);
	}
	Feed_Dog();
	OLED_Set_Position(x, y + 1);
	for(i = 16; i < 32; i++)
	{ 
        
		OLED_Write_Data(OLED_Cn_Font[n][i]);
	}
	Feed_Dog();
}

显示图片

忘记那个图片的数组是怎样的了,如果是二维数组的话可能还有点麻烦。 不记得二维数组跟指针之间的关系了,但如果是一个128x64的超大一维数组反而还容易处理。 连续往里面写数据,因为目前采用的是页地址,遇到边界自己进行跳转,不用自己额外控制坐标。

这个是全局的图片,所以尺寸固定是128x64这种。

void OLED_Set_Pic(const unsigned char *p)
{ 
        
	unsigned char i, j;

	OLED_Display_All(0x00);
	OLED_Set_Position(0, 0);
	for(i = 0; i < 8; i++)
	{ 
        
		Feed_Dog();
		for(j = 0; j < 128; j++)
		{ 
        
			OLED_Write_Data(*(p + 128*i + j));
		}
	}
}

显示信息

这个是配合那个温湿度的数据来显示,

标签: 3c12p01770位移传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台