iic
(Inter-Integrated Circuit Bus)内部集成电路总线
用处:常用于微控制器与外设之间的通信。
1、硬件接法:
如下图所示,主控芯片引出两条线SCL,SDA线,在一条I2C总线上可以接很多I2C设备。数据可以从主设备传到从设备上,从设备也能传数据到主设备上,即双向传输。
常用设备:eeprom, pca9555(串转并), RTC实时时钟
注意:iic本身没有拉高的能力,需要接外接电阻才能拉高。
2、收发信号:(主机写从机读)
1、主设备发start
刚开始主芯片要发出一个start信号,然后发出一个设备地址(用来确定是往哪一个芯片写数据),读/写(0表示写,1表示读)。
回应(用来确定这个设备是否存在),然后就可以传输数据,传输数据之后,要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
每传输一个数据,接受方都会有一个回应信号,数据发送完之后,主芯片就会发送一个停止信号。如下图:白色为主设备到从设备传输,灰色为从设备传输到主设备。
传输是以8位为单元数据传输的,先传输最高位(MSB),主芯片发出start信号之后,然后发出9个时钟传输数据。(初始化后SDA和SCL线都处于高电平状态)
(1)开始信号(S):SCL为高电平时,SDA高电平向低电平跳变,开始传送数据。
(2)结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
(3)响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA。(第九个时钟周期的SDA电平信号由接收方决定)
SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上的数据只能在SCL为低电平期间变化。如图:
I2C通常应该在时钟的高电平而不是上升沿或者下降沿读取数据,IC抽样判决的时候只要求在时钟上升沿阶段SDA要能够稳定的保持一段时间。
I2C要求SDA在SCL为高电平不能跳变,是因为SCL为高电平时,会触发I2C的起始条件和停止条件。
其实I2C的采样也是边沿采样,只不过是上下沿都采一次,这样做的目的是为了检测起始和终止信号,I2C规定:SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
因此,必须保证在每个时钟周期内对数据线SDA采样两次。
3、响应ACK和非响应NACK(Not Acknowledge)
每个字节传输必须带响应位,相关的响应时钟也由主机产生,在响应的时钟脉冲期间(第9个时钟周期),
发送端释放SDA线,接收端把SDA拉低。
以下情况会导致出现NACK位:
1.接收机没有发送机响应的地址,接收端没有任何ACK发送给发射机
2.由于接收机正在忙碌处理实时程序导致接无法接收或者发送
3.传输过程中,接收机识别不了发送机的数据或命令
4.接收机无法接收
5.主机接收完成读取数据后,要发送NACK结束告知从机
4、主机读,从机写:
读数据与写数据相似,但读数据会多几个步骤。要想从从机读取数据,首先要知道从机地址以及寄存器地址,这两部需要进行读操作来实现,和读操作一致。读操作完成后,主机发送重复开始+从机地址+R/W=1(读操作,设置为1),从机返回ACK,此时主机释放SDA线转由从机控制,主机读取SDA总线进行数据接收,每发送1 byte数据,主机会响应ACK表示还需要再接收数据。当主机接收完想要的数据后,主机将会返回NACK,告诉从机释放SDA总线,随后主机发送STOP命令,将总线释放,完成读操作。如下图示意:
主机:start+从机地址+写(0)0xae
从机:ack
主机:寄存器地址 //有可能要两次,看eeprom大小
从机:ack
主机:restart+从机地址+读(1,指运行从机写)0xaf
从机:ack + 发送8bit数据
主机:NACK+stop
实际读取过程中,使用以上步骤,可能存在以下问题:
操作:主机设在接收N之后,再设置ack = 0。
实际情况:再接N后,ACK还没设置成功,ack=1已经发出去,且上一个发 出去的ack标志也是1,再设nack之后,此时DR中的数据为N+1,移位寄存器 中的数据为N+2.
结论:
经过测试不影响最终传输的准确性(数据正确)和完整性(等的到busy=0,且下一轮收发正常)
最早设ack的时机:可以再接收到N-2之后(即count_remain=3,接收完成后),直接设ack为0.
最早设stop的时机:等btf=1,设置stop_flag=1,DT=N-1。(此时N-1在DT中,N在移位寄存器中,所以不存在少收的情况)
总结:
主从机制。SCL始终由主设备控制。SDA谁用谁控制(单双共,串行)。
带校验,低速率,可靠。
默认高电平。
正常情况:高电平稳定,低电平采样。(可以看做采两次)
(不正常的高电平跳变正好作为开始和结束信号)
开始:SCL高电平,SDA拉低。
接收:SCL高电平,SDA拉高。
所谓低电平采样:SCL低电平期间,对SDA的电平进行采样。
SCM下驱动
创建:
iic时钟使能
SCL,SDA对应GPIO时钟使能
对应DMA时钟使能
配置iic工作模式
中断模式下配置中断使能(event中断和err中断)
Iic工作模式:
priv->param.clk_speed = MS_I2C_CLK_SPEED_STANDARD;
priv->param.duty_cycle = MS_I2C_DUTY_CYCLE_2;
priv->param.own_address1 = 0U;
priv->param.addressing_mode = MS_I2C_ADDRESSING_MODE_7B;
priv->param.dual_address_mode = MS_I2C_DUAL_ADDRESS_DISABLE;
priv->param.own_address2 = 0U;
速度100K,400K,1M,3.4M
快速模式下的CLK低高电平时间占比:2/1;16/9。(低电平周期/高电平周期)
自身地址寄存器:一般不用
地址模式:7位或10位。
双字节寻址模式:10位时,需要使用。
传输流程以中断模式为例:
static ms_ssize_t __scm_i2c_bus_trans_it(privinfo_t *priv, const ms_i2c_msg_t *msg, ms_size_t n_msg)
{
ms_ssize_t i = 0;
ms_err_t err = 0;
if (priv->sembid == 0U) {
err = ms_semb_create("i2c_semb", MS_FALSE, MS_WAIT_TYPE_PRIO, &priv->sembid);
if(err != MS_ERR_NONE) {
ms_printk(MS_PK_ERR, "%s, %d, ms_semb_create error: %d\n", __FILE__, __LINE__, err);
return i;
}
}
for (i = 0; i < n_msg; i++, msg++) {
priv->msg = msg;
priv->err_code = 0U;
priv->pbuffer = msg->buf;
priv->count_remain = msg->len;
if (!(priv->msg->flags & MS_I2C_M_NOSTART)) {
I2C_GenerateSTART((I2C_Type *)(priv->base), ENABLE);
err = __scm_i2c_check_event_wait((I2C_Type *)(priv->base), I2C_EVENT_MASTER_START_GENERATED, SUCCESS);
if (err != MS_ERR_NONE) {
ms_printk(MS_PK_ERR, "%s %d : check event wait timeout!\r\n", __func__, __LINE__);
break;
}
}
if (msg->flags & MS_I2C_M_READ) {
I2C_PECPositionConfig((I2C_Type *)(priv->base), I2C_PECPosition_Current);
if (msg->len > 1) {
I2C_AcknowledgeConfig((I2C_Type *)(priv->base), ENABLE);
} else {
I2C_AcknowledgeConfig((I2C_Type *)(priv->base), DISABLE);
}
}
if (I2C_GetFlagStatus((I2C_Type *)(priv->base), I2C_FLAG_STARTF) == RESET) {
I2C_Cmd((I2C_Type *)(priv->base), ENABLE);
}
I2C_INTConfig((I2C_Type *)(priv->base), I2C_INT_BUF | I2C_INT_EVT | I2C_INT_ERR, ENABLE);
if (ms_semb_wait(priv->sembid, MS_TICK_PER_SEC) != MS_ERR_NONE) {
ms_printk(MS_PK_DEBUG, "%s, %d, wait semb fail\n", __FILE__, __LINE__);
break;
}
if (!(priv->msg->flags & MS_I2C_M_NOSTOP)) {
/*
* wait until BUSY clear
*/
while (I2C_GetFlagStatus((I2C_Type *)(priv->base), I2C_FLAG_BUSYF));
}
}
return i;
}
event中断的处理
void scm_i2c_event_irqhandle(ms_uint8_t channel)
{
privinfo_t *priv = __scm_i2c_bus[channel - 1].ctx;
I2C_Type *instance = (I2C_Type *)(priv->base);
ms_uint16_t sts1 = instance->STS1;
ms_uint16_t ctl2 = instance->CTRL2;
/*
* handle start flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_STARTF) != RESET))
{
__scm_handle_start_flag(priv);
return;
}
/*
* handle addr10 flag, Must use sts1 and ctl2 read when the interrupt is first entered
*/
if ((GET_STS1_ADDR10F(sts1) == SET) && (GET_CTL2_EVT(ctl2) == SET)) {
__scm_handle_addr10_flag(priv);
return;
}
/*
* handle addr flag, Must use sts1 and ctl2 read when the interrupt is first entered
*/
if ((GET_STS1_ADDRF(sts1) == SET) && (GET_CTL2_EVT(ctl2) == SET)) {
__scm_handle_addr_flag(priv);
return;
}
/*
* handle tde flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_TDE) != RESET))
{
__scm_handle_tde_flag(priv);
return;
}
/*
* handle rdne flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_RDNE) != RESET))
{
__scm_handle_rdne_flag(priv);
return;
}
}
err中断的处理
void scm_i2c_error_irqhandle(ms_uint8_t channel)
{
privinfo_t *priv = __scm_i2c_bus[channel - 1].ctx;
I2C_Type *instance = (I2C_Type *)priv->base;
ms_uint32_t error = I2C_ERROR_NONE;
/*
* handle bus_err flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_BUSERR) != RESET))
{
error |= I2C_ERROR_BERR;
I2C_ClearITPendingBit(instance, I2C_INT_BUSERR);
}
/*
* handle ack_err flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_ACKFAIL) != RESET)){
I2C_ClearITPendingBit(instance, I2C_INT_ACKFAIL);
error |= I2C_ERROR_AF;
I2C_GenerateSTOP(instance, ENABLE);
}
/*
* handle over_run/under_run err flag
*/
if ((I2C_GetINTStatus(instance, I2C_INT_OVRUN) != RESET))
{
error |= I2C_ERROR_OVR;
I2C_ClearITPendingBit(instance, I2C_INT_OVRUN);
}
/*
* set err_code and close interrupt
*/
if (error != I2C_ERROR_NONE)
{
priv->err_code |= error;
I2C_NACKPositionConfig(instance, I2C_NACKPosition_Current);
I2C_INTConfig(instance, I2C_INT_BUF | I2C_INT_EVT | I2C_INT_ERR, DISABLE);
}
}