河南驰诚电气股份有限公司 朱 斌 张 磊 怯肇乾
意法半导体STM(STMicroelectronics)的Cortex-M系列微控制器MCU(Micro Control Unit),性价比高,应用广泛。然而,其片内集成的硬件IIC总线(Inter-Integrated Circuit)接口运用,几乎是共认的“鸡肋”。此类MCU外接IIC设备,如数据随机存储器RAM(Ramdom Access Memory)、传感器Sensor、液晶显示模块LCM()等,很多工程师,宁可用通用输入输出端口GPIO(General Purpose Input Output)模拟实现IIC通信,也不用其片内集成的IIC接口。
选用片内IIC接口,即使采用STM提供的库驱动函数,程序卡死或跑飞,是“家常便饭”。GPIO模拟IIC通信,尽管容易实现,但它耗用MCU资源多,形成的IIC时序不规范,而且为保证IIC时序的完整性而在通信期间关闭系统中断又会严重影响嵌入式软件体系的整体调度性能。
GPIO模拟IIC“得不偿失”,闲置片内IIC“弃之可惜”。深入研究STM-MCU-IIC时序,借鉴GPIO模拟和STM库函数通信经验,充分运用并简化片内IIC驱动通信,势在必行。
选用片内IIC接口,采用STM-IIC驱动库函数或自编驱动,进行IIC总线主从通信,通常MCU为主,设备为从,很容易捕捉发现:发送数据没有问题,接收多个数据没有问题,读入1个数据,IIC总线停止。对于RAM等外设的访问,多读几个数据很容易回避读入1个数据时的“尴尬”,但必需读入1个字节的寄存器时,就不得不“面对”了。
图1下面给出的是示波器测量空气质量传感器CSS811的配置与状态回读时序,CSS811作为从机,地址为0x5A,对CSS811内部寄存器0x01地址写入0x38完成配置,之后对状态寄存器0x00回读1 字节,即:发送0x01、0x38完成配置,发送0x00启动状态读,之后读入1 字节。
图1说明,按1个字读,没有结果;按2个以上字节读,数据线拉低,致使IIC总线停止。
图1 片内IIC原始收发时序展示图Dig.1 Transmit & receive Timing Diagram for Primitive IIC on Chip
图2 STM-CortexMx-MCU-IIC主发送器传送序列及其说明图Dig.2 STM-CortexMx-MCU-IIC Master Sender Timing Diagram
图3 STM-CortexMx-MCU-IIC主接收器传送序列及其说明图Dig.3 STM-CortexMx-MCU-IIC Slaver Sender Timing Diagram
深入研究各类STM-CortexMx-MCU参考手册的IIC总线控制器,以7位地址主机收发为例,归纳IIC操作控制时序,如图2、3所示。
对比图2、3的传送序列和片内IIC驱动的现状,可以看到:IIC发送和多字节接收,各个操控时刻点和内容非常明确,所以很容易编程实现;而单字节接收的操控时刻点EV6_1极难把握,几乎没有办法及时发出清除位和产生停止位。在发出“从设备地址slvAddr+读r”后,产生停止位,就会收不到任何数据;在收好数据后产生停止位,就出现数据线拉低的IIC总线停止现象。
确定单字节接收的操控时刻点EV6_1,准确给出操控指令,是片内IIC驱动程序设计的关键。
针对单字节数据接收,先标记“发出从设备地址slvAddr+读r”,再在收到数据标识并且有“发出从设备地址slvAddr+读r”标识后“控制发送停止位并清除发出从设备地址slvAddr+读r标识,之后读入数据。跟踪试验,问题解决,并且不响应2个以上数据的接收。综合设计恰当的片内IIC驱动程序流程如图4所示,这里采用GPIO模拟IIC的中断关闭经验,在中断中进行IIC数据收发,并且设置IIC收发中断事件优先级仅次于系统调度时钟,以确保不因其它事件插入时间过长而破坏IIC操控时序的完整性。
图4 恰当的片内IIC驱动程序流程图Dig.4 Fitting Driver Flow Chart for IIC on Chip
驱动程序有4个部分启动准备、从机访问、数据接收和数据发送,前2部分是操控,后2部分是收发,字节流形式,每次进入仅完成1个部分操作,不同于传统的软件流。
主要实现程序代码如下,篇幅限制,略去初始化部分:
unsigned char slvAddr = 0x07; // 主机欲寻址的从机地址
unsigned char IIC2_Buf[Iic2BufSize]; // IIC2: 收发数据缓冲
int IIC2_DtaPrt;
char IIC2_MsgFlag,IIC2_EvtFlag = 0; // 接收标识: 公共信息[0-是/1-非],事件
short IIC2_DataCts; // 收发计数器
char IIC2_Mode; // 收发标识: 0--收,1--发
char IIC2_Read(unsigned int count) // IIC2数据接收(指定数量,返回标识公共信息标识)
{ IIC2_Mode = 0;IIC2_DtaPrt = 0;
IIC2_DataCts = count;
IIC2_MsgFlag = 0;IIC2_EvtFlag = 0;
IIC2_CR1 |= 1 << 8; // 启动IIC控制传输[主模式]
if(IIC2_DataCts>1) IIC2_CR1 |= 1 << 10; // 允许应答
while(IIC2_DataCts) ; // 等待数据接收完成(中断方式)
return IIC2_MsgFlag;
}
void IIC2_Write(unsigned int count) // IIC2数据发送(指定数据数量)
{ IIC2_Mode = 1;IIC2_DtaPrt = 0;
IIC2_DataCts = count;IIC2_EvtFlag = 0;
IIC2_CR1 |= 1 << 8; // 启动IIC控制传输[主模式]
IIC2_CR1 |= 1 << 10; // 允许应答
while(IIC2_DataCts) ; // 等待数据接收完成(中断方式)
IIC2_CR1 |= 1 << 9; // 停止IIC控制传输[主模式]
}
void Iic2Evt_Process(void) // IIC2数据收发处理
{ unsigned int iicSt = IIC2_SR2; // 获得IIC2工作状态
iicSt <<= 16;iicSt |= IIC2_SR1;
if((iicSt&0x00030001)==0x00030001) // 主机收发: 启动位已经发出,准备SLA+W/R
{ if(IIC2_Mode) IIC2_DR = slvAddr << 1; // 写W
else IIC2_DR = (slvAddr << 1) | 1; // 读R
}
if(!IIC2_Mode) // 主机接收
{ if((iicSt&0x00030002)==0x00030002) // 已经发出SLA+R,收到了ACK
{ if(IIC2_DataCts==1) // 只有一个字节,NACK
{ IIC2_CR1 &= ~(1 << 10);
IIC2_CR1 &= ~(1 << 9);
IIC2_EvtFlag |= 1;
}
}
else if((iicSt&0x00030040)==0x00030040) // 数据接收
{ if(IIC2_EvtFlag&1)
{ IIC2_CR1 |= 1 << 9;
IIC2_EvtFlag &= ~1;
}
IIC2_Buf[IIC2_DtaPrt++] = IIC2_DR;
IIC2_DataCts--;
if(IIC2_DataCts==1) // 最后字节,NACK
{ IIC2_CR1 &= ~(1 << 10);
IIC2_CR1 |= 1 << 9;
}
}}
if(IIC2_Mode) // 主机发送
{ if((iicSt&0x00070082)==0x00070082) // 已经发出SLA+W,收到了ACK
IIC2_EvtFlag |= 1;
else if((IIC2_EvtFlag&1)&& // 第一个数据发送
((iicSt&0x00070080)==0x00070080))
{ IIC2_DR = IIC2_Buf[IIC2_DtaPrt++];
IIC2_DataCts--;IIC2_EvtFlag &= ~1;
}
else if((iicSt&0x00070084)==0x00070084) // 数据已经传输,收到了ACK
{ if(IIC2_DataCts) // 继续发送数据
{ IIC2_DR = IIC2_Buf[IIC2_DtaPrt++];
IIC2_DataCts--;
}
}}
}
所有驱动代码,不过200行,相对GPIO模拟IIC和STM-IIC库函数,得到了最大的简化。
图5是示波器捕捉的前述空气质量传感器CSS811(从地址0x5A)配置后的状态查询过程时序,先发送指令0x00,再做1字节数据接收。
图5 空质传感器的1字节状态指令读取时序图Dig.5 1 Byte State-Receiving Timing Diagram for Air-Sensor
图6 是示波器捕捉的温湿度传感器CTH21(从地址0x40)的温度读取时序,发送指令0xE3后做3字节数据接收。
图6 温湿度传感器的3字节数据读取时序Dig.6 3 Bytes Data-Receiving Timing Diagram for Temperature-Humidity-Sensor
图7 是示波器捕捉的CTH21测量的电池供电时序状况,发送指令0xE7后做1字节数据接收。
结合图5~7和程序仿真跟踪,可以看出设计驱动程序,很好实现了各种数据的收发,无论单个数据还是多个数据读写。之后,运用到空质测控终端,从数个月的连续运行的结果看,新设计的片内IIC驱动,非常给力的。
图7 电池供电传感器的1字节数据读取时序Dig.1 1 Byte State-Receiving Timing Diagram for Bettery-Sensor
经过深入的查阅分析思考和反复的测量鉴定与跟踪仿真,找准了片内IIC接口驱动程序合适的操控点并及时发出了操控指令,在借鉴GPIO模拟操控的基础上,终于实现了STM-MCU-IIC硬件接口全面可靠高效的驱动,既提升了功能和性能,还充分简化了驱动设计。仔细观察IIC接收时序,无论单字节接收或多字节接收,无意中在最后都多发了一个字节的操作时钟,这个字节数据进入了接收寄存器,只是没有理会它罢了,是“得意”中的“败笔”,有待彻底去除,以求“完美”。