0%

起因

这几天在做云途单片机的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)

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

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

GPS模块

商品链接:Beitian北天高精度GPS模块NEO-M8M陶瓷天线GPS北斗GLONASS三模GNSS授时BN-357

输出协议:NMEA-0183协议

NMEA-0183协议

<CR> 回车,(ASCII 13, \r)
<LF> 换行,(ASCII 10, \n)
hh 报文$到*之间数据的异或校验

RMC

Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐定位信息

报文:$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh<CR><LF>

<1> UTC 时间,hhmmss.sss(时分秒)格式

<2> 定位状态,A=有效定位,V=无效定位

<3> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)

<4> 纬度半球N(北半球)或S(南半球)

<5> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)

<6> 经度半球E(东经)或W(西经)

<7> 地面速率(000.0~999.9 节,前面的0 也将被传输)

<8> 地面航向(000.0~359.9 度,以真北为参考基准,前面的0 也将被传输)

<9> UTC 日期,ddmmyy(日月年)格式

<10> 磁偏角(000.0~180.0 度,前面的0 也将被传输)

<11> 磁偏角方向,E(东)或W(西)

<12> 模式指示(仅NMEA0183 3.00 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

GGA

Global Positioning System Fix Data(GGA)GPS 定位信息

报文:$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh<CR><LF>

<1> UTC 时间,hhmmss.sss(时分秒)格式

<2> 纬度ddmm.mmmm(度分)格式(前面的0 也将被传输)

<3> 纬度半球N(北半球)或S(南半球)

<4> 经度dddmm.mmmm(度分)格式(前面的0 也将被传输)

<5> 经度半球E(东经)或W(西经)

<6> GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算

<7> 正在使用解算位置的卫星数量(00~12)(前面的0 也将被传输)

<8> HDOP 水平精度因子(0.5~99.9)

<9> 海拔高度(-9999.9~99999.9)

<10> 地球椭球面相对大地水准面的高度

<11> 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空

<12> 差分站ID 号0000~1023(前面的0 也将被传输,如果不是差分定位将为空)

GSA

GPS DOP and Active Satellites(GSA)当前卫星信息

报文:$GPGSA,<1>,<2>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<3>,<4>,<5>,<6>*hh<CR><LF>

<1> 模式,M=手动,A=自动

<2> 定位类型,1=没有定位,2=2D 定位,3=3D 定位

<3> PRN 码(伪随机噪声码),正在用于解算位置的卫星号(01~32,前面的0 也将被传输)

<4> PDOP 位置精度因子(0.5~99.9)

<5> HDOP 水平精度因子(0.5~99.9)

<6> VDOP 垂直精度因子(0.5~99.9)

GSV

报文:$GPGSV,<1>,<2>,<3>,<4>,<5>,<6>,<7>,…<4>,<5>,<6>,<7>*hh<CR><LF>

<1> GSV 语句的总数

<2> 本句GSV 的编号

<3> 可见卫星的总数(00~12,前面的0 也将被传输)

<4> PRN 码(伪随机噪声码)(01~32,前面的0 也将被传输)

<5> 卫星仰角(00~90 度,前面的0 也将被传输)

<6> 卫星方位角(000~359 度,前面的0 也将被传输)

<7> 信噪比(00~99dB,没有跟踪到卫星时为空,前面的0 也将被传输)
注:<4>,<5>,<6>,<7>信息将按照每颗卫星进行循环显示,每条GSV 语句最多可以显示4 颗卫星的信息。其他卫星信息将在下一序列的NMEA0183 语句中输出。

VTG

Track Made Good and Ground Speed(VTG)地面速度信息

报文:$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh<CR><LF>

<1> 以真北为参考基准的地面航向(000~359 度,前面的0 也将被传输)

<2> 以磁北为参考基准的地面航向(000~359 度,前面的0 也将被传输)

<3> 地面速率(000.0~999.9 节,前面的0 也将被传输)

<4> 地面速率(0000.0~1851.8 公里/小时,前面的0 也将被传输)

<5> 模式指示(仅NMEA0183 3.00 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

GLL

Geographic Position(GLL)定位地理信息

报文:$GPGLL,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh<CR><LF>

<1> 纬度ddmm.mmmm(度分)格式(前面的0 也将被传输)

<2> 纬度半球N(北半球)或S(南半球)

<3> 经度dddmm.mmmm(度分)格式(前面的0 也将被传输)

<4> 经度半球E(东经)或W(西经)

<5> UTC 时间,hhmmss(时分秒)格式

<6> 定位状态,A=有效定位,V=无效定位

<7> 模式指示(仅NMEA0183 3.00 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

str转int/float函数

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int hexStr2Int(char *pStr)
{
int num = 0;
while(1)
{
if(*pStr >= '0' && *pStr <= '9')
{
num *= 16;
num += *pStr - '0';
pStr++;
}
else if(*pStr >= 'a' && *pStr <= 'f')
{
num *= 16;
num += *pStr - 'a' + 10;
pStr++;
}
else if(*pStr >= 'A' && *pStr <= 'F')
{
num *= 16;
num += *pStr - 'A' + 10;
pStr++;
}
else
{
break;
}
}
return num;
}

int decStr2Int(char *pStr)
{
int num = 0;
char isMinus = 0;
if(*pStr == '-')
{
isMinus = 1;
pStr++;
}
while(*pStr >= '0' && *pStr <= '9')
{
num *= 10;
num += *pStr - '0';
pStr++;
}
if(isMinus)
num = -num;
return num;
}

float str2Float(char *pStr)
{
int intNum = 0;
float num = 0;
char isMinus = 0;
intNum = decStr2Int(pStr);
if(*pStr == '-')
{
isMinus = 1;
pStr++;
}
while(*pStr >= '0' && *pStr <= '9')
{
pStr++;
}
if(*pStr == '.')
{
pStr++;
if(*pStr >= '0' && *pStr <= '9')
{
num = decStr2Int(pStr);
}
while(*pStr >= '0' && *pStr <= '9')
{
num /= 10.f;
pStr++;
}
}
if(isMinus)
num = intNum - num;
else
num = intNum + num;
return num;
}

查找char和异或校验函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
inline uint16_t findChar(uint8_t findValue, uint8_t *pStr, uint16_t size)
{
uint16_t i = 0;
for(i = 0; i < size; i++, pStr++)
{
if(*pStr == findValue)
return i;
}
return 0xffff;
}

inline uint8_t calcXorCheck(uint8_t *pStart, uint8_t *pEnd)
{
uint8_t ret = 0;
while(pStart < pEnd)
{
ret ^= *pStart;
pStart++;
}
return ret;
}

utcTime增加一秒函数

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
uint32_t gps_addSecondUtcTime(uint32_t now)
{
uint8_t hh, mm, ss;
ss = now%100;
if(ss < 59)
{
now++;
}
else
{
ss = 0;
mm = (now/100)%100;
hh = (now/10000)%100;
if(mm < 59)
{
mm++;
}
else
{
mm = 0;
if(hh < 23)
{
hh++;
}
else
{
hh = 0;
}
}
now = hh*10000 + mm*100 + ss;
}
return now;
}

解析函数

Github:https://github.com/hao0527/gps_data_parse

Windows串口接收代码

参考

实现

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
#include <Windows.h>

void* serial_openSerial(void* lpFileName, unsigned int baudRate, unsigned int dwInQueue, unsigned int dwOutQueue) {
HANDLE hComm = NULL;
DCB dcb;
COMMTIMEOUTS commTimeOuts;
hComm = CreateFile(lpFileName, //串口名称
GENERIC_READ | GENERIC_WRITE, //允许读和写
0, //独占方式
NULL, // 无安全属性,不可被子程序继承
OPEN_EXISTING, //创建文件的性质,打开而不是创建
0, // Non Overlapped I/O
NULL); // Null for Comm Devices
SetupComm(hComm, dwInQueue, dwOutQueue);
GetCommState(hComm, &dcb);
dcb.BaudRate = baudRate;
SetCommState(hComm, &dcb);
commTimeOuts.ReadIntervalTimeout = 0;
commTimeOuts.ReadTotalTimeoutMultiplier = 0;
commTimeOuts.ReadTotalTimeoutConstant = 0;
commTimeOuts.WriteTotalTimeoutMultiplier = 0;
commTimeOuts.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(hComm, &commTimeOuts); // 配置Timeout参数(ms),0表示不Timeout
return hComm;
}

int serial_readLen(void* hComm, unsigned char* pBuff, unsigned int len, unsigned int* pLenRead) {
return ReadFile(hComm, pBuff, len, pLenRead, NULL);
}

int serial_closeSerial(void* hComm) {
return CloseHandle(hComm);
}

int serial_purgeSerial(void* hComm) {
return PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR);
}

注意

Windows.h的api是正确返回非0

不同系统编译各数据类型所占内存空间大小

区别

类型 win32 win64 linux32 linux64
char 1 1 1 1
short 2 2 2 2
int 4 4 4 4
long 4 4 4 8
long long 8 8 8 8
float 4 4 4 4
double 8 8 8 8
void* 4 8 4 8

总结

  1. 指针所占空间看系统是16位、32位还是64位。
  2. win64把long编成4字节,linux64把long编成8字节。
  3. 在32位系统中,int和long都是4字节,取值范围相同。

使用DMA发送串口数据问题

  1. CubeMX生成的代码初始化DMA和UART顺序问题,应该先初始化DMA再UART,可以在CubeMX中调整生成初始化代码的顺序。2207031-CubeMX-1.jpg
  2. CubeMX生成的代码使用HAL_UART_Transmit_DMA()后需要手动将串口状态配置成空闲状态,可以在DMA传输完成中断中加(&huart1)->gState = HAL_UART_STATE_READY;

串口接收溢出后接收不到数据

  1. 产生问题的原因:超出接收size、在没接收的时候接收超过1个字节的数据。
  2. 解决方法参考:stm32cube,HAL库 HAL_UART_Receive_IT中断接收多个字符,串口溢出卡死问题
  3. 关闭检测Overrun功能,或者使用错误处理回调函数。

串口接收一帧不定长数据

  1. 可使用tm32f7xx_hal_uart_ex.h中的HAL_UARTEx_ReceiveToIdle()函数。

HAL库操作Flash

  1. 参考:基于STM32F407 HAL库的Flash编程操作 和 STM32F10xxx闪存编程参考手册
  2. 在每次擦除或编程前先要解锁Flash,在HAL库中,只需要调用stm32f1xx_hal_flash.h中的HAL_FLASH_Unlock()函数。
  3. 擦除时最小要以页为单位,传入的地址需要注意是否是页的起始地址。
  4. 编程时要注意4字节对齐,不同单片机可能不同。

起因

不喜欢用现成的账本app记账,喜欢在手机记事本里记账,没次统计花费总额都需要按计算器,比较麻烦也不确定会不会按错,所以用Python写个脚本算算总共花费多少,额外也可以统计些自己想知道的数据。

账本格式

1
2
3
4
7.8 两餐-29 电费-57 开箱-30 充气宝-160
7.9 一餐-10 充话费-54
7.10 两餐-33 鼠标脚垫-11 早餐包-24
7.11 两餐-22 出行-7 理发-13

脚本代码

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
import matplotlib.pyplot as plt

'''
costStruct = [ {'date': '7.1', 'item': ['两餐'], 'consume': [33]},
{'date': '7.2', 'item': ['两餐', '遮阳布', '出行'], 'consume': [25, 6, 13]},
...
]
'''
costStruct = []
with open(file='cost.txt', mode='r', encoding='utf-8') as fp:
costStrList = fp.readlines()

# 删除空白行
delNum = 0
for i in range(len(costStrList)):
j = i - delNum
costStrList[j] = costStrList[j].strip('\n')
if len(costStrList[j].replace(' ', '')) == 0:
delNum = delNum + 1
del(costStrList[j])
# print(costStrList)

for i in range(len(costStrList)):
costDic = {'date': '', 'item': [], 'consume': []}
strList = costStrList[i].split(' ')
costDic['date'] = strList[0]
for j in range(1, len(strList)):
cost = strList[j].split('-')
costDic['item'].append(cost[0])
costDic['consume'].append(int(cost[1]))
costStruct.append(costDic)
# print(costStruct)

# 计算总共花费
dayCost = []
dateStr = []
for i in range(len(costStruct)):
dayCost.append(sum(costStruct[i]['consume']))
dateStr.append(costStruct[i]['date'])
print(costStruct[i]['date'] + ' cost ¥' + str(dayCost[i]))
print('total cost ¥' + str(sum(dayCost)))

# 绘图
plt.figure(figsize=(3+0.25*len(dayCost), 8), dpi=100) # 自适应长度
plt.bar(dateStr, dayCost)
plt.xticks(rotation=45)
plt.title('total cost ' + str(sum(dayCost)) + ' yuan')
plt.xlabel('date')
plt.ylabel('consume')
plt.show()

Github

代码存放在https://github.com/hao0527/costSummary,以后有新的统计分析需求,会直接在我的Github更新。

RC振荡器

在振荡电路中的频率选择部分可以只用电阻和电容构成,这种只用电阻和电容构成的振荡器称为RC振荡器。RC振荡器需要起振电路,常用的正弦波荡电路有文氏桥振荡电路,要起振所以电路是正反馈,RC构成选频网络,两个二极管和R3构成稳幅电路。

2207010-RC与晶体振荡器-1.jpg

RC振荡器容易封装到芯片中,MCU内部的时钟一般就是RC振荡器。成本低、功耗小、电路板上无需外部晶振,这些都是RC振荡器的优点。

缺点:MCU的内部振荡电路对外界干扰很敏感,非常容易受到外界环境温度的影响。同时精度也低,下图是用F767内部和外部振荡器生成1Hz方波的区别。

2207010-RC与晶体振荡器-2.jpg

晶体振荡器

只要在晶体板级上施加交变电压,就会是晶片产生机械变形振动,此现象即所谓逆压电效应。当外加电压频率等于晶体谐振器的固有频率时,就会发生压电谐振,从而导致机械变形的振幅突然增大。一般而言,晶振的振荡频率比较稳定。但是价格稍微高点,还有用晶体振荡器一般还要接两个15-33pF起振电容。

有源晶振(Oscillator,晶振)只需要供电自身就能起振,无源晶振(Crystal,晶体)最高精度为5ppm,而有源晶振的精度则可以达到0.1ppm。有源晶振的信号电平是固定,所以需要选择好合适输出电平,灵活性较差。无源晶振单片机可以配置振荡输出电压。

有源晶振
无源晶振

STM32CubeMX中的时钟配置

STM32中的时钟配置

BYPASS Clock Source:使用有源晶振的话,则只需要给它加上电源,即可输出时钟到MCU的时钟输入端,绕过MCU的OSC模块,时钟直接供MCU使用。

Crystal/Ceramic Resonator:使用晶体的话,除了外部需要加上谐振电容(有些会加上MΩ的反馈电阻)之外,还需要MCU内部的OSC振荡电路辅助才能正常产生所需时钟。

好久不见,甚是想念

一个多月没写博客了,毕业后学习时间少了,白天忙公司的项目,偶尔晚上有空看看自己想学的资料,自己还在做个地质分析仪的项目,每周日会花一天的时间做。为自己加油,2022年我还要完成这块FPGA的学习,感谢那位支持我学这块开发板的人。

安装Vivado

  1. 下载vivado安装包,资料链接B盘:https://pan.baidu.com/s/1eM7Sx-RmeYFE1ht_RPqxhw 提取码:a8vu
  2. 解压安装包到无中文路径的目录下,否则会出现安装包无法打开的情况。打开安装包,我在安装选件的页面取消了K系列、V系列和Soc Zynq的选件,安装空间要70GB左右,因此我还买了个1T的固态。
  3. 激活只需要网上下载对应版本的激活licences,在激活页面load a licences即可。

软件操作

  1. Tools -> Settings -> Text Editor中选择编辑器,我选择的是notepad++,需要将编辑器路径加到系统环境变量。
  2. 创建PLL IP核:220708-XilinxFPGA点灯-2.jpg
  3. 功能仿真,RTL分析,综合,约束输入,设计实现都在左侧的Flow Navigator中。

流水灯

  1. New Project,芯片选择xc7a35tfgg484-2。220708-XilinxFPGA点灯-1.jpg
  2. Add Sources -> Create File,创建led_top.v文件。
  3. Vivado中打开文件会调用Notepad++编辑器,编写流水灯代码:220708-XilinxFPGA点灯-5.jpg
  4. 再功能仿真(可选),再综合、约束输入。
  5. 最后生成bit流下载到开发板:220708-XilinxFPGA点灯-4.jpg
  6. Xilinx的集成开发环境要比Altera的好用不少,就是编译速度较慢。

交叉编译简介

交叉编译,是一个和本地编译相对应的概念,交叉编译通俗地讲就是一种平台上编译出的程序能够运行在不同体系结构的平台上,比如在PC平台(X86 CPU)上编译出能运行在ARM CPU的程序。

使用交叉编译的原因

主要原因是:嵌入式系统中的资源太少。具体的解释就是:所要运行的目标环境中,各种资源,都相对有限,所以很难进行直接的本地编译。嵌入式开发板的CPU、RAM、Falsh等硬件资源相对比较紧张,在已经运行了嵌入式Linux的前提下,没法方便的进行本地编译。因为编译,开发,都需要相对比较多的CPU,内存,硬盘等资源,而嵌入式开发上的资源,只够嵌入式(Linux)系统运行的,没太多剩余的资源,供你本地编译。

交叉编译工具链组成

常用交叉编译工具有交叉编译器、交叉连接器、交叉解释器还有交叉ELF文件工具、交叉反汇编器等工具。交叉编译工具链主要由binutils、gcc和glibc三个部分组成。有时出于减小 libc 库大小的考虑,也可以用别的 c 库来代替 glibc,例如 uClibc、dietlibc 和 newlib。

编译器能将我们编写的语言转成计算机可以识别的机器语言,解释器能够执行用其他计算机语言编写的程序的系统软件,它是一种翻译程序,转换一行,运行一行,再转换一行,再运行一行。解释性语言:Python,JavaScript,编译性语言:Java,c,c++。

交叉工具链命名规则

交叉编译工具链的命名规则为:arch - vendor - os - (gnu)eabi

arch – 体系架构,如ARM,MIPS,表示该编译器用于编译哪个目标平台的程序
vendor – 工具链提供商,通常是把vendor写成体系架构的值,比如cortex_a8
os – 运行编译产生的程序的目标操作系统,一般用linux表示有操作系统,none表示裸系统,uboot编译无os
eabi – 嵌入式应用二进制接口(Embedded Application Binary Interface),abi是计算机上的

编译工具使用(持续更新)

交叉编译工具使用方法与本地编译工具链基本一样,只是命名不同。

gcc

Linux系统下的GCC编译器实际上是GNU编译工具链中的一款软件,可以用它来调用其他不同的工具进行诸如预处理、编译、汇编和链接这样的工作。gcc编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤:

220522-交叉编译工具链-1.jpg

ld

ld是GNU操作系统上的连接器,把二进制文件连接成可执行文件。ELF文件可用于程序的链接,重定位目标文件。用于链接的ELF文件格式:

220522-交叉编译工具链-2.jpg

从编译和链接角度看ELF文件ELF头,每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如:版本信息、入口信息、偏移信息等,程序执行也必须依靠其提供的信息。

段头表,存放的是所有不同段将在内存中的位置。代码段.text section,存放已编译程序的机器代码,一般是只读的。只读数据段.rodata section,此段的数据不可修改,存放常量。数据段.data section,存放已初始化的全局变量。.bss section,未初始化全局变量,仅是占位符,不占据任何实际磁盘空间,目标文件格式区分初始化和非初始化是为了空间效率。

符号表.symtab section,它存放在程序中定义和引用的函数和全局变量的信息。.text节的重定位信息.rel.txt section,用于重新修改代码段的指令中的地址信息。.data节的重定位信息.rel.data section,用于对被模块使用或定义的全局变量进行重定位的信息。调试用的符号表.debug section。.strtab section,包含symtab和debug节中符号及节名。.line section,存储调试的行号信息,描述源代码和机器码之间的对应关系。

ELF(Executable and Linkable Format)的完整描述,可以参考这个文档 - 这里

size

220522-交叉编译工具链-3.jpg

用于显示二进制文件各节的大小。

text段最终是存放在FLASH存储器中的,text段不仅包含函数,还有常量。

data段是用于初始化数据(全局/外部),既有初始化值的数据。

bss段包含着所有未初始化(或初始化值为0)的数据(全局/外部)。

dec(decimal的缩写,即十进制数)是text,data和bss的算术和。

objcopy

220522-交叉编译工具链-4.jpg

把一种目标文件中的内容复制到另一种类型的目标文件中。

objcopy -O ihex xxxxxx.elf xxxxxx.hex 将编译生成的elf文件转换为hex格式的文件。

objcopy -O srec xxxxxx.elf xxxxxx.srec 将编译生成的elf文件转换为srec格式的文件。