I2C
I2C简介
I2C是一种两线的协议,一条使SCL,一条是SDA。
他们两个分别承担时钟线和数据线的作用,
- 时钟线SCL用来保持与主机的同步通信;
- 数据线SDA用于数据的发送与接收。
SCL的英文是 Slave Clock ,意思是给从机的时钟
SDA的英文是 Slave Data ,意思是给从机的数据
I2C的4种信号与5种速率
4种信号
I2C协议最基础的几种信号:起始、停止、应答和非应答信号。
1、起始信号与停止信号
在一个时间区间内:
- 先把SDA给拉低电平,再把SCL拉底电平就是起始信号(两个信号原先默认是高电平);
- 先把SCL给拉高电平,再把SCL拉高电平就是终止信号;(两个信号原先默认是低电平)
2、发送应答与接收应答
如图所示:
3、发送一个字节
4、接收一个字节
5种速率
I2C协议可以工作在以下5种速率模式下,不同的器件可能支持不同的速率。
- 标准模式(Standard):100kbps
- 快速模式(Fast):400kbps
- 快速模式+(Fast-Plus):1Mbps
- 高速模式(High-speed):3.4Mbps
- 超快模式(Ultra-Fast):5Mbps(单向传输)
在cubemx中配置f1芯片的时候只有前面两种,标准模式和快速模式。
I2C与多设备通信
一组I2C总线上可以挂载多个设备,如图所示:
当主机要与某一个I2C设备进行通信的时候,因为当前的线上挂载了多个设备,所以此时的信号相当于是以广播的形式发送出去的,所有设备都会接收到这个信号。
但是当前我只想和其中一个设备通信该怎么办呢?为了针对这种情况,于是每个设备都有了一个对应的设备地址(这个设备地址是由厂家设定的,同一种设备的设备地址是一样的。如,有两个AT24C02的芯片,他们的设备地址就是一样的)
我们要与设备进行通信会先发一个起始信号,再发送一个设备地址字节信号,所有的设备都会接收到这串消息,但是只有与自己的设备号对应上了,才能算是开始通信。
对于AT24C02的设备地址信号就是0xA0和0xA1(在A0、A1、A2引脚都拉地的情况下),这两个字节的设备地址的区别就是在与写和读的区别,0xA0是写设备信号,而0xA1是读设备信号。
AT24C02
AT24C02简介
AT24C02是ATMAL公司开发的串行 COMS 型 EEPROM存储类芯片,与他同类型的芯片还有AT24C01A、AT24C04、AT24C08A、AT24C016A,
这些芯片唯一的区别就是存储容量不同,后面是1的就是1Kbit、后面是2的就是2Kbit,以此类推。
芯片引脚介绍
AT24C02有8个引脚:
- A0、A1、A2:这三个是设备地址选择引脚
- WP:写保护引脚,高低平有效,一般给他拉底电平
- SCL:I2C时钟线
- SDA:I2C数据线
AT24C02地址选择
对于AT24C02来说,如果把A0、A1、A2都拉到地上,那么器件地址就是0xA0或0xA1,一般来说也是把A0、A1、A2都拉到地上
CubeMX的配置
这里用的芯片是野火的指南者STM32F103VET6
新建工程
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3.配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
5. I2C参数配置
选择I2C1中打开I2C,右边会出现PB7为SDA、PB6为SCL。
这些配置从上到下分别是:
- I2C Speed Mode:配置I2C速率,这里有标准模式、快速模式。
- I2C Clock Speed(Hz):这里不用配置,上面,那个Speed Mode配置好了,这里会默认变成对应的速率。
- Clock No Stretch Mode:时钟不拉伸,这里选择disabled,意思是选择不拉伸时钟,感觉这里就是为了匹配一些速度慢设备的,而专门搞了一个配置。
- Primary Address Length selection:设备地址长度选择,有7位的有10位的,默认7位即可。
- 后面的其他配置我不懂,但我知道配置默认即可。
6_1. 工程配置1
6_2. 工程配置2
这里要注意,工程名一定不能有中文,并且路径下也一定不能有中文
7生成工程
代码部分
AT24C02.h
#ifndef __AT24C02_H_
#define __AT24C02_H_
#include "i2c.h"
//修改对应的I2C句柄
#define AT24C02_I2C hi2c1
void EEPOM_Clear_Single(uint16_t i); //使第i个字节的值归0
void EEPOM_Empty(void); //使清空EEPOM内存
void Wrinte_EEPOM(uint16_t i, uint8_t data); //在第i个字节的位置写入SIZE__大小的data
uint8_t Read_EEPOM(uint16_t i); //读取第个字节的数据
#endif
AT24C02.c
#include "stdarg.h"
#include "AT24C02.h"
#define ADDR_24LCxx_Write 0xA0
#define ADDR_24LCxx_Read 0xA1
#define AT24C02_NUM_Byte 256 //AT24C02的大小
/*清除单个位置的数据*/
void EEPOM_Clear_Single(uint16_t i)
{
uint8_t clear__ = 0;
HAL_I2C_Mem_Write(&AT24C02_I2C, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT, &clear__, 1, 1000);
}
/*清除全部数据*/
void EEPOM_Empty(void)
{
uint8_t clear__ = 0;
for(uint16_t i=0; i<256; i++)
{
EEPOM_Clear_Single(i);
}
}
void Wrinte_EEPOM(uint16_t i, uint8_t data)
{
/* wrinte date to EEPROM */
HAL_I2C_Mem_Write(&AT24C02_I2C, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
}
uint8_t Read_EEPOM(uint16_t i)
{
uint8_t data;
/* read date from EEPROM */
HAL_I2C_Mem_Read(&AT24C02_I2C, ADDR_24LCxx_Read, i, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
return data;
}
Login.h
#ifndef LOGIN__H___
#define LOGIN__H___
#include "usart.h"
/*修改对应串口句柄*/
#define PASS_UART_HAND huart1
#define PASS_UART USART1
/*****密码长度选择*****/
typedef enum{
PASS_LENGTH_4 = 4,
PASS_LENGTH_5 = 5,
PASS_LENGTH_6 = 6,
PASS_LENGTH_7 = 7,
PASS_LENGTH_8 = 8,
}PASSWORD_LENGTH;
/*****AT中分给密码区的空间*****/
typedef enum{
AT_DOWN_PA_RE = 0,//密码下限区
AT_UP___PA_RE = 7,//密码上限区
AT_PASS__SIZE = 8
}PASSWORD_REGION;
/*****密码类型*****/
typedef enum{
LOGGED_NOT = 0,//未登入
LOGGING_IN = 1,//登入中
}LOGIN_STATUS;
/*****密码选择确认或者撤销*****/
typedef enum{
PASS_REVOKE = 11, //撤销
PASS_CONFIRM = 12, //确认
PASS_EMPTY = 13, //清空
}PASS_YES_OR_NO;
/*****登入后其他操作*****/
typedef enum{
CHANGE_PASS = 14, //修改密码
RESET_PASS = 15, //重置密码
EXIT_LOGIN = 16 //退出登入
}LOGIN_IN_OPERATION;
/*密码设定长度*/
#define PASSLEN PASS_LENGTH_4
/*****密码句柄*****/
typedef struct Pass_Hand{
uint8_t Login_Status; //登入状态,取值在LOGIN_STATUS中
uint8_t * Password_Buf; //密码缓冲区
uint8_t a_Password_Buf; //密码中转站
uint8_t Password_Buf_Cnt; //密码缓存计数
ErrorStatus (*Init_PossWord)(void); //初始化函数
ErrorStatus (*Reset_PossWord)(void); //复位清除原来的密码,并重新初始化密码
ErrorStatus (*Revise_PossWord)(uint8_t*); //修改密码,用新的密码替换原来的密码
ErrorStatus (*Match_PossWord)(uint8_t*); //密码匹配,拿用户输入的密码和设定的密码进行匹配
// struct Pass_Hand* yxypass;
}Password_hand;
extern Password_hand yxypassword; //句柄
/*函数区*/
/*输入密码提示*/
void Password_Promot(void);
/****未登入回调函数****/
/*
未登入:
-- 撤销密码
-- 确认密码
-- 清空密码
-- 其他:
-- 判断密码是否超值:
-- 超值,舍弃
-- 没超值,输入一位密码
*/
void Not_Login_in(void);
/****已登入回调函数****/
/*
已登入:
-- 退出登入
-- 确认密码
-- 清空密码
-- 其他:
-- 判断密码是否超值:
-- 超值,舍弃
-- 没超值,输入一位密码
*/
void Login_in(void);
#endif /*LOGIN__H___*/
Login.c
#include "AT24C02.h"
#include "Login.h"
#include "redirect.h" //试着删
#define D_MS_Len 2
/*内部函数声明区*/
static ErrorStatus Password_Init(void);
static ErrorStatus Password_Reset(void);
static ErrorStatus Password_Revise(uint8_t* User_Pass);
static ErrorStatus Password_Match(uint8_t* User_Input_Pass);
static uint8_t PasswordBuf[PASS_LENGTH_8] = {0}; //密码缓冲区,按最大密码量来设置大小
/*密码延时*/
#define Pass_Delay_Ms yxy_delay_ms
Password_hand yxypassword = {
LOGGED_NOT, //登入状态(未登入)
PasswordBuf, //密码缓存区
0, //密码中转站
0, //密码计数
Password_Init, //初始化密码函数
Password_Reset, //重置密码函数
Password_Revise, //修改密码
Password_Match //密码匹配
};
/*
AT24C02中的密码区(个人设定):0~7字节的位置
*/
void yxy_delay_us(uint32_t us)
{
SysTick->CTRL =0;
SysTick->LOAD =72*us-1;
SysTick->VAL =0;
SysTick->CTRL =5;
while((SysTick->CTRL & 0x10000) == 0);
SysTick->CTRL =0;
}
void yxy_delay_ms(uint32_t ms)
{
while(ms--)
{
SysTick->CTRL =0;
SysTick->LOAD =72000-1;
SysTick->VAL =0;
SysTick->CTRL =5;
while((SysTick->CTRL & 0x10000) == 0);
}
SysTick->CTRL =0;
}
/****初始密码****/
/*
初始一个默认密码:1234……
*/
/*
(SUCCESS)0 是重置成功的
(ERROR )1 是重置失败的
*/
static ErrorStatus Password_Init(void)
{
/*默认密码*/
uint8_t DefaultPassword[PASSLEN] = {1,1,1,1};
/*在密码区中对密码初始化*/
for(uint8_t i=AT_DOWN_PA_RE; i<AT_UP___PA_RE; i++)
{
if(i>=PASSLEN) //判断当前数值是否超过密码设定值
{
break;
}
Wrinte_EEPOM(i, DefaultPassword[i]); //写入默认密码
Pass_Delay_Ms(D_MS_Len);
if(Read_EEPOM(i) != DefaultPassword[i]) //判断是否清除成功
{
return ERROR;
}
Pass_Delay_Ms(D_MS_Len);
}
return SUCCESS;
}
/****密码重置****/
/*
(SUCCESS)0 是重置成功的
(ERROR )1 是重置失败的
*/
static ErrorStatus Password_Reset(void)
{
/*在密码区中对密码重置*/
for(uint8_t i=AT_DOWN_PA_RE; i<AT_UP___PA_RE; i++)
{
if(i>=PASSLEN) //判断密码是否超过密码设定值
{
break;
}
Wrinte_EEPOM(i, 0); //把使用密码区的密码清零
Pass_Delay_Ms(D_MS_Len);
if(Read_EEPOM(i) != 0) //判断是否清除成功
{
return ERROR;
}
Pass_Delay_Ms(D_MS_Len);
}
Password_Init(); //复位后初始化密码
return SUCCESS;
}
/****修改密码****/
/*
(SUCCESS)0 是重置成功的
(ERROR )1 是重置失败的
*/
static ErrorStatus Password_Revise(uint8_t* User_Pass)
{
/*在密码区中修改密码*/
for(uint8_t i=AT_DOWN_PA_RE; i<AT_UP___PA_RE; i++)
{
if(i>=PASSLEN) //判断密码是否超过密码设定值
{
break;
}
Wrinte_EEPOM(i, User_Pass[i]);//在EEPOM中写入密码
Pass_Delay_Ms(D_MS_Len);
if(Read_EEPOM(i) != User_Pass[i]) //不等于就直接退出
{
return ERROR;
}
Pass_Delay_Ms(D_MS_Len);
}
return SUCCESS;
}
/****匹配密码****/
/*
(SUCCESS)0 是重置成功的
(ERROR )1 是重置失败的
*/
static ErrorStatus Password_Match(uint8_t* User_Input_Pass)
{
/*在密码区中匹配密码*/
for(uint8_t i=AT_DOWN_PA_RE; i<AT_UP___PA_RE; i++)
{
if(i>=PASSLEN) //判断密码是否超过密码设定值
{
break;
}
if(User_Input_Pass[i] != Read_EEPOM(i))//密码一个一个比较
{
return ERROR;
}
}
return SUCCESS;
}
void Password_Input_sigle(void)
{
}
/****密码提示****/
void Password_Promot(void) //试着删
{
printf("请输入密码:\r\n");
}
/****未登入回调函数****/
/*
未登入:
-- 撤销密码
-- 确认密码
-- 其他:
-- 判断密码是否超值:
-- 超值,舍弃
-- 没超值,输入一位密码
*/
void Not_Login_in(void)
{
switch(yxypassword.a_Password_Buf)
{
case PASS_REVOKE: //撤销输入一位
if(yxypassword.Password_Buf_Cnt > 0) //减少计数后直接退出
{
printf("清除1位\r\n"); //试着删
yxypassword.Password_Buf_Cnt--;
}
break;
case PASS_CONFIRM: //确认密码
printf("12的进入\r\n"); //试着删
if(yxypassword.Password_Buf_Cnt == PASSLEN) //判断是否密码输入完
{
if(yxypassword.Match_PossWord(yxypassword.Password_Buf) == SUCCESS) //判断是否匹配正确
{
yxypassword.Login_Status = LOGGING_IN; //登入成功,进入已登入状态
yxypassword.Password_Buf_Cnt=0; //登入成功,清零计数位
printf("登入成功\r\n"); //试着删
break;
}
}
printf("登入失败\r\n"); //试着删
break;
case PASS_EMPTY: //清空输入密码
printf("清空\r\n"); //试着删
yxypassword.Password_Buf_Cnt = 0; //清空输入计数
break;
default:
if(yxypassword.Password_Buf_Cnt >= PASSLEN) //判断是否密码输入超值,超值丢弃,不超存入
{
printf("超值舍弃\r\n"); //试着删
}
else
{
//把中转站的值赋给缓冲区
yxypassword.Password_Buf[yxypassword.Password_Buf_Cnt] = yxypassword.a_Password_Buf;
yxypassword.Password_Buf_Cnt++; //密码数量加1
}
break;
}
}
/****已登入回调函数****/
/*
已登入:
-- 退出登入
-- 确认密码
-- 其他:
-- 判断密码是否超值:
-- 超值 ,舍弃
-- 没超值,输入一位密码
*/
void Login_in(void)
{
static uint8_t Change_Flag = 0;
switch(yxypassword.a_Password_Buf)
{
case CHANGE_PASS: //修改密码
printf("开始修改密码\r\n"); //试着删
Change_Flag=1; //修改密码标志位
break;
case RESET_PASS: //复位密码
if(yxypassword.Reset_PossWord() == SUCCESS)
{
printf("复位成功\r\n"); //试着删
}
else
{
printf("复位失败r\n"); //试着删
}
break;
case EXIT_LOGIN: //退出登入
yxypassword.Login_Status = 0;
printf("退出成功\r\n"); //试着删
break;
default:
if(Change_Flag == 1)//判断修改密码标志位
{
// printf("进入修改密码界面\r\n"); //试着删
switch(yxypassword.a_Password_Buf)
{
case PASS_REVOKE: //撤销输入一位
if(yxypassword.Password_Buf_Cnt > 0) //减少计数后直接退出
{
printf("清除1位\r\n");
yxypassword.Password_Buf_Cnt--;
}
break;
case PASS_CONFIRM: //确认修改密码
if(yxypassword.Password_Buf_Cnt == PASSLEN) //判断是否密码输入完
{
printf("满值的确认修改\r\n"); //试着删
if(yxypassword.Revise_PossWord(yxypassword.Password_Buf) == SUCCESS) //判断修改密码是否成功
{
yxypassword.Login_Status = LOGGING_IN; //修改成功,进入已登入状态
yxypassword.Password_Buf_Cnt=0; //修改成功,清零计数位
printf("修改成功\r\n"); //试着删
break;
}
}
printf("修改失败\r\n"); //试着删
break;
case PASS_EMPTY: //清空输入修改
printf("清空\r\n"); //试着删
yxypassword.Password_Buf_Cnt = 0; //清空输入计数
break;
default:
if(yxypassword.Password_Buf_Cnt >= PASSLEN) //判断是否密码输入超值,超值丢弃,不超存入
{
printf("超值舍弃\r\n"); //试着删
}
else
{
//把中转站的值赋给缓冲区
yxypassword.Password_Buf[yxypassword.Password_Buf_Cnt] = yxypassword.a_Password_Buf;
yxypassword.Password_Buf_Cnt++; //密码数量加1
}
break;
}
}
break;
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
static uint8_t cnt=0; //试着删
// printf("cnt%d: %d\r\n", cnt, yxypassword.a_Password_Buf);//试着删
if(huart->Instance == PASS_UART)
{
if((cnt%2==1)) //试着删
{
goto yyy;
}
if(yxypassword.a_Password_Buf>=16) //试着删
{
yxypassword.a_Password_Buf -= 6;
}
switch(yxypassword.Login_Status)
{
case LOGGED_NOT: //未登入状态
Not_Login_in(); //未登入函数
break;
case LOGGING_IN: //登入状态
Login_in();
break;
default:
break;
}
//HAL_UART_Transmit(&PASS_UART_HAND, &yxypassword.Password_Buf[yxypassword.Password_Buf_Cnt-1], 1, 10); //阻塞发送
printf("键入:%d\r\n", yxypassword.a_Password_Buf); //试着删
yyy: //试着删
HAL_UART_Receive_IT(&PASS_UART_HAND, &(yxypassword.a_Password_Buf), 1); //再次开启接收中断
cnt++; //试着删
}
}
main.c
头文件包含
主函数代码试用
效果图
登入
框起来的是一个输入,打印出来的
修改密码重新登入
框起来的是一个输入,打印出来的