Подключение AM2320 по I2C и 1-Wire к STM32F103C8T6

Подключение AM2320 по I2C и 1-Wire к STM32F103C8T6

Датчик температуры и влажности AM2320 имеет на борту интерфейс I²C который позволяет подключить его к STM32 достаточно просто.

Я решил написать эту статью, чтобы поделиться с проблемой, которая возникла у меня при подключением AM2320 по I²C. Для начала рассмотрим подключение по 1 проводу.

Подключение AM2320 по 1-Wire.

Датчик AM2320 можно подключить с помощью 1 линии данных, но писать по этому отдельную статью я не стал, так как есть две статьи с описанием подключения по 1 линии: "Подключение DHT11 и DHT22 к STM32F103C8T6" и "DMA (ПДП) и Таймер в STM32F103C8T6. Подключаем DHT11 (DHT22)".

Вариант подключения по I2C к STM32F0.

Разница лишь в том, что данные передаются немного в другом формате. От DHT мы получали по 1 байту [целая часть влажности] + [дробная часть влажности] + [целая часть температуры] + [дробная часть температуры] + [crc].

В случае подключения AM2320 по одной линии нужно лишь переделать вывод температуры на дисплей. То есть мы получаем [старший байт влажности HH] + [младший байт влажности LH] + [старший байт температуры HT] + [младший байт температуры LT] + [ CRC ]

CRC - вычисляется точно так как, как для DHT.

CRC == (uint_8)(HH + HL + HT + LT)

То есть температура и влажность передается как 2-х байтное значение умноженное на 10. То есть для температуры 31.5℃ мы получим 315.

Если самый старший бит будет установлен, это значит что температура отрицательная:
10.1℃ будет представлено как 0b 0000 0000 0110 0101
-10.1℃ будет представлено как 0b 1000 0000 0110 0101

AM2320 pinout 1-wire
Подключение происходит по 1-му проводу, вывод 4 (SCL) датчика подключается на землю. Вывод 2 подтягивается к питанию через резистор 5.1К-10К.

Подключение AM2320 по I²C к STM32F10 3C8T6.

AM2320 pinout

Для подключения используют выводы 2 (SDA) и 4 (SCL). Подтягиваем их через резисторы 5.1K - 10K к питанию. Подключаем к выводам I2C нашего мк, и настраиваем их как альтернативная функция с открытым стоком.

Я выбрал порты PB6, PB7 мк STM32F103C8T6. На них висит I2C1.

Взаимодействие с датчиком AM2320 по I2C.

Адрес I2C датчика AM2320: 0xB8.

Датчик имеет две команды, чтение данных 0x03 и запись данных 0x10.

Нужные нам регистры расположены по адресам:
0x00 - Старший байт значения влажности
0x01 - Младший байт значения влажности
0x02 - Старший байт значения температуры
0x03 - Младший байт значения температуры

Обмен данных состоит из следующего алгоритма
[START] + [ адрес устройства ] + [ команду ] + [ адрес регистра ] + [ количество байт ] + [END]

И так, что бы прочесть температуру и влажность, нам нужно послать следующие данные:
[START] + [ 0xB8 ] + [ 0x03 ] + [ 0x00 ] + [ 0x04 ] + [ END ]

То есть мы говорим устройству 0xB8 что читаем данные с адреса 0x00 в количестве 4 байт.

В ответ мы получим: 0x03 + 0x04 + 4 байта прочитанных данных + 2 байта подпись CRC.

Далее код с комментариями. Тактовый генератор настроен на 72Мгц.

// вспомогательные макросы
#define AM_GPIO GPIOB
#define AM_PIN_SCL GPIO_Pin_6
#define AM_PIN_SDA GPIO_Pin_7
 
#define AM_I2C I2C1
 
#define AM_ADDR 0xB8
 
#define AM2320_CMD_READ_REG 0x03
#define AM2320_CMD_WRITE_REG 0x10
 
#define AM2320_ADDR_HH 0x00
// так как мы не будем читать по отдельности регистры
// то нам следующие 3 адреса не нужны
//#define AM2320_ADDR_LH 0x01
//#define AM2320_ADDR_HT 0x02
//#define AM2320_ADDR_LT 0x03
 
// структура, для в которую будем читать ответ
typedef struct {
    uint8_t addr;
    uint8_t len;
    uint16_t h;
    uint16_t t;
    uint16_t crc;
} temp_s;

Инициализация нашего I2C

void I2C_Conf(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
 
    GPIO_InitTypeDef port;
    GPIO_StructInit(&port);
 
    port.GPIO_Mode = GPIO_Mode_AF_OD;
    port.GPIO_Pin = AM_PIN_SCL | AM_PIN_SDA;
    port.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(AM_GPIO, &port);
 
    I2C_InitTypeDef i2c;
    i2c.I2C_Mode = I2C_Mode_I2C;
    i2c.I2C_ClockSpeed = 100000;
    i2c.I2C_Ack = I2C_Ack_Enable;
    i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    i2c.I2C_DutyCycle = I2C_DutyCycle_2;
    i2c.I2C_OwnAddress1 = 0x28;
 
    I2C_Init(AM_I2C, &i2c);
 
    I2C_Cmd(AM_I2C, ENABLE);
}

AM2320 может работать по I2C на частоте до 100 кГц.

Так как мк в режиме MASTER то не важно какой адрес мы укажем для себя, он не используется.

Теперь о проблеме о которой я писал в самом начале статьи. Я написал код, но ничего не заработало, мк зависал на строке

I2C_Send7bitAddress(AM_I2C, AM_ADDR, I2C_Direction_Transmitter);

В регистре статуса SR1 был установлен флаг AF: Acknowledge failure.

Решив перечитать даташит не так бегло, я обнаружил упоминание о том, что при первом обращении к датчику, он спит, и не ответит нам. Что бы датчик пробудить, нужно послать на его адрес команду, подождать от 800 us до 3 ms и потом начинать опрос датчика еще раз.

// функция вычисления CRC из datasheet
uint16_t crc16(uint8_t *ptr, uint8_t len) {
  uint16_t crc = 0xFFFF;
  uint8_t i;
  while (len--) {
    crc ^= *ptr++;
    for (i = 0; i < 8; i++)
    {
      if (crc & 0x01) {
      crc >>= 1;
      crc ^= 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }
  return crc;
}

// почему 6 написано ниже
#define AM_LEN 6
uint8_t buf[AM_LEN];
 
int8_t AM_Read(temp_s* temp) {
    // проверяем свободен ли I2C
    while (I2C_GetFlagStatus(AM_I2C, I2C_FLAG_BUSY));

    // включаем бит подтверждения на случай, если его где то выключили
    I2C_AcknowledgeConfig(AM_I2C, ENABLE);

    // генерируем сигнал старт
    I2C_GenerateSTART(AM_I2C, ENABLE);
    // ждем события говорящего что линия готова и свободна
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_MODE_SELECT));

    // передаем адрес устройства
    I2C_Send7bitAddress(AM_I2C, AM_ADDR, I2C_Direction_Transmitter);

    // здесь не смотрим событие
    // если датчик спит, мы получим ошибку подтверждения
    // просто делаем задержку 800-3000us
    delay_ms(1);
    
    // и шлем сигнал STOP
    I2C_GenerateSTOP(AM_I2C, ENABLE);
 
    while (I2C_GetFlagStatus(AM_I2C, I2C_FLAG_BUSY));

    // еще раз шлем старт
    I2C_GenerateSTART(AM_I2C, ENABLE);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_MODE_SELECT));

    // шлем адрес устройства
    I2C_Send7bitAddress(AM_I2C, AM_ADDR, I2C_Direction_Transmitter);
    
    // теперь, если все хорошо, датчик проснулся, и подтвердит нам получение команды
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    // шлем команду на чтение 0x03
    I2C_SendData(AM_I2C, AM2320_CMD_READ_REG);
    // ждем отправки байта
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // шлем адрес откуда начать читать
    I2C_SendData(AM_I2C, AM2320_ADDR_HH);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    // и сколько байт прочесть
    I2C_SendData(AM_I2C, 4);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // генерируем сигнал стоп
    I2C_GenerateSTOP(AM_I2C, ENABLE);
 
    while (I2C_GetFlagStatus(AM_I2C, I2C_FLAG_BUSY));
 
    I2C_GenerateSTART(AM_I2C, ENABLE);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_MODE_SELECT));

    // переходим в режим чтения
    I2C_Send7bitAddress(AM_I2C, AM_ADDR, I2C_Direction_Receiver);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    // читаем наши байты
    // 1 байт адрес + 1 байт длинна + 4 байта запрошенные нами
    // то есть это 2 + то что вернется во 2 байте, но я упростил и сразу написал 6
    for (uint8_t i = 0; i < AM_LEN; ++i) {
        // ждем байт
        while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED));
        // и читаем его
        buf[i] = I2C_ReceiveData(AM_I2C);
    }

    // так же можно прочесть было еще 2 байта crc в массив
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED));
    temp->crc = I2C_ReceiveData(AM_I2C);
    while(!I2C_CheckEvent(AM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED));
    temp->crc |= I2C_ReceiveData(AM_I2C) << 8;
 
    I2C_GenerateSTOP(AM_I2C, ENABLE);
    I2C_AcknowledgeConfig(AM_I2C, DISABLE);

    // заполним нашу структуру
    temp->h = buf[2] << 8;
    temp->h |= buf[3];
    temp->t = buf[4] << 8;
    temp->t |= buf[5];

    // проверяем CRC
    return crc16(&buf[0], AM_LEN) == temp->crc;
}

Ну вот и все, правильней было бы читать данные в цикле думаю, да и процедуру старта чтения/записи вынести в отдельную функцию, но я не делаю библиотеку, а лишь хочу показать более наглядный алгоритм.

При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Подключение AM2320 по I2C и 1-Wire к STM32F103C8T6

Нет ни одного комментария. Будьте первым!