时间片轮询:
结构体封装每个任务的时间片(也就是执行间隔),计数器,函数指针。采用定时器计时。比如说任务1每2s执行一次,任务2每5s执行一次,定时器每1s产生中断,定时时间一到每个任务的计数器就加1,当计数器和时间片相等时执行该任务。
任务冲突:将每个任务对应的结构体装入结构体数组,采用遍历结构体数组的方式遍历每个任务,遍历到谁就通过函数指针执行那个任务的函数。
NORFlash与NANDFlash
- 写和擦除方面NOR比NAND要慢
- 代码可以在NOR上直接运行,而代码不可以在NAND上直接运行
- NOR可靠性比较高,NOR位反转比例要小于NAND
- NOR地址总线和数据总线分开,可以随机寻址随机读取任意字节;NAND地址线和数据线共用,不能随机寻址随机读取,读取时只能按页读取,按块擦除。NAND在作为系统的启动盘需要先将NAND里面的数据搬到SRAM的地址0位置
- NOR容量小于NAND,价格方面NOR更贵
- NOR常用来保存代码和关键性数据,NAND可以用来保存数据
- NAND和NOR的0地址是不冲突的,NOR占用BANK地址,NOR不占用BANK地址,它的地址是内部的
- NAND标记坏块软件上可以跳过坏块,NOR一经损坏就不可以使用
补充:
- 地址总线传输地址信息(用来寻址)数据总线用来传输数据信息。地址总线的宽度决定了存储器可以存储多少数据,数据总线的宽度决定了一次性可以处理多长的数据。例如:内存容量=4GB=4*(2^10)*(2^10)*(2^10)=2^32Byte 因为一个存储单元占用一个字节,所以存储单元个数为2^32 所以地址线条数是32,字长是32位 则1字=32bit=4B 则表明处理器一次可以处理4个存储单元【计算机组成原理】地址线和数据线_地址线和数据线的计算-CSDN博客
内部Flash
1.ZET6:512K(SRAM64K),C8T6:64K(SRAM20K)
2.起始地址0x08000000(SRAM0x02000000)
3.Flash包括程序存储器,系统存储器,选项字节(读写保护,配置软硬件看门狗)
4.全片擦除(仅擦除主存储区),页擦除,写入数据都需要先解锁然后再操作然后上锁
5.Flash一次只能写入半字,字的话库函数也有提供是每半字的写入写入两次
6.Flash保存数据的时候要擦除待保存的页,这个时候不用担心标志位被擦除,因为SRAM第一个数据就是标志位,这样将SRAM写入Flash,Flash就重新获得标志位了
1 void Flash_Store_Save(uint32_t Address,uint32_t *SRAMArr,uint32_t SRAMSize) 2 { 3 MyFLASH_ErasePage(Address); 4 for(uint32_t i=0;i<SRAMSize;i++) 5 { 6 MyFLASH_ProgramWord(Address+i*4,SRAMArr[i]); 7 } 8 }
W25Q64
1.最小擦除单位:扇区
2.可选择擦除单位:扇区、块、全片
3.最大编程(写入)单位:页( 256 Byte),大于256 Byte则需要循环写入。
4.Flash 写入数据时和 EEPROM 类似,不能跨页写入,一次最多写入一页,W25Q128的一页是 256 字节。写入数据一旦跨页,必须在写满上一页的时候,等待 Flash 将数据从缓存搬移到非易失区,重新再次往里写。
5.最小编程(写入)单位:1 Byte,即一次可写入 1~256 Byte的任意长度字节。
6.写等待问题
EEPROM
型号,不同型号的存储量
每页的字节数
跨页写问题
1 uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize) 2 { 3 uint16_t i,m; 4 uint16_t usAddr; 5 6 /* 7 写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。 8 对于24xx02,page size = 8 9 简单的处理方法为:按字节写操作模式,每写1个字节,都发送地址 10 为了提高连续写的效率: 本函数采用page wirte操作。 11 */ 12 13 usAddr = _usAddress; 14 for (i = 0; i < _usSize; i++) 15 { 16 /* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */ 17 if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0) 18 { 19 /* 第0步:发停止信号,启动内部写操作 */ 20 i2c_Stop(); 21 22 /* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms 23 CLK频率为200KHz时,查询次数为30次左右 24 */ 25 for (m = 0; m < 1000; m++) 26 { 27 /* 第1步:发起I2C总线启动信号 */ 28 i2c_Start(); 29 30 /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */ 31 i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR); /* 此处是写指令 */ 32 33 /* 第3步:发送一个时钟,判断器件是否正确应答 */ 34 if (i2c_WaitAck() == 0) 35 { 36 break; 37 } 38 } 39 if (m == 1000) 40 { 41 goto cmd_fail; /* EEPROM器件写超时 */ 42 } 43 44 /* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */ 45 i2c_SendByte((uint8_t)usAddr); 46 47 /* 第5步:等待ACK */ 48 if (i2c_WaitAck() != 0) 49 { 50 goto cmd_fail; /* EEPROM器件无应答 */ 51 } 52 } 53 54 /* 第6步:开始写入数据 */ 55 i2c_SendByte(_pWriteBuf[i]); 56 57 /* 第7步:发送ACK */ 58 if (i2c_WaitAck() != 0) 59 { 60 goto cmd_fail; /* EEPROM器件无应答 */ 61 } 62 63 usAddr++; /* 地址增1 */ 64 } 65 66 /* 命令执行成功,发送I2C总线停止信号 */ 67 i2c_Stop(); 68 return 1; 69 70 cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */ 71 /* 发送I2C总线停止信号 */ 72 i2c_Stop(); 73 return 0; 74 }
i=0判断写入的是否是第一个字节,_usAddress待写入数据的寄存器地址,EEPROM_PAGE_SIZE是每页的字节数,(usAddr & (EEPROM_PAGE_SIZE - 1)) == 0代表usAddr可以整除EEPROM_PAGE_SIZE,也就是新的一页,这两种情况要重新开始时序,usAddr++是跟踪写入数据的地址,看看它是否超过一页(注意每写完一页需要发送停止信号)
(代码来自野火)
写等待
寄存器地址,I2C时序
怎么使EEPROM只被初始化一次
我们第一次使用EEPROM的时候要往里面写入一个出厂值,因为我们上电的时候都需要将EEPROM里面的数据拿出来使用,第一次EEPROM没有值因此第一次我们应该往里面写入一个出厂值。但是在以后的使用就不需要人为的写入出厂值了,因为它里面保存的是掉电之前的结果。设置和数据同样长的标志位,将标志位写入第一个位置,比如4个字节的数据要有4个字节的标志位,那么对应页起始的前4个字节就写入标志位。数据从第二个位置开始写,每次执行到EEPROM初始化的时候都判断第一个位置有没有对应的标志位,有的话就不再执行EEPROM参数初始化程序(也就是向EEPROM对应的位置写入“出厂值”,当然程序执行时还需要将这些出厂值拿出来使用,当出厂值需要更改的时候还要把更新值写入对应位置),没有的话就将第一个位置写入标志位,对应数据的位置写入出厂值
float类型的数据写入问题
float类型数组:用uint8_t指针指向数组的首地址
1 void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)//u16 NumByteToWrite告知float类型数组的长度
(程序来自野火)
float类型数据:使用联合体或则将float类型的数据当作一个数组(能用指针尽量用指针,因为少了一个定义联合体的步骤)
OLED多级菜单
- 首先有个前提:显示的时候只能连续显示也就是说第一个位置如果显示索引值0的话,接下来就必须显示索引值1,2,3……
- 每一页都是一个结构体数组,每个结构体都是每一页中的一个菜单项,结构数组中存放着父页指针,子页指针,和它的属性
菜单到顶(底)了怎么办
设置两个变量一个是光标的位置,另外一个是第一个位置的索引值CursorPositon, FirstPositionIndex;
那么CursorPositon<0的话,此时如果FirstPositionIndex!=0,那么CursorPositon=0,第一个位置的索引值就减1个。如果FirstPositionIndex==0,CursorPositon=最后一个位置,那么第一个位置的索引值就是隐藏值(也就是几个菜单项不能显示出来)比如说有两个不能显示出来,那么FirstPositionIndex==0的时候再往上翻的话第一个位置的索引值就应该是2,同时光标跑到最后一个位置。
那么CursorPositon=最后一个的话,此时如果FirstPositionIndex!=隐藏值,那么CursorPositon=最后一个位置,那么第一个位置的索引值就加1个。如果FirstPositionIndex==隐藏值,那么CursorPositon=第一个位置,第一个位置的索引值就是0同时光标跑到第一个位置。
父页切换到子页
此时每个结构体中应该定义一个记录变量CursorPositon_pre;
FirstPositionIndex_pre;记录父页跳转前的CursorPositon, FirstPositionIndex;等到再跳回来的时候再将CursorPositon_pre;FirstPositionIndex_pre赋给CursorPositon, FirstPositionIndex;
SHT20
主机发送从机地址+读标志位后(也就是发送读命令后),如果SHT20没有测量完成它是不会给主机应答信号的,因此程序上应该采用while死循环等待SHT20应答
1 do 2 { 3 Delay_us(8); 4 SHT20IIC_Start(); 5 SHT20IIC_SendByte(SHT20_ReadAddress); 6 SHT20IIC_W_SDA(1); 7 SHT20IIC_W_SCL(1); 8 } 9 while(SHT20IIC_R_SDA()); 10 SHT20IIC_W_SCL(0);