🚀write in front🚀
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝💬本系列哔哩哔哩江科大51单片机的视频为主以及自己的总结梳理📚
前言:
本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记,在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。
引用:
51单片机入门教程-2020版 程序全程纯手打 从零开始入门_哔哩哔哩_bilibili
c51语言变量语句意思,C51中循环语句-CSDN博客
51单片机和AT24C02链接:
51之AT24C02数据存储(I2C总线)_at24c02的地址线-CSDN博客
江协科技 51单片机 AT24C02(I2C总线) 学习笔记——略带拓展_iic总线江协科技-CSDN博客
51单片机系列--AT24C02(总线)_at24c02电路-CSDN博客
ROM简介:
ROM学习笔记1-ROM、PROM、EPROM、EEPROM_掩膜rom-CSDN博客
正文:
0. 🌿概述
在淘宝上购买了江协科技51单片机开发板套件(普中科技STC51单片机A2型号),就上在上一篇博文里说的自己计划学习下江协科技51单片机开发教程,通过STC51单片机这种MCU这种贴近于裸机的开发来增加对于系统硬件层面知识的了解和掌握。
术语和缩略语:
缩写 | 全称 | 说明 |
I2C | Inter-Integrated Circuit | 一种串行通信总线 |
SDA | (Serial Data) | 串行数据线,用于数据传输 |
SCL | (Serial Clock) | 串行时钟线 |
1. 🚀 存储器介绍
正如图所示,存储器主要分为2大类,RAM(Random Access Memory,随机存取存储器)和ROM(Read-Only Memory,只读存储器)。RAM由于是随机存储,所以存储速度快,但是它存在掉电数据丢失的缺点;ROM就正好相反,它存储速度较慢,但是掉电不丢失。
1.1 RAM
RAM主要分成两种:SRAM和DRAM。
🌳SRAM(Static RAM,静态RAM),速度比DRAM还更快,内部结构主要是使用触发器来完成数据存储,现在广泛使用于我们的电脑CPU等需要快速缓存的地方,缺点就是相对较贵。
🌳DRAM(Dynamic RAM,动态RAM),速度稍慢于SRAM,内部主要基于电容来存储数据,所以价格稍微更低。但是电容在存入电信号之后会放电导致电流失,所以DRAM就要每隔一段时间再把数据重新读入,从而完成数据的持续存储。
1.2 ROM
ROM分为很多种,有:Mask ROM(掩膜ROM),PROM(可编程ROM),EPROM(可擦除编程ROM),E2PROM(电可擦除可编程ROM),Flash(闪存),以及硬盘,软盘,光盘等。
- 🌳Mask ROM(掩膜ROM)
- 🌳PROM(可编程ROM)
- 🌳EPROM(可擦除编程ROM,有一个开窗可以用紫外线照射进行ROM数据擦除)
- 🌳E2PROM(电可擦除可编程ROM)
- 🌳Flash(闪存)
- 🌳硬盘,软盘,光盘等
1.2.1Mask ROM(掩膜ROM)
作为最早出现的一种ROM,它的缺陷自然是很多的,首先它的主要结构是下面的这种二极管:
并且,由于使用的是这种二极管为基础的电路,它存储的数据就无法被更改,所以得名只读存储器。Mask ROM的数据写入是不可逆的,即一旦数据被写入,就无法更改。这种类型的存储器通常用于存储固定内容,因为它的成本相对较低,且由于其不可擦写的特性,适合于大批量生产。Mask ROM的优点包括成本较低和稳定性较高,因为它们不像其他可擦写存储器那样需要额外的擦写机制。然而,它们的缺点是缺乏灵活性,一旦制造完成,就不能更改存储在其中的数据。
🌳掩膜ROM的读取原理,是按照行列的方式扫描,横线为地址线,竖线为数据输出线,地址横线选中加上高电平,因为二极管的单向导电性,掩膜有二极管的交叉线(相当于一个bit位)就读取到高电平,没有掩膜二极管的就读取到低电平,通过这种方式就可以存储0或者1,这也就是掩膜ROM的工作模型。
1.2.2PROM(可编程ROM)
PROM,即Program ROM,也称为可编程ROM,它的主要基本原理也是基于二极管:
🌲这里的蓝色二极管是根据我们写入的程序更改的,不同于Mask ROM,Mask ROM的数据是在厂家生产时就定下来了,PROM的数据则是在写入程序的时候生成,所有的电路都具备一对反向的二极管,这样的组成,当我们写入数据为高电平的时候,会触发击穿电压,从而达到写入数据的目的,因为是把这个蓝色的二极管击穿,所以我们写入程序也叫“烧入程序”,名字就是由此而来。但是PROM依旧是不能够擦除数据,只能写入程序,所以它的局限性依旧很大。
1.2.3EPROM(可擦除编程ROM)
由于时代的变迁和技术的革新,ROM的材料也开始变革,所以产生了EPROM,即可擦除编程ROM,它无非就是在PROM的二极管基础上更新材料,变成了可以恢复的一种材料,导致半导体击穿之后还可以通过一定的技术手段恢复,所以称为可擦除编程ROM。
通过紫外线照射擦写ROM的数据,ROM芯片封装上有一个开口,用紫外线灯照射就可以擦除数据。
擦除:EPROM的die会漏在外面,如下图所示。擦除时,只需要用如紫外灯照射20min即可。
1.2.4E2PROM(电可擦除可编程ROM)
由于技术的革新,产生了电可擦除编程ROM,E2PROM和前面的EPROM的区别就是:E2PROM可以通过电直接擦除数据,但是EPROM是不能用电直接擦除的,所以使用电擦除数据,也是一种使用电控制电的手段,也是现在大部分ROM存储的前身。
🌲EEPROM全称Electricially Erasable Programmable Read Only Memory, 通过分析EPROM的原理可知,SIMOS的二氧化硅层比较厚,势垒比较高,所以不管是往浮栅中充入电子还是擦除电子,都需要给电子充较大的能量,使用上的体现就是较高的电压和长时间的光照擦除。后来科学家门对其结构进行改进,在浮栅和漏区设置了一个极薄的氧化层区域,如下图所示。这样的mos管学名叫浮栅隧道氧化层MOS管(Floating-gate Tunnel Oxide MOS),又称Flotox管。
🌲Flotox管的工作原理与SIMOS类似,只是给浮栅充电方式是隧穿(Fowler-Nordheim隧穿),而不是热电子注入。当隧道区的电场足够大时,电子就能在电场的作用下通过隧道形成隧道电流,这种现象叫隧道效应。
- 🌲编程前,浮栅上没有电荷,就是一个普通NMOS。
- 🌲编程时,漏源均接地,控制栅上施加一个20V的脉冲。此时,隧道区产生强电场,吸引漏极的电子穿过隧道到达浮栅。撤除电压后,浮栅上的电子可以永久保留(通常可达十年以上)。
- 🌲擦除时,源极与控制栅接地,在漏极上施加一个20V的脉冲。类似编程,隧道区产生一个反向的强电场,浮栅上的电子又被吸引回漏极,从而擦除信息。
EEPROM的存储单元由两个MOS管组成,T1为Flotox管,T2为普通mos管。
读过程:当要读这个存储单元时,首先给字线施加一个高电平,选通T2。如果T1管未经过编程,浮栅上不带电,在控制栅上施加正电压可以导通T1,位线上读到0;若浮栅中带电荷,控制栅上给高电平无法使T1管导通,位线上读到1(位线上有未画出的上拉到高电平的部分,常态读取时将电压拉高)。
擦除(相当于写1):擦除时,在字线和控制栅线上施加一个高电平脉冲,同时将位线拉低。在脉冲时间内,T2导通,T1的Ugd之间为20V高压,T1管被编程,浮栅上带了电荷。
写过程:对于要写0的存储单元,在字线和位线上施加一个高电平脉冲,同时将控制栅线拉低。在脉冲时间内,T2导通,T1的Ugd之间为-20V高压,T1管浮栅上的电荷被释放(如果有电荷的话)。
1.2.5Flash(闪存)
现在最为广泛使用的ROM存储手段,非常适合用作便携式设备的存储介质,如USB闪存驱动器、智能手机、平板电脑和其他嵌入式系统,广泛适用于各个地方,包括我们的51单片机,堪称ROM唯一真神。
当然,除了上面这些,还有硬盘,软盘,光盘等ROM式的存储,我们这里就不多赘述了。
2.地址总线
地址总线较为复杂,我们可以简化成上面的原理图进行说明:首先,地址总线上对应的是不同的设备,一般前面还会加一个译码器,通过对应的地址找到需要选中的设备,并且只能选中一行,这样就可以实现每次只有一个设备写入读取数据,就不会搞混了。选中设备后,设别就把数据写入到数据总线中去了。
3.AT24C02芯片简介
AT24C02芯片
存储介质: E2PROM
通讯接口: I2C 总线
容量: 256 字节
引脚 | 功能 |
VCC、GND | 电源(1.8V~5.5V ) |
WP | 写保护(高电平有效) |
SCL、SDA | I2C接口 |
A0、A1、A2 | I2C地址 |
AT24C02芯片是一个典型的掉电不丢失的ROM芯片,在单片机中,它的原理图是这样的:
这里写的是EEPROM,其实就是我们前面所说的E2PROM,电可擦除编程ROM,这里的这个引脚好像和前面标注的不太一样,其实是一样的,只是不同的工程师对它有不同的命名罢了。
🌳还有这里有一个WE,上面有一个横线,在这里就表示Write Eable,即写入使能且低电平有效,意思就是接低电平这个芯片才可以写入数据,在原来的引脚定义图里面其实是WP即Write Protect,写保护,高电平有效,意思就是高电平开启写保护,此时无法写入数据,相反的,想要写入数据就要低电平,在普中单片机的原理图里面,这个引脚接GND,即默认这个引脚就是低电平,无论什么时候都是可以直接写入数据的。
所以,普中科技51单片机开发板在使用单片机的时候其实只要配置SCL,SDA还有三根数据线就行了。它们都是使用的I2C接口,所以我们这里就先了解一下I2C总线。
4.I2C总线
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线,它使用两根通信线:SCL(Serial Clock)、SDA(Serial Data)。
- 🌳工作特点是:同步、半双工,带数据应答
- 🌳前面在讲串口通信的时候稍微提了一下I2C还有这个半双工和同步
- 🌳同步且半双工就是下面这样:两个设备通过一根时钟线约定时钟周期(同步),在使用通信的两个设备可以通过同一个时钟线互相传递数据。
- 1.🌿所有I2C设备的SCL连在一起,SDA连在一起。总线结构,在线上外接各种设备。如图中所示。
- 2🌿.设备的SCL和SDA均要配置成开漏输出模式
这张图就是我们单片机IO口的输出线路,可以将控制器理解为一个电源连上一个无限大的电阻,Q1是一个控制开关的MOS口,当Q1打开时相当于此处不分压,所以输出低电平;当Q1关闭时,Q1处的电阻无限大,外部的VCC和控制器内部形成一个串联电路电阻相比于Q1很小,所以输出高电平。(我们可能在视频中常听到的强/弱上拉就是改变上拉电阻的阻值来达到控制输出电流电压大小的情况。)
- 🌿开漏输出模式就是将此图中的VCC和上拉电阻去掉,那么在Q1关闭时电路相当于断开,输出的电平是不稳定,非常容易被影响的。
- 3.🌿SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
- 4.🌿开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
这是一个设备的内部电路图:
- 🌿I2C总线协议使用两条通信线,一条是SCL串行时钟线,一条是SDA串行数据线。
- 🌿I2C总线上的所有设备的SCL总线连接在一起,SDA总线连接在一起。
- 🌿I2C总线是同步,半双工,带应答的协议。
- 🌿I2C总线的IO口需要配置为开漏输出模式,并且SCL,SDA总线上接上拉电阻,一般接上拉电阻为 4.7K~10K。
- 🌿I2C总线当SCL为高电平,SDA为高电平时为空闲状态。
- 🌿I2C总线的通信速率一般是 100Kb/s,高速I2C总线速率为 400Kb/s。
I2C总线是半双工通信协议,也就是同一时间数据只能由主机发送从机接受,或者从机发送主机接收,I2C总线的通信时钟总是由主机控制的,从机按照主机的时钟信号做出反应。
🔍️I2C总线是真正的多主机多从机通信协议,I2C主机和从机通信之间需要先进行从机地址呼叫,I2C从机的地址一般是7位,最多支持2 ^7=128个从机地址。
🔍️I2C从机地址的高4位是向飞利浦公司申请的,第3位地址是设备厂商自行设置的。
4.1 I2C通信协议
起始条件 | SCL为高电平,SDA从高电平变为低电平表示起始信号 |
结束条件 | SCL为高电平,SDA从低电平变为高电平表示接受信号 |
应答 | I2C固定每发送或接受8个数据位,需要回复应答ACK或者非应答 应答ACK=0 非应答NACK=1 |
非应答 | I2C固定每发送或接受8个数据位,需要回复应答ACK或者非应答 应答ACK=0 非应答NACK=1 |
发送bit | 在SCL为高电平时SDA必须保持不变,在SCL为低电平时SDA总线上数据才允许变化 |
接收bit | 在SCL为高电平时SDA必须保持不变,在SCL为低电平时SDA总线上数据才允许变化。 主机释放SDA,在主机发送SCL低电平时,从机将0或者1发送到SDA总线,在SCL为高电平时,主机读取SDA总线获取从发送的数据。 |
4.2 数据帧(针对AT24C02芯片)
- Start起始信号
- 从机地址7位+1位写位
- 等待从机应答ACK
- 发送8位地址
- 等待从机应答ACK
- 发送8位数据
- 等待从机应答ACK
- 发送Stop信号
- Start起始信号
- 从机地址7位+1位写位
- 等待从机应答ACK
- 发送8位地址
- 等待从机应答ACK
- 重新Start起始信号 (Restart)
- 从机地址7位+1位读位
- 等待从机应答ACK
- 接收从机发送8位数据
- 回复NACK给从机,通知从机读取结束
- 发送Stop信号
4.3 I2C从机地址
📒这里我们发现,在S(即开始)之后,有一个SLAVE ADRESS+R/W的一个字节,这个字节就是我们前面说过的匹配对象的过程同时也确定了主机为接收端还是发送端。
📒SLAVE ADRESS+R/W分为前面七个位表示地址和最后一位表示读/写模式
在不同的设备中,这里都有不同的含义,这里就主要用我们这次使用的AT24C02芯片为例:
首先,对于AT24C02芯片而言,前面四位为它的固定位,这四个位是在I2C原厂商为AT24C02制定的固定地址位,为1 0 1 0,不能更改,后面的三位可以仔细看看:
4.4 I2C时序
根据上述原理图,写程序时我们要建立六个子函数模块,开始传输数据时SCL和SDA都应该处于高电平,先拉低SDA电平再拉低SCL电平,在传输或者接收数据时要拉高并保持SCL高电平然后读取放到SDA上的数据,结束传输数据就和开始时相反,还要设置应答模块在主机和从机间建立应答通讯。
📚️发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节 。注:真正传输时只有一条SDA
📚️接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,SDA置1)
4.5 AT24C02写周期
AT24C02写之后,内部需要5ms的写周期,数据才能写入到内部存储位置。
我们可以发现,这里最短时间限制少于1us,然而我们的单片机最短反应时间足足有1us,所以我们根本不需要担心时间不够的问题,直接使用,不需要延时,单片机完全有时间反应过来。
✏️注意:
写进去立马读出来能读到吗?
✏️不能!
为什么不能?
我们看一下手册上的写周期是5ms
5. 程序源码
I2C.c中模拟I2C总线的时序,起始信号,结束信号,发送字节,接收字节,发送ACK,接收ACK。
I2C.c
#include <REGX52.H>
#include <INTRINS.H>
#include "Delay.h"
#include "I2C.h"sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;/*** @brief I2C延时通过 * @param * @retval */
void I2C_Nop(void)
{_nop_();_nop_();_nop_();_nop_();_nop_();
}/*** @brief I2C发送起始信号Start* @param 无* @retval 无*/
void I2C_Start(void)
{I2C_SDA = 1;I2C_SCL = 1; //SCL时钟拉高电平I2C_SDA = 0; //当SCL为高电平时,SDA从高变低表示StartI2C_Nop();I2C_SCL = 0;
}/*** @brief I2C发送停止信号Stop* @param 无* @retval 无*/
void I2C_Stop(void)
{I2C_SDA = 0; //SDA拉低I2C_SCL = 1; //SCL拉高I2C_SDA = 1; //当SCL为高电平时SDA从低变为高电平表示Stop
}/*** @brief I2C发送8位数据* @param 无* @retval 无*/void I2C_SendByte(unsigned char byte)
{unsigned char i = 0;for(i=0; i < 8; i++){I2C_SDA = byte & (0x80 >> i);I2C_Nop();I2C_SCL = 1;I2C_Nop();I2C_SCL = 0;}
}/*** @brief I2C接收8位数据* @param 无* @retval 接收到的8位数据*/
unsigned char I2C_RecvByte(void)
{unsigned char i = 0;unsigned char byte = 0;byte = 0;for(i=0; i < 8; i++){I2C_SDA = 1; //释放SDA总线I2C_SCL = 1; //SCL拉高if(I2C_SDA){byte |= (0x80 >> i); //读取SDA}I2C_Nop();I2C_SCL = 0; //SCL拉低}return byte;
}/*** @brief I2C发送ACK应答信号* @param 无* @retval 无*/
void I2C_SendAck(void)
{I2C_SDA = 0;I2C_SCL = 1;I2C_Nop();I2C_SCL = 0;
}/*** @brief I2C发送NACK应答信号* @param 无* @retval 无*/
void I2C_SendNack(void)
{I2C_SDA = 1;I2C_SCL = 1;I2C_Nop();I2C_SCL = 0;
}/*** @brief 获取I2C ACK应答* @param 无* @retval 返回值为0或1,0表示ACK,1表示NACK*/
unsigned char I2C_RecvAck(void)
{unsigned char ack = 0;I2C_SDA = 1; //释放SDA总线I2C_SCL = 1; //拉高SCL总线if(I2C_SDA == 1) //读取SDA总线ack = 1;elseack = 0;I2C_Nop();I2C_SCL = 0;return ack;
}
I2C.h
#ifndef __I2C_H__
#define __I2C_H__void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char byte);
unsigned char I2C_RecvByte(void);
void I2C_SendAck(void);
void I2C_SendNack(void);
unsigned char I2C_RecvAck(void);#endif
AT24C02.C
#include <REGX52.H>
#include <INTRINS.H>
#include "I2C.h"
#include "AT24c02.h"#define AT24C02_ADDR 0xA0/*** @brief AT24C02 EPROM存储器指定地址写一个字节数据* @param addr 写存储器地址* @param data 写存储器数据* @retval 0 写成功,1 写失败*/
unsigned char AT24c02_WriteByte(unsigned char addr, unsigned char byte)
{I2C_Start();I2C_SendByte(AT24C02_ADDR);I2C_RecvAck();I2C_SendByte(addr);I2C_RecvAck();I2C_SendByte(byte);I2C_RecvAck();//结束条件I2C_Stop();
}/*** @brief AT24C02 EPROM存储器指定地址读一个字节数据* @param addr 读存储器地址 * @retval */
unsigned char AT24c02_ReadByte(unsigned char addr)
{unsigned char byte = 0;//起始条件I2C_Start();//从机地址 写I2C_SendByte(AT24C02_ADDR);I2C_RecvAck();//写存储器地址I2C_SendByte(addr);I2C_RecvAck();//Restart起始条件I2C_Start();//从机地址 读I2C_SendByte(AT24C02_ADDR | 0x01);I2C_RecvAck();byte = I2C_RecvByte();I2C_SendNack();//结束条件I2C_Stop();return byte;
}
AT24C02.H
#ifndef __AT24C02_H__
#define __AT24C02_H__unsigned char AT24c02_ReadByte(unsigned char addr);
unsigned char AT24c02_WriteByte(unsigned char addr, unsigned char byte);#endif
main.c
#include <REGX52.H>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "timer0.h"
#include "LCD1602.h"
#include "Nixie.h"
#include "AT24c02.h"unsigned char Minute;
unsigned char Second;
unsigned char HunSecond;
unsigned char Second_Enable = 1;void main()
{unsigned char Key_Num = 0;unsigned char Byte = 0;unsigned int i = 0;Timer0_Init();P2 = 0xff;//LCD_Init();//LCD_ShowString(1,1,"Hello!");Nixie_Setbuffer(1, 0);Nixie_Setbuffer(2, 0);Nixie_Setbuffer(3, 10);Nixie_Setbuffer(4, 0);Nixie_Setbuffer(5, 0);Nixie_Setbuffer(6, 10);Nixie_Setbuffer(7, 0);Nixie_Setbuffer(8, 0);Minute = AT24c02_ReadByte(0x00);Second = AT24c02_ReadByte(0x01);HunSecond = AT24c02_ReadByte(0x02);while(1){Key_Num = Key();if(Key_Num == 1){Second_Enable = !Second_Enable;}if(Key_Num == 2){AT24c02_WriteByte(0x00, Minute);Delay(5);AT24c02_WriteByte(0x01, Second);AT24c02_WriteByte(0x02, HunSecond);//LCD_Init();//LCD_ShowString(1,1,"Saving Data");//Delay(1000);//LCD_Init();}if(Key_Num == 2){Minute = AT24c02_ReadByte(0x00);Second = AT24c02_ReadByte(0x01);HunSecond = AT24c02_ReadByte(0x02);//LCD_Init();//LCD_ShowString(1,1,"Read Data");//Delay(1000);//LCD_Init();}//Delay(1000);}
}/*** @brief 定时器0中断处理函数模版* @param 无* @retval 无*/
void Timer_Routine(void) interrupt 1
{static unsigned int key_count = 0;static unsigned int nixie_count = 0;static unsigned int sec_count = 0;TR0 = 0;key_count++;if(key_count >= 20){Key_Loop();key_count = 0; }nixie_count++;if(nixie_count >= 2){Nixie_Loop();nixie_count = 0; }sec_count++;if(sec_count >= 10) //10ms{sec_count = 0; //10ms counterif(Second_Enable){HunSecond++;if(HunSecond >= 100){HunSecond = 0;Second++;}if(Second >= 60){Second = 0;Minute++;}if(Minute >= 60){Minute = 0;}Nixie_Setbuffer(1, HunSecond%10);Nixie_Setbuffer(2, HunSecond/10);Nixie_Setbuffer(3, 10);Nixie_Setbuffer(4, Second%10);Nixie_Setbuffer(5, Second/10);Nixie_Setbuffer(6, 10);Nixie_Setbuffer(7, Minute%10);Nixie_Setbuffer(8, Minute/10);}}//定时器溢出之后需要重新装载TH0 = (65535 - 1000) / 256; //12MHz晶振,12分频TL0 = (65535 - 1000) % 256 + 1; //TR0 = 1;
}
6. 实验结果
使用Keil5编译代码,并使用STC-ISP烧录到普中科技51单片机开发板上上效果如下: