CH592F /CH582通过硬件IIC读写AHT10 /AHT20,并将数据通过BLE发送给上位机

网上很多读写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发送给上位机,这段代码网上很多,我们就不多说,确实有需要的可以联系我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永远的元子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值