摘要:在单片机开发板上或者是核心板上通常会看到除了mcu之外的的芯片—eeprom和flash,一般是at24cxx、w25qxx这两颗芯片。但在利用单片机做一些项目的时候,比如做一个小车,驱动一些外设、显示一些温湿度信息等,却发现一般没有用到这些芯片。
在做一些显示的时候却会用到。他们与单片机之间的通信方式就是iic和spi通信,在单片机的开发中用到的非常多。很多小伙伴就会说了,用oled来显示一些数据,iic通信直接用别人的代码,驱动sd卡或者nrf24l01直接拿别人的spi代码就可以啊,难道我还自己去写驱动吗?当然需要,学会了这些操作,层次就会提高很多,不信那就接着往下看!
eeprom at24c02存储器学单片机的时候大家可能有一个问题,为啥是iic读写eeprom,而不是读写其他的东西。为什么大部分的单片机开发教程都教我利用iic通信来读写eeprom这颗at24c02芯片?4针0.96寸oled也是iic操作的,为啥他们不叫我如何利用iic通信来操作oled?原因很简单,主要是读写eeprom你学完了没有成就感,会读写eeprom又怎么样?归根到底是没有掌握iic体会到iic通信的重要性。
今年疫情很严重,有一款红外测温芯片mlx90641就是通过iic来读取温度的。我想如果教程是iic读写红外测温芯片,大家可能会比较感兴趣。言归正传,来说一说eeprom。rom是“read only memory”的缩写,意为只能读的存储器。由于技术的发展,后来设计出了可以方便写入数据的 rom,而这个“read only memory”的名称被沿用下来了。eeprom(electrically erasable programmable rom)是电可擦除存储器。eeprom 可以重复擦写,eeprom 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。它的擦除和写入都是直接使用电路控制,不需要再使用外部设备来擦写。而且可以按字节为单位修改数据,无需整个芯片擦除。现在主要使用的rom芯片都是eeprom。24c02是一个2k bit的串行eeprom存储器(掉电不丢失),内部含有256个字节,在24c02里面有一个8字节的页写缓冲器。
操作任何的iic设备一般都要知道从机地址,也就是利用单片机操作读写的那个设备的地址。一般来说对于iic设备地址是7位,其中高 4 位固定为:1010 b,低 3 位则由 a0/a1/a2信号线的电平决定。所以一个iic总线上可以挂载2^3=8个eeprom芯片,当然一般一个单片机只有一块eeprom芯片,所以我们直接把这个a2a1a0接地即可,当然接vcc也没有问题,如果接gnd那么地址就是1010000(0x50),如果接vcc那么地址就是1010111(0x57)。
因为24c02是一个2k bit的串行eeprom存储器(掉电不丢失),内部含有256个字节。也就是说有256个存储单元,一个字节就是一个存储单元,因为每个字节可以出存256个数,也就是说每个存储单元可以存0~255个数。我们可以这样理解,at24c02是一栋教学楼,这个教学楼有256个房间(存储单元),没每个房间可以容纳256个学生(每个存储单元可以存储0 ~ 255个数)。
而且这个芯片在断电的时候数据不会丢失,利用掉电不会丢失以及这款芯片容量不大的特性,可以大致判断它会在哪些地方可以用到。比如我们看电视得时候,正在看cctv6电影频道,播放的声音比较大,那么这时候正好停电了。那么你下次来电时你打开电视机,电视机默认肯定是cctv6电影频道,播放的声音也是很大。那么这些“频道”、“音量”这些数据就存在eeprom里面,至于是不是atc02就不一定了。
总结:
存储量少,用起来方便
可以任意访问地址数据,每一个存储单片可以独立访问,
写入前是不需要对写入的单片做独立的擦除
这三个特点对我们理解存储器的特性非常重要,因为接下来要说的flash芯片的特性就与它完全相反。
flash w25q128存储器
flsah字面意思就是闪现、一瞬间的意思,所以flsah存储器又称闪存,与 eeprom都是掉电后数据不丢失的存储器,但flash存储器容量普遍大于 eprom,现在基本取代了它的地位。生活中常用的 u 盘、sd卡、ssd 固态硬盘以及我们 stm32 芯片内部用于存储程序的设备,都是 flash 类型的存储器。在存储控制上,最主要的区别是 flash 芯片只能一大片一大片地擦写,而 eeprom可以单个字节擦写。
flash 芯片的最小擦除单位为扇区(sector),而一个块(block)包含 16 个扇区,4kbytes为一个sector,16个扇区为1个block。w25q64 容量为8m字节(即 64m bit), 分为128块(block),每一块的大小为64k字节,每块又分为16个扇区(sector),那么每个扇区就是4k个字节。w25q128 容量为16m字节(即 128m bit),分为256块(block),每一块的大小为64k字节,每块又分为16个扇区(sector),那么每个扇区就是4k个字节(4096个字节,也就是4096个存储单元)。
w25qxx的最小擦除单位为一个扇区,也就是每一次必须擦除4k字节。所以必须给w25qxx开辟至少4k的缓冲区,这样对单片机的ram的要求比较高,要求芯片必须有4k以上的ram才能很好的操作。所有的flash我们在写之前都要擦出对应的扇区,擦除后的数据是0xff。我们可以这样理解。我们要改写flash芯片w25q128的一个扇区中某一个数据,就必须在stm32芯片的内部ram中开辟4k字节(4096字节)的缓冲区域。先把flash芯片w25q128的一个扇区中数据全部读到stm32芯片的内部ram中开辟4k字节(4096字节)的缓冲区域中去,把我们要改写的数据在缓冲区域改写好之后,再把flash芯片w25q128的一个扇区中的数据全部擦除完毕,擦除完成之后再把数据写回去。这是写入数据的操作,在读数据的时候不需要以扇区为单位,想读哪个扇区就读哪个扇区的数据。
/************************************************************************* * function name : spi_flash_write * description : 在指定地址开始写入指定长度的数据,该函数带擦除操作! * input : *pbuffer:要写入数据的指针 writeaddr:开始写入的地址(24bit) numbytetowrite:要写入的字节数(最大16 x 1024 x 1024) * output : none * return : none
****************************************************************************/ void spi_flash_write(u8* pbuffer, u32 writeaddr, u16 numbytetowrite) { u8 numofpage = 0, numofsingle = 0, addr = 0, count = 0, temp = 0; addr = writeaddr % 4096;//mod运算求余,若writeaddr是4096整数倍,运算结果addr值为0 numofpage = numbytetowrite / 4096;//计算出要写多少整数扇区 numofsingle = numbytetowrite % 4096;//mod
运算求余,计算出剩余不满一扇区的字节数 count = 4096 - addr;//差count个数据值,刚好可以对齐到扇区地址 if (addr == 0)//addr=0,则writeaddr刚好按扇区对齐或者说小于一个扇区 { //numbytetowrite 《 4096,写入的字符串大小长度小于一个扇区(4096个字节)的大小,如22 if (numofpage == 0) { spi_flash_write_page(pbuffer, writeaddr, numbytetowrite); } else //numbytetowrite 》 4096,写入的字符串大小长度大与一个扇区(4096个字节)的大小,如4098 { //先把整数扇区都写了 while (numofpage--) { spi_flash_write_page(pbuffer, writeaddr, 4096); writeaddr += 4096; pbuffer += 4096; } //若有多余的不满一扇区的数据,把它写完
spi_flash_write_page(pbuffer, writeaddr, numofsingle); } } //若地址与 4096 不对齐 else //addr不等于0,则要写入的writeaddr地址与4096不对齐 { //numbytetowrite 《 4096 if (numofpage == 0)//大小不够一个扇区,如22 { //当前页剩余的count个位置比numofsingle小,一扇区写不完 if (numofsingle 》 count) { temp = numofsingle - count; //先写满当前扇区 spi_flash_write_page(pbuffer, writeaddr, count); writeaddr += count; pbuffer += count; //再写剩余的数据 spi_flash_write_page(pbuffer, writeaddr, temp); } else //当前扇区剩余的count个位置能写完
numofsingle个数据 { spi_flash_write_page(pbuffer, writeaddr, numbytetowrite); } } else //numbytetowrite 》 4096 //大小够一个扇区,而且还超出一点点,如4098 { //地址不对齐多出的count分开处理,不加入这个运算 numbytetowrite -= count; numofpage = numbytetowrite / 4096; numofsingle = numbytetowrite % 4096; //先写完count个数据,为的是让下一次要写的地址对齐 spi_flash_write_page(pbuffer, writeaddr, count); //接下来就重复地址对齐的情况
*/ writeaddr += count; pbuffer += count; //把整数扇区都写了*/ while (numofpage--) { spi_flash_write_page(pbuffer, writeaddr, 4096); writeaddr += 1096; pbuffer += 4096; } //若有多余的不满一扇区的数据,把它写完 if (numofsingle != 0) { spi_flash_write_page(pbuffer, writeaddr, numofsingle); } } } }
总结:
/********************************************************************** * function name : spi_flash_read * description : 在指定地址开始读取指定长度的数据 * input : *pbuffer:存储读出数据的指针 readaddr:开始读取的地址(24bit) numbytetoread:要读取的字节数(最大 16 x 1024 x 1024) * output : none * return : none ************************************************************************/ void spi_flash_read(u8* pbuffer,u32 readaddr,u16 numbytetoread) { u16 i; spi_flash_cs=0; //使能器件
spi1_readwritebyte(cmd_w25x_readdata); //发送读取命令 spi1_readwritebyte((readaddr& 0xff0000)》》16); //发送扇区地址的高8bit spi1_readwritebyte((readaddr& 0xff00)》》8); //发送扇区地址的中间8bit spi1_readwritebyte( readaddr& 0xff); //发送扇区地址的低8bit for(i=0;i《numbytetoread;i++) { pbuffer[i]=spi1_readwritebyte(0xff); //循环读数 } spi_flash_cs=1; //取消片选 } 总结:
存储量大
不能任意访问字节地址数据,每一个存储单片不可以独立访问,最小读取单元是一个扇区
写入前是必须对写入的扇区做独立的擦除操作。擦除的目的是使存储单元的数据全为1
sd卡大容量存储器sd 卡(secure digital memory card)在我们生活中已经非常普遍了,控制器对 sd卡进行读写通信操作一般有两种通信接口可选,一种是 spi接口,另外一种是 sdio 接口。sdio全称是安全数字输入/输出接口,多媒体卡(mmc)、sd卡、sd i/o 卡(专指使用sdio 接口的一些输入输出设备)都可使用 sdio 接口通讯。stm32f10x 系列控制器有一个 sdio 主机接口,它支持与上述使用 sdio 接口的设备进行数据传输。
stm32f10x 系列控制器只支持 sd 卡规范版本 2.0,即只支持标准容量sd和高容量 sdhc 标准卡,不支持超大容量 sdxc 标准卡,所以可以支持的最高卡容量是 32gb。sd 卡一般都支持 sdio 和 spi 这两种接口。另外,stm32f42x 系列控制器的 sdio 是不支持 spi通信模式的,如果需要用到 spi通信只能使用 spi外设。因为spi通信方式操作sd卡的数据线只有一根,而如果用sdio的通信方式操作sd卡的数据线却又3根。为了节省资源一般在stm32f10x 系列控制器上用spi的通信方式,而在引脚资源比较多的f4系列上就用sdio的通信方式了。
sd容量有8mb、16mb、32mb、64mb、128mb、256mb、512mb、1gb、2gb (磁盘格式fat12、fat16)
sdhc容量有2gb、4gb、8gb、16gb、32gb(磁盘格式fat32)
sdxc容量有32gb、48gb、64gb、128gb、256gb(磁盘格式exfat)
3.1初始化1、初始化与sd卡连接的硬件条件(mcu的spi配置,io口配置);
2、上电延时(》74 个 clk);
3、复位卡(cmd0),进入idle状态;
4、发送cmd8,检查是否支持2.0协议;
5、根据不同协议检查sd卡(命令包括:cmd55、cmd41、cmd58 和 cmd1 等);
6、取消片选,发多 8个clk,结束初始化
/******************************************************************************* * function name : sd_init * description : 初始化sd卡 * input : none * output : none * return : u8 * 0:no_err * 1:time_out * 99:no_card *******************************************************************************/ u8 sd_init(void) { u8 r1; // 存放sd卡的返回值 u16 retry; // 用来进行超时计数 u8 buf[4]; u16 i; sd_spi_init(); //初始化io sd_spi_speedlow(); //设置到低速模式 //先产生至少74个脉冲,让sd卡自己初始化完成 for(i=0;i《10;i++) { sd_spi_writebyte(0xff);
////80clks } //-----------------sd卡复位到idle开始----------------- //循环连续发送cmd0,直到sd卡返回0x01,进入idle状态 //超时则直接退出 retry=0; do { r1=sd_sendcmd(cmd0,0,0x95);//进入idle状态,作用是让sd卡进入spi模式。这里的crc校验位0x95是固定的,不能修改 retry++; }while((r1!=0x01) && (retry《20));//如果 sd 卡有正确的回应,代码就继续执行,如果没有回应程序就终止执行。 //跳出循环后,检查原因:初始化成功?or 重试超时? if(retry==20) return 1; //超时返回1 sd_type=0;
//默认无卡 //下面是v2.0卡的初始化 //其中需要读取ocr数据,判断是sd2.0还是sd2.0hc卡 if(r1==0x01) { if(sd_sendcmd(cmd8,0x1aa,0x87)==1)//sd v2.0 { //v2.0的卡,cmd8命令后会传回4字节的数据,要跳过再结束本命令 buf[0]=sd_spi_readbyte(); //should be 0x00 buf[1]=sd_spi_readbyte(); //should be 0x00 buf[2]=sd_spi_readbyte(); //should be 0x01 buf[3]=sd_spi_readbyte(); //should be 0xaa if(buf[2]==0x01&&buf[3]==0xaa)//判断卡是否支持2.7~3.6v的电压范围 { retry=0xfffe; //发卡初始化指令cmd55+cmd41 do { sd_sendcmd(cmd55,0,0x01); //发送cmd55 r1=sd_sendcmd(cmd41,0x40000000,0x01);//发送cmd41 }while(r1&&retry--); //初始化指令发送完成,接下来获取ocr信息
//-----------鉴别sd2.0卡版本开始----------- if(retry&&sd_sendcmd(cmd58,0,0x01)==0)//鉴别sd2.0卡版本开始 { //读ocr指令发出后,紧接着是4字节的ocr信息 buf[0]=sd_spi_readbyte(); buf[1]=sd_spi_readbyte(); buf[2]=sd_spi_readbyte(); buf[3]=sd_spi_readbyte(); //检查接收到的ocr中的bit30位(ccs),确定其为sd2.0还是sdhc //如果ccs=1:为sdv2.0hc的2.0高容量卡 ccs=0:为sdv2.0的2.0版本的标准卡 if(buf[0]&0x40) sd_type=sd_type_v2hc; //检查ccs else sd_type=sd_type_v2; lcd_shownum(164,250,sd_type,5,16);//显示sd卡容量 //-----------鉴别sd2.0卡版本结束----------- } } } //如果卡片版本信息是v1.0版本的,即r1=0x05,则进行以下初始化
else//sd v1.0/ mmc v3 { //先发cmd55,应返回0x01;否则出错 r1 = sd_sendcmd(cmd55,0,0x01); //发送cmd55 if(r1 != 0x01) return r1; //得到正确响应后,发acmd41,应得到返回值0x00 r1=sd_sendcmd(cmd41,0,0x01); //发送cmd41 if(r1《=1) { sd_type=sd_type_v1; retry=0xfffe; do //等待退出idle模式 { sd_sendcmd(cmd55,0,0x01); //发送cmd55 r1=sd_sendcmd(cmd41,0,0x01);//发送cmd41 }while(r1&&retry--); }else//mmc卡不支持cmd55+cmd41识别 { sd_type=sd_type_mmc;//mmc v3 retry=0xfffe; do //等待退出idle模式 { r1=sd_sendcmd(cmd1,0,0x01);//发送cmd1,发送mmc卡初始化命令
}while(r1&&retry--); } if(retry==0||sd_sendcmd(cmd16,512,0x01)!=0) sd_type=sd_type_err;//错误的卡 } } sd_disselect();//取消片选 sd_spi_speedhigh();//高速 if(sd_type) return 0; else if(r1) return r1; return 0xaa;//其他错误 }
3.2写数据通过 cmd24实现1、发送cmd24;
2、接收卡响应r1;
3、发送写数据起始令牌 0xfe;
4、发送数据;
5、发送2字节的伪crc;
6、禁止片选之后,发多8个clk;
/******************************************************************************* * function name : sd_writedisk * description : 向sd卡写数据 * input : buf:数据缓存区 * sector:扇区 * cnt:扇区数 * output : none * return : u8 * 0:ok * 其他,失败。 *******************************************************************************/ u8 sd_writedisk(u8*buf,u32 sector,u8 cnt) { u8 r1; if(sd_type!=sd_type_v2hc)sector *= 512;//转换为字节地址 if(cnt==1) { r1=sd_sendcmd(cmd24,sector,0x01);//读命令 if(r1==0)//指令发送成功 { r1=sd_sendblock(buf,0xfe);//写512个字节 } }else { if(sd_type!=sd_type_mmc) { sd_sendcmd(cmd55,0,0x01); sd_sendcmd(cmd23,cnt,0x01);//发送指令 } r1=sd_sendcmd(cmd25,sector,0x01);//连续读命令 if(r1==0) { do { r1=sd_sendblock(buf,0xfc);//接收512个字节 buf+=512; }while(--cnt && r1==0); r1=sd_sendblock(0,0xfd);//接收512个字节 } } sd_disselect();//取消片选,释放spi总线 return r1; }
3.3读取数据通过 cmd17实现1、发送cmd17;
2、接收卡响应r1;
3、接收数据起始令牌 0xfe;
4、接收数据;
5、接收2个字节的 crc,如果不使用crc,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个clk;
/*******************************************************************************
* function name : sd_readdisk
* description : 读sd卡数据
* input : buf:数据缓存区
* sector:扇区
* cnt:扇区数
* output : none
* return : u8
* 0:ok
* 其他,失败。
*******************************************************************************/
u8 sd_readdisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(sd_type!=sd_type_v2hc)sector 《《= 9;//转换为字节地址
if(cnt==1)
{
r1=sd_sendcmd(cmd17,sector,0x01);//读命令
if(r1==0)//指令发送成功
{
r1=sd_recvdata(buf,512);//接收512个字节
}
}else
{
r1=sd_sendcmd(cmd18,sector,0x01);//连续读命令
do
{
r1=sd_recvdata(buf,512);//接收512个字节
buf+=512;
}while(--cnt && r1==0);
sd_sendcmd(cmd12,0,0x01); //发送停止命令
}
sd_disselect();//取消片选
return r1;
}
原文标题:你必须知道的单片机存储器的那些事!
文章出处:【微信公众号:fpga之家】欢迎添加关注!文章转载请注明出处。
毫米波(mmWave):频段之战
由于开办涉案电话卡数量较多,这36个通信运营商营业网点被曝光
Lab WindowsCVI连接硬件进行单通道AD连续采集并采样
华为nova5 Pro首发简单评测 人像超级夜景自拍很有些看头!
Maxim Integrated锂离子电池监测器 现身日产混动版探路者
单片机存储器中提高层次的操作
在AGL中引入开源虚拟化
人工智能未来的发展与将面临的威胁
荣耀V9、小米6对比评测:最具性价比之争,看了再买!
T3Ster的瞬态热测试技术2大亮点
基于示波器的射频分析方法
上海BitSE公司成为了全球首家正在运行生产系统的区块链公司
IIR滤波器种类和设计
海天雄电子DJ101IA-01G规格
小米2和小米1s的区别_小米2和小米1s对比
OPPO Find X7 系列手机将于下月发布,搭载两款传感器首发
QLC闪存未来将更廉价 1TB硬盘仅300元出头
贴片元件温度(散热)的影响
Qualcomm和Jacoti携手为真无线耳塞用户提供优化的聆听体验
智能消防应急灯的类型应该如何选择?