时间片轮询:

结构体封装每个任务的时间片(也就是执行间隔),计数器,函数指针。采用定时器计时。比如说任务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一经损坏就不可以使用

补充:

  1. 地址总线传输地址信息(用来寻址)数据总线用来传输数据信息。地址总线的宽度决定了存储器可以存储多少数据,数据总线的宽度决定了一次性可以处理多长的数据。例如:内存容量=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);

 

posted on 2024-01-23 20:40  小凉拖  阅读(19)  评论(0编辑  收藏  举报