Appearance
存储器
常用的存储器
一般来说易失性存储器存储的速度比较快
RAM随机存储器
随机读取任意地址, 现在一般专用于半导体种类的易失存储器
- DRAM动态随机存储器=>硬盘, 动态的, stm32f10x不能使用, 因为没有刷新用的电路
使用电容的电荷表示, 会有放电, 写入之后会掉电, 导致出错, 需要有一段时间就进行充电或者放电
存储速度慢, 集成度高, 生产成本低, 需要刷新
由于通讯方式不同有进行分类
在时钟线上升沿或者下降沿判断数据有效, SDRAM上升沿的时候判断数据有效, DDRII上升沿下降沿都有效800MHz, DDRIII上升沿下降沿有效时钟频率1600MHz
- SRAM静态随机存储器=>静态的
实际上是一个锁存器, 会保持稳态
存储速度块, 集成度较低, 生产成本较高, 不需要刷新
ROM
MASK ROM: 只读, 不能写, 出厂的时候固化
OTPROM: 可以写一次, 写入之后不能修改
EPROM: 高电压擦除, 可以重复擦写, 需要专门装备
EEPROM: 低电压可以擦除, 方便实用, 一般来说储存的比较小, 成本比较高
FLASH
NOR FLASH: 写入的速度比较低, 一般存储程序, 比较贵, 结构复杂, 随机储存, 但是支持XIP且坏块比较少, 地址线数据线分开, 支持代码的运行
NAND FLASH: 容量较大,改写速度快等优点,适用于大量数据的存储,因而在业界得到了越来越广泛的应用,如嵌入式产品中包括数码相机、MP3随身听记忆卡、体积小巧的U盘等, 用来存储数据, 必须以块进行读写, 地址线数据线不分开
NOR Flash使用并行架构,允许在任意地址上进行随机访问,而NAND Flash使用串行架构,只能按页进行顺序访问。
可以使用FLASH制造SSD硬盘
FLASH在写入之前只能先进行擦除
I2C协议
同步串行半双工总线
硬件
只需要两根线就可以实现, 在集成电路中广泛使用
支持多个设备连接, 多个设备公用的信号线
SCL: 串行时钟线
SDA: 串行数据线
寻址机制: 通过一个地址, 在设计硬件的时候就已经设置好了, 通过SDA线进行发送, 有七位或者是十位
有一个上拉电阻, 一般是4.7K
连接的数量受到最大电容400pF限制
当空闲的时候设备输出高阻态, GPIO的开漏模式, 总线不会受到影响, 多个主机使用的时候为防止冲突, 会使用仲裁大方式进行选择, 有三种传输模式, 标准模式100Kbit/s, 400kbit/s, 3.4Mbit/s
软件
首先传输起始信号
之后是从机地址
R/W选择是读还是写
等待一个应答信号
传输数据
时钟信号和起始信号都是主机产生的
一般都是先写之后再进行读
SCL高电平的时候SDA向低电平切换, 表示起始
实际的数据
一般在传输的时候把七位的传输地址加上一位的读或者写叫做读地址或者叫做写地址
STM32实现I2C
stm32硬件I2C在某些情况下会出现问题
支持主机和从机
①通讯引脚, 还有一个引脚是为了兼容SMBus协议
②时钟控制逻辑, SCL线的时钟信号, 根据寄存器CCR控制, 在快速模式下可以选择时钟的占空比, 外设时钟源为PCLK1
③数据控制逻辑, 比较器用来比较自身的地址, PEC寄存器拿来数据校验, 很少使用
④整体控制逻辑,
寄存器
- I2C_CR1: 控制寄存器1
I2C_CR2: 控制寄存器2
I2C_OAR1: 自身地址寄存器
I2C_OAR2: 自身地址寄存器2
I2C_DR: 数据寄存器
I2C_SR1: 状态寄存器
I2C_SR2: 状态寄存器2
- I2C_CCR: 时钟控制寄存器
T~pclk~ = 1/36M, 可以计算出实际的数值
- I2C_TRISE: TRISE寄存器, TRISE代表“Timer Input Rise Time”。它是用于定时器输入信号上升时间的配置位或参数。
手册中的过程
- EVx实际上是对应的状态寄存器变化
SB: 起始位发送完成
ADDR: 地址位发送完成
TxE: 数据寄存器为空
BTF: 数据字节发送完成(移位寄存器为空)
RxNE: 接收的时候数据寄存器为非空
状态位需要清除
使用函数I2C_CheckEvent可以直接检查对应的事件, 这个函数自动清除事件
库实现
c
typedef struct
{
uint32_t I2C_ClockSpeed; /*!< Specifies the clock frequency.
This parameter must be set to a value lower than 400kHz时钟频率 */
uint16_t I2C_Mode; /*!< Specifies the I2C mode.
This parameter can be a value of @ref I2C_mode 设置使用I2C或者SMBus*/
uint16_t I2C_DutyCycle; /*!< Specifies the I2C fast mode duty cycle.
This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode
高速模式下时钟的比例*/
uint16_t I2C_OwnAddress1; /*!< Specifies the first device own address.
This parameter can be a 7-bit or 10-bit address.自己的地址设置地址1
使用函数可以设置另一个地址*/
uint16_t I2C_Ack; /*!< Enables or disables the acknowledgement.
This parameter can be a value of @ref I2C_acknowledgement是否使能应答 */
uint16_t I2C_AcknowledgedAddress; /*!< Specifies if 7-bit or 10-bit address is acknowledged.
This parameter can be a value of @ref I2C_acknowledged_address
设置使用的地址*/
}I2C_InitTypeDef;
EEPROM(AT24C02)
32个页, 每一页8字节
链接I2C的两个引脚, 256字节
WP: 写保护, 这里直接接地不使能
A0, A1, A2: 控制地址
在实际发送的时候输入的是八位写地址
在实际使用的时候首先选定地址, 之后发送要保存的地址, 然后输入多个要保存的数据, 最多可以写入八个字节(一页)
直接读取的时候返回当前地址
读的时候没有限制, 读到最后以后会返回开头
在写之后需要检测写完成
c
#include "bsp_i2c.h"
void I2C_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//初始化时钟
EEPROM_I2C_GPIO_APBxClkCmd(EEPROM_I2C_GPIO_CLK, ENABLE);
EEPROM_I2Cx_APBxClkCmd(EEPROM_I2Cx_CLK, ENABLE);
//设置GPIO, 均为推挽输出
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(EEPROM_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_GPIO_PIN;
GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);
//初始化I2C
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_ClockSpeed = EEPROM_I2Cx_BAUDRATE;//时钟速率
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//使用的模式
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//时钟时间比率
I2C_InitStructure.I2C_OwnAddress1 = EEPROM_I2Cx_ADDRESS;//自己的地址
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//使能应答
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//七位地址
I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
I2C_Cmd(EEPROM_I2Cx, ENABLE);
}
void EEPROM_Byte_Write(uint8_t addr, uint8_t data)
{
//发送成功的函数
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
//发送成功
//发送地址
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS_PERI, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
//发送一个数据, 要操作的地址
I2C_SendData(EEPROM_I2Cx, addr);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
//写入数据
I2C_SendData(EEPROM_I2Cx, data);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
void EEPROM_Write(uint8_t addr, uint8_t *data, uint8_t num)
{
//发送成功的函数
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
//发送成功
//发送地址
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS_PERI, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
//发送一个数据, 要操作的地址
I2C_SendData(EEPROM_I2Cx, addr);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
//发送一个数据, 要操作的地址
while(num)
{
//写入数据
I2C_SendData(EEPROM_I2Cx, *data);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
data++;
num--;
}
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
//进行读取
void EEPROM_Read(uint8_t addr, uint8_t *data, uint8_t num)
{
//发送成功的函数
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
//发送成功
//发送地址, 这时候为读, 如果间隔较小会在这一步没有应答
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS_PERI, I2C_Direction_Transmitter);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
//发送一个数据, 要操作的地址
I2C_SendData(EEPROM_I2Cx, addr);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);
//第二次信号
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
//发送地址
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS_PERI, I2C_Direction_Receiver);
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);
//开始接受, EV7被检查到, 有新的信息
while(num)
{
if(num==1)
{
//最后一次时候设置为不响应
I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
}
//检查接收到数据时候的信号
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);
*data = I2C_ReceiveData(EEPROM_I2Cx);
data++;
num--;
}
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
//恢复自动响应
I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
}
void EEPROM_WaitForWrite(void)
{
do{
//发送成功的函数
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_SB)==RESET);
I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_SB);
//发送成功
//发送地址, 这时候为读, 如果间隔较小会在这一步没有应答
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS_PERI, I2C_Direction_Transmitter);
}
while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_ADDR) == RESET);
//实现之后直接结束这一次==测试没有也行
//I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
问题
首先是由于stm32硬件问题, 会导致数据线不释放, 所以下载其他程序之后重新上电可以解决
在进行页写入的时候最大值为8, 并且首地址为可以被8整除的数字
在写之后不能立刻进行读取
还可以在进去之前检测BUSY标志
软件实现
c
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H
#include <inttypes.h>
#define EEPROM_I2C_WR 0 /* 写控制bit */
#define EEPROM_I2C_RD 1 /* 读控制bit */
/* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define EEPROM_GPIO_PORT_I2C GPIOB /* GPIO端口 */
#define EEPROM_RCC_I2C_PORT RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define EEPROM_I2C_SCL_PIN GPIO_Pin_6 /* 连接到SCL时钟线的GPIO */
#define EEPROM_I2C_SDA_PIN GPIO_Pin_7 /* 连接到SDA数据线的GPIO */
/* 定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 */
#if 0 /* 条件编译: 1 选择GPIO的库函数实现IO读写 */
#define EEPROM_I2C_SCL_1() GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN) /* SCL = 1 */
#define EEPROM_I2C_SCL_0() GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN) /* SCL = 0 */
#define EEPROM_I2C_SDA_1() GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* SDA = 1 */
#define EEPROM_I2C_SDA_0() GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() GPIO_ReadInputDataBit(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN) /* 读SDA口线状态 */
#else /* 这个分支选择直接寄存器操作实现IO读写 */
/* 注意:如下写法,在IAR最高级别优化时,会被编译器错误优化 */
#define EEPROM_I2C_SCL_1() EEPROM_GPIO_PORT_I2C->BSRR = EEPROM_I2C_SCL_PIN /* SCL = 1 */
#define EEPROM_I2C_SCL_0() EEPROM_GPIO_PORT_I2C->BRR = EEPROM_I2C_SCL_PIN /* SCL = 0 */
#define EEPROM_I2C_SDA_1() EEPROM_GPIO_PORT_I2C->BSRR = EEPROM_I2C_SDA_PIN /* SDA = 1 */
#define EEPROM_I2C_SDA_0() EEPROM_GPIO_PORT_I2C->BRR = EEPROM_I2C_SDA_PIN /* SDA = 0 */
#define EEPROM_I2C_SDA_READ() ((EEPROM_GPIO_PORT_I2C->IDR & EEPROM_I2C_SDA_PIN) != 0) /* 读SDA口线状态 */
#endif
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
#endif
c
#include "bsp_i2c_gpio.h"
#include "stm32f10x.h"
static void i2c_CfgGpio(void);
/*
*********************************************************************************************************
* 函 数 名: i2c_Delay
* 功能说明: I2C总线位延迟,最快400KHz
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
uint8_t i;
/*
下面的时间是通过逻辑分析仪测试得到的。
工作条件:CPU主频72MHz ,MDK编译环境,1级优化
循环次数为10时,SCL频率 = 205KHz
循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
*/
for (i = 0; i < 10; i++);
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线启动信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
EEPROM_I2C_SDA_1();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_0();
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Stop
* 功能说明: CPU发起I2C总线停止信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
EEPROM_I2C_SDA_0();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_SendByte
* 功能说明: CPU向I2C总线设备发送8bit数据
* 形 参:_ucByte : 等待发送的字节
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
EEPROM_I2C_SDA_1();
}
else
{
EEPROM_I2C_SDA_0();
}
i2c_Delay();
EEPROM_I2C_SCL_1();
i2c_Delay();
EEPROM_I2C_SCL_0();
if (i == 7)
{
EEPROM_I2C_SDA_1(); // 释放总线
}
_ucByte <<= 1; /* 左移一个bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 数 名: i2c_ReadByte
* 功能说明: CPU从I2C总线设备读取8bit数据
* 形 参:无
* 返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
EEPROM_I2C_SCL_1();
i2c_Delay();
if (EEPROM_I2C_SDA_READ())
{
value++;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
}
return value;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_WaitAck
* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
* 形 参:无
* 返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
i2c_Delay();
if (EEPROM_I2C_SDA_READ()) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
EEPROM_I2C_SCL_0();
i2c_Delay();
return re;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Ack
* 功能说明: CPU产生一个ACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
EEPROM_I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
EEPROM_I2C_SDA_1(); /* CPU释放SDA总线 */
}
/*
*********************************************************************************************************
* 函 数 名: i2c_NAck
* 功能说明: CPU产生1个NACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
EEPROM_I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
EEPROM_I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
EEPROM_I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CfgGpio
* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void i2c_CfgGpio(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(EEPROM_RCC_I2C_PORT, ENABLE); /* 打开GPIO时钟 */
GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; /* 开漏输出 */
GPIO_Init(EEPROM_GPIO_PORT_I2C, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_CfgGpio(); /* 配置GPIO */
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address | EEPROM_I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}