Neuron C Programmers Guide
Neuron C Programmers Guide
Neuron C Programmers Guide
0 7 8 - 0 0 0 2 -02 H
Echelon, LONWORKS, LONMARK, NodeBuilder, LonTalk, Neuron,
3120, 3150, ShortStack, LonMaker, and the Echelon logo are
trademarks of Echelon Corporation registered in the United
States and other countries. 3170 is a trademark of the
Echelon Corporation.
Echelon Corporation
www.echelon.com
Welcome
This guide describes how to write programs using the Neuron® C Version 2.2
language. Neuron C is a programming language based on ANSI C that is
designed for Neuron Chips and Smart Transceivers. It includes network
communication, I/O, and event-handling extensions to ANSI C, which make it a
powerful tool for the development of LONWORKS® applications. Key concepts in
programming with Neuron C are explained through the use of specific code
examples and diagrams. A general methodology for designing and implementing
a LONWORKS application is also presented.
A subset of the Neuron C language is also used to describe the interoperable
interface of host-based applications that are designed with the ShortStack®
Developer’s Kit, FTXL™ Developer’s Kit, or the i.LON® SmartServer. This
interoperable interface is contained within a file called a model file, which
contains Neuron C declarations and definitions for the device interface. In
addition to describing the Neuron C Version 2.2 language, this guide also
provides a brief introduction to model file compilation, and points out syntactical
differences between compilation for Neuron-hosted devices and compilation for
host-based devices that use a model file.
The Neuron C Programmer’s Guide:
• Outlines a recommended general approach to developing a LONWORKS
application, and
• Explains key concepts of programming in Neuron C through the use of
code fragments and examples.
Audience
The Neuron C Programmer’s Guide is intended for application programmers who
are developing LONWORKS applications. Readers of this guide are assumed to be
familiar with the ANSI C programming language, and have some C programming
experience.
For a complete description of ANSI C, consult the following references:
• —. 1989. American National Standard for Information Systems
Programming Language C. Standard number X3.159-1989. New York,
NY: American National Standards Institute.
• —. 2007. International Standard ISO/IEC 9899:1999. Programming
languages – C. Geneva, Switzerland: International Organization for
Standardization.
• Harbison, Samuel P. and Guy L. Steele, Jr. 2002. C: A Reference
Manual, 5th edition. Upper Saddle River, NJ: Prentice Hall, Inc.
• Kernighan, Brian W. and Dennis M. Ritchie. 1988. The C Programming
Language, 2nd edition. Upper Saddle River, NJ: Prentice Hall, Inc.
• Plauger, P.J. and Jim Brodie. 1989. Standard C: Programmer’s Quick
Reference Series. Buffalo, NY: Microsoft Press.
• Plauger, P.J. and Jim Brodie. 1992. ANSI and ISO Standard C
Programmer's Reference. Buffalo, NY: Microsoft Press.
• I/O Model Reference for Smart Transceivers and Neuron Chips (078-
0392-01A). This manual describes the I/O models that are available for
Echelon’s Smart Transceivers and Neuron Chips.
All of the Echelon documentation is available in Adobe® PDF format. To view the
PDF files, you must have a current version of the Adobe Reader®, which you can
download from Adobe at: www.adobe.com/products/acrobat/readstep2.html.
iv
Table 1. Typographic Conventions
literal characters {
• You replace the abstract elements netvar modifier, class, type, bind-info,
and identifier with the actual modifier, class, type, bind information, and
identifier for the network variable
• The declaration must include either input or output, but not both
• The elements netvar modifier, class, and bind-info are all optional
#include <mem.h>
viii
Instantiation of Configuration Properties .................................................. 88
Device Property Lists ............................................................................ 89
Network Variable Property Lists ......................................................... 90
Accessing Property Values from a Program............................................... 91
Advanced Configuration Property Features .............................................. 92
Configuration Properties Applying to Arrays...................................... 92
Initialization of Configuration Properties at Instantiation ................ 95
Sharing of Configuration Properties .................................................... 96
Configuration Property Sharing Rules ................................................ 97
Type-Inheriting Configuration Properties ........................................... 98
Type-Inheriting CPs for NVs of Changeable Type........................ 99
Chapter 5. Using Functional Blocks to Implement a Device Interface.........101
Overview ..................................................................................................... 102
Functional Block Declarations .................................................................. 104
Functional Block Property Lists......................................................... 107
Shared Functional Block Properties .................................................. 108
Scope Rules................................................................................................. 109
Accessing Members and Properties of an FB from a Program................ 110
Accessing Members and Properties of an FB from a Network Tool ....... 112
The Director Function................................................................................ 113
Sharing of Configuration Properties......................................................... 115
Chapter 6. How Devices Communicate Using Application Messages...........117
Introduction to Application Messages ...................................................... 118
Layers of Neuron Software........................................................................ 119
Implicit Messages: Network Variables .................................................... 119
Application Messages................................................................................. 120
Constructing a Message............................................................................. 120
The msg_out Object Definition ........................................................... 121
Message Tags ................................................................................ 122
Message Codes .............................................................................. 123
Block Transfers of Data ...................................................................... 124
Sending a Message..................................................................................... 125
Receiving a Message .................................................................................. 126
The msg_arrives Event ....................................................................... 126
The msg_receive( ) Function ............................................................... 127
Format of an Incoming Message......................................................... 127
Importance of a Default When Clause ............................................... 128
Application Message Examples................................................................. 129
Lamp Program ..................................................................................... 129
Switch Program ................................................................................... 129
Connecting Message Tags ................................................................... 130
Explicit Addressing .................................................................................... 130
Sending a Message with the Acknowledged Service ............................... 131
Message Completion Events ............................................................... 131
Processing Completion Events for Messages .............................. 133
Preemption Mode and Messages............................................................... 134
Asynchronous and Direct Event Processing............................................. 135
Using the Request/Response Mechanism ................................................. 136
Constructing a Response..................................................................... 137
Sending a Response ............................................................................. 138
Receiving a Response .......................................................................... 138
The resp_arrives Event................................................................. 138
x
Chips without Off-Chip Memory ........................................................ 177
Memory Regions .................................................................................. 177
Memory Areas...................................................................................... 178
Default Memory Usage ....................................................................... 180
Controlling Non-Default Memory Usage ........................................... 181
eeprom Keyword (for Functions and Data Declarations) ........... 181
far Keyword (for Data Declarations) ........................................... 182
offchip Keyword (for Functions and Data Declarations) ............ 183
onchip Keyword (for Functions and Data Declarations) ............ 183
ram Keyword (for Functions) ....................................................... 183
uninit Keyword (for Data Declarations) ...................................... 184
Compiler Directives ............................................................................. 184
When the Program Is Relinked .......................................................... 184
Use of Flash Memory........................................................................... 184
Use of Flash Memory for Series 3100 Chips ............................... 185
Use of Flash Memory for Series 5000 Chips ............................... 186
The eeprom_memcpy( ) Function ....................................................... 187
Reallocating On-Chip EEPROM ............................................................... 188
Address Table ...................................................................................... 188
Alias Table ........................................................................................... 189
Domain Table....................................................................................... 190
Allocating Buffers ...................................................................................... 190
Buffer Size............................................................................................ 191
Application Buffer Size ................................................................. 191
Network Buffer Size...................................................................... 192
Errors............................................................................................. 192
Buffer Counts....................................................................................... 192
Compiler Directives for Buffer Allocation.......................................... 193
Outgoing Application Buffers....................................................... 194
Outgoing Network Buffers ........................................................... 194
Incoming Network Buffers ........................................................... 195
Incoming Application Buffers....................................................... 195
Number of Receive Transactions ................................................. 195
Usage Tip for Memory-Mapped I/O .......................................................... 198
What to Try When a Program Does Not Fit on a Neuron Chip .............. 199
Reduce the Size of the Configuration Property Template File......... 200
Reduce the Number of Address Table Entries .................................. 200
Remove Self-Identification Data if Not Needed ................................ 200
Remove Network Variable Names if Not Needed ............................. 201
Declare Constant Data Properly ........................................................ 201
Use Efficient Constant Values............................................................ 202
Take Advantage of Neuron Firmware Default Initialization ........... 202
Use Neuron C Utility Functions Effectively ...................................... 202
Be Aware of Library Usage................................................................. 203
Use More Efficient Data Types........................................................... 203
Observe Declaration Order ................................................................. 204
Use the Optional fastaccess Feature.................................................. 205
Eliminate Common Sub-Expressions................................................. 205
Use Function Calls Liberally .............................................................. 206
Use the Alternate Initialization Sequence......................................... 207
Use C Operators Effectively................................................................ 207
Use Neuron C Extensions Effectively ................................................ 208
Using the Link Map ................................................................................... 209
xii
1
Overview
2 Overview
• fb_properties
• nv_properties
• device_properties
• cp
• cp_family
You can still manually create the self-documentation strings, if necessary, by
avoiding the use of any of these keywords and by declaring the self-
documentation strings using the Neuron C version 1 syntax. Using this syntax
could be useful for migrating older applications (created with the NodeBuilder 1.5
or LonBuilder tools) to the NodeBuilder FX Development Tool. Applications that
do not use these keywords still get the benefit of access to resource definitions
contained within resource files.
4 Overview
0x0 .. 0x7F signed short
0x80 .. 0xFF unsigned short
0x100 .. 0x7FFF signed long
0x8000 .. 0xFFFF unsigned long
Octal constants have the following default types, which can also be modified as
described above with the u, U, l, and L suffixes:
0 .. 0177 signed short
0200 .. 0377 unsigned short
0400 .. 077777 signed long
0100000 .. 0177777 unsigned long
Binary constants have the following default types, which can also be modified as
described above with the u, U, l, and L suffixes:
0b0 .. 0b01111111 signed short
0b10000000 .. 0b11111111 unsigned short
0b0000000100000000 .. 0b0111111111111111 signed long
0b1000000000000000 .. 0b1111111111111111 unsigned long
Neuron C Variables
The following sections briefly discuss various aspects of variable declarations.
Data types affect what sort of data the variable represents. Storage classes affect
where the variable is stored, whether it can be modified (and if so, how often),
and whether there are any device interface aspects to modifying the data.
6 Overview
uninit When combined with the eeprom keyword (see below), specifies that
the EEPROM variable is not initialized or altered on program load or
reload over the network.
The following Neuron C keywords allow you to direct portions of application code
and data to specific memory sections:
• eeprom
• far
• offchip (only for Neuron Chips and Smart Transceivers
with external memory)
• onchip
• ram (only for Neuron Chips and Smart Transceivers
with external memory)
These keywords are particularly useful on the Neuron 3150 Chip and 3150 Smart
Transceivers, because a majority of the address space for these parts is mapped
off chip. See Using Neuron Chip Memory on page 176 for a more detailed
description of memory usage and the use of these keywords.
Variable Initialization
Initialization of variables occurs at different times for different classes. The
const variables, except for network variables, must be initialized. Initialization
of const variables occurs when the application image is first loaded into the
Neuron Chip or Smart Transceiver. The const ram variables are placed in off-
chip RAM that must be non-volatile. Therefore, the eeprom and config variables
are also initialized at load time, except when the uninit class modifier is included
in these variable definitions.
Automatic variables cannot be declared const because Neuron C does not
implement initializers in declarations of automatic variables.
Global RAM variables are initialized at reset (that is, when the device is reset or
powered up). By default, all global RAM variables (including static variables) are
initialized to zero at this time. Initialization to zero costs no extra code space, as
it is a firmware feature.
Initialization of I/O objects, input network variables (except for eeprom, config,
config_prop, or const network variables), and timers also occurs at reset. Zero is
the default initial value for network variables and timers.
Local variables (except static ones) are not automatically initialized, nor are their
values preserved when the program execution leaves the local scope.
Neuron C Declarations
Both ANSI C and Neuron C support the declarations listed in Table 2.
Table 2. ANSI C and Neuron C Declarations
Declaration Example
Declaration Example
8 Overview
declared with the polled modifier), the new value of the network variable is
propagated across the network to all devices with input network variables
connected to that output network variable. If the output network variable is not
currently a member of any network variable connection, no transaction and no
error occurs.
Although the propagation of network variables occurs through LONWORKS
messages, these messages are sent implicitly. The application program does not
require any explicit instructions for sending, receiving, managing, retrying,
authenticating, or acknowledging network variable updates. A Neuron C
application provides the most recent value by writing into an output network
variable, and it obtains the most recent data from the network by reading an
input network variable.
Example:
network input SNVT_temp nviTemperature;
network output SNVT_temp nvoTemperature;
void f(void)
{
nvoTemperature = 2 * nviTemperature;
}
Network variables greatly simplify the process of developing and installing
distributed systems because devices can be defined individually, then connected
and reconnected easily into many new LONWORKS applications. Network
variables are discussed in detail in Chapter 3, How Devices Communicate Using
Network Variables, on page 43, and also in the Neuron C Reference Guide.
Network variables promote interoperability between devices by providing a well-
defined interface that devices use to communicate. Interoperability simplifies
installation of devices into different types of networks by keeping the network
configuration independent of the device’s application. A device can be installed in
a network and logically connected to other devices in the network as long as the
data types (for example, SNVT_switch or SNVT_temp_p) match. To further
promote interoperability, the LONWORKS platform provides standard functional
profiles that define standard functional interfaces for devices, and standard
network variable types (SNVTs) that define standard data encoding, scaling, and
units, such as degrees C, volts, or meters. There are standard functional profiles
for a variety of functions and industries. There are SNVT definitions for
essentially every physical quantity, and other more abstract definitions tailored
for certain industries and common applications.
You can also create your own user functional profiles and user network variable
types (UNVTs). You can define resource files for your custom types and profiles
to enable your devices to be used with devices from other manufacturers. The
NodeBuilder Resource Editor included with the NodeBuilder tool provides a
simple interface for viewing existing resources and defining your own resources.
Configuration Properties
A configuration property is a data item that, like a network variable, is part of
the device interface for a device. A configuration property can be modified by a
network tool. Configuration properties facilitate interoperable installation and
configuration tools by providing a standardized network interface for device
10 Overview
of this feature in Chapter 3, How Devices Communicate Using Network
Variables, on page 43, and also in the Neuron C Reference Guide.)
An application image for a device created by the Neuron C compiler contains SD
information unless the #pragma disable_snvt_si directive is used. (See the
Compiler Directives chapter of the Neuron C Reference Guide for more
information.)
Low-Level Messaging
In addition to the functional block and network variable communication model,
Neuron C also supports application messages. You can use application messages
– in place of or in conjunction with the network variables approach – to
implement proprietary interfaces to your devices. They are also used for the
LONWORKS file transfer protocol. Application messages are described in Chapter
6, How Devices Communicate Using Application Messages, on page 117.
I/O Devices
A Neuron Chip or Smart Transceiver can be connected to one or more physical
I/O devices. Examples of simple I/O devices include temperature and position
sensors, valves, switches, and LED displays. Neuron Chips and Smart
Transceivers can also be connected to other microprocessors. The Neuron
firmware implements numerous I/O models that manage the interface to these
devices for a Neuron C application. I/O models are discussed in detail in Chapter
2, Focusing on a Single Device, on page 15, and in the I/O Model Reference.
12 Overview
Differences between Neuron C and ANSI C
Neuron C adheres closely to the ANSI C language standard; however, Neuron C
is not a "conforming implementation" of Standard C, as defined by the American
National Standards Institute committee X3-J11.
The following list outlines the major differences between Neuron C and ANSI C:
• Neuron C does not support floating-point computation with C syntax or
operators. However, a floating-point library is provided to allow use of
floating-point data that conforms to the IEEE 754 standard.
• ANSI C defines a short int as 16 bits or more and a long int as 32 bits or
more. Neuron C defines a short int as 8 bits and a long int as 16 bits. In
Neuron C, int defaults to a short int. A 32-bit signed integer library is
available to allow use of 32-bit quantities.
• Neuron C does not support the register or volatile classes. These storage
classes can be specified but are ignored.
• Neuron C does not implement initializers in declarations of automatic
variables.
• Neuron C does not support structures or unions as procedure parameters
or as function return values.
• Neuron C does not support declaration of bitfields as members of unions.
However, an equivalent declaration can be accomplished by defining a
structure as a member of the union, where the structure contains the
bitfields.
• Network variable structures cannot contain pointers. Configuration
property structures also cannot contain pointers.
• Pointers to timers, to message tags, or to I/O objects are not supported.
• Pointers to network variables, configuration properties, and EEPROM
variables are treated as pointers to constants (that is, the contents of the
variable referenced by the pointer can be read, but not modified). Under
special circumstances, and with certain restrictions, the pointers can be
used to modify the memory. See the discussion of the eeprom_memcpy( )
function in Chapter 8, Memory Management, on page 173, and also in the
Functions chapter of the Neuron C Reference Guide. Also refer to the
discussion of the #pragma relaxed_casting_on compiler directive in the
Compiler Directives chapter of the Neuron C Reference Guide.
• Macro arguments are not rescanned until after the macro is expanded,
thus the macro operators # and ## might not yield results as defined in
the ANSI C standard when they occur in nested macro expansions.
• Names of network variables and message tags are limited to 16
characters. Names of functional blocks are limited to 16 characters
unless they are declared using the external_name feature, in which case
the external name is limited to 16 characters, and the internal name of
the functional block is limited to 64 characters.
• A few ANSI C library functions are included in Neuron C, such as
memcpy( ) and memset( ). A string and byte operation library is provided
to allow use of a subset of the ANSI C functions defined in the <string.h>
14 Overview
2
Focusing on a Single Device
This chapter describes the Neuron C event scheduler and I/O objects.
The concepts of predefined events and user-defined events are
introduced. Code examples in this chapter illustrate the use of events,
I/O and timer objects, and I/O functions.
The Scheduler
The scheduling of application program tasks is event driven: when a given
condition becomes TRUE, a body of code (called a task) associated with that
condition is executed. The scheduler allows you to define tasks that run as the
result of certain events, such as a change in the state of an input pin, receiving a
new value for a network variable, or the expiration of a timer. You can also
specify certain tasks as priority tasks, so that they receive preferential service
(see Priority When Clauses on page 23). Series 5000 chips also allow you to
specify interrupt tasks that are serviced independently of the scheduler; see
Interrupts on page 153 for more information.
When Clauses
Events are defined through when clauses. A when clause contains an expression
that, if evaluated as TRUE, causes the body of code (the task) following the
expression to be executed to completion. Multiple when clauses can be associated
with a single task. A simple when clause and its associated task are shown
below. The when clause or clauses and the associated task are frequently
referred to as one entity known as a when task or a when statement.
{
// Turn off the LED
io_out(io_led, OFF); task
}
When Statement
The syntax for a when statement (the when clause or clauses plus the associated
task) is:
when-clause
[when-clause ... ]
task
The syntax for when-clause is:
[priority] [preempt_safe] when (event)
priority Forces evaluation of the following when clause each time the
scheduler runs. See Priority When Clauses on page 23.
preempt_safe Allows the scheduler to execute the associated when task
even if the application is in preemption mode. See the discussions on
preemption mode in Chapter 6, How Devices Communicate Using
Application Messages, on page 117.
Predefined Events
The timer_expires event shown earlier is one type of predefined event. Table 4
lists other predefined events that are represented by unique keywords.
Table 4. Predefined Events
flush_completes Chapter 7
msg_arrives Chapter 6
msg_completes Chapter 6
msg_fails Chapter 6
msg_succeeds Chapter 6
nv_update_occurs Chapter 3
nv_update_completes Chapter 3
nv_update_fails Chapter 3
nv_update_succeeds Chapter 3
offline Chapter 7
online Chapter 7
resp_arrives Chapter 6
wink Chapter 7
A modifier that narrows the scope of the event may follow some predefined
events, such as the I/O events and network variable events. If the modifier is
optional and not supplied, any event of that type qualifies.
Predefined events can also be used as any sub-expression, including within the
control expression of if, while, and for statements. This method is termed direct
event processing. An example of direct event processing is:
mtimer t;
when (event)
{
. . .
if (timer_expires(t)) {
io_out(io_led, OFF);
}
. . .
}
Any built-in event keyword or keyword expression (such as timer_expires(t)) is
treated the same as any other sub-expression and any combination allowed by
standard C expression syntax is allowed when programming in Neuron C.
The special case of the io_changes event expression must be treated carefully.
The to and by qualifier keywords are treated as general expression operators for
purposes of precedence (although they are only permitted in combination with
Event Processing
Events related to network activity are processed using two separate queues. One
queue serves the following events related to incoming network messages:
nv_update_occurs
msg_arrives
online
offline
wink
The other queue serves the remaining network events pertaining to completion
events and responses:
nv_update_completes
nv_update_succeeds
nv_update_fails
msg_completes
Reset Event
The reset event is TRUE the first time this event is evaluated after the Neuron
Chip or Smart Transceiver is reset for any reason. Note that I/O object and
global variable initializations are performed before processing any events. The
reset event task is the first task to be executed after reset of the Neuron Chip or
Smart Transceiver.
The reset event task executes only if the device is in the configured state (that is,
if the device is not applicationless, hard-offline, or unconfigured). Also, the reset
event task runs when the device is unconfigured if the directive #pragma
run_unconfigured is specified in the application program. The task runs
regardless of whether the device is soft-offline or not. The soft-offline state is not
reset-retained, so the only case where this is meaningful is when the device
transitions from unconfigured or hard-offline to configured state after a reset, as
would typically happen during initial commissioning. In this case, the device
executes the reset task followed by the offline task.
A reset occurs as a natural part of the process of commissioning a LONWORKS
device, and the reset process includes the execution of the reset event task. The
device undergoes a state transition to complete the commissioning process, and
that state transition can only be completed after the reset event task has been
executed. Consequently, you should keep the reset event task short so that the
device can be commissioned at maximum speed. You must keep the total reset
event task processing time under 18 seconds to prevent commissioning failures.
Reset event task processing time includes Neuron firmware initialization time as
described in the Smart Transceivers databooks.
Interrupts
Neuron C supports application-specific asynchronous interrupts from various
interrupt sources, and provides a semaphore for synchronization in a
multiprocessing environment.
Each interrupt statement consists of an interrupt clause, followed by an
interrupt task. Unlike when statements, which can include more than one when
clause, interrupt statements support only a single interrupt clause.
The interrupt clause defines the interrupt source and the conditions that trigger
the interrupt. The interrupt task contains code that runs as a result of the
interrupt.
Example:
interrupt(IO_3, clockedge(-)) {
...
}
Interrupt clauses support interrupt requests from signal conditions on I/O pins,
from timer or counter I/O objects, or from the high-resolution system timer.
A simple __lock{ } construct implements synchronization through a hardware
semaphore.
See Interrupts on page 153 for more information.
Function Prototypes
Neuron C requires the use of function prototypes if a function is to be called
before it is defined. Examples of valid prototypes include the following:
void f(void);
int g(int a, int b);
The following are not considered prototypes because they do not have argument
lists. They are merely forward declarations:
void f();
g(); // defaults to 'int' return value
If you define a function before you call it, Neuron C automatically creates an
internal prototype for you. Only one prototype is created for a given function.
The following examples are technically not prototypes, but Neuron C creates
function prototypes for them:
g (a,b)
int a;
int b;
{ /* body */ }
Although Neuron C can create prototypes, it does not employ the ANSI C
Miranda prototype rule. According to the Miranda prototype rule, if a function
call does not already have a prototype, a prototype is automatically created for it.
In Neuron C, a function prototype is automatically created only when the
function is defined.
Timers
Two types of software timer objects are available to a Neuron C application:
millisecond timers and second timers. The millisecond timers provide a timer
duration of 1 to 64,000 milliseconds (or .001 to 64 seconds). The second timers
provide a timer duration of 1 to 65,535 seconds. For more accurate timing of
durations of 64 seconds or less, use the millisecond timer. These timers are
separate from the two hardware timer/counters in the Neuron core (see also
Input Clock Frequency and Timer Accuracy on page 36).
For Series 5000 chips, a high-resolution hardware timer is also available. You
can program an interrupt handler to asynchronously handle interrupts that occur
based on this hardware timer; see Interrupts on page 153 for more information.
Declaring Timers
A maximum of 15 timer objects (total of both types) can be defined within a single
program. A timer object is declared using one of the following:
mtimer [repeating] timer-name [=initial-value];
stimer [repeating] timer-name [=initial-value];
mtimer Indicates a millisecond timer.
stimer Indicates a second timer.
repeating An option for the timer to restart itself automatically upon expiration.
With this option, accurate timing intervals can be maintained even if
the application cannot respond immediately to an expiration event.
timer-name A user-supplied name for the timer. Assigning a value to
this name starts the timer for the specified length of time (the
specified time is in seconds for an stimer and milliseconds for an
mtimer). A timer that is running or has expired can be started over
by assigning a new value to this name. The timer object can be
evaluated while the timer is running, and it indicates the time
remaining. Setting the timer to 0 turns the timer off. No timer
expiration event occurs for a timer that has been turned off (see the
description of the timer_expires event described in the Neuron C
Reference Guide).
Examples
An example of declaring a timer object and assigning a value to it is shown below:
// start timer with value of 5 sec
stimer led_timer = 5;
An example of turning a timer off is shown below:
stimer led_timer;
when (some-event)
{
led_timer = 0;
}
An example of evaluating the value of a running timer is shown below:
stimer repeating led_timer;
when (some-event)
{
time_remaining = led_timer;
...
}
Note: When setting and examining timers in the NodeBuilder debugger, certain
inaccuracies could occur. When a timer is set during program execution and is
examined while the program is halted (includes single stepping and breakpoints),
the timer value can be as much as 200 milliseconds larger than the actual time
until expiration. No such inaccuracy exists on a timer that is allowed to run
without a debugger halt.
Input/Output
Each Neuron Chip and each Smart Transceiver has a variety of built-in electrical
interface options for performing input and output (I/O) functions. Before
performing I/O, you must first declare the I/O objects that monitor and control
the 11 or 12 Neuron Chip or Smart Transceiver I/O pins, named IO_0, IO_1, ...,
IO_11.
To perform I/O, you normally use the built-in I/O functions: io_in( ), io_out( ),
io_set_direction( ), io_select( ), io_change_init( ), and io_set_clock( ). The
io_out_request( ) function is used to perform I/O with the parallel I/O object. I/O
Device Self-Documentation
You can include a text string that describes your device in your application. This
text string can be accessed by any network management tool, and can be used by
a network integrator to verify that they have the correct device when designing
in or installing your device. This text string is appended to the device self-
documentation (SD) string. The Neuron C compiler automatically generates a
portion of the SD string that documents the functional profiles that are
implemented by the functional blocks in your application. You can add
additional text for the SD string using the following compiler directive as
described in the Compiler Directives chapter of the Neuron C Reference Guide:
#pragma set_node_sd_string C-string-const
Examples
This section presents three complete programs that illustrate Neuron C
capabilities and good coding style. The examples are:
1 Thermostat interface
2 Simple light dimmer interface
3 Seven-segment LED display interface
ontime
The example also uses a shaft encoder generating a quadrature input as a dial to
select a new temperature setting (see Figure 2). The quadrature input object
type is used with the io_update_occurs event. The input value of the input object
represents the change in rotational offset since the last input. Shaft encoders
typically generate offsets of 16 to 256 counts per 360 degrees rotation. The
io_update_occurs event evaluates to TRUE only when a nonzero offset has been
measured. In the following application, the task associated with the when
(io_update_occurs...) clause is executed only when the quadrature input dial has
moved from the previously measured position.
enum {
OFF, HEATING, COOLING
} equip = OFF; // current state of HVAC equipment
/////////////////////////////////////////////////////
// I/O update task -- read quadrature encoder
// A quadrature input is used as a dial to select a new
// temperature setting.
when (io_update_occurs(ioShaftIn)) {
// An update occurs for a quadrature I/O object when the
// accumulated offset is nonzero. The value is placed in
// 'input_value' by the io_update_occurs event.
desiredTemp += input_value; // Assumes no overflow
desiredTemp = min(DESIRED_TEMP_MAX, desiredTemp);
case OFF:
if (newTemp < desiredTemp - BAND_SIZE) {
equip = HEATING; // if too cold, then
io_out(ioHeatingOn, TRUE); // turn on heater
} else if (newTemp > desiredTemp + BAND_SIZE) {
equip = COOLING; // if too hot, then
io_out(ioCoolingOn, TRUE); // turn on cooler
}
break;
case COOLING:
if (newTemp < desiredTemp) { // if too cold
equip = OFF; // turn off cooler
io_out(ioCoolingOn, FALSE);
}
break;
}
}
///////////////////////////////////////////////////////
// Reset task -- Set the repeating timer to 300 seconds
when (reset) {
tmCheckHeatOrCool = 300; // 5 minutes, repeating
}
Dimmer Switch
Zero-Crossing Neuron
Detector Chip
110VAC IO_6
Triac IO_0
Trigger
IO_4
Shaft
Encoder
IO_5
currentBrightness -= input_value;
R10 Multi-Character
LED Display Driver
Fixed Timers
In general, timers discussed in this manual are of fixed duration unless noted
otherwise. The following timers are implemented in hardware and have periods
that are independent of the Neuron Chip or Smart Transceiver input clock
frequency. However, the accuracy of these timers is determined by the accuracy
and frequency of the input clock for the Neuron Chip or Smart Transceiver.
• Preemption mode timeout timer.
• Pulsecount input timer. Timer used to determine the counting interval
for the pulsecount input object. The interval is (223)/107 (approximately
0.8388608) seconds.
0.063 — 80 MHz
0.125 — 40 MHz
1 10 MHz 5 MHz
2 5 MHz —
4 2.5 MHz —
8 1.25 MHz —
16 625 kHz —
Repeating Timers
For repeating timers, there is no cumulative drift other than that produced by
the difference in D and E. The Nth timeout for repeating timers occurs in the
range of LR to HR, where:
ER = E * N
and
LR = ER - (11*S + 1)
HR = ER + (11*S + 1)
For repeating timers, intermediate timeout events are lost if the following is true:
abs(AR - ER ) ≥ E
ER - AR > E
where AR is the actual duration of the repeating timer.
E1 E1
L1
H2
E2
L2
Delay Functions
Three functions allow an application to perform timing directly by suspending
execution for a given time. These functions provide a concise way to perform
timing in-line:
delay( )
msec_delay( )
scaled_delay( )
The delay( ) function produces a delay of fixed duration that is independent of
input clock speed. This function can be used with the wink feature and for I/O
debouncing. Its prototype is the following:
void delay (unsigned long count);
The scaled_delay( ) function produces a delay with a duration that scales with
input clock speed. Its syntax is:
void scaled_delay (unsigned long count);
count A value between 1 and 33,333. See the Neuron C Reference Guide for
the formula used in determining the duration of the delay.
Overview
As described in Chapter 1, Overview, a network variable is an object that
represents a data value and may be connected to multiple devices on a network.
How many network variables a Neuron-hosted device can support depends on the
device’s memory map, the system firmware, and the development tool version, as
shown in Table 6.
Table 6. Network Variable Limits
NodeBuilder
Neuron System Development Tool Maximum Number of
Firmware Version Version Network Variables
3.1 or earlier 62
Version 16 or later
FX or later 254
The maximum number of network variables for applications developed with the
Mini EVK Evaluation Kit or the Mini FX Evaluation Kit is 32. The limits for
host-based applications depend on the development product used.
Important: The NodeBuilder CodeWizard declares network variables so that
they are placed in RAMNEAR memory by default. However, because of the
larger number of network variables that are available for devices built with the
NodeBuilder FX Development Tool, your device could run out of available
RAMNEAR memory. In this case, declare as many network variables as
necessary to use RAMFAR memory:
1. Within the NodeBuilder CodeWizard, right-click the network variable
and select Properties to open the NV Properties dialog.
2. Within the NV Properties dialog, click Advanced to open the Advanced
NV Properties dialog.
3. Within the Advanced NV Properties dialog, select far. Click OK to close
the dialog.
4. Within the NV Properties dialog, click OK to close the dialog.
Network variables are defined within the program that runs on an individual
Neuron Chip or Smart Transceiver. As an example, consider a lamp program
with one network variable, named nviLamp (see Figure 6 on page 46). Also,
consider a switch program with one network variable, named nvoSwitch. The
same lamp program is installed on each of the three lamp devices, and the same
switch program is installed on each of the two switch devices in the figure.
switch1/nvoSwitch lamp1/nviLamp
lamp2/nviLamp
when (nv_update_occurs(nviLampState)) {
when (nv_update_fails(nvoSwitch))
{
// take some corrective action
}
Here is an example of testing for network update failure and success:
boolean heater_failed;
network output SNVT_switch nvoHeater;
when (nv_update_fails(nvoHeater))
{
heater_failed = TRUE;
// remember update failure
}
when (nv_update_succeeds(nvoHeater))
{
Tradeoffs
Using comprehensive completion event testing for processing network variable
completion events within a program requires more code space and is less efficient
than using partial completion event testing. If you choose a comprehensive
completion event testing feature, such as checking nv_update_completes, you are
limited to comprehensive completion event testing features for whichever
network variable’s events in which you are interested. For example, within a
program using comprehensive completion event testing, you cannot simply check
for nv_update_fails, because that feature applies only to partial completion event
testing.
when (reset) {
// set up timer for delayed power-up polling:
tDelayedPolling = 4ul * random(); // >= 1 second
... // other reset processing
}
when (timer_expires(tDelayedPolling)) {
poll(nviCooling);
...
}
when (nv_update_occurs(nviCooling)) {
...
}
Here is a lamp program that includes a poll of the input network variable
nviLampState after a reset event. The device obtains the most recent value of
nviLampState, and then uses that value after reset.
// LAMP.NC -- Sample lamp actuator program,
// polls the switch on reset
when (reset) {
// set up timer for delayed power-up polling:
tDelayedPolling = 4ul * random(); // >= 1 second
... // other reset processing
}
when (timer_expires(tDelayedPolling)) {
poll(nviLampState);
}
////////////////////////////////////////////////////////
// Reset and timer task
// request last value from any switch attached
when (reset) {
tmPoll = 4ul * random(); // >= 1 second
}
when (timer_expires(tmPoll) ) {
poll(nviLampState);
}
Listing 2. Switch Program Using Polling
// SWITCH.NC -- Sample switch sensor program
// Only transmits switch state when polled by the lamp
when (reset) {
io_change_init(ioButton);
... // other reset processing
}
when (timer_expires(heartbeat))
{
propagate(nvoTemp);
}
The propagate( ) function can also be useful where pointers are used to update
output network variables. For example, assume that some function, f( ),
calculates a complicated set of values and places them in a network variable
structure. Assume the function is designed to operate on several similar such
variables within a device, thus the function is passed a pointer to each variable.
For efficiency, it might be best to code this function to operate on the variables
through a pointer reference. However, the Neuron C compiler cannot distinguish
between a pointer to a regular application variable, and a pointer to a network
variable. Thus, updates to a network variable through a pointer do not trigger an
implicit propagation, and an explicit propagation is required.
Furthermore, because of the inability to distinguish pointers to network
variables, Neuron C treats pointers to network variables as pointers to const
data, thus avoiding the problem of a modification to the variable through the
pointer. In Neuron C, removal of the const attribute is not normally permitted.
However, the #pragma relaxed_casting_on directive directs the compiler to
permit this cast. Casting can either be explicit, or implicit by variable
assignment or function parameter passing.
Example:
network output SNVT_temp_f nvoTemp;
//
// the hypothetical function f() could be supplied with a
// binary (pre-compiled) function library for easy re-use,
// of for protection of intellectual property (function
// f’s algorithm).
//
extern void f(SNVT_temp_f* pNv);
when (some-event)
{
#pragma relaxed_casting_on
// Without pragma above, this would result in
// an error, because the address of a network
// variable is treated as 'const <type> *'.
// Passing such a type as the function parameter
// results in an implicit cast, since the function
// prototype defines the variable as '<type> *'.
f((SNVT_temp_f*)&nvoTemp);
propagate(nvoTemp); // Explicit propagation needed
// because f() modified nvoTemp by pointer.
}
when(nv_update_occurs(nviCurrent)) {
nvoValve = control_algorithm(nviCurrent);
}
When this device powers up, it simply waits for the current temperature input
value to change. After a new value is received, the device updates the
nvoValveDriver output network variable, and the valve moves to a new position.
For many devices, this model is sufficient. The temperature sensor’s reading
changes over time, and most sensors also implement periodic heartbeats at a
configurable interval. When the heartbeat interval expires, or the actual
temperature reading changes over the the threshold of a hysteresis, the sensor
updates its output network variable, the room temperature controller takes
appropriate action, and the heating valve is adjusted accordingly. The devices in
this network respond to external events and act as soon as necessary.
Some devices, however, need to produce up-to-date control values sooner after
power-up or reset, or need to ensure a consistent and up-to-date set of input
network variables. Consider, for example, an enhanced room temperature
controller. This new controller also accepts a temperature setpoint value through
an input network variable.
Example:
network input SNVT_temp nviCurrent;
network input SNVT_temp nviSetpoint;
network output SNVT_volt nvoValve;
when(nv_update_occurs(nviSetpoint))
when(nv_update_occurs(nviCurrent)) {
nvoValve = control_algorithm(nviCurrent, nviSetpoint);
}
However, this example is flawed, because it cannot successfully compute a new
control value unless both input network variables have been updated. There are
several ways to solve this problem:
• Track the last-known good value using a suitable type of non-volatile
memory or a battery back-up device design.
when(nv_update_occurs(nviCurrent)) {
lastGoodCurrent = nviCurrent;
nvoValve = algorithm(lastGoodCurrent, lastGoodSetpoint);
}
when(nv_update_occurs(nviSetpoint)) {
lastGoodSetpoint = nviSetpoint;
nvoValve = algorithm(lastGoodCurrent, lastGoodSetpoint);
}
• After power-up or reset, track which network variable has been updated,
and execute the algorithm only after all required input data has arrived.
Example:
unsigned received;
when(nv_update_occurs(nviCurrent)) {
received |= CURRENT_OK;
if (received & ALL_OK == ALL_OK) {
nvoValve = algorithm(lastGoodCurrent, lastGoodSetpoint);
}
}
when(nv_update_occurs(nviSetpoint)) {
received |= SETPOINT_OK;
if (received & ALL_OK == ALL_OK) {
nvoValve = algorithm(lastGoodCurrent, lastGoodSetpoint);
}
}
when(reset) {
poll(nviCurrent);
mtimer powerupDelay;
when(reset) {
powerupDelay = 16ul * random() + 500ul;
// 0.500 to 4.5s delay
}
when(timer_expires(powerupDelay)) {
poll(nviCurrent);
poll(nviSetpoint);
}
when(nv_update_occurs(nviSetpoint))
when(nv_update_occurs(nviCurrent)) {
nvoValve = control_algorithm(nviCurrent, nviSetpoint);
}
The best option, where appropriate, is to use poll-free techniques. Tracking of
incoming updates, keeping records of last known-good-values or use of
heartbeating sensors are all good tools to solve the problem of initial network
variable updates for many devices.
Example:
network input SNVT_alarm nviAlarmArray[50];
SNVT_alarm alarm_value;
unsigned int alarm_device;
when (nv_update_occurs(nviAlarmArray))
{
alarm_device = nv_array_index;
alarm_value = nviAlarmArray[alarm_device];
Example:
network input SNVT_alarm nviAlarm;
SNVT_alarm alarm_value;
nv_in_addr_t alarm_device_addr;
when (nv_update_occurs(nviAlarm)) {
alarm_device_addr = nv_in_addr;
alarm_value = nviAlarm;
// Process alarm_device_addr and alarm_value
// Look up alarm_device_addr in a configuration
// property set by a plug-in at installation time
}
Authentication
Authentication is a special form of an acknowledged service between one writer
device and from 1 to 63 reader devices. Authentication is used by the reader
devices to verify the identity of the writer device. This type of service is useful,
for example, if a device containing an electronic lock receives a message to open
the lock. By using authentication, the electronic lock device can verify that the
“open” message comes from the owner, not from someone attempting to break
into the system.
Authentication doubles the number of messages per transaction. Authentication
can be used with acknowledged updates or network variable polls. It cannot be
used with unacknowledged or repeated updates. An acknowledged message
normally requires two messages, an update and an acknowledgment. An
authenticated message requires four messages, as shown in Figure 8 on page 68.
This could affect system response time and capacity.
The following sections describe how to set up devices to use authentication and
how authentication works.
ACKD Message or
1 Request
Device A Device B
(Writer) 2 Challenge (reader)
3 Reply to challenge
4 ACK or Response
( )
scaled = a * 10 b * (raw + c )
Your application can convert the raw data of a changeable type input network
variable, internally, to an actual scaled value for use as a floating-point data
item, for example, using the above formula. To convert the data back to a raw
value for an output network variable, use the following inverted scaling formula:
⎛ scaled ⎞
raw = ⎜ b ⎟
−c
⎝ a * 10 ⎠
You can use cast operations and pointer manipulations to handle type changes.
See Changeable-Type Example on page 75 for an example.
If a network variable type or size is changed and that network variable is a
member of an inheriting configuration property’s application set, and that
property is implemented as a configuration network variable, then the
application must process the same type or length changes that were performed on
the network variable for the configuration network variable.
However, if the configuration property is implemented within a configuration file,
no change to the configuration file is required. The configuration file states the
configuration property’s initial and maximum size (in the CP documentation-
string length field), and LNS derives the current and actual type for type-
inheriting CPs from the associated network variable.
By setting the functional block status, the rest of the functional blocks on
your device can continue to function normally. You can use both methods
to provide a more precise indication of the error to a network integrator.
See Chapter 5, Using Functional Blocks to Implement a Device Interface,
on page 101, for more information on using functional blocks.
• Reset the SCPTnvType value to the last known good value.
• Reset all other housekeeping data, if any, so that the last known good
type is re-established.
In the interest of future-proof implementations, the application should be sure to
reject all change requests to unknown types, as shown in the changeable-type
example below.
Changeable-Type Example
The following code sample shows a typical implementation of a changeable-type
network variable. It implements nvoVolt as a changeable-type output network
variable. This example uses utility functions, such as getObjStatus( ),
updateNode_Status( ), and setFblockDisable( ). These utility functions are part
#pragma relaxed_casting_on
#define TYPE_ERROR 1
#define NV_LENGTH_MISMATCH 2
void changeLength(void) {
if ((nvoVolt::nvType.type_category != NVT_CAT_NUL)
&& (memcmp((void*)&nvTypeLastGood,(void*)&nvoVolt::nvType,
sizeof(SCPTnvType)) != 0)) {
nvoVolt::nvType = nvTypeLastGood;
error_log(TYPE_ERROR);
getObjStatus(fbSensor::global_index)->invalid_request
= TRUE;
updateNode_Status();
setFblockDisable(fbSensor::global_index, TRUE);
case NVT_CAT_SIGNED_LONG:
case NVT_CAT_UNSIGNED_LONG:
case NVT_CAT_FLOAT:
nvTypeLastGood = nvoVolt::nvType;
break;
case NVT_CAT_INITIAL:
nvoVolt::nvType.type_length = sizeof(nvoVolt);
nvTypeLastGood = nvoVolt::nvType;
break;
default:
nvoVolt::nvType = nvTypeLastGood;
error_log(TYPE_ERROR);
getObjStatus(fbSensor::global_index)->invalid_request
= TRUE;
updateNode_Status();
} // end of switch
} // any change at all
} // function changeLength()
if (uNvIndex == fbSensor::nvoValue::global_index) {
// Return current length for our example NV, or return
// 0xFF to indicate the NV has the initial length:
if (nvTypeLastGood.type_category != NVT_CAT_INITIAL
&& nvTypeLastGood.type_category != NVT_CAT_NUL) {
// this is a distinct current length:
uResult = nvTypeLastGood.type_length;
}
}
return uResult;
}
// Triggered by some appropriate I/O event, timer, or network event,
// the application will need to process data for the changeable-type
// network variable. This example does not include an algorithm that
// performs numeric operations using the changeable-type data, but two
// conversion routines are shown that convert the current type of
// the changeable network variable into a float_type variable for
// internal use in such numeric operations, and vice versa.
nvLocal.xInitial = nvoVolt;
switch (nvoVolt::nvType.type_category) {
case NVT_CAT_SIGNED_LONG:
// Current type is signed long. Convert to float.
fl_from_slong(nvLocal.sLong,pFloat);
bProcessABC = TRUE;
break;
case NVT_CAT_UNSIGNED_LONG:
// Current type is unsigned long. Convert to float.
fl_from_ulong(nvLocal.uLong,pFloat);
bProcessABC = TRUE;
break;
case NVT_CAT_INITIAL:
// Fall through to float.
case NVT_CAT_FLOAT:
// Float is current. No conversion is required, just
// copy data into local variable.
*pFloat = nvLocal.xInitial;
break;
default:
// Unsupported type. The changeLength() handler should
// have recognized this and rejected the type earlier.
// Log this application error and set the device offline:
error_log(TYPE_ERROR);
go_offline();
} // switch
if (bProcessABC) {
boolean bConversionOK;
boolean bProcessABC;
bConversionOK = TRUE;
bProcessABC = nvoVolt::nvType.type_category == NVT_CAT_SIGNED_LONG
|| nvoVolt::nvType.type_category == NVT_CAT_UNSIGNED_LONG;
if (bProcessABC) {
// TODO: if needed by the application algorithm, revert the
// conversion done in GetCurrent() by using the following
// formula:
// raw = (*pFloat / (A * 10**B)) - C
// See GetCurrent(), above, for more details.
}
switch (nvoVolt::nvType.type_category) {
case NVT_CAT_SIGNED_LONG:
// Current type is signed long. Convert from float.
nvLocal.sLong = fl_to_slong(pFloat);
break;
case NVT_CAT_UNSIGNED_LONG:
// Current type is unsigned long. Convert from float.
nvLocal.uLong = fl_to_ulong(pFloat);
break;
case NVT_CAT_INITIAL:
// Fall through to float.
case NVT_CAT_FLOAT:
// Float is current. No conversion is required, just
// copy data into local variable.
nvLocal.xInitial = *pFloat;
break;
default:
// Unsupported type. The changeLength() handler should
// have recognized this and rejected the type earlier.
// Log this application error and set the device offline:
error_log(TYPE_ERROR);
go_offline();
bConversionOK = FALSE;
} // switch
if (bConversionOK) {
// Update the actual network variable in case the conversion
// was OK (current type is in fact supported).
// A more generic implementation of these conversion functions
// is likely to use a pointer to the changeable type network
// variable's initial type as a second argument, thus allowing
// the SetCurrent() and GetCurrent() functions to be used for
// all changeable type NVs of the same initial type.
// This approach is likely to require explicit calls to the
// propagate() function; see the Neuron C Reference Guide
nvoVolt = nvLocal.xInitial;
} // bConversionOK
} // SetCurrent()
device_properties {
cpLocation = { "Unknown" }
};
The device property list appears at file scope. This is the same level as a function
declaration, a task declaration, or a global data declaration. The device property
list begins with the device_properties keyword. It then contains a list of property
references, separated by commas. Each property reference must be the name of a
previously declared CP family or the name of a previously declared configuration
network variable. If the network variable is an array, a single array element can
be chosen as the device property, so an array index must be given as part of the
property reference in that case to identify the element. Alternatively, the entire
network variable array can be chosen as the device property, so no array index is
given in the property reference in that case.
Example of a CP network variable array element:
network input SCPTlocation cp cpLocation[5];
device_properties {
cpLocation[0] = { "Unknown" }
};
The example above implements a single device property of type SCPTlocation,
which is implemented by the first element of the configuration property network
variable array. The remaining four elements of that array are unused in the
above example.
In contrast, the following example illustrates the use of a configuration network
variable array as a single device property. The device property with internal
name cpOemType is a single-dimensional array of three elements, each of type
SCPToemType.
Example of entire CP network variable array as a single property:
network input SCPToemType cp cpOemType[3];
device_properties {
cpOemType = { "Label 1", "Label 2", "Label 3" }
};
Following the property-identifier, there can be an optional initializer, and an
optional range-mod. These elements are discussed in detail in the Neuron C
Reference Guide chapter on Configuration Properties and Network Variables.
device_properties {
cpSomeDeviceCp,
cpLocation,
cpPlacement // Conflicts with cpLocation
};
void f(void)
{
...
if (nvoValue::cpMaxSendT.seconds > 0) {
...
}
}
The particular CP family member is identified by a qualifier that precedes it.
This qualifier is called the context. The context is followed by two consecutive
colon characters, called the context operator, and then the name of the property.
Because there cannot be two or more properties with the same configuration
property type that apply to the same network variable, each property is unique
within a particular context. The context therefore uniquely identifies the
property. For example, a network variable array, nvoArray, with 10 elements,
could be declared with a property list referencing a CP family named cpXyz.
There would then be 10 different members of the cpXyz CP family, all with the
same name. However, adding the context, such as nvoArray[4]::cpXyz, or
nvoArray[j]::cpXyz, uniquely identifies the CP family member.
device_properties {
cpOemType = { "Label 1", "Label 2", "Label 3" }
};
void f(void)
{
if (strcmp(::cpOemType[0].ascii, "Demo") == 0) {
... // special demo mode
} else {
... // normal operation
}
}
Even though a configuration network variable can be uniquely accessed through
its variable identifier, it can also be accessed equally well through the context
expression, just like the CP family members.
When more than one syntactically correct method exists for accessing a
particular CP, these methods are equivalent. The compiler translates all
alternatives into the same, equally efficient, code. In such a case, you should
choose the syntax that best documents your application algorithm.
fblock SFPTopenLoopSensor {
nvoAmpere implements nvoValue;
} fbAmpereMeter;
The initial value (123 in the example) can only be provided in the instantiation of
the configuration property as discussed above, and not in the declaration,
because the type for cpDefaultOutput is not known until it is instantiated and
because it is a type-inheriting configuration property.
fblock SFPTopenLoopSensor {
nvoAmpere implements nvoValue;
} fbAmpereMeter;
A Neuron C program can also implement additional network variables in the
functional block that are not in the lists of mandatory or optional members of the
profile. Such additional network variable members beyond the profile are called
implementation-specific members. Declare these extra members in the member
list using the implementation_specific keyword, followed by a unique index
number, and a unique name.
Each network variable in a functional profile assigns an index number and a
member name to each profile network variable member, and the implementation-
specific member cannot use any of the index numbers or member names that the
profile has already used.
Example:
network output SNVT_amp nvoAmpere;
network output polled SNVT_time_stamp nvoInstallDate;
fblock SFPTopenLoopSensor {
nvoAmpere implements nvoValue;
nvoInstallDate implementation_specific(128)
nvoInstall;
} fbAmpereMeter;
The above example implements the nvoValue mandatory network variable of the
SFPTopenLoopSensor functional profile, and adds an implementation-specific
SNVT_time_stamp network variable with a member name of nvoInstall. The
member name, nvoInstall in this example, is typically used to refer to the
member network variable, as discussed in Accessing Members and Properties of a
Functional Block from a Program on page 110.
The name of the network variable, nvoInstallDate, however, is the name that is
exposed to the network integrator by means of network variable self-
documentation (SD) data and device interface files. In a network tool, the name
nvoInstall appears as the member of the functional block, wherever the network
tool uses the profile definition.
Important: Implementation-specific network variable or configuration properties
are no longer acceptable according to the rules of the interoperability application
layer guidelines, starting with version 3.4.
If you plan to submit your device for certification, you must perform one of the
following tasks:
• Remove the implementation-specific items from the interoperable
interface (for example, declare them as device network variables).
• Create a user-defined functional profile that includes the desired
additions, listed as mandatory or optional member NV or CP.
The implementation-specific NV member feature can also be used repeatedly to
add each element of an entire NV array to the functional block. One element is
fblock SFPTopenLoopSensor {
nvoAmpere implements nvoValue;
director MeterDirector;
} fbAmpereMeter;
See The Director Function on page 113 for more details about directors.
After the member list, the functional block declaration continues with the name
of the functional block itself. A functional block can be a single declaration, or it
can be a singly dimensioned array.
If the functional block is implemented as an array as shown in the example
below, then each network variable that implements a member of that functional
block must be declared as an array of at least the same size. When implementing
the fblock array’s member with an array network variable element, the starting
index of the first network variable array element in the range of array elements
must be provided in the implements statement. The Neuron C compiler
automatically adds the following network variable array elements to the fblock
array elements, distributing the elements consecutively.
Example:
network output SNVT_lev_percent nvoValue[6];
fblock SFPTanalogInput {
nvoValue[2] implements nvoAnalog;
} myFb[4];
You can provide an optional external name for each functional block. To specify
an external name, use the external_name keyword, followed by a string of up to
16 characters in parentheses. The string becomes part of the device interface
which is exposed to network tools.
Alternatively, you can provide an optional external name that is specified by a
language string in a resource file using the external_resource_name keyword. In
this case, the device interface information contains a scope and index pair (the
first number is a scope, then a colon character, then the second number is an
index). The scope and index pair identifies a language string in a resource file,
which a network tool can access for a language-dependent name of the functional
block. You can use the scope and index pair to reduce memory requirements and
fblock SFPTopenLoopSensor {
nvoAmpere[0] implements nvoValue;
} fbAmpereMeter[NUM_AMMETERS] external_name("AmpereMeter");
fblock SFPTopenLoopSensor {
nvoAmpere implements nvoValue;
} fbAmpereMeter external_name("AmpereMeter")
fb_properties {
cpDefaultOutput, // optional CP
cpDisplayBrightness // implementation-spec.
};
The example implements an open-loop sensor as an ampere meter. The nvoValue
mandatory network variable is implemented, but no optional network variables
are. The SCPTdefOutput optional configuration property is also implemented.
The names in the above example for the CP families (cpDefaultOutput and
cpDisplayBrightness) have no external relevance; these names are only used
within the device’s source code to reference the configuration property. See
Accessing Members and Properties of a Functional Block from a Program on page
110 and Accessing Members and Properties of a Functional Block from a Network
Tool on page 112 for more details.
fblock SFPTopenLoopSensor {
nvoAmpere[0] implements nvoValue;
} fbAmpereMeter[NUM_AMMETERS] external_name("AmpereMeter")
fb_properties {
cpGain,
static cpUpdateRate
};
Assume, furthermore, that the same device also contains a three-phase voltage
meter with an implementation that mirrors the one from the ampere meter.
And, assume there is a SCPTbypassTime configuration property that limits the
duration of a locally initiated bypass mode for all six meters.
The following example implements all six meters, implementing a global
SCPTbypassTime configuration property that is shared between all fblocks that
refer to it, and implementing two static SCPTupdateRate configuration
properties, shared among the members of the respective fblock array:
Example:
#define NUM_PHASES 3
fblock SFPTopenLoopSensor {
nvoAmpere[0] implements nvoValue;
} fbAmpereMeter[NUM_PHASES] external_name("AmpereMeter")
fblock SFPTopenLoopSensor {
nvoVolt[0] implements nvoValue;
} fbVoltMeter[NUM_PHASES] external_name("AmpereMeter")
fb_properties {
cpGain,
static cpUpdateRate,
global cpBypassTime
};
Scope Rules
When adding implementation-specific network variables or configuration
properties to a standard or user functional profile, you must ensure that the
scope of the resource definition for the additional item is numerically less than or
equal to the scope of the functional profile.
For example, if you add an implementation-specific network variable or
configuration property to a standard functional block (SFPT, scope 0), you must
define that configuration property with a standard type (SCPT), and use a
standard network variable type (SNVT) for the implementation-specific network
variable.
A second example: if you implement a functional block based on a manufacturer
scope (scope 3) resource file, you can add an implementation-specific network
variable or configuration property that is defined in the same scope 3 resource
file, and you can also add an implementation-specific network variable or
configuration property defined by a SNVT or SCPT.
You can add implementation-specific members to standard functional profiles
using inheritance by performing the following steps:
1 Use the NodeBuilder Resource Editor to create a user functional profile
with the same functional profile key as the standard functional profile
you wish to inherit from.
2 Set Inherit Members from Scope 0 in the functional profile definition.
This setting makes all members of the standard functional profile part of
your user functional profile.
3 Declare a functional block based on the new user functional profile.
4 Add implementation-specific members to the functional block. These
members can be implemented using user-defined UNVT or UCPT types,
themselves defined at the same scope as the inheriting functional profile.
Important: Implementation-specific network variable or configuration properties
are no longer acceptable according to the rules of the interoperability application
layer guidelines, starting with version 3.4.
Alternatively, you can create a functional profile that inherits members from a
standard functional profile, and add your own profile-specific members to the
functional profile, by performing the following steps:
fblock SFPTopenLoopSensor {
nvoAmpere[0] implements nvoValue;
} fbAmpereMeter[NUM_AMMETERS] external_name("AmpereMeter")
fb_properties {
cpGain, // Each property is an array [4]
static cpUpdateRate
};
All of the following constructs are examples for valid code:
nvoAmpere[2] = 123;
fbAmpereMeter[2]::nvoValue = 123;
fbAmpereMeter[0]::cpGain[i].multiplier = 2L;
nvoAmpere[2]::cpMaxSendTime.seconds = 30;
fbAmpereMeter[2]::nvoValue::cpMaxSendTime.hour = 0;
z = ((SCPTmaxSndT *)&nvoAmpere[2]::cpMaxSendTime)->day;
Pointers can be used with CP family members as shown; however, the
configuration properties are stored in EEPROM. This causes the compiler to
apply special rules as described for the #pragma relaxed_casting_on directive in
the Neuron C Reference Guide.
Because cpGain is a static configuration property, the following expression is
always true:
fbAmpereMeter[0]::cpGain[i].multiplier ==
fbAmpereMeter[1]::cpGain[i].multiplier
The following expressions are incorrect and cause a compiler error:
// '.' instead of '::'
fbAmpereMeter[0].cpGain[i].multiplier = 123;
fblock . . . {
/* Member NVs, “implements” . . . */
director myDirector;
} myFB;
when (nv_update_occurs)
{
fblock_director(fblock_index_map[nv_in_index],
CMD_NV_UPDATE);
}
There are no limitations on how you use a director function or how you interpret
the second parameter to the director function. The director function is a useful
means to create Node Object implementations, but you can extend its usage as
well.
fblock ... {
...
} fbB fb_properties {
global cpGain // shared by fbA and fbB
};
fblock ... {
...
} fbC[5] fb_properties {
static cpGain // shared among fbC[0]..fbC[4]
};
The rules and considerations for shared configuration properties that are
outlined in Chapter 4, Using Configuration Properties to Configure Device
Behavior, on page 83, apply to functional blocks as well as network variables.
See Sharing of Configuration Properties on page 96 for more details.
Hardware Hardware
MAC MAC
Network Network
Scheduler Scheduler
Application Application
= data
Constructing a Message
You can construct an application message using the msg_out outgoing message
object. This definition is built into Neuron C. Use the msg_send( ) function to
send the message. You can only construct one outgoing message (or response)
struct {
boolean priority_on; // TRUE if a priority message
// (default:FALSE)
msg_tag tag; // message tag (required)
int code; // message code (required)
int data[MAXDATA] // message data (default:none)
boolean authenticated; // TRUE if to be authenticated
// (default:FALSE)
service_type service; // service type (default:ACKD)
msg_out_addr dest_addr; // see include file msg_addr.h
// (optional field)
} msg_out;
priority_on When set to TRUE, sends the message as a priority message. Specify
FALSE, or do not assign to this field, if the message is not a priority
message. If used, this field must be the first field set in the message
object, even before the tag. The default is FALSE (that is,
nonpriority).
tag A message tag identifier for the message. This field is required. See
Message Tags on page 122.
code A numeric message code. This field is required. See Message Codes
on page 123.
data The application's data. This field is optional; a message can consist of
only a message tag and message code. Because of network buffer
overhead, MAXDATA must never exceed 228. MAXDATA is a
function of the app_buf_out_size pragma (see Chapter 8, Memory
Management, on page 173):
MAXDATA = app_buf_out_size – 6
or
MAXDATA = app_buf_out_size – 17
(if explicit addressing is used for messages or network variables in
this program)
Note: The Neuron firmware observes which locations in the data
array have assignments and automatically sets the length of the
outgoing message accordingly.
authenticated A TRUE value specifies that the message is to be
authenticated. You can specify FALSE, or not assign to this field, if
Note: To use this field, you must include the <addrdefs.h> and
<msg_addr.h> files.
Message Tags
A message tag is a connection point for application messages. Incoming
application messages are always received on a common message tag called
msg_in, but you must declare one or more message tags if outgoing explicit
messages are used. The incoming tag and each outgoing tag or tags can be
assigned a unique network address by a network tool.
A message tag declaration can optionally include connection information. The
syntax for declaring a message tag is as follows:
msg_tag [connection-info] tag-identifier [, tag-identifier ...];
The connection-info field is an optional specification for connection options, in the
following form:
bind_info (options)
The following connection options apply to message tags:
nonbind Denotes a message tag that carries no implicit addressing
information and does not consume an address table entry. It is used
as a destination tag when creating explicitly addressed messages.
rate_est (const-expr) The estimated sustained message rate, in tenths of
messages per second, that the associated message tag is expected to
transmit. The allowable value range is from 0 to 18780 (0 to 1878.0
messages/second).
max_rate_est (const-expr) The estimated maximum message rate, in tenths of
messages per second, that the associated message tag is expected to
transmit. The allowable value range is from 0 to 18780 (0 to 1878.0
messages/second).
tag-identifier A Neuron C identifier for the message tag.
a = 2 ( n / 8 ) −5
rounded to the nearest tenth. The actual value, a, produced by the formula, is in
units of messages per second.
You must assign a message tag to the msg_out.tag field for each outgoing
message. This specifies which connection point (corresponds to an address table
entry) to use for the outgoing message. After the tag field has been assigned, the
message must be either sent or cancelled.
Besides addressing, message tags are also used for correlating completion events
and responses with outgoing messages. For example, the following when clause
correlates a message completion event with a message sent by means of the tag1
message tag:
when (msg_completes(tag1))
By qualifying an event with a message tag, the event becomes TRUE only when
an event corresponding to that particular outgoing message occurs.
Message Codes
A message code is a numeric identifier for a message. Each application message
must include a message code that the receiving applications can use to interpret
the contents of the message.
Message codes are used by all LonTalk messages, not just application messages.
They fall into the ranges shown in Table 8. Codes 0-62 and 64-78 are for use by
applications. The lower range is used for proprietary application-specific
messages, and the upper range is used for proprietary application-level gateways
to other networks.
Table 8. Ranges for Message Codes
Network Variables 128 to 255 The lower six bits of the message
(0x80..0xFF) code contain the upper six bits of
the (14-bit) network variable
selector. The first data byte
contains the lower eight bits of the
selector.
#define MOTOR_ON 0
#define ON_FULL 100
msg_out.tag = motor;
msg_out.code = MOTOR_ON;
msg_out.data[0] = ON_FULL;
typedef enum {
MOTOR_FWD,
MOTOR_REV
} motor_dir;
struct {
long motor_speed;
motor_dir motor_direction;
int motor_ramp_up_rate;
} motor_on_message;
when(some_event) {
msg_out.tag = motor;
msg_out.code = MOTOR_ON;
motor_on_message.motor_direction = MOTOR_FWD;
motor_on_message.motor_speed = 500;
motor_on_message.motor_ramp_up_rate = 100;
memcpy(msg_out.data, &motor_on_message,
sizeof (motor_on_message));
msg_send();
}
Sending a Message
You can send and cancel sending a message using the following functions:
msg_send( )
msg_cancel( )
The msg_send( ) function has the following syntax:
void msg_send(void);
This function sends a message using the msg_out object (which must have
already been constructed prior to the call to the msg_send( ) function). It has no
parameters, and has no return value.
The following code fragment illustrates sending a message:
msg_tag motor;
#define MOTOR_ON 0
#define ON_FULL 100 // (100 percent)
Receiving a Message
You typically receive a message using the msg_arrives predefined event. You can
also use the msg_receive( ) function to receive a message.
when (msg_arrives(1))
{
io_out(sprinkler, ON);
}
when (msg_arrives(2))
{
io_out(sprinkler, OFF);
}
struct {
int code; // message code
int len; // length of message data
int data[MAXDATA]; // message data
boolean authenticated; // TRUE if message was
// authenticated
service_type service; // service type used by sender
msg_in_addr addr; // see <msg_addr.h> include file
boolean duplicate; // the message is a duplicate
unsigned rcvtx; // the message's receive tx ID
} msg_in;
Lamp Program
First, here is the program for the lamp devices:
// lamp.nc - Generic program for a lamp
// The lamp’s state is governed by an incoming
// application message
#define LAMP_ON 1
#define LAMP_OFF 2
#define OFF 0
#define ON 1
// I/O declaration
IO_0 output bit io_lamp_control;
when (msg_arrives) {
switch (msg_in.code) {
case LAMP_ON:
io_out(io_lamp_control, ON);
break;
case LAMP_OFF:
io_out(io_lamp_control, OFF);
break;
} //end switch
} //end when
Switch Program
Here is the program for the switch devices:
// switch.nc - Generic program for a switch
// Send a message when the switch changes state
#define LAMP_ON 1
#define LAMP_OFF 2
// I/O Declaration
IO_4 input bit io_switch_in;
// Event-driven code
when (reset) {
io_change_init(io_switch_in);
}
when (io_changes(io_switch_in)) {
// Set up message code based on the switch state
msg_out.code = (input_value == ON) ? LAMP_ON : LAMP_OFF;
Explicit Addressing
You can explicitly specify a destination address for application messages and
network variables using the data structures in the <msg_addr.h> and
<addrdefs.h> include files. To use explicit addressing for outgoing messages, you
must assign appropriate values to all applicable fields of one of the elements of
the dest_addr union in the msg_out object, prior to calling msg_send( ). The
message still needs a message tag, although no addressing information is derived
from the message tag. Thus, no matter how the message tag is bound, explicit
addressing overrides the address specified by the tag.
When you assign an explicit destination address, the message tag is only relevant
for correlation with response and completion event processing. However, if you
use a standard message tag, you still consume an address table entry, even if you
only use the message tag for explicitly addressed messages. To permit a more
optimal use of Neuron resources, use non-bindable message tags that carry no
addressing information and do not consume an address table entry. Use the
following syntax to declare a non-bindable message tag:
msg_tag bind_info(nonbind [, other-info]) tag-name;
Hardware Hardware
MAC MAC
Network Network
Scheduler Scheduler
Application Application
when (msg_completes(TAG1))
{
...
}
when (io_changes(dev2))
{
...
msg_out.tag = TAG2;
...
msg_send();
}
A third restriction applies to use of the unqualified completion event, which
implicitly refers to all messages. When you use the unqualified completion event,
you must process all acknowledged messages, either explicitly for each message
tag, or implicitly through use of an unqualified event each time a message is sent.
The following code shows correct processing of completion events by message tag:
int failures[2], success;
msg_tag TAG1, TAG2;
when (io_changes(toggle))
{
msg_out.tag = TAG1;
msg_out.code = TOGGLE_STATE;
msg_out.data[0] = input_value;
msg_send();
msg_out.tag = TAG2;
msg_out.code = TOGGLE_STATE;
msg_out.data[0] = input_value;
when (msg_fails(TAG1))
{
failures[0]++;
}
when (msg_fails(TAG2))
{
failures[1]++;
}
when (msg_completes)
{
msg_out.tag = t; // This sequence is not
// recommended.
msg_out.code = 1; // Causes a device reset
// if the system is
// already in preemption
// mode
}
Instead of using this sequence, build messages and call msg_send( ) in a task
with a when clause that does not use the msg_completes event. When you
update synchronous output network variables, preemption mode is entered at the
critical section boundary if there are insufficient application output buffers to
accommodate the updates. For example, if you update three synchronous output
network variables in a critical section and only two application output buffers are
available, preemption mode is entered upon leaving the critical section. The
application leaves preemption mode and returns to normal operation after all the
outstanding network variable updates are buffered.
When implicit buffer allocation is used (that is, building an explicit message
without calling msg_alloc( ) first), then preemption mode is entered upon the first
assignment to msg_out if no application output buffer is available. Preemption
mode ends as soon as a buffer becomes available (that is, when a completion
event is processed). While a device is in preemption mode, no outgoing network
variable updates occur, priority or otherwise. Thus, a program that expects
priority updates to occur within a bounded amount of time should not use
nonpriority synchronous network variables or messages with implicit buffer
allocation.
To allocate and free buffers explicitly, use the functions described in Allocating
Application Buffers on page 143.
You can detect whether or not a program is already in preemption mode with use
of the following function:
boolean preemption_mode (void);
This function returns TRUE if the device is in preemption mode. The application
must include the <status.h> file to use this function.
when (x==3)
{
// send a message
flush_wait();
msg_out.tag = motor;
msg_out.code = MOTOR_ON;
msg_send();
when (io_changes(switch1) to 0)
{
//send a request to the motor
msg_out.tag = motor;
msg_out.service = REQUEST;
msg_out.code = MOTOR_STATE;
msg_send();
}
The request is packaged as shown in Figure 11 on page 119. The application
program on the receiver device receives the request through a when clause (or
msg_receive( ) function) and must then formulate a response to this request, as
shown in Figure 13.
Hardware Hardware
MAC MAC
Network Network
Scheduler Scheduler
Application Application
= data
Constructing a Response
You can construct a response to a request message. As shown in Figure 13, the
response contains a data portion that is sent to the application processor of the
sender device. A response is different from an acknowledgment (Figure 12 on
page 131), which does not contain a data portion and is sent only to the network
processor on the sender device.
The name of the outgoing response object is resp_out. The response inherits its
priority and authentication designation from the request to which it is replying.
MAXDATA = app_buf_in_size – 6
or
MAXDATA = app_buf_in_size – 17
(if explicit addressing is used)
Note: The Neuron firmware observes which locations in the data array have
assignments and automatically sets the length of the outgoing message
accordingly.
Sending a Response
You can send a response with the resp_send( ) function. You must send
responses from the same critical section that processed the incoming request.
The response is constructed in the application input buffer in which the request
arrived. Therefore, after you start response construction, you can no longer
examine the incoming request. Also, no other intervening messages can be sent
or received. This restriction only applies to the case in which an outgoing
message uses an input application buffer.
The syntax for the resp_send( ) function is the following:
void resp_send (void);
This function sends a response using the resp_out object.
Note: While the response is constructed in the application input buffer by the
application, the network processor uses a network output buffer to construct the
response packet. So, the network output buffer must be sized to accommodate
outgoing responses in addition to other outgoing messages.
Receiving a Response
A program usually receives a response through the predefined event
when(resp_arrives). The resp_receive( ) function can also be used to receive a
response.
Format of a Response
The name of the incoming response object is resp_in.
The incoming response structure is predefined in the Neuron C Compiler as
follows:
struct {
int code; // message code
int len; // length of message data
int data[MAXDATA]; // message data
resp_in_addr addr; // explicit address - see the
// <msg_addr.h> include file
} resp_in;
code A numeric message code in the range 0 to 79. See Message Codes on
page 123.
len The length of the message data.
data The data contained in the message. This field is valid only if len is
greater than 0. MAXDATA is a function of the #pragma
app_buf_in_size (see Chapter 8, Memory Management, on page 173):
MAXDATA = app_buf_in_size – 6
or
MAXDATA = app_buf_in_size – 17
(if explicit addressing is used by any message or network variable in
this program)
addr An optional field in the incoming message that an application
program can use to determine the source and destination of the
message. You can find the definition of the type resp_in_addr in the
<msg_addr.h> include file.
Request/Response Examples
This example shows sending a request and asynchronously receiving the
responses. The code for receiving this request and responding to it follows in the
next example.
msg_tag tag1;
#define DATA_REQUEST 0
when (io_changes(toggle))
{
msg_out.tag = TAG1;
msg_out.code = DATA_REQUEST;
msg_out.service = REQUEST;
msg_send();
}
when (resp_arrives(TAG1))
{
if (resp_in.code == OK)
process_response(resp_in.data[0]);
}
Here is the code for the responder to this request:
#define DATA_REQUEST 0
#define OK 1
when (msg_arrives(DATA_REQUEST))
{
int x, y;
x = msg_in.data[0];
y = get_response(x);
resp_out.code = OK;
// msg_in no longer available
resp_out.data[0] = y;
resp_send();
}
The following example shows sending a request and receiving the responses
directly:
int x;
msg_tag motor;
#define MOTOR_ON 0
#define DO_MOTOR_ON 3
struct RespBuffer {
int code;
unsigned int len;
int data[MAXRESP];
} resp_buffer[16];
when (msg_arrives) {
struct RespBuffer *buf_p;
if (msg_in.service == REQUEST) {
buf_p = &resp_buffer[msg_in.rcvtx];
if (!msg_in.duplicate) {
int i;
Application Buffers
You can set the number of incoming and outgoing buffers available for use by a
Neuron C application during compilation. The defaults for all models of the
Neuron Chip and the Smart Transceivers, except the Neuron 3120 Chip and the
Neuron 3120E1 Chip, are:
• Two priority application output buffers
• Two nonpriority application output buffers
when (x == 2)
{
if(msg_alloc() == FALSE)
return;
msg_out.tag = motor1;
msg_out.code = MOTOR_ON;
msg_send();
if(msg_alloc() == FALSE)
return;
msg_out.tag = motor2;
msg_out.code = MOTOR_ON;
msg_send();
}
Initialization
Reset Task
Top of
Scheduling Loop
T T T T T
Figure 14. Neuron Firmware Scheduling of Nonpriority and Priority When Clauses
Note that application interrupts, supported by Series 5000 chips, execute
asynchronously, and are not governed by the scheduler. See Interrupts on page
153 for more information.
Incoming messages and network variable updates use application input buffers,
which are processed by when(msg_arrives) and when(nv_update_occurs) tasks,
respectively.
Scheduler Example
Turning on the reset mechanism ensures that events are processed in the order
intended. For example, you might want ensure that specific events are checked
first, followed by a catch-all event, as illustrated in this code fragment:
#pragma scheduler_reset
when (nv_update_occurs(nviSwitch1))
{
...
}
when (nv_update_occurs(nviSwitch2))
{
...
}
when (nv_update_occurs)
{ // provides a generic check
... // for all network variable
// updates
}
Updates received for nviSwitch1 cause both the first and third events to become
TRUE. Similarly, updates for nviSwitch2 cause the second and third events to
Bypass Mode
All scheduling of Neuron C programs, as described above, is event-driven and
handled by the scheduler. Within a program, however, you can choose when to
return control to the scheduler. The term bypass mode refers to a method of
programming in which one when clause always evaluates to TRUE and never
returns. In this case, a single task must handle all event processing.
You should use bypass mode rarely, and only in cases where you need a different
scheduling algorithm than that provided by the Neuron firmware scheduler.
While in bypass mode, your program is responsible for all event processing. You
define critical sections through the post_events( ) function (see the following
section), and then check for predefined events in if, while, and for expressions.
If your application runs on a Series 5000 device, consider using interrupts for
specific kinds of events, so that you do not need to use bypass mode for the
scheduler. See Interrupts on page 153 for more information.
when (online)
{
y(); // Start up again
}
The application has no means to refuse a change into the offline or online states,
respectively. The respective state becomes effective after the relevant task has
been completed, allowing the application to prepare for that state by disabling
peripheral hardware, stopping timers, and so on.
The device can change into the online state without the online when clause
evaluating to TRUE: If the device is being taken offline into the soft-offline state,
resetting the device loses, or discards, the soft-offline state and returns the device
to normal, online, operation. The technique shown below can be used to handle
this situation:
void HandleOnline (void)
{
...
}
when (reset)
{
// regular reset code here:
...
when (online)
{
HandleOnline();
}
Wink Event
You can use the wink event to perform an action in response to a wink network
management message from a network tool. A network tool can send a wink
message to a device to help a network integrator physically identify a particular
device. The wink event becomes TRUE any time a wink message is received by a
device, whether it is configured or unconfigured.
For an unconfigured device, I/O and variable initialization occur before the wink
event is evaluated. However, none of the initialization in the when (reset) task
has occurred. In addition, the scheduler is not running on an unconfigured
Interrupts
Series 5000 chips provide a number of hardware interrupts that are available to
a Neuron C application. At higher system clock rates, these interrupts run in a
dedicated processor on the chip, the interrupt service routine (ISR) processor. At
the lowest system clock rates (5 MHz and 10 MHz), these interrupts run in the
same processor as the Neuron C application, the application (APP) processor.
Running interrupts in their own ISR processor provides higher performance for
both the interrupt routines and the Neuron C application.
In addition, Series 5000 chips provide other hardware interrupts, such as those
related to the SCI UART or SPI I/O models, that are automatically handled by
the Neuron firmware and are not available to a Neuron C application.
This section describes the hardware interrupts that are available to a Neuron C
application.
Interrupt Sources
The hardware interrupt support provides the following interrupt sources for
application use:
• I/O interrupts
• Timer/counter interrupts
• Periodic system timer interrupts
The Neuron firmware, version 18 and later, combined with an application-specific
interrupt dispatcher automatically generated by the compiler, identifies the
interrupt source, manages the interrupt management flags, and calls the correct
application-specific interrupt task for each interrupt request. Interrupts are
dispatched in the order of source code declaration, so that interrupt tasks that
require the lowest latency and smallest jitter should be declared first, and
interrupt tasks that are least sensitive to latency and jitter should be declared
last.
The Series 5000 chips also supply one binary semaphore to support synchronized
access to shared resources between the application and the ISR processor. A
Neuron C application uses the __lock keyword to access this semaphore and
synchronize access to shared resources. See the Neuron C Reference Guide for
more information about this keyword, and see Sharing Data with an Interrupt
Task on page 161 for more information about using the semaphore.
Network I/O is not allowed from within an interrupt task, but your interrupt task
can access I/O devices and variables shared with the application.
In addition, you cannot use the NodeBuilder Debugger to set break points within
an interrupt task. See Debugging Interrupt Tasks on page 164 for suggestions
about how to debug an interrupt task.
Timer/Counter Interrupts
As described in the I/O Model Reference, the Series 5000 chip hardware supports
two hardware timer/counters:
• Timer/counter 1 is connected to IO_0 (output) and IO_4..7 (input) through
a multiplexer
• Timer/counter 2 is dedicated to IO_1 (output) and IO_4 (input)
Both timer/counters can generate one optional timer/counter interrupt each. The
interrupt occurs when the counter overflows or underflows, or when the timer
event latch triggers, depending on which I/O model the timer/counter is defined
for.
For timer/counter I/O models that use multiplexed I/O pins, a timer/counter
interrupt task runs when any of the pins within the defined I/O model meet the
critera for an interrupt.
Example:
interrupt (IO_0, clockedge(+)) { // rising edge of IO_0
...
}
interrupt(pulsecount_B){
// process the interrupt for the pulsecount
// multiplexed I/O object
}
interrupt(pulsecount_C){
// You cannot define a second interrupt task for the
// pulsecount multiplexed I/O object
// Compiler issues NCC#584:
// “The hardware timer/counter unit is already used with
// a different interrupt task”
}
Timer/counter interrupts for the following I/O models use overflow interrupt
triggering:
• Dualslope input
• Frequency output
• Infrared pattern output
• Oneshot output
• Pulsecount output
• Pulsewidth output
• Triggered count output
Note: Triggering the interrupt on overflow for the dualslope input model means
that the interrupt occurs at the end of the second integration period.
Timer/counter interrupts for the following I/O models use latch interrupt
triggering:
• Edgelog input
interrupt(repeating, “3456.789Hz”) {
...
}
Controlling Interrupts
By default, all application-defined interrupts are disabled, thus allowing the
device to complete its initialization or reset processing before being interrupted.
When the device is ready to receive interrupts, typically towards the end of the
when(reset) task, the application calls the following function:
void interrupt_control(unsigned irqSelect);
The irqSelect argument specifies the type of interrupts to enable. You can use
the following predefined symbols to specify the interrupt type:
#define INTERRUPT_IO 0x03
#define INTERRUPT_TC 0x0C
#define INTERRUPT_REPEATING 0x10
A value of zero (0x00) disables all interrupts. A value of -1 (0xFF) enables all
interrupt tasks defined for the application.
when(reset) {
// query node status:
status_struct status;
retrieve_status(&status);
when(offline) {
interrupt_control(0);
}
when(online) {
interrupt_control(INTERRUPT_IO | INTERRUPT_REPEATING);
}
If you call the interrupt_control() function for an interrupt type that does not
have a corresponding interrupt task defined, the call does nothing. However, if
you call the interrupt_control() function and there are no interrupt tasks defined,
the Neuron linker issues an error message.
If you need to disable or enable all interrupts at the same time, you can also use
the io_idis() function to disable interrupts and the io_iena() function to enable
interrupts. These functions have the following declaration:
void io_idis(void);
void io_iena(void);
void f() {
__lock {
globalVariable = ...;
}
}
interrupt(IO_3, clockedge(-)) {
__lock {
f();
}
}
Because the interrupt task acquires the semaphore and then calls function f(),
the second lock request (the __lock{ } construct within the function f()) can never
succeed. The chip resets after the watchdog timer expires, and a system error is
logged.
While an interrupt task cannot perform network activity, it can read and
evaluate values for network variables and configuration properties. You can
guard those operations with a __lock{ } statement to synchronize between the
interrupt task and the when tasks, but you cannot guard against asynchronous
updates to these variables as they arrive over the network.
In an application that requires an interrupt task to read such data, it is
recommended that the appropriate when task creates a locked copy of the
relevant data, which can be safely accessed from the interrupt task.
Interrupt Latency
Interrupt latency is the amount of time that elapses between the generation of an
interrupt by a device and the running of the associated interrupt task.
Variations in this latency for servicing interrupts can lead to jitter in output
signals generated by an interrupt task, or otherwise affect the algorithm
performed by the interrupt task. This section describes the latency required by
the system firmware, the Neuron C application, and the interrupt task.
When a Neuron C application contains one or more interrupt tasks, the Neuron C
compiler generates an interrupt dispatcher that is added to the application. The
system firmware calls this dispatcher when an interrupt is triggered.
When the system firmware receives an interrupt request from the hardware, the
firmware prepares to run the interrupt task. This preparation adds the following
latency to interrupt processing:
• 54 clock cycles if interrupt tasks run on the interrupt (ISR) processor
• 115 clock cycles if interrupt tasks run on the application (APP) processor
This preparation occurs once for each hardware interrupt request, regardless of
the number of interrupt tasks defined within the application. Interrupts run on
the ISR processor for system clock multiplier settings of 2 or higher (20 MHz or
higher), or interrupts run on the application processor for system clock multiplier
settings of ½ or 1 (5 or 10 MHz).
1 7 10
More than 1 9 16
After the interrupt task completes, the system firmware performs a number of
clean-up steps. For any given interrupt task, the clean-up steps add latency, as
shown in Table 12. As the table shows, the clean-up steps apply only to
timer/counter interrupt tasks.
Table 12. Clean-Up Latency
1 0 63
More than 1 0 66
Figure 15 on page 164 summarizes how the system firmware and interrupt
dispatcher add latency to interrupt processing. In the figure, the numbers are
the clock cycles required for each step, as defined above. The numbers N0, N1,
and N2 represent the number of clock cycles required to run an interrupt task
itself.
Sleep Mode
You can use sleep mode to place a Series 3100 Neuron Chip or Smart Transceiver
in a low-power state. To instruct a Series 3100 Neuron Chip or Smart
Transceiver to enter sleep mode, perform the following steps:
1 Flush all pending network variable updates as well as all outstanding
outgoing and incoming messages.
2 Put the Neuron Chip or Smart Transceiver into sleep mode after the
flush completes. The Neuron Chip or Smart Transceiver always wakes
up when the service pin is activated, or when there is activity on an I/O
pin (the pin selected is configurable) or on the communications channel,
or both.
Series 5000 chips do not support sleep mode.
Example:
mtimer m_30;
network output SNVT_switch nvoValue;
static SNVT_switch temp;
when (timer_exp(m_30))
{
nvoValue = temp;
flush(TRUE);
}
when (flush_completes)
{
sleep(COMM_IGNORE);
}
flush_completes Event
The following predefined event becomes TRUE when the flush completes:
flush_completes
This event becomes TRUE when all outgoing network buffers and application
buffers are free, no more incoming messages are outstanding, and no network
variable updates are outstanding.
Note: The flush_wait( ) function should not be used in preparation for putting
the device to sleep. The flush_wait( ) function does not check for outstanding
network variable updates or incoming messages.
when timer_expires(timer_2)
{
sleep(COMM_IGNORE, wakeup_pin1);
//or, sleep(COMM_IGNORE, IO_4);
}
You can force sleep mode even though the flush has not completed, as described
in Forced Sleep on page 168.
When an event occurs that wakes the Neuron Chip or Smart Transceiver, the
program resumes at the first statement after the sleep function call. If the
sleep( ) call is the last statement in a task, the program returns to the scheduler
after it wakes up.
A device wakes up whenever a packet is received by the transceiver (unless you
specified COMM_IGNORE). The device wakes up even if the packet is not
addressed to the device. You are responsible for putting the device back to sleep
if this occurs.
If a device sleeps for less than the receive timer duration and uses the
comm_ignore option, it could receive duplicate messages or network variable
update events. The default receive timer is set by a network tool during device
installation. The receive timer has a hard-coded minimum value of 768 ms. This
value can be increased by a network tool, depending on the network connections
to the device.
when (timer_expires(flush_timeout))
when (flush_completes)
{ // Ready to go to sleep since the flush
// either completed or timed out
flush_timeout = 0; // First, turn off timer
// if not expired
sleep(comm_ignore);
}
When you force sleep mode, the following occurs:
1 All pending network variable updates, outstanding application output
buffers, and outstanding network output buffers are not sent and freed.
2 If you specify the comm_ignore option, any incoming network buffers are
freed.
3 If any outstanding incoming application buffers remain, the device fails
to sleep (regardless of whether the COMM_IGNORE option was
specified). This feature prevents the device from receiving stale messages
when it wakes up. In the example above, the application would have 300
milliseconds to process any incoming messages already in the queue. In
addition, since the comm_ignore parameter was set to TRUE in the call to
flush( ), no new incoming messages would arrive. Thus, it is likely that
the device sleeps, assuming it processes, in the 300 msec prior to the
timeout, any incoming messages that were outstanding prior to the call to
flush( ).
Error Handling
You can take one or more of the following actions to recover from or report an
application error:
• Reset the device
• Restart the application
• Take the application offline
• Disable a functional block
• Change functional block status
System Errors
The Neuron firmware reports system errors using the same error log used to
report application errors. System errors include programming errors and
network errors and inconsistencies. As with application errors, network tools can
retrieve the last value from the error log using the query status network
diagnostic command.
System error numbers are in the range of 128 to 255; see the Neuron Tools Errors
Guide for an annotated list of system error messages.
RAM Use
RAM is used as follows:
code Size of code (for functions declared with ram keyword)
config_prop 0
cp_family 0
fblock 0
io_changes 3 bytes each (any type)
I/O object 0
msg_tag 0
mtimer 4 bytes each
The following sizes pertain to global and static data as declared in the program
(except for eeprom and config variables). These amounts also apply to network
variables.
char 1 byte
int 1 byte
enum 1 byte
long 2 bytes
structures Sum of the size of the elements. Each 8 bits (or fraction of
8 bits) of consecutive bitfields uses up a byte. No bitfield
can span a byte boundary. No padding is performed. The
float_type and s32_type extended arithmetic structures
each take 4 bytes.
unions Size of the largest element
EEPROM Use
Approximately 65 bytes of EEPROM is used for constant system overhead,
although this can vary depending on the firmware version. In addition,
EEPROM or flash memory is used as follows:
• Each domain table entry requires 15 bytes for configurable information,
to define the domain address, subnet number, device number, and
authentication key. A system can have a maximum of two domain table
entries and must have at least one domain table entry. The default is
two domain table entries. See Domain Table on page 190.
Figure 16. Off-Chip Memory for the Neuron 3150 Chip, the FT 3150 Smart Transceiver, the
Neuron 5000 Processor, and the FT 5000 Smart Transceiver
EEFAR
Neuron 3120E2 Chip
F000 EECODE On-Chip Memory
EFFF FFFF
RAMFAR Reserved
E800 F800
F7FF
EENEAR
Neuron 3120 Chip
On-Chip Memory EEFAR
FFFF
Reserved EECODE
F200 F000
F1FF
EENEAR EFFF
RAMNEAR
EEFAR RAMFAR
E800
EECODE
F000
EFFF 27FF
RAMNEAR ROM
(Neuron Chip Firmware)
0000
EC00 RAMFAR
27FF
ROM
(Neuron Chip Firmware)
0000
Figure 17. Memory Maps for the Various Chips, Showing Areas Defined by
the Linker
Memory Regions
The definitions of the three memory regions are as follows:
• ROM: Non-volatile memory initialized before program execution on a
device. ROM cannot be changed by the program. It is used for the
For Series 3100 devices, if flash is used for the EEPROM region, it can
also take the place of the ROM region. In this case, you cannot write to
the system area of the flash (see Memory Areas), but you can write to the
user area. For more information, including the particular flash parts
supported, see Use of Flash Memory on page 184.
The Neuron hardware does not implement wait states for slow devices.
The memory must be readable and writable in one machine cycle at the
selected system clock rate.
The off-chip RAM region can be used for code. Any portion of the off-chip
RAM used for code is retained over resets. The remainder of RAM, the
area not used for code, is zeroed each time the chip is reset.
Memory Areas
The Neuron firmware and the Neuron linker divide the memory regions into
memory areas as follows:
EECODE
EEFAR
EENEAR
The EEFAR area contains variables declared with the far keyword
combined with either the config or eeprom keywords. This area also
contains configuration property network variables declared with the
config_prop (or cp) keyword and the modifiable configuration property file
for configuration properties declared with the cp_family keyword.
You can use the offchip and onchip keywords in Neuron C to force the
compiler and linker to place specific objects in the offchip and onchip
EEFAR areas.
The EENEAR area contains variables declared with either the config or
eeprom keywords. It is the default, but is limited to a total size of 255
bytes.
• The RAM region consists of three areas:
RAMCODE
RAMFAR
RAMNEAR
RAMCODE can only be located off-chip (Neuron 3150 Chip or 3150 Smart
Transceiver only). It contains executable code and constant data. By
using the ram keyword in a declaration, you can explicitly place
executable code and constant data in this area. This area can only be
implemented in a RAM that is based on a non-volatile memory
technology, such as battery-backed RAM.
RAMFAR may be located both on-chip and off-chip. There can be one or
two sections of RAMFAR in the on-chip RAM. If there is off-chip RAM, it
can contain only one RAMFAR area. The RAMFAR area contains
variables.
You can use the offchip and onchip keywords in Neuron C to force the
There can be only one RAMNEAR area. It can be located on-chip (all
chips) or off-chip (Neuron 3150 Chip and FT 3150 Smart Transceiver
only). The linker automatically determines the location of the
RAMNEAR area. The RAMNEAR area is the default memory area for all
Neuron C variables. This area is limited to a total size of 256 bytes.
However, the maximum allowable size may be smaller under certain
circumstances depending on the amount of memory the user has
allocated for buffers. See Compiler Directives for Buffer Allocation on
page 193, and also see Controlling Non-Default Memory Usage on page
181.
Note that for Series 5000 chips, the Extended Memory region can be configured
for either extended RAM (for three RAM regions) or for non-volatile memory (for
the three EEPROM areas).
Compiler Directives
Configuration property value files and the configuration property template files,
which hold values and self-documentation data for configuration properties
declared with the cp_family keyword, can be allocated in on-chip EEPROM or off-
chip EEPROM using the linker’s default relocation algorithm. You can use the
#pragma codegen put_cp_template_file_offchip and #pragma codegen
put_cp_value_files_offchip compiler directives to force the template file or the
value files into off-chip memory, if off-chip memory is available. If insufficient
off-chip memory is available, forcing the files to off-chip memory cause the link to
fail. See Compiler Directives in the Neuron C Reference Guide for more details
about these directives.
void y() {
const int* pointer;
pointer = &x; // '&x' is 'const int *'
}
The compiler normally prevents removing the const attribute from any pointer so
typed. This is prevented for both implicit and explicit cast operations. An
implicit cast occurs when there is an assignment of a value to a variable of
different type, or when there is an actual parameter passed to a function whose
formal parameter is a different type, as illustrated in the following example:
eeprom int x;
int *p;
void f (int *p);
void y() {
p = &x; // implicit cast, compiler error
f(&x); // another erroneous implicit cast
p = (int *)&x; // explicit cast, also error
}
This behavior of the Neuron C compiler is stricter than the behavior specified by
ANSI C. However, if you specify the #pragma relaxed_casting_on directive, the
compiler only generates a warning message for each such implicit or explicit cast.
You can use the #pragma warnings_off or #pragma disable_warning directive to
further suppress the warning messages. You can use the corresponding
warnings_on (or enable_warning) and relaxed_casting_off directives later in the
program to restore the default behavior of the compiler for the remainder of the
program.
Use of this feature is dangerous, because you can circumvent the compiler’s
checking and attempt a spurious write (that is, a write without knowledge of the
firmware) to EEPROM or flash memory. The eeprom_memcpy( ) function is
Address Table
The address table contains the list of network addresses to which the device
sends network variable updates or polls, or sends implicitly addressed application
messages. The address table can be configured through network management
messages from a network tool.
Note: See the ISO/IEC 14908-1 Control Network Protocol standard for a
description of the address table.
Alias Table
The alias table is generated according to the alias table size specified with the
#pragma num_alias_table_entries compiler directive, shown below. This
compiler directive can be used to set the alias table size to any size between zero
and 127 entries. Each alias entry uses 4 bytes of EEPROM (on-chip for Series
3100 devices or offchip for Series 5000 devices). An alias is an abstraction for a
network variable that is managed by network tools and the Neuron firmware.
Network tools use aliases to create connections that cannot be created solely with
the address and network variable tables, providing network integrators with
more flexibility in how devices are installed into networks. This feature requires
Neuron firmware version 6 or later.
#pragma num_alias_table_entries nn
(see Table 13 for the maximum value of nn)
How many network variable aliases a Neuron-hosted device can support depends
on the device’s memory map, the system firmware, and the development tool
version, as shown in Table 13.
Table 13. Alias Limits
NodeBuilder
Neuron System Development Tool Maximum Number of
Firmware Version Version Aliases
3.1 or earlier 62
Version 16 or later
FX or later 127
Domain Table
By default, the domain table is configured for two domains. Each domain uses 15
bytes of EEPROM (on-chip for Series 3100 devices or offchip for Series 5000
devices). The number of domain table entries is a function of the network where
the device is installed, it is not a function of the application. You can reduce the
size of the domain table using the following compiler directive:
#pragma num_domain_entries 1
Notes:
• See the ISO/IEC 14908-1 Control Network Protocol standard for a
description of the domain table.
• As a general rule, the domain table should be sized to the maximum of 2
entries, if possible. LONMARK International requires all interoperable
LONWORKS devices to have two domain table entries. Reducing the size
of the domain table to one entry prevents certification.
Allocating Buffers
You can use compiler directives to set certain Neuron firmware memory
resources, such as buffer counts and sizes and receive transaction counts. These
values can be set only during compilation. They cannot be configured at run-
time. Figure 18 on page 191 illustrates where application and network buffers
are used. Application buffers are used between the application and network
processors. Network buffers are used between the network and media access
control (MAC) processors.
Hardware Hardware
MAC MAC
Network Network
Scheduler Scheduler
Application Application
= data
Buffer Size
If you use application messaging, you must set the appropriate buffer sizes to
accommodate the largest message that the application or Neuron firmware could
generate or receive for processing. In some cases, this could require an increase
in buffer size. If you only use network variables, the compiler chooses buffer
sizes based on the size of the largest network variable that you declare and the
minimum sizes required by the Neuron firmware.
Figure 19 shows the basic components of an application buffer and a network
buffer. An application buffer contains application message data and system
overhead. A network buffer contains application message data, protocol layer 2
through layer 5 overhead, and system overhead.
Application Buffer
Network Buffer
System Overhead
System Overhead
Application
Protocol L2 - L5 Message
Application
Message
Errors
If an input message fits into a network input buffer but does not fit into an
application input buffer, the message is discarded. An APP_BUF_TOO_SMALL
error code is logged by the Neuron firmware. If the message was sent with the
acknowledged service, no acknowledgment is sent. If the message was a request,
no response is sent.
If an output message fits into an application output buffer but does not fit into a
network output buffer, a NET_BUF_TOO_SMALL error is logged and the device
resets.
Buffer Counts
In most cases, the default number of output application buffers is sufficient.
Increasing the number of application buffers on the output side decreases the
likelihood of entering preemption mode if you are using synchronous network
variable outputs (see Preemption Mode on page 55).
The number of input network buffers needed is a function of the types of service
used and the types of connections between devices:
• If you are using authentication, you might need to increase the number of
network buffers because authentication doubles the number of messages.
The minimum keyword specifies that the number of buffers indicated for
the pragma directive is the minimum number of buffers required for the
application. The compiler defines at least as many buffers as specified in
the directive, but could define additional buffers if the application
requires them. This keyword encourages modular development, wherein
different modules could have different buffer count or size requirements.
• final
The final keyword specifies that the number of buffers indicated for the
pragma directive is the absolute number of buffers to be used by the
application. The compiler defines exactly as many buffers as specified in
the directive, and issues an error if additional buffers are required. This
keyword promotes self-documenting source code.
Example: The following two directives specify the incoming network buffers.
The first directive specifies that the compiler should define at least 66-byte
receive_trans_count 1 .. 16 D
Notes:
A. app_buf_out_size default
If outgoing messages are sent with msg_send( ):
If explicit addressing is used:
A = 66
If explicit addressing is not used:
A = 50
If no outgoing explicit messages are sent (msg_send( ) is not used), and:
If explicit addressing is used for network variables:
A = max(34, 19 + sizeof(largest output NV))
If explicit addressing is not used:
A = max(20, 8 + sizeof(largest output NV))
B. net_buf_out_size default
If outgoing explicit messages are sent with msg_send( ) or resp_send( ):
B = 66
else:
B = max(42, 22 + sizeof(largest NV))
While the response is constructed in the application input buffer by the
application, the network processor uses a network output buffer to construct
the response packet. So, the network output buffer must be sized to
accommodate outgoing responses in addition to other outgoing messages.
C. app_buf_in_size default
If any explicit message functions or events are used (incoming or outgoing):
If explicit addressing is used:
C = 66
If explicit addressing is not used:
C = 50
If no explicit message functions or events are used, and:
void read() {
// Read from device ...
unsigned int x, y;
unsigned long z;
x = pDevice->controlReg1;
y = pDevice->controlReg2;
z = pDevice->dataReg;
}
void write() {
// Write to device ...
unsigned int x, y;
unsigned long z;
pDevice->controlReg1 = x;
pDevice->controlReg2 = y;
pDevice->dataReg = z;
}
void f(int i) {
a[i+2].x = 3;
a[i+2].y = 5;
a[i+2].z = 7;
}
After (compiles to 33 bytes of code):
struct s {
int x, y, z;
} a[5];
void f(int i) {
struct s *p;
p = &(a[i+2]);
p->x = 3;
p->y = 5;
p->z = 7;
}
void f(void) {
t1 = 5000;
l = 5000;
}
After (compiles to 13 bytes of code):
mtimer t1;
unsigned long int l;
void f(void) {
t1 = l = 5000;
}
Use of the logical operators && and || for complex conditions typically perform
faster than similar expressions that use the bit operators & and |. In addition,
use of the logical operators can make the code smaller, especially when tests for
equality or inequality with zero are part of the conditional expression. The
following Before-and-After example demonstrates this efficiency:
Before (compiles to 15 bytes of code):
void f (int a, int b, int c) {
if ((a < 0) | (b == 0) | (c > 0)) {
// take some action
}
}
After (compiles to 12 bytes of code):
void f (int a, int b, int c) {
if ((a < 0) || (b == 0) || (c > 0)) {
// take some action
}
}
Common Syntax
If no command switches or arguments follow the name of the tool, the tool
responds with usage hints.
Example:
C:\>NAS
Tool responds:
Neuron Assembler, version 5.00.21, build 0
Copyright (c) Echelon Corporation 1992-2009
--bootflags=1024
--infolder=d:\lm\Source\EPR\23305\Development\IM
--outfolder=d:\lm\Source\EPR\23305\Development
Neuron C Compiler
The Neuron C compiler is named ncc.exe. You can run the stand-alone compiler
from the command prompt to produce a Neuron assembly source file. The
compiler command line contains the name of the executable file, then zero or
more optional command switches, and finally the file name to compile.
Example:
C:\>NCC mycode.nc
The most interesting switches are the -D (- -define) and -I (- -include) switches.
You can use the -D switch to define a symbol from the command line, which can
then be tested from the program using the #ifdef and #ifndef directives.
You can use the -I (- -include) switch to specify a directory containing include
files. You can specify additional include directories with additional -I switches.
The search order corresponds to the order of the switches, if you specify more
than one -I switch.
Example:
C:\>ncc -DVERSION5 -I..\include -Id:\include mycode.nc
When run for a filename with a .nc extension, the Neuron C compiler uses
Neuron C rules for code generation. Libraries and custom system images cannot
contain Neuron C code. To compile a pure C file, and use pure C rules for code
generation, the filename must end with a .c extension as shown in the command
line example below:
Example:
C:\>ncc -I..\include mycode.c
As a final, complete example, to compile myfile.nc with a myinc.h include file in a
subdirectory named myincs, and to define the OPTION1 symbol for conditional
compilation purposes, run the command shown below:
Example:
Neuron Assembler
The Neuron assembler is named nas.exe. The Neuron assembler is only provided
for supporting the Neuron C compiler. It should not be used to generate Neuron
Assembly Language applications.
You can run the Neuron assembler from the command prompt to produce a
Neuron object file. The assembler command line contains the name of the
executable file, then one or more optional switches, and finally the file name to
assemble. The most useful assembler switch is the -l switch (the long form is - -
listing), which tells the assembler to produce a listing.
Continuing the example from the compiler section above, the following command
assembles the myfile.ns file to produce a myfile.nl listing file and a myfile.no
object file. After the object file is produced, you can delete the myfile.ns
intermediate assembly file to conserve disk space.
Example:
C:\>NAS -l myfile.ns
Neuron Linker
The Neuron Linker is named nld.exe. You can run the linker from the command
prompt to produce a Neuron executable file. The linker command line contains
the name of the executable file, then one or more switches, and finally the object
file name or names to link. Several switches must be used in combination to
produce a correct link.
The -a (or - -appimage) switch should always be used when linking a Neuron C
application program.
The -t (or - -neurontype) switch should be used to specify the name of the Neuron
Chip or Smart Transceiver for which the application is being linked.
Example:
C:\>NLD -a -t3120E2 ....
When linking for a Neuron 3150 Chip or 3150 Smart Transceiver, the external
memory map must be specified using a set of switches. The switches specify the
beginning or end of the external RAM, EEPROM, and ROM areas. Each of these
switches is followed by a hex number corresponding to the first (last) page
number of the area. A page is 256 bytes, thus the page number is the upper two
hex digits of the four-digit byte address.
The -r and -R switches specify the first and last pages of external RAM,
respectively. The -e and -E switches specify the first and last pages of external
Neuron Exporter
The Neuron exporter is named nex.exe. The exporter takes input from the
compiler and the linker and produces the device file set. The device file set
contains the device interface files (.xif and .xfb extensions) as well as image files
(.nri, .nfi, .nxe, .nei, .nme, .nmf, and .apb extensions, as needed).
Advantages of a Library
Use of a library to contain utility routines and constant data tables can provide
the following advantages:
1 Use of a library can speed up compilation, because utility routines are not
recompiled each time.
2 A library can provide modularity, encapsulation, and reuse — software
engineering techniques which can be used to increase quality and
decrease development costs.
3 The library can contain several related constant tables and procedures.
When organized properly, only the pieces used by a given application are
linked into the application. Unused pieces do not consume any code
space in the device’s application.
4 A library can contain data declarations for objects in any part of a Neuron
Chip’s or Smart Transceiver’s memory space, including near RAM and
EEPROM areas. Libraries also can contain initialized RAM variables.
The initialization rules are identical to Neuron C application programs.
Disadvantages of a Library
Use of a library has the following disadvantages:
1 The NodeBuilder tool offers no way to debug the contents of a library.
However, the contents of data objects in a library can be examined from
the Neuron C debugger, provided that the data is declared as extern in
the application program. Procedures should be fully debugged prior to
placing them in a library.
Translation (J.3.1)
Q: How is a diagnostic identified? (Sec. 3.10, Sec. 5.1.1.3)
A: Each Neuron C diagnostic consists of at least two lines output to the standard
output file. One of these keywords introduces the diagnostic: FYI (For Your
Information), Warning, Error, or FATAL. The remainder of the first line consists
of the full path name of the source or include file to which the diagnostic applies,
followed by a line number, and a column number in parentheses.
The second (and possibly subsequent) lines contain the diagnostic. Each of the
diagnostic message lines is indented one tab stop.
FYI and warning diagnostics do not prevent the compiler from successfully
completing translation. All warning diagnostics should be examined and
corrected, however, as they are likely to indicate programming problems or poor
programming practice.
Error diagnostics do prevent the compiler from successfully completing
translation. They may also result in masking of other errors; thus the compiler
may not be able to locate all errors in a single compilation pass.
FATAL diagnostics prevent the compiler from performing any further
translation. These diagnostics result from resource problems (out of memory,
disk full, and so on) or from internal checking on the compiler itself. Any
diagnostic of the form ***TRAP n***, where n is a decimal number, should be
reported to Echelon Customer Support.
Identifiers (J.3.3)
Q: What is the number of significant initial characters (beyond 31) in an
identifier without external linkage? (Sec. 5.2.4.1, Sec. 6.4.2)
A: An identifier without external linkage can extend to 256 characters. All
characters are significant.
Integers (J.3.5)
Q: What is the result of converting an integer to a shorter signed integer? What
is the result of converting an unsigned integer to a signed integer of equal length,
if the signed integer cannot represent the unsigned integer’s value? (Sec. 6.3.1.3)
A: Conversion from long to short may result in data loss, depending on the value
being converted, since this conversion is performed by discarding the most
significant byte of the long integer. If, for example, a long integer containing the
value 513 (hex 0201) was converted to a signed short, discarding the most
significant byte of the long integer would result in the value 1.
Conversion from an unsigned integer to a signed integer of equal length may
result in a negative number. For example, an unsigned short integer may have
the value 255 (hex FF), but when converted to a signed short integer, it is then
interpreted using two’s complement, and the value
becomes -1.
The Neuron C compiler produces diagnostic messages when data might be lost as
the result of an implicit conversion operation. Explicit conversion, such as
through a cast operation, does not produce a diagnostic message. As an example,
in the code fragment below, the assignment to x results in a diagnostic warning
message, but the assignment to y does not.
int x, y;
x = 285; // Data is lost, x is assigned 29.
// Warning is produced.
248 Index
conversion EENEAR memory area, 179
cast, 242 EEPROM, 178
integer, 241 on-chip, address table, 189
pointer, 242 on-chip, alias table, 189
cp keyword. See config_prop keyword on-chip, domain table, 190
cp_family keyword, 6, 85, 86, 175, 179, 180, on-chip, reallocating, 188
184 pointers to, 187
cp_info keyword, 87 use of, 174
CPT, definition, 3 variables
create a new library, 219 pointers to, 13
critical sections, 138, 143, 144 write timer, 37, 41
boundary, 48, 127, 135, 139, 149 eeprom keyword, 6, 7, 179, 180, 181
definition of, 47 eeprom_memcpy( ) function, 13, 187
ctrl-Z character, 240 efficiency of code, 204
custom functional profiles, 9 enable_sd_nv_names pragma, 10, 48, 201
custom system images end-of-file marker, 240
advantages of, 230 end-of-line character, 240
construction of, 231 enum variable type, 202
definition, 228 predefined, 5
disadvantages of, 230 enumeration type, 243
providing a large RAM space, 234 EOT character, 240
error diagnostic from compiler, 238
error handling, 168
D error status, access, 172
data keyword, 121, 128 error_log( ) function, 75, 171
declaration, I/O object, 29 event-driven scheduling, 4
declarations, 7 events, 18, 20
order of, 204 blocking queue, 21, 22, 127, 129, 148
declarators expression, 22
limits on, 244 latency, 37
delay( ) function, 40 posting of, 37
dest_addr keyword, 122 predefined, 18
device processing of, 20
bringing online, 151 completion events, 20
commissioning, 21 network events, 20
context for properties. See context queue, 20
expression, for device responses, 20
forced sleep, 168 when clause, 21
initialization scheduler, 16
and the wink event, 152 unqualified, 26, 131
interface, 3, 9, 10, 102, 105 unsolicited, 21
reset, 7, 134, 151, 169 user-defined, 18, 22
causes of, 150 expired timers. See timers
effect of, 21 explicit addresses, 197
time required, 169 for network variable updates, 131
direct event processing, 19, 127, 135, 153 explicit addressing, 192, 198
director keyword, 104, 106, 112 explicit messages, 119, 191
disable_mult_module_init pragma, 207 events, 197
disable_snvt_si pragma, 11, 48, 201 functions, 197
disadvantages of a library, 223 receiving
distributed systems, 9 implementation caveat, 127
domain table, 188, 190 exporter
memory use, 174 command line switches, 218
duplicate keyword, 128 exporter command line switches, 217
extended arithmetic, 5
extern keyword, 6, 233, 239
E external_name keyword, 13, 104, 106, 239
EECODE memory area, 179 external_resource_name keyword, 104, 106,
EEFAR memory area, 179 239
250 Index
periodic, 154, 158 use by program elements, 174
restrictions, 165 wait states, 178
semaphore, 161 memory-mapped I/O
sharing data, 161 usage tip, 198
sources, 153 message codes, 120, 123
task, 155 application-specific, 123
timer/counter, 154, 156 ranges, 123
timing, 162 message data
interrupt service routine. See ISR block transfer, 124
io_changes event, 19 message tags, 13, 123, 200
memory use, 174 and explicit addressing, 130
io_update_occurs event connecting, 130
examples, 31, 33 declaration, 122
is_bound( ) function, 50 default msg_in tag, 130
ISR limitations on name length, 13
defining, 155 non-bindable, 130
syntax, 122
messages, 9
L cancelling, 126
len keyword, 128 code, 118
librarian, 220, 222 completion status, 131
command line switches, 219 data field, 118
libraries events, 153
advantages of, 223 explicit, 11
disadvantages of, 223 explicit addressing of, 130, 197
including in link, 217 foreign-frame, 120, 124
report of library contents, 219 implicit, 119
library, 222 incoming, 122
definition, 228 format of, 127
functions, 13 list of steps, 120
limits.h, 241 priority, 143
link map, 209 processing completion events, 133
linker, 222, 223 protocol overhead, 192
command line switches, 216 receiving, 126
linking a program, 184 sending, 125
lock keyword, 161 unwanted, 128
logging system errors, 171 messaging service, 4
long int, 13, 241 millisecond timers, 25
long to short integer conversion, 241 min( ) function, 202
LONMARK Interoperability Association Miranda prototype rule, 25
website, 10 model file, 12
LonTalk protocol, 119 monitoring device, 65
LONWORKS messages, 9 msec_delay( ) function, 40
lowering power consumption, 167 msg_alloc( ) function, 135, 143
msg_alloc_priority( ) function, 143
msg_arrives event, 20, 126, 127
M msg_cancel( ) function, 125, 144
magcard I/O object, 150 msg_completes event, 20, 123, 131, 148
magtrack1 I/O object, 150 msg_fails event, 21, 131, 141, 148
main( ), 14, 239 msg_free( ) function, 143
max( ) function, 202 msg_in message tag, 122
max_rate_est option, 122 msg_in object, 127, 142, 143
Media Access Control (MAC) layer, 119 addr field, 128, 198
memcpy( ) function, 124, 188 fields invalidated, 128
memory msg_out object, 120, 130, 144
page, definition of, 205 defined, 121
usage dest_addr field, 198
default, 180 tag field, 123
non-default, 181 msg_receive( ) function, 126, 127, 137, 144, 150
252 Index
O ptrdiff_t, 242
pullup resistors, internal, 167
object files, 222, 223 pulsecount I/O object, 36, 150
offchip keyword, 7, 179, 181, 183, 185 pure C, 222, 225, 228, 234
off-chip memory definition, 228
use of, 176
offline event, 18, 20, 127, 151, 152
offline_confirm( ) function, 152
Q
onchip keyword, 7, 179, 181, 183 quadrature I/O object
online event, 18, 20, 127, 151 examples, 31, 33
optimization
common sub-expressions, 205
outgoing network variable updates, 149
R
RAM, 178
P custom image needs, 234
use, 174
padding of structures, 243 ram keyword, 7, 179
parallel processing, 149 for functions, 183
partial completion event testing. See RAMCODE memory area, 179
completion events RAMFAR memory area, 179
pending updates RAMNEAR memory area, 179
flushing, 165 range_mod_string keyword, 90, 107
pointers, 13, 242 rate_est option, 122
subtraction of, 242 raw data value, 73
poll( ) function, 57 rcvtx keyword, 128
polled applications, 11 reader devices, 66
polled keyword, 57, 58 behavior of, 46
polled network variables. See network receive transactions
variables, polling of number of, 195
polling requirements, 195
definition of, 56 size, 195
post_events( ) function, 47, 127, 139, 149, 150 receive_trans_count pragma, 195, 197, 198
power consumption receiving a message, 126
limiting, 168 register keyword, 13, 243
lowering, 167 registers, 243
power failure relaxed_casting_on pragma, 13, 61, 111, 187
effects on flash memory, 186 relinking a program, 184
pragmas, 245 remainder operation
predefined events, 18 sign of result, 242
preempt_safe keyword, 17, 55, 134 repeating keyword, 25
preemption mode, 17, 54, 55, 134, 135, 144, 192 repeating timers, 39
preemption_mode( ) function, 135 request message. See request/response
preprocessor directives, 244 message service
priority keyword, 17, 146 REQUEST service type, 122, 128
priority when clauses, 23 request/response message service, 137
starving execution of nonpriority, 24 examples, 140
priority_on keyword, 121 for messages, 136
processor execution using, 136
lockout when writing flash or EEPROM with explicit messages, 118
memory, 185 reserved words, 14
propagate( ) function, 60 reset cause register, 172
propagation reset event, 21, 202
of network variables. See network variables, reset pin, 169
propagation of reset time, 21
property lists, 88 resetting a device. See device, reset
for functional blocks, 107 resource files, 3, 9, 10, 84, 87, 103
for network variables, 90 Resource Editor, 9, 10, 84, 103
protocol resp_alloc( ) function, 144
overhead, 192 resp_arrives event, 21, 138, 141
254 Index
domain. See domain table definition, 3
network variable configuration, 188 use of, 86
tag keyword, 121 UFPT, 103
tasks, 16, 18, 47 UFPT, definition, 3
order of execution, 23 UNACKD service type, 122, 128
priority, 23 UNACKD_RPT service type, 122, 128
returning from, 18 unacknowledged service, 49
template file. See configuration properties, unicast connections
template file and buffer use, 193
timeout uninit keyword, 7, 182, 184
waiting on buffer, 134 unions, 13, 243
timer unqualified events. See events, unqualified
millisecond, 25, 37 unsigned long, 241
preemption mode timeout, 36 unsigned to signed integer conversion, 241
pulsecount input, 36 UNVT
second, 25, 37 definition, 3
triac pulse, 37 user-defined events, 18, 22
write, EEPROM. See EEPROM, write timer
timer objects. See timers
timer_expires event, 26, 153
V
examples, 17 value files. See configuration properties, value
timers, 4, 13, 16 files
accuracy of, 36, 37 variables
checking for specific, 27 declaration order, 204
duration, formula for, 38 initialization, 7
examples, 26 volatile keyword, 13, 244
expiration of, 149
expired, 25
fixed duration, 36
W
in the wink task, 153 wait states
initialization of, 7 Neuron Chip, 178
maximum number of, 25 wake up Neuron Chip, 165
measuring very short durations of time, 39 warning diagnostic from compiler, 238
memory use, 174 warnings_off pragma, 187
repeating, 25, 39 watchdog timer, 37, 41, 134, 149, 150, 186, 188
starting over, 25 watchdog_update( ) function, 150
time remaining, 25 examples, 150
turning off, 25 wchar_t, 241
turning off before sleep, 167 when clauses, 16
unqualified event expressions, 26 default, 127
use in the debugger, 26 memory use, 175
timers_off( ) function, 203 priority, 23, 146
transaction, idempotent, 141 scheduling, 22
transmit transaction buffers, 198 when statement, 14
TRAP n diagnostic from compiler, 238 wiegand I/O object, 150
triac I/O object, 37 wink command, 151
examples, 33 wink event, 20, 127, 151, 152
type qualifiers, 6 working directory, 245
typedef keyword, 86 writer device, 66
behavior of, 46
U
UCPT