Atmel Tiny88 - software I2C master

 Software I2C (master)

Microchip Tiny-88 只有一組 TWI master/slave 功能,在系統設計中,Tiny-88 一般做為電源管理,系統開關機,還有和系統主晶片組溝通。Tiny-88 和主晶片組溝通的介面會採用 TWI slave,當還需要再讀取系統溫度,系統電流,系統生產資料或者系統電池時,還需要一組 I2C master 去讀取系統的資料,只能使用 GPIO 來模擬 software I2C.   


Software I2C header

宣告程式碼中,需要用到的變數和子函式,使用宣告的好處是日後若要更換成其他組的 GPIO 容易更換,也不容易有遺漏。
  • 宣告 GPIO PORT 和接腳位置去模擬 software I2C
  • 宣告 I2C 寛度的時間設定值
  • 宣告 GPIO High / Low / Input 子函式
/* Constant Variable Define */
#define SW_I2C_DDR			DDRD
#define SW_I2C_DIN			PIND
#define SW_I2C_DOUT			PORTD

#define SW_I2C_SDA			6
#define SW_I2C_SCL			7

/** Bit to or with address for read start and read restart */
#define I2C_READ			1

/** Bit to or with address for write start and write restart */
#define I2C_WRITE			0

/** Delay used for software I2C */
#define I2C_DELAY_USEC		10
#define I2C_DELAY_CLKH		6	/* I2C Clock High Width         */
/* I2C Clock Low Width (by instruction run time)        */
#define I2C_DELAY_CLKL		0
/* I2C Clock Low Width for finial bit   */
#define I2C_DELAY_CLKFL		4
#define I2C_DELAY_DATBR		3	/* I2C Data Delay to before read */
#define I2C_DELAY_DATAR		2	/* I2C Data Delay to after read */

/* Declare Function */
#define SET_I2C_SCL_HI()	(SW_I2C_DOUT |= (1 &lt< SW_I2C_SCL))
#define SET_I2C_SCL_LO()	(SW_I2C_DOUT &= ~(1 &lt< SW_I2C_SCL))
#define SET_I2C_SDA_HI()	(SW_I2C_DOUT |= (1 &lt< SW_I2C_SDA))
#define SET_I2C_SDA_LO()	(SW_I2C_DOUT &= ~(1 &lt< SW_I2C_SDA))
#define SET_I2C_SDA_OUT()	(SW_I2C_DDR |= (1 &lt< SW_I2C_SDA))
#define SET_I2C_SDA_IN()	(SW_I2C_DDR &= ~(1 &lt< SW_I2C_SDA))
#define GET_I2C_SDA_IN()	(SW_I2C_DIN & (1 &lt< SW_I2C_SDA))

Software I2C code
I2C protocol 分為 start-bit,stop-bit,repeat start-bit,I2C-read,I2C-write 的子函式來組合成主函式。因此,在編寫程式碼前,建議先使用流程圖規劃一下,再開始編寫程式碼,先完成子函式,再組成主函式。
  • SW_I2C_start(address): 
子函式主要是用在一開始傳送 start-bit 和 slave address,也用於 read protocol 時的 repeat start-bit 只是傳遞的 8-bit 位址的 bit 0 要改為 read.
/* ! \brief  SW I2C issue a start condition
 *		This function use to send start condition of SW I2C
 * \note
 *		(input) slave device address
 * \return
 *		1 : the address has ACK
 *		0 : the address without ACK
 */
uint8_t SW_I2C_start(uint8_t addressRW)
{
	uint8_t result;

	SET_I2C_SDA_LO();	/* set data to low */
	_delay_us(I2C_DELAY_USEC);
	SET_I2C_SCL_LO();	/* set clock to low */
	_delay_us(I2C_DELAY_CLKFL);
	result = SW_I2C_write(addressRW);
	return result;
}
  • SW_I2C_restart(address) : 
子函式用於 read protocol 的 repeat start bit ,需記得更改 slave address 為 read。  
/* ! \brief  SW I2C issue a re-start condition
 *		This function use to send re-start condition of SW I2C
 * \note
 *		(input) slave device address
 * \return
 *		1 : the address has ACK
 *		0 : the address without ACK
 */
uint8_t SW_I2C_restart(uint8_t addressRW)
{
	SET_I2C_SDA_HI();
	SET_I2C_SCL_HI();
	_delay_us(I2C_DELAY_USEC);
	return SW_I2C_start(addressRW);
}
  • SW_I2C_stop(void): 
子函式 stop-bit 結束 I2C packet
/* ! \brief  SW I2C issue a stop condition
 *		This function use to send stop condition of SW I2C
 * \note
 *		(input) slave device address
 * \return
 *		1 : the address has ACK
 *		0 : the address without ACK
 */
void SW_I2C_stop(void)
{
	SET_I2C_SDA_LO();	/* set data to low */
	_delay_us(I2C_DELAY_USEC);
	SET_I2C_SCL_HI();
	_delay_us(I2C_DELAY_USEC);
	SET_I2C_SDA_HI();
	_delay_us(I2C_DELAY_USEC);
}
  • SW_I2C_read(last): 
子函式讀取一個 byte 和回應 ack /nack 給 host
/* ! \brief  SW_I2C_read
 *		This function use to read a byte and send ack/nack to device
 * \note
 *		decide the terminate bit is ACK or NACK.
 * \return
 *		data byte of I2C read
 */
uint8_t SW_I2C_read(uint8_t last)
{
	uint8_t b = 0;
	/* make sure pull-up enabled  */
	SET_I2C_SDA_HI();
	SET_I2C_SDA_IN();	/* change to input mode */
	/* read byte */
	for (uint8_t i = 0; i < 8; i++) {
		/* do not change this loop unless you verify the change with a
		 * scope   */
		_delay_us(I2C_DELAY_CLKFL);
		b &lt<= 1;
		SET_I2C_SCL_HI();	/* set clock to high */
		_delay_us(I2C_DELAY_DATBR);
		if (GET_I2C_SDA_IN())
			b |= 1;	/* read bit data */
		_delay_us(I2C_DELAY_DATAR);
		SET_I2C_SCL_LO();	/* set clock to low */
	}
	/* send Ack or Nak */
	SET_I2C_SDA_OUT();
	if (last)
		SET_I2C_SDA_HI();
	else
		SET_I2C_SDA_LO();

	SET_I2C_SCL_HI();	/* set clock to high */
	_delay_us(I2C_DELAY_CLKH);
	SET_I2C_SCL_LO();	/* set clock to low */
	SET_I2C_SDA_LO();

	return b;
}
  • SW_I2C_write(data): 
子函式寫入一個 byte 並讀回 slave 的 ack/nack 
/* ! \brief  SW_I2C_write
 *		This function use to write a byte to device
 * \note
 *		(input) data byte
 * \return
 *		1 : slave return ACK
 *		0 : slave return NACK
 */
uint8_t SW_I2C_write(uint8_t data)
{
	uint8_t rtn;

	/* write byte */
	for (uint8_t m = 0x80; m != 0; m >>= 1) {
		/* don't change this loop unless you verify the change with a
		 * scope */
		if (m & data)
			SET_I2C_SDA_HI();
		else
			SET_I2C_SDA_LO();

		SET_I2C_SCL_HI();	/* set clock to high */
		_delay_us(I2C_DELAY_CLKH);
		SET_I2C_SCL_LO();	/* set clock to low */
		_delay_us(I2C_DELAY_CLKL);
	}
	_delay_us(I2C_DELAY_CLKFL);
	SET_I2C_SCL_HI();
	_delay_us(I2C_DELAY_DATBR);
	/* get Ack or Nak */
	SET_I2C_SDA_IN();	/* change to input mode */
	rtn = GET_I2C_SDA_IN();
	_delay_us(I2C_DELAY_DATAR);
	SET_I2C_SCL_LO();	/* set clock to low */
	_delay_us(I2C_DELAY_CLKFL);
	SET_I2C_SDA_OUT();
	SET_I2C_SDA_LO();	/* set data to low */
	return rtn == 0;
}
  • SW_I2C_buffer_read:
主函式完成 I2C read byte 功能,包含 start-bit,I2C-slave address,I2C-command,repeat start-bitaddressI2C-data buffer,stop-bit 完整的 I2C read protocol. 
/* ! \brief  SW I2C read protocol
 *		This function use to SW I2C read protocol
 * \note
 *		(input) address-slave device address
 *		(input) cmd-read command
 *		(input) buf-read protocol buffer
 *		(input) count-read protocol byte count
 * \return
 *		1 : success of read protocol
 *		0 : fail of read protocol
 */
uint8_t SW_I2C_buffer_read(uint8_t address, uint8_t cmd, uint8_t *buf,
			   uint8_t count)
{
	cli();
	/* issue a start condition, send device address and write direction
	 * bit */
	if (!SW_I2C_start(address | I2C_WRITE)) {
		SW_I2C_stop();
		sei();
		return FALSE;
	}
	/* send the DS1307 address */
	if (!SW_I2C_write(cmd)) {
		SW_I2C_stop();
		sei();
		return FALSE;
	}
	/* issue a repeated start condition, send device address and read
	 * direction bit */
	if (!SW_I2C_restart(address | I2C_READ)) {
		SW_I2C_stop();
		sei();
		return FALSE;
	}
	/* read data from the DS1307 */
	for (uint8_t i = 0; i < count; i++) {
		/* send Ack until last byte then send Ack */
		buf[i] = SW_I2C_read(i == (count - 1));
	}

	/* issue a stop condition */
	SW_I2C_stop();
	sei();
	return TRUE;
}
  • SW_I2C_buffer_write:
主函式完成 I2C read write 功能,包含 start-bit,I2C-slave address,I2C-commandI2C-data buffer,stop-bit 完整的 I2C write protocol.
/* ! \brief  SW I2C write protocol
 *		This function use to SW I2C write protocol
 * \note
 *		(input) address-slave device address
 *		(input) cmd-write command
 *		(input) buf-write protocol buffer
 *		(input) count-write protocol byte count
 * \return
 *		1 : success of write protocol
 *		0 : fail of write protocol
 */
uint8_t SW_I2C_buffer_write(uint8_t address, uint8_t cmd, uint8_t *buf,
			    uint8_t count)
{
	cli();
	/* issue a start condition, send device address and write direction
	 * bit */
	if (!SW_I2C_start(address | I2C_WRITE)) {
		SW_I2C_stop();
		sei();
		return FALSE;
	}
	/* send the DS1307 address */
	if (!SW_I2C_write(cmd)) {
		SW_I2C_stop();
		sei();
		return FALSE;
	}
	/* send data to the DS1307 */
	for (uint8_t i = 0; i < count; i++) {
		if (!SW_I2C_write(buf[i])) {
			SW_I2C_stop();
			sei();
			return FALSE;
		}
	}

	/* issue a stop condition */
	SW_I2C_stop();
	sei();
	return TRUE;
}

留言

這個網誌中的熱門文章

EC 所需知識 - SMBUS

EC 所需知識 - KBC

EC 所需知識 - LPC