IIC 是一种总线,就两根线,两根线上可以挂很多设备也就是所说的两线式串行总线,由PHILIPS公司开发用于微型控制器及其外围设备。
他是由数据线SDA和时钟线SCL构成的串行总线,可发送和接受数据。在CPU与被控IC之间,IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
IIC是半双工通信方式。
IIC总线是不同的IC或者模块之间双向两线通信。这两条线是串行数据线(SDA)和串行时钟线(SCL)。这两条线必须通过上拉电路连接至正电源。数据传输只能在总线不忙时启动。
数据和时钟线在总不忙时保持高电平。在时钟为高电平时,数据线上的一个由高到底的变化被定义为开始条件。
在比赛中IIC基本的时序是自己写的,比赛的时候会给我们一个资料盘,里边会有IIC的底层代码
void I2CStart(void)
{
SDA_Output(1);
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
SDA_Output(0);
delay1(DELAY_TIME);
SCL_Output(0);
delay1(DELAY_TIME);
}
/**
* @brief I2C结束信号
* @param None
* @retval None
*/
void I2CStop(void)
{
SCL_Output(0);
delay1(DELAY_TIME);
SDA_Output(0);
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
SDA_Output(1);
delay1(DELAY_TIME);
}
发送器发送一个字节,就在时钟脉冲期9间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答信号(ACK简称应答位),表示接收器已经成功接受该字节,应答信号为高电平时,规定为非应答信号,(NACK),一般表示接收器接受该字节没有成功。
/**
* @brief I2C等待确认信号
* @param None
* @retval None
*/
unsigned char I2CWaitAck(void)
{
unsigned short cErrTime = 5;
SDA_Input_Mode();
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
while(SDA_Input())//主机在第九个时钟脉冲的时候去读SDA的数据如果是0,则接收到应答信号
{
cErrTime--;
delay1(DELAY_TIME);
if (0 == cErrTime)
{
SDA_Output_Mode();
I2CStop();
return ERROR;
}
}
SDA_Output_Mode();
SCL_Output(0);
delay1(DELAY_TIME);
return SUCCESS;
}
/**
* @brief I2C发送确认信号
* @param None
* @retval None
*/
void I2CSendAck(void)//主机发送ACK
{
SDA_Output(0);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
SCL_Output(0);
delay1(DELAY_TIME);
}
/**
* @brief I2C发送非确认信号
* @param None
* @retval None
*/
void I2CSendNotAck(void)//发送非应答信号
{
SDA_Output(1);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
SCL_Output(0);
delay1(DELAY_TIME);
}
一个数据位在每一个时钟脉冲期间传输,SDA线上的数据必须在时钟脉冲的高电压期间保持稳定,(就是在时钟信号为高电平的时候读取数据)这个期间数据线上的变化被当做控制信号。
/**
* @brief I2C发送一个字节
* @param cSendByte 需要发送的字节
* @retval None
*/
void I2CSendByte(unsigned char cSendByte)
{
unsigned char i = 8;
while (i--)
{
SCL_Output(0);
delay1(DELAY_TIME);
SDA_Output(cSendByte & 0x80);
delay1(DELAY_TIME);
cSendByte += cSendByte;//自加的操作相当于左移一位
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
}
SCL_Output(0);
delay1(DELAY_TIME);
}
/**
* @brief I2C接收一个字节
* @param None
* @retval 接收到的字节
*/
unsigned char I2CReceiveByte(void)
{
unsigned char i = 8;
unsigned char cR_Byte = 0;
SDA_Input_Mode();
while (i--)
{
cR_Byte += cR_Byte;
SCL_Output(0);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output(1);
delay1(DELAY_TIME);
cR_Byte |= SDA_Input();
}
SCL_Output(0);
delay1(DELAY_TIME);
SDA_Output_Mode();
return cR_Byte;
}
IIC总线的SDA和SCL两条信号线同处于高电平的时候,规定总线的状态为空闲状态。此时各个器件的输出级场效应管均处于截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高 。
在咱们嵌入式板子中的模块:
24C02的芯片地址为1010(A2 A1 A0)(R=1,W=0)最后一位读写位
所以芯片的地址可以敲定为:
10100000(0xA0)写或者
10100001(0xA1)读
E1,E2 E3 为地址引脚都接地
个引脚的作用:
注意的是:AT24C02存储器进行页读写的时候,不能产生跨页的现象
而对我们编程来说最重要的是下边的写操作:
按照字节来写:
AT24C02存储器写操作需要再给出开始态、器件地址和确认后,紧跟着给出一个8位数据地址。一经收到该地址,EEPROM就通过SDA发送确认信号(ACK),并随时钟输入八个位的数据。在收到8位数据后,EEPROM将通过SDA对主机发送信号,数据传输设备(主机)必须用停止状态来中止写操作,然后EEPROM进入一个内记时固定存储器写入周期。在写周期时,所有输入被禁止,EEPROM直到写完后在应答,如下图片
//该代码在比赛中是要自己写的 /** * @brief 往AT24C02里写数据 * @param 将要写的内容放到pucBuff:数组 unAddr:入口地址 ucNum数据个数 * @retval None */ void IIC_AT24C02_write(unsigned char * pucBuff,unsigned char unAddr,unsigned char ucNum) { I2CStart();//开始信号 I2CSendByte(0xa0);//写器件地址 I2CWaitAck();//等待从机回复应答信号(ACK) I2CSendByte(unAddr);//发送数据地址 I2CWaitAck();//等待从机回复应答信号(ACK) while(ucNum--)//几个数据 { I2CSendByte(* pucBuff++);//每次发送一个字节的数据 I2CWaitAck();//没发送完一个数据都有一个应答信号 } I2CStop();//发送停止 delay1(500);//延时函数 }
按照页面来写:
1KB/2KB EEPROM能进行8字节页面写入,4KB、8KB、16KB设备能进行16字节页面写入。激发写页面与激发写字节相同,只是数据传输设备无需再第一个字节随时钟输入后,发出一个停止状态。再EEPROM确认收到第一个数据之后,数据传输设备能在传输7个或者15个数据,每个数据收到之后,EEPROM都将通过SDA回送一个确认信号,,最后数据传输设备必须通过停止状态终止页面写序列。
/** * @brief 从AT24C02里读数据 内存大小(256*8bit)2k * @param 将读取的数据存到数组里边pucBuff:数组 unAddr:入口地址 ucNum:数据个数 * @retval None */ void IIC_AT24C02_read(unsigned char * pucBuff,unsigned char unAddr,unsigned char ucNum) { //先写入读的地址 I2CStart();//开始信号 I2CSendByte(0xa0);//写操作地址 I2CWaitAck();//等到应答信号 I2CSendByte(unAddr);//发送数据地址位 I2CWaitAck();//等待应答 //第八位写高执行读EEPROM操作 I2CStart();//发送开始信号 I2CSendByte(0xa1);//发送读操作地址 I2CWaitAck();//等待应答 while(ucNum--)//个数 { *pucBuff++=I2CReceiveByte();//将读取的数据放到数组里 if(ucNum) I2CSendAck(); else I2CSendNotAck(); } I2CStop(); }
注意:数据字地址的低三位(1KB/2KB)或者低四位(4KB、8KB、16KB)在接收到每个数据字之后,内部自动加一,数据字地址的高位字节保持不变,以保持存储器页地址不变。如果传送到EEPROM中的数据字超过8位或者16位数据字节地址将会滚动以前的数据将被覆盖。
将官方提供的IIC例程移植到自己的工程里边,将上边所写的读写AT24C02的操作加到自己的代码当中,然后声明一下。
那么接下来该如何使用一下这个代码呐
首先先在main.c文件调用IIC的h文件,然后在主函数中初始化IIC的初始化函数, I2CInit;
现在前提准备就做好了,开始尝试往EEPROM里边写数据,
根据咱们所写的代码就可以知道,现在我们还缺少EEPROM相关变量来传输数据,
然后来看一下咱们之前写的代码IIC_AT24C02_write(....入口参数...)等怎么用
///AT24C02:写入数据 IIC_AT24C02_write(EEROM_write,0,5); //第一个入口参数是一个数组地址,指向将要存储的数据 //第二个参数,入口地址,从零开始 //第三个参数,需要多少个数据 HAL_Delay(100);//实际赛题的时候写操作和读操作一般不会挨得这么近,就不用加延时函数,否则显示出错 IIC_AT24C02_read(EEROM_read,0,5); //第一个入口参数是一个数组地址,指向将要存储读取数据 //第二个参数,入口地址,从零开始 //第三个参数,需要读多少个数据
将上述代码加到主函数里边,咱们可以将读取的数据通过LCD显示出来如下图所示
什么是可编程电阻,他的阻值和我们的滑动电阻还不一样,滑动变阻是通过物理的方式进行旋转来改变阻值,但是可编程电阻是通过软件的形式对其进行电阻值的修改,对应的开发板的U3位置。
开发板接线图:
104啥意思呢?
10*10^4=100K的电阻
硬件配置:
首先先来看一下内部结构:
他的内部分为三大块,电源、IIC数据、电阻器网络
根据原理图和内部结构的提示可以知道A没有连接(Note.2)(在某些配置中,此信号可能未连接到外部存储器(内部浮动或接地)。)
再来看一下内部电阻网是怎么变化的
雨刮器电阻(也就是我说的小开关)取决于抽头。
也就是说,每个抽头选择电阻具有小的变化。这种变化对RAB电阻较小(5.0 kΩ)的器件影响更大内部有127个可控制小开关也就是N的个数就是Rs的个数,通过给他一个不同的N值他的电阻值会发生变化
在芯片手册里提供了一个公式:
和雨刮器电阻值的设置范围:
再来看一下4017地址:
有芯片手册可以知道地址的前七位是被固定了的为0101111最后一位来决定是以读的形式来写还是以写的程序来写。
软件编程:
写一个字节:
我们只需要把器件地址写好然后把N值写进去即可。
/** * @brief 往MCP4017里边写数据 * @param resist_val也就是传入的N的值//根据雨刷器电阻可设置的范围可知为(0~7F) * @retval None */ //MCP4017可编程芯片 void write_MCP4017(uint8_t resist_val) { I2CStart();//起始信号 I2CSendByte(0x5E);//0101 1110 器件地址和写操作 I2CWaitAck();//等待应答 I2CSendByte(resist_val);//发送数据 I2CWaitAck(); I2CStop(); }
读取电阻值:
/** * @brief 从MCP4017里读取电阻值 * @param resist_val: * @retval None */ /*********用于接收电阻的值***********/ uint8_t resist_val;//将接受的N值给到一个变量里边 /**************END******************/ uint8_t read_MCP4017(void) { uint8_t value; //先写入读的地址 I2CStart();//起始信号 I2CSendByte(0x5f);//0101 1111 读操作 I2CWaitAck(); value=I2CReceiveByte(); I2CSendNotAck(); I2CStop(); return value; }//读出来的是原始数据,不是真正的电阻值
电阻的计算:
100000/127约等于787.4Ω,所以Rs=0.7874KΩ,根据公式假设要设置一个10k的电阻
10K=0.7874*N
N=12.7近似等于13=d
//N值写入 write_MCP4017(0x0d);
//lcd显示代码 //MCP4017 : 可编程电阻测试 resist_val=read_MCP4017(); sprintf((char *)MCP4017_Disp,"4017:Rx=%x Rwb=%5.3f ",resist_val,0.7874*resist_val); LCD_DisplayStringLine(Line2,MCP4017_Disp); sprintf((char *)MCP4017_Disp,"4017:U=%3.3f ",3.3*((0.7874*resist_val)/(0.7874*resist_val+10))); LCD_DisplayStringLine(Line3,MCP4017_Disp);
4017的第二行就是电压的转换,3.3乘以(4017的电阻/总电阻值)