网上很多读写AHT10 /AHT20的代码,但大多都是用IO口模拟实现,这种方式对理解IIC的工作原理及时序很有益处,但在一些对时间要求比较严格的场合下,比如一些带RTOS的软件,可能就不是很合适。
CH582/CH592单片机主攻蓝牙相关的功能,也带有硬件IIC模块。考虑到低功耗蓝牙对时间的把控比较严格,相比软件模拟IIC,硬件IIC能够节省模拟时序代码中“无意义”的等待,且时序更精准,达到更高的通信速率,更适合需要保留BLE功能的场合。
以下代码以aht10 / aht20外设为例,简要描述硬件IIC的代码实现。
首先我们通过aht10 / aht20的规格书,获取读写控制aht10 / aht20的命令:
以及读取到的数据如何转换成我们直观的温湿度值:
根据aht10 / aht20的规格书,我们知道,aht10 / aht20的读写控制主要有4个阶段:初始化、软复位、开始测量温湿度、读取测量数据,接下来我们分别介绍一下各个阶段的代码。
#define AHT10_20_RdAddr 0x71
#define AHT10_20_WrAddr 0x70
#define AHT10_20_InitCmd 0xE1
#define AHT10_20_InitDat1 0x08
#define AHT10_20_InitDat2 0x00
#define AHT10_20_MeasureCmd 0xAC
#define AHT10_20_MeasureDat1 0x33
#define AHT10_20_MeasureDat2 0x00
#define AHT10_20_RESETCmd 0xBA
1、初始化:
void InitAHT10_20(void)
{
GPIOB_ModeCfg(GPIO_Pin_14 | GPIO_Pin_15, GPIO_ModeIN_PU); //PB14:SDA,PB15:SCL 内部上拉较弱,可能需要外部上拉
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);
//择IIC模式,400k速率,选择占空比,默认开启ACK(接收模式必须开启),作从机时使用地址位数为7位,作从机时的地址(本测试中单片机为主机模式,后面两个参数不起作用)
IIC_Send_Cmd_2Data(AHT10_20_WrAddr,AHT10_20_InitCmd,AHT10_20_InitDat1,AHT10_20_InitDat2);
}
需要注意的是,CH582的IIC接口使用的是PB12及PB13,而CH592使用的是PB14及PB15。
另外,在规格书中,没有找到初始化的命令后面需要接0x08,0x00这两个数据的说明,但网上很多初始化的代码都有发送这两个数据,这里选择保留了这两个数据。不过我试着不发过这两个数据,好象也能正常测量温湿度。
void InitAHT10_20(void)
{
GPIOB_ModeCfg(GPIO_Pin_14 | GPIO_Pin_15, GPIO_ModeIN_PU); //PB14:SDA,PB15:SCL 内部上拉较弱,可能需要外部上拉
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);
//择IIC模式,400k速率,选择占空比,默认开启ACK(接收模式必须开启),作从机时使用地址位数为7位,作从机时的地址(本测试中单片机为主机模式,后面两个参数不起作用)
// IIC_Send_Cmd_2Data(AHT10_20_WrAddr,AHT10_20_InitCmd,AHT10_20_InitDat1,AHT10_20_InitDat2);
IIC_Send_Cmd(AHT10_20_WrAddr,AHT10_20_InitCmd);
}
发送命令给aht10 / aht20时,我们需要区分命令是几个字节,所以我们有以下代码:
void IIC_Send_Cmd(uint8_t addr,uint8_t cmd)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(cmd);
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}
void IIC_Send_Cmd_OneData(uint8_t addr, uint8_t cmd, uint8_t data)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(cmd);
//ACK之后直接写入数据
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(data); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}
void IIC_Send_Cmd_2Data(uint8_t addr, uint8_t cmd, uint8_t data1,uint8_t data2)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(cmd); //发送寄存器的地址
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(data1); //发送寄存器的地址
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(data2); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}
void IIC_Send_Cmd_nData(uint8_t addr, uint8_t cmd, uint8_t *src, uint8_t len)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
I2C_SendData(cmd); //发送寄存器的地址
//ACK之后直接写入数据
for(uint8_t i=0; i<len; i++)
{
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
I2C_SendData(*(src+i)); //发送寄存器的地址
}
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
I2C_GenerateSTOP(ENABLE); //停止信号
}
2、软复位:
void Reset_AHT10_20(void)
{
IIC_Send_Cmd(AHT10_20_WrAddr,AHT10_20_RESETCmd);
}
3、开始测量温湿度:
void Measure_AHT10_20(void)
{
IIC_Send_Cmd_2Data(AHT10_20_WrAddr,AHT10_20_MeasureCmd,AHT10_20_MeasureDat1,AHT10_20_MeasureDat2);
}
4、数据测量数据:
void Read_AHT10_20(uint8_t *buff,uint8_t len)
{
IIC_read_nByte(AHT10_20_RdAddr,buff,len);
}
同样,读取数据我们也根据读取数据的长度,有以下代码:
uint8_t IIC_read_oneByte(uint8_t addr)
{
uint8_t data = 0;
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
I2C_GenerateSTART(ENABLE); //起始信号
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags
I2C_GenerateSTOP(DISABLE); //关闭停止信号使能
I2C_AcknowledgeConfig(DISABLE); //关闭ACK使能,接收一个字节数据后,主机就回NACK表示不再接收数据
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
data = I2C_ReceiveData(); //获得从机的寄存器中的数据
I2C_GenerateSTOP(ENABLE); //停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能
return data;
}
uint16_t IIC_read_2Bytes(uint8_t addr)
{
uint8_t dataH = 0, dataL = 0;
uint16_t data = 0;
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
//直接产生一个重起始信号即可开始读的过程
I2C_GenerateSTART(ENABLE); //重起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags
I2C_GenerateSTOP(DISABLE); //关闭停止信号使能
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
dataH = I2C_ReceiveData(); //获得从机的寄存器中的数据
I2C_AcknowledgeConfig(DISABLE); //清除ACK位 主设备为了能在收到最后一个字节后产生一个NACK脉冲,
//必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0)
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态
dataL = I2C_ReceiveData(); //获得从机地址的寄存器地址中的数据
I2C_GenerateSTOP(ENABLE); //使能停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能
data = (uint16_t)(dataH<<8) + dataL;
return data;
}
void IIC_read_nByte(uint8_t addr, uint8_t *des, uint8_t len)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
//直接产生一个重起始信号即可开始读的过程
I2C_GenerateSTART(ENABLE); //重起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
I2C_Send7bitAddress(addr, I2C_Direction_Receiver); //发送地址+最低位1表示为“读”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //判断BUSY, MSL and ADDR flags
I2C_GenerateSTOP(DISABLE); //关闭停止信号使能
for(uint8_t i=0; i<len; i++)
{
if(i == len-1)
I2C_AcknowledgeConfig(DISABLE); //清除ACK位 主设备为了能在收到最后一个字节后产生一个NACK脉冲,
//必须在读取倒数第二个字节之后(倒数第二个RxNE 事件之后)清除ACK位(ACK=0)
while(!I2C_GetFlagStatus(I2C_FLAG_RXNE)); //获取RxEN的状态,等待收到数据
*(des+i) = I2C_ReceiveData(); //获得从机的寄存器中的数据
}
I2C_GenerateSTOP(ENABLE); //使能停止信号
I2C_AcknowledgeConfig(ENABLE); //传输完毕,再次打开ACK使能
}
我们需要对读取到的数据进行转换,变成更直观的数据:
float wendu,shidu;
long readdata;
uint8_t AHT_buff[6];
readdata = 0; //原始湿度数据合成
readdata = (AHT_buff[1]<<8)|AHT_buff[2];
readdata = ((readdata<<8)|AHT_buff[3])>>4;
readdata = readdata & 0x000fffff;
readdata*=1000;
readdata/=1048576; //1048576=2的20次方
shidu = (short)readdata; //0.0~100.0%
shidu /= 10;
readdata = 0; //原始温度数据合成
readdata = ((AHT_buff[3] % 16)<<8)|AHT_buff[4];
readdata = (readdata<<8)|AHT_buff[5];
readdata = readdata & 0x000fffff;
readdata*=2000;
readdata/=1048576;
wendu = (short)readdata-500; //-40.0~85.0℃
wendu /= 10;
最后,我们需要在软复位、测量温湿度、读取数据三个阶段循环:
if(events & AHT_CMD_EVENT)
{
tmosTimer AHTtmosTimer;
long readdata;
uint8_t AHT_buff[6];
PRINT("* \n");
// OLED_Display_Test();
// tmos_start_task(halTaskID, OLED_DISPLAY_EVENT, MS1_TO_SYSTEM_TIME(1000));
switch (AHT_Phase)
{
case AHT_INIT_PHASE:
InitAHT10_20();
AHTtmosTimer = 15;
AHT_Phase = AHT_MEASURE_PHASE;
break;
case AHT_RESET_PHASE:
Reset_AHT10_20();
AHTtmosTimer = 15;
AHT_Phase = AHT_MEASURE_PHASE;
break;
case AHT_MEASURE_PHASE:
Measure_AHT10_20();
AHTtmosTimer = 120;
AHT_Phase = AHT_READ_PHASE;
break;
case AHT_READ_PHASE:
Read_AHT10_20(AHT_buff,6);
readdata = 0; //原始湿度数据合成
readdata = (AHT_buff[1]<<8)|AHT_buff[2];
readdata = ((readdata<<8)|AHT_buff[3])>>4;
readdata = readdata & 0x000fffff;
readdata*=1000;
readdata/=1048576;
shidu = (short)readdata; //0.0~100.0%
shidu /= 10;
readdata = 0; //原始温度数据合成
readdata = ((AHT_buff[3] % 16)<<8)|AHT_buff[4];
readdata = (readdata<<8)|AHT_buff[5];
readdata = readdata & 0x000fffff;
readdata*=2000;
readdata/=1048576;
wendu = (short)readdata-500; //-40.0~85.0℃
wendu /= 10;
tmos_start_task(halTaskID, OLED_DISPLAY_EVENT, MS1_TO_SYSTEM_TIME(100));
AHTtmosTimer = 3000;
AHT_Phase = AHT_RESET_PHASE;
break;
}
tmos_start_task( halTaskID, AHT_CMD_EVENT, AHTtmosTimer );
return events ^ AHT_CMD_EVENT;
}
至此,我们得到了直观的温湿度数据,可以通过BLE发送给上位机,这段代码网上很多,我们就不多说,确实有需要的可以联系我。