STM32---IIC通信协议(含源码,小白进)
节我们再次学习了IIC通信协议的基本知识,结合AT24C02的EEPROM进行了数据的读取实验,
写在前面:在前面的学习过程中,我们学习了串口通信的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的时序十分熟悉,对代码的操作也需要亲自动手。有问题欢迎在评论区探讨。
创作不易,还请大家多多点赞支持!!!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)