引言
IIC总线协议,经常被应用于MCU与其他从设备通信。例如各种传感器:温度,光强,压力,加速度,速度,位置等等。应用非常广泛。其原因在于协议简单,连线只要两根。我们需要经常为各种从设备编写驱动程序,有硬件IIC外设那当然最好,如果没有或者有但IO口被占用了,这时我们就需要用GPIO口模拟协议时序。
协议
具体协议内容:start,stop,ack条件等等,就不再详细描述了。
具体协议规范:
IIC-bus specification and user manual.
https://www.nxp.com/docs/en/user-guide/UM10204.pdf
IIC-bus resource.
软件模拟新思路
很多的传统的模拟方式,按照协议时序顺序生成 开始条件 传输字节 应答 结束条件,其中时间由delayus(5--30)来控制,而delayus几乎都是阻塞CPU,让CPU不停的查询是否到时。就浪费了一定的CPU时钟,虽然传统的模拟方式能够正常工作。当CPU频率 比较低的时候,传统的模拟方式反而更高效,并不浪费CPU时钟。但是当CPU频率 比较高的时候,几个微秒CPU可以做很多条指令。STM32F429性能225DMIPS<1125 I/5uS>。STM32H743性能856DMIPS<4280 I/5uS>。为了CPU更高效的运作,用硬件定时器中断 重新设计了IIC软件模拟方案。
软件IIC组件 方案思路
用软件对象 模拟 硬件外设,每个软件对象 内嵌状态机,用于控制每一位的生成。每个软件对象具有自己独有的SDL和SCL引脚 和这两个引脚的控制函数,以及完成回调等等。我们可以创建多个IIC软件对象,每个对象 就代表 一个 IIC硬件外设,就可以控制很多条IIC总线。多个软件对象 串连成一个链表。硬件定时器中断 扫描链表。更新 每个对象的IO操作。只要CPU足够强悍,可以添加很多个IIC软件对象。当然也需要足够的GPIO。
仓库
这里介绍一个开源的软件模拟IIC组件 softiic v1.0,非阻塞式,中断处理方式。
废话不多说,仓库地址如下:
G-ATLAS/softiic: IIC, software IIC driver module, only master, non-blocking (github.com)
如何使用
1.移植该IIC组件,如后续所述。
2.使用前 先初始化该IIC组件: 调用 siic_init();
3.主机 写 从设备:调用 例如 siic_device_write_it(&hsiic1,&data[0],3);
4.主机 读 从设备:调用 例如 siic_device_read_it(&hsiic1,&data[0],4);
5.修改 传输完成回调 根据工程需要。
使用实例如下:非常简单
void StartTask02(void *argument)
{
uint8_t data[5];
///init this module
siic_init();
///read
data[0] = 0x70u; ///slave add+write
data[1] = 0x02u; ///register
data[2] = 0x71u; ///slave add+read
data[3] = 0x00u; ///the read data will be here
data[4] = 0x00u;
siic_device_read_it(&hsiic1,&data[0],4);
hsiic1_wait_cplt(100);
///write
data[0] = 0x70u; ///slave add+write
data[1] = 0x80u; ///register
data[2] = 22u; ///value to be written
siic_device_write_it(&hsiic1,&data[0],3);
hsiic1_wait_cplt(100);
for(;;)
{
osDelay(100);
}
}
组件特点
1.纯C语言构建
2.非阻塞式软件IIC设备
3.无限扩展虚拟化软件IIC设备,如果有充足的GPIO和CPU速度
4.通信速度可调,如果有充足的CPU速度
5.极为简单的API接口
6.自动生成START,STOP,ACK,NACK,STARTr条件
7. 只支持7位地址,和几个主机常用命令模式
8. 不支持时钟同步
9.不支持总线仲裁
10.不支持时钟拉扯
11.需要硬件定时器节拍
组件功能
- 主机写从设备
-<主机写从设备>
/**
* @brief
* @note pdata -pdata[0]=<Slave address+write> ,pdata[1]=<register offset or command> ,pdata[2--n]=the data to be written.
* @param hsiicdevice -a pointer to the structure which contains information about siic.
* @param pdata -the data pointer.
* @param size -the size of data<include pdata[0]pdata[1]>. in byte. should be >=2.
* @retval SIIC_Status_TypeDef
*/
SIIC_Status_TypeDef siic_device_write_it(SIIC_Device_TypeDef *hsiicdevice,uint8_t *pdata,uint16_t size)
- 主机读从设备
-<主机读从设备>
/**
* @brief
* @note pdata -pdata[0]=<Slave address+write> ,pdata[1]=<register offset or command> ,
pdata[2]=<Slave address+read> ,pdata[3--n]= the data to be read.
* @param hsiicdevice -a pointer to the structure which contains information about siic.
* @param pdata -the data pointer.
* @param size -the size of data<include pdata[0]pdata[1]pdata[2]>. in byte.should be >=3.
* @retval SIIC_Status_TypeDef
*/
SIIC_Status_TypeDef siic_device_read_it(SIIC_Device_TypeDef *hsiicdevice,uint8_t *pdata,uint16_t size)
- 主机写从设备+不带Stop条件
/**
* @brief the same as func<siic_device_write_it>,just without the stop condition.
* @note pdata -pdata[0]=<Slave address+write> ,pdata[1]=<register offset or command> ,pdata[2--n]=the data to be written.
* @param hsiicdevice -a pointer to the structure which contains information about siic.
* @param pdata -the data pointer.
* @param size -the size of data<include pdata[0]pdata[1]>. in byte. should be >=2.
* @retval SIIC_Status_TypeDef
*/
SIIC_Status_TypeDef siic_device_write_nostop_it(SIIC_Device_TypeDef *hsiicdevice,uint8_t *pdata,uint16_t size)
- 主机读从设备+不指定寄存器偏移
-<主机读从设备+不指定寄存器偏移>
/**
* @brief the same as func<siic_device_read_it>,just without indicate the offset of the internal register.
* @note pdata -pdata[0]=<Slave address+read> ,pdata[1--n]= the data to be read.
* @param hsiicdevice -a pointer to the structure which contains information about siic.
* @param pdata -the data pointer.
* @param size -the size of data<include pdata[0]>. in byte.should be >=2.
* @retval SIIC_Status_TypeDef
*/
SIIC_Status_TypeDef siic_device_read_random_it(SIIC_Device_TypeDef *hsiicdevice,uint8_t *pdata,uint16_t size)
- 更多
-<软件IIC组件初始化>
void siic_init(void)
移植说明
1- 把目光转移到xx_port.c文件中,为软件IIC准备 硬件定时器 相关接口函数。
void siic_tick_init(void)-初始化硬件定时器,使用其 更新中断。配置中断优先级等等。中断频率5-50uS为宜,根据平台性能。
void siic_tick_start(void) -启动硬件定时器。可做重复启动优化,见源文件。
void siic_tick_stop(void) -停止硬件定时器。可做重复停止优化,见源文件。
siic_tick_handler(); -在 定时器更新中断 的服务程序中调用,原则尽量快。
2-为 某个软件IIC设备<siic1> 准备 GPIO 相关接口函数。
void siic1_api_init(void) -初始化SDL,SCL相关io口,注意默认高电平,OD输出,上拉。
void siic1_api_sdlin(void) -切换SDL方向为输入。
void siic1_api_sdlout(void) -切换SDL方向为输出。
void siic1_api_sdlset(uint8_t iostate) -设置SDL输出电平。
void siic1_api_sclset(uint8_t iostate) -设置SCL输出电平 。
uint8_t siic1_api_sdlread(void) -读入SDL输入电平。
3-为 更多软件IIC设备<siic2.3.4> 准备 GPIO 相关接口函数。如果需要。
void siic2_api_init(void) -初始化SDL,SCL相关io口,注意默认高电平,OD输出,上拉。
void siic2_api_sdlin(void) -切换SDL方向为输入。
void siic2_api_sdlout(void) -切换SDL方向为输出。
void siic2_api_sdlset(uint8_t iostate) -设置SDL输出电平。
void siic2_api_sclset(uint8_t iostate) -设置SCL输出电平 。
uint8_t siic2_api_sdlread(void) -读入SDL输入电平。
4-实现软件IIC组件 初始化函数。如下
SIIC_Device_TypeDef hsiic1;
///SIIC_Device_TypeDef hsiic2;and more device
void siic_init(void)
{
/* tick init */
siic_tick_init();
/* hsiic1 init */
hsiic1.API.init =siic1_api_init;
hsiic1.API.sdlin =siic1_api_sdlin;
hsiic1.API.sdlout =siic1_api_sdlout;
hsiic1.API.sclset =siic1_api_sclset;
hsiic1.API.sdlset =siic1_api_sdlset;
hsiic1.API.sdlread =siic1_api_sdlread;
siic_device_init(&hsiic1);
/* hsiic2 and more init */
/* blank statement */
/* register hsiic1's callbcak */
siic_callback_register(&hsiic1,SIIC_XFER_CPLT_CB_ID,hsiic1_CpltCallback);
siic_callback_register(&hsiic1,SIIC_XFER_ERROR_CB_ID,hsiic1_ErrorCallback);
/* register hsiic2's callbcak */
/* blank statement */
/* register siic device */
siic_device_register(&hsiic1);
///siic_device_register(&hsiic2); and more device
}
void hsiic1_CpltCallback(SIIC_Device_TypeDef *hSIIC)
{
///osSemaphoreRelease(myBinarySem01Handle);
}
void hsiic1_wait_cplt(uint16_t timeout)
{
///osSemaphoreAcquire(myBinarySem01Handle,timeout);
}
void hsiic1_ErrorCallback(SIIC_Device_TypeDef *hSIIC)
{
}
5-注意!!!
本组件测试平台STM32F429IGT6 180Mhz,性能 225DMIPS, <225 I/uS><1125 I/5uS>
本硬件定时器中断周期的 设置为5uS。 一个位的传输需要3个tick<15uS>
!!!!!!!!!!!!!!
不同平台性能不同,你必须确保 <从 中断发生 到 扫描完一遍siic_tick_handler(); >
所用时间 < 硬件定时器中断周期。
总的来说 性能越好,中断周期可以更短。性能越差,中断周期必须更长。
!!!!!!!!!!!!!!
6-注意!!!
建议CPU>32Mhz的平台移植,频率过低 从 这种实现的软件IIC组件 得不到任何好处。
虽然也可以工作,通信速度变慢,效率会降低。由于过多的中断开销,还不如传统的
CPU阻塞式软件模拟方案。
组件设计意义
非阻塞式工作方案,尽可能的节约CPU的时钟。
更适合有RTOS的软件结构。
更适合具有高频率的CPU的平台。
结语
通过这个IIC组件,可以创建很多个 IIC软件对象 虚拟化 多个IIC硬件外设。虽然要适当降低通信速率,但是以后操作 IIC设备 就像 操作 串口一样简单。屏蔽了协议的复杂性,抽出简单的通信接口,就像串口通信一样。根本就不用考虑 时序问题,简化了软件设计。