打印

我设计的单片机开发环境,跟大家分享

[复制链接]
5012|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
0xCC|  楼主 | 2012-6-20 20:49 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
我花了好长时间设计了一个编程开发环境,在这个IDE中,用户可以写MCS51单片机程序,可以写AVR程序,可以写电脑上位机控制程序(生成exe),还可以写一种虚拟机程序(类似java),在这些上层的诸多的处理器类型之下,我给这个语言设计了一个唯一的标准化语法,或者说,这个语言移植到各类处理器中。

       我的毕业设计就是用这个开发环境设计的,全部程序写在一个记事本文件中(超萌小地鼠.txt)。用编译器打开这个记事本文件,点击编译按钮,生成3个我们最终需要的目标文件。其中两个是hex文件下载到单片机中,不过上位机程序编译之后不是生成exe文件,而是生成一个V扩展名的文件:超萌小地鼠控制器.V 。这个类似于java的字节码文件,需要在一个虚拟机中运行。和java有一点不同的是,java的编译器(JC)和虚拟机(JVM)是分开的,而我的设计中把编译器和虚拟机都集成到这个开发环境中,因此编译生成的V文件,在打开方式中选择编译器的文件名目录,或者点击编译器的“打开文件”这个按钮也可,由于是V文件,编译器打开后会自动调用虚拟机运行这个文件。程序运行后出现一个窗体,这个窗体可以接受电脑键盘按键事件,并控制远程小地鼠相应运动。




       以下我从几个方面说明一下这个开发环境和编程语言。

1 程序编辑器

       程序编辑器分为两种模式——文本模式和图形模式。图形模式主要是实现一些端口的连接配置,例如做个单片机控制1602液晶屏的实验,那么液晶屏的这些端口和单片机的连接方式就可以在图形界面拖出相应的模块,再用鼠标连好,然后切换到文本模式写程序。在文本模式下,编译器可以对用户输入的关键字进行着色,还有自动格式对齐、转换注释等基本功能。上面说到可以在图形界面中拖出一些模块,如1602液晶屏,4位数码管模块等,我们暂时先把这些模块称作“组件”,编译器自带一个组件库、一个组件查看器和一个组件设计器。

1.1 组件查看器
通过组件查看器浏览组件库中的组件,浏览界面的左部分是一个树形列表,表示组件库中的各个组件,当点击树形列表的某个节点时,在右侧的图形区显示出这个组件的外形,包括它的背景图片,它所拥有的端口等。如果当前在图形模式下,那么单击这个库组件就可以添加到当前的程序中。




1.2 组件设计器

       用户可以运行组件设计器来设计一个新的组件并添加到组件库中,设计一个新组件只需要一个背景图片,一般可以用相机把某个实际的电路板拍下来,留出电路板主体部分作为组件的背景,另外电路板上会有一些插针端口,在这些端口上放置“虚拟端口”,虚拟端口就是以后可以用鼠标连线的点。下图正在设计一个4位数码管组件,图中已经放置了两个虚拟端口,虚拟端口的放置点最好和实际电路板的插针端口位置一致,这样也能方便实物电路的连线,当在电脑上连好虚拟导线之后,可以照着这个图在实物电路板上进行相应的实际连线,用杜邦线连接等等。








       其实到这里肯定有人能看出来,这套开发环境系统是软件和硬件结合起来的,假设用户事先拥有一大堆的各种电路板组件(实物),然后到开发环境的组件库中选择自己需要的组件,在图形界面把这些组件连好,对应的实物组件也用导线连好,然后就可以切换到文本模式写程序直接控制各个组件工作,至于每个组件的端口配置,编译器会根据用户的连接自动产生相应的配置代码,相当于编译器自动生成:

#define RS P1.0
#define RW P1.1
#define E P1.2
#define DATA P2
#include LCD1602.h

       然后转到文本模式之后,就可以直接写这样的控制语句控制屏幕显示了:

液晶屏 初始化,
液晶屏 第一行显示 “Hi, Kuai Qi Chuang”
液晶屏 第二行显示 “Zai Shui Hui er”

2 虚拟机

       虚拟机其实是这个编译器支持的一种处理器类型,编译器为它生成机器代码。目前编译器支持的处理器类型有:AVRMCS51PC80X86VM。设置处理器类型的语法如下(选自小地鼠程序的第5行,本文最后有程序的完整附录):

ACI cpu = ACI cputype VM 小地鼠控制器,

其中VM表示设置目标芯片类型为虚拟机类型,生成的V文件名称是“小地鼠控制器.V”。如下为设置目标芯片为AVR:

ACI cpu = ACI cputype MEGA32 小地鼠控制器,

如果一段程序目标芯片设置为AVRMCS51时,那么生成hex文件;目标芯片为PC80X86时生成exe文件,可以双击运行;目标芯片为VM(虚拟机)时生成V文件,目前这个V文件还只能在编译器自带的虚拟机中运行,以后也可以在单片机中加载一个虚拟机去解释执行这些V程序。和这个虚拟机对应的有一个调试器,在调试器界面上可以控制虚拟机全速运行,单步运行或定时运行。

上面这些是软件意义上的虚拟机,这个系统还有另一形式的硬件虚拟机,也可以看成是一种硬件端口模拟器。比如说,用户把LCD1602,几个按键和一个DS1302时钟模块连接到这个硬件端口模拟器上(可以由单片机实现),用户可以在其他的单片机上控制这个系统工作,当然两者之间会有某种通信连接,无线或串口等,但又不是普通的双机通信,……哎 这个我也说不清楚了,主要是没有一个看得见的实例展示一下,大家会误以为只是普通的单片机通信而已,其实不是的,这个语言带有内置的“虚拟端口”机制实现这个的。主要是没有现成的实例…… 这个需要去工厂做电路板的,现在没时间,也没钱。等以后做一个端口模拟器出来我再好好图文并茂地讲解一下这个“硬件虚拟机”,真的很酷~~ 哎,现在说不清楚…… 呜呜

3 ISP下载器

     上面我们的程序都写完了,就可以编译下载到芯片中了。我在这个开发环境中集成了一个ISP下载器,目前支持AT89S51S52MEGA8MEGA16MEGA32。当我们写完一个程序单击编译按钮,生成hex文件之后,再单击ISP下载器的按钮,就出现下载器界面,并且它自动装载刚才生成的hex文件,用户只需要点击“自动下载”即可。“自动下载”包含一系列操作,具体跳过哪个步骤可以用户设置,按顺序包含如下步骤:

1 文件改变重载
2 比较芯片ID
3 擦除Flash
4 检查空片
5 写入Flash
6 校验Flash

       每次单击“自动下载”都会依次执行这些操作。也可以取消选中第46项,也就是跳过这两项,因为执行擦除之后芯片一般就是空的了,而且写入的出错几率也很小,也就不需要校验了。选中第1项时,每次目标文件改变了,比如用户重新点击编译按钮更新hex文件等,反正只要文件发生变化,下载器都会自动重新加载目标文件,这样就省去了每次下载程序都要手工打开文件这一步了。

       这个是下载器的界面:

相关帖子

沙发
0xCC|  楼主 | 2012-6-20 20:51 | 只看该作者
本帖最后由 0xCC 于 2012-10-15 20:45 编辑

4 多核编程/云编程



4.1 多核编程



多核编程~~ 其实是我胡乱杜撰的一个词,也就是上面说过的,可以在一个txt文件中写多个程序,编译器生成相应的多个目标文件。在小地鼠程序的第5行:

ACI cpu = ACI cputype VM 小地鼠控制器,

ACI 启动 -> 启动,

编译器遇到这个语句,那么之后遇到的程序编译为虚拟机代码;然后在程序的第88行:

ACI cpu = ACI cputype mcs51 USB控制器,

ACI 启动 -> 控制器 启动,

在这个语句之后编译器开始转换为MCS51芯片的编译了,生成USB控制器.hex,入口函数是控制器-启动(相当于main函数);

最后在201行:

ACI cpu = ACI cputype mega32 小地鼠,

ACI 启动 -> 从机 控制器 启动,

在这个语句之后编译器开始转换为mega32芯片的编译,起始函数是 从机-控制器-启动。



       通过这种语法使编译器在同一个txt文件中切换不同的处理器,产生多个目标文件。那么现在多个程序写在同一个txt文件中,会有一个在C语言中遇不到的问题,比如上面的txt文件包含3个程序,假如用户在上位机程序段定义了一个变量 Data,如果又在下位机某个程序段也定义了 Data 变量,那么这两个同名的变量会不会冲突呢?其实这个就是编译器符号表的可见域大小的问题,相当于你在Keil中建立了两个工程,这两个工程中的变量是否可以重名一样。



       显然是可以重名,因为这两个工程是完全独立的。 但是在我这里的编译器设计中,我把所有程序的符号表(函数、变量、结构体定义等)放在一起,和Keil编译器相比,这个编译器的符号表中的每个符号会多出一个属性——所在的芯片序号。编译器在切换芯片时候会设置当前芯片序号,一般情况下只是访问当前芯片序号的符号表,不过也可以访问其他芯片序号的符号表——符号表的可见域被扩大了,相当于,你可以在Keil的一个工程中“看到”另一个工程里定义的变量、函数。



       为何要这样设计呢,它和传统编程相比有什么优势吗?——为了实现“云编程”。



4.2 云编程



       下面我用一个类比的方式解释这个设计。假设我们要做一个无线温度探测器,它包括两个电路板,其中一个放置在“热得快”上,检测当前的水温数据,并通过无线电发射出去;另一个电路板做成电子手表的形式戴在用户的手腕上,不断接收水温数据,当到达90摄氏度后就会发出报警声,如果用户在另一个房间里上网玩,他听到报警声就会赶回去关掉热得快。我们在这个实例上解释“云编程”。



       在测温这一端,用一个MCS51单片机接一个DS18B20测量温度,保存在一个变量Temp中,“Temp”就成为编译器符号表中的一个项;另一端,我们用一个MEGA32连接一个蜂鸣器作为报警,这个系统包含两个单片机,“Temp”这个符号在第一个单片机(MCS51)中,在通常编程中是用无线模块把Temp的值发送给第二个单片机。不过现在这两个单片机上的程序都在一个记事本中保存,编译也是统一编译,因此Temp这个符号在第二个芯片中也是可见的,如果你在MEGA32芯片的报警程序中直接读取Temp这个变量会怎样呢?例如读取这个值并和温度上限值90相比较。由于这个读取和比较的程序段位于第二个芯片中,而“Temp”是第一个芯片中的符号——我们假设编译器足够智能,它检测到Temp这个变量不是一个可以直接读取的变量,因为它定义在另一个芯片中,于是编译器判断当前对Temp是进行读操作还是写操作,如果是读操作(在表达式中作为右值),那么编译器查找符号表,找到Temp这个变量的分配地址,假设位于芯片1的0x34这个地址单元(而当前是在编译芯片2的程序),接着编译器自动插入一个无线通信函数,把0x34这个值发送给芯片1,也就是“热得快”测温端,测温端的MCS51芯片无线通信中断收到这个0x34之后,把0x34作为指针读取这个地址上存放的数据,也就是温度值,再把读到的数据通过无线传回给芯片2,即用户手腕上戴的电子报警装置。



       这个过程有点繁琐,不过它是编译器内部自动执行的,用户不需要关心,那么用户关心的是什么呢?用户只是在程序中使用Temp而已:



如果 Temp > 90, 蜂鸣器 滴滴响.



       其实总的来看并不难,上面这个判断语句是在芯片2中运行的,而其中的Temp是芯片1中的变量,编译器自动把Temp替换为一个函数调用去获取芯片1中的这个Temp值,但是对于用户而言,他感觉好像Temp就是在本机中定义的变量,可以直接拿来用。



       对于Temp的写入也是一样,编译器产生的底层通信调用中有一个标志位,标志位为0表示“读取”,就是刚才的过程;标志位为1表示写入,这个时候不但发送地址(0x34),还会发送要写入的值,另一端收到地址和数据后,把数据写到那个地址空间。



       我们暂时先把这种自动读写其他芯片中变量的机制称作“跨核变量”的访问,和上面的“多核编程”相对应,一个“核”就代表一个单片机芯片,“跨核变量”的读写就是说在一个芯片中直接读写另一个芯片中的变量,包括结构体,数组和普通变量等。



       可能会有人问,你的编译器自动产生无线通信程序处理“跨核变量”的读写,那么假如我的系统使用串口通信连接两个单片机,或者用CAN总线等,这时候编译器会自动产生底层代码么?  实际情况是这样的,如果平白无故就是两个单片机,编译器肯定不知道用户是用的哪种通信,上面曾经说过,这个编译器开发环境是软硬件结合的,也就是说你在图形界面,拖到面板上一个MCS51单片机最小系统板子,拖出一个MEGA32单片机最小系统板子,拖出一个蜂鸣器模块和一个DS18B20测温模块——最重要的,再拖出一个无线通信模块,这些模块都存放在组件库中,然后用户使用鼠标把各个组件连接起来,实物电路也对应连起来,编译器根据用户连线自动产生各个端口的配置代码,并生成两个hex文件。



由于界面上拖出一个无线通信组件,编译器就导入和无线通信对应的驱动函数,用于 “跨核变量”的读写;如果用户把无线通信组件换成串口通信组件或者是CAN总线组件呢?没关系,这些组件在设计时就会处理好接口,它提供给编译器一个标准的格式,使得编译器不必考虑具体的通信方案,例如全部提供 Get() 和 Set() 函数用于发送和接收数据,而编译器在需要读取跨核变量时就调用Get函数,需要写入跨核变量时就调用Set函数,而不考虑底层具体是什么通信,然后每个通信模块保证自己的驱动程序中的Get和Set这两个函数能够正常工作就行。这个是由组件设计者(目前是我)保证的,用户不必操心。



       函数调用也有类似的机制,当在一个芯片中调用另一个芯片中的某个函数时,编译器把这个函数调用替换为调用底层通信接口,通过接口传送函数参数到另一个芯片中;当另一个芯片执行完那个函数之后再把结果传回来,作为用户的函数调用结果。在这个过程中,用户感觉他好像就是调用本机中的一个普通函数而已。



       我设想的“云编程”就是这样的:用户在图形界面上拖出多个单片机最小系统板子(可以是不同型号的单片机MCS,AVR…),拖出一些通信模块把各个单片机连接起来,再拖出一些功能模块,比如 步进电机啦,小键盘啦,各种避障传感器模块啦…… 等等,把它们的端口用鼠标连起来,然后用户可以在任意的芯片中访问其他芯片中的变量,或控制其他芯片上的组件,比如在A芯片中,直接控制B芯片上连接的电机转动;或者在A芯片中直接控制C芯片上的小灯闪烁,编译器自动在B芯片产生一个底层配置代码传送来自A芯片的命令到C芯片上……



       实际上这时候不必由用户区分芯片了,用户只是把所有程序写到一个记事本中,编译器智能确定把哪段代码分配在哪个芯片上,不需要用户操心,用户感觉,他面对的不是一堆具体的单片机,而是一个“虚拟”的处理器,他可以直接控制系统的各个组件,而不必关心哪个组件连在哪个单片机板子上,也不必关心每个处理器之间用无线还是串口还是其他的何种通信。



这就是“云编程”——云的内部是各种单片机组件、各种通信组件以及连接关系等,而用户关心的那些“功能组件”在云的边缘上可见,他直接控制这些组件,而不用管组件连接到哪个处理器上。



       那么,这有什么用呢?专业人士肯定不需要这个,因为这个方案,编译器做的事情太多了,感觉不可靠。而且对于一个程序维护者,假如他看到一个赋值语句:Data = 34,那么Data这个变量在哪?如果Data存放在另一个单片机中,并且和这个语句所在的单片机系统通过因特网相连,这两个单片机相距很远——那么,这一个简单的赋值语句在底层上处理成一次网络通信,把34这个数通过因特网发送到那个系统。所以这个赋值语句会执行好长时间,等数据传过去之后再接着执行后面的程序。这种延时和不确定性对于要求严格的工业应用来说肯定是不可接受的。 那么,是否有只要求实现功能,而不太关心底层实现的一些场合呢?



       我们可以设想这样一个情景:一个学校给小学生设置“小制作”活动课,学校会事先买到好多的小模块,比如超声波测距器啦,红绿灯模块啦,液晶屏模块啦,红外线接收器啦,还有小电机模块,小喇叭等等,反正好多好多有趣的小东西,小学生们只要用导线把它们连接起来,用杜邦线,可以随时插上随时拔下的那种。假如一个小学生要做个遥控小车,就找到一个小电机模块、单片机模块和无线通信模块,连起来放到车身上;又找到一个单片机模块和按钮模块等连起来作为手持端遥控器,整个的硬件就OK了,然后编写控制程序:



反复执行

       如果 按钮A 按下,电机 正转。

       如果 按钮B 按下,电机 反转。

       …



       后边的三个点“…”表示循环执行以前的那些语句。 这段程序并不要求小学生了解底层的无线通信协议。这里用到了跨核函数的调用,“电机 正转”这是一个函数,它肯定是在小车车身的单片机上执行,但是这个函数的“调用点”却不只限于那个单片机,也可以在手持端的单片机上直接调用,手持端还有两个按钮A和B,这里我们假定编译器把上面的代码分配在手持端;那如果这段程序分配在小车车身的芯片上呢?这个时候就反过来,“电机 正转”是一个普通的函数调用,因为函数调用点和函数本身都在小车车身芯片上,而“按钮A 按下”这个函数调用却成了“跨核函数”调用,因为这个函数是在手持端执行的。



       以上属于编译器内部的细节部分,对用户而言不需要管这些,用户只需知道,他可以任意调用每个组件的函数。



       我再解释一下为何这个编译器会加入虚拟机。其实这个编程语言不用于java那样嵌入网页,也不需要“一次编写到处执行”,这里的虚拟机有两个用处,一是给编译器增加调试功能,本来一段程序是针对单片机的,写好后下载到芯片上去运行,但是编译器可以用虚拟机执行这段程序,例如在文本右键菜单添加一个功能:“从光标所在行开始执行”,那么编译器临时把这段程序编译为虚拟机字节码并执行。虚拟机的第二个用处是支持一个命令窗口。这个命令窗口非常酷——比如你在硬件电路上连好了液晶屏,电机等模块,然后你可以在命令窗口输入一行程序:



电机 正转 5圈,



然后单击一个执行按钮,编译器会自动补全这个程序,比如加上必要的头文件生成一个格式齐全的源代码并编译,然后立刻执行。而且这个命令窗口是保存上次的结果的,这样你可以去外边玩一圈再回来,把命令窗口清空,然后再输入一行文字:



液晶屏 显示 “我回来了”



       编译器重新编译并执行,这些都是在虚拟机中运行的。



我再举个非常酷的例子吧~~ 你还记得我刚才说的“硬件虚拟机”吗,因为没有现成的实例,所以我也很纠结,解释不清楚。这么说吧,“硬件虚拟机”就是一个单片机最小系统,上面带有无线或者串口等通信接口,然后把这个单片机的所有端口都引出来,在芯片中写入一个驱动程序,这就是一个“硬件虚拟机”,我下面暂时用HV表示它。好吧,暂时没有实物,那听我凭空给你描述一下这个东西怎么玩。在电脑USB接口插上一个电路板,通过无线或串口和那个HV通信,然后你手上有一堆新奇小组件,比如一个红绿灯组件吧,把这个红绿灯组件和HV相连,你在上面的命令窗口输入“红灯 点亮”或“绿灯 熄灭”等等就可以控制它们了,你也可以在另一个单片机中“动态”地控制这个HV上的组件,只是速度会慢了很多,因为硬件虚拟机的每个端口高低电平变化都由一次串口或无线通信触发的。



       虽然速度慢一点,但是,非常酷~~  可惜现在没有成品呀~  我争取赶紧在工厂做出一套东西来,再写篇**专门讲解这个小东西的神奇用处——当然,不是说一块带无线通信的单片机最小系统就这么神奇的,主要是我的编程语言针对这个硬件虚拟机有专门的配套机制,所以结合起来很好玩。 我先做个广告吧,到时候板子做出来大家可以买回去好好研究。

使用特权

评论回复
板凳
0xCC|  楼主 | 2012-6-20 20:51 | 只看该作者
本帖最后由 0xCC 于 2012-10-15 21:10 编辑

汗了~~     新浪博客的图片复制不过来了

使用特权

评论回复
地板
0xCC|  楼主 | 2012-6-20 20:52 | 只看该作者
本帖最后由 0xCC 于 2012-6-20 21:01 编辑

这个开发环境不是给专业人士用的,这个比较适合快速开发和测试用,或者给初学者用来快速入门

使用特权

评论回复
5
dong_abc| | 2012-6-20 21:38 | 只看该作者
顶一下,搞点原创不容易。

使用特权

评论回复
6
amwrdfe| | 2012-6-21 00:27 | 只看该作者
很不错

使用特权

评论回复
7
linbei1988| | 2012-6-21 09:07 | 只看该作者
LZ是学生?却是不容易

使用特权

评论回复
8
yoyoball123| | 2013-5-26 21:05 | 只看该作者
您好  我毕设也是做类似的开发系统  请问您是如何将c语言转换成hex文件的

使用特权

评论回复
9
lch0821| | 2015-8-11 21:20 | 只看该作者
支持楼主,有机会向楼主学习!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

45

帖子

8

粉丝