Tuesday, July 26, 2016

AM2320 library for AVR's without hardware TWI support

Hi! Some time ago I've bought several AM2320 sensors. They have I2C interface, so it is very easy to use them with AVR's that have h\w TWI support. But what about low-end MCU's without full TWI implementation (Tiny2313, Tiny24, etc.)?
First I tried to use USI module for TWI implementation. As for me resulting code was too "fat" for 2Kb FLASH and there was no need for full TWI feature support (the only TWI device in my project was AM2320).

After some time spent on datasheet reading and hardware testing I've wrote a C library for these sensors. Please note that AM2320_ReadData function writes raw data to variables. This notice is actual for negative temperature values, where 0x8000 bit is set.

AM2320 Datasheet


Header file:

/*
 * AM2320S.h
 *
 * Created: 23.05.2016 17:30:13
 *  Author: TSX
 */


#ifndef AM2320S_H_
#define AM2320S_H_

#include <avr/io.h>

#define AM2320_PORT PORTD
#define AM2320_DDR DDRD
#define AM2320_PIN PIND

#define AM2320_SDA_BIT 3
#define AM2320_SCL_BIT 2

#ifdef __cplusplus
extern "C" {
#endif

extern void AM2320_Init(void);
// Returns raw sensor data to variables. Be careful for negative temperature =)
extern uint8_t AM2320_ReadData(uint16_t * tempValue, uint16_t * humiValue);

#ifdef __cplusplus
}
#endif

#endif /* AM2320S_H_ */

Code file:

/*
 * AM2320S.c
 *
 * Created: 23.05.2016 17:29:50
 *  Author: TSX
 */

#include "AM2320S.h"
#include <util/delay.h>

/************************************************************************/
/* Port functions                                                       */
/************************************************************************/

#define SDA_LOW AM2320_DDR |= _BV(AM2320_SDA_BIT)
#define SDA_HIGH AM2320_DDR &= ~_BV(AM2320_SDA_BIT)
#define SCL_LOW AM2320_DDR |= _BV(AM2320_SCL_BIT)
#define SCL_HIGH AM2320_DDR &= ~_BV(AM2320_SCL_BIT)
#define SCL_IS_LOW (!(AM2320_PIN & _BV(AM2320_SCL_BIT)))

#define SCL_WAIT_HIGH while (!(AM2320_PIN & _BV(AM2320_SCL_BIT))) {}

static void _bus_delay(void) {
 _delay_us(50);
}

#ifdef USE_Q_DELAY
static void _bus_q_delay(void) {
 _delay_us(30);
}
#else
#define _bus_q_delay() _bus_delay()
#endif

static void SCL_SetHigh(void) {
 SCL_HIGH;
 _bus_delay();
}

static void SCL_SetLow(void) {
 SCL_LOW;
 _bus_delay();
}

/************************************************************************/
/* Bus functions                                                        */
/************************************************************************/

static void _bus_start(void) {
 SCL_HIGH; // prepare
 // I2C start condition
 SDA_LOW;
 _bus_delay();
 SCL_SetLow();
}

static void _bus_stop(void) {
 SDA_LOW;
 _bus_delay();
 SCL_HIGH;
 _bus_q_delay();
 SDA_HIGH;
 _bus_delay();
}

static uint8_t _bus_write(uint8_t data) {
 for (uint8_t i = 0; i<8; i++) {
  SCL_SetLow();

  if (data & 0x80) SDA_HIGH; // MSB first
  else SDA_LOW;
  _bus_delay();

  SCL_SetHigh();

  SCL_WAIT_HIGH;
  data <<= 1;
 }

 SCL_SetLow();
 SDA_HIGH;
 _bus_delay();

 // Byte sent, wait for slave ACK
 SCL_HIGH;
 SCL_WAIT_HIGH; // wait for slave ack
 _bus_delay();
 _Bool ack = !(AM2320_PIN & _BV(AM2320_SDA_BIT));
 SCL_SetLow();

 return (uint8_t)ack;
}

static uint8_t _bus_read(_Bool sendAck) {
 uint8_t data = 0;

 for (uint8_t i=0; i<8; i++) {
  SCL_SetLow();
  SCL_SetHigh();

  SCL_WAIT_HIGH;

  if (AM2320_PIN & _BV(AM2320_SDA_BIT)) data |= (0x80 >> i);
 }
 SCL_SetLow();

 // Generate ACK/NACK
 if (sendAck) SDA_LOW;
 else SDA_HIGH;
 _bus_delay();

 SCL_SetHigh();
 SCL_SetLow();

 SDA_HIGH;
 _bus_q_delay();

 return data;
}

static 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;
}

void AM2320_Init(void) {
 AM2320_DDR &= ~(_BV(AM2320_SCL_BIT)|_BV(AM2320_SDA_BIT));
 AM2320_PORT &= (_BV(AM2320_SCL_BIT)|_BV(AM2320_SDA_BIT));
}

uint8_t AM2320_ReadData(uint16_t * tempValue, uint16_t * humiValue) {
 uint8_t buffer[8];

 // Wake up
 _bus_start();
 _bus_write(0xB8);
 _delay_ms(1);
 _bus_stop();

 _bus_start();
 _bus_write(0xB8);
 _bus_write(0x03);
 _bus_write(0x00);
 _bus_write(0x04);
 _bus_stop();
 _delay_ms(1);

 _bus_start();
 _bus_write(0xB8 + 1);
 _delay_us(50);
 for (uint8_t i = 0; i<7; i++) {
  buffer[i] = _bus_read(1);
 }
 buffer[7] = _bus_read(0);
 _bus_stop();

 uint16_t Rcrc = ((uint16_t)buffer[7] << 8)+buffer[6];

 if (Rcrc == crc16(buffer, 6)) {
  *humiValue = ((uint16_t)buffer[2] << 8) + buffer[3];
  *tempValue = ((uint16_t)buffer[4] << 8) + buffer[5];
  return 1;
 }
 return 0;
}

1 comment: