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

硬件环境

OLED

在这里插入图片描述
在这里插入图片描述
OLED用的是这款,4pin的I2C驱动,尺寸为0.96英寸,像素为128x64。

AHT20

自己买的传感器加上自己设计的小板子。
在这里插入图片描述

程序配置

I2C部分

这个在这里就不用过多介绍了,可以参照之前我写过的另一篇笔记:
STM32F103C6T6 | 模拟IIC主机读取AHT20温湿度传感器数据
虽然这种用法在只有16M主频的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));
		}
	}
}

显示信息

这个是配合那个温湿度的数据来显示,目前是单个字符来控制。后续看看怎样直接输出一个浮点数比较合适再改动这个部分。
目前这种写法适用度确实不够高。

这里有一个需要注意的点是,那个显示数字的字符,平时使用都是直接把一个0 - 9的数字作为参数直接索引。后面发现一个问题,有一次不小心把字符型的0 - 9作为参数,显示出字母来了,然后发现有可能是字符和整形之间的转换关系导致的数组内存越界,所以这下面的例程+48就是为了把字符转成整形然后去索引。具体可以参照ASCII码数字和字母之间的关联。

void OLED_Display_Infomation()
{
	unsigned char temp_data[3];
	unsigned int hum_data[3];

	temp_data[0] = ((unsigned char)AHT20_Data.Temperature[0]) / 10;
	temp_data[1] = ((unsigned char)(AHT20_Data.Temperature[0]));
	temp_data[2] = ((unsigned char)(AHT20_Data.Temperature[0] * 10));

	hum_data[0] = ((unsigned int)(AHT20_Data.Humidity[0] * 10));
	hum_data[1] = ((unsigned int)(AHT20_Data.Humidity[0] * 100));
	hum_data[2] = ((unsigned int)(AHT20_Data.Humidity[0] * 1000));
	

    OLED_Set_Cn_Char(0, 1, 0);          /* 温 */
    OLED_Set_Cn_Char(16, 1, 2);         /* 度 */
    OLED_Set_Symbol_Char(32, 1, 4);     /* : */
	OLED_Set_Num_Char(48, 1, (temp_data[0]) + 48);		/* 十位 */	/* +48是把char字符转成一个整形去索引,‘1’这种是映射不到正确位置的 */
	OLED_Set_Num_Char(56, 1, (temp_data[1] % 10) + 48);		/* 个位 */
	OLED_Set_Symbol_Char(64, 1, 1);
	OLED_Set_Num_Char(72, 1, (temp_data[2] % 10) + 48);		/* 小数 */
    OLED_Set_Cn_Char(100, 1, 3);        /* ℃ */

    OLED_Set_Cn_Char(0, 5, 1);          /* 湿 */
    OLED_Set_Cn_Char(16, 5, 2);         /* 度 */
    OLED_Set_Symbol_Char(32, 5, 4);     /* : */
	OLED_Set_Num_Char(48, 5, (hum_data[0]) + 48);		/* 十位 */
	OLED_Set_Num_Char(56, 5, (hum_data[1] % 10) + 48);		/* 个位 */
	OLED_Set_Symbol_Char(64, 5, 1);
	OLED_Set_Num_Char(72, 5, (hum_data[2] % 10) + 48);		/* 小数 */
    OLED_Set_Symbol_Char(100, 5, 10);    /* % */
}

OLED.h

#ifndef _OLED_H_
#define _OLED_H_

void OLED_Write_Data(unsigned char data);
void OLED_Write_Command(unsigned char com);
void OLED_Init();
void OLED_Set_Position(unsigned char x, unsigned char y);
void OLED_Display_All(unsigned char);
void OLED_Clear_Area(unsigned char , unsigned char , unsigned char , unsigned char );
void OLED_Set_En_Char(unsigned char x, unsigned char y, char n);
void OLED_Set_En_String(unsigned char x, unsigned char y, char *p);
void OLED_Set_Num_Char(unsigned char x, unsigned char y, unsigned char n);
void OLED_Set_Num_String(unsigned char x, unsigned char y, char *p);
void OLED_Set_Symbol_Char(unsigned char x, unsigned char y, unsigned char n);
void OLED_Set_Cn_Char(unsigned char x, unsigned char y, unsigned char n);
void OLED_Display_Infomation();
unsigned char OLED_Is_Symbol(char sym);
void OLED_Set_Pic(const unsigned char *p);

#define     OLED_Dev_Address       0x3C
#define     OLED_In_Tx             1
#define     OLED_In_Rx             0

#endif

AHT20部分

AHT20.c

初始化

一开始以为这个编译器没有那些标准的库函数的,后来找了一下那个IDE自带的指导手册,发现居然能使用一些标准的库函数,比如下面这个memset()函数,这个用来清空数组是比较方便的,因为之前都是使用一个for循环,看起来有点蠢😒

void AHT20_Init()
{
    memset(AHT20_Data.Rx_Humidity, 0, 3);
    memset(AHT20_Data.Rx_Temperature, 0, 3);
    memset(AHT20_Data.Humidity, 0, AHT20_DataIndex_Max);
    memset(AHT20_Data.Temperature, 0, AHT20_DataIndex_Max);
    AHT20_Data.Sensor_Status = 0;
    AHT20_Data.Status = 0;
    AHT20_Data.Data_Index = 0;

    AHT20_Data.Measure_ScanCnt = 1;
}

获取传感器状态

void AHT20_Get_Status()
{
    unsigned char ack_flag;
    I2C_Start(&I2C_Dev[I2C_AHT20]);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], (( (AHT20_Dev_Address)<<1 ) | (AHT20_In_Tx) ));
    ack_flag = I2C_Check_ACK(&I2C_Dev[I2C_AHT20]);
    if(ack_flag == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
    I2C_Send_NACK(&I2C_Dev[I2C_AHT20]);
    GCC_DELAY(I2C_Delay);
    I2C_Stop(&I2C_Dev[I2C_AHT20]);
    return ;
}

传感器校准

传感器校准这个操作,上电之后校准一次就够了,如果每次采集数据之前调用一次的结果就是,湿度数据一直都是0x08000,因为之前用其他单片机也使用过这个模块,按照道理来说那边没有问题,这边有问题的话只能是程序问题了,最后才发现是这个校准的问题。

void AHT20_Calibration()
{
    unsigned char ack_flag;
    I2C_Start(&I2C_Dev[I2C_AHT20]);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], (( (AHT20_Dev_Address)<<1 ) | (AHT20_In_Rx) ));
    ack_flag = I2C_Check_ACK(&I2C_Dev[I2C_AHT20]);
    if(ack_flag == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0xBE);
    ack_flag = I2C_Check_ACK(&I2C_Dev[I2C_AHT20]);
    if(ack_flag == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0x08);
    ack_flag = I2C_Check_ACK(&I2C_Dev[I2C_AHT20]);
    if(ack_flag == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0x00);
    ack_flag = I2C_Check_ACK(&I2C_Dev[I2C_AHT20]);
    if(ack_flag == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Stop(&I2C_Dev[I2C_AHT20]);
}

测量开始

按照模块的数据手册的流程来发送数据,没有什么需要注意的。

void AHT20_Measure_Start()
{
    I2C_Start(&I2C_Dev[I2C_AHT20]);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], (( (AHT20_Dev_Address)<<1 ) | (AHT20_In_Rx) ));
    if(I2C_Check_ACK(&I2C_Dev[I2C_AHT20]) == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0xAC);
    if(I2C_Check_ACK(&I2C_Dev[I2C_AHT20]) == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0x33);
    if(I2C_Check_ACK(&I2C_Dev[I2C_AHT20]) == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Write_Byte(&I2C_Dev[I2C_AHT20], 0x00);
    if(I2C_Check_ACK(&I2C_Dev[I2C_AHT20]) == I2C_NACK)
    {
        I2C_Stop(&I2C_Dev[I2C_AHT20]);
        return ;
    }
    GCC_DELAY(I2C_Delay);
    /*-------------------------------------------------------------*/
    I2C_Stop(&I2C_Dev[I2C_AHT20]);
}

采集传感器数据

数据格式是先从高位开始读取的,注意移位不要弄错。还有一个比较重要的点是,因为湿度最后的结果是百分比显示,然而读到的数据是1以下的小数,所以这边要注意一下转化的问题。
因为从后面测试到的结果来看,我用手贴着传感器理论上温度应该是一直上升才对的,但是不知道为什么上升第二次的时候会往下掉一次再继续上升,每次都是这样所以我怀疑是数据是不是也需要通过滤波之类的算法?
然后写了一个比较粗糙的算法是,读取5次数据,冒泡法排序,取中间3次数值的平均来显示,发现结果还是一样。其实滤波可能没有必要,因为我这边对接的是一个模块而不是传感器本身,数值滤波那边应该是模块那边的主控自己处理好才把数据发出来的,我进行二次滤波的话其实是浪费时间的。
所以有可能是显示那边的问题,但是感觉也不应该🥱暂时查不出问题。

void AHT20_Get_SensorData()
{
	I2C_Start(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	I2C_Write_Byte(&I2C_Dev[I2C_AHT20], (AHT20_Dev_Address << 1) | AHT20_In_Tx);
	if(I2C_Check_ACK(&I2C_Dev[I2C_AHT20]) == I2C_NACK)
	{
		I2C_Stop(&I2C_Dev[I2C_AHT20]);
		return ;
	}
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Sensor_Status = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Humidity[0] = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Humidity[1] = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Humidity[2] = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Temperature[0] = AHT20_Data.Rx_Humidity[2] & 0x0F;
	AHT20_Data.Rx_Humidity[2] >>= 4;
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Temperature[1] = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.Rx_Temperature[2] = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_ACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	AHT20_Data.CRC_Data = I2C_Read_Byte(&I2C_Dev[I2C_AHT20]);
	I2C_Send_NACK(&I2C_Dev[I2C_AHT20]);
	GCC_DELAY(I2C_Delay);
	/*--------------------------------------------------------------*/
	I2C_Stop(&I2C_Dev[I2C_AHT20]);

	AHT20_Data.Humidity[AHT20_Data.Data_Index] = (AHT20_Data.Rx_Humidity[0] << 12) + (AHT20_Data.Rx_Humidity[1] << 4) + AHT20_Data.Rx_Humidity[2];
	AHT20_Data.Humidity[AHT20_Data.Data_Index] = AHT20_Data.Humidity[AHT20_Data.Data_Index] / 1048576;

	AHT20_Data.Temperature[AHT20_Data.Data_Index] = (AHT20_Data.Rx_Temperature[0] << 16) + (AHT20_Data.Rx_Temperature[1] << 8) + AHT20_Data.Rx_Temperature[2];
	AHT20_Data.Temperature[AHT20_Data.Data_Index] = ((AHT20_Data.Temperature[AHT20_Data.Data_Index] * 25) / 131072) - 50;

    AHT20_Data.Data_Index += 1;
    if(AHT20_Data.Data_Index >= (AHT20_DataIndex_Max))  /* 数据采集索引范围:0~AHT20_DataIndex_Max - 1 */
    {
        AHT20_Data.Data_Index = 0;
        AHT20_Data.Status &= ~AHT20_Mask_DataMeasure;    /* 重置标志位,切换扫描功能 */
        AHT20_Data.Status |= AHT20_Mask_DataCollection; /* 数据采集完毕 */
    }

	return ;
}

数据处理

库函数本身带一个qsort()的排序算法,但是不知道为什么调用两次之后,我的ROM空间直接爆炸了,看不到那个函数的原型不知道什么情况,反正也不是追求什么效率,自己写了一个冒泡法也能满足需求。因为湿度显示的话就没有我提到的那个问题就没有继续滤波了,说实话其实真没必要,但是还是预留在这里,后面有哪些需求再回来看看🙄

void AHT20_Data_Process()
{
    unsigned char i, j;
    long double buf;

    /* 使用冒泡排序将数据降序排列 */
    for(i = 0; i < AHT20_DataIndex_Max; i++)
    {
        for(j = 0; j < AHT20_DataIndex_Max - i - 1; j++)
        {
            if(AHT20_Data.Temperature[j] < AHT20_Data.Temperature[j + 1])
            {
                buf = AHT20_Data.Temperature[j];
                AHT20_Data.Temperature[j] = AHT20_Data.Temperature[j - 1];
                AHT20_Data.Temperature[j - 1] = buf;
            }
        }
    }

    // for(i = 0; i < AHT20_DataIndex_Max; i++)
    // {
    //     for(j = 0; j < AHT20_DataIndex_Max - i - 1; j++)
    //     {
    //         if(AHT20_Data.Humidity[j] < AHT20_Data.Humidity[j + 1])
    //         {
    //             buf = AHT20_Data.Humidity[j];
    //             AHT20_Data.Humidity[j] = AHT20_Data.Humidity[j - 1];
    //             AHT20_Data.Humidity[j - 1] = buf;
    //         }
    //     }
    // }
    
    /* 清空[0]然后配合for循环把数值累加再取平均值 */
    //AHT20_Data.Humidity[0] = 0;
    AHT20_Data.Temperature[0] = 0;

    /* 去除最值,将中间数相加然后取平均值 */
    for(i = 1; i < AHT20_DataIndex_Max - 1; i++)
    {
        //AHT20_Data.Humidity[0] += AHT20_Data.Humidity[i];
        AHT20_Data.Temperature[0] += AHT20_Data.Temperature[i];
    }

    //AHT20_Data.Humidity[0] = AHT20_Data.Humidity[0] / (AHT20_DataIndex_Max - 2);
    AHT20_Data.Temperature[0] = AHT20_Data.Temperature[0] / (AHT20_DataIndex_Max - 2);
}

流程任务1

从开始测量到把数据从模块里读到MCU里,这个过程数据手册说要延时至少75ms,我这边给到80ms。
整个模块的运行流程存在一个明显的先后顺序,执行频率可以不同。
运行流程:测量开始 -> 采集数据五次 -> 处理数据 -> 显示数据。

void AHT20_Task_1()
{
    if((AHT20_Data.Status & AHT20_Mask_DataMeasure) == 0)       /* 数据测量轮询扫描 */
    {
        if(AHT20_Data.Measure_ScanCnt != 0)
        {
            AHT20_Data.Measure_ScanCnt -= 1;
            if(AHT20_Data.Measure_ScanCnt == 0)
            {
                AHT20_Data.Status |= AHT20_Mask_DataMeasure;    /* 切换扫描功能 */
                AHT20_Data.Measure_ScanCnt = 8;                /* 重置倒计时 */
                AHT20_Measure_Start();                          /* 开始测量数据 */
            }
        }
    }
    else        /* 测量后延时一段时间才能读取数据 */
    {
        if(AHT20_Data.Measure_ScanCnt != 0)
        {
            AHT20_Data.Measure_ScanCnt -= 1;
            if(AHT20_Data.Measure_ScanCnt == 0)
            {
                
                AHT20_Data.Measure_ScanCnt = 2;                /* 重置倒计时 */
                AHT20_Get_SensorData();                         /* 开始采集数据 */                
            }
        }
    }
}

流程任务2

void AHT20_Task_2()
{
    if(AHT20_Data.Status & AHT20_Mask_DataCollection)
    {
        AHT20_Data.Status ^= AHT20_Mask_DataCollection;     /* 清除标志位 */
        AHT20_Data_Process();                               /* 处理数据过程 */
        AHT20_Data.Status |= AHT20_Mask_DataProcessing;     /* 数据处理完毕 */
    }
    else if(AHT20_Data.Status & AHT20_Mask_DataProcessing)
    {
        AHT20_Data.Status ^= AHT20_Mask_DataProcessing;     /* 清除标志位 */
        /* OLED输出处理函数 */
        OLED_Display_Infomation();
    }
}

AHT20.h

#ifndef _AHT20_H_
#define _AHT20_H_

#define     AHT20_Dev_Address           0x38
#define     AHT20_In_Tx                 1
#define     AHT20_In_Rx                 0

#define     AHT20_Mask_DataMeasure      0x01
#define     AHT20_Mask_DataCollection   0x02
#define     AHT20_Mask_DataProcessing   0x04

#define     AHT20_DataIndex_Max         5

void AHT20_Init();
void AHT20_Calibration();
void AHT20_Measure_Start();
void AHT20_Get_SensorData();
void AHT20_Get_Status();
void AHT20_Data_Process();
void AHT20_Task_1();
void AHT20_Task_2();

typedef struct
{
    long Rx_Temperature[3];
    long Rx_Humidity[3];
    long double Temperature[AHT20_DataIndex_Max];
    long double Humidity[AHT20_DataIndex_Max];
    unsigned char Sensor_Status;
    unsigned char CRC_Data;
    unsigned char Status;
    unsigned char Data_Index;

    unsigned char Measure_ScanCnt;

}AHT20_Data_T;

extern AHT20_Data_T AHT20_Data;

#endif

主函数流程

main.c

void main()
{
    System_Init();
    Timebase_0_Init();
    I2C_Init(&I2C_Dev[I2C_AHT20]);
    I2C_Init(&I2C_Dev[I2C_OLED]);
    AHT20_Init();
    OLED_Init();
    OLED_Display_Infomation();
    AHT20_Calibration();
    while(1)
    {
        if(TB0.FragmentFlag & Fragment_1ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_1ms_Mask;
            AHT20_Task_2();
        }

        if(TB0.FragmentFlag & Fragment_2ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_2ms_Mask;
        }

        if(TB0.FragmentFlag & Fragment_10ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_10ms_Mask;
            AHT20_Task_1();
        }

        if(TB0.FragmentFlag & Fragment_100ms_Mask)
        {
            TB0.FragmentFlag ^= Fragment_100ms_Mask;
        }

        if(TB0.FragmentFlag & Fragment_1s_Mask)
        {
            TB0.FragmentFlag ^= Fragment_1s_Mask;
        }
        
        Feed_Dog();
    };

最后结果

在这里插入图片描述
现在才看到板子右边部分有一个小的黑色PCB,是之前调试其他功能的时候,那个LED亮度太高需要遮挡一下,这个没有什么问题😒

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值