原理了解
IIC总线协议
IIC:Inter Integrated Circuit,集成电路总线,是一种同步 串行 半双工通信总线。
在使用IIC时分为硬件IIC以及软件IIC,下图为两者的区别:
在使用IIC前先来了解一下IIC总线结构图,即下图:
从图中可以看出IIC有两个双向信号线,一根是数据线SDA,一根是时钟线SCL,并且都接上拉电阻,保证总线空闲状态为高电平;同时上面可以挂在多个设备,允许多主机存在,每个设备都有一个唯一的地址。
在使用IIC过程中可以归纳为以下几个比较重要的部分:
三个信号:起始信号、停止信号、应答信号
两个注意:数据有效性、数据传输顺序
一个状态:空闲状态
后续会将这几个部分拆开来看并使用代码进行实现
AT24C02器件
EEPROM是一种掉电后数据不丢失的储存器,常用来存储一些配置信息,在系统重新上电时就可以加载,而AT24C02是一个2K bit的EEPROM存储器,使用IIC通信方式。所以在本实验中将AT24C02作为我们的EEPROM。首先我们先来了解一下如何对AT24C02进行写入和读取,AT24C02的通讯地址具有8位,如下图:
前四位是固定死的为1010,后面的A0-A2三个位则为可编程部分,这七个位则构成了我们的设备地址,而最后一位为方向位,当你置1时则为读数据,置0时则为写入数据,那么八位构成我们的通讯地址,此时将A0-A2置0,最后一位也置0,则生成八位数字10100000即0xA0,这个就是我们的写操作地址,同理读操作地址则为0xA1。
完成这两个之后,根据硬件手册的写时序及读时序设置我们的函数就可以实现对EEPROM的操作啦,后续将会结合代码讲解。
STM32CubeMx配置
选好自己的板子型号,根据硬件原理图:
选用PB6以及PB7两个IO口进行软件模拟IIC,配置如下:
PB6设置为推挽输出,用作SCL线,方便输出高低电平对时钟线进行控制,PB7设置为开漏输出,用作SDA线,这里将PB7设置为开漏输出是因为SDA线既要用作输出,也要用作输入(从机应答信号),使用开漏模式则可以解决这个问题,剩下的PE3/4/5则是按键和LED方便验证程序,可根据自身需要进行设置。使用串口通信验证写入和读取,接下来就可以生成代码了。
工程生成及代码编写
工程生成
打开工程后可以看到左边已经生成了我们需要的代码:
打开gpio.c文件可以看到里面已经初始化好我们刚刚设置IO口:
代码编写
延时函数
在操作IIC的过程中需要对时间进行一定的掌控,包括高电平的稳定,电平跳变的产生,等待EEPROM写入数据,所以第一步我们先实现延时函数方便控制。
delay.c编写
#include "delay.h"
static uint16_t g_fac_us = 0; /* us延时倍乘数 */
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(HCLK)
* @retval 无
*/
void delay_init(uint16_t sysclk)
{
SysTick->CTRL = 0; /* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); /* SYSTICK使用内核时钟源8分频,因systick的计数器最大值只有2^24 */
g_fac_us = sysclk / 8; /* g_fac_us作为1us的基础时基 */
}
/**
* @brief 延时nus
* @param nus: 要延时的us数.
* @note 注意: nus的值,不要大于1864135us(最大值即2^24 / g_fac_us @g_fac_us = 9)
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * g_fac_us; /* 时间加载 */
SysTick->VAL = 0x00; /* 清空计数器 */
SysTick->CTRL |= 1 << 0 ; /* 开始倒数 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */
SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */
SysTick->VAL = 0X00; /* 清空计数器 */
}
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
uint32_t repeat = nms / 1000; /* 记录超出1000ms的值,即1s */
uint32_t remain = nms % 1000; /* 记录未超出1000ms的值 */
while (repeat)
{
delay_us(1000 * 1000); /* 利用delay_us 实现 1000ms 延时 */
repeat--;
}
if (remain)
{
delay_us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
}
}
delay.h编写
#ifndef __DELAY_H
#define __DELAY_H
#include "main.h"
void delay_init(uint16_t sysclk);
void delay_us(uint32_t nus);
void delay_ms(uint16_t nms);
#endif
IIC函数实现
完成了延时函数,首先再定义一个IIC中独立使用到的delay函数:
/* iic delay函数 */
static void iic_delay(void)
{
delay_us(2);
}
接下来根据上面所讲的三个信号来看看对应的时序是怎么完成的。
IIC起始信号
首先是起始信号:
从图中可以看出,起始信号在代码中要做的就三件事,1.保持SCL高电平;2.SDA产生一个下降沿;3.将SCL拉低,代码如下:
/* iic起始信号 */
void iic_start(void)
{
/* 保持时钟线高电平,数据线产生下降沿 */
IIC_SDA(1);
IIC_SCL(1);
iic_delay();
IIC_SDA(0);
iic_delay();
/* 拉低时钟线,准备发送/接收数据 */
IIC_SCL(0);
iic_delay();
}
IIC停止信号
再来看看停止信号:
从图中可以看出,停止信号操作跟起始信号操作大致一样,只不过SDA的下降沿改为了上升沿,代码如下:
/* iic停止信号 */
void iic_stop(void)
{
/* 保持时钟线高电平,数据线产生上升沿 */
IIC_SDA(0);
IIC_SCL(1);
iic_delay();
IIC_SDA(1);
iic_delay();
}
应答信号
最后再来看看应答信号:
从图中可以看出,在保持SCL高电平的状态下,通过读取SDA线的电平状态来判断从机是否应答,由于SDA默认是为高信号,所以通过从机操作SDA线,将SDA线拉低则视为应答,而不动则视为非应答。实现代码如下:
/* 等待应答信号 */
/* return 0:fail 1:succeed*/
uint8_t iic_wait_ack (void)
{
IIC_SDA(1); /* 主机释放SDA线 */
iic_delay();
IIC_SCL(1); /* 拉高SCL等待读取从机应答信号 */
iic_delay();
if (IIC_READ_SDA) /* SCL高电平读取SDA状态 */
{
iic_stop(); /* SDA高电平表示从机非应答 */
return 0;
}
IIC_SCL(0); /* SCL低电平表示结束应答检查 */
iic_delay();
return 1;
}
同样主机在读取从机发送数据时,也要通知从机是应答还是非应答来选择是否继续接受数据,实现函数如下:
/* 应答信号 */
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(1); /* 数据线为高电平,表示非应答 */
iic_delay();
IIC_SCL(1);
iic_delay();
}
到这里,三个信号我们就解决了。
数据的发送及读取
接下来看一下两个注意,即实现IIC发送函数及读取函数,首先根据前文所说IIC的特性之一是串行,即数据的发送是一位一位进行发送,在这里选择数据的发送顺序为高位在先,实现代码如下:
/* 发送一个字节数据 */
void iic_send_byte(uint8_t data)
{
for (uint8_t t=0; t<8; t++)
{
/* 高位先发 */
IIC_SDA((data & 0x80) >> 7);
iic_delay();
/* 拉高时钟线,稳定数据接收 */
IIC_SCL (1);
iic_delay();
IIC_SCL (0);
data <<= 1; /* 左移1位, 用于下一次发送 */
}
IIC_SDA (1); /* 发送完成,主机释放SDA线 */
}
/* 读取1字节数据 */
/* ack:通知从机是否继续发送。
0,停止发送;1,继续发送
*/
uint8_t iic_read_byte(uint8_t ack)
{
uint8_t receive = 0 ;
for (uint8_t t=0; t<8; t++)
{
/* 高位先输出,先收到的数据位要左移 */
receive <<= 1;
IIC_SCL(1);
iic_delay();
if (IIC_READ_SDA)
{
receive++;
}
IIC_SCL(0);
iic_delay();
}
/* 判断是否继续读取从机数据 */
if ( !ack )
{
iic_nack();
}
else
{
iic_ack();
}
return receive;
}
还有一个状态需要注意的是,空闲状态为高电平,所以在我们操作完SCL以及SDA两条线之后都需要将其置为高电平,代码中已实现。
iic函数头文件
#ifndef __MYIIC_H
#define __MYIIC_H
#include "main.h"
#include "delay.h"
/* 引脚定义 */
#define IIC_SCL_GPIO_PORT GPIOB
#define SCL GPIO_PIN_6
#define IIC_SDA_GPIO_PORT GPIOB
#define SDA GPIO_PIN_7
/* SCL引脚设置宏定义 */
#define IIC_SCL(x) do{x ? \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, SCL, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, SCL, GPIO_PIN_RESET); \
}while(0)
/* SDA引脚设置宏定义 */
#define IIC_SDA(x) do{x ? \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, SDA, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, SDA, GPIO_PIN_RESET); \
}while(0)
#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, GPIO_PIN_7)
void at24c02_write_one_byte(uint8_t addr, uint8_t data);
uint8_t at24c02_read_one_byte(uint8_t addr);
#endif
这样我们的IIC函数就大功告成啦,但是我们只是完成了IIC的基础功能,接下来则是添加写入/读取AT24C02函数啦,即头文件中的:
void at24c02_write_one_byte(uint8_t addr, uint8_t data);
uint8_t at24c02_read_one_byte(uint8_t addr);
AT24C02的写/读函数
AT24C02写函数
先来看看AT24C02支持的写入操作有哪些:
可以看出,若使用页写模式可能会因为操作不当导致原先的数据被覆盖,所以这里选择字节写模式。
选定好字节写模式后,接来就是了解字节写的时序并进行代码编写:
从图中我们就可以清晰的看出我们需要操作的步骤,结合IIC实现的函数即可实现对AT24C02的写操作,代码如下:
/* 写入一个字节到从机AT24C02中 */
void at24c02_write_one_byte(uint8_t addr, uint8_t data)
{
/* 1、发送起始信号 */
iic_start();
/* 2、发送通讯地址(写操作地址) */
iic_send_byte(0xA0);
/* 3、等待应答信号 */
iic_wait_ack();
/* 4、发送内存地址:0~255 */
iic_send_byte(addr);
/* 5、等待应答信号 */
iic_wait_ack();
/* 6、发送写入数据 */
iic_send_byte(data);
/* 7、等待应答信号 */
iic_wait_ack();
/* 8、发送停止信号 */
iic_stop();
/* 等待EEPROM写入完成 */
delay_ms(10);
}
在这里需要注意,AT24C02的容量为256byte,所以在传输地址参数时范围应当选择0~255,以及EEPROM的写入时间在器件手册中有标明最长是需要5ms时间,所以这里延时10ms确保其写入完成。
AT24C02读函数
接下来来看看读函数的实现,首先了解一下支持的读取操作
这里从图中可以看出随机地址读模式是比较自由的,因为可以读取指定地址的数据,所以这里选定随机地址读模式。
接下来同样先查看对应的时序图:
可以看出读时序相比较写时序则多了很多步骤,但是实质上也就是将先前编写好的IIC函数按照时序一个一个放进去就好了,代码如下:
/* 往从机AT24C02中读取一个字节 */
uint8_t at24c02_read_one_byte(uint8_t addr)
{
uint8_t read = 0;
/* 1、发送起始信号 */
iic_start();
/* 2、发送通讯地址(写操作地址) */
iic_send_byte(0xA0);
/* 3、等待应答信号 */
iic_wait_ack();
/* 4、发送内存地址:0~255 */
iic_send_byte(addr);
/* 5、等待应答信号 */
iic_wait_ack();
/* 6、发送起始信号 */
iic_start();
/* 7、发送通讯地址(读操作地址) */
iic_send_byte(0xA1);
/* 8、等待应答信号 */
iic_wait_ack();
/* 9、接收数据并发送非应答(获取该地址即可) */
read = iic_read_byte(0);
/* 10、发送停止信号 */
iic_stop();
return read;
}
至此AT24C02的写入和读取函数就完成了,接下来就是验证程序是否能实现所需的功能了。
main函数编写
/* USER CODE BEGIN Includes */
#include "key.h"
#include "myiic.h"
#include "delay.h"
/* USER CODE END Includes */
首先将需要的头文件包含进去,接下来就是功能验证:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
delay_init(72);
uint8_t t = 0;
uint8_t key = 0;
uint8_t data = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
key = key_scan();
if(key == KEY0_PRESS)
{
at24c02_write_one_byte(0, 100);
printf("write success\r\n");
}
if(key == KEY1_PRESS)
{
data = at24c02_read_one_byte(0);
printf("data:%d\r\n",data);
}
/* LED闪烁证明程序正常运行 */
t++;
if(t == 20)
{
t = 0;
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
delay_ms(10);
}
/* USER CODE END 3 */
}
这里通过板子上的两个按键,一个进行写入,一个进行读取,通过串口打印到电脑上观看数据是否成功写入,以及LED灯闪烁证明程序正常运行。文章来源:https://www.toymoban.com/news/detail-759321.html
实现效果
可以看到已经能够成功的写入及读取,这里将数据改为50再来看看
修改为50也成功的写入了,证明代码是没有问题的。
至此软件模拟IIC实验就完成了,也希望大家可以指出有不对地方共同学习,谢谢!文章来源地址https://www.toymoban.com/news/detail-759321.html
到了这里,关于STM32软件模拟实现IIC写入和读取AT24C02(STM32CubeMx配置)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!