IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 关于单片机的一些容易被忽视问题 -> 正文阅读

[嵌入式]关于单片机的一些容易被忽视问题

?最近一直在单片机项目上打转,也碰到了很多稀奇古怪的问题,在这里做个记录。

01

变量

很多刚接触单片机嵌入式编程没有太多程序风格可言的程序员来说,变量的滥用以及命名的随意,导致在后期代码实际跑起来出现问题的时候,都不知道问题出在哪里,听到最多的回答就是"刚刚还是好好的,突然之间就卡死了"。

1、变量的命名

说实话每个人都有自己对变量以及函数名的命名习惯,但是个人认为名称这个东西应该是给人所见即所得的感觉,就是我看到名称就知道这个是干什么的,比如我要定义一个记录错误次数的变量errorCnt,可以使用驼峰式的书写方法,单驼峰或者双驼峰都可以。

2、变量的范围

单片机中常见的变量有全局变量、局部变量、静态局部变量、静态全局变量、易变变量。

全局变量:在整个工程文件内都有效;“在函数外定义的变量”,即从定义变量的位置到本源文件结束都有效。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值, 就能影响到其他函数中全局变量的值。

静态全局变量:只在定义它的文件内有效,效果和全局变量一样,不过就在本文件内部;

静态局部变量:只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;静态局部变量的生存期虽然为整个工程,但是其作用仍与局部变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。    

局部变量:在定义它的函数内有效,但是函数返回后失效。“在函数内定义的变量”,即在一个函数内部定义的变量,只在本函数范围内有效。

注意:全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

静态局部变量与全局变量最明显的区别就在于:全局变量在其定义后所有函数都能用,但是静态局部变量只能在一个函数里面用。

还有一个比较重要的是如果要在其他文件中引用另外文件的全局变量,需要用extern先对变量进行声明,声明的时候不要赋初值

不要初始化静态和全局变量为0(或NULL),让编译器来做

volatile变量:一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:? ?

? ? 1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

? ? 2、多任务环境下各任务间共享的标志应该加volatile;

? ? 3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义

?volatile应该解释为“直接存取原始内存地址”比较合适

3、全局变量的管理

很多时候在做状态机或者计时的时候,全局变量就成了一个简便快捷的方法,但是随之而来的就是管理问题,比如计数到一定数值后忘记归零,导致溢出。而且大量的使用全局变量,有时候也会导致堆栈的溢出,所以对于全局变量的使用最好能遵循下面的原则:

1)如果只有某.c文件用,就直接定义成static,如果只有一个函数用,那就static到函数里面去;

2)如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了;


3)如果非要赋值,可以做成开放函数接口方式来进行传参赋值,或者如果非要extern,可以控制extern的的.h文件,而不是放到一个common.h当中;

4)当一个模块中用到的全局变量超过3个的时候,用结构体封装起来吧

5)在函数里面开个静态的全局变量,全局数组,是不占用栈空间的。只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。若是在keil C51,因为是静态编译,栈爆掉了会报警

6)如果要在中断中使用全局变量,加上volatile,中断函数中改变全局变量的值,但你要确保该值没有在主程序中被占用。

02


SRAM、ROM、EEPROM、FLASH

1、SRAM在单片机中相当于电脑中的内存,单片机在运行程序的时候,需要将存储在flash中的指令、数据都读取到内存中来进行操作,另外堆栈也是分配在SRAM中的。堆是从内存地址自下而上,栈是自上而下

程序内存可以分为几个区,栈区(stack),堆区(Heap),全局区(static),文字常亮区,程序代码区。

程序编译之后,全局变量,静态变量已经分配好内存空间

在函数运行时,程序需要为局部变量分配栈空间,

当中断来时,也需要将函数指针入栈,保护现场,以便于中断处理完之后再回到之前执行的函数。

一般说的堆栈指的是栈,栈是系统自动分配空间,比如定义一个char a,系统会自动在栈上分配一个空间,用完后会自动回收

堆是由用户自己申请的,比如malloc(a),用完后由用户自己释放,否则会造成内存泄漏

2、ROM和EEPROM

ROM最初不能编程,出厂什么内容就是什么内容,不灵活,后来出现了PROM,可以自己写入一次,要是写错了,只能换一片,随着不断的改进,出现了可多次擦鞋的EPROM,每次擦除需要把芯片拿到紫外线上照半小时,还是不方便,后来出现了EEPROM,可以随意修改ROM中的内容

EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。是相对于紫外擦除的rom来讲的。但是今天已经存在多种EEPROM的变种,变成了一类存储器的统称。

3、Flash和EEPROM

Flash属于广义的EEPROM,因为它也是电擦除的ROM。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它Flash。

既然两者差不多,为什么单片机中还要既有Flash又有EEPROM呢?

通常,单片机里的Flash都用于存放运行代码,在运行过程中不能改;EEPROM是用来保存用户数据,运行过程中可以改变,比如一个时钟的闹铃时间初始化设定为12:00,后来在运行中改为6:00,这是保存在EEPROM里,不怕掉电,就算重新上电也不需要重新调整到6:00。

但最大区别是其实是:FLASH按扇区操作,EEPROM则按字节操作,二者寻址方法不同,存储单元的结构也不同,

1)FLASH的电路结构较简单,同样容量占芯片面积较小,成本自然比EEPROM低,因而适合用作程序存储器,EEPROM则更多的用作非易失的数据存储器。当然用FLASH做数据存储器也行,但操作比EEPROM麻烦的多,所以更“人性化”的MCU设计会集成FLASH和EEPROM两种非易失性存储器,而廉价型设计往往只有 FLASH,早期可电擦写型MCU则都是EEPROM结构,现在已基本上停产了。

2)在芯片的内电路中,FLASH和EEPROM不仅电路不同,地址空间也不同,操作方法和指令自然也不同,不论冯诺伊曼结构还是哈佛结构都是这样。技术上,程序存储器和非易失数据存储器都可以只用FALSH结构或EEPROM结构,甚至可以用“变通”的技术手段在程序存储区模拟“数据存储区”,但就算如此,概念上二者依然不同,这是基本常识问题。

3)EEPROM:电可擦除可编程只读存储器,Flash的操作特性完全符合EEPROM的定义,属EEPROM无疑,首款Flash推出时其数据手册上也清楚的标明是EEPROM,现在的多数Flash手册上也是这么标明的,二者的关系是“白马”和“马”。至于为什么业界要区分二者,主要的原因是 Flash EEPROM的操作方法和传统EEPROM截然不同,次要的原因是为了语言的简练,非正式文件和口语中Flash EEPROM就简称为Flash,这里要强调的是白马的“白”属性而非其“马”属性以区别Flash和传统EEPROM。

4)Flash的特点是结构简单,同样工艺和同样晶元面积下可以得到更高容量且大数据量下的操作速度更快,但缺点是操作过程麻烦,特别是在小数据量反复重写时,所以在MCU中Flash结构适于不需频繁改写的程序存储器。

5)很多应用中,需要频繁的改写某些小量数据且需掉电非易失,传统结构的EEPROM在此非常适合,所以很多MCU内部设计了两种EEPROM结构,FLASH的和传统的以期获得成本和功能的均衡,这极大的方便了使用者。随着ISP、IAP的流行,特别是在程序存储地址空间和数据存储地址空间重叠的MCU系中,现在越来越多的MCU生产商用支持IAP的程序存储器来模拟EEPROM对应的数据存储器,这是低成本下实现非易失数据存储器的一种变通方法。为在商业宣传上取得和双EEPROM工艺的“等效”性,不少采用Flash程序存储器“模拟”(注意,技术概念上并非真正的模拟)EEPROM数据存储器的厂家纷纷宣称其产品是带EEPROM的,严格说,这是非常不严谨的,但商人有商人的目的和方法,用Flash“模拟”EEPROM可以获取更大商业利益,所以在事实上,技术概念混淆的始作俑者正是他们。

4)在51系列中data,idata,xdata,pdata的区别:

  data: 固定指前面0x00-0x7f的128个RAM,可以用acc直接读写的,速度最快,生成的代码也最小。

  idata: 固定指前面0x00-0xff的256个RAM,其中前128和data的128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式 访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:c中idata做指针式的访问效果很好)

  xdata: 外部扩展RAM,一般指外部0x0000-0xffff空间,用DPTR访问。  

  pdata: 外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,用movx ACC,@Rx读写。这个比较特殊,而且C51好像有对此BUG, 建议少用。但也有他的优点,具体用法属于中级问题,这里不提。

5)在使用IAP技术对FLASH进行操作来实现EEPROM的过程中需要注意的是,FLASH的操作只能按扇区执行,写过的字节不能重复写,所以在修改扇区中某个数据时,需要先将扇区的数据导入到RAM中,然后再擦除扇区,然后将导出的数据一并写入。

03


程序跑飞

1. 意外中断。是否打开了某个中断,但是没有响应和清除中端标志,导致程序一直进入中断,造成死机假象。

2. 中断变量处理不妥。若定义某些会在中断中修改的全局变量,

这时要注意两个问题:首先为了防止编译器优化中断变量,要在这些变量定义时前加volatile,

其次在主循环中读取中断变量前应该首先关闭全局中断,防止读到一半被中断给修改了,读完之后再打开全局中断;否则出现造成数据乱套。

3. 地址溢出,常见错误为指针操作错误。我要着重说的是数组下标使用循环函数中循环变量,

如果循环变量没控制好则会出现数组下标越界,意外修改系统的寄存器造成死机,这种情况下如果死机说明运气好,否则后面不知道发生什么头疼的事。

4. 无条件的死循环;比如使用while(x);等待电平变化,正常情况下x都会变成0,就怕万一,因此最好加上时间限制。

5. 看门狗没有关闭。有的单片机即使没使用看门狗开机时也有可能意外自动开启了最小周期的看门狗,导致软件不断复位,造成死机,这个要看芯片手册,最好在程序复位后首先应该显式清除看门狗再关闭看门狗。

6. 堆栈溢出。最难查找的问题,对于容量小的单片机,尽量减少函数调用层级,减少局部变量,从而减少压栈的时候所需的空间。

当你把以上几条都试过不能解决问题,试一试把你的被调用少函数直接内置到调用的地方并且把占用RAM大的局部变量改成全局变量,试一试说不定就可以了。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-10 11:01:09  更:2021-09-10 11:03:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:48:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码