醋醋百科网

Good Luck To You!

STM32嵌入式-LCD-IIC(stm32f4 lcd)

前面已经讲解过了 IIC,之前使用的是通过串口打印输出,本章我们使用按键 K_UP 进行 IIC 写入,按键 K_DOWN 进行 IIC 读取。将写入和读取的数据现在是TFTLCD 上。 本章分为以下学习目标:

1. 学习 I2C 协议

2. 学会操作 AT24C02

1.1 I2C 简介

I2C 总线时 PHILIPS 公司推出的一种串行总线,具备多主机系统所需的包括总线仲裁和高低速器件同步功能的高性能串行总线。它只需要两跟双向的信号线,一根数据线 SDA,一个是时钟线 SCL。在 I2C 总线上面,每个器件都有自己相应的 I2C 地址,所以在两个器件之间进行通信的时候,都要首先呼叫你想要通信的器件地址,然后等待相应的从器件进行应答之后才开始通信。首先我们来看一下,一个 I2C 信号传输的一个过程,如图:

从图上我们可以看出在 I2C 上面一个完整信号的传输过程,一定要有一个始信号,还有一个结束信号,在每个字节传输结束的时候,从机还要提供一个应答信号。一个完整的信号传输就是这样子。接下来我们来看一下,I2C 总线上面对起始信号、应答信号、结束信号、还有高低电平的协定是怎么样的呢?这里有两个要注意的要点:

1、在总线空闲的时候,SDA 和 SCL 都是高电平的。

2、在 SCL 为高电平期间,SDA 必须保持稳定。所以 SDA 改变状态最好在 SCL 为低电平的时候改变,如果在高电平改变的话回被认为是一种有效信号(如:起始信号或者结束信号)。

1. 起始信号

起始信号简介 SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号,信号时序如图:

这里要注意的就是,在 I2C 总线上面,当总线空闲的时候,SCL 和 SDA 都是高电平的。起始信号,它是需要有一定的保持时间的,在 SDA 从高电平向低电平跳变的时候,两个先必须至少保持 4.7us 的时间,而跳变之后,也要保持 SCL 高电平和 SDA 低电平要至少保持 4us 的时间(从这里我们看出 I2C 总高速率已经决定了) 。IO 口模拟起始信号

//产生起始信号
void I2C_Start(void)
{
 I2C_SDA_OUT();
I2C_SDA_H;
I2C_SCL_H;
delay_us(5);
I2C_SDA_L;
delay_us(6);
I2C_SCL_L;
}

2. 结束信号

结束信号简介 SCL 线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。信号时序如图:

注意的就是这里保持时间也是有一定限制的。 IO 口模拟结束信号

//产生停止信号
void I2C_Stop(void)
{
 I2C_SDA_OUT();
 I2C_SCL_L;
 I2C_SDA_L;
 I2C_SCL_H;
 delay_us(6);
 I2C_SDA_H;
 delay_us(6);
}

3. 应答信号

应答,也叫响应。数据的传输必须要带应答。在响应的时钟脉冲期间(也就是 SCL 在高电平的时候) ,发送器释放 SDA 线(释放 SDA 意思就是将 SDA 拉为高电平,这里要注意的是,不能在 SCL 为高电平的时候讲 SDA 从低电平拉到高电平,可以在在 SCL 在低电平的时候,将 SDA 拉为高电平等待),然后等待应答,在应答时钟脉冲器件,接收器必须将 SDA 拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。而一个字节传输完毕之后,接收器没有应答则表示接收完毕。还有一种情况是,当主机作为接收器的时候,接收完最后一个字节之后,必须向从机发出一个结束传送的信号。这个信号是由对从机“非应答”来实现的。(从上面的规则我们知道,当主机作为接收器的时候,如果是进行应答,那么在接收完一个字节的最后一位之后产生一个低电平的时钟,进行应答。而非应答呢,就是产生一个高电平的时钟,进行应答) 。如果大家不是很理解呢,大家可以参考我们例程里面 I2C 的 IO 模拟信号里面接收数据的函数,最后的应答和非应答。 IO 口模拟应答信号

//主机产生应答信号 ACK
void I2C_Ack(void)
{
 I2C_SCL_L;
 I2C_SDA_OUT();
 I2C_SDA_L;
 delay_us(2);
 I2C_SCL_H;
 delay_us(5);
 I2C_SCL_L;
}
//主机不产生应答信号 NACK
void I2C_NAck(void)
{
 I2C_SCL_L;
 I2C_SDA_OUT();
 I2C_SDA_H;
 delay_us(2);
 I2C_SCL_H;
 delay_us(5);
 I2C_SCL_L;
}
//等待从机应答信号
//返回值:1 接收应答失败
//0 接收应答成功
u8 I2C_Wait_Ack(void)
{
u8 tempTime=0;
I2C_SDA_IN();
I2C_SDA_H;
delay_us(1);
I2C_SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
tempTime++;
if(tempTime>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
return 0;
}

4. 逻辑“1”的表示

要传输数据,那么肯定要分传输“1”和“0” ,而在 I2C 上面是怎么表示这两个逻辑变量的呢?如图:

一般 I2C 读取的时候,都是在 SCL 的为高电平的时候进行读取,所以在 SCL 为高电平的时候,需要保持 SDA 稳定。而且注意的还有就是他们的保持时间要大于 4us。

5. 逻辑“0”的表示

逻辑“0”和逻辑“1”的表示其实差不多,只是 SDA 正好相反。如图:

注意的事项跟逻辑“1”的表示差不多。

6. IO 口模拟发送一个字节数据

//I2C 发送一个字节

void I2C_Send_Byte(u8 txd)

{

u8 i=0;

I2C_SDA_OUT();

I2C_SCL_L;//拉低时钟开始数据传输for(i=0;i<8;i++)

{

if((txd&0x80)>0) //0x80 1000 0000

I2C_SDA_H;

else

I2C_SDA_L;

txd<<=1;

I2C_SCL_H;

delay_us(2); //发送数据

I2C_SCL_L;

delay_us(2);

}

}

7. IO 口模拟接收一个字节数据

//I2C 读取一个字节

u8 I2C_Read_Byte(u8 ack)

{

u8 i=0,receive=0;

I2C_SDA_IN();

for(i=0;i<8;i++)

{

I2C_SCL_L;

delay_us(2);

I2C_SCL_H;

receive<<=1;if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))

receive++;

delay_us(1);

}

if(ack==0)

I2C_NAck();

else

I2C_Ack();

return receive;

}

1.2 24C02 简介

在这里呢,我们来了解几个重要的参数就可以了,如果要了解详细的 IC 信息,大家可以参考光盘上面的 DATASHEET。

1. 24C02 可以提供 2K 位,也就是 256 个 8 位字节的 EEPROM 内存。也就是说它可以保存 256 个字节的数据。所以从这里我们可以了解到,256 个字节也就是有 256 个内存地址,也正好对应一个字节的。当我们向 24C02 读写数据的时候,地址正好跟一个字节一一对应。

2. 24C02 通过 I2C 总线接口进行操作。

3. 24C02 的写操作,可以一个地址一个地址的写,也可以一次写一页。所写的一页,在 24C02。这里是 8 个字节,(在有些数据手册上面是 16 个字节,不过开发板上面使用 24C02 是一页 8 个字节,ARM 公司提供的官方例程里面 24C02 设定的也是一页 8 个字节。 )也就是说当你写入的数据,在同一页的时候(注意是在同一页的时候) ,你可以只写入一次地址,每写入一个字节,地址自动加 1。

4. 24C02 的读操作,24C02 的读操作就可以连续读,不管连续读的数据是不是在同一页,每次读完一次数据之后,读取地址都会自动加 1。接下来我们看一下我们开发板上面 24C02的原理图。

从图上我们可以看出 24C02 使用的是 STM32 的 I2C2,使用的是 PB10、 PB11 两个 IO口。我们知道,在 I2C 总线上面,每个器件都会有一个器件地址,而 24C02 的器件地址是多少呢?我们来看一个图:

高四位 1010 是 24Cxx 系列的固定器件地址,接下来是 A2、A1、A0 是根据器件连接来决定,也就是我们原理图上面的 E2、E1、E0。我们的原理图都接地所以是 000。R/W 为是选择读还是写,1 的时候是读,0 的时候是写。所以写的地址为 0xA0,读地址为 0xA1。

1.3 STM32 的硬件 I2C 简介

STM32 的硬件 I2C 做得很复杂,而且不好用。所以大部分人都不太选择使用 STM32 的硬件 I2C,很难调试。不过 AT24C02 有 ARM 官方的提供的例程,读写还是挺稳定的,我们的例程使用的也是参考 ARM 官方的例程来的。接下来我们看看一下通过库函数使用硬件 I2C 来操作 AT24C02。

1. 首先我们要打开时钟使能

要打开 GPIOB 的时钟使能。代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

2. 配置 IO 的模式

GPIO_InitStructure.GPIO_Pin=I2C_SCL|I2C_SDA;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);

管脚的端口定义如下:

//如果移植程序时只要改一下三个地方就行了
/* 定时使用的 IO 口 */
#define I2C_SCL GPIO_Pin_10 //PB10
#define I2C_SDA GPIO_Pin_11 //PB11
#define GPIO_I2C GPIOB

3. AT24C02 写操作

首先我们来看一下写 AT24C02。

一般步骤是:

1) 发送起始信号

2) 发送写器件地址

3) 等待应答

4) 发送要写入的 24C02 的地址

5) 等待应答

6) 发送要写入的数据

7) 等待应答

8) 发送数据结束发送结束信号

具体程序如下:

/******************************************************************************
*
* 函 数 名 : AT24Cxx_WriteOneByte
* 函数功能
 : 24c02 写一个字节地址 数据 * 输 入 : addr dt
* 输 出 : 无
*******************************************************************************
*/
void AT24Cxx_WriteOneByte(u16 addr,u8 dt)
{
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xA0);
I2C_Wait_Ack();
I2C_Send_Byte(addr>>8); //发送数据地址高位
}
else
{
 I2C_Send_Byte(0xA0+((addr/256)<<1));//器件地址+数据地址
}
I2C_Wait_Ack();
I2C_Send_Byte(addr%256);//双字节是数据地址低位
//单字节是数据地址低位
I2C_Wait_Ack();
I2C_Send_Byte(dt);
I2C_Wait_Ack();
I2C_Stop();
delay_ms(10);
}

4.AT24C02 读操作

那么读取 AT24C02 的步骤是:

1) 发送起始信号2) 发送写器件地址

3) 等待应答

4) 发送要读取的 AT24C02 的地址

5) 等待应答

6) 再发送其实信号

7) 发送读器件地址

8) 等待应答

9) 接收数据

10) 如果没有接收完数据,发送应答

11) 接收数据

12) 直到接收完数据,发送非应答

13) 发送结束信号

/******************************************************************************
*
* 函 数 名 : AT24Cxx_ReadOneByte
* 函数功能
 : 24c02 读一个字节地址 数据
* 输 入 : addr
* 输 出 : 返回值 temp
*******************************************************************************
*/
u8 AT24Cxx_ReadOneByte(u16 addr)
{
u8 temp=0;
I2C_Start();
if(EE_TYPE>AT24C16)
{
I2C_Send_Byte(0xA0);I2C_Wait_Ack();
I2C_Send_Byte(addr>>8);
//发送数据地址高位
}
else
{
 I2C_Send_Byte(0xA0+((addr/256)<<1));//器件地址+数据地址
}
I2C_Wait_Ack();
I2C_Send_Byte(addr%256);//双字节是数据地址低位
//单字节是数据地址低位
I2C_Wait_Ack();
I2C_Start();
I2C_Send_Byte(0xA1);
I2C_Wait_Ack();
temp=I2C_Read_Byte(0); // 0 代表 NACK
I2C_NAck();
I2C_Stop();
return temp;
}

1.4 例程主函数

/****************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Output : None* Return : None
****************************************************************************/
int main()
{
u8 wdata=0,value;
u8 i,j,dat[6],dat1[6];
I2C_INIT();
TFT_Init(); //TFT 彩屏初始化
LED_Init(); //端口初始化
key_init(); //按键初始化
printf_init(); //printf 初始化
TFT_ClearScreen(BLACK); //清屏
GUI_Show12ASCII(10,10,"This is a IIC-AT24C02 Check!",YELLOW,BLACK);
GUI_Show12ASCII(10,27,"PB10 PB11 is IIC Interface!",YELLOW,BLACK);
GUI_Show12ASCII(10,44,"The K_UP is:AT24C02 Write",YELLOW,BLACK);
GUI_Show12ASCII(10,61,"The K_DOWM is:AT24C02 Read",YELLOW,BLACK);
GUI_Show12ASCII(10,100,"Write data Range is: 0-255",YELLOW,BLACK);
GUI_Show12ASCII(10,150,"Write data is:",YELLOW,BLACK);
GUI_Show12ASCII(10,167,"Read data is:",YELLOW,BLACK);
while(1)
{
if(k_up==1)
 //按键 k_up 按下写入数据
{
delay_ms(10);
if(k_up==1)
{
AT24Cxx_WriteOneByte(0,++wdata);//写入的是单字节,0-255 取值范围
dat[0]=wdata/100+0x30;
dat[1]=wdata%100/10+0x30;
dat[2]=wdata%100%10+0x30;dat[3]='\0';
GUI_Show12ASCII(160,150,dat,YELLOW,BLACK);
}
//
while(k_up);
}
if(k_down==0) //读取数据
{
delay_ms(10);
if(k_down==0)
{
value=AT24Cxx_ReadOneByte(0);
dat1[0]=value/100+0x30;
dat1[1]=value%100/10+0x30;
dat1[2]=value%100%10+0x30;
dat1[3]='\0';
GUI_Show12ASCII(160,167,dat1,YELLOW,BLACK);
}
while(!k_down);
}
if(j>1)
{
j=0;
GPIO_SetBits(GPIOC,GPIO_Pin_0);
}
else
{
j++;
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
}
delay_ms(200);}
}

该函数实现按键 K_UP 按下写入数据加 1 写入到 24C02 中,当按键 K_DOWN 按下后进行24C02的数据读取,LED 闪烁表示系统正在运行。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言