jianhong_wu 发表于 2016-1-17 10:16:07

从单片机基础到程序框架(连载)

本帖最后由 jianhong_wu 于 2020-5-22 13:37 编辑

前言:



       该教程免费授权给所有的出版社和做单片机学习板的厂家和各大培训机构以及全国各大院校,我本人不从中赢利也不收取任何版权费用,我本人也不卖书也不卖学习板也不搞线下培训。该教程的版权无偿捐给全社会。


第一节:我的价值观。

第二节:初学者的疑惑。

第三节:单片机最重要的一个特性。

第四节:平台软件和编译器软件的简介。

第五节:用keil2软件关闭,新建,打开一个工程的操作流程。

第六节:把.c源代码编译成.hex机器码的操作流程。

第七节:本节预留。

第八节:把.hex机器码程序烧录到单片机的操作流程。

第九节:本节预留。

第十节:程序从哪里开始,要到哪里去?

第十一节:一个在单片机上练习C语言的模板程序。

第十二节:变量的定义和赋值。

第十三节:赋值语句的覆盖性。

第十四节:二进制与字节单位,以及常用三种变量的取值范围。

第十五节:二进制与十六进制。

第十六节:十进制与十六进制。

第十七节:加法运算的5种常用格式。

第十八节:连加、自加、自加简写、自加1。

第十九节:加法运算的溢出。

第二十节:隐藏中间变量为何物?

第二十一节:减法运算的5种常见格式。

第二十二节:连减、自减、自减简写、自减1。

第二十三节:减法溢出与假想借位。

第二十四节:借用unsigned long类型的中间变量可以减少溢出现象。

第二十五节:乘法运算中的5种常用组合。

第二十六节:连乘、自乘、自乘简写,溢出。

第二十七节:整除求商。

第二十八节:整除求余。

第二十九节:“先余后商”和“先商后余”提取数据某位,哪家强?

第三十节:逻辑运算符的“与”运算。

第三十一节:逻辑运算符的“或”运算。

第三十二节:逻辑运算符的“异或”运算。

第三十三节:逻辑运算符的“按位取反”和“非”运算。

第三十四节:移位运算的左移。

第三十五节:移位运算的右移。

第三十六节:括号的强制功能---改变运算优先级。

第三十七节:单字节变量赋值给多字节变量的疑惑。

第三十八节:第二种解决“运算过程中意外溢出”的便捷方法。

第三十九节:if判断语句以及常量变量的真假判断。

第四十节:关系符的等于“==”和不等于“!=”。

第四十一节:关系符的大于“>”和大于等于“>=”。

第四十二节:关系符的小于“<”和小于等于“<=”。

第四十三节:关系符中的关系符:与“&&”,或“||”。

第四十四节:小括号改变判断优先级。

第四十五节: 组合判断if...else if...else。

第四十六节: 一维数组。

第四十七节: 二维数组。

第四十八节: while循环语句。

第四十九节: 循环语句do while和for。

第五十节: 循环体内的continue和break语句。

第五十一节: for和while的循环嵌套。

第五十二节: 支撑程序框架的switch语句。

第五十三节: 使用函数的三要素和执行顺序。

第五十四节: 从全局变量和局部变量中感悟“栈”为何物。

第五十五节: 函数的作用和四种常见书写类型。

第五十六节: return在函数中的作用以及四个容易被忽略的功能。

第五十七节: static的重要作用。

第五十八节: const(或code)在定义数据时的作用。

第五十九节: 全局“一键替换”功能的#define。

第六十节: 指针在变量(或常量)中的基础知识。

第六十一节: 指针的中转站作用,地址自加法,地址偏移法。

第六十二节: 指针,大小端,化整为零,化零为整。

第六十三节: 指针“化整为零”和“化零为整”的“灵活”应用。

第六十四节: 指针让函数具备了多个相当于return的输出口。

第六十五节: 指针作为数组在函数中的入口作用。

第六十六节: 指针作为数组在函数中的出口作用。

第六十七节: 指针作为数组在函数中既“入口”又“出口”的作用。

第六十八节: 为函数接口指针“定向”的const关键词。

第六十九节: 宏函数sizeof()。

第七十节: “万能数组”的结构体。

第七十一节: 结构体的内存和赋值。

第七十二节: 结构体的指针。

第七十三节: 结构体数据的传输存储和还原。

第七十四节: 结构体指针在函数接口处的频繁应用。

第七十五节: 指针的名义(例:一维指针操作二维数组)。

第七十六节: 二维数组的指针。

第七十七节: 指针唯一的“单向”输出通道return。

第七十八节: typedef和#define和enum。

第七十九节: 各种变量常量的命名规范。

第八十节: 单片机IO口驱动LED。

第八十一节: 时间和速度的起源(指令周期和晶振频率)。

第八十二节: Delay阻塞延时控制LED闪烁。

第八十三节: 累计主循环的“非阻塞”延时控制LED闪烁。

第八十四节: 中断与中断函数。

第八十五节: 定时中断的寄存器配置。

第八十六节: 定时中断的“非阻塞”延时控制LED闪烁。

第八十七节: 一个定时中断产生N个软件定时器。

第八十八节: 两大核心框架理论(四区一线,switch外加定时中断)。

第八十九节: 跑马灯的三种境界。

第九十节: 多任务并行处理两路跑马灯。

第九十一节: 蜂鸣器的“非阻塞”驱动。

第九十二节: 独立按键的四大要素(自锁,消抖,非阻塞,清零式滤波)。

第九十三节: 独立按键鼠标式的单击与双击。

第九十四节: 两个独立按键构成的组合按键。

第九十五节: 两个独立按键的“电脑键盘式”组合按键。

第九十六节: 独立按键“一键两用”的短按与长按。

第九十七节: 独立按键按住不松手的连续均匀触发。

第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。

第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。

第一百节: “行列扫描式”矩阵按键的单个触发(优化版)。

第一百零一节: 矩阵按键鼠标式的单击与双击。

第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发。

第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发。

第一百零四节: 矩阵按键“一键两用”的短按与长按。

第一百零五节: 矩阵按键按住不松手的连续均匀触发。

第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。

第一百零七节: 开关感应器的识别与软件滤波。

第一百零八节: 按键控制跑马灯的启动和暂停和停止。

第一百零九节: 按键控制跑马灯的方向。

第一百一十节: 按键控制跑马灯的速度。

第一百一十一节: 工业自动化设备的开关信号的运动控制。

第一百一十二节: 数码管显示的基础知识。

第一百一十三节: 动态扫描的数码管显示数字。

第一百一十四节: 动态扫描的数码管显示小数点。

第一百一十五节: 按键控制数码管的秒表。

第一百一十六节: 按键控制数码管的倒计时。

第一百一十七节: 按键切换数码管窗口来设置参数。

第一百一十八节: 按键让某位数码管闪烁跳动来设置参数。

第一百一十九节: 一个完整的人机界面的程序框架的脉络。

第一百二十节: 按键切换窗口切换局部来设置参数。

第一百二十一节: 可调参数的数码管倒计时。

第一百二十二节: 利用定时中断做的“时分秒”数显时钟。

第一百二十三节: 一种能省去一个lock自锁变量的按键驱动程序。

第一百二十四节: 数显仪表盘显示“速度、方向、计数器”的跑马灯。

第一百二十五节: “双线”的肢体接触通信。

第一百二十六节: “单线”的肢体接触通信。

第一百二十七节: 单片机串口接收数据的机制。

第一百二十八节: 接收“固定协议”的串口程序框架。

第一百二十九节: 接收带“动态密匙”与“累加和”校验数据的串口程序框架。

第一百三十节: 接收带“动态密匙”与“异或”校验数据的串口程序框架。

第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架。

第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架。

第一百三十三节:常用的三种串口发送函数。

第一百三十四节:“应用层半双工”双机串口通讯的程序框架。

waphaoyun 发表于 2016-1-17 10:40:44

我抢个沙发鸿哥不会生气吧?

jianhong_wu 发表于 2016-1-18 11:08:56

本帖最后由 jianhong_wu 于 2016-8-1 14:52 编辑

第一节:我的价值观。
      我2006年毕业,2009年就出来做自由职业者在深圳以接单片机项目谋生,到现在我有自己的机器人技术有限公司,做数控机器人系统,运动卡,三轴五轴联动之类的产品。目前公司的ARM单片机编程,DSP编程,FPGA编程,电路板设计,上位机软件都是由我带领的研发团队在做。我只专心做技术,而市场,生产,行政,资金,财务,采购都不用我分心去管,有我另外的合伙人雷总去打理,所以我常感恩能过上研发创作的日子,是因为有雷总的关照。      光有经济保障还是不够的,人最重要的是要找到自己的归宿自己的位置。我最爱看老子,庄子,孔子,王阳明的圣人书,王阳明说人人皆可成为圣人,所以我一直在追求圣人之道,我渴望成为圣人,圣人之道有真三不朽之说,立功,立言,立德。在立功层面,我这一辈子的定位就是做技术,我想做超级宇宙技术大牛,特别牛的那种牛,然后以我的技术协助雷总把我们的机器人公司做大做强。在立言层面,在不涉及我公司商业机密的前提下,我的天命和归宿就是做单片机技术分享,写一辈子源源不断的技术分享连载帖,然后写一本《从单片机基础到程序框架》的书,帮助更多单片机初学者,出书可以满足我在立言方面的追求。在立德层面,我平时信因果,在生活中多传播正能量。       我有自知之明,我的天命就是传播单片机技术。人最宝贵的东西是生命,生命属于人只有一次,人的一生应当这样度过:当他回首往事的时候,他不因虚度年华而悔恨,也不应碌碌无为而羞愧。在他临死的时候,他能够这样说:我的整个生命和全部精力,都献给了世界上最壮丽的事业——为传播单片机技术而奋斗。

szdzjs 发表于 2016-1-23 19:05:26

本帖最后由 szdzjs 于 2016-1-23 19:08 编辑

来欣赏鸿哥的佳作,就算没了沙发,已无板凳,坐个地板也别无遗憾!

jianhong_wu 发表于 2016-1-26 13:32:17

本帖最后由 jianhong_wu 于 2016-1-31 15:54 编辑

第二节:初学者的疑惑。
【2.1   单片机应用的核心技术是什么?】
      单片机应用的核心技术是什么?是按键,数码管,流水灯,串口。是它们的程序框架。按键和数码管是输入是人机界面,把它们的程序框架研究透了,以后做彩屏或者更花销的显示界面,程序框架也可以通用。流水灯是应用程序是APP,把它的程序框架研究透了,以后控制飞机大炮的程序框架也是一样。串口是通讯是接口,把它的程序框架研究透了,以后搞SPI,CAN,USB等通讯项目时,上层的程序框架也可以通用。如果某天你突然腰酸背痛可能是缺钙了,如果某天你第一次做项目时突然发现无从下手,你缺的可能是程序框架。
【2.2   跟我学单片机到底是学什么?】

      跟我学单片机到底是学什么?我的回答是像驾驶汽车一样驾驭单片机。我教给大家的是驾驶汽车的技术而不是研发汽车的技术。因此每当别人问我学51单片机,PIC,AVR,stm32哪个更加有前途,应该先学哪个再学哪个时,我的回答是既然你是学驾驶技术,那么用桑塔纳车来学还是用宝马车来学有差别吗?差别很小的,它们只是不同的厂家而已,只要会一种其它的就触类旁通了。把学单片机当作考驾照这是我常用的一个比喻。
【2.3   单片机神奇的工作原理是什么?】
      单片机神奇的工作原理是什么?初学者不用纠结这个问题,这不是我们学习的方向。考驾照只要大概知道汽车是由四个轮,发动机,制动系统,离合器,方向盘等部分构成就够了,再深入的细节不用纠结。学单片机只要大概知道单片机内部由运算器,寄存器,IO口,复位电路,晶振电路等部分组成就够了,再深入的不用纠结。说实话,我本人做单片机开发有很多年了,但是我对单片机的工作原理也很模糊,就像人为什么能通过大脑来灵活控制双手,对于我仍然是一个迷。有这样的疑惑时咋办?我建议用“游戏规则”这个概念去应付它。游戏规则是不需要解释的,只要遵守就可以了。在应用的技术领域,把暂时不解的东西当作游戏规则来解读和遵守是我常用的思维方式。
【2.4   很难记住繁杂的寄存器?】
       很难记住繁杂的寄存器?寄存器不用死记硬背,只要知道它大概的操作流程,有哪几类就够了。配置寄存器时,可参考别人已经配置好的代码,这些代码都很容易通过网络或者书本获得。也可以查找芯片数据手册,有很多单片机厂家会给出各个功能的代码范例。

【2.5   C语言很难学?】
      C语言很难学?暂时不用学指针,暂时不用学结构体,暂时不用学多文件编程,暂时不用学链表,暂时不用学宏定义,暂时不用学文件操作,暂时不用学所有的数据类型。只要会:
      5条指令语句switch语句,if else语句,while语句,for语句,=赋值语句。
      7个运算符+,-,*,/,|,&,!。
      4个逻辑关系符||,&&,!=,==.
      3个数据类型unsigned char, unsigned int, unsigned long。
      3个进制相互转化,二进制,十六进制,十进制。
      1个void函数。
      1个一维数组code(或const) unsigned char array[]。
      只要会上述一些知识点,应付任何一种逻辑功能的单片机软件都足够了。
      我在学校时只学了汇编程序,出来工作后才知道用C语言也能开发单片机,一开始只用常见的几条语句就把项目做出来了,没有用到printf这类繁琐格式的语句,第一次感觉C语言原来是那么容易那么简单,我是到后来才陆陆续续用到C语言其他的高级功能。
【2.6   汇编语言很难学怎么办?】
       汇编语言很难学怎么办?我提个建议,对于初学者,一开始就学汇编语言确实难学,不如先学C语言,学会了C语言再学汇编,这样理解起来就容易多了。也可以把C语言列入必修课,汇编语言列入选修课,因为实际工作中也是C语言为主。
【2.7   很难记住各种繁杂的通信协议?】
      很难记住繁杂的各种通信协议?IIC,SPI,232,CAN,USB等等。不用记那么多,你只要理解串行和并行通讯方式的基本原理就可以了,剩下的只是不同的协议而已,工作时再根据需要去看看相关资料就可以上手。不管世上有多少种通讯协议,物理世界上只有这两种通讯方式。
【2.8   很难写出短小精悍的程序?】
      很难写出短小精悍的程序?初学者不用纠结于此。很多项目开发,程序容量不是刻意追求的目标,多一点少一点没关系,不会是寸土寸金的事情,现在大容量的单片机品种也非常多,反而更值得关注的是程序的运行效率,可读性和可修改性。当然,一些成本敏感的消费类电子不在此讨论范围,这类项目往往对程序容量也要求很苛刻。



jianhong_wu 发表于 2016-1-31 21:06:37

本帖最后由 jianhong_wu 于 2016-1-31 21:07 编辑

第三节:单片机最重要的一个特性。
【3.1   单片机的“一”。】                                                                              图3.1
    “道生一,一生二,二生三,三生万物。”《道德经》认为,世间万物,缤纷多彩,都源自一个东西,这个“一”的东西就是“道”。电子世界也存在“一”,这个“一”繁衍出手机,电脑,电视机,机器人等丰富多彩的电子世界,这个“一”就是单片机最重要的一个特性:“程序下载进内存后,单片机既可以通过管脚识别外部输入的高低电平信号,也可以通过管脚对外部输出不同时间长度的高低电平。”这句话有5个关键词“程序,内存,管脚,电平,时间。”下面我详细解读每个关键词的含义,涉及到某些专用名词如果不理解也没关系,主要是让大家有个感性的认识就足矣。
【3.2   程序。】
    单片机程序有3种:C程序,汇编程序,机器程序。能下载进单片机的只有机器程序,C程序和汇编程序都不能直接下载进单片机,所以C程序和汇编程序都要经过编译软件翻译成机器程序后,才能下载进单片机。程序是语言,语言是用来交流,交流就必须存在两个对象,对象分别是程序员和单片机。程序员是人所以用C或者汇编语言,单片机是机器所以用机器语言,人和机是不同的世界,两者交流就需要一个翻译家,翻译家就是编译软件,俗称编译器,它能把C语言或者汇编语言翻译成单片机能识别的机器语言。机器语言就是0101的代码,一般以十六进制的形式呈现。    理论上,程序员也可以抛开C和汇编语言,直接用机器语言做项目。我读书时老师让我们做实验就是这么整。那时我还不知有烧录器,老师让我们先用汇编语言写好程序,然后自己充当编译器,对照汇编语言和机器语言的指令表,人工把汇编语言翻译成十六进制的机器语言,最后把机器语言按字节一个一个的输入到特定的实验设备来观察现象。现在回想起来,老师当时的初衷是让我们了解编程语言的本质。    既然可以直接用机器语言做项目,为什么还要C语言或者汇编语言?在C语言或者汇编语言没有诞生前,程序员就是通过在纸带上打孔来代表01的机器语言,此时相当于结绳记事的原始阶段。后来人类发明了汇编语言,通过英语单词来表示01机器语言特定的指令语句,此时开始诞生了汇编语言的编译器相当于进入象形文字的阶段。再到后来人类又发明了C语言,通过数学符号和英语单词来表达自己的逻辑,诞生了C语言的编译器相当于进入了汉字白话文阶段,从此程序员写出来的C程序就非常方便移植,编辑,阅读,传播,继承。现在单片机开发的主流是C语言,我本人出来工作后就没有用过汇编做项目开发。C语言是必修课,汇编语言是选修课;C语言是白话文简单易懂,汇编语言是文言文繁琐难读。当然汇编也有它的应用场合,汇编的翻译效率高,往往是一句汇编语言对应一句机器语言,而一句C语言有可能对应几句机器语言,这样C程序的代码效率在很大程度上取决于编译器的水平,编译器能不能帮你翻译出高效的机器语言对于我们来说往往像黑盒子,不像汇编语言那么可控制。所以很多嵌入式系统某段要求简洁高效的源代码往往用汇编来写,也有少数一些很便宜的单片机不提供C编译器,只能用汇编语言开发。要不要学汇编,最好根据个人的工作需求来决定。
【3.3   内存。】
    单片机就像MP3,程序代码就像歌曲,把不同的歌曲下载到MP3里就可以听到不同的音乐,把不同的程序下载到单片机里,单片机就能做不同的事。能装程序的单片机必然有内存,内存由ROM和RAM组成,ROM和RAM都能装东西,但各有不同。    ROM的优点是存储的东西断电后不会丢失,缺点是存储的东西上电后不能更改,想要改变ROM的内容除非重新下载程序,而且下载次数有限制,FLASH的ROM最大次数通常是10万次,而OTP的ROM只能下载1次,所以平时上电工作时ROM存储的东西是不能更改的,某些具有IAP功能的高级单片机不在此讨论范围内。而RAM恰好反过来,RAM的优点是存储的东西上电后可以随时被单片机更改,更改次数没有限制,缺点是存储的东西断电后会丢失,没有记忆功能。    ROM和RAM各有特点,单片机从中各取所长。ROM用来存储不可更改的指令代码和常量数据,ROM的容量往往相当于代码的容量。RAM用来存储可以被更改的变量数据,RAM的容量往往相当于全局变量和局部变量的容量。不管是用C语言还是汇编,所写的程序代码就自然包含了指令代码、常量数据、全局变量、局部变量,那么谁在幕后帮我们进行分类存储,谁把一个程序代码的一分为二让它们在ROM和RAM里各就各位?是编译器软件和下载器(烧录器),编译器除了把C语言翻译成机器语言之外,还帮我们分好了类,分配好了存储的地址和位置,下载器(烧录器)再根据这些信息把程序存储到内存中,这些工作一般不用程序员干预,它们自动完成。
【3.4   管脚。】
    管脚是单片机与外部电路进行能量和信息交互的桥梁。有电源,复位,晶振和IO口这4类管脚。       第一类电源管脚。是给单片机内部电路供电的接口。单片机有两种常用的供电电压,一般是3.3V或者5V,有的单片机两种电压都兼容。    第二类复位管脚。单片机上电后需要外部电路给它一个瞬间高电平或者低电平的复位信号,才能启动工作。复位电路通常是由电容和电阻组成的充电电路来实现,也有一些系统是用专门的复位芯片来实现。    第三类晶振管脚。任何单片机想要工作必须要有晶振。单片机执行程序指令是按一个节拍一个节拍来执行的。这个节拍的时间就是由晶振产生,所以把晶振比喻成单片机的心脏是非常恰当的。现在也有很多单片机直接把晶振集成到内部,这类单片机不用外接晶振也可以。    第四类IO口管脚。这是跟我们编写程序关联最密切的管脚。前面提到的电源,复位,晶振这3种管脚是为了让单片机能工作,俗称单片机工作的三要素。而单片机工作的具体内容就是通过IO口管脚来体现的。比如,IO口能识别按健的输入,也能输出驱动继电器工作的开关信号,也能跟外围器件进行通信。
【3.5   电平。】
    电平就是电压的两种状态,低或者高,低相当于程序里的0,高相当于程序里的1。单片机IO口管脚检测到的电压低于或等于0.8V时是低电平,程序里读取到的是0数字。单片机IO口管脚检测到的电压高于或等于2.4V时是高电平,程序里读取到的是1数字。必须注意的是,IO口输入的最大电压不能超过单片机的供电电压。单片机输出的低电平是0V,单片机输出的高电平等于它的供电电压值,往往是3.3V或者5V。
【3.6   时间。】
    时间是单片机程序必不可少的元素。跟外围芯片通信的时序节拍需要时间,驱动发光二极管闪烁需要时间,工控自动化的某些延时需要时间。单片机的时间来源自两方面。第一方面源自指令的周期时间。单片机是根据节拍来执行程序指令的,每执行一条指令都要消耗一点时间,只要让程序执行的指令数量越多,产生的时间就越长,通过调整所执行指令的数量就可以得到所需的时间长度。第二方面源自单片机内部自带的定时器。假如设置定时器每20毫秒产生一次中断,现在要获取10秒钟的时间,只需在程序里统记500次定时中断就可以了,因为1秒等于1000毫秒。指令和定时器这两者的时间最后都来源于晶振。

jianhong_wu 发表于 2016-2-11 10:46:54

本帖最后由 jianhong_wu 于 2016-2-11 10:47 编辑

第四节:平台软件和编译器软件的简介。
【4.1   平台软件和编译器软件的各自分工。】
      C语言写在哪?谁来把C语言翻译成单片机可以识别的机器语言?这就是平台软件和编译器软件的分工。平台软件负责编辑C语言,编译软件负责把C语言翻译成单片机可以识别的机器语言。
【4.2   每一种单片机的平台软件和编译器软件不一定是唯一的。】
      C语言在单片机的应用也是最近这些年发展起来的,早期做单片机的原厂更关注芯片硬件本身,配套的C语言开发软件方面涉入不深,他们往往只管把单片机芯片生产出来后,给大伙提供一个汇编语言的编译器软件就草率了事,所以给了很多第三方商家做平台软件和C编译器的机会,后来单片机原厂也乐意支持和配合这些第三方开发软件的厂商,也有一些单片机原厂直接收购这类第三方软件公司。因此,不同厂家的单片机,它所用的平台和编译器软件可能都不一样。即使是同样一个厂家的单片机,它也有可能存在多种不同的第三方平台软件和编译器软件,每一种单片机所用的平台软件和编译器不一定是唯一的。比如stm8单片机可以用 STVD软件平台,也可以IAR平台。stm32单片机可以用keil平台,也可以用IAR平台。
【4.3平台软件和编译器软件的宿主与寄生关系。】
      平台软件选定了之后,所用的编译器软件也可能存在多种选择,并不是一种平台软件就绑定一种编译器软件。生物学的比喻,平台软件是宿主,编译器软件是寄生在平台软件里的。一个平台软件可以嵌入多种不同的编译器软件,平台软件和编译器软件存在一对多的关系。比如,PIC单片机的平台软件是MPLAB,8位PIC单片机是PICC编译器,12位PIC单片机是PIC18编译器,16位PIC单片机是C30编译器。而且MPLAB平台软件与上述各种编译器软件都要单独一个一个分开来安装,最后运行MPLAB平台软件,在里面操作某个菜单设置选项,把各种C编译器软件跟MPLAB平台软件关联起来。
【4.451单片机的平台软件和编译器软件。】
      我后面的讲解,51单片机的平台软件用keil2,编译器软件用C51。单片机程序开发需要用到这两种软件,但在项目开发的时候,只要跟平台软件打交道就可以了,因为编译器软件是当做一种独立配件嵌入到平台软件里,统一接受平台软件的控制。我在用PIC的8位单片机时,需要安装一次MPLAB平台软件,也需要独立再安装一次PICC编译器软件,然后运行MPLAB平台软件,在里面操作某个菜单设置选项,把PICC编译器跟MPLAB平台软件关联起来,也就是我所说的把PICC编译器嵌入到MPLAB平台软件里,统一接受平台软件的控制,但我平常写代码时只需要跟MPLAB平台软件打交道就可以了。我早期在做51单片机开发时,也是需要把keil平台软件和C51软件分开安装,然后再把它们关联起来,但是现在从keil2版本开始,在安装keil平台软件时就已经默认把C51安装好了,并且自动把C51嵌入到了keil平台软件。我现在用keil2这个版本的平台软件,只需要安装一次keil2平台软件就可以了,不需要像早期那样再单独安装C51编译器。



jianhong_wu 发表于 2016-2-19 15:04:18

本帖最后由 jianhong_wu 于 2016-3-6 06:42 编辑

第五节:用keil2软件关闭,新建,打开一个工程的操作流程。
【5.1   本教程选择keil2软件版本的原因。】
      Keil软件目前有Keil2,Keil4,Keil5等版本。本教程之所以选用keil2版本,是因为keil2版本比较单纯,它本身内置了C51编译器,并且只适用于51单片机不能适用于stm32这类单片机。而Keil4,Keil5等版本不仅可以适用于51单片机的,还可以适用于ARM类的单片机,它们有C51编译器和MDK-ARM编译器两种选择,在同一个keil4或者keil5版本里,C51和MDK-ARM两者往往只能二选一,MDK-ARM编译器是针对stm32这类单片机,如果你电脑上用了MDK-ARM编译器想再切换到C51编译器就很麻烦了往往不兼容,为了电脑上既能用C51编译器,又能兼容MDK-ARM编译器,我的电脑上是同时安装了C51编译器的keil2和MDK-ARM编译器的keil4,一台电脑同时安装keil2和keil4不会冲突,能兼容的。
【5.2   如何在不用关闭keil2软件的前提下又能关闭当前被打开的工程?】
         要关闭当前工程,最简单的方法是直接点击keil2软件右上角的“X”直接把keil2软件也一起关了,这种方法不在讨论范围,现在要介绍的是如何在不关闭keil2软件的前提下又能关闭当前被打开的工程。


                        图5.2.1            
         第一步:启动keil2软件。         双击桌面”keil uVision2”的图标启动keil2软件。
----------------------------------步骤之间的分割线----------------------------------------

                        图5.2.2      
      第二步:关闭被打开的已有工程。      启动keil2软件后,假设发现此软件默认打开了一个之前已经存在的工程。关闭已有工程的操作是这样子的:点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”即可。这时keil2软件处于“空”的状态,没有打开任何工程了。

【5.3   keil2如何新建一个工程?】

                        图5.3.1
       第一步:新建一个工程文件夹。       在电脑D盘目录下新建一个文件夹,取名为“stc89c52rc”。

   补充说明:
   (1)文件夹的命名以及后面涉及到的工程文件名统统都不要用中文,请全部用英文,数字,或者下划线这些字符。即使keil软件支持中文名,我建议也不要用中文名,因为在单片机这个行业,有一些单片机厂家提供的平台软件,某些版本是不支持中文名的,所以大家从一开始就养成这个习惯,以后可以避免遇到一些不必要的麻烦。
   (2)新建的文件夹请直接放在某盘的根目录下,而不要放到某个已有文件夹的目录下。一方面是因为已有的文件名目录往往带有中文单词,另外一方面是有一些单片机厂家的平台软件不支持嵌入层次太深的文件目录,所以大家从一开始就养成这个习惯,以后可以避免遇到一些不必要的麻烦。

----------------------------------步骤之间的分割线----------------------------------------
       第二步:启动keil2软件。       双击桌面”keil uVision2”的图标启动keil2软件。
----------------------------------步骤之间的分割线----------------------------------------
       第三步:关闭默认被打开的已有工程。       启动keil2软件后,如果发现此软件默认打开了一个之前已经存在的工程,请先关闭此工程让keil2软件处于“空”的状态,如果没有发现此软件默认打开已有工程,这一步可以忽略跳过。关闭已有工程的操作是这样子的:点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”即可。这时keil2软件处于“空”的状态,没有打开任何工程了。
----------------------------------步骤之间的分割线----------------------------------------


                               图5.3.4.1


                               图5.3.4.2


                               图5.3.4.3


                               图5.3.4.4
      第四步:利用工具向导新建一个工程。      点击上面”Project”选项,在弹出的下拉菜单中选择“New Project...”,在弹出的对话框中,选择保存的目录是刚才第一步新建的文件夹“stc89c52rc”,同时输入跟文件夹名称一样的工程文件名“stc89c52rc”,然后单击“保存”按键(一个新工程模板就建成了),单击“保存”按键后此时会弹出一个选择单片机型号的对话框,单击”Atmel”这个厂家前面的“+”号,在展开的下拉选项中选中“AT89C52”这个型号,然后点击“确定”,此时会弹出一个英文询问框,大概意思是“是否要复制STARTUP.A51这个文件到工程里?”我们单击“否”即可。

补充说明:
      (1)以上新建的保存文件名应该跟我们第一步在D盘新建的文件夹名称一致,确保都是“stc89c52rc”,因为有一些单片机厂家的平台软件是有这个要求的,所以大家养成这个习惯,以后可以避免遇到一些不必要的麻烦。
      (2)上面之所以选择Atmel厂家的AT89C52单片机,是因为本教程选用的单片机STC89C52RC跟AT89C52是兼容的。
      (3)在弹出的英文询问框,大致意思是“是否要复制STARTUP.A51这个文件到工程里?”,那么STARTUP.A51这个文件有什么含义?STARTUP.A51是一个启动程序文件,在单片机进入.c程序执行main函数之前,先去执行这个启动程序,这个启动程序是专门用来初始化RAM和设置堆栈等,如果我们选“否”不添加这个启动程序,编译器也会自动加入一段我们不能更改的默认启动程序。如果选“是”,那么这个文件就会出现在我们工程里,我们可以根据需要对它进行更改。但是大多数的情况下,我们都不会去更改此文件,所以无论你选“是”还是“否”,只要你不更改START.A51文件,对我们来说都是一样的。因此我本人一般情况下都是选“否”。

----------------------------------步骤之间的分割线----------------------------------------

                        图5.3.5.1


                     图5.3.5.2

                     图5.3.5.3
       第五步:新建一个.c源文件。       点击上面”File”选项,在弹出的下拉菜单中选择“New...”,会看到弹出来一个名字为”Text1”的文件。再一次点击上面”File”选项,在弹出的下拉菜单中选择“Save”,会弹出一个保存的对话框,此时还是选择保存在第一步新建的文件夹目录下,并且把“Text1”文件名更改为“stc89c52rc.c”(注意后缀是.c扩展名),单击“保存”。

    补充说明:
    (1)此时你如果打开D目录下“stc89c52rc”的文件夹,你会发现此文件夹有一个“stc89c52rc.c”的文件,这个文件就是在这一步被新建添加进来的,但是此文件“stc89c52rc.c”目前跟整个工程还没有关联,还需要在接下来的第六步那里进行关联操作。
    (2)上面新建添加的文件,它的文件名必须是带.c这个扩展名,表示此文件是C文件格式,这一个很重要不要搞错了。往后我们所写的C语言程序代码就是写在此C格式的文件里。此文件也俗称C源文件。

----------------------------------步骤之间的分割线----------------------------------------

                                 图5.3.6.1


                                 图5.3.6.2



                                  图5.3.6.3
      第六步:把刚才新建的.c源文件添加到工程里,跟当前工程关联起来。      点击左边竖着的选项框里面的”Target 1”前面的“+”号,在展开的下拉菜单下看到“SourceGroup 1”。右键单击“SourceGroup 1”选项,在下拉菜单中选择“Add Files to Group ‘SourceGroup 1’”选项,弹出一个文件选择对话框,单击选中刚才新建的.c源文件,然后单击一次“Add”按钮,此时虽然对话框没有关闭,但是已经悄悄地把.c源文件添加到工程里了(这个地方keil的用户体验设计得不够好,容易让人误解还没有把文件添加进来),这时再点击一次“Close”按钮先把此对话框关闭,然后发现左边的“Source Group 1”前面多了一个”+”号,单击此”+”号展开,发现下面的文件恰好是刚才新添加进去的.c源文件“stc89c52rc.c”。

   补充说明:
       (1)在刚才的操作中,我本人觉得keil软件有一个地方的用户体验做得不够好,容易引起误解。就是在弹出一个文件选择对话框时,先单击选中刚才新建的.c源文件,此时单击一次“Add”按钮,已经相当于把.c文件添加进工程了,但是此时keil软件并没有自动关闭对话框,这样很容易让初学者误以为.c源文件还没有被添加进去。

   ----------------------------------步骤之间的分割线----------------------------------------
      
                        图5.3.7
      第七步:至此,可以正常的编辑C语言代码了。      双击打开左边Target1里面Source Group1下刚刚被添加进工程的“stc89c52rc.c”源文件,就可以在此“stc89c52rc.c”文件下输入C语言代码了,请把以下范例代码复制进去,然后再一次点击”File”选项,在弹出的下拉菜单中选择“Save”保存。此时,新建一个工程的步骤已经完成。供复制的范例代码如下:
#include "REG52.H"
void delay_long(unsigned int uiDelayLong); //延时函数
sbit led_dr=P1^6;
void main()
{
   while(1)
   {
       led_dr=1;//LED亮
       delay_long(100);    //延时50000个空指令的时间
       led_dr=0;//LED灭
       delay_long(100);    //延时50000个空指令的时间
   }
}
void delay_long(unsigned int uiDelayLong) //延时函数
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++);//内嵌循环的空指令数量
   }
}

------------------此处为分割线,上面的是代码的结束,下面的是补充说明的开始------------------

补充说明:
       (1)可能有些朋友不是用keil2版本,如果他们是用keil4的版本,当把代码复制到keil4时,如果中文注释出现乱码怎么办?解决办法是这样的:点击keil4软件的左上角"Edit",在下拉菜单中选最后一项“Configuration”,在弹出的对话框中把Encoding的选项改成“Chinese GB2312(Simplified)”.然后删除所有C代码,重新复制一次代码进去就恢复正常了。当然,我们用keil2版本不会遇到这个问题,况且keil2版本的"Edit"下拉菜单也没有“Configuration”这个选项,所以keil2和keil4还是有一些差别的。


【5.4   keil2如何打开一个现有的工程?】
      第一步:启动keil2软件。      双击桌面”keil uVision2”的图标启动keil2软件。
----------------------------------步骤之间的分割线----------------------------------------
      第二步:关闭默认被打开的已有工程。      启动keil2软件后,如果发现此软件默认打开了一个之前已经存在的工程,请先关闭此工程让keil2软件处于“空”的状态,如果没有发现此软件默认打开已有工程,这一步可以忽略跳过。关闭已有工程的操作是这样子的:点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”即可。这时keil2软件处于“空”的状态,没有打开任何工程了。
----------------------------------步骤之间的分割线----------------------------------------

                                    图5.4.3.1



                                    图5.4.3.2
      第三步:打开一个现成的工程。      点击上面”Project”选项,在弹出的下拉菜单中选择“Open Project”,在弹出的文件对话框中,找到需要被打开工程文件夹(本例程是D盘下的“stc89c52rc”文件夹),在此文件夹目录下单击选中“stc89c52rc.Uv2”这个工程文件名,然后点击“打开”,就可以打开一个现有的工程文件了。

jianhong_wu 发表于 2016-2-28 11:16:39

本帖最后由 jianhong_wu 于 2016-3-25 00:06 编辑

第六节:把.c源代码编译成.hex机器码的操作流程。


【6.1   详细步骤。】

         第一步:启动keil2软件。
         双击桌面”keil uVision2”的图标启动keil2软件。

----------------------------------步骤之间的分割线----------------------------------------

         第二步:关闭默认被打开的已有工程。
         启动keil2软件后,如果发现此软件默认打开了一个之前已经存在的工程,请先关闭此工程让keil2软件处于“空”的状态,如果没有发现此软件默认打开已有工程,这一步可以忽略跳过。关闭已有工程的操作是这样子的:点击上面”Project”选项,在弹出的下拉菜单中选择“Close Project”即可。这时keil2软件处于“空”的状态,没有打开任何工程了。

----------------------------------步骤之间的分割线----------------------------------------

          第三步:打开一个现成的工程。
          点击上面”Project”选项,在弹出的下拉菜单中选择“Open Project”,在弹出的文件对话框中,找到需要被打开工程文件夹(本例程是上一节在D盘下建的“stc89c52rc”文件夹),在此文件夹目录下单击选中“stc89c52rc.Uv2”这个工程文件名,然后点击“打开”,就可以打开一个现有的工程文件了。

----------------------------------步骤之间的分割线----------------------------------------

         
                                 图6.1.4.1


         
                                 图6.1.4.2

          第四步:设置编译环境让keil2软件允许产生.hex格式的机器码文件。
          鼠标右键点击选中左边选项框里面的”Target 1”选项,在右键下拉菜单中选择“Options for Target‘Target 1’ ”选项,弹出一个编译环境设置对话框,左键单击上面子菜单切换到“Output”窗口下,把“Create Hex File”勾选上。点击“确定”。

         补充说明:
      (1)这个选项很重要,必须把“Create Hex File”选项勾上,否则后续的操作不能在工程文件夹的目录里生成.Hex的机器码文件。对于一个文件夹的工程模板,只需要设置一次就可以保存起来了,下次开电脑重新打开此工程模板时不需要再设置,这些被设置的参数都是能掉电保存起来的。


----------------------------------步骤之间的分割线----------------------------------------


         
                                 图6.1.5.1

         第五步:启动编译。
         在确保stc89c52rc.c源文件里面有C语言源代码的情况下(如果没有,请先复制上一节的例程源代码),点击上面”Project”选项,在弹出的下拉菜单中点击“Rebuild all target files”编译命令,编译器开始编译工作。

----------------------------------步骤之间的分割线----------------------------------------

         
                                 图6.1.6.1

         第六步:在”Output Window”窗口下观察编译结果。
         可以在最下方的”Output Window”窗口下观察到编译的过程提示。”Output Window”窗口默认出现在源代码区的最下方,如果没有发现”Output Window”窗口,请把鼠标的光标移动到最下方的滑动条下边,当它呈现移动光标的形状时,按住左键往上拖动就可以看到“Output Window”窗口了。当“Output Window”窗口最后一行显示“"stc89c52rc" - 0 Error(s), 0 Warning(s).”等信息时,表示翻译工程结束了。其中0 Error(s)代表编译成功,没有任何错误。0 Warning(s)代表没有任何警告。

         补充说明:
      (1)只要有一个错误Error产生,就说明编译不通过。如果没有任何错误Error产生,但是有几个警告Warning产生,在这种情况下很多时候都不影响程序的正常运行,只有少数情况下是会影响代码的正常运行的,因此我本人建议哪怕是一个警告,大家也不要放过它,也要找到产生这个警告的原因。


         
                                 图6.1.6.2

         (2)查找错误的时候,只需要双击错误提示error那行内容,光标就会自动跳到源代码错误的附近,方便大家寻找语法错误。(3)还有一种很实用的方法,就是直接把提醒出错那一整行英文复制粘贴到网上去搜索,往往能搜索到所需的正确答案或者重要提示。


----------------------------------步骤之间的分割线----------------------------------------


         
                                 图6.1.7.1

         第七步:编译后生成.hex机器码文件的目录位置。
         以上编译成功后,只要打开电脑D盘的stc89c52rc文件夹,就可以找到.hex扩展名的机器码文件,这个文件就是我们要下载到单片机的机器码文件。

【6.2   注意!最后,还有一个非常重要的keil编译环境需要设置。】

      STC89C52单片机与AT89C52单片机是兼容的,它们程序容量ROM都是8K字节,而它们的数据容量RAM容量是不一样的,STC89C52的RAM是512字节,而AT89C52的RAM是256字节,尽管两者的RAM容量有一些小差异,但是对于我们用来入门学习来说,这些都是无所谓的,所以本教程硬件平台虽然是用STC89C52单片机,但是keil的编译环境其实是用AT89C52的芯片环境,因此本教程就以AT89C52为准。 刚才提到AT89C52的程序容量ROM是8K字节,数据容量RAM是256字节,那么问题来了,很多初学者经常遇到,有一些程序代码ROM明明没有超过8K,或者数据容量明明还没超过256字节,编译器居然报错提醒容量不够!什么原因?怎么解决?
       什么原因?是单片机的内存分配模式问题引起的,具体原因暂时不深入讲解。
       怎么解决?最后还要有一个非常重要的keil编译环境需要要设置,设置步骤是:
      
                        图6.2.1

      如上图所示,在一个已经打开的工程里,鼠标右键点击选中左边选项框里面的”Target 1”选项,在右键下拉菜单中选择“Options for Target‘Target 1’ ”选项,弹出一个编译环境设置对话框,第一步单击上面子菜单切换到“Target”窗口下,第二步在Memory Model选项的下拉菜单中选中“Compact: variables in PDATA”,第三步在Code Ram Size选项的下拉菜单中选中“Large: 64K program”,第四步点击“确定”。


jianhong_wu 发表于 2016-3-6 13:10:01

本帖最后由 jianhong_wu 于 2016-8-1 10:28 编辑

第七节:本节预留。
         本节预留。

jianhong_wu 发表于 2016-3-12 16:57:33

本帖最后由 jianhong_wu 于 2016-3-12 18:31 编辑

第八节:把.hex机器码程序烧录到单片机的操作流程。





【8.1   烧录程序的本质。】

       “烧录”是比较专业的说法,很多初学者第一次听这词还以为跟火有关,莫名其妙的“烧录”是啥意思?烧录其实就是下载,烧录程序就是下载程序。下载好理解了吧,下载电影,下载歌曲,让播放器去播放。此处的下载程序跟下载歌曲的“下载”完全是一回事。有人会问,下载歌曲到手机,手机是成品,下载程序到单片机,单片机也是成品?新买回来的单片机不是一张白纸的电子元件吗?其实,新买回来的单片机就是一个成品,它不是白纸,它内部已经嵌入了一段系统程序,这个系统程序就像你刚买回来的手机就帮你预装了安卓系统一样,只是它的用户存储区是空白的。比如手机,你往这个存储区里存电影就可以看电影,存音乐就可以听音乐。比如单片机,你往这个存储区存不同的程序就可以让单片机做不同的事。而预装在新单片机内部的系统程序就是专门负责跟外部接口通讯,同时负责把hex格式的程序代码存放在单片机内部正确的位置,这个就是烧录程序(下载程序)的本质。这样一比喻,所以.hex格式的烧录文件跟.MP3格式的音乐文件在存储本质上是一样的。
      再回顾总结一下,烧录程序的本质是:把单片机当做一个存储器,每一条程序指令都对应一个唯一的存储地址,把这些指令以字节为单位一条条存储到指定的存储地址中,这就是烧录程序的本质。对于STC89C52RC单片机,在下载程序时需要上位机软件和一根USB转串口线。上位机软件负责把.hex格式的机器码文件打开,机器码文件里面记录着每条程序指令所对应的地址信息,下载过程时,上位机软件根据.hex文件记录的指令内容和对应的地址信息,经过USB转串口线,跟单片机的预置系统程序进行串口通讯,从而把.hex记录的信息传输到单片机内部的flash存储器中,实现了程序的烧录下载。

【8.2   烧录程序所需的工具和软件。】

      (1)装有XP或者WIN7系统的电脑一台。
       其它更高系统的我还没测试过,应该问题也不大。



      
                                  图8.2.2

      (2)带9针串口、1颗LED灯、电源拨动开关、能5V电源供电的stc89c52rc单片机核心板一块。
      单片机的学习离不开硬件平台的编程练习,本教程用的是坚鸿51学习板,此学习板的原理图在第七节已经分享到附件资料里。大家也可以根据原理图自己焊接一块学习板来学习,或者用其它厂家带有串口的单片机学习板来学习。
      串口是用来单片机跟电脑通讯的接口,是STC89C52RC单片机下载程序的通道。LED灯用来观察单片机是否正常运行程序。电源拨动开关方便烧录程序时提供所需的断电和上电的操作。本单片机系统是5V供电。


      
                                  图8.2.3

      (3)主控芯片是CH340的USB转RS232串口线一条。
       我之所以推荐主控芯片是CH340的USB转RS232串口线,因为CH340的下载线在烧录程序时很稳定可靠。这款USB转串口线可以在淘宝购买到。


      
                                  图8.2.4

   (4)5V供电的USB电源线一条。
      此USB线可以从电脑的USB口取电,也可以从输出5V的手机充电器处取电。但是我建议大家用输出5V的手机充电器来供电,因为很多电脑的USB口供电干扰比较大,会影响程序烧录。


      
                                  图8.2.5

   (5)主控芯片是CH340的USB转RS232串口线驱动安装程序。
      此驱动程序USB转RS232串口线的厂家通常都会提供,但是建议用我在附件资料里推荐给大家的驱动程序,毕竟这个程序经过我本人验证测试过。


      
                                  图8.2.6

   (6)烧录程序和串口助手功能都具备的“stc-isp-15xx-v6.85I”上位机软件。
       这是宏晶单片机官方免费提供的上位机软件,可以在宏晶单片机的官网上下载获取。这款软件有很多功能,除了有下载程序和串口助手的功能外,还可以用来配置自动生成所需的初始化代码。当然,本教程后面主要是用到此软件的下载程序和串口助手这两个功能。所以大家所选的软件版本必须是v6.85I版本或者以上的版本,因为早些年有一些版本只有烧录功能但是没有串口助手的功能。

【8.3   把.hex文件烧录到单片机的操作流程。】

       前面第6节内容已经教大家把一个驱动LED灯闪烁的C源代码编译成.hex文件的操作流程,同时在D盘的“stc89c52rc”文件夹里已经生成了一个“stc89c52rc.hex”的机器码文件,现在就要教大家如何烧录此文件到单片机内。此程序的功能是让单片机驱动一颗LED灯闪烁。



         
                               图8.3.1.1

      
                               图8.3.1.2

      
                               图8.3.1.3

       第一步:安装USB转串口驱动程序。
       上位机“stc-isp-15xx-v6.85I”烧录软件就是安装在电脑端的用户软件,电脑跟单片机进行通讯,需要一根USB转串口线,欲使USB转串口线正常工作,必须预先安装USB转串口的驱动程序。具体的操作是这样的:在本连载贴附件资料处下载“USB转串口的驱动程序CH340.zip”文件压缩包,解压后打开此文件夹,找到“SETUP.EXE”这个安装应用程序,双击启动,在弹出的界面中,单击“安装”按钮即可完成驱动程序的安装。

----------------------------------步骤之间的分割线----------------------------------------

         
                               图8.3.2.1

      
                               图8.3.2.2

      
                               图8.3.2.3

      
                               图8.3.2.4

      第二步:硬件线路连接,同时记录串口号。
      把USB转串口线插入电脑USB口,此时USB转串口线的另外一端连接51学习板的9针串口。同时,电源线一端用输出的5V手机充电器USB端口供电,电源线另一端连接51学习板的USB供电端口,此时可以通过51学习板的电源拨动开关来控制断电和上电。然后是查找串口号,方法是:以电脑WIN7系统为例,右击桌面“计算机”,单击选择下拉菜单的“管理”选项,在弹出的窗口中,点击“设备管理器”选项切换到对应的设置窗口,双击“端口(COM和LPT)”选项,在展开的下拉选项中,会看到“USB-SERTAL CH340(COM3)”,这个COM3就是我们要记住的串口号,记住此串口号,后面的步骤要用到。你们的串口号不一定是COM3,请以你们电脑显示的串口号为准。

----------------------------------步骤之间的分割线----------------------------------------


      
                                 图8.3.3

       第三步:打开上位机用户软件“stc-isp-15xx-v6.85I.exe”。
       这个软件可以在宏晶单片机的官网下载获取,获取到的软件压缩包只需解压后就可以使用,不用安装,
直接双击打开“stc-isp-15xx-v6.85I.exe”,此时会弹出“温馨提示”的窗口,我们按“确定”就可以进入到真正的工作界面了。

----------------------------------步骤之间的分割线----------------------------------------


      
                                  图8.3.4

       第四步:选择单片机型号。
       在“单片机型号”的下拉菜单中选择“STC89C/LE52RC”这个型号。如果中途弹出推荐选用其它型号的窗口,可以忽略它,我们用来学习只要认准“STC89C/LE52RC”这个型号就可以了。

----------------------------------步骤之间的分割线----------------------------------------

       第五步:设置串口号。
       点击“串口号”右侧的选项,在“串口号”的下拉菜单中,选择跟前面第二步所记录一样的串口号COM3(你们的串口号不一定是COM3,请根据你们的电脑实际显示情况来选择)。


----------------------------------步骤之间的分割线----------------------------------------

      第六步:设置最低波特率和最高波特率。
       “最低波特率”设置为2400,,“最高波特率”设置为9600。波特率如果设置太高,可能会导致烧录(下载)不成功。

----------------------------------步骤之间的分割线----------------------------------------

      第七步:导入.hex格式的机器码文件。
      点击“打开程序文件”的按钮,在弹出的对话框中,选择D盘下“stc89c52rc”文件夹目录下的“stc89c52rc.hex”,双击把“stc89c52rc.hex”导入到上位机用户软件。


----------------------------------步骤之间的分割线----------------------------------------

      
                                 图8.3.8            

      第八步:启动下载。
      点击“下载/编程”的按钮,发现“正在检测目标单片机..”的提示信息,此时需要通过电源波动开关把51学习板重新断电然后再上电才能正常下载,很多人也把这个重新上电的过程称为“冷启动”。之所以要重新断电再上电,是因为单片机内部预置的系统程序只在上电短暂的瞬间才会检查一下是否接收到需要重新烧录程序的指令,如果没有接收到烧录指令,单片机整个话语权就由原来的系统程序转交给我们的用户程序来接管,所以此串口后面的时间就给我们用户程序来使用。因此每次烧录程序时,先启动上位机的下载命令,此时上位机不断发送请求下载的命令给单片机,但是此时单片机并不理会这些指令,因为此时单片机的话语权已经交给了我们的用户程序,此时并不是预置系统程序在掌控,所以除非重新断电然后再上电那一瞬间才会让系统内置程序去检测并且响应此下载命令。另外多说一句,其实不是所有厂家的单片机在烧录程序时都需要“冷启动”,也不是所有单片机都支持串口烧录,各厂家的单片机烧录程序方式会有一些差异,但基本原理是一样的,大同小异。


----------------------------------步骤之间的分割线----------------------------------------

      
                                 图8.3.9

      第九步:“冷启动”后观察是否操作成功的信息。
      执行完前面第九步的“冷启动”后,如果发现有“...操作成功!”的提示信息,就说明程序下载成功了。此时会发现51学习板上面的一颗LED灯不断闪烁,是因为我们的LED灯程序驱动它才开始闪烁的,说明我们的程序在单片机上正常工作了。

       补充说明:
      (1)以后只要每次重新编译了C源代码后,都会生成最新版本的.hex格式文件,所以每次烧录程序时,必须重新返回第七步,重新执行一次导入最新版本.hex格式文件的操作,确保被烧录的程序是最新版本的.hex烧录文件。


【8.4   51学习板下载程序失败时的解决办法。】

      (1)可以先松一下单片机卡座,稍微挪动一下单片机,然后再卡紧单片机。卡座必须卡紧单片机,避免接触不良。
      (2)改变供电电源,很多电脑的USB口供电电源干扰非常大,严重影响下载程序,请把USB电源线插入到手机充电器5V的USB接口,效果显著,明显提高了下载的成功率。
      (3)检查确保所选择的单片机型号是STC89C/LE52RC,如果软件弹出推荐其它型号的单片机窗口,不用管它,我们就选STC89C/LE52RC。
      (4)检查STC-ISP烧写软件是否选择匹配的COM口。
      (5)单片机是靠串口烧录程序进去的,单片机的串口是P3.0,P3.1两根线经过232转换芯片,然后才与USB转串口线连接的。因此,在烧录程序时,请确保P3.0,P3.1两个IO口不能跳线连接到其它外围元器件上。
      (6)点击“下载/编程”后,记得再断电并且重新上电一次。看看是否烧录成功。
      (7)确保最低波特率一直设置为2400,最高波特率为9600。如果还不行再把最高波特率也改成2400试试。
      (8)如果还不行,就退出软件,拔掉USB转串口线,同时断电(必须把整根电源线拔出!),重新插入USB串口线,重新插入电源线开电,重新打开软件。
      (9)如果还不行,学习板先断电(必须把整根电源线拔出!),然后重启一次电脑。
      (10)总之:如果还不行,就按上述步骤多折腾几次。
      (11)最后实在不行,就尝试更换到其它USB口,或者尝试更换到其它电脑上试试。


jianhong_wu 发表于 2016-3-17 07:37:36

本帖最后由 jianhong_wu 于 2016-8-1 10:29 编辑

第九节:本节预留。

          本节预留。

jianhong_wu 发表于 2016-3-19 10:56:33

本帖最后由 jianhong_wu 于 2016-3-19 11:22 编辑

第十节:程序从哪里开始,要到哪里去?


       程序从哪里开始,要到哪里去?为了让初学者了解C语言程序的执行顺序,我把程序分成三个区域:进入主程序前的区域,主程序的初始化区域,主程序的循环区域。当然,这里三个区的分类暂时没有把中断程序的情况考虑进去,中断程序的内容我会在后面相关的章节中再详细介绍,这里暂时不考虑中断。
       进入主程序前的区域。这是上电后,在单片机执行主程序代码之前就已经完成了的工作。包括头文件的包含,宏定义,内存分配这些工作。这部分的内容可以暂时不用去了解,我会在后面的一些章节中陆续深入讲解。
       主程序的初始化区域。这是上电后,单片机进入主程序后马上就要执行的程序代码,这部分区域的代码有一个特点,大家也必须记住的,就是单片机只执行一次。只要单片机不重启,不复位,那么上电后这部分的代码只被执行一次。
       主程序的循环区域。单片机在主程序中执行完了初始化区域的代码,紧接着就进入这片循环区域的代码。单片机一直在逐行循环执行这些代码,执行到末尾时又返回到循环区域的开始处继续开始新一轮的执行,周而复始,往复循环,这就是上电后单片机的最终归宿,一直处在循环的状态。
       下面我跟大家分析一个程序源代码的三个区域和执行顺序,大家先看中文解释部分的内容,暂时不用理解每行指令的语法,有个整体的认识就可以了。此源代码实现的功能是:上电后,蜂鸣器鸣叫一声就停止(初始化区域),然后看到一个LED灯一直在不停的闪烁(循环区域)。


                                       图10.1


源代码如下:

#include "REG52.H"             //进入主程序前的区域:头文件包含

sbit beep_dr=P3^4;            //进入主程序前的区域:宏定义
sbit led_dr=P1^6;               //进入主程序前的区域:宏定义

unsigned long i;                  //进入主程序前的区域:内存分配

void main()                        //主程序入口,即将进入初始化区域
{
   beep_dr=0;                     //第一步:初始化区域:蜂鸣器开始鸣叫。
   for(i=0;i<6250;i++);       //第二步:初始化区域:延时0.5秒左右。也就是蜂鸣器鸣叫的持续时间。
   beep_dr=1;                     //第三步:初始化区域:蜂鸣器停止鸣叫。
   while(1)                           //执行完上面的初始化区域,即将进入循环区域
   {
       led_dr=0;                  //第四步:循环区域:LED开始点亮。
       for(i=0;i<6250;i++);   //第五步:循环区域:延时0.5秒左右。也就是LED点亮的持续时间。
       led_dr=1;                  //第六步:循环区域:LED开始熄灭。
       for(i=0;i<6250;i++);   //第七步:循环区域:延时0.5秒左右。也就是LED熄灭的持续时间。
   }                                    //执行完上面第七步后,单片机又马上返回到上面第四步继续往下执行。
}

      上述代码执行顺序分析:
      单片机进入主程序后,从第一步到第三步是属于初始化区域,只被执行一次。然后进入循环区域,从第四步执行到第七步,执行完第七步之后,马上又返回上面第四步继续循环往下执行,单片机一直处于第四步到第七步的往复循环中。可以很清晰的看到,上面的main和while(1)关键词就是三个区域的边界分割线。   
      经过以上的分析,可以看出这三个区域的大概分布如下:
//...进入主程序前的区域
void main()               
{
   //...初始化区域
   while(1)                     
   {
       //...循环区域
   }
}






jianhong_wu 发表于 2016-3-27 22:55:07

本帖最后由 jianhong_wu 于 2018-12-16 12:42 编辑

第十一节:一个在单片机上练习C语言的模板程序。


【11.1   一套完整的模板源代码。】

       先给大家附上一套完整的模板源代码,后面章节练习C语言的模板程序就直接复制此完整的源代码,此源代码适合的单片机型号是STC89C52RC,晶振是11.0592MHz,串口波特率是9600,初学者只需修改代码里从“C语言学习区域的开始”到“C语言学习区域的结束”的区域,其它部分不要更改。可复制的源代码请到网上论坛原贴处复制或者下载,搜索本教程名字就可以找到原贴出处。一套完整的模板源代码如下:
#include "REG52.H"
void View(unsigned long u32ViewData);
void to_BufferData(unsigned long u32Data,unsigned char *pu8Buffer,unsigned char u8Type);
void SendString(unsigned char *pu8String);   

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   unsigned char a; //定义一个变量a。
   unsigned intb; //定义一个变量b。
   unsigned long c; //定义一个变量c。
   a=100;          //给变量a赋值。
   b=10000;      //给变量b赋值。
   c=1000000000;   //给变量c赋值。
   View(a);   //在电脑串口端查看第1个数a。
   View(b);   //在电脑串口端查看第2个数b。
   View(c);   //在电脑串口端查看第3个数c。
   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

void View(unsigned long u32ViewData)
{
    static unsigned char Su8ViewBuffer;
            code unsigned char Cu8_0D_0A[]={0x0d,0x0a,0x00};
         code unsigned char Cu8Start[]={"开始..."};
    static unsigned char Su8FirstFlag=0;
    static unsigned int Su16FirstDelay;
    if(0==Su8FirstFlag)
            {
         Su8FirstFlag=1;
         for(Su16FirstDelay=0;Su16FirstDelay<10000;Su16FirstDelay++);
         SendString(Cu8Start);
         SendString(Cu8_0D_0A);
         SendString(Cu8_0D_0A);
   }
    to_BufferData(u32ViewData,Su8ViewBuffer,1);
          SendString(Su8ViewBuffer);
    to_BufferData(u32ViewData,Su8ViewBuffer,2);
         SendString(Su8ViewBuffer);
    to_BufferData(u32ViewData,Su8ViewBuffer,3);
         SendString(Su8ViewBuffer);
    to_BufferData(u32ViewData,Su8ViewBuffer,4);
            SendString(Su8ViewBuffer);
         SendString(Cu8_0D_0A);
}
void to_BufferData(unsigned long u32Data,unsigned char *pu8Buffer,unsigned char u8Type)
{
      code unsigned char Cu8Array1[]={0xB5,0xDA,0x4E,0xB8,0xF6,0xCA,0xFD,0x00};
         code unsigned char Cu8Array2[]="十进制:";
            code unsigned char Cu8Array3[]="十六进制:";
         code unsigned char Cu8Array4[]="二进制:";
    static unsigned char Su8SerialNumber=1;
    static unsigned intSu16BufferCnt;
    static unsigned intSu16TempCnt;
    static unsigned intSu16TempSet;
    static unsigned long Su32Temp1;
    static unsigned long Su32Temp2;
    static unsigned long Su32Temp3;
    static unsigned char Su8ViewFlag;
    if(1==u8Type)
          {
      for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                   pu8Buffer=Cu8Array1;
            }
                pu8Buffer=Su8SerialNumber+'0';
               pu8Buffer=0x0d;
               pu8Buffer=0x0a;
                pu8Buffer=0;
             Su8SerialNumber++;
                return;
         }
    else if(2==u8Type)
            {
                for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                     pu8Buffer=Cu8Array2;
                }
               Su32Temp1=1000000000;
                   Su32Temp2=10;
                   Su16TempSet=10;
          }
    else if(3==u8Type)
         {
                for(Su16BufferCnt=0;Su16BufferCnt<9;Su16BufferCnt++)
                {
                     pu8Buffer=Cu8Array3;
                }
                Su32Temp1=0x10000000;
                  Su32Temp2=0x00000010;
                  Su16TempSet=8;      
    }
    else
    {
                for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                     pu8Buffer=Cu8Array4;
                }
                Su32Temp1=0x80000000;
                  Su32Temp2=0x00000002;
                  Su16TempSet=32;      
         }
    Su8ViewFlag=0;
    for(Su16TempCnt=0;Su16TempCnt<Su16TempSet;Su16TempCnt++)
    {
       Su32Temp3=u32Data/Su32Temp1%Su32Temp2;
               if(Su32Temp3<10)
               {
               pu8Buffer=Su32Temp3+'0';
               }
               else
               {
               pu8Buffer=Su32Temp3-10+'A';
               }
               if(0==u32Data)
               {
                     Su16BufferCnt++;
                     break;
               }
               else if(0==Su8ViewFlag)
               {
                   if('0'!=pu8Buffer)
                {
               Su8ViewFlag=1;
                               Su16BufferCnt++;
         }
               }
               else
               {
                   Su16BufferCnt++;
               }

       Su32Temp1=Su32Temp1/Su32Temp2;
    }
    pu8Buffer=0x0d;
    pu8Buffer=0x0a;
    pu8Buffer=0;
}
void SendString(unsigned char *pu8String)   
{
static unsigned int Su16SendCnt;
static unsigned int Su16Delay;
SCON=0x50;
TMOD=0X21;
TH1=TL1=256-(11059200L/12/32/9600);
TR1=1;
ES = 0;
TI = 0;
for(Su16SendCnt=0;Su16SendCnt<43;Su16SendCnt++)
{
   if(0==pu8String)
             {
               break;
             }
             else
             {
      SBUF =pu8String;
      for(Su16Delay=0;Su16Delay<800;Su16Delay++);
                   TI = 0;
             }
}
}



【11.2   模板程序的使用说明。】

      
                                 图11.2.1

       大多数初学者在学习C语言的时候,往往是在电脑端安装上VC平台软件来练习C语言,这种方法只要在代码里调用printf语句,编译后就可以看到被printf语句调用的变量,挺方便的。本教程没有用这种方法,既然本教程的C语言主要针对单片机,所以我想出了另外一种方法,这种方法就是直接在单片机上练习C语言,这样会让初学者体验更深刻。这种方法对硬件平台要求不高,只要51学习板上有一个9针的串口就可以,这个串口既可以用来烧录程序,也可以用来观察代码里的某个变量,只要在代码里调用View函数就可以达到类似VC平台软件下printf语句的效果,View函数可以向串口输出某个变量的十进制,十六进制和二进制,大家只要在电脑端的串口助手软件就可以看到某个变量的这些信息,View函数能查看的变量最大数值范围是4个字节的unsigned long变量,十进制的范围是从0到4294967295,也可以查看unsigned int 和unsigned char的类型变量(数据的进制以及long,int,char等知识点大家目前还没接触到,因此不懂也没关系,当前只要有个大概的认识就可以,暂时不用深入理解,后面章节还会详细介绍)。View函数是我整个模板程序的其中一部分,所以要用这种方法就必须先复制我整个模板程序,初学者练习代码的活动范围仅仅局限于模板程序里的“C语言学习区域”,在此区域里有一个main主函数,main主函数内有一个初始化区域,初学者往往在这个初始化区域里练习C语言就够了,初学者最大的活动范围不能超过从“C语言学习区域的开始”到“C语言学习区域的结束”这个范围,这个范围之外其它部分的代码主要用来实现数据处理和串口发送的功能,大家暂时不用读懂它,直接复制过来就可以了。比如:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   //...初始化区域,也就是主要用来给初学者学习C语言的区域。
   while(1)
   {

   }

}

/*---C语言学习区域的结束。-----------------------------------------------*/

       上述例子中,初学者练习代码只能在从“C语言学习区域的开始”到“C语言学习区域的结束”这个范围,此范围外的代码直接复制过来不要更改。我们再来分析分析下面节选的main函数源代码:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   unsigned char a; //定义一个变量a。
   unsigned intb; //定义一个变量b。
   unsigned long c; //定义一个变量c。
   a=100;          //给变量a赋值。
   b=10000;      //给变量b赋值。
   c=1000000000;   //给变量c赋值。
   View(a);   //在电脑串口端查看第1个数a。
   View(b);   //在电脑串口端查看第2个数b。
   View(c);   //在电脑串口端查看第3个数c。
   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      上述节选的main函数代码里,比如“a=100;          //给变量a赋值。”这行代码,所谓的“赋值”就是“=”这个语句,它表面上像我们平时用的等于号,实际上不是等于号,而是代表“给”的意思,把“=”符号右边的数复制一份给左边的变量,比如“a=100;”就是代表把100这个数值复制一份给变量a,执行这条指令后,a就等于100了。这里的分号“;”代表一条程序指令的结束。 而双斜线“//”是注释语句,双斜线“//”这行后面的文字或字符都是用来注释用的,编译器会忽略双斜线“//”这一行后面的文字或字符,编译器不把注释文字或字符列入源代码,也就是“//”这一行中后面的文字或字符是不占单片机内存的。当然“//”仅仅局限于当前一行代码。上面除了“//”是注释语句外,上面的“/*”和“*/”之间也是注释语句,跟”//”的作用一样,只不过“/*”是注释开始,“*/”是注释结束,它们的范围不局限于一行,而是从“/*”到“*/”的范围,因此可以用于注释连着的多行文字或者字符。
      接着在分析上述代码中最重要的函数,也是本节最核心最重要的函数View(某个变量)。比如“ View(a); ”这行代码,View(a)就是要把变量a的十进制,十六进制和二进制的数值都发送到串口,我们通过USB转串口线让学习板连接上电脑,在电脑串口助手软件上就能看到被View函数调用的变量a的信息。

【11.3   如何在电脑上使用串口助手软件查看被View函数调用的变量?】

      前面章节在讲烧录程序时提到一个叫“stc-isp-15xx-v6.85I”的上位机软件,这个软件除了用来烧录程序,还集成了串口助手软件的功能。所以本节直接共用烧录程序时的USB转串口线和“stc-isp-15xx-v6.85I”软件就可以了,无需额外再购买新的USB转串口线和下载其它串口助手软件,但是如何设置这个“stc-isp-15xx-v6.85I”上位机软件,还是有一些需要特别注意的地方的,现在把这个详细的步骤介绍给大家。

      第一步:设置烧录软件的选项。
      按前面章节介绍烧录程序时所需的步骤,用USB转串口线连接51学习板和电脑,记录COM号,打开“stc-isp-15xx-v6.85I”软件,选择单片机型号,选择对应的串口号(COM号),设置最低波特率和最高波特率,这部分的内容跟烧录程序时的配置步骤是一样的,唯一必须要特别注意的是最高波特率必须选择9600!最低波特率建议选择2400。否则在烧录完程序后,当上位机集成软件自动切换到串口助手软件窗口时,接收区域显示的一些汉字信息可能会出现乱码。

----------------------------------步骤之间的分割线----------------------------------------

      
                                 图11.3.2

      第二步:设置串口助手软件的选项。
      先点击右上方选中“串口助手”选项切换到串口助手的窗口,接收缓冲区选择“文本模式”,串口选择匹配的COM号(跟烧录软件一致的COM号),波特率必须选择9600,勾选上“编程完成后自动打开串口”选项,最后点击“打开串口”按钮使之切换到显示“关闭串口”的文字状态,至此串口助手软件的设置完毕。接下来就是按烧录程序的流程,打开新的HEX程序文件,程序烧录完成后上位机软件会自动切换到串口助手的串口,就可以观察到View函数从单片机上发送过来的某个变量的十进制,十六进制,二进制的信息了。接收缓冲区的窗口比较小,如果收到的信息比较多,只要在上下方向拖动窗口右边的滑块就可以依次看到全部的信息。如果想让单片机重新发送数据,只要让51学习板断电重启就可以重发一次数据,当串口助手的接收区接收的信息太多影响观察时,大家可以点击“清空接收区”的按钮来清屏,然后断电重启让它再重发一次数据。在电脑的串口助手软件里观察到的数据格式大概是什么样子的呢?比如编译完本章节上述完整的模板源代码程序后,会在串口助手软件里看到a,b,c三个变量的信息如下:

开始...

第1个数
十进制:100
十六进制:64
二进制:1100100

第2个数
十进制:10000
十六进制:2710
二进制:10011100010000

第3个数
十进制:1000000000
十六进制:3B9ACA00
二进制:111011100110101100101000000000

      多说一句,烧录程序后,当软件自动切换到串口助手软件选项的窗口时,串口助手窗口显示单片机返回的信息,这时有可能第一行的文字“开始...”会丢失或者显示不出来,但是后面其它的关键信息不受影响,我猜测可能是串口助手软件本身的某个环节存在的小bug,跟我们没关系,我们不用深究原因,因为不会影响我们的使用。此时如果让单片机断电重启就可以看到第一行的文字“开始...”。

【11.4   如何利用现有的工程编辑编译新的源代码?】

      本教程后面有很多章节的源代码,是不是每个章节都要重新建一个工程?其实不用。我们只要用一个工程就可以编译编辑本教程所有章节的源代码。方法很简单,就是打开一个现有的工程,用快捷组合键“Ctrl+A”把原工程里面的C源代码全部选中,再按“Backspace”清空原来的代码,然后再复制本教程相关章节的代码粘贴到工程的C文档里,重新编译一次就可以得到对应的Hex格式的烧录文件。用这种方法的时候,建议大家做好每个程序代码的备份。每完成一个项目的小进度,都要及时把源代码存储到电脑硬盘里,电脑硬盘里每个项目对应一个项目文件夹,每个项目文件夹里包含很多不同版本编号的源代码文件,每个源代码文件名都有流水编号,方便识别最新版本的程序,每天下班前都要把最新版本的源代码文件上传到自己的网盘里备份,在互联网时代,把源代码存到自己的网盘,可以随时异地存取,即使遇到电脑故障损坏也不担心数据永久丢失。

【11.5   编辑源代码的5个常用快捷键。】

      介绍一下常用的快捷键,好好利用这5个快捷键,会让你在编辑源代码时效率明显提高。
(1)选中整篇所有的内容:组合键Ctrl+A。
(2)把选中的内容复制到临时剪贴板:组合键Ctrl+C。
(3)把临时剪贴板的内容粘贴到光标开始处:组合键Ctrl+V。
(4)把选中的一行或者几行内容整体往右边移动:单键Tab。每按一次就移动几个空格,很实用。
(5)把选中的一行或者几行内容整体往左边移动:组合键Shift+Tab。每按一次就移动几个空格,很实用。






jianhong_wu 发表于 2016-4-3 10:21:03

本帖最后由 jianhong_wu 于 2016-4-3 10:52 编辑

第十二节:变量的定义和赋值。
【12.1   学习C语言的建议和方法。】
       先提一些学C语言的建议和方法,帮大家删繁就简,去掉一些初学者常见的思想包袱。现阶段我们的学习是使用单片机,把单片机当做一个成品,把单片机当做一个忠诚的士兵,学习C语言就是学习如何使用单片机,如何命令单片机,如何让单片机听懂我们的话并且听我们指挥。单片机内部太细节的构造原理暂时不用过多去关注,只要知道跟我们使用相关的几个特征就可以,这样初学者的学习包袱就没有那么重,就可以把重点放在使用上的,而不是好奇于根本原理的死磕到底。学C语言跟学习英语的性质是一样的,都是在学习一门外语,只是C语言比英语的语法要简单很多,非常容易上手,词汇量也没有英语那么多,C语言常用单词才几十个而已。学习任何一门语言的秘诀在于练习,学习C语言的秘诀是多在单片机上练习编程。本教程后面几乎每个章节都有例程,这个例程很重要,初学者即使看懂了,我也强烈建议要把“C语言学习区域”的那部分代码亲自上机敲键盘练习一遍,并且看看实验现象是否如你所愿。
【12.2   变量定义和赋值的感性认识。】
       这些年我用过很多单片机,比如51,PIC,LPC17系列,STM8,STM32等单片机。尽管各类单片机有一些差异,但是在选单片机时有3个参数我们一定会关注的,它们分别是:工作频率,数据存储器RAM,程序存储器ROM。工作频率跟晶振和倍频有关,决定了每条指令所要损耗的时间,从而决定了运算速度。RAM跟代码里所定义变量的数量有关。ROM跟程序代码量的大小有关。程序是什么?程序就是由对象和行为两者构成的。对象就是变量,就是变量的定义,就是RAM,RAM的大小决定了一个程序允许的对象数量。行为就是赋值,判断,跳转,运算等语法,就是ROM,ROM的大小决定了一个程序允许的行为程度。本节的标题是“变量的定义和赋值”,其中“定义”就是对象,“赋值”就是行为。
【12.3   变量的定义。】
       变量的定义。一个程序最大允许有多少个对象,是由RAM的字节数决定的(字节是一种单位,后面章节会讲到)。本教程的编译环境是以AT89C52芯片为准,AT89C52这个单片机有256个字节的RAM,但是并不意味着程序就一定要全部占用这些RAM。程序需要占用多少RAM,完全是根据程序的实际情况来决定,需要多少就申请多少。这里的“对象”就是变量,这里的“申请”就是变量的定义。       定义变量的关键字。常用有3种容量的变量,每种变量的取值范围不一样。第一种是”unsigned char”变量,取值范围从0到255,占用RAM一个字节,比喻成一房一厅。第二种是”unsigned int”变量,取值范围从0到65535,占用RAM两个字节,比喻成两房一厅。第三种是“unsigned long”变量,取值范围从0到4294967295,占用RAM四个字节,比喻成四房一厅。unsigned char,unsigned int和unsigned long都是定义变量的关键字,所谓关键字也可以看成是某门外语的单词,需要大家记忆的,当然不用死记硬背,只要多上机练习就自然熟记于心,出口成章。多说一句,上述的变量范围是针对本教程所用的单片机,当针对不同的单片机时上述变量的范围可能会有一些小差异,比如在stm32单片机中,unsigned int的字节数就不是两个字节,而是四个字节,这些都是由所选的编译器决定的,大家暂时有个初步了解就可以。       定义变量的语法格式。定义变量的语法格式由3部分组成:关键字,变量名,分号。比如:       unsigned char a;
       其中unsigned char就是关键字,a就是变量名,分号”;”就是一条语句的结束符号。       变量名的命名规则。变量名的第一个字符不能是数字,必须是字母或者下划线,字母或者下划线后面可以带数字,一个变量名之间的字符不能带空格,两个独立变量名之间也不能用空格隔开(但是两个独立变量名之间可以用逗号隔开)。变量名不能跟编译器已征用的关键字重名,不能跟函数名重名,这个现象跟古代要求臣民避讳皇帝的名字有点像。哪些名字是合法的,哪些名字是不合法的?现在举一些例子说明:      unsigned char 3a; //不合法,第一个字符不能是数字。
      unsigned char char; //不合法,char是编译器已征用的关键字。
      unsigned char a b; //不合法,ab是一个变量名,a与b的中间不能有空格。
      unsigned char a,b; //合法,a和b分别是一个独立的变量名,a与b的中间可以用逗号隔开。
      unsigned char a; //合法。
      unsigned char abc; //合法。
      unsigned char _ab; //合法。
      unsigned char _3ab; //合法。
      unsigned char a123; //合法。
      unsigned char a12ced; //合法。
      定义变量与RAM的内在关系。当我们定义一个变量时,相当于向单片机申请了一个RAM空间。C编译器会自动为这个变量名分配一个RAM空间,每个字节的RAM空间都有一个固定唯一的地址。把每个字节的RAM空间比喻成房间,这个地址就是房号。地址是纯数字编号,不利于我们记忆,C语言编译器为了降低我们的工作难度,不用我们记每个变量的地址,只需要记住这个变量的名称就可以了。操作某个变量名,就相当于操作某个对应地址的RAM空间。变量名与对应地址RAM空间的映射关系是C编译器暗中悄悄帮我们分配好的。比如:      unsigned char a;//a占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
      unsigned char b;//b占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
      unsigned char c;//c占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
      上述a,b,c三个变量各自占用一个字节的RAM空间,同时被C编译器分配了3个不同的RAM空间地址。      变量定义的初始化。变量定义之后,等于被C编译器分配了一个RAM空间,那么这个空间里面存储的数据是什么?如果没有刻意给它初始化,RAM空间里面存储的数据是不太确定的,是默认的。有些场合,需要在给变量分配RAM空间时就给它一个固定的初始值,这就是变量定义的初始化。变量初始化的语法格式由3部分组成:关键字,变量名赋值,分号。比如:      unsigned char a=9;
      其中unsigned char就是关键字。      其中a=9就是变量名赋值。a从被C编译器分配RAM空间那一刻起,就默认是预存了一个9的数据。      分号“;”就是一条语句的结束符号。
【12.4   变量的赋值。】
       赋值语句的含义。把右边对象的内容复制一份给左边对象。赋值语句有一个很重要的特性,就是覆盖性,左边对象原来的内容会被右边对象复制过来的新内容所覆盖。比如,左边对象是变量a,假设原来a里面存的数据是3,右边对象是数据6,执行赋值语句后,会把右边的6赋值给了对象a,那么a原来的数据3就被覆盖丢失了,变成了6。       赋值语句的格式。赋值语句的语法格式由4部分组成:左边对象,关键字,右边对象,分号。比如:       a=b;
       其中a就是左边对象。       其中“=”就是关键字。写法跟我们平时用的等于号是一样,但是在C语言里不是等于的意思,而是代表赋值的意思,它是代表中文含义的“给”,而不是用于判断的“等于”,跟等于号是两码事(C语言的等于号是“==”,这个后面章节会讲到)。       其中b就是右边对象。       其中分号“;”代表一条语句的结束符。       赋值语句与ROM的关系。赋值语句是行为的一种,所以编译会把赋值这个行为翻译成对应的指令,这些指令在下载程序时最终也是以数据的形式存储在ROM里,指令也是以字节为单位(字节是一种单位,后面章节会讲到)。本教程的编译环境是以AT89C52芯片为准,AT89C52这个单片机有8K的ROM容量,也就是有8192个字节的ROM(8乘以1024等于8192),但是并不意味着程序就一定要全部占用这些ROM。程序需要占用多少ROM,完全是根据程序的行为程度决定,也就是通常所说的你的程序容量有多大,有多少行代码。多说一句,在单片机或者我们常说的计算机领域里,存储容量是以字节为单位,而每K之间的进制不是我们日常所用的1000,而是1024,所以刚才所说的8K不是8000,而是8192,这个是初学者很容易迷惑的地方。刚才提到,赋值语句是行为,凡是程序的行为指令都存储在单片机的ROM区。C编译器会把一条赋值语句翻译成对应的一条或者几条机器码,机器码指令也是以字节为单位的。下载程序的时候,这些机器码就会被下载进单片机的ROM区。比如以下这行赋值语句:       unsigned char a;
       unsigned char b=3;
       a=b;
       经过C编译器编译后会生成以字节为单位的机器码。这些机器码记录着这些信息:变量a的RAM地址,变量b的RAM地址和初始化时的预存数据3,以及把b变量的内容赋值给a变量的这个行为。所有这些信息,不管是“数据”还是“行为”,本质都是以“数据”(或称数字,数码都可以)的形式存储记录的,单位是字节。
【12.5   例程的分析和练习。】
       接下来练习一个程序实例。直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。本章节在“C语言学习区域”练习的代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a;//定义的变量a被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char b;   //定义的变量b被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char c;   //定义的变量c被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char d=9; //定义的变量d被分配了一个字节的RAM空间,保存的数据被初始化成9.

       b=3;//把3赋值给变量b,b由原来不确定的默认数据变成了3。
       c=b;//把变量b的内容复制一份赋值给左边的变量c,c从不确定的默认值变成了3。
   View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
   View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
   View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/


       在电脑串口助手软件上观察到的程序执行现象如下:
开始...
第1个数
十进制:255
十六进制:FF
二进制:11111111

第2个数
十进制:3
十六进制:3
二进制:11

第3个数
十进制:3
十六进制:3
二进制:11

第4个数
十进制:9
十六进制:9
二进制:1001


分析:      第1个数a居然是255,这个255从哪来?因为a我们一直没有给它初始值,也没有给它赋值,所以它是不确定的默认值,这个255就是所谓的不确定的默认值,是编译器在定义变量a时分配的,带有不确定的随机性,不同的编译器可能分配的默认值都会存在差异。根据我的经验,unsigned char类型定义的默认值往往是0或者255(255是十六进制的0xff,十六进制的内容后续章节会讲到)。

jianhong_wu 发表于 2016-4-10 10:21:51

本帖最后由 jianhong_wu 于 2016-4-10 10:45 编辑

第十三节:赋值语句的覆盖性。


【13.1   什么是赋值语句的覆盖性?】

      a=b;
       上述代码,执行完这条赋值语句后,会把右边变量b的数值复制一份给左边变量a,a获得了跟b一样的数值,但是a原来自己的数值却丢失了,为什么会丢失?就是因为被b复制过来的新数据给覆盖了,这就是赋值语句的覆盖性。

【13.2   例程的分析和练习。】

       既然赋值语句有覆盖性的特点,那么如何让两个变量相互交换数值?假设a原来的数据是1,b原来的数据是5,交换数据后,a的数据应该变为5,b的数据应该变为1,怎么做?很多初学者刚看到这么简单的题目,会马上根据日常生活的思路,你把你的东西给我,我把我的东西给你,就两个步骤而已,看似很简单,现在按这个思路编写一段程序看看会出什么问题,代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a=1;   //定义的变量a被分配了1个字节的RAM空间,保存的数据被初始化成1。
       unsigned char b=5;   //定义的变量b被分配了1个字节的RAM空间,保存的数据被初始化成5。

       b=a; //第一步:为了交换,先把a的数赋值给b。
       a=b; //第二步:为了交换,再把b的数赋值给a。

       View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:1
十六进制:1
二进制:1

第2个数
十进制:1
十六进制:1
二进制:1
分析:      
         第1个数a和第2个数b居然都是1!这不是我们想要的结果。我们要的交换结果是:交换后,a变为5,b变为1。在哪个环节出了问题?把镜头切换到上述代码的“第一步”和“第二步”,由于b的数据在执行完“第一步”后,b自己原来的数据5被覆盖丢失了变成新的数据1,接着执行“第二步”后,此时相当于把 b的新数据1赋值给a,并没有5!所以a和b的数据都是1,不能达到交换后“a为5,b为1”的目的。其实就是赋值语句的覆盖性在作祟。


       上述交换数据的程序宣告失败!怎么办?既然赋值语句具有覆盖性,那么两变量想交换数据,就必须借助第三方变量来寄存,此时只需要多定义一个第三方变量t。正确的代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a=1;   //定义的变量a被分配了1个字节的RAM空间,保存的数据被初始化成1。
       unsigned char b=5;   //定义的变量b被分配了1个字节的RAM空间,保存的数据被初始化成5。
       unsigned char t;   //定义一个第三方变量t,用来临时寄存数值。

       t=b; //第一步:为了避免b的数据在赋值后被覆盖丢失,先寄存一份在第三方变量t那里。
       b=a; //第二步:把a的数赋值给b,b原来的数据虽然丢失,但是b在t变量那里有备份。
       a=t; //第三步:再把b在t变量里的备份赋值给a。注意,这里不能用b,因b原数据已被覆盖。

       View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

      在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:5
十六进制:5
二进制:101

第2个数
十进制:1
十六进制:1
二进制:1
分析:      
      实验结果显示,两变量的数值交换成功。

【13.3   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-4-17 09:19:06

本帖最后由 jianhong_wu 于 2016-4-17 09:43 编辑

第十四节:二进制与字节单位,以及常用三种变量的取值范围。


【14.1   为什么要二进制?】

       为什么要二进制?我们日常生活明明是十进制的,为何数字电子领域偏要选择二进制?这是由数字硬件电路决定的。人有十个手指头,人可以直接发出十种不同声音来命名0,1,2,3...9这些数字,人可以直接用眼睛识别出十种不同状态的信息,但是数字底层基础硬件电路要直接处理和识别十种状态却很难,相对来说,处理和识别两种状态就轻松多了,所以选择二进制。比如,一颗LED灯的亮或灭,一个IO口的输出高电平或低电平,识别某一个点的电压是高电平或低电平,只需要三极管等基础元器件就可把硬件处理电路搭建起来,二进制广泛应用在数字电路的存储,通讯和运算等领域,想学好单片机就必须掌握它。

【14.2   二进制如何表示成千上万的大数值?】

       二进制如何表示成千上万的数值?现在用LED灯的亮和灭来跟大家讲解。

   (1)1个LED灯:
      灭   第0种状态
      亮   第1种状态
      合计:共2种状态。

    (2)2个LED灯挨着:
      灭灭   第0种状态
      灭亮   第1种状态
      亮灭   第2种状态
      亮亮   第3种状态
      合计:共4种状态。

   (3)3个LED灯挨着:
      灭灭灭   第0种状态
      灭灭亮   第1种状态
      灭亮灭   第2种状态
      灭亮亮   第3种状态
      亮灭灭   第4种状态
      亮灭亮   第5种状态
      亮亮灭   第6种状态
      亮亮亮   第7种状态
      合计:共8种状态。

   (4)8个LED灯挨着:
      灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭亮   第1种状态
      ......                         第N种状态
      亮亮亮亮亮亮亮灭   第254种状态
      亮亮亮亮亮亮亮亮   第255种状态
      合计:共256种状态。

    (5)16个LED灯挨着:
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   第1种状态
      ......                                                   第N种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   第65534种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   第65535种状态
      合计:共65536种状态。

    (6)32个LED灯挨着:
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   第1种状态
      ......                                                               第N种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   第4294967294种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   第4294967295种状态
      合计:共4294967296种状态。

结论:
       连续挨着的LED灯越多,能表达的数值范围就越大。

【14.3   什么是位?】

       什么是位?以上一个LED灯就代表1位,8个LED灯就代表8位。位的英文名是用bit来表示。一个变量的位数越大就意味着这个变量的取值范围越大。一个单片机的位数越大,就说明这个单片机一次处理的数据范围就越大,意味着运算和处理速度就越快。我们日常所说的8位单片机,32位单片机,就是这个位的概念。为什么32位的单片机比8位单片机的处理和运算能力强,就是这个原因。

【14.4   什么是字节?】

       什么是字节?字节是计算机很重要的一个基本单位,一个字节有8位。8个LED灯挨着能代表多少种状态,就意味着一个字节的数据范围有多大。从上面举的例子中,我们知道8个LED灯挨着,能表示从0到255种状态,所以一个字节的取值范围就是从0到255。

【14.5   三种常用变量的取值范围是什么?】

       前面章节曾提到三种常用的变量:unsigned char,unsigned int ,unsigned long。现在有了二进制和字节的基础知识,就可以跟大家讲讲这三种变量的取值范围,而且很重要,这是我们写单片机程序必备的概念。
       unsigned char的变量占用1个字节RAM,共8位,根据前面LED灯的例子,取值范围是从0到255。
       unsigned int的变量占用2个字节RAM,共16位,根据前面LED灯的例子,取值范围是从0到65535。多说一句,对于51内核的单片机,unsigned int的变量是占用2个字节。如果是在32位的stm32单片机,unsigned int的变量是占用4个字节的,所以不同的单片机不同的编译器是会有一些差异的。
       unsigned long的变量占用4个字节RAM,共32位,根据前面LED灯的例子,取值范围是从0到4294967295。

【14.6   例程练习和分析。】

       现在我们编写一个程序来验证unsigned char,unsigned int,unsigned long的取值范围。
       定义两个unsigned char变量a和b,a赋值255,b赋值256,255和256恰好处于unsigned char的取值边界。
       再定义两个unsigned int变量c和d,c赋值65535,d赋值65536,65535和65536恰好处于unsigned int的取值边界。
       最后定义两个unsigned long变量e和f,e赋值4294967295,f赋值4294967296,4294967295和4294967296恰好处于unsigned long的取值边界。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
      unsigned char a;   //定义一个变量a,并且分配了1个字节的RAM空间。
      unsigned char b;   //定义一个变量b,并且分配了1个字节的RAM空间。
      unsigned int c;    //定义一个变量c,并且分配了2个字节的RAM空间。
      unsigned int d;    //定义一个变量d,并且分配了2个字节的RAM空间。
      unsigned long e;   //定义一个变量e,并且分配了4个字节的RAM空间。
      unsigned long f;   //定义一个变量f,并且分配了4个字节的RAM空间。

      a=255;         //把255赋值给变量a,a此时会是什么数?会超范围溢出吗?
      b=256;         //把256赋值给变量b,b此时会是什么数?会超范围溢出吗?
      c=65535;       //把65535赋值给变量c,c此时会是什么数?会超范围溢出吗?
      d=65536;       //把65536赋值给变量d,d此时会是什么数?会超范围溢出吗?
      e=4294967295;//把4294967295赋值给变量e,e此时会是什么数?会超范围溢出吗?
      f=4294967296;//把4294967296赋值给变量f,f此时会是什么数?会超范围溢出吗?

      View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
      View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
      View(e);   //把第5个数e发送到电脑端的串口助手软件上观察。
    View(f);   //把第6个数f发送到电脑端的串口助手软件上观察。

    while(1)
    {
    }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:255
十六进制:FF
二进制:11111111

第2个数
十进制:0
十六进制:0
二进制:0

第3个数
十进制:65535
十六进制:FFFF
二进制:1111111111111111

第4个数
十进制:0
十六进制:0
二进制:0

第5个数
十进制:4294967295
十六进制:FFFFFFFF
二进制:11111111111111111111111111111111

第6个数
十进制:0
十六进制:0
二进制:0

分析:      
      通过实验结果,我们知道unsigned char变量最大能取值到255,如果非要赋值256就会超出范围溢出后变成了0。unsigned int变量最大能取值到65535,如果非要赋值65536就会超出范围溢出后变成了0。unsigned long变量最大能取值到4294967295,如果非要赋值4294967296就会超出范围溢出后变成了0。

【14.7   如何在单片机上练习本章节C语言程序?】

      直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


转眼十年未谋面 发表于 2016-4-20 16:26:03

鸿哥你好,我是当初在群里跟你因为一些技术问题有过争执的“转眼十年未谋面”。。。今日偶然看见你这个帖子,对你的奉献精神感到十分钦佩和仰慕。。。。分享和开源也一直是我的技术信条,愿与君共勉。。:lol:lol

jianhong_wu 发表于 2016-4-23 10:31:14

本帖最后由 jianhong_wu 于 2016-4-23 10:54 编辑

第十五节:二进制与十六进制。


【15.1   十六进制是二进制的缩写。】

       在我的印象中,C51编译器好像并不支持二进制的书写格式,即使它能支持二进制的书写格式,二进制的书写还是有个弊端,就是数字太多太长了,写起来非常费劲不方便,怎么办?解决办法就是用十六进制。十六进制是二进制的缩写,之所以称它为二进制的缩写,是因为它们的转换关系非常简单直观,不需要借助计算器即可相互转换。

【15.2   何谓十六进制?】

       何谓十六进制?欲搞清楚这个问题,还得先从十进制说起。所谓十进制,就是用一位字符可以表示从0到9这十个数字。所谓二进制,就是用一位字符可以表示从0到1这二个数字。所谓十六进制,当然也就是用一位字符可以表示从0到15这十六个数字。但是十六进制马上就会面临一个问题,十六进制的10到15这6个数其实是有两位字符组成的,并不是一位呀?于是C语言用这些字符A,B,C,D,E,F分别替代10,11,12,13,14,15这6个数,10前面的0到9还是跟十进制的字符一致。A,B,C,D,E,F也可以用小写a,b,c,d,e,f来替代,在数值上不区分大小写,比如十六进制的a与A都是表示十进制的10。

【15.3   二进制与十六进制是如何转换的?】

       前面提到了十六进制是二进制的缩写,它们的转换关系非常简单直观,每1位十六进制的字符,对应4位二进制的字符。关系如下:

    十进制       二进制      十六进制
    0            0000      0
    1            0001      1
    2            0010      2
    3            0011      3
    4            0100      4
    5            0101      5
    6            0110      6
    7            0111      7
    8            1000      8
    9            1001      9
    10         1010      A
    11         1011      B
    12         1100      C
    13         1101      D
    14         1110      E
    15         1111      F

       二进制转换成十六进制的时候,如果不是4位的倍数,则最左边高位默认补上0凑合成4位的倍数。比如一个二进制的数101001,可以在左边补上2个0变成00101001,然后把每4位字符转成1个十六进制的字符。左边高4位0010对应十六进制的2,右边低4位1001对应十六进制的9,所以二进制的101001合起来最终转换成十六进制的数是29(实际上正确的写法是0x29,为什么?请继续往下看。)。

【15.4   十六进制数的标准书写格式是什么样子的?】

       十六进制的标准书写格式是什么样子的?实际上,十六进制29并不能直接写成29,否则就跟十进制的写法混淆了。为了把十六进制和十进制的书写格式进行区分,C语言规定凡是十六进制必须加一个数字0和一个字母x作为前缀,也就是十六进制必须以0x作为前缀,所以刚才的十六进制29就应该写成0x29,否则,如果直接写29编译器会认为是十进制的29,而十进制的29转换成十六进制是0x1D(十进制与十六进制之间如何转换在后面章节会讲到),0x29与0x1D可见差别很大的,凡是不加前缀的都会被默认为十进制。 多说一句,在C语言程序里,对于同样一个数值,既可以用十六进制,也可以用十进制,比如:d=0x2C与d=44的含义是一样的,因为十六进制的0x2C和十进制的44最终都会被C51编译器翻译成二进制00101100,是表示同样大小的数值。

【15.5   例程练习和分析。】

       现在我们编写一个程序来观察十六进制和二进制的关系。
       程序代码如下:
/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
      unsigned char a;    //定义一个变量a,并且分配了1个字节的RAM空间。
      unsigned char b;    //定义一个变量b,并且分配了1个字节的RAM空间。
      unsigned char c;    //定义一个变量c,并且分配了1个字节的RAM空间。
      unsigned char d;    //定义一个变量d,并且分配了1个字节的RAM空间。

      a=0x06;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
      b=0x0A;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
      c=0x0e;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。
      d=0x2C;   //十六进制前记得加0x前缀,超过9部分的字母不分大小写。

      View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
      View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。

    while(1)
    {
    }
}

/*---C语言学习区域的结束。-----------------------------------------------*/
       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:6
十六进制:6
二进制:110

第2个数
十进制:10
十六进制:A
二进制:1010

第3个数
十进制:14
十六进制:E
二进制:1110

第4个数
十进制:44
十六进制:2C
二进制:101100

分析:      
       通过实验结果,我们知道二进制与十六进制的转换关系确实非常清晰简单,所以十六进制也可以看作是二进制的缩写。

【15.6   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


jianhong_wu 发表于 2016-5-2 10:21:32

本帖最后由 jianhong_wu 于 2016-5-2 10:59 编辑

第十六节:十进制与十六进制。


【16.1   十进制与十六进制各自的应用场合。】

       C语言程序里只用了十进制和十六进制这两种书写格式,有的初学者会问,为什么没有用二进制?我的回答是:不是没有用二进制,而是十六进制已经代表了二进制,因为十六进制就是二进制的缩写形式,所以可以把十六进制和二进制看作是同一个东西。
      十进制和十六进制各自有什么应用场合?十六进制方便人们理解机器,通常应用在配置寄存器,底层通讯驱动,底层IO口驱动,以及数据的移位、转换、合并等场合,在底层驱动程序方面经常要用到。而十进制则方便人们直观理解数值的大小,在程序应用层要经常用到。总之,进制只是数据的表现形式而已,不管是什么进制的数,最终经过编译后都可以看做是二进制的数据。

【16.2   十进制与十六进制相互转换的方法。】

       十进制与十六进制如何相互转换?其实很多教科书上都有介绍它们之间如何通过手工计算进行转换的方法,这种方法当然是有助于我们深入理解数据的含义和转换关系,有兴趣的朋友可以自己找相关书籍来看看,但是在实际应用中,我本人是从来没有用过这种手工计算方法,而我用的方法是最简单直接的,就是借助电脑自带的计算器进行数制转换即可。现在把这种方法介绍给大家,以WIND7系统的电脑为例来讲解详细的操作步骤。

                  
                   图16.2.1.1点击“所有程序”选项切换到系统自带程序的窗口      

                     
                   图16.2.1.2在“附件”子菜单下点击“计算器”启动此软件

                  
                   图16.2.1.3已启动的“计算器”软件界面

       第一步:打开电脑自带的计算器。
       点击电脑左下角“开始” 菜单,在菜单中点击“所有程序”选项切换到自带程序的窗口,在此窗口下,再点击“附件”的文件夹图标,在“附件”子菜单下点击“计算器”启动此软件。

----------------------------------步骤之间的分割线----------------------------------------

                  
                  图16.2.2.1把“计算器”的主界面切换到“程序员”界面      

                  
                  图16.2.2.2已打开的“程序员”界面   

       第二步:把“计算器”的主界面切换到“程序员”界面。
点击打开左上角“查看”的下拉菜单,在下拉菜单中选择“程序员”选项。

----------------------------------步骤之间的分割线----------------------------------------

                     
                     图16.2.3.1在十进制的选项下输入十进制的数据   

                     
                     图16.2.3.2把十进制的数据转换成十六进制的数据

       第三步:十进制转换成十六进制的方法。
       点击勾选中“十进制”选项,在此选项下输入十进制的数据,输入数据后,再切换点击勾选“十六进制”,即可完成从十进制到十六进制的数据转换。比如输入十进制的“230”,切换到十六进制后就变成了“E6”。

----------------------------------步骤之间的分割线----------------------------------------

                     
                      图16.2.4.1在十六进制的选项下输入十六进制的数据   

                     
                      图16.2.4.2把十六进制的数据转换成十进制的数据

       第四步:十六进制转换成十进制的方法。
       点击勾选中“十六进制”选项,在此选项下输入十六进制的数据,输入数据后,再切换点击勾选“十进制”,即可完成从十六进制到十进制的数据转换。比如输入十六进制的“AC”,切换到十进制后就变成了“172”。

----------------------------------步骤之间的分割线----------------------------------------

       第五步:十六进制,十进制,八进制,二进制它们四者之间相互转换的方法。
       我们看到“计算器”软件里已经包含了十六进制,十进制,八进制,二进制这四个选项,所以它们之间相互转换的方法跟上面介绍的步骤是一样的。

----------------------------------步骤之间的分割线----------------------------------------

【16.3   例程练习和分析。】

      现在我们编写一个程序来验证上面讲到的两个例子:
   (1)输入十进制的230,看看它的十六进制是什么样的。
   (2)输入十六进制的AC,看看它的十进制是什么样的。

       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a;    //定义一个变量a,并且分配了1个字节的RAM空间。
       unsigned char b;    //定义一个变量b,并且分配了1个字节的RAM空间。

       a=230;    //把十进制的230赋值给变量a,在串口助手上观察一下它的十六进制是不是E6。
       b=0xAC;   //把十六进制的AC赋值给变量b,在串口助手上观察一下它的十进制是不是172。
      View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

    while(1)
    {
    }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:230
十六进制:E6
二进制:11100110

第2个数
十进制:172
十六进制:AC
二进制:10101100

分析:      
       通过实验结果,发现在单片机上转换的结果和在电脑自带“计算器”上转换的结果是一样的。

【16.4   如何在单片机上练习本章节C语言程序?】

       直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


页: [1] 2 3 4 5 6 7
查看完整版本: 从单片机基础到程序框架(连载)