MM32L0系列包含一个硬件I2C接口,支持主从模式,能控制所有符合I2C总线协议的设备。I2C可以工作在标准模式(数据传输速率为0∼100 Kbps),快速模式(数据传输速率最大为400 Kbps)。
I2C 总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
当总线处于空闲状态时,SCL和SDA同时被外部上拉电阻拉为高电平。当主机启动数据传输时,必须先产生一个起始条件。在SCL线是高电平时,SDA线从高电平向低电平切换表示起始条件。当主机结束传输时要发送停止条件。在SCL线是高电平时,SDA线由低电平向高电平切换表示停止条件。下图显示了起始和停止条件的时序图。数据传输过程中,当SCL为1时,SDA必须保持稳定。
eMiniBoard板载一颗24C02,我们接下来一起进入I2C实战操作过程,通过MM32 MCU的硬件I2C接口来与24C02进行双向通信。
板级24C02的SCL和SDA分别连在MM32的PB6和PB7上,如下图所示:
图2 MM32与24C02连接图
I2C主机初始化
AMetal 平台提供了MCU的I2C初始化函数,可以直接调用初始化函数。在AMetal 中,由于用户无需关心读/写方向位的控制,因此其地址使用7-bit地址表示。
函数原型为:
am_i2c_handle_t am_mm32l073_i2c1_inst_init (void)
该函数在user_config目录下的am_hwconf_mm32l073_i2c.c文件中定义,在am_mm32l073_inst_init.h中声明。因此使用I2C初始化函数时,需要包含头文件 am_mm32l073_inst_init.h。
初始化I2C,调用该函数时需要定义一个am_i2c_handle_t 类型的变量,用于保存获取的I2C 服务句柄,初始化程序为:
am_i2c_handle_t i2c_handle
i2c_handle = am_mm32l073_i2c1_inst_init()
获取了I2C服务句柄后,还应该有一个描述I2C从设备的结构体,构造I2C设备函数的原型为:void am_i2c_mkdev ( am_i2c_device_t *p_dev,
am_i2c_handle_t handle,
uint16_t dev_addr,
uint16_t dev_flags)
p_dev 为指向 am_i2c_device_t 的结构体指针
handle 为 I2C 服务句柄
dev_addr 为从机设备地址
dev_flags 为传输过程中的控制标识位,其可用的值已在 am_i2c.h 中宏定义
I2C从机初始化
和MM32 MCU作为主机一样,AMetal平台也提供了作为I2C从机初始化函数, 可以直接调用初始化函数。函数原型为:
am_i2c_slv_handle_t am_mm32l073_i2c1_slv_inst_init (void)
该函数在user_config目录下的am_hwconf_mm32l073_i2c_slv.c文件中定义,在 am_mm32l073_inst_init.h中声明。因此使用定时器初始化函数时,需要包含头文件 am_mm32l073_inst_init.h。
初始化I2C,调用该函数时需要定义一个am_i2c_slv_device_t 类型的变量, 用于保存获取的I2C从机服务句柄,初始化程序为:
am_i2c_slv_handle_t slv_handle = am_mm32l073_i2c1_slv_inst_init ()
获取了I2C服务句柄后,还应该有一个描述I2C从设备的结构体,构造I2C设备函数的原型为:
void am_i2c_slv_mkdev (am_i2c_slv_device_t *p_dev,
am_i2c_slv_handle_t handle,
am_i2c_slv_cb_funcs_t *p_cb_funs,
uint16_t dev_addr,
uint16_t dev_flags,
void *p_arg)
p_dev 为指向从机设备描述结构体的指针
handle 是与从设备关联的 I2C 标准服务操作句柄
p_cb_funs 是回调函数的函数指针
dev_addr 为从机设备地址
dev_flags 为从机设备特性
p_arg 指向回调函数参数
其中 p_cb_funs 指向的回调函数结构体包括:
从机地址匹配时回调函数指针
获取一个发送字节回调函数指针
提交一个接收到的字节回调函数指针
停止传输回调函数指针
广播回调函数指针
回调函数得用户定义,如果不需要某个回调函数,可以不定义并将其函数指针指向NULL。
在AMetal中提供了实例初始化、读操作和写操作等函数接口,用户不用操作底层,直接按照规范调用相关函数即可,下面我们将结合EEPROM操作来熟悉IIC的函数接口调用。
EEPROM操
AMetal 提供I2C接口EEPROM的驱动函数,可以适配不同型号不同容量的产品,下面将以FM24C02为例予以说明,其函数原型(am_ep24cxx.h)为:
am_ep24cxx_handle_t am_ep24cxx_init (am_ep24cxx_dev_t *p_dev,
const am_ep24cxx_devinfo_t *p_devinfo,
am_i2c_handle_t i2c_handle);
该函数意在获取器件实例句柄 24c02_handle,其中p_dev为指向 am_ep24cxx_dev_t类型实例的指针,p_devinfo为指向 am_ep24cxx_devinfo_t 类型实例信息的指针。
01 实例
单个FM24C02可以看作EP24Cxx的一个实例,EP24Cxx只是抽象了代表一个系列或同种类型的E²PROM芯片,显然多个24C02是EP24Cxx的多个实例。如果I2C总线上只外接一个FM24C02,定义 am_ep24cxx_dev_t 类型(am_ep24cxx.h)实例如下:
am_ep24cxx_handle_t am_ep24cxx_init (am_ep24cxx_dev_t *p_dev,
const am_ep24cxx_devinfo_t *p_devinfo,
am_i2c_handle_t i2c_handle)
其中,g_AT24C02_dev为用户自定义的实例,其地址作为p_dev的实参传递。如果同一个 I2C 总线上外接了2个FM24C02,需要定义 2 个实例。即:am_ep24cxx_dev_t g_24c02_dev0
am_ep24cxx_dev_t g_24c02_dev1
每个实例都要初始化,其每个实例的初始化均会返回一个该实例的handle,便于使用其它接口函数时,传递不同的handle操作不同的实例。
01实例信息
实例信息主要描述了具体器件固有的信息,即 I2C 器件的从机地址和具体型号,其类型am_ep24cxx_devinfo_t 的定义(am_ep24cxx.h)如下:
typedef struct am_ep24cxx_devinfo {
uint8_t slv_addr;
uint32_t type;
} am_ep24cxx_devinfo_t;
当前已经支持的器件型号均在 am_ep24cxx.h 中定义了对应的宏, 比如, FM24C02 对应的宏为 AM_EP24CXX_FM24C2,实例信息定义如下:
const am_ep24cxx_devinfo_t _g_24c02_devinfo = {
0x50;
AM_EP24CXX_FM24C02
}
其中,g_24c02_devinfo为用户自定义的实例信息,其地址作为p_devinfo的实参传递。
02 I2C 句柄 I2C_handle
以I2C1为例, 其实例初始化函数 am_mm32l073_i2c1_inst_init()的返回值将作为实参传递给i2c_handle。即:
i2c_handle = am_mm32l073_i2c1_inst_init()
03实例句柄 fm24c02_handle
FM24C02初始化函数 am_ep24cxx_init()的返回值 fm24c02_handle,作为实参传递给读ARM 嵌入式软件工程方法和实践:
写数据函数,其类型 am_ep24cxx_handle_t(am_ep24cxx.h)定义如下:
typedef struct am_ep24cxx_dev *am_ep24cxx_handle_t
若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回一个有效的 handle。
基于模块化编程思想,将初始化相关的实例信息等的定义存放到对应的配置文件中,通过头文件引出实例初始化函数接口。
实例初始化函数范例程序:
#include "ametal.h"
#include "am_ep24cxx.h"
#include "am_mm32l073_inst_init.h"
static const am__ep24cxx_devinfo_t __g_24c02_devinfo = { }
static am_ep24cxx_dev_t __g_24c02_dev; //定义 FM24C02 器件实例
am_ep24cxx_handle_t am_fm24c02_inst_init(void)
{
am_i2c_handle_t i2c_handle =am_mm32l073_i2c1_inst_init();
return am_ep24cxx_init(&__g_24c02_dev, &__g_24c02_devinfo, i2c_handle);
}
实例初始化函数接口:
#pragma once
#include "ametal.h"
#include "am_ep24cxx.h"
am_ep24cxx_handle_t am_fm24c02_inst_init(void);
后续只需要使用无参数的实例初始化函数,即可获取到 FM24C02 的实例句柄。即:
am_ep24cxx_handle_t fm24c02_handle =am_fm24c02_inst_init();
注意, i2c_handle 用于区分 I2C0、 I2C1、 I2C2、 I2C3,初始化函数返回值实例句柄用于区分同一系统中连接多个器件。
表1 ep24cxx 读写函数
各 API 的返回值含义都是相同的:AM_OK 表示成功,负值表示失败,失败原因可根据具体的值查看 am_errno.h 文件中相对应的宏定义。正值的含义由各 API 自行定义,无特殊说明时,表明不会返回正值。
2
数据读写
01
写入数据
从指定的地址开始写入一段数据的函数原型为:
int am_ep24cxx_write (am_ep24cxx_handle_t handle,
int start_addr,
uint8_t *p_buf,
int len);
如果返回值为AM_OK,则说明写入成功,反之失败。假定从0x20地址开始,连续写入16字节。
写入数据范例程序:uint8_t data[16] = {0,1,2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15};
am_ep24cxx_write(fm24c02_handle, 0x20, &data[0],16);
02
读取数据
从指定的起始地址开始读取一段数据的函数原型为:
int am_ep24cxx_read (am_ep24cxx_handle_t handle,
int start_addr,
uint8_t *p_buf,
int len);
如果返回值为 AM_OK,则说明读取成功,反之失败。假定从 0x20 地址开始,连续读取数据范例程序为:
uint8_t data[16];
am_ep24cxx_read(fm24c02_handle, 0x20, &data[0],16);
03
应用实例
E2PROM 读写测试, 向存储器写入20个字节数据再读出来,然后校验是否读写正常的范例。
#include "ametal.h"
#include "am_LED.h"
#include "am_delay.h"
#include "am_mm32l073_inst_init.h"
#include "am_ep24cxx.h"
#include "am_hwconf_ep24cxx.h"
int app_test_ep24cxx(am_ep24cxx_handle_t handle)
{
int i;
uint8_t data[20];
for(i=0;i<20;i++) //填充数据
data=i;
am_ep24cxx_write(handle,0,&data[0],20); //从 0 地址开始,连续写入 20 字节数据16 for(i=0;i<20;i++) //清零数据
data=0;
am_ep24cxx_read(handle,0,&data[0],20); //从 0 地址开始,连续读出 20 字节数据19 for(i=0;i<20;i++)
{ //比较数据
IF(data!=i)
return AM_ERROR;
}
return AM_OK;
}
int am_main(void)
{
am_ep24cxx_handle_t fm24c02_handle =am_fm24c02_inst_init();//获取24C02初始化实例句柄
if(app_test_ep24cxx(fm24c02_handle)!=AM_OK)
{
am_led_on(0);
}
while(1)
{
am_led_toggle(0);//翻转 LED
am_mdelay(100);
}
}
app_test_ep24cxx()的参数为实例 handle,与EP24Cxx器件具有依赖关系,因为没办法实现全兼容调用,用户可根据EEPROM信息设置对应的参数。