MS51FB9AE – I2C

Thông tin chi tiết về giao thức I2C thì cũng đã có nhiều người viết nên mình không viết lại. Chi tiết về I2C thì có thể tham khảo tài liệu gốc của hãng NXP, có thể tìm trên Google với từ khóa UM10204 hoặc tải tại đây:
https://drive.google.com/open?id=14SH-jJxcOGfdsLl2CTiBnSoFv0SfYQzW

Bộ I2C của MS51FB9AE có thể tham khảo thêm tại tài liệu Technical Reference Manual của VĐK MS51FB9AE:
https://drive.google.com/open?id=11A-HH1Kc5Jxm_ThSLOcyx0pKHFh3d_lS

Lúc mới đọc mình cũng thấy hơi khó hiểu về cách sử dụng I2C của con MS51 này. Để đơn giản và mọi người có thể dùng được ngay mình đã viết sẵn I2C Driver của con MS51 này. Mọi người có thể copy về và dùng ngay:

file i2c.h

/* i2c.h */

#ifndef I2C_H_
#define I2C_H_

#include <MS51.h>
#ifdef __C51__
#include "myIntType.h"
#else
#include <stdint.h>
#endif

void I2C_Init(void);
uint8_t I2C_Write(uint8_t Address, uint8_t *pData, uint8_t length);
uint8_t I2C_Read(uint8_t Address, uint8_t *pData, uint8_t length);
uint8_t I2C_CheckAddress(uint8_t Address);

#endif

file i2c.c

/* i2c.c */

#include "i2c.h"

static uint8_t send_stop(void);

void I2C_Init(void)
{
	I2CLK = 39;
	/* P1.3 */
	/* Quasi */
	P1M1 &= ~(1 << 3);
	P1M2 &= ~(1 << 3);
	/* P1.4 */
	/* Quasi */
	P1M1 &= ~(1 << 4);
	P1M2 &= ~(1 << 4);
	
	P13=1;
	P14=1;
	
	I2CEN=1;
}

uint8_t I2C_Write(uint8_t Address, uint8_t *pData, uint8_t length)
{
	uint8_t i;
	uint16_t t;
	uint8_t u8TimeOut;
	
	if (I2STAT != 0xF8) {
		return 0;
	}
	
	/* start */
	STO = 0;
	STA = 1;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* start error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x08) {
		/* start error */
		send_stop();
		return 0;
	}
	
	/* send address */
	STA = 0;
	STO = 0;
	I2DAT = Address;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x18) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	/* send data */
	for (i = 0; i < length; ++i) {
		I2DAT = pData[i];
		SI = 0;
		t = 1;
		u8TimeOut = 0;
		while (1) {
			if (SI) {
				break;
			}
			if (!t) {
				u8TimeOut = 1;
				break;
			}
			++t;
		}
		if (u8TimeOut) {
			/* send data error */
			send_stop();
			return 0;
		}
		if (I2STAT != 0x28) {
			/* send data error */
			send_stop();
			return 0;
		}
	}
	
	/* stop */
	return send_stop();
}

uint8_t I2C_Read(uint8_t Address, uint8_t *pData, uint8_t length)
{
	uint8_t i;
	uint16_t t;
	uint8_t u8TimeOut;
	
	if (I2STAT != 0xF8) {
		return 0;
	}
	
	/* start */
	STO = 0;
	STA = 1;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* start error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x08) {
		/* start error */
		send_stop();
		return 0;
	}
	
	/* send address */
	STA = 0;
	STO = 0;
	I2DAT = Address;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x40) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	/* get data */
	for (i = 0; i < length - 1; ++i) {
		AA = 1;
		SI = 0;
		t = 1;
		u8TimeOut = 0;
		while (1) {
			if (SI) {
				break;
			}
			if (!t) {
				u8TimeOut = 1;
				break;
			}
			++t;
		}
		if (u8TimeOut) {
			/* send data error */
			send_stop();
			return 0;
		}
		if (I2STAT != 0x50) {
			/* send data error */
			send_stop();
			return 0;
		}
		pData[i] = I2DAT;
	}
	
	/* last byte */
	AA = 0;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	if (u8TimeOut) {
		/* send data error */
		send_stop();
		return 0;
	}
	if (I2STAT != 0x58) {
		/* send data error */
		send_stop();
		return 0;
	}
	pData[i] = I2DAT;
	/* stop */
	return send_stop();
}

uint8_t I2C_CheckAddress(uint8_t Address)
{
	uint16_t t;
	uint8_t u8TimeOut;
	
	Address |= 0x01;
	
	if (I2STAT != 0xF8) {
		return 0;
	}
	
	/* start */
	STO = 0;
	STA = 1;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* start error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x08) {
		/* start error */
		send_stop();
		return 0;
	}
	
	/* send address */
	STA = 0;
	STO = 0;
	I2DAT = Address;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	
	if (u8TimeOut) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	if (I2STAT != 0x40) {
		/* send address error */
		send_stop();
		return 0;
	}
	
	/* last byte */
	AA = 0;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (SI) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	if (u8TimeOut) {
		/* send data error */
		send_stop();
		return 0;
	}
	if (I2STAT != 0x58) {
		/* send data error */
		send_stop();
		return 0;
	}
	t = I2DAT;
	/* stop */
	return send_stop();
}

uint8_t send_stop(void)
{
	uint16_t t;
	uint8_t u8TimeOut;
	
	STA = 0;
	STO = 1;
	SI = 0;
	t = 1;
	u8TimeOut = 0;
	while (1) {
		if (I2STAT == 0xF8) {
			break;
		}
		if (!t) {
			u8TimeOut = 1;
			break;
		}
		++t;
	}
	return (!u8TimeOut);
}

driver có 4 hàm:
void I2C_Init(void);
Dùng để khởi tạo I2C của MS51 (trong thư viện hiện tại đang để tốc độ clock của i2c là 100KHz, có thể thay đổi bằng cách thay đổi giá trị I2CLK = 39; ngay dòng đầu tiên của hàm khởi tạo. Tốc độ clock được tính theo công thức f = 4000000 / ( I2CLK + 1); ở trên giá trị I2CLK là 39 nên tốc độ clock sẽ là 4000000 / 40 = 100000 = 100KHz.
uint8_t I2C_Write(uint8_t Address, uint8_t *pData, uint8_t length);
Dùng để ghi dữ liệu qua I2C
Address là địa chỉ I2C
pData là con trỏ tới dữ liệu cần ghi
length là số byte cần ghi.
Kết quả trả về 1 nếu ghi thành công và trả về 0 nếu gặp lỗi.
uint8_t I2C_Read(uint8_t Address, uint8_t *pData, uint8_t length);
Dùng để đọc dữ liệu qua I2C
Address là địa chỉ I2C
pData là con trỏ tới vùng nhớ để chứa giá trị đọc về
length là số byte cần đọc
Kết quả trả về 1 nếu thành công và 0 nếu gặp lỗi.
uint8_t I2C_CheckAddress(uint8_t Address);
Dùng để kiểm tra xem có tìm thấy thiết bị có địa chỉ Address trên bus I2C hay không
Address là địa chỉ của thiết bị muốn kiểm tra
Kết quả trả về 1 nếu tìm thấy thiết bị và trả về 0 nếu không tìm thấy thiết bị

Ví dụ sử dụng I2C để đọc, ghi dữ liệu của EEPROM AT24C32
Ghi giá trị 0x01, 0x04, 0x19, 0x86 vào từ địa chỉ 0x0000 của 24C32
Sau đó đọc lại 4 byte từ địa chỉ 0x0000 của 24C32 để kiểm tra.
Kết quả có thể xem bằng Logic Analyzer.

#include "ms51.h"
#include "i2c.h"
#include "delay.h"

uint8_t u8Address[2] = {0x00, 0x00};
uint8_t u8Write[6] = {0x00, 0x00, 0x01, 0x04, 0x19, 0x86};
uint8_t u8Read[4];

void main(void)
{
	P1M1 &= ~(1 << 5);
    P1M2 |= (1 << 5);
	P15 = 0;
	
	/* init */
	I2C_Init();
	Delay_Init();
	
	/* check address */
	if (!I2C_CheckAddress(0xA0)) {
		while (1) {
			P15 = 1;
			Delay_Ms(50);
			P15 = 0;
			Delay_Ms(50);
		}
	}
	
	Delay_Ms(500);
	
	/* write data */
	if (!I2C_Write(0xA0, u8Write, 6)) {
		while (1) {
			P15 = 1;
			Delay_Ms(100);
			P15 = 0;
			Delay_Ms(100);
		}
	}
	
	Delay_Ms(500);
	
	/* read data */
	if (!I2C_Write(0xA0, u8Address, 2)) {
		while (1) {
			P15 = 1;
			Delay_Ms(100);
			P15 = 0;
			Delay_Ms(100);
		}
	}
	if (!I2C_Read(0xA1, u8Read, 4)) {
		while (1) {
			P15 = 1;
			Delay_Ms(200);
			P15 = 0;
			Delay_Ms(200);
		}
	}
	
	Delay_Ms(500);
	
	while (1) {
		P15 = 1;
		Delay_Ms(500);
		P15 = 0;
		Delay_Ms(500);
	}
}

Kết quả khi đo tín hiệu I2C bằng Logic Analyzer:

Khi ghi dữ liệu vào 24C32

Khi đọc dữ liệu từ 24C32

Tốc độ i2c clock là 100KHz

Link project:
https://drive.google.com/open?id=1k2KAX1rtfL7DcDWxiEHulVt9P1joaQWp