写在前面:在前面的学习过程中,我们学习了串口通信的USART(通用同步异步收发器),本节我们将继续学习一种串行通信协议——IIC通信协议。之前我使用51单片机也分享过相关的IIC通信的知识,其实本质的知识是相通的,本节我们继续探讨STM32关于IIC的基础知识。
一、IIC总线协议介绍
1.1基础介绍
常见的通信协议有:串口、SPI、IIC、CAN以及USB;
IIC:集成电路总线,是一种同步、串行、半双工通信总线;
同步:含有时钟线SCL;串行:数据的传输是一位一位传送的;半双工:单方向发、单方向收;
什么是总线?什么又是协议呢?
这其实是一种说法,总线就是从硬件层面看,是传输数据的通道,协议是从软件层面看,是传输数据的规则。总线就好比是一条道路,而协议是通过这条道路的交规。
IIC总线的特点:
1、总线由数据线 SDA 和时钟线 SCL 构成的串行总线,数据线用来传输数据,时钟线用来
同步数据收发。并且都接有上拉电阻,保证总线在空闲状态为高电平。2、总线支持多设备连接,允许多主机并存,每个设备都有属于自己的设备地址,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。
3、连接到总线上的数目受总线的最大电容400pf限制,(寄生电容)每个设备都有电容;
4、数据的传输速率:
标准模式:100kbit/s;快速模式:400kbit/s;高速模式:3.4Mbit/s;
IIC协议:
三个信号:起始信号、停止信号与应答信号;
两个注意:数据的有效性;数据的传输顺序;
一个状态:空闲状态;
I2C两设备进行通信的流程为:首先主机控制总线,通过总线时序找到想要通信的从设备(每个从设备都有固定地址),被选中的从机准备发送/接收收据,未被选中的从机打开上述开关,断绝同总线的联系。主机与从机传递数据,如果传递1直接打开开关通过上拉电阻实现总线高电平,如果传递0关闭开关,使总线达到低电平。主从机设备传递信息严格遵循相应的时序。
1.2 IIC协议
下图为:IIC协议的时序图:
其中主要为 1、起始信号;2、终止信号;3、应答信号;4、数据有效性;5、
数据传输;6、空闲状态;
1、起始信号与终止信号
起始条件:SCL高电平期间,SDA从高电平切换到低电平;
结束条件:SCL高电平期间,SDA从低电平切换到高电平;
2、应答信号
应答条件:在上拉电阻的影响下,SDA默认为高电平,而从机拉低SDA表示收到信号即(ACK),若没有收到则不用拉低(NACK);发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,应该释放SDA);
3、数据收发
数据在低电平期间准备好,高电平期间数据发送(高电平期间保持有效),数据先发送高8、位,数据以8位(1bit)为一个单位进行发送。数据在SCL高电平期间,保持稳定,主句发送完数据释放数据线,不影响从机的应答;
1.3相关驱动代码:
起始信号
void iic_start(void){
IIC_SDA(1);
IIC_SCL(1);
iic_delay();
IIC_SDA(0);
iic_delay();
IIC_SCL(0);
iic_delay()
}
停止信号
void iic_stop(void)
{
IIC_SDA(0);
iic_delay();
IIC_SCL(1);
iic_delay();
IIC_SDA(1);
iic_delay();
}
发送应答信号
void iic_ack(void)
{
IIC_SCL(0);
iic_delay();
IIC_SDA(0);
iic_delay();
IIC_SCL(1);
iic_delay();
}
发送非应答信号
void iic_nack(void)
{
IIC_SCL(0);
iic_delay();
IIC_SDA(0);
iic_delay();
IIC_SCL(1);
iic_delay();
}
检测应答信号
uint8_t iic_wait_ack(void)
{
IIC_SDA(1);
iic_delay();
IIC_SCL(1);
iic_delay();
if(IIC_READ_SDA)
{iic_stop();
return 1;
}
IIC_SCL(1);
iic_delay();
reutn 0;
}
发送一个字节:
void iic_send_byte(uint8_t data)
{
uint8_t i;
for (i=0;i<8;i++)
{
IIC_SDA((data&0x80)>>7); /* 高位先发送 */
iic_delay();
IIC_SCL(1);
iic_delay();
IIC_SCL(0);
iic_delay();
data<<=1;
}
IIC_SDA(1); /* 发送完成,释放数据线 */
}
接收一个字节
uint8_t icc_recive_byte(uint8_t ack)
{
uint8_t i,recive;
for(i=0;i<8;i++)
{
recive<<=1; /* 高位先输出,所以先收到的数据位要左移 */
IIC_SCL(1);
iic_delay();
if(IIC_READ_SDA)
{
recive++;
}
IIC_SCL(0);
iic_delay();
}
if(ack==1)
{
iic_ack();/* 发送 ACK */
}
else
{
iic_nack();/* 发送 nACK */
}return recive;
}
二、AT2402介绍
2.1基础介绍
24C02 是一个 2K bit 的串行 EEPROM 存储器,内部含有 256 个字节。在 24C02 里面还有
一个 8 字节的页写缓冲器。该设备的通信方式 IIC,通过其 SCL 和 SDA 与其他设备通信。
存储介质:E2PROM;
通讯接口:I2C通信接口;
容量:256字节;
1 | A0 | 地址输入 |
2 | A1 | 地址输入 |
3 | A2 | 地址输入 |
4 | VSS | 电源地 |
5 | SDA | 串行地址和数据输入输出 |
6 | SCL | 串行时钟输入 |
7 | WP | 写保护 |
8 | VCC | 电源正极 |
AT24C02 器件地址为 7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时, 器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向,它与地址 无关。其格式如下:
1 | 0 | 1 | 0 | A2 | A1 | A0 | R/W |
2.2 AT24C02读写时序
写操作:起始信号——地址和方向(设备地址+写)——应答——内存地址——数据内容——应答——停止;
读操作:起始信号——地址和方向(设备地址+写)——应答——内存地址——应答——起始信号——地址和方向(设备地址+读)——应答——数据——应答······——停止;
2.3 AT24C02驱动与步骤
IIC配置:
1、使能SCL和SDA引脚的时钟;
2、GPIO配置模式:SDA:复用开漏;SCL:复用推挽;
3、编写基本信息:起始,停止,应答,接收应答;
4、编写读写函数;
AT24C02驱动步骤:
1、初始化IIC接口;
2、编写写入/读取一个字节数据函数;
3、编写连续读写函数;
三、程序设计
每按下 KEY1,MCU 通过 IIC 总线向 24C02 写入数据,通过按下 KEY0 来控制 24C02 读
取数据。同时在 LCD 上面显示相关信息。LED0 闪烁用于提示程序正在运行。
源码文件:
链接:https://pan.baidu.com/s/1JtCEJ4oTfy6Da-Rffr93qg
提取码:1022
myiic.c
#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"/*** @brief 初始化IIC* @param 无* @retval 无*/
void iic_init()//
{__HAL_RCC_GPIOB_CLK_ENABLE();/* 使能 IIC 的 SCL 和 SDA 对应的 GPIO 时钟 */GPIO_InitTypeDef gpio_init_struct;gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;gpio_init_struct.Pin=GPIO_PIN_6;gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;gpio_init_struct.Pull=GPIO_PULLUP; HAL_GPIO_Init(GPIOB, &gpio_init_struct);/* 设置对应 GPIO 工作模式(SCL 推挽输出 SDA 开漏输出) */gpio_init_struct.Mode=GPIO_MODE_OUTPUT_OD;gpio_init_struct.Pin=GPIO_PIN_7;gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &gpio_init_struct);}/*** @brief IIC延时函数,用于IIC读写的速度,IIC在进行数据传递时,时钟信号为高电平期间,数据线必须保持稳定延时的作用就是保持在高电平期间的数据稳定。* @param 无* @retval 无*/
static void iic_delay(void)
{delay_us(2);/* 2us 的延时, 读写速度在 250Khz 以内 */
}/*** @brief IIC起始信号* @param 无* @retval 无*/
void icc_start()
{IIC_SCL(1);IIC_SDA(1);iic_delay();IIC_SDA(0);/* 在时钟为高电平期间,数据线由高电平变为低电平,表示起始信号 */iic_delay();IIC_SCL(0); /* 钳住I2C总线,并没有把数据线释放,准备发送或接收数据,因为紧接着还是主机进行操作 */iic_delay();
}/*** @brief IIC停止信号* @param 无* @retval 无*/
void icc_stop()
{ IIC_SDA(0);iic_delay();IIC_SCL(1);iic_delay();IIC_SDA(1);/* 在时钟为高电平期间,数据线由低电平变高低电平,表示停止信号 */iic_delay();
}/*** @brief IIC发送一个字节* @param data: 要发送的数据* @retval 无*/
void iic_send_byte(uint8_t data)
{uint8_t i;for (i=0;i<8;i++){IIC_SDA((data&0x80)>>7); /* 高位先发送 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0);iic_delay();data<<=1;}IIC_SDA(1); /* 发送完成,释放数据线 */
}/*** @brief IIC读取一个字节* @param ack: ck=1时,发送ack; ack=0时,发送nack* @retval 接收到的数据*/
uint8_t icc_recive_byte(uint8_t ack)
{uint8_t i,recive;for(i=0;i<8;i++){recive<<=1; /* 高位先输出,所以先收到的数据位要左移 */IIC_SCL(1);iic_delay();if(IIC_READ_SDA){recive++; } IIC_SCL(0);iic_delay(); }if(ack==1){iic_ack();/* 发送 ACK */}else{iic_nack();/* 发送 nACK */}return recive;}
/*** @brief 等待应答信号* @param 无* @retval 返回0,接收应答成功返回1,接收应答失败*/
uint8_t icc_wait_ack(void)
{uint8_t wattime=0;uint8_t rack=0;IIC_SDA(1); /* 主机释放数据线,由对应的从机进行拉低 */ iic_delay();IIC_SCL(1); /* 高电平期间,从机发送应答信号 */iic_delay();while(IIC_READ_SDA) /* 主等待应答 */{wattime++;if(wattime>250){icc_stop();rack=1; /* 超时,应答失败 */break; }}IIC_SCL(0); /*时钟结束,应答成功 */iic_delay();return rack;
}/*** @brief 产生应答信号* @param 无* @retval 无*/
void iic_ack(void)
{IIC_SDA(0); /* 产生应答信号 */iic_delay();IIC_SCL(1); /* 高电平期间,发送应答信号 */iic_delay();IIC_SCL(0);iic_delay();IIC_SDA(1); /* 释放数据线 */iic_delay();}/*** @brief 产生不应答信号* @param 无* @retval 无*/
void iic_nack(void)
{IIC_SDA(1); /* 产生不应答信号 */iic_delay();IIC_SCL(1);iic_delay(); /* 高电平期间,发送不应答信号 */IIC_SCL(0);iic_delay();
}
AT24Cxx.c
#include "./BSP/AT24CXX/AT24Cxx.h"
#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
/*** @brief 初始化IIC接口* @param 无* @retval 无*/
void at24cxx_init(void)
{iic_init();
}
/*** @brief 在AT24CXX指定地址写入一个数据* @param arr: 写入数据的目的地址* @param data: 要写入的数据* @retval 无*/
void at24cxx_write_onebyte(uint16_t arr,uint8_t data)
{icc_start(); /* 发送起始信号 */if(EE_type>AT24C16) /* 24C16以上的型号, 分2个字节发送地址 */{iic_send_byte(0XA0);icc_wait_ack();iic_send_byte(arr>>8);}else{iic_send_byte(0XA0 + ((arr >> 8) << 1)); /* 24C16以下信号发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */ }icc_wait_ack();/* 每次发送完一个字节,都要等待ACK */iic_send_byte(arr%256);/* 内存地址低位 */icc_wait_ack();iic_send_byte(data); /* 发送1字节 */icc_wait_ack(); /* 要等待ACK */icc_stop(); /* 产生一个停止条件 */delay_ms(10); /* 注意: EEPROM 写入比较慢,必须等到10ms后再写下一个字节 */
}/**
* @brief
在 AT24CXX 指定地址读出一个数据
* @param
readaddr: 开始读数的地址
* @retval读到的数据
*/
uint8_t at24cxx_read_onebyte(uint16_t arr)
{uint8_t temp;icc_start();if(EE_type>AT24C16){iic_send_byte(0XA0);icc_wait_ack();iic_send_byte(arr>>8);}else{iic_send_byte(0XA0 + ((arr >> 8) << 1)); }icc_wait_ack();iic_send_byte(arr%256);icc_wait_ack();icc_start();iic_send_byte(0XA1);icc_wait_ack();temp=icc_recive_byte(0);icc_stop();return temp;
}
/**
* @brief
检查 AT24CXX 是否正常
* @note
检测原理: 在器件的末地址写如 0X55, 然后再读取, 如果读取值为 0X55
*
则表示检测正常. 否则,则表示检测失败.
* @param无
* @retval检测结果
*0: 检测成功
*1: 检测失败
*/
uint8_t at24cxx_chack(void)
{uint8_t temp;uint16_t add = EE_type;temp =at24cxx_read_onebyte(add);if(temp==0x55){return 0;}else{at24cxx_write_onebyte(add,0x55);temp =at24cxx_read_onebyte(add);if(temp==0x55) return 0;}return 1;
}void at24cxx_read(uint16_t add,uint8_t *pbuf,uint16_t datalen){while(datalen--){*pbuf++=at24cxx_read_onebyte(add++);} }void at24cxx_write(uint16_t add,uint8_t *pbuf,uint16_t datalen){while(datalen--){at24cxx_write_onebyte(add,*pbuf);add++;pbuf++;} }
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/AT24CXX/AT24Cxx.h"
const uint8_t g_text_buf[] = {"STM32 IIC TEST"};
#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */
int main(void)
{uint8_t key;uint8_t datatemp[TEXT_SIZE];HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */usart_init(115200); /* 串口初始化为115200 */delay_init(72); /* 延时初始化 */LED_init(); lcd_init(); /* LED初始化 */key_init();at24cxx_init();lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "IIC TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED); /* 显示提示信息 */while (at24cxx_chack()) /* 检测不到24c02 */{lcd_show_string(30, 130, 200, 16, 16, "24C02 Check Failed!", RED);delay_ms(500);lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);delay_ms(500); }lcd_show_string(30, 130, 200, 16, 16, "24C02 Ready!", RED);while(1){ key=key_scan();if (key == 1) /* KEY1按下,写入24C02 */{lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */lcd_show_string(30, 150, 200, 16, 16, "Start Write 24C02....", BLUE);at24cxx_write(0, (uint8_t *)g_text_buf, TEXT_SIZE);lcd_show_string(30, 150, 200, 16, 16, "24C02 Write Finished!", BLUE); /* 提示传送完成 */}if (key == 2) /* KEY0按下,读取字符串并显示 */{lcd_show_string(30, 150, 200, 16, 16, "Start Read 24C02.... ", BLUE);at24cxx_read(0, datatemp, TEXT_SIZE);lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); /* 提示传送完成 */lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE); /* 显示读到的字符串 */}}
}
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
void key_init(void)
{GPIO_InitTypeDef gpio_init_struct;__HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE();gpio_init_struct.Mode=GPIO_MODE_INPUT;gpio_init_struct.Pull=GPIO_PULLUP;gpio_init_struct.Pin=GPIO_PIN_4;HAL_GPIO_Init(GPIOE,&gpio_init_struct);gpio_init_struct.Mode=GPIO_MODE_INPUT;gpio_init_struct.Pull=GPIO_PULLUP;gpio_init_struct.Pin=GPIO_PIN_3;HAL_GPIO_Init(GPIOE,&gpio_init_struct);gpio_init_struct.Mode=GPIO_MODE_INPUT;gpio_init_struct.Pull=GPIO_PULLDOWN;gpio_init_struct.Pin=GPIO_PIN_0;HAL_GPIO_Init(GPIOA,&gpio_init_struct);}uint8_t key_scan(void)
{uint8_t keyval = 0;if (KEY0 == 0|| KEY1==0 || WK_UP==1){delay_ms(10); if(KEY0==0){while(KEY0==0); keyval=1;}if(KEY1==0){while(KEY1==0); keyval=2;}if(WK_UP==1){while(WK_UP==0); keyval=3;}}return keyval;}
led.c
#include "./BSP/LED/led.h"void LED_init()
{__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitTypeDef gpiob_init_struct;gpiob_init_struct.Mode=GPIO_MODE_OUTPUT_PP;gpiob_init_struct.Pin=GPIO_PIN_5;gpiob_init_struct.Speed=GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOB, &gpiob_init_struct);__HAL_RCC_GPIOE_CLK_ENABLE();GPIO_InitTypeDef gpioe_init_struct;gpioe_init_struct.Mode=GPIO_MODE_OUTPUT_PP;gpioe_init_struct.Pin=GPIO_PIN_5;gpioe_init_struct.Speed=GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOE, &gpioe_init_struct);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);}
lcd.c
此驱动代码太长,就不在展示,在百度网盘中的文件里面都有;
实验现象:
iic实验
总结:本节我们再次学习了STM32的IIC通信协议的基本知识,结合AT24C02的EEPROM进行了数据的读取实验,大家需要对IIC的时序十分熟悉,对代码的操作也需要亲自动手。有问题欢迎在评论区探讨。
创作不易,还请大家多多点赞支持!!!