我想在芯片上的 SRAM 中存储一个简单的整数。(Cortex M4) 我用的程序是mbed online。我知道 SRAM 的地址从 0x2000 0000 开始,并且芯片有 4KB 的内存。
我已经阅读了数据表和位带部分,但这对我来说没有意义。
有人可以向我解释我如何在SRAM中存储例如数字5并再次读取它吗?
当前代码是这样的(c是一个整数,用户通过按钮改变):
if(c==100){
temp=c;
MBX_B0 = 1; // Word write
temp = MBX_B7; // Word read
TIMER_B0 = temp; // Byte write
return TIMER_B7; // Byte read
}
pc.printf("%d",temp);
它只是在 c==100 后停止运行 即使在断电后也应该保存该值。
编辑,您的问题完全改变了答案,因为您对 SRAM 写入根本不感兴趣,但对闪存/eeprom ...
因此,在此答案中添加一个主要部分,您的评论在这里至关重要:
但是即使在断电后该值是否仍存储?那不是 SRAM 会代替普通 RAM 做的事情吗?RAM=断电丢失值,SRAM=断电保持值?
SRAM 表示静态 RAM,RAM 表示随机存取存储器。现在,根据该定义,RAM 可以安全地用于 ROM(只读存储器)之类的东西,因为随机部分与寻址有关,我可以寻址我想要的任何随机地址,或者我可以只使用线性一个地址之后读取这个东西另一个按照一些规则。
约定是 ROM 是非易失性的,而 RAM 是易失性的,这是这里的相关术语。ROM 实现在技术上不是只读的 PROM 是可编程的 rom,这意味着可写,所以有点打破了术语 EPROM 电可编程,EEPROM 是电可擦除和可编程的。闪存是一种较新的技术,可电擦除和可编程 rom 或非易失性存储。
从这个意义上说,易失性意味着它可以或不能在电源循环中幸存下来。volaitle 意味着它不能非易失性意味着它可以。
SRAM 中的 S 表示静态,该术语意味着它可能会在您学习 DRAM 时存活下来,尤其是当您学习 DRAM 时,D 意味着动态,假设一个可以在电源循环中存活而另一个没有,但不幸的是,这不是他们所指的内容。 . 相反,在这两种情况下,它们都与保持供电的存储器有关,它们都是易失性存储器。去维基百科看看这些。静态使用四个晶体管,可以说,经典触发器实现中的两个带有反馈的门,您将位写入高位或低位,只要电源不关闭,它就会保持该值,它不会忘记(只要电源保持开启) . DRAM 虽然使用一个晶体管,并且在某种程度上严重依赖该晶体管的电容,有点像一个蹩脚的可充电电池,
所以静态 ram 是静态的,因为我们只需要告诉它一次,它就会记住,动态 ram 是动态的,因为我们告诉 dram 系统那位是什么,作为一个系统,我们必须不断提醒它,这是通过读取该位然后以特定频率重新编程/充电该位来完成。
DRAM 便宜,可以将四倍的位数装入相同数量的晶体管中,SRAM 速度快,没有复杂的开销,也没有刷新周期妨碍,它只是门,所以可以像其他门一样快地运行门做其他事情(处理指令)。
微控制器将具有某种形式的非易失性存储器,如 ROM、PROM、EEPROM 或闪存(现在有各种形式)。有时您同时拥有闪存和 eeprom eeprom 用于您可能在这里要求的那种东西,有时出于反向兼容性原因,他们有一个 eeprom 传统接口,但它实际上是使用主闪存进行存储。在任何情况下,您都必须查看您的芯片和/或芯片系列的文档。如今,在应用程序中具有写入片上非易失性存储器(eeprom/flash)的能力是很常见的(尽管有很多例外)。文档会告诉您如何执行此操作。
这一切都很好,但一些免费的建议是,如果你做错了,你可能会在几小时或几天内磨损你的闪光灯......字面上......这部分可能会被丢弃。理想情况下,您希望电路板支持检测电源下降,同时有足够的大容量电容或电池或两者兼有,以使电路板/设备保持足够长的时间,以在最坏的情况下节省非易失性信息(理想情况下首先确认值已更改,否则不会烧毁擦除周期)。实现起来很简单,但仍然比磨损闪光灯好。
关于如何不磨损闪存的许多解决方案和意见,可悲的是,一些闪存硬件具有执行写入平衡的逻辑,如果软件和硬件都试图将事物分散以减少闪存的磨损,则它们可以工作互相反对,弊大于利。
您的部件支持的写入周期数应记录在数据表中,如果超过了使用此设备构建的产品的生命周期,它可能会忘记您写的内容。这是支持的最小值,它可能会说 10000 次写入,但在测试中,您可能会达到 100,000 次并且设备仍然可以工作。并不意味着它们的所有重置都将超过数据表中的额定值,因此您可以从该额定值开始反向工作,如果我每隔这么多时间单位获得一个新值,并且产品的使用寿命是我希望有这么多时间单位,那么我只能节省一些简单的数学时间单位(每个存储位置/擦除边界等)。
因此,首先学习如何在应用程序中擦除您不使用的块,然后向其中写入一些内容,然后在您重新启动时查看它是否存在,如果没有闪存,请尝试使用 eeprom。在这些 STM32 设备上通常有很好的文档记录并且很容易做到。然后一旦你知道如何去做,就开始担心你觉得你需要多久做一次。
曾经注意到在某些汽车中,当您将它们“关闭”并重新打开时,时钟仍然有效,并且收音机会记住您最喜欢的电台或空调会记住您上次使用的温度和风扇速度。但是如果您断开电池的连接,部分或全部内容就会丢失。他们没有使用非易失性存储,他们使用的是 ram (SRAM) 并且电源只是关闭了,他们依赖于备用电池。对于您的“CMOS”或“BIOS”设置,主板确实并且可能仍然会这样做。电池支持的 ram 基本上是因为 ram 不会断电,主电源可能会关闭,但电池会保持 ram 供电。那是您可以使用的另一种设计解决方案,电池或超级电容器(acitor),可能会假设您永远不需要存储来闪光,如果像汽车音响一样,电池就快没电了。
当然,所有这些都需要我事先回答,为了获得控制 eeprom/flash 的寄存器,您需要知道如何从程序中访问它们:
首先,这里不需要位带(向/从 ram 存储/加载一些值),您是问如何写入和读取 ram 中的特定地址,还是询问如何使用位带?通常,您不会将位带与 ram 一起使用,例如,该功能可以更改寄存器中位的子集,其中设计人员出于某种原因将单独的项目打包到同一个寄存器中(例如 gpio 引脚配置是有意义的,并且您可能想要更改单个引脚的配置,而无需在软件中进行读-修改-写(硬件可能仍需执行读-修改-写))
当然你可以在 ram 上使用 bitbanding 功能,如果 cortex-m 允许的话,我需要重新阅读,这不一定有意义,除非你对 ram 非常饥渴以至于你需要将单独的东西打包成一个单词(像位域,但甚至不要从那个开始)...
#define BITBAND_SRAM_REF 0x20000000
#define BITBAND_SRAM_BASE 0x22000000
#define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + (a-BITBAND_SRAM_REF)*32 + (b*4)))
#define BITBAND_PERI_REF 0x40000000
#define BITBAND_PERI_BASE 0x42000000
#define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + (a-BITBAND_PERI_REF)*32 + (b*4)))
#define MAILBOX 0x20004000
#define TIMER 0x40004000
#define MBX_B0 *((volatile unsigned int*)(BITBAND_SRAM(MAILBOX,0)))
#define MBX_B7 *((volatile unsigned int*)(BITBAND_SRAM(MAILBOX,7)))
#define TIMER_B0 *((volatile unsigned char*)(BITBAND_PERI(TIMER,0)))
#define TIMER_B7 *((volatile unsigned char*)(BITBAND_PERI(TIMER,7)))
MBX_B0 = 1;
Run Code Online (Sandbox Code Playgroud)
所以这些都不是特别的,或者与 cortex-m 或 arm 相关,只是基本的 C 代码。MBX_B0 是一个宏,你向后工作宏
#define MBX_B0 *((volatile unsigned int*)(BITBAND_SRAM(MAILBOX,0)))
Run Code Online (Sandbox Code Playgroud)
然后
#define MAILBOX 0x20004000
#define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + (a-BITBAND_SRAM_REF)*32 + (b*4)))
#define BITBAND_SRAM_BASE 0x22000000
#define BITBAND_SRAM_REF 0x20000000
Run Code Online (Sandbox Code Playgroud)
所以
0x22000000+(0x20004000-0x20000000)*32 + (0*4)
= 0x22080000
Run Code Online (Sandbox Code Playgroud)
volatile unsigned int 只是一种 C 语法方式,它采用一些常量,如 0x22080009 并说这是我想指向的东西的地址
MBX_B0 = 1;
Run Code Online (Sandbox Code Playgroud)
意味着写一个 0x00000001 到地址 0x22080000 但由于这是使用位带,这意味着设置地址 0x20004000 的位 0 的位 1(位带非常特定于这些 arm cortex-m 内核)
如果您只想将值 5 写入内存中的某个位置,您可以
#define SOME_ADD *((volatile unsigned int*)(0x20001234)
unsigned int x;
SOME_ADD = 5;
x = SOME_ADD;
Run Code Online (Sandbox Code Playgroud)
要看到这一切都为您完成,您可以尝试一下:
#define BITBAND_SRAM_REF 0x20000000
#define BITBAND_SRAM_BASE 0x22000000
#define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + (a-BITBAND_SRAM_REF)*32 + (b*4)))
#define MAILBOX 0x20004000
#define MBX_B0 *((volatile unsigned int*)(BITBAND_SRAM(MAILBOX,0)))
#define SOME_ADD *((volatile unsigned int*)(0x20001234))
unsigned int fun ( void )
{
unsigned int x;
MBX_B0 = 1;
SOME_ADD = 5;
x = SOME_ADD;
}
Run Code Online (Sandbox Code Playgroud)
arm-none-eabi-gcc -c -O2 so.c -o so.o arm-none-eabi-objdump -D so.o
00000000 <fun>:
0: e3a0c001 mov r12, #1
4: e3a02005 mov r2, #5
8: e59f1010 ldr r1, [pc, #16] ; 20 <fun+0x20>
c: e59f3010 ldr r3, [pc, #16] ; 24 <fun+0x24>
10: e581c000 str r12, [r1]
14: e5832234 str r2, [r3, #564] ; 0x234
18: e5933234 ldr r3, [r3, #564] ; 0x234
1c: e12fff1e bx lr
20: 22080000 andcs r0, r8, #0
24: 20001000 andcs r1, r0, r0
Run Code Online (Sandbox Code Playgroud)
处理器加载地址 0x20001000,在这种情况下,汇编器选择将立即数 0x234 添加到该地址,而不是将整个 0x20001234 放在加载的地址中,六分之一......两种方式都没有不同的成本,正如编译器不需要的那样对齐加载的值。
现在,如果您不需要访问特定地址(0x20001234 或某些外围寄存器等),那么只需
unsigned int some_value;
void fun ( void )
{
some_value = 5;
}
Run Code Online (Sandbox Code Playgroud)
需要编译并链接它才能看到整个故事:
00000004 <fun>:
4: e3a02005 mov r2, #5
8: e59f3004 ldr r3, [pc, #4] ; 14 <fun+0x10>
c: e5832000 str r2, [r3]
10: e12fff1e bx lr
14: 20000000 andcs r0, r0, r0
Disassembly of section .bss:
20000000 <some_value>:
20000000: 00000000 andeq r0, r0, r0
Run Code Online (Sandbox Code Playgroud)
并且代码现在已将数字 5 存储到 ram 中的某个位置(由链接器选择)。
在位带方面,如果您阅读 arm 文档,您会发现它并不总是受支持,在某些内核中,它是一个可选功能,这意味着当他们编译芯片时,他们可以选择不包含它。例如,如果这是一个特定的 st 芯片或系列,您可能会发现他们忘记记录一个或两个位带地址(0x22000000、0x42000000),但将其放在库中。
就我个人而言,我不喜欢 volatile 指针技巧,我见过编译器无法生成正确的指令,所以我编写了一个很小的两行汇编函数,我可以抽象所有此类访问,通过它具有强制抽象的巨大副作用,例如无论如何,您将拥有 linux 或其他驱动程序。让代码更有用,可以抽象对软件模拟的访问,可以抽象对逻辑模拟的访问,可以通过 mmap 抽象,可以在内核驱动程序中使用,可以通过这种方式添加用于调试的 printf 层,单个如果您喜欢这种类型的调试,可以设置断点的位置,可以使用几行 asm 实现裸机,或者如果您愿意,可以使用通用宏/定义来执行 volatile 指针。天啊。
注意局部变量
void fun ( void )
{
unsigned int some_value;
some_value = 5;
}
Run Code Online (Sandbox Code Playgroud)
不一定最终在 ram 中,它们理想情况下会进入堆栈,但如果您进行优化,则可以得到优化(推荐用于微控制器等资源匮乏的设备,除非 MISRA 或其他一些要求阻止您使用优化器)。上面的代码当然是完全死代码,结果是简单的返回:
00000000 <fun>:
0: e12fff1e bx lr
Run Code Online (Sandbox Code Playgroud)