0%

起因

在学习FPGA时使用了DDR3,最近学ARM处理器也用到了DDR3的外设,在FPGA是使用 MIG(Memory Interface Generators)IP核驱动DDR3,ARM处理器是通过配置MMDC(Multi Mode DDR Controller)模块驱动DDR3,这编博客将会介绍使用DDR需配置的几个关键参数。

SRAM操作流程、时序图可以浏览我之前的文章,FPGA之SDRAM学习

关键参数

传输速率

传输速率的单位是MT/s(Mega Transfer Per Second),即每秒传输的百万次数,常见DDR3有800MT/s、1066MT/s、1333MT/s、1600MT/s等,这是首先需要考虑的,该参数决定了DDR的最高数据传输速率。

tRCD 参数

tRCD 全称是 RAS-to-CAS Delay,也就是行寻址到列寻址之间的延迟。 DDR 的寻址流程是先指定 BANK 地址,然后在指定行地址,最后指定列地址确定最终要寻址的单元。 BANK 地址和行地址是同时发出的,这个命令叫做行激活(Row Active)。行激活以后就发送列地址和具体的操作命令(读还是写),这两个是同时发出的,因此一般也用读/写命令表示列寻址。在行有效(行激活)到读/写命令发出的这段时间间隔叫做 tRCD。

CL 参数

当列地址发出以后就会触发数据传输,但是从数据从存储单元到内存芯片 IO 接口上还需要一段时间,这段时间就是非常著名的 CL(CAS Latency),也就是列地址选通潜伏期

AL 参数

在 DDR 的发展中,提出了一个前置 CAS 的概念,目的是为了解决 DDR 中的指令冲突,它允许 CAS 信号紧随着 RAS 发送,相当于将 DDR 中的 CAS 前置了。但是读/写操作并没有因此提前,依旧要保证足够的延迟/潜伏期,为此引入了 AL(Additive Latency),单位也是时钟周期数。 AL+CL 组成了 RL(Read Latency),从 DDR2 开始还引入了写潜伏期 WL(Write Latency),WL 表示写命令发出以后到第一笔数据写入的潜伏期。

tRAS 参数

RAS active time,也指Active to Precharge Delay,行有效至行预充电时间。是指从收到一个请求后到初始化RAS(行地址选通脉冲)真正开始接受数据的间隔时间,tRAS 是 ACTIVE 命令到 PRECHARGE 命令之间的最小时间,tRAS=tRCD+tWR。

其他参数

tRP 参数

在发出预充电命令之后,要经过一段时间才能允许发送RAS行有效命令打开新的工作行,这个间隔被称为tRP(RAS Precharge time,预充电有效时间)。

tRC 参数

tRC(Row Cycle Time),表示“SDRAM行周期时间”,它是包括行单元预充电到激活在内的整个过程所需要的最小的时钟周期数,是两个 ACTIVE 命令或者 ACTIVE 命令到 REFRESH 命令之间的周期。tRC=tRAS+tRP。如果tRC的时间过长,会因在完成整个时钟周期后激活新的地址而等待无谓的延时,而降低性能。然而如果该值设置过小,在被激活的行单元被充分充电之前,新的周期就可以被初始化,也会导致数据丢失和损坏。

tWR 参数

由于数据信号由控制端发出,输入时芯片无需做任何调校,只需直接传到数据输入寄存器中,然后再由写入驱动器进行对存储电容的充电操作,因此数据可以与CAS同时发送,也就是说写入延迟为0。不过,数据并不是即时地写入存储电容,因为选通三极管(就如读取时一样)与电容的充电必须要有一段时间,所以数据的真正写入需要一定的周期。为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR,Write Recovery Time),这个操作也被称作写回(Write Back)。tWR至少占用一个时钟周期或再多一点(时钟频率越高,tWR占用周期越多)。

起因

这几天在做ID自分配协议栈,使用的是J1939协议,汽车中还有其他的EOL协议、快充协议也都是使用的J1939协议。

SAE-J1939与CAN2.0的关系

CAN2.0是一种总线规范,是数据链路层的技术。J1939是SAE(美国汽车协会)基于CAN总线定义的的规范,主要用于解决不同发动机厂商、不同ECU厂商之间的兼容性问题。J1939定义了一系列的PGN和SPN,这些PGN包含了发动机、变速器、车轴等汽车上各部件的信息;对参数的表示方法(状态和值)又定义了SLOT(Scaling—比例、Limit—界限、Offset—偏移、Transfer—传送)。ECU厂商开发设备时都应该遵循这个规范。ECU模块的功能不同,厂商不同,在J1939的基础上,又表现出其多样性:支持或者不支持某些PGN、SPN和SLOT;新增了某些J1939未定义的PGN和SPN。

SAE-J1939消息帧格式

CAN2.0规范包括CAN2.0A(标准帧格式),CAN2.0B(扩展帧格式),二者使用不同的帧格式位码。J1939是在CAN2.0B的基础上进一步封装,对仲裁场部分的29位ID的重新定义。SAE-J1939中只为扩展帧格式定义了标准化的通信,因此,SAE-1939设备必须使用扩展帧格式

SAE-J1939数据帧结构

SAE-J1939将每个协议数据单元(PDU)融合进一个CAN2.0B数据帧中,其结构如下:

img

参数群编号(PGN)对于制定基于SAE-J1939的CAN协议来说十分重要,很多ECU厂商规定在接受CAN报文时识别的就是PGN而不是整个报文的ID。参数群编号是由24位组成的(其实是18位),主要包括下面几个部分:保留位(R,1bit,默认为:0),数据页位(DP,1bit,多数情况下为:0),PDU格式(PF,8bit)和特定PDU(PS,8bit,目标地址是否群扩展)。当PF值为:0~239之前时PGN的低字节将被设置为:0;当PF值为240~254之时,PGN的低字节为PS的值。PGN结构如下:

img

程序设计

点灯流程

  1. 使能指定 GPIO 的时钟
  2. 设置 IO 的复用功能
  3. 配置 GPIO 输出功能、上拉、速度等等
  4. 设置 GPIO 输出高电平或低电平

点灯汇编代码

代码中的地址参考《i.MX 6ULL Applications Processor Reference Manual》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.global _start	@ global symbol

_start:
ldr r1, =0xffffffff
mov r2, #6
ldr r0, =0x020c4080 @ CCM_CCGR6
ldr r2, =0x020c4068 @ CCM_CCGR0

CCGR_loop_init:
str r1, [r0]
sub r0, #4
cmp r2, r0
ble CCGR_loop_init

ldr r0, =0x020e0068 @ IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
ldr r1, =0x5
str r1, [r0]

ldr r0, =0x020e02f4 @ IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
ldr r1, =0x10b0
str r1, [r0]

ldr r0, =0x0209c004 @ GPIO1_GDIR
ldr r1, =0x8
str r1, [r0]

loop:
ldr r0, =0x0209c000 @ GPIO1_DR
ldr r1, =0x0
str r1, [r0]

ldr r0, =0x1f78a400 @ 528M
ldr r1, =0xfbc5200 @ 264M
delay1:
sub r0, #100
cmp r0, r1
bge delay1

ldr r0, =0x0209c000 @ GPIO1_DR
ldr r1, =0x8
str r1, [r0]

ldr r0, =0x1f78a400 @ 528M
ldr r1, =0xfbc5200 @ 264M
delay2:
sub r0, #100
cmp r0, r1
bge delay2

b loop

程序编译

编译

arm-linux-gnueabihf-gcc -g -c led.s -o led.o

上述命令就是将 led.s 编译为 led.o,其中“-g”选项是产生调试信息,GDB 能够使用这些调试信息进行代码调试。“-c”选项是编译源文件,但是不链接。“-o”选项是指定编译产生的文件名字。

链接

arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf

上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里命名为 led.elf。

格式转换

arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin

烧录要用到bin文件,上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。

反汇编

arm-linux-gnueabihf-objdump -D led.elf > led.dis

有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,上述代码中的“-D”选项表示反汇编所有的段。

Makefile脚本

1
2
3
4
5
6
7
led.bin:led.s
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o led.bin led.elf led.dis

注意:每一个命令行必须以 注意:每一个命令行必须以[Tab]字符开始,不能是空格开始,[Tab] 字符告诉 make 此行是一个命令行,make 按照命令完成相应的动作。这也是书写按照命令完成相应的动作,这也是书写 Makefile 中容易产生,而且比较隐蔽的错误。报错信息:Makefile:2: *** 遗漏分隔符 (null)。 停止。

反汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Disassembly of section .text:

87800000 <_start>:
87800000: e3e01000 mvn r1, #0
87800004: e3a02006 mov r2, #6
87800008: e59f0078 ldr r0, [pc, #120] ; 87800088 <delay2+0x10>
8780000c: e59f2078 ldr r2, [pc, #120] ; 8780008c <delay2+0x14>

87800010 <CCGR_loop_init>:
87800010: e5801000 str r1, [r0]
87800014: e2400004 sub r0, r0, #4
87800018: e1520000 cmp r2, r0
8780001c: dafffffb ble 87800010 <CCGR_loop_init>
87800020: e59f0068 ldr r0, [pc, #104] ; 87800090 <delay2+0x18>
87800024: e3a01005 mov r1, #5
87800028: e5801000 str r1, [r0]
8780002c: e59f0060 ldr r0, [pc, #96] ; 87800094 <delay2+0x1c>
87800030: e59f1060 ldr r1, [pc, #96] ; 87800098 <delay2+0x20>
87800034: e5801000 str r1, [r0]
87800038: e59f005c ldr r0, [pc, #92] ; 8780009c <delay2+0x24>
8780003c: e3a01008 mov r1, #8
87800040: e5801000 str r1, [r0]

87800044 <loop>:
87800044: e59f0054 ldr r0, [pc, #84] ; 878000a0 <delay2+0x28>
87800048: e3a01000 mov r1, #0
8780004c: e5801000 str r1, [r0]
87800050: e59f004c ldr r0, [pc, #76] ; 878000a4 <delay2+0x2c>
87800054: e59f104c ldr r1, [pc, #76] ; 878000a8 <delay2+0x30>

87800058 <delay1>:
87800058: e2400064 sub r0, r0, #100 ; 0x64
8780005c: e1500001 cmp r0, r1
87800060: aafffffc bge 87800058 <delay1>
87800064: e59f0034 ldr r0, [pc, #52] ; 878000a0 <delay2+0x28>
87800068: e3a01008 mov r1, #8
8780006c: e5801000 str r1, [r0]
87800070: e59f002c ldr r0, [pc, #44] ; 878000a4 <delay2+0x2c>
87800074: e59f102c ldr r1, [pc, #44] ; 878000a8 <delay2+0x30>

87800078 <delay2>:
87800078: e2400064 sub r0, r0, #100 ; 0x64
8780007c: e1500001 cmp r0, r1
87800080: aafffffc bge 87800078 <delay2>
87800084: eaffffee b 87800044 <loop>
87800088: 020c4080 andeq r4, ip, #128 ; 0x80
8780008c: 020c4068 andeq r4, ip, #104 ; 0x68
87800090: 020e0068 andeq r0, lr, #104 ; 0x68
87800094: 020e02f4 andeq r0, lr, #244, 4 ; 0x4000000f
87800098: 000010b0 strheq r1, [r0], -r0
8780009c: 0209c004 andeq ip, r9, #4
878000a0: 0209c000 andeq ip, r9, #0
878000a4: 1f78a400 svcne 0x0078a400
878000a8: 0fbc5200 svceq 0x00bc5200

和我写的汇编代码都是一一对应的,只是把直接数放在了代码段的最后,ldr通过pc+offset来取。

从反汇编来看还把ldr一些短的直接数改成了mov指令。

起因

云途MCU内存有ECC(Error Correcting Code)功能,需要在startup.s中初始化所有内存(既赋值0),所以软复位后从startup.s中reset_handle运行,会重新初始化内存,原本内存的值会被清0,无法使用内存OTA升级程序,需要用到Flash来保存OTA信息。

这篇文章来讲下汇编启动程序做了什么,单片机启动过程,ld链接脚本中定义的变量在汇编程序中的引用,不同编译器汇编程序的区别。

参考文档

  1. 正点原子《I.MX6U 嵌入式 x Linux 驱动开发指南 V1.6 6》——第七章 ARM 汇编基础
  2. ARM开发人员网站,可以直接搜索指令
  3. ARM资源图书馆,可以下载白皮书、ARM编程手册
  4. Documentation for binutils,binutils工具链(ld, as…)的官方文档

启动程序和启动过程

常用指令

2020032320502673.png

startup_stm32f40_41xxx.s 代码分析

1
2
3
4
5
6
7
8
9
10
Stack_Size      EQU     0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit

第1行:EQU 是表示宏定义的伪指令,类似于 C 语言中的#define。伪指令的意思是指这个“指令”并不会生成二进制程序代码,也不会引起变量空间分配。0x00000400 表示栈大小,字节为单位。0x00000400 =1024字节=1KB。

第2行:开辟一段数据空间可读可写,段名 STACK,按照8字节对齐。ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。

  • STACK :表示这个段的名字,可以任意命名。
  • NOINIT:表示此数据段不需要填入初始数据。
  • READWRITE:表示此段可读可写。
  • ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于0)。

第3行:SPACE 这行指令告诉汇编器给STACK段分配 0x00000400 字节的连续内存空间。

第4行:__initial_sp 紧接着SPACE语句放置,表示了栈顶地址。__initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
                PRESERVE8   ; 指定当前文件保持堆栈8字节对齐
THUMB ; 表示后面的指令是THUMB指令集


; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors ; EXPORT申明标号为可被外部引用
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
...... 省略
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
DCD CRYP_IRQHandler ; CRYP crypto
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors

上面这块代码初始化了中断向量表,第一个是SP指针初始化地址,后面是中断向量表,包含异常处理和外设中断,DCD会定义个4Bytes空间存储中断要跳转的地址。这块RESET数据段放在Flash开始,程序从Flash首地址开始运行,先初始化SP和PC(PC就是Reset_Handler),再跳转去Reset_Handler执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                AREA    |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ; 弱定义
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0 ; 跳转至SystemInit()函数初始化时钟
LDR R0, =__main ; 跳转至__main()初始化堆栈, __main()由MDK自动生成
BX R0
ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
...... 省略

上面这块定义了中断服务函数,都是弱定义,用户可以在别的文件中重定义。除了Reset_Handler有实现,其他都为死循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit

ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap

__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF

启动代码的最后一部分,简单的汇编语言实现 IF ELSE语句。如果定义了__MICROLIB,那么程序是不会执行ELSE分支的代码。MDK中MicroLIB的作用,参考:KeilMDK配置项中Use MicroLIB

起因

之前UART用的比较多,232, 485, 422通信方式用的比较少,最近储能的项目用到了485通信,在之前卡片机的项目也用到485通信,今天来归纳下232, 485, 422这三种通信的区别。

参考资料

  1. 本文搬运自 转载:串口通信232/485/422 详细解析! ,自己复习使用

UART

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)。

通信协议

UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,其中各位的意义如下:

起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。

数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。

奇偶校验位:数据位加上这一位后,使得”1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。

停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢

空闲位:处于逻辑”1”状态,表示当前线路上没有数据传送。

图片

波特率与比特率

比特率在数字信道中,比特率是数字信号的传输速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数bit/s(bps)、每秒千比特数(Kbps)或每秒兆比特数(Mbps)来表示(此处K和M分别为1000和1000000,而不是涉及计算机存储器容量时的1024和1048576)。

波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,其单位为波特(Baud)。波特率与比特率的关系为:比特率=波特率X单个调制状态对应的二进制位数。

如何区分两者?显然,两相调制(单个调制状态对应1个二进制位)的比特率等于波特率;四相调制(单个调制状态对应2个二进制位)的比特率为波特率的两倍;八相调制(单个调制状态对应3个二进制位)的比特率为波特率的三倍;依次类推。

232

232 通信主要是由RX,TX,GND三根线组成。RX与TX,TX接RX,GND接GND。因为发送和接收分别是由不同的线处理的,也就是能同时发送数据和接收数据,这就是所谓的全双工。

图片

在这里扩展一下,串口通信还有一个功能叫做全功能串口通信,也叫标准串口。因为在两个设备间进行数据传输,有些设备处理速度比较快,有些数据比较慢。为了保证数据能正常传输,在RX,TX的基础上,还增加了几个控制引脚,本来好端端就R,T,G,三根线,凑着就凑齐了9个引脚,召唤出了DB9这个东西。

图片

这要怪就怪当时使用电脑的时候,还没有互联网这个概念,但是又想在两台电脑间进行通信。所以才有这样一个东西。在后来的设备,很多控制器,人机界面,PLC等使用串口通信中,基本上就不使用标准串口,而是就直接使用RX,TX,GND三根线来通信了。但是这里为什么要提到这个呢。因为只是很多设备这样用,也就是还存在少数设备还保留了标准串口的功能。这就是为什么会遇到明明电脑通信是好的,换成触摸屏通信就不行了。因为很多触摸屏只使用了RX,TX,GND通信,遇到一些还保留标准串口功能的就比较讨厌了。

485

485是为了解决232通信距离的问题。原理什么之类的就不多讲了。反正232通信距离就是不长。485主要是以一种差分信号进行传输,只需要两根线,+,-两根线,或者也叫A,B两根线。A,B两根线的差分电平信号就是作为数据信号传输。

那么问题来了,那是不是就没有RX和TX的概念了。是的,发送和接收就不能分开了。发送和接收都是靠这两根的来传输,也就是每次只能作发送或者只能作接收,这就是半双工的概念了,这在效率上就比232弱很多了。就像对讲机一样,经常是某个人讲完之后,都要说一个over,确保当前说完了,等待对方回复。

图片

485就是这样牺牲了232全双工的效率来达到自己传输距离远的代价。那有没有即保留了232的全双工,又可以像485这样提高传输距离呢,于是,422出来了。

422

422呢,有些标注为485-4。而485就标注为485-2。有什么区别呢。就是为了好记呢。485-2就是2根线。485-4就是4根线。

图片

422就是把232的RX分成两根线,RX+,RX-,把TX分成TX+,TX-。这样就可以同时发送和同时接收了,还可以像485这样,有较远的传输距离。可是这样一种很有优势的通信方式,为什么用的不多呢。我个人的答案和理解就是:线太多了。特别是像我这样懒得接线的人,超过3根线就头晕的。搞个通信还需要接这么多线,什么TX,RX,正啊负啊。交换来交换去。

因为在很多设备通信中,基本上是属于一问一答式的,因此,232的全双工通信优势其实也并没有发挥出来。就像现在打电话,虽然两个人可以同时说话,但是两个人同时说话,叽叽歪歪的,谁知道说什么呀。特别是一个主站与多个从站通信的时候,485的接线就就方便多了,反正大家就两根线,把+都接一块,把-都接一块。如果是422作一主多从,接线上还要理半天呢,而且通信异常了也不好解决。

参数解析

argparse — 命令行选项、参数和子命令解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import argparse

# 创建一个解析器
parser = argparse.ArgumentParser(description='use for Encrypting/Decrypting.')
# 添加参数
parser.add_argument('-d', action="store_true", help='Decryption command')
parser.add_argument('-e', action="store_true", help='Encryption command')
parser.add_argument('-r', action="store_true", help='Rename command')
parser.add_argument('-p', action="store", required=True, help='file path or folder path')

if __name__ == '__main__':
args = vars(parser.parse_args())
print(args)

指令 -p 为必须,通过 -p 传入文件或文件夹路径参数,指令 -d -e -r 告知脚本要执行什么任务。

Python vars() 函数 返回对象object的属性和属性值的字典对象。

1
2
PS C:\decipherer> python main.py -p .\test1.c
{'d': False, 'e': False, 'r': False, 'p': '.\\test1.c'}

解析路径

Python os.path() 模块 — 主要用于获取文件的属性

1
2
3
4
5
6
main_file_path = os.path.dirname(os.path.realpath(__file__))
file_name = os.path.basename(file_path)
file_content = fp.read()
file_name_md5 = hashlib.md5(file_name.encode(encoding='UTF-8')).hexdigest()
file_relative_path = os.path.realpath(file_path).replace(main_file_path, '') # 相对地址
file_relative_dir = os.path.dirname(file_path).replace(main_file_path, '') # 相对地址不带文件名

Python3 os.walk() 方法 — 用于遍历文件夹

1
2
3
4
def rename_dir(dir_path):
for root, dirs, files in os.walk(dir_path, topdown=False):
for name in files:
rename_file(os.path.join(root, name))

shutil.rmtree() — 删除一个完整的目录树

1
2
if os.path.exists('./output_rename'):
shutil.rmtree('./output_rename') # 删除之前的目录

makedirs() — 递归目录创建函数

1
2
if os.path.exists('./output_rename' + file_relative_dir) == 0:
os.makedirs('./output_rename' + file_relative_dir) # 如果目录不存在 创建新目录

MD5加解密

hashlib — 安全哈希与消息摘要

字符串编解码

方法 描述
string.decode(encoding=’UTF-8’, errors=’strict’) 以 encoding 指定的编码格式解码 string,如果出错默认报一个 ValueError 的 异 常 , 除非 errors 指 定 的 是 ‘ignore’ 或 者’replace’
string.encode(encoding=’UTF-8’, errors=’strict’) 以 encoding 指定的编码格式编码 string,如果出错默认报一个ValueError 的异常,除非 errors 指定的是’ignore’或者’replace’

保存文件名是用encode指定编码格式,再读取时用decode指定格式解码,否则遇到中文字读取会出问题。

起因

今天在移植陀螺仪项目时,之前的代码全局变量过多,函数功能不够独立,现在使用国产雅特力MCU,M4主频120MHz,64K ROM,16K RAM,想使用RTOS重新写代码,实践一次嵌入式RTOS编程。

参考资料

  1. 参考RT-Thread官网教程:https://www.rt-thread.org/document/site/#/

开始干

选型

选用RT-Thread Nano版本,资源占用小:对 RAM 与 ROM 的开销非常小,在支持 semaphore 和 mailbox 特性,并运行两个线程 (main 线程 + idle 线程) 情况下,ROM 和 RAM 依然保持着极小的尺寸,RAM 占用约 1K 左右,ROM 占用 4K 左右。

移植

移植参考官网教程:使用 MDK 移植

移植时遇到的几个问题

  1. #error "TODO 1: OS Tick Configuration."一直报错,这个只是编译时提醒我们要配置OS Tick,配置后需手动注释掉这条。
  2. SysTick_Handler()有时进有时不进,检查方法查看SysTick结构体,发现CTRL中使能位0,原因:后面的代码使用了delay函数关闭SysTick的使能位。

线程

初始化静态线程

1
2
3
4
5
rt_err_t rt_thread_init(struct rt_thread* thread,
const char* name,
void (*entry)(void* parameter), void* parameter,
void* stack_start, rt_uint32_t stack_size,
rt_uint8_t priority, rt_uint32_t tick);

启动线程

rt_err_t rt_thread_startup(rt_thread_t thread);

遇到的问题

  1. 遇到rt_thread_init()函数卡死,百度竟然啥都查不到(rt-thread用的人这么少的吗),后尝试将栈大小增加至256后成功初始化线程。虽然任务里没什么局部变量,但是一个简单的按键任务竟然占了500多字节内存,可能是因为栈中还保存了寄存器、TCB等信息,那我这单片机16K字节内存可能不够。

  2. 需要在main函数while(1)中加入rt_thread_mdelay(10); 否则main线程优先级更高,其他线程无法运行。

起因

这几天在做云途单片机的CAN接口移植,学习了CAN通信,还看了一些通信时用到的环形队列、通信协议栈。

参考资料

  1. 本文搬运自 CAN通信讲解 ,自己复习使用

  2. s32k144、云途ME0单片机参考手册

背景介绍

CAN是控制器局域网络(Controller Area Network, CAN)的简称,是一种能够实现分布式实时控制的串行通信网络。

CAN的发展历史节点:

  • 1983年,BOSCH开始着手开发CAN总线;
  • 1986年,在SAE会议上,CAN总线正式发布;
  • 1987年,Intel和Philips推出第一款CAN控制器芯片;
  • 1991年,奔驰 500E 是世界上第一款基于CAN总线系统的量产车型;
  • 1991年,Bosch发布CAN 2.0标准,分 CAN 2.0A (11位标识符)和 CAN 2.0B (29位标识符);
  • 1993年,ISO发布CAN总线标准(ISO 11898),随后该标准主要有三部分:
  • ISO 11898-1:数据链路层协议
  • ISO 11898-2:高速CAN总线物理层协议,通信速度为 125kbps-1Mbps。
  • ISO 11898-3:(整合了ISO11519)低速CAN总线物理层协议,通信速度为 125kbps 以下。
  • 2011年,开始CAN FD协议的开发。
  • 2015年ISO11898-1进行了修订,将CAN FD加入其中。

CAN总线协议介绍

CAN总线协议有CAN1.0、CAN2.0(CAN2.0A、CAN2.0B),其中CAN2.0对比1.0,主要是增加了CAN的扩展帧定义。现在我们所说的CAN通常都是指CAN2.0标准的总线。

CAN-FD协议在原有的CAN协议基础上,增加了可变波特率、扩大数据场、提升校验算法安全性等改进。

本文主要讲述CAN的数据通信,CAN-FD的区别会在其他文章单独讲解。

CAN的物理通信形式

通过两条通信线(双绞线)产生的电压差传输数据,一个CAN网络里的所有节点都挂在这两条通信线上,使用差分信号半双工通信。

img

CAN 使用称为 CANH / CANL 的通信线路执行传输和接收。没有电位差的信号称为隐性(Recessive)信号,其逻辑值为1。具有电位差的信号称为显性(Dominant)信号,其逻辑值0。如果通信总线上发生显性和隐性(Recessive)冲突,则显性(Dominant)优先。总线空闲时保持隐性。

CAN总线的物理层逻辑电平,分为高速ISO11898标准(125kbps ~ 1Mbps)和低速ISO11519标准(10kbps ~ 125kbps);

低速的物理层电平如图:

img

而我们现在通常使用的CAN2.0,都是使用高速CAN标准,其物理层电平如图:

img

对于高速CAN,总结一下,也就是:

  • CAN_H-CAN_L < 0.5V 时候为隐性的,逻辑信号表现为”逻辑1”- 高电平。
  • CAN_H-CAN_L > 0.9V 时候为显性的,逻辑信号表现为”逻辑0”- 低电平。

关于CAN通信的电平传输,一个重要概念就是:

CAN总线在电平传输上,具有仲裁判断逻辑,优先级为:显性(低电平)>隐形(高电平)!

在理解CAN总线传输的整个过程中,主要就是清楚这一规则在传输时的灵活运用,并定义的各种帧形式。

CAN的数据格式

CAN的数据定义了有5种帧类型:

img

其中,遥控帧也常被称为远程帧。CAN的应用开发者只能使用“数据帧”和“遥控帧”,其他的3种帧类型是由CAN的底层固件自动帮我们在特定场景下进行收发,开发者无需担心也无法直接参与控制。

所以,本文把“数据帧”和“遥控帧”与其他的3种帧类型分别进行介绍。

数据帧与遥控帧

关于数据帧,也就是我们最常用的帧类型,用于数据的收发;也是CAN通信里最主要的内容。

而遥控帧,只是CAN网络里的某一节点发送一个遥控帧请求其他的节点反馈数据给自己,关于遥控帧其实在实际使用中,显得很鸡肋,原因有:

1、CAN通信作为一种半双工通信形式,在实际使用中的应用层通信协议往往会定义好数据的应答机制与时间间隔,节点与节点之间只要按照协议规定进行数据的收发即可。

2、遥控帧与数据帧对比,其实就是一条数据长度为0的数据帧而已,只是在帧格式里的仲裁段RTR位为隐性。那么,似乎有数据帧就足够了。

3、遥控帧的概念定义只是一个预定义,所谓的请求其他节点给自己发送数据并不是强制的,与数据帧一样完全根据应用层协议来规定其具体的使用。

综上所述,CAN里定义的遥控帧实际作用不大,而且可以用数据帧配合应用协议的定义,进行替代。所以在后来的CAN-FD中已经取消了遥控帧的定义了。

本文主要以数据帧进行介绍,并简单介绍遥控帧。

数据帧与遥控帧的数据格式

不管是Classic CAN Frame还是CANFD Frame,其帧结构都由以下7个段组成:

— SOF帧起始;

— arbitration field仲裁段;

— control field控制段;

— data field数据段;

— CRC field;

— ACK field;

— EOF.

img

这7个段,每个段里又都有自己的格式细分,有两种格式:标准格式和扩展格式。

img

CAN的应用开发者只使用其中的仲裁段、控制段和数据段。其他部分都由CAN底层固件自动封装!

由上图可以看到,对于仲裁段和控制段在标准帧与扩展帧里有不同的定义,其他段一致。

帧起始与帧结束

SOF帧起始:由一个显性位(低电平)组成,发送节点发送帧起始,其他节点同步于帧起始;

EOF帧结束:由7个隐形位(高电平)组成。

img

仲裁段

仲裁机制

只要总线空闲,总线上任何节点都可以发送报文,如果有两个或两个以上的节点开始传送报文,那么就会存在总线访问冲突的可能。但是CAN使用了标识符的逐位仲裁方法可以解决这个问题。帧ID越小,优先级越高。

CAN总线控制器在发送数据的同时监控总线电平,如果电平不同,则停止发送并做其他处理。如果该位位于仲裁段,则退出总线竞争;如果位于其他段,则产生错误事件。

img

仲裁段内容

RTR位:用于指示这包数据是遥控帧还是数据帧,数据帧的RTR位为显性电平,远程帧为隐性电平。

所以帧格式和帧ID相同的情况下,数据帧优先于远程帧。

IDE位:用于指示这包数据是标准帧还是扩展帧,标准帧的IDE位为显性电平,扩展帧的IDE位为隐形电平。

对于前11位ID相同的标准帧(RTR为显性的遥控帧)和扩展帧,标准帧优先级比扩展帧高。

img

可以看到,在标准格式里,仲裁段没有IDE位,其实这个位在标准格式里是放在控制段的第一位的,这样就正好可以和扩展格式的IDE位对应上进行仲裁了。

控制段

仲裁段之后紧跟控制段,控制段共6位,标准帧的控制段由IDE、保留位r0和数据长度代码DLC组成;扩展帧控制段则由保留位r1、r0和DLC组成,如图:

img

在这里可以看到,在标准格式里,IDE位放到了控制段的第一位来了,对应前文仲裁段的内容,就可以使标准格式与扩展格式进行仲裁了。

保留位( r0 、 r1 ):保留位必须全部以显性电平发送。

数据长度码( DLC ):数据的字节数必须为 0 ~ 8 字节。数据帧的DLC表示的就是当前包数据段所带的字节数,遥控帧的DLC表示的是请求返回的数据长度。

数据段

一个数据帧传输的数据量为0~8个字节。遥控帧的数据段长度固定为0。

img

CRC段

CAN-bus使用CRC校验进行数据检错,CRC校验值存放于CRC段。 CRC校验段由15位CRC值和1位CRC界定符构成如图:

img

ACK段

当一个接收节点接收的帧起始到CRC段之间的内容没发生错误时,它将在ACK段发送一个显性电平如图:

img

位填充

CAN数据在收发上除了会遵循以上数据格式定义之外,还有一个“位填充”的底层规则(类似通信协议里的“转义符”),这个操作是在CAN的底层固件中自动判断执行的,其目的是为了增强数据正确性,以便识别错误信号。

为防止突发错误而设定,CAN协议中规定,当相同极性的电平持续五位时,则添加一个极性相反的位。填充位的添加和删除是由发送节点和接收节点完成的,CAN-BUS只负责传输,不会操纵信号。

img

  • 对于发送节点而言:

在发送数据帧和遥控帧时,对于SOF~CRC(除去CRC界定符) 之间的位流,相同极性的电平如果持续5位,那么在下一个位插入一个与之前5位反型的电平;

  • 对于接收节点而言:

在接收数据帧和遥控帧时,对于SOF~CRC(除去CRC界定符)之间的位流,相同极性的电平如果持续5位,那么需要删除下一位再接收。如果这个第 6 个位的电平与前 5 位相同,将被视为错误并发送位填充错误帧

错误帧、过载帧与帧间隔

对于这三种帧,都是在使用数据帧或遥控帧的过程当中进行错误、时序管理的辅助信号,并不会单独出现在CAN网络中;如前文所述是由CAN的底层固件自动判断并执行他们收发的,但是CAN的开发人员有必要对它们进行了解,以对CAN网络有一个整体的认识。

错误帧

尽管CAN-bus是可靠性很高的总线,但依然可能出现错误;CAN-bus的错误类型共有5种:

img

当出现5种错误类型之一时,发送或接收节点将发送错误帧。但是错误帧又有两种格式:主动错误格式和被动错误格式。

主动错误和被动错误,指的并不是发送方与接收方。而是指某一CAN节点的“错误状态”,无论发送方还是接收方,都会处于自己的错误状态,并根据自身的状态来决定自己要发送主动错误格式还是被动错误格式:

img

由上图可知,6个连续的显性或隐性电平位,正好违反了之前所提及的“位填充”规则,CAN总线设计上就是利用了自己的这一规则对错误数据进行刻意的覆盖破坏,使总线上其他节点都知道错误的发生。

错误状态

在CAN节点内,有两个计数器:发送错误计数器(TEC)和接收错误计数器(REC),当该节点检测到错误后,内部REC/TEC计数器会相应的增加,基于REC/TEC的值判定节点状态,CAN的错误状态如图:

img

节点错误状态的转换就是一个 “量变”到“质变” 的过程:

  • 主动错误状态:【REC<127 且TEC<127】

初步可判定该节点相对稳定可靠,该错误计数很可能是由于某个节点异常导致的,那么其他节点很可能也会触发该错误,那么允许该节点破坏CAN总线的异常报文并告知其他节点;
节点检测到一个错误就会发送带有主动错误标志的错误帧,因为主动错误标志是连续六个显性位,所以这个时候主动错误标志将会“覆盖”掉总线上其它节点的发送,而之前在CAN总线上传输的报文就被这“六个连续显性位”破坏掉了。
如果发出主动错误帧的节点是发送节点,这个情况下就相当于:刚刚发送的那一帧报文我发错了,现在我破坏掉它(发送主动错误帧),你们不管收到什么都不算数;

如果发出主动错误帧的节点是接收节点,这个情况就相当于:刚刚我收报文的时候发现了错误,不管你们有没有发现这个错误,我现在主动站出来告诉大家这个错误,并把这一帧报文破坏掉(发送主动错误帧),刚才你们收到的东西不管对错都不算数了。

  • 被动错误状态:【REC>128 或TEC>128】

节点发送错误帧的次数较多,初步可判定该节点相对不可靠,该错误计数很可能是由于自身节点问题导致,即该错误很可能仅有该节点才有,对于其他节点而言是可以正常交互的,总线不信任该节点提供的错误标识,将不允许破坏总线数据,那么允许该节点发送错误帧“6个连续隐性位”至CAN总线,仅告知其他节点异常;
如果发出被动错误帧的节点为报文的发送节点,那么在发送被动错误帧之后,刚刚正在发送的报文被破坏,并且该节点不能在错误帧之后随着连续发送刚刚发送失败的那个报文。随之而来的是帧间隔,并且连带着8位隐性位的 “延迟传送” 段;这样总线电平就呈现出连续11位隐性位,总线上的其它节点就能判定总线处于空闲状态,就能参与总线竞争。

此时如果该节点能够竞争成功,那么它就能接着发送,如果竞争不能成功,那么就接着等待下一次竞争。这种机制的目的正是为了让其它正常节点(处于主动错误)优先使用总线。

  • 总线关闭状态:【TEC>255】

一个处于被动错误状态的节点,仍然多次发送被动错误帧,使该节点转为总线关闭态;
该节点不能向总线上发送报文,也不能从总线上接收报文,整个节点脱离总线。等到检测到128次11个连续的隐性位时,TEC和REC置0,重新回到主动错误状态。
由于存在实现方式的不同,CAN总线关闭状态存在只允许用户请求恢复和检测到128个11位连续的隐性位时自恢复两种不同的恢复形式。

如果总线上只有一个节点,该节点发送数据帧后得不到应答,TEC最大只能计数到128,即这种情况下节点只会进入被动错误状态而不会进入总线关闭状态。

错误帧的发送

按照CAN协议的规定:
发生位错误、填充错误、格式错误、ACK错误时,则在错误产生的那一位的下一位开始发送错误帧。
发生CRC错误时,紧随ACK界定符后的位发送错误帧。

错误帧发送完成后,总线空闲时自动重发出错的数据帧。

【位错误】举例(情况1):

  • 设总线上所有节点处于主动错误状态;
  • 当一个发送节点监控到总线上的位数值与发送的位数值不一致时,检测为位错误,并发送主动错误标志(6个连续的显性位);
  • 接收节点接收到发送节点发送的6个连续的显性位时,会检测为位填充错误,也会发送主动错误标志;
  • 发送节点发送完主动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);
  • 当接收节点发送完主动错误标志后,开始向总线发送错误界定符; 等待错误帧发送完成,总线空闲后,发送节点重新发送出错的报文.

由于发送节点发送6个连续的显性位会破坏位填充规则,触发接收节点发送主动错误标志,发送节点和接收节点的结合是形成错误标志叠加部分的原因。

img

【位错误】举例(情况2):

  • 假设发送节点处于被动错误状态,接收节点处于主动错误状态;
  • 当发送节点监控到总线上的位数值与发送的位数值不一致时,检测为位错误,并发送被动错误标志(6个连续的隐性位);
  • 接收节点接收到发送节点发送的6个连续的隐性位时,会检测为位填充错误,并会发送主动错误标志;
  • 发送节点发送完被动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);
  • 接收节点发送完主动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);

img

过载帧与帧间隔

过载帧与主动错误帧非常相似,甚至可以把过载帧直接理解成也是一种错误帧,只是它的错误触发条件不同罢了。

当某个接收节点没有做好接收下一帧数据的准备时,将发送过载帧以通知发送节点;过载帧由过载标志和过载帧界定符组成,如图所示:

img

由于存在多个节点同时过载且过载帧发送有时间差问题,可能出现过载标志叠加后超过6个位的现象,如图所示:

img

过载帧是用于接收单元通知发送单元它尚未完成接收准备的帧。在两种情况下,节点会发送过载帧:

  • 接收单元条件的制约,要求发送节点延缓下一个数据帧或远程帧的传输;
  • 帧间隔(Intermission)的 3 bit 内检测到显性位

帧间隔是用于分隔数据帧、遥控帧这些有效数据的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、 遥控帧、错误帧、过载帧)分开。

img

注意,过载帧和错误帧由于要按照发送条件立即执行,前不能插入帧间隔

CAN的采样点与波特率

根据前文描述,由于CAN总线在通信时,每个节点都会不断的监控总线上的实际电平用于仲裁、判断错误等功能,因此CAN定义了采样点这一概念指明节点对总线的监控时间点。

因为CAN要对总线上特定数据里的每一个位都要进行一次单独的监控,所以这个监控的时间点,指的是在一个“位”的时间范围之内的一个“相对时间”,比如:一个位时间的中间时刻,就把它称为50%采样点(率)。这个解释只是让读者直接理解它的大体概念,实际情况并不是这么简单。

实际的CAN采样点的位时间划分如下:

img

img

由上图可知,CAN每发送一个位,需要涉及的内容有:

时间单元(Tq):指的是CAN模块时钟提供的单位时间,与其他的芯片外设一样,任何外设模块都需要提供合适的时钟才能正常工作,在很多芯片里CAN时钟还会配合一个分频器,也就是:CAN时钟= CAN时钟源(如图中的晶振时钟) / CAN_Prescaler。时间单元指的就是分频后的实际CAN时钟的单位时间。

位时间:就是我们理解的CAN波特率里一位的时间,由上图可知,CAN每发送一个位都由几个“段”组成,而每个段又需要占用几个“时间单元(Tq)”,所以我们在使用CAN的时候,就需要通过指定这些段的Tq个数来得到CAN的波特率。

同步段(SS:Synchronization Segment):用于同步CAN总线上的各个节点。输入信号的跳变沿就发生在同步段,该段持续时间固定为 1 Tq。同步段用于同步总线上的各个节点,一个位的跳变边沿在此时间段内。

传播段(PTS:Propagation Time Segment):用于补偿各节点之间的物理传输延迟时间。传输延迟时间为信号在总线上传播时间的两倍,包括总线驱动器延迟时间。传播段的长度一般有一个取值范围,不同的控制器不完全一致,典型值为 1 – 8 TQ。在CAN-FD中取消了传播段。

相位缓冲段1(PBS1:Phase Buffer Segment 1):用于补偿节点间的晶振误差,允许通过重同步(SJW)对该段加长。在该时间段结束时进行总线电平采样点的采样。

相位缓冲段2(PBS2:Phase Buffer Segment 2):用于补偿节点间的晶振误差,允许通过重同步(SJW)对该段缩短。

不同的控制器,PBS1/PBS2 的取值范围不完全一致,一般 PS1 为 1 – 8 TQ,PS2 为 2 – 8 TQ。

在有的控制器里,把传播段与相位缓冲段1合并称为“时间段1”,而相位缓冲段2称为“时间段2”,如图:

img

重同步(SJW):时间段1而不是在同步段(SS)检测到有效跳变,那么相位缓冲段1(PBS1) 的时间就被延长最多SJW那么长,从而采样点被延迟了。相反如果在时间段2而不是在同步段(SS)检测到有效跳变,那么相位缓冲段2(PBS2) 的时间就被缩短最多SJW那么长,从而采样点被提前了。如图:

img

综上所述:

CAN时钟 = CAN时钟源 / 分频值CAN_Prescaler;

CAN波特率 = CAN时钟 / (SS(1Tq) + PTS + PBS1 + PBS2)的Tq总个数;

CAN采样点(率) = (SS(1Tq) + TSEG1) / (SS(1Tq) + TSEG1 + TSEG2) * 100%

= (SS(1Tq) + PTS + PBS1) / (SS(1Tq) + PTS + PBS1 + PBS2) * 100%;

注意:在实际的CAN使用中,一个CAN网络的各节点最好把采样点设置成一样,如果采样点的设置偏差较大,虽然可能不会造成完全不能通信的情况,但是由于不同节点的判断时间点不同,会造成CAN通信上出现较大概率的错误数据。

如果发现通信误码率较高,不妨可以排查一下各个节点的CAN采样点设置。

起因

这几天在做云途单片机的flash接口移植,同时陀螺仪的代码也需要将校准参数保存到flash每次开机读取,于是就看了s32k单片机的flash操作和模拟eeprom,同时看了云途的手册和sdk,也看了stm32闪存编程参考手册和hal库。

参考资料

  1. 【正点原子】 手把手教你学STM32入门教学视频单片机 嵌入式 之 F103-基于新战舰V3/精英/MINI板
  2. 《STM32F10xxx闪存编程参考手册》,《STM32 FLASH 模拟 EEPROM 使用和优化》
  3. 云途、s32k的参考手册,云途和stm32的sdk
  4. nor flash原理详细讲解
  5. NANDFlash原理

eeprom, norflash, nandflash特性

搬运自EEPROM, NAND FLASH, NOR FLASH

内部结构

EEPROM基于浮栅管单元(Floating gate transister)的结构。 EEPROM 的 单元是由FLOTOX(Floating- gate tuneling oxide transister)及一个附加的Transister 组成。由于FLOTOX 的特性及两管结构,所以可以单bit写。它的每个存储单元并联。

flash属于广义的EEPROM。FLASH 基于EEPROM, 与EEPROM主要的不同是FLASH 去除了 EEPROM 存储单元里的Tansister, 简化了存储电路(注意不是控制电路)。除此之外FLASH 的浮栅工艺不同, 所以写入速度更快。

NOR FLASH的每个存储单元(bit)以并联的方式连接到数据bit线,方便对每一位进行随机存取。同时具有专用的地址线(可以理解为字线),可以实现byte的直接寻址。NORFLASH具有足够的地址和数据线来映射整个存储区域,类似于SRAM的工作方式。例如,具有16位数据总线的2Gbit(256MB)NOR闪存将具有27条地址线,可以对任何byte进行随机读取访问。所以NORFLASH可以按byte读取。

NAND FLASH各存储单元(bit)之间是串联的。在读取数据时,当字线和位线锁定某个晶体管时,该晶体管的控制极不加偏置电压,其它的 7 个都加上偏置电压而导通,如果这个晶体管的浮栅中有电荷就会导通使位线为低电平,读出的数就是 0,反之就是 1。所以每次读取都是读取一行bytes里面的同一个bit, 最后整合为一个块:它是按块读取。

成本,容量

EEPROM存储电路并联, 每个bit的存储单元还要多加一个三极管, 最复杂所以单位成本也最高(注意不是控制电路)
因此它容量最低。EEPROM都是几十kbytes到几百kbytes,基本没有有超过512K。

NOR FLASH去除了EEPROM存储单元的三极管,每个存储单元并联, 去除了EEPROM存储单元的三极管, 集成度较小, 所以单位成本高。容量一般为1~16MB。

NAND FLASH, 去除了EEPROM存储单元的三极管, 各存储单元之间串联, 所以集成度大,单位成本最低。容量一般为8~512MB。

写入

EEPROM和FLASH在写入新数据之前,必须先将一个单元擦除(写 1)。 然后再在相应位写0。

NOR&NAND FLASH 去除了EEPROM存储单元的三极管。 所以只能整块的擦除。每次擦除只能擦除一行字节的一个bit。这是一个降低了单位成本的折衷办法,因为这容易导致损耗:减少了总擦除/写入次数。擦除块越小擦除越快。 然而擦除块越小芯片面积和存储器成本增加。 与NOR闪存相比NAND闪存可以更经济高效地支持更小的擦除块。目前,NAND闪存的典型块大小为8KB至32KB, NOR Flash为64KB至256KB。

除此之外在NOR闪存中,每个字节在擦除之前都需要写入“0”。这使得NOR闪存的擦除操作比NAND闪存慢得多。例如,NAND闪存S34ML04G2需要3。5ms才能擦除128KB块,而NOR闪存S70GL02GT则需要约520ms来擦除类似的128KB扇区。这相差近150倍。除此之外NOR的擦除块更大,这就更慢了。

EEPROM和FLASH的浮栅工艺不同, 所以NANDFLASH写入速度最快, EEPROM居中, NOR最慢。

读取数据粒度与速度

EEPROM可以单字节读取。EERPOM一般用于低端产品,读的速度不需要那么快,真要做的话,其实也是可以做的和FLASH 差不多。它的每个存储单元并联。 所以它的特点是可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。

NOR FLASH可以单字节读取。NORFLASH的每个存储单元(bit)以并联的方式连接到数据bit线,方便对每一位进行随机存取;同时具有专用的地址线(可以理解为字线),可以实现byte的直接寻址。NORFLASH具有足够的地址和数据线来映射整个存储区域,类似于SRAM的工作方式。例如,具有16位数据总线的2Gbit(256MB)NOR闪存将具有27条地址线,可以对任何byte进行随机读取访问。所以NORFLASH可以按byte读取, 随机读取(给一个地址读一个字节)时间很短。

NAND FLASH以页为单位读取。NAND FLASH各存储单元(bit)之间是串联的。在读取数据时,当字线和位线锁定某个晶体管时,该晶体管的控制极不加偏置电压,其它的 7 个都加上偏置电压而导通,如果这个晶体管的浮栅中有电荷就会导通使位线为低电平,读出的数就是 0,反之就是 1。所以每次读取都是读取一行bytes里面的同一个bit, 最后整合为一个页:它是按页读取。 随机读取时间很长。NAND 的全部存储单元分为若干个块,每个块又分为若干个页,每个页是 512byte: 每个页有 512 条位线,每条位线下有 8 个存储单元。

NAND FLASH每页存储的数据正好跟硬盘的一个块存储的数据相同,这是设计时为了方便与磁盘进行数据交换而特意安排的。在NAND闪存中,使用多路复用地址和数据总线访问存储器。典型的NAND闪存使用8位或16位多路复用地址/数据总线以及其他信号,如芯片使能,写使能,读使能,地址锁存使能,命令锁存使能和就绪/忙碌。 NAND Flash需要提供命令(读,写或擦除),然后是地址和数据。这些额外的操作也使NAND闪存的随机读取速度慢得多。

NAND闪存的顺序访问持续时间通常低于NOR闪存设备中的随机访问持续时间。利用NOR Flash的随机访问架构,需要在每个读取周期切换地址线,从而累积随机访问以进行顺序读取。随着要读取的数据块的大小增加,NOR闪存中的累积延迟变得大于NAND闪存。因此,NAND Flash顺序读取可以更快。但是由于NAND Flash的初始读取访问持续时间要长得多,两者的性能差异只有在传输大数据块(超过1 KB)时才明显。

可靠性

EEPROM存储电路复杂, 具有较高的可靠性, 最稳定可以保存100年。

NOR FLASH存储电路比复杂, 具有比较高的可靠性。

NAND FLASH存储电路简单, 可靠性比较低。

闪存会遭遇称为位翻转的现象,其中一些位可以被反转。这种现象在NAND存储电路简单中所以位翻转比NOR更常见。NAND器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太低,代价太高,根本不划算。NAND需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。一般来说NAND的Block0是没有位翻转的。随着擦除和编程周期在NAND闪存的整个生命周期中持续,更多的存储器单元变坏。因此坏块处理(错误探测/错误更正(EDC/ECC)算法。)是NAND闪存的强制性功能。 这导致NAND的控制器电路复杂。这个问题对于用NAND存储多媒体信息时不是致命的。 但如果用本地存储设备来存储操作系统,配置文件或其他敏感信息时,必须使用EDC/ECC系统以确保可靠性。

另一方面, NOR闪存坏块少,在存储器的使用寿命期间具有非常低的坏块累积。因此,当涉及存储数据的可靠性时,NOR Flash优于NAND Flash。可靠性的另一个方面是数据保留,这方面,NOR Flash再次占据优势,例如,NOR Flash闪存S70GL02GT提供20年的数据保留,最高可达1K编程/擦除周期,NAND闪存S34ML04G2提供10年的典型数据保留。

擦除次数

EEPROM 每次只需要擦除一个字节所以不容易损耗,可以擦写100w次。

NAND闪存编程和擦除次数比NOR闪存好10倍。在NAND闪存中每个块的最大擦写次数是一百万次,NOR的擦写次数是十万次,NAND存储器除了块擦除次数优势, 典型的NAND块尺寸要比NOR器件小8倍, 每个NAND存储器块在给定的时间内的删除次数要少一些。编程和擦除的数量曾是一个需要考虑的重要特性。随着技术进步,这已不再适用,因为这两种存储器在这方面的性能已经很接近。例如,S70GL02GT NOR和S34ML04G2 NAND都支持100,000个编程 - 擦除次数。但是,由于NAND闪存中使用的块尺寸较小,因此每次操作都会擦除较小的区域, 与NOR Flash相比其整体寿命更长。

XIP(eXecute In Place)

eXecute In Place,即芯片内执行、就地执行,是指CPU直接从存储器中读取程序代码执行,而不用再读到内存中。应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。

flash内执行是指nor flash不需要初始化,可以直接在flash内执行代码。但往往只执行部分代码,比如初始化RAM。好处即是程序代码无需占用内存,减少内存的要求。

EEPROM&NORFLASH 可以像内存一样读任意地址(任意字节),可以XIP,当然这也要看接口是不是内存接口,如果不支持随机读取就一般不行,大部分NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。可以非常直接地使用基于NOR的闪存,可以像其他存储器那样连接,并可以在上面直接运行代码。有的Norflash可以并行连接实现XIP,也可以串行通过SPI实现XIP。对于PC这需要相应的总线桥(内存控制器)芯片支持, Soc内部要集成相应桥芯片, 挂接到PCIE或者AMBA总线。 桥芯片的硬件逻辑会实现串并转换,总线仲裁,Cache结构,Burst等逻辑。

NANDFLASH 是按块读取,随机读取太慢所以不适合XIP当然这也要看接口是不是内存接口,如果不支持随机读取就一般不行,NAND使用复杂的I/O口来串行地存取数据。 一般是8个引脚用来传送控制,地址和数据信息 NANDFLASH只是不适合做XIP,但并不是不能做XIP,它坏块多,读取也太慢。比如EMMC启动就必须要把代码load到RAM里才能启动,你看到某些芯片支持EMMC启动,必定是有片内程序把EMMC的代码读到了RAM里。

驱动复杂程度

NAND更容易遇到位翻转, 驱动和控制电路要复杂的多。在使用NAND器件时,必须先写入驱动程序,才能继续执行其他操作。NAND在每一行上有CRC位标记,以及一些用于指示行是否为好的位。NAND处理芯片将管理CRC计算,允许在读取时纠正位错误,并管理坏行。

在USB驱动器中,这一切都由USB接口芯片处理。在SSD中,它由SATA(或其他接口模式)芯片处理,因此CRC错误和坏点映射对用户来说都是不可见的。这意味着在NAND器件上自始至终都必须进行虚拟映射。NAND内存的可靠运行还需要损耗均衡(就是把擦除平均到每个块上),损耗均衡也由USB或SSD控制器处理,因此用户也不可见。

用途

通常,NOR闪存是需要较低容量,快速随机读取访问和更高数据可靠性的应用的理想选择,例如代码执行。
NAND闪存则非常适用于需要更高内存容量和更快写入和擦除操作的数据存储等应用。

LINUX嵌入式系统多用一个小容量的nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核。

ADC过采样

用过不少模块中有ADC采样,如陀螺仪、磁罗盘、气压计、AFE中,都有ADC过采样率的配置,这些模块配置过采样率,输出的ADC量化位数没变,ADC值静态偏移量减小(信噪比提高),所以可以理解为多次采样取平均值。使用ADC过采样能提升精度,但是由于输出ADC值为多次采样平均,并不是单次采样时刻的值,AFE采电池电压的同时采电流,电压的值不是采电流那一时刻的瞬时值,导致SOC估算不准确。使用ADC过采样会提升采样次数,增大ADC的功耗。

下面来说说书里介绍的过采样。首先是结论:过采样率每提高4倍,可以提高ADC 1bit的有效分辨率

假设ADC过采样率为4,ADC会采集4次将值相加,这其实增加了2bits的有效分辨率,这个过程还需要降低采样,或者下抽,下抽是将四次采样累计值>>1,这么做除了降低数据量外,就是可以提高分辨率。

ADC降采样(减采样/下采样/down sampling)

降采样网上的资料比较少,一般是原有的采样率比实际信号的有效最高频率要高很多,就可以对采到的信号做低通滤波,除去高频干扰后向下抽样。

采样这一块还得向香农和奈奎斯特学习,香农-奈奎斯特采样定理,当一个信号被减采样时,必须满足采样定理以避免混叠。为了满足采样定理的要求,信号在进行减采样操作前,必须通过一个具有适当截止频率的低通滤波器。这个用于避免混叠的低通滤波器,称为抗混叠滤波器。