890 likes | 1.12k Views
现场总线控制系统. 第 3 讲 Neuron C 语言与 I/O 对象. 信息学院自动化系 凌志浩. 内容简介 1 Neuron C 简介 2 Neuron C 编程及技巧 3 节点间通信 3.1 网络变量 3.2 显示报文 4 输入输出对象. 1 Neuron C 简介. Neuron 芯片的应用程序是用 Neuron C 编写的 。 Neuron C 是建立在 ANSI C 的基础上的,与之相比有如下 三方面 扩展功能: ( 1 ) 一种新的语句类型 when ,引入事件并定义任务事件的执行顺序。
E N D
现场总线控制系统 第3讲 Neuron C 语言与I/O对象 信息学院自动化系 凌志浩
内容简介 1 Neuron C 简介 2 Neuron C 编程及技巧 3 节点间通信 3.1 网络变量 3.2 显示报文 4 输入输出对象
1 Neuron C 简介 Neuron芯片的应用程序是用Neuron C编写的 。 Neuron C是建立在ANSI C的基础上的,与之相比有如下三方面扩展功能: (1)一种新的语句类型when,引入事件并定义任务事件的执行顺序。 (2)新增加了37种数据类型,34种输入/输出对象,2个定时器/计数器对象,大大简化了设备控制器的用法。 (3)网络变量的内部消息传送机制和其他消息处理机制 。 它是将程序样例建立在事件上的。也即应用程序是被发生在网络当中或指定设备上的事件所触发的。因此网络自己是被事件驱动的。
1.1 对ANSI C 的扩展包括 (1)一个内部多任务调度程序,它允许程序员以自然的方式描述事件驱动的任务,同时控制这些任务的优先级的执行。 (2)将I/O对象直接映射到处理器的I/O能力。 (3)网络变量对象定义:提供一种简单的实现节点之间数据共享的方法。 (4)when语句:引入事件并定义这些事件所对应的任务。 (5)显式消息传递( explicit message ):用于直接对LonTalk协议的底层进行访问。 (6)秒及毫秒级软件定时器对象。 (7)函数库:当调用时,可以执行事件检查、管理输入/输出、网上发送或接收消息以及控制Neuron芯片的各种功能。 (8)Neuron C中有三个ANSI包含文件:<stddef.h>、<stdlib.h> <limits.h>。
1.2 Neuron C 支持的变量的类型 (1)整型(整型常数或整型变量) int 、short int、long int、unsinged int 、signed(可省略) int (2)字符型(字符型常数或字符型变量) unsigned char(8位)、 signed char (8位) (3) typedef enum {FALSE、TRUE} boolean (4)其他
1.3 Neuron C 变量定义 (1)Neuron C和ANSI C支持的变量定义如下 简单的数据类型int a,b,c ;char a; 数据类型typedef unsigned long ULONG; 枚举enum hue{RED,GREEN,BLUE} 指针char *p; 函数int f(int a, int b) 数组int a[4] 结构和共用体struct {char name[10]; int age; char addr[10]; }
(2)Neuron C中附加定义的对象 I/O对象:IO_0 output bit alarm 定时器:mtimer led_on_timer 网络变量:network input int net_is_car 消息标签:msg_tag command
1.4 编译指令 NEURON C允许通过#pragma编译指令进行编译器扩充。#pragma可用来设置一个Neuron 芯片的系统资源以及节点参数,诸如缓存器数及其大小,接收事务数等。也可用于对特定的Neuron芯片参数进行控制,这些指令可在源文件的任何位置出现。 例:#pragma enable_io_pull_ups 可使IO4---IO7的上拉使能
2 Neuron C 编程及技巧 2.1 调度程序 Neuron 芯片的任务调度是由事件驱动的:当一个给定的条件判断为“真”时,与该事件有关的代码被执行(称为任务被执行)。调度程序允许定义任务,该任务作为特定事件的结果而被运行,如:输入管脚的改变、接收一个网络变量的新值、或定时器溢出等。也可以指定某些任务是具有优先级的任务,以便能得到优先服务。
(1) when语句 事件由when语句来定义,when语句包含一个表达式,当表达式为真时,表达式后面的代码段被执行。 例:when(timer_expires(led_timer)) //当定时器溢出时执行下列任务 { io_out(io_led,OFF); } 在任务执行后,时间溢出事件被清除。当led_timer再次溢出,when子句判断为真,任务又将执行;否则,任务被忽略。
A. 多个when子句可与一个任务发生关联 when(reset) when(io_change(io_switch)) when(!timer_expires) when(x= =3) { ………. }
B. when子句不能嵌套 如下为错误: when(io_changes(io_switch)) { when(x= =3) ….. }
(2)when子句语法 [priority] [prompt_safe] when(event) { task } priority(优先级) :可选择项 prompt_safe:可选项,如使用即便应用程序处于占先模式,仍然允许调度程序执行相关的when任务 。 event:可是预定的事件也可是有效的Neuron C 表达式。 task:是Neuron C的复合语句,任务同void函数体等同,也即它不能返回一个值.
(3)when语句中的事件类型 分为两种:预定义事件和用户定义事件 预定义事件:使用编译器内部固有的关键字,包括输入引脚状态变化、网络变量修改、定时器溢出以及消息的接收等。 用户定义事件:可以是任何有效的Neuron C表达式。 例: when(msg_arrives) //正确 when(online) //正确 when(msg_arrives && flag = =TURE) //正确
(4)when语句的调度 调度程序对一组when子句的判断过程是一个循环往复的过程,每一个when语句都由调度程序检测,如果为真,则与其相关联的任务就被执行。如果when语句为假(FALSE),调度程序将继续检查后面的when语句,在检查完最后一个when语句后,调度程序返回顶部重复执行上述过程。 例如: (其中只有C为真) when(A){ A } when(B) { B } when(C) { C } when(D){ D }
(5)优先级when子句 如when子句选用priority关键字,相比无优先级的when子句,调度程序对具有优先级的when子句的判断次数要频繁的多。优先级when语句在每次调度程序运行时以指定的顺序被检查。如果任何优先级when语句被检测为真,则与它相对应的任务就被执行,然后调度程序又重新回到优先级when语句队列头,从头开始检测优先级when语句。 使用优先级when语句必须仔细考虑。因为优先级when语句太多的话,将使无优先级的when语句根本没机会执行。如果一个优先级when语句在大部分时间里都为真,则它将独占处理器时间。
(6)预定义事件关键字 flush_completes 、offline 、online 、wink、io_changes、io_in_ready、io_out_ready、reset、timer_expires、io_update_occurs、msg_arrives、msg_completes、nv_update_fails、nv_update_succeeds等等。 预定义事件还可以作为子表达式放置在if 、while 、for语句的控制表达式中,这种方法称为直接事件处理。例: mtimer t; when(event) {…… if(timer_expires(t)) {io_out(io_led,OFF);} ………}
2.2 定时器 Neuron C可以使用两种类型的软件定时器对象:毫秒定时器和秒定时器。 毫秒定时器提供一个计时范围为1~64000毫秒的定时器。 秒定时器提供一个计时范围为1~65535秒的定时器。 它们和Neuron芯片上两个硬件定时器/计时器无关,由网络处理器实现(15个)。
(1)定时器的定义 mtimer [repeating] timer-name[= initial-value] 毫秒定时器 stimer [repeating] timer-name[=initial-value]秒定时器 repeating:为可选项,如果定时器溢出,定时器将自动开始重新计时。使用该选项,即使应用不能立即响应该终止事件,精确的时间间隔也能够被保留。 timer-name:为定时器指定的名字 init-value:为可选项,指定当加电或者复位时赋给定时器的值。如果不提供该初始值,定时器的值被置为0。
例 stimer led_timer; //定义秒定时器led_timer when(reset) { led_timer=5; } when(t = =50) { led_timer=0;//关闭秒定时器 }
(2) 时间溢出事件 语法:timer_expires [( 定时器名)] 定时器名:是可选项,由它来指定所要检查的具体的定时器。如没有该选项,该事件是一个未加限定的timer_expires事件。它与其它预定义事件不同的是,其它的某个挂起事件只为真一次,而对未加限定的timer_expires事件,只要任何一个定时器已经终止,未加限定的timer_expires将一直保持为真。该事件只有当检测到特定的定时器终止事件时才能被清除 stimer led_timer; when(timer_expires(led_timer)) { io_out(io_led,OFF);}
如果程序中有多个定时器,对每个具体的定时器都要作检查,以便这个终止事件被清除。例如:如果程序中有多个定时器,对每个具体的定时器都要作检查,以便这个终止事件被清除。例如: mtimer x; mtimer y; mtimer z; when( timer_expires(x)) { } when(timer_expires(y)) { } when(timer_expires(z)) { }
另外也可采用如下的方法: when(timer_expires) { if(timer_expires(x)) …… else if(timer_expires(y)) …… else if(timer_expires(z)) ….. }
2.3 输入/输出 对未定义的I/O引脚也就是不用的引脚,默认为无效状态,即高阻状态。如果引脚不用,应设计上拉电阻,对IO4~IO7可使用enable_io_pullups编译指令,加上软件实现的上拉电阻。为避免使用上拉电阻,可将不用的管脚定义为输出管脚。为实现I/O,可使用内嵌的I/O函数:io_out( ) 、io_in( )、 io_select( ) 、io_select_dirction( ) 、io_change_init( ) 、io_set_clock( ) 等。
(1)I/O对象的定义 说明一个I/O对象,完成了两件事: 1.在哪个或哪几个管脚上将实现什么类型的I/O操作。 2.将I/O对象的名字和硬件连在一起。 语法: pin type [option] io-object-name ; pin :IO0~IO10中的一个,同一个引脚可以出现在多个 I /O对象定义中 type:I/O对象类型 option:是可选的I/O参数,不同I/O对象有不同的选项
(2)定义I/O对象的指导原则 (1) 最多定义16个I/O对象 (2) Neurowire、I2C、磁卡、磁迹1以及串行I/O对象是互斥的。在一个程序中可以说明一个或多个该组中的某一种I/O对象。 (3)定时器/计数器对象定义了的引脚不能再定义 为移位I/O对象。 (4)定时器/计数器1可以有多到4个的输入对象供选择(多路复用输入对象)。 (5)并行和muxbus I/O对象要求使用所有的I/O管脚,任何一个这种类型的I/O对象被说明后,就不能再说明其它I/O对象类型。
(3)I/O对象的重叠使用 可能同一引脚要定义为多种I/O对象 例: IO_4 input nibble io_all_points; IO_4 input bit io_point_1; IO_5 input bit io_point_2; IO_6 input bit io_point_3; IO_7 input bit io_point_4; 允许一个程序在同一个操作中读相邻的4个引脚或分别读每个引脚(比特I/O)。
(4) I/O函数及事件 输入对象的访问可以采用两种方法: 1.显式的调用io_in( )函数。 2. 判断与该对象有关的事件 输出对象的访问方法: 调用io_out( )函数
内嵌的I/O函数 io_in ( ) return-value =io_in(io-object-name[,args]) //从I/O对象读取数据 io_out ( )当信号要发送到某个设备时,使用 io_out (io-object-name,output-value[,agrs]) // 向一个I/O对象写数据
与I/O有关的事件 代替显式调用io_in( )函数的方法 使用预定义事件: io_changes( ) io_update_occurs() 仅用于输入对象,在检测时,io_update_occurs和io_changes事件都隐含的执行io_in( )函数,该函数包括对象的输入值。任务可以通过使用关键字input_value访问这个输入值。
1)io_changes事件 语法:io_changes(io-object-name)[by|to expr] 当从I/O对象读到的值改变时,该事件判断为真。值的改变有三种类型: ①改变为某指定的值(to ) ②至少改变一指定的量(by 绝对值) ③任意改变(无限制) 参考值是上次事件判断为真时读取的值,对于无限制的io_changes事件,如果当前值与引用值不同时就意味着发生了一个状态的改变.对于定时器/计数器输入设备有一个新的值并且该值与以前的值不同时,io_changes事件才发生。
例 IO_0 input bit push_button; when(io_changes(push_button) to 0) { ……… } IO_7 input pulsecount total_ticks; when(io_changes(total_ticks) by 100) { …… }
对于定时器/计数器对象,io_changes事件发生于:对于定时器/计数器对象,io_changes事件发生于: 双斜率输入:转换完成时事件发生 定期及周期输入:如果测量时间与上次测量时间相比已发生改变时事件发生。 脉冲计数输入:如果脉冲计数的值与上次计数相比已发生改变,则事件发生。
2) io_update_occurs事件 语法:io_update_occurs (io-object-name) 当输入对象(io-object-name)读取的值发生改变时,io_update_occurs事件为真。该事件只能用在某些定时器/计数器的输入对象中。对事件的定时依赖于输入对象的类型 。
例如 双斜率输入:转换完成且值发生变化时,该事件发生 定期及周期输入:事件发生在定时度量结束时 脉冲计数输入:每0.839秒事件发生一次,即当一个新的脉冲计数值有效时。
3)input_value变量 long int类型,内嵌变量,可象任何其他的C变量一样使用. 例: when(io_changes(io_switch_in)) { nv_switch_state= (input_value= = SWITCH_ON)? ST_ON:ST_OFF ;} 例中可以根据input_value的值设置网络变量 nv_switch_state的值。input_value 只有在io_changes和io_update_occurs事件发生后才有效。 有两种方法可以帮助你确定输入值是否为新值。
3 节点间通信 3.1 网络变量 概述 (1)可定义为输入或输出 (2)基于Neuron节点定义62个,基于非Neuron节点可定义4096个 (3)实现节点间通信、数据共享 (4)由LonTalk协议实现,被称为隐式消息 (5)类型相同的网络变量才能建立I/O连接 (6)不用考虑消息的打包、发送及接收,简化编程,缩短开发周期。
3.1.1 网络变量的说明 network input | output type identifier [ = initial-value]; network input | output type identifier [array-bound] [ = initializer-list]
说明 input、output :输出/输入网络变量 identifier:用户定义的网络变量名 初值:(initial-value)指定一个初值
Type: 网络变量的数据类型 (1)[signed]long int (2) unsigned long int (3)[signed][short] int (4) unsigned [short] int (5)signed char (6) [unsinged ] char (7)枚举 (9)标准网络变量(SNVT) (8)以上类型构成的结构体、数组(最多62个元素) SNVT: 是一组与数据的单位(如摄氏、伏、米等)相关联的预定义网络变量类型,同时SNVT 还定义了网络变量值的范围以及类型标识号ID。目前,已定义了的标准网络变量有255种。
例 network input SNVT_temp temp_set_point; network output SNVT_lev_disc primary_heater; network output int current_temp; network output boolean bind_info(priority) fire_aalarm; network output boolean bind_info priority(nonconfig) fire_aalarm;
3.1.2 网络变量的连接 网络变量的连接是独立于节点上的Neuron C 应 用的。网络变量的连接由网络管理工具中称为连 接器(Binder)的部分来建立。Binder是LonBuilder网络管理程序、LonMaker安装工具或其它网络管理工具的一部分。 连接器首先找共享共同网络变量的所有节点。然 后对每个网络变量,连接器给所有相应的节点分配 地址,以保证信息从正确的地方来,到正确的地方 去。is_bound( )函数用来确定网络变量是否连接到 其它任何的网络变量 。
3.1.3 网络变量事件 有4个和网络变量相关的预定义的事件: nv_update_completes [(network-var)] nv_update_fails [(network-var)] nv_update_occurs [(network-var)] 只用于输入网络变量 nv_update_succeeds [(network-var)] 其它三个事件,当输出网络变量被更新时,用于输出网络变量,当输入网络变量被轮循时,应用于输入网络变量 。 network-var :可用网络变量名、网络变量数组名或网络变量数组元素来限定 例如: network_var[index],如事件被一个数组名限定,事件将对每个数组元素发生一次 。
① nv_update_occurs [(network-var)]事件 network-var:如果省略,事件对任何网络变量更新都为 真。当输入网络变量收到一个新值,nv_update_occurs事 件为真。 例 network input SNVT_temp tempSetPoint; when(nv_update_occurs(tempSetPoint)){ primaryHeader= (curTemp<tempSetPoint)?ST_ON:ST_OFF; }
② nv_update_completes [(network-var)]事件 network-var:可是网络变量名、网络变量数组名或网络变量数组元素,也可没有。 用于: (1)输出网络变量更新完成 (2)被轮循的输入网络变量操作完成 无论成功还是失败,只要完成该事件都判断为真。 例:network output int humidity; ……… humidity=32; when(nv_update_completes(humidity)) { ……………………… }
③ nv_update_fails和nv_update_succeeds nv_update_fails(network-var) 当一个网络变量更新或轮循失败后,nv_update_fails事件为真。如果没有为该事件指定相应的网络变量,那么该节点上任何网络变量的更新或轮循失败该事件都检测为真。如果多个网络变量被指定,则每一个网络变量更新或轮循失败都使该事件为真。 nv_update_succeeds(network-var) 同样当输出网络变量更新已被成功地发送或来自所有写出节点的轮循都已被接收到,nv_update_succeeds事件为真。
三个节点网络示意图 电灯 节点 nv_lamp_state 开关 节点 nv_switch_state 电灯 节点 nv_lamp_state 一个输出网络变量控制两个输入网络变量
开关节点 #pragma enable_io_pullups; #include <snvt_lev.h> network output SNVT_lev_disc nv_switch_state=ST_OFF; #define BUTTON_DOWN 1 #define BUTTON_UP 0 IO_4 input bit ioButton=BUTTON_UP; // I/O任务 When(io_changes(ioButton)to BUTTON_DOWN){ nv_switch_state=(nv_switch_state!=ST_OFF)? ST_OFF:ST_ON; }
电灯节点 network input SNVT_lev_disc nv_lamp_state=ST_OFF; #define LED_ON 1 #define LED_OFF 0 IO_0 output bit ioLED=LED_OFF; //修改任务---修改电灯的状态,用网络变量的值作为电灯的新状态 when(nv_update_occurs(nv_lamp_state)){ io_out(ioLED,(nv_lamp_state!=ST_OFF)?LED_ON:LED_OFF); }