龙空技术网

单片机入门:C语言讲解(一)

电源网论坛 373

前言:

现在姐妹们对“c语言调用三角函数”大致比较看重,朋友们都想要知道一些“c语言调用三角函数”的相关资讯。那么小编同时在网络上搜集了一些对于“c语言调用三角函数””的相关内容,希望我们能喜欢,姐妹们一起来了解一下吧!

好久没来电源网发帖,学习电子技术的过程中积累了一点心得体会,总想直抒胸臆,可身边没几个同行,只好跑到电源网写点文,贻笑于大方之家。

既然是技术心得类的帖子,肯定存在理解不透彻的地方,肯定有自己没有关注到的知识点,还望大家海涵,及时斧正,谢谢。

C语言的书籍以及视频教程看了很多,但还是觉得不够透彻,理解不够深入。希望热心的网友能够及时补充纠正。

操作系统为ubuntu18.04_x86_64,编译器采用的是gcc version 7.5.0 (ubuntu18.04_x86_64),后期的开发板验证采用的是金沙滩的51单片机开发板,编译器为keil uversion 4(windows_xp_x86_32),单片机采用的是STC89C52单片机。

程序的框架分解完成,接下来聊聊程序的存储规则。

我们通常意义上敲的代码实质是静态的或者说是未运行的程序,这样的程序只能放在ROM中,这里的ROM代指外存储器,比如硬盘,FLASH,EMMC,EEPROM等。

运行的程序通常称为进程,它通过调用各类寄存器实现某一功能,对于单片机而言,讲进程就有点扯,因为常规单片机速度很慢,一般仅仅用于实现某一特定功能,所以其进程只有一个。真正的进程概念是基于操作系统而言。操作系统中会涉及多个进程的处理,这里就不扩展了。上述对进程的认识只是我个人的粗略描述。

进程只能运行于RAM中,单片机上电复位后,固化的微指***令将ROM中的程序加载到RAM中,这时程序就开始运行了。那问题来了,大家有没有想过进程(运行的程序)在内存中是怎么分布的,或者说进程是如何在大片的RAM区中运行的?如果能考虑到这一层次的问题,恭喜你,总算对单片机有了一个相对深入的思考。

学习C语言以及单片机,很多的工程师的套路都是对照书籍或者例程敲代码,虽然当时按照例程实现了某一功能,但是脱离源码后又不知道从何下手。于是尝试着反复敲例程,重复了很多次后依然没有理解单片机为何物。至此完成了从入门到放弃的过程。

学习单片机的心态很重要,我是一个学习单片机失败了很多次的LOSER(我对电子技术没有多少兴趣,坚持至今是因为没有更好的谋生手段罢了,我比常人多了一点耐心,恒心),在此总结归纳一下我认为正确的或者说是恰当的学习单片机的方法及心态。

第一点是忘,忘掉过往已经掌握的开关电源的系统知识(电源网的大多数受众是电源工程师),对于射频工程师,PLC工程师,上位机软件工程类似。为什么有此一说呢,这是我尝试着学习单片机失败了多次后悟出来的。

我们一旦有门技术傍身后,总会习惯性地靠这门擅长的手艺谋生,慢慢就形成了依赖,专业术语称作思维舒适区。我们思想中总会有一个误区,认为过往掌握的技能有助于我们学习后面的新知识。不自觉的我们就把以前的思维模式带入到新的知识领域中。这就是最大的问题。

单片机与开关电源,变频器,射频电子,PLC,上位机软件编程等都是相对独立的技能,不要指望学会了一门手艺后,就能触类旁通其他的相关技能。这是我们开始学习单片机必须要有的思想觉悟。

不信,你可以问问身边精通单片机,或者PLC,乃至JAVA编程的同事懂不懂开关电源的设计,大概率是不懂的。会抄个反激电源的单片机工程师不算,他那点浅薄的对开关电源的认识不够看。

能够将开关电源和单片机区分开来学习,你已经成功了一大步。单片机和开关电源是两个不同的领域,虽然熟悉开关电源有助于我们理解单片机中涉及的硬件知识,但这远远不够。

学习单片机需要具有的第二个心态是不急,有耐心。市面上有一些教材号称一个礼拜掌握单片机,这样的教程针对的受众通常是有一定基础的,且资质相对较好的朋友。如果没有一定的知识储备,个人建议还是按部就班的学习。

知识是逐步缓慢积累的,快速上手的教程最大的副作用可能是营造了一种焦虑的环境,让很多初学者再学过好几遍仍找不着头绪时,开始了自我否定,最终放弃。在此我送给那些一直徘徊在单片机入门阶段的朋友一句话:“坚持就是在快要放弃时再忍耐忍耐。”

我个人定义单片机技术为一门谋生的手艺,甚至可以说是安身立命的手段,那肯定不太容易掌握,否则满大街的人都会玩,何谈养家糊口一说。

周围也有很多电子工程师精通单片机技术,那说明这门技术的学习掌握也没有难于上青天。只要努力,理顺思路,储备了足够的知识,找到适合自己的学习方法,肯定可以精通单片机技术。

学习单片机技术需要具备的第三个心态是恒心,或者说是韧性。想学好单片机必然会遇到各种各样稀奇古怪的问题,这时心态一定要足够坚韧,不能但凡遇到一点困难,一时半会解决不了就想着放弃。

在某个问题上卡住了,如果以当前的知识储备确实解决不了,那就放一放,继续往前走,千万不能自设囚笼,钻牛角尖把自己困在那里,这一点很重要。我自己属于典型的一根筋,遇到问题总想着立马解决,但身边又缺少能够及时给自己答疑解惑的朋友。在苦思冥想过一番仍不可得时,我就失去了继续往前探索学习的兴趣。等下一次再提起兴趣时,又得从头开始,周而复始,永远困在单片机入门学习的道路上。

在学习的过程中明知会遇到各种问题而依旧选择努力前行不失为勇者的表现,在遇到超出自己能力范围的问题而选择适时地放一放,可谓是智者的选择。

C语言编写的程序经调试编译后生成HEX文件烧写进单片机ROM中,单片机上电复位后,将ROM中的程序引导至RAM中(内存中),单片机便开始正式工作了。我们知道程序都是在内存中运行的,为保证程序高效有序的运行,内存通常被人为的划分为四个区域,这就引出上文提到的内存四区的概念。

内存区域的划分,我准备梳理两类,一类是C语言内存四区的划分,一类是单片机的内存区域的划分,实质上大同小异。这一章讲讲C语言的内存四区,等写到单片机的内部框架章节时,再列举单片机内存区域的相关知识点。

内存的分配方式实质是由不同的编译器针对不同的单片机架构再配合相应的编程语言决定的。图灵机模型中,内存相当于一段无限长的纸带,指示着机器的下一步动作,周而复始,永不停息。

但现实世界中,内存是有限的,而机器只要不断电,肯定会不知疲倦地占用内存进行运算处理,经过一定的机器周期,终会耗尽有限的内存。站在哲学的角度,这就是有限与无限的矛盾。

怎么办呢,是否存在一种方法或者说策略使得有限近似于无限,答案是肯定的。合理的规划内存,同时及时释放回收内存,就能使有限的内存用之不竭。这就是内存区域划分思想的来源。

C语言中的内存被分割为四个区域,分别对应着代码区(code),数据区(data),栈区(stack),堆区(heap)。不同区域的功能特性也不尽相同。首先讲讲代码区(code).

单片机上电复位后,编译生成的HEX代码经由机器内部固化的微指***令加载进内存,实质上代码被引导至内存中的代码区,即code区。该段内存的特性之一是不可以修改,专业术语为只读。其次该区域内容是可共享的(即某段可执行代码能够访问它)。

C语言代码区可共享的目的是针对频繁调用的只读数据,使用此法可有效提高程序的访问效率。代码区设置为只读的意义在于防止程序运行过程中的意外而修改了某些指***令或数据,导致不可预料的错误发生。

51单片机代码区如何配置可共享数据呢?使用51单片机特有的C语言关键字code,即可实现。C51中该类变量常用于存放一些复杂函数的固定值数据,如各类真值表,三角函数值,反三角函数值,对数值,指数值等。

为什么要这样做呢?很多的8位机,16位机内部并没有浮点运算器,遇到复杂一些的运算基本都不能胜任,而用户仅仅关注最后的结果。我们在程序中预先给出运算结果,当涉及某一功能函数计算时,直接将代码区中对应的运算结果调用显示即可。此种方法既高效,又节省了大量机器资源。这种思想在单片机技术中被称作查表执行。

51单片机中code关键字修饰的数据,其特性为只读。从单片机内部资源的角度看,此类数据存储至程序空间ROM中(C语言中则为代码区)。这样可以大大节省单片机的RAM使用量,毕竟我们的单片机RAM空间比较小,而程序空间相对大的多。

数据区,顾名思义用于存放各种特性的数据。该区域存放的数据有全局变量(Global_Variable)、静态变量(Static_Variable)、常量(Constant)。由于数据区存放此三类变量,所以又被称为全局区或静态区。至于全局变量,静态变量以及常量该如何定义,它们之间的差异在后续讲解C语言详细语法时再介绍。

数据区又可以细分为data区,bss区及常量区。其中data区里主要存放的是已经初始化的全局变量、静态变量。bss区主要存放未初始化的全局变量、静态变量。未初始化的数据在程序执行前会被编译器自动初始化为0或NULL。顾名思义常量区存放的是一些常量,如const关键字修饰的全局变量以及字符串常量等。

数据区中的数据相较于代码区中的数据,其不仅可读而且可写。可读表明该区域里的数据可被函数调用,可写表明该区域中的数据能够被改变。该区域数据的特点是只要内存不掉电,数据就能一直存活。单片机中的各功能寄存器也有此类似特性。只要配置好寄存器相关数据后,寄存器中的数据就能一直存在,除非掉电。有很多场合的应用需要保证寄存器中的数据不丢失,为防止外界电源断电,单片机系统会接入备用电池。如实时时钟,日历,低功耗模式等。

不同类型的变量在内存中的存活时间各不相同,这一特性专业术语称作变量的生命周期。其次可读数据变量在内存中能被哪些函数调用涉及到变量的调用范围,专业术语叫变量的作用域。

不管何种程序语言,均由三种基本结构组成。他们是顺序结构,选择(分支)结构,循环结构。拿到一份写好的源码,我们首先会浏览一遍。通常的做法是从上到下快速滚动阅读一遍,熟悉程序的大体框架。从总览的高度看,CPU执行程序是从头至尾依次执行的,我们可以说从全局的角度看,程序是由不同的功能语句组成的顺序结构。

类人猿之所以能够进化为人类,我的理解是它们具备不断试错不断学习的能力。在进化的道路中每一次试错都伴随着一次成长过程。在缓慢的成长过程中,其心志得到不断的进化。当积累到一定程度,量变引起质变,类人猿终于成长为今天的你我。将人类的进化过程映射到计算机技术上,虽有些扯,但多少有些相通性。

想要让计算机执行不同的功能,最终必然需要将不同的功能转化为计算机能够识别的语句。计算机之所以能够根据外界的变化执行不同的动作,恰恰是因为它具备试错能力,也即是判断能力。任何形式的程序语言都必须具备根据不同条件执行不同语句的能力,此形式被描述为程序语言的选择结构。

某种意义上说,循环结构是特殊的选择结构。程序语言根据条件判断依次往下执行时,我们说这是典型的选择结构。当程序语言根据条件判断执行若干动作后,又回至条件判断的起点,重新进入条件判断的流程,依此往复执行…… 我们可以说这就是循环结构。

从上述角度解读,我们可以认为循环结构是特殊的选择结构。当然,如果循环结构的判断执行条件仅只执行一次,我们同样可以说选择结构是特殊的循环结构。换句话说,选择结构和循环结构之间可以相互转换。

不管是代码区还是数据区中的数据自始至终都驻扎在内存中,而内存规划的初衷是为了高效利用内存,及时回收释放多余的内存,使有限的内存有足够的空间支撑程序的全过程运行,显然仅仅靠代码区及数据区远远不够,遂引入堆和栈的概念。

堆区(heap),该区域可看作是一个存储数据的大容器,其容量远远大于后面要讲解的栈区,该区域中的数据不需要像栈区那样遵循先进后出的规则。

堆区主要用于动态内存分配。堆区在内存中位于数据区中的BSS区和栈区之间。该区域一般由程序员手动分配和释放,若程序员不释放,程序结束时由操作系统回收(该话题涉及到操作系统内存回收的话题,此处一笔带过,有机会分析嵌入式linux系统时讲解)。

C语言中堆区的大小可使用malloc函数进行分配,堆区的释放采用free函数实现。程序中malloc等内存分配函数的使用次数一定要和free函数相等,并一一配对使用。否则随着程序的运行会出现内存泄漏的问题。

栈区(stack),用于存储所有的局部变量,包括用户定义的auto型局部变量,函数的实参,返回值,嵌套函数的调用(递归函数)等。栈区和其他三个区域最大的不同点在于其采用的是先进后出的内存结构。局部变量在该区域由编译器在程序运行过程中实时加载和释放。所以局部变量的生存周期很短,需要时即申请,不用即销毁释放。

auto型局部变量经编译器自动放入栈中后,其生存周期及作用域只限于定义的功能函数内有效,当一个auto型局部变量超出其作用域时,自动从栈中弹出,用完后再由操作系统自动释放。

auto型局部变量的生存周期及作用域的特点牵出一个很有意思的话题,即函数实参的调用是否影响对应变量的数值,答案是不影响(后续C语言语法章节会详细讲解)。

另外一个注意事项是不要返回局部变量的地址,因为栈区开辟的地址空间由编译器自动释放,当auto型局部变量失效后,原先对应的地址空间也随之释放,返回的局部变量地址是一个随机数,而不是之前对应的局部变量的地址。

上述堆区和栈区的概念是针对C语言而言,对于单片机我们也经常提到堆栈的概念,但C语言的堆区栈区和单片机的堆栈还是有区别的。单片机的堆栈实质是C语言中栈的描述。通常单片机的内存资源相当有限,所以一般不分配堆区,而且很少用到标准库的malloc类函数,当然上升到操作系统层面就是另一说了。

单片机的堆栈也属于内存的一部分,所以它肯定有临时存储的功能,那单片机的堆栈主要用于哪些存储场合呢?如果说代码区和数据区中的数据是用于单片机自始至终的全程数据存储,那堆栈就是用于单片机运行过程中的中间数据处理。

通俗的说,代码区和数据区中存储的数据只要单片机不掉电,其中的数据就不会被释放。而堆栈中的数据是根据程序需要实时产生,实时释放,我们通常提到的内存回收与释放实质是针对堆栈中数据的回收释放操作。

原文链接:

标签: #c语言调用三角函数