本帖最后由 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上的组件,只是速度会慢了很多,因为硬件虚拟机的每个端口高低电平变化都由一次串口或无线通信触发的。
虽然速度慢一点,但是,非常酷~~ 可惜现在没有成品呀~ 我争取赶紧在工厂做出一套东西来,再写篇**专门讲解这个小东西的神奇用处——当然,不是说一块带无线通信的单片机最小系统就这么神奇的,主要是我的编程语言针对这个硬件虚拟机有专门的配套机制,所以结合起来很好玩。 我先做个广告吧,到时候板子做出来大家可以买回去好好研究。
|