硬件环境
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亮度太高需要遮挡一下,这个没有什么问题😒