C supports a wide variety of commands:
Comments provide explanation to anyone reading your file. They are ignored by the compiler and have no effect on the code operation.
The IDE supports two different styles of comment; line comments and block comments.
Line comments are indicated with a pair of slashes ("//"). Any text after the comment marker will be ignored by the compiler.
// Check if any error is detected if (DI[3]) Errors += 1; // Increment the error count
Block comments are indicated with the /* and */ markers. Any text between the comment markers will be ignored by the compiler. The comment may span multiple lines.
/* Loop through all the discrete inputs and get a tally of how many are active. */ Active = 0; for (i = 0; i < 8; i += 1) Active += DI[i];
The compiler does not support nested, block comments.
The most fundamental type of command in C is the assignment statement. Assignment statements assign a new value to a variable. Typically, they take the form:
var = expr ;
Although C does let you can chain assignments together to assign multiple variables to the same value. For example:
x = y = z = 0;
This would set all three variables (x, y, and z) to 0.
In addition to the standard assignment operator, =, the C language defines a variety of assignment operators which can be used to modify a variable. The IDE supports:
The command... | which means... |
---|---|
X += Y; | X = X + Y; |
X -= Y; | X = X - Y; |
X *= Y; | X = X * Y; |
X /= Y; | X = X / Y; |
X %= Y; | X = X % Y; |
X &= Y; | X = X & Y; |
X |= Y; | X = X | Y; |
X ^= Y; | X = X ^ Y; |
Ports are treated as if they were variables and can be assigned likewise. You can write to any of the following ports: Count[n], DAC[n], DAC_B[n], DAC_D[n], DO[n], Pulse[n], and Xfer[n].
The other ports (ADC[n], DI[n], etc.) are read-only. Trying to assign these ports a value will cause a compiler error.
Note that although any value can be written to the DO ports, they are boolean, and any non-zero value written to them will be treated as true. For example:
int X; DO[0] = 5; X = DO[0];
This will turn on DO1, and then read the value of DO1 into variable X, setting it to 1. This will not set X to 5.
At this point, true übernerds may be wondering what would happen in this very contrived example:
int X = DO[0] = 5;
Okay, yes, in that case X would be set to 5. The value is written to multiple places, but DO1 is never really read, even though the syntax might imply it.
Some function calls (such as SerialOut and MBPutDO) do not return a value. The C language treats these calls as a special case of the assignment command.
Example:
SerialOut("Error detected!\n");
The C language implements a few commands that control which command will be executed next. These are called control structures.
The if command tests a condition and then executes code if the result is true. The if command can also optionally execute code on a false result as well. if syntax:
if ( cond ) true-cmd [ else false-cmd ]
For example:
if (Errors > 5) { SerialOut("Too many errors encountered.\n"); end; }
This example would test the variable Errors, and if the value was more than 5, it would send an error message to the serial port and stop executing. Note how brackets were used to indicate that multiple commands were to be executed (see the section on compound blocks, below, for more details).
Here's an example of if/else:
if (X & 1) Odd += 1; else Even += 1;
This code would increment the variable Even if X is even and Odd if X is odd.
Note how semicolons (";") were required after each of the individual commands executed depending on the result of the if condition, however no semicolon was used to terminate the if command itself. No semicolon followed the compound block in the first example, either.
In C, if statements are a great way to test the value of a variable, but the code can get a little tedious when there are many values you want to test against. The switch/case statement accomplishes much the same thing but gives you a more succinct syntax. For example:
switch (X) { case 1: One += 1; break; case 2: Two += 1; break; case 3: Three += 1; break; case 4: Four += 1; break; default: Other += 1; }
This would test the value in variable X against the constants 1-4, incrementing the appropriate variable. If no match is made, then Other is incremented.
switch/case statements are pretty straight-forward, but there are a few things worth noting:
The default clause is completely optional. Had it been omitted and no match was made, then no code would be executed.
You can associate multiple cases with a single block of code. For example:
switch (X) { case 1: case 2: Small += 1; break; case 3: Medium += 1; break; case 4: case 5: Large += 1; }
This would increment Small if X was 1 or 2, it would increment Medium if X was 3, and it would increment Large if X was 4 or 5.
The break commands are important. If you leave a break command out, then execution will continue from one case to the next.
I can't think of any practical reason why the C language was designed this way, or why you might want one case to fall through to the next; so be careful to always include break commands at the end of each case.
Do not list a single value in multiple case blocks. Only the first occurrence will ever be compared for a match.
Despite being frowned upon by programmers world-wide, C still supports the goto command.
Although it is true that careless use of the goto command can lead to code that is not only difficult to read, but difficult to debug and maintain, I feel that goto is a powerful tool that you would be foolish to ignore.
In a nutshell, goto moves the execution point to the label specified. For example:
LOOP: DO[0] = DI[0] and DI[1]; DO[1] = not DO[2]; DO[2] = DI[3] or DI[1]; goto LOOP;
In this example, goto is used to repeat the three assignment statements forever.
Label names are global in scope. Repeating the same label name multiple times will trigger a compiler error.
Another handy place to use the goto command is when multiple errors should be handled by the same piece of code:
SerialOut("AT\x0D"); if (SerialIn(T, "OK\x0D") == 2) goto ERROR; SerialOut("AT+CMGF=1\x0D"); if (SerialIn(T, "OK\x0D") == 2) goto ERROR; SerialOut("AT+CMGW=\"" + PHONE_NUMBER + "\"\x0D"); if (SerialIn(T, ">") == 2) goto ERROR; ... // etc. ERROR: ... // error handling code
The C language implements three different loop structures; for loops for when you know how many times you need to loop, while loops for when you want to test a condition before each iteration, and do/until loops for when you want to test a condition after each iteration.
for loops are generally used when you know how many times you want the code to loop. In actuality, the for command is little more than a while command with some syntactic sugar. for lets you accomplish this common task with some small, easy-to-read code. The syntax is as follows:
for ( [ init-assign1 [, init-assign2... ]] ; [ loop-cond ] ; [ post-assign1 [, post-assign2... ]] ) loop-cmd
I'll be the first to admit that that's pretty hard to read, so let's try a simple example:
int i; for (i = 1; i <= 5; i += 1) SerialOut("Loop #" + FormatInt(i) + "\n");
This code will initialize i to 1, execute the SerialOut command with each iteration, and add 1 to i after each iteration. The loop will continue to execute as long as i is less than or equal to 5. It will output the following to the serial port:
Loop #1 Loop #2 Loop #3 Loop #4 Loop #5
Although there is rarely a call for it, you can list multiple initialization and post-execution assignments. Each of these should be separated with commas (","). You can even omit all of the parameters to loop forever:
for (;;) { ... // the code here would be repeated endlessly }
while loops are similar to for loops in that they will repeat code as long as a condition is true. The while syntax is as follows:
while ( loop-cond ] ) loop-cmd
The loop-cond is tested before each iteration, so if it is false initially, then the loop-cmd will not be executed at all.
The while loop can be used in conjunction with the null command. This is handy when you want to wait idly while a condition is true:
// Wait for DI3 to go off while (DI[2]) ;
do/until loops are similar to while loops except that the condition is tested at the end of the iteration instead of the beginning. Additionally, a true condition in a do/until loop will cause the looping to stop, whereas a true condition in a while loop means that the loop should continue. The do/until syntax is as follows:
do loop-cmd until ( loop-cond ] );
Since the loop-cond is tested after each iteration, loop-cmd will always be executed at least one time.
Like the while loop, the do/until loop can be used in conjunction with the null command. This is handy when you want to wait idly until a condition is true:
// Wait for DI3 to go on do ; until (DI[2]);
The continue and break commands can be used in any of C's loop structures.
continue indicates that all the remaining commands in the loop should be skipped and that execution should continue with the loop's next iteration. break (discussed earlier) indicates that regardless of loop conditions, the loop should exit and execution should continue with whatever command follows the loop.
For example:
int i, X; timer T; for (i = 1; i <= 10; i += 1) { SerialOut("Loop #" + FormatInt(i) + ":"); T = 5000; if (SerialIn(T, ParseInt(&X) + "\x0D") == 2) break; if (i == X) continue; ... // other commands that will be skipped }
In this example, some text will be output on each iteration through the loop. If the user enters the same number as output, then the remaining commands in the loop will be skipped and the loop will continue to the next iteration. If the user does not return a response within 5 seconds, then execution will exit the loop entirely.
The end command ends the program execution. No commands will be executed after end. Syntax:
end;
Once execution has ended, it will only be restarted with reset, power cycling, or upon receipt of the appropriate Modbus command.
Multiple commands may be placed within brackets ("{" and "}") to form a compound block. A compound block executes as if it was a single command with regard to if commands and loop structures.
Local variables of type int may be defined at the top of a compound block. These variables have a scope limited to the compound block only. If a variable with the same name is defined in a larger scope, its value will be occluded while execution is within the block.
Use caution when combining locally scoped variables and the goto command. Entering a compound block midway will skip the block's initialization code, leaving any locally scoped variables with unpredictable values.
The C language supports the use of a single semicolon (";") as the null command. The null command does not do anything when executed.
The null command is used in wait loops that take no action and in conjunction with labels at the end of a compound block or program.