Examples - Master Program

Introduction

Description of the Example:

This section of the programming handbook describes the implementation of a serial port profile master program for the AIRcable Industrial. It serves as a good example of how to debug wirelessly.

Operation Principle

When we end this example you will know how to:

  • Discover other Bluetooth devices in range
  • Filter out one peer with a particular, friendly name
  • Connect to the remote device on the serial port (SPP)
  • Configure the UART for 9600 baud and odd parity
  • Link the wireless SPP port to the UART for streaming data

Debugging

While the AIRcable Industrial is finding other Bluetooth devices and streaming data, it is still accepting slave connections. We use the second wireless serial port (the slave SPP) for debugging. Make a SPP connection to the AIRcable and run a terminal emulator (such as Hyperterminal) on the connected slave SPP port. The BASIC interpreter will write every BASIC line it executes and debug any strings the BASIC program writes to the slave port. This is independent of the data communication between the peer Bluetooth device connected through the master port and the UART.

Theories of Operation

On the AIRcable Industrial (and other AIRcable OS based devices) a BASIC program controls the Bluetooth baseband processor. Since all operations (Bluetooth, BASIC, timer etc) are running on the same processor, the device is implemented as a multi-tasking system where several tasks perform operations concurrently. The BASIC program controls the multi-tasking system underneath it.
The BASIC program is organized in routines. Each routine is a section in the BASIC program that starts with @ROUTINE and ends with RETURN. These routines are like interrupt routines in an operating system. The BASIC interpreter knows 12 of these routines:

  1. # @INIT
  2. # @IDLE
  3. # @SLAVE
  4. # @ALARM
  5. # @SENSOR
  6. # @PIO_IRQ
  7. # @MASTER
  8. # @PIN_CODE
  9. # @CONTROL
  10. # @MESSAGE
  11. # @INQUIRY
  12. # @UART

The important programming principle is to keep the routines short. This means that routines should not occupy the processor for very long and then it should end quickly. This allows the Bluetooth baseband processor to execute other tasks that are important for the functionality of the whole system. Routines that don't end (even with WAIT) will block the system.
These routines are scheduled for execution either by the system automatically, or by other parts of the program or by external hardware or Bluetooth events. Some of the routines interrupt the execution of other routines because they have higher priority. For example, the @PIN_CODE routine has the highest priority and will interrupt any other routine. Otherwise routines are scheduled to run after the current routine is finished.

Code Explanations

The implementation of the master program will serve as an example of the various routines available. The master program starts with @ERASE. This is a command to the BASIC loader in the AIRcable when a new program is transmitted via wireless FTP. It will cause the AIRcable to erase its BASIC memory completely. As the other example (slave code), we had divided the code in several parts so it is more easy to understand.

Device Initialization

The first routine is @INIT. It runs only once when the system is booted or a new BASIC program has been uploaded. In this example the green LED is first switched on to indicate that the AIRcable boots. The lines 7 to 19 configure the power to the RS232 level shifter and schedule a hardware interrupt on PIO4. This is DSR input on the RS232 connector.
The PIN code that is used for connections (slave, master and ftp) is configured here in the BASIC program. This gives the application more control over security. Communication is only with devices with a friendly name starting with "PUMP" the string variables are initialized here as well. Debugging is switched on using the special variable Z. A value of 2 means to send debugging to the slave port.
The state engine is initialized and any pending wireless links are disconnected. This is helpful when changing programs frequently without rebooting the AIRcable. Slave ports are not disconnected so that the initialization execution on the slave port can be determined when a new version is uploaded.

@ERASE
@INIT 10
0 REM LED output and on
10 A=pioout 2
11 A=pioset 2
0 REM RS232_off set
12 A=pioout 5
13 A=pioset 5
0 REM RS232_on set
14 A=pioout 3
15 A=pioset 3
0 REM DTR output clear
16 A=pioout 6
17 A=pioclr 6
0 REM DSR input
18 A=pioin 4
0 REM set DSR to IRQ so that PIO_IRQ is called
19 A=pioirq "P00010000000"
0 REM PIN code
20 $1 = "1234"
0 REM connect to devices starting with
21 $2 = "PUMP"
0 REM connection state variable X
0 REM 1=found, 2=connecting, 3=connected, 4=slave restarted
22 X = 0
23 IF $4[0] = 0 THEN 26
0 REM we have a saved connection address, try first
24 $3 = $4
25 X = 1
0 REM switch on debug to the slave port
26 Z = 2
0 REM 25 A = disconnect 0
27 A = disconnect 1
28 A = unlink 1
29 ALARM 2
30 IF Z <> 2 THEN 32
31 PRINTS $3
32 RETURN

Running the Application using the Scheduler

The AIRcable Industrial knows 5 program-controlled, scheduled routines:

  • @INIT
  • @IDLE, @SLAVE
  • @MASTER
  • @ALARM
  • @SENSOR
  • And 6 hardware or event scheduled routines:
  • @PIN_CODE
  • @PIO_IRQ
  • @CONTROL
  • @MESSAGE
  • @INQUIRY
  • @UART

Slave Connections

@IDLE is called whenever a slave connection is not available or when a slave connection closed. This routine starts right after @INIT. Normally the BASIC program would open up the incoming serial slave port (SPP) for a number of seconds and then exit. It will be called again when the time is up and no connection was established. At this time the BASIC program will again open up the slave port for incoming connections to SPP.
When a connection to the slave port is successful, meaning another Bluetooth device has initiated a connection to the AIRcable, the @SLAVE routine starts up. The BASIC program can find out who is connecting using the built-in function getconn.
A successful connection requires that the two partners are paired. PIN code is exchanged and then a unique link key is stored on both ends to indicate a successful pairing. The pairing information is stored persistently for future connections that will no longer need a PIN code. Pairing information can be erased either when more than 8 peers are stored or manually with the unpair function (single peers) or the @UNPAIR command (all peers) at the beginning of a BASIC program upload.
In a simple slave application the program would link the wireless SPP port to the UART. Fundamentally, this example does the same.

@IDLE 40
0 REM slave port stopped
40 Y = 0
0 REM give the processor time to
0 REM finish a pending master connection
0 REM this is in state 1, 2 and 3
41 IF X = 1 THEN 46;
42 IF X = 2 THEN 46;
43 IF X = 3 THEN 46;
REM in state 0 and 4: allow slave for 5 seconds
44 A = slave 5
0 REM slave port open now
45 Y = 1
46 RETURN

@SLAVE 90
0 REM slave connection to shell
90 A = pioset 2
91 A = shell
92 RETURN

@PIN_CODE 488
488 $0 = $1
489 RETURN

Master Connections

In more complex master examples the program allows master and slave connections at the same time. Basically there are three ways to achive this: a state machine allowing the Bluetooth baseband processor to do master connections without conflicting with the slave connections, scheduling @ALARM interrupt and checking for Bluetooth status with A=status, and the final is a combination of the two you do have a state machine, but you still check the Bluetooth activity to be more secure.
In this simple example we will use an state machine, but if you want to see a combination of both we strongly recommend that you read the developers manual on command line implementation - COMMING SOON.
The states are as follows, with the variable X used to keep state.
0. State is initial state.
1. We have found a peer that matches the criteria
2. We are in process to connect as master to the peer
3. We have successfully connected to the peer
4. We are connected as master
Looking at the @IDLE routine, slave connections are only allowed when inquiries are still going on, or when a master connection has been established. When the slave connection mechanism is scheduled while a master connection is being established it causes conflicts. @IDLE will return in those cases and will have to be rescheduled when a certain state is reached.
The @MASTER routine is called whenever a master connection is successful. This is the second program-controlled, scheduled routine. The routine is simple. It configures the UART with 9600 baud and odd parity and links the SPP master port to the UART. We also store the address of the peer in $4(if connection is lost, reconnect to this address first before starting the inquiry process). Since a longer master connection timeout has been scheduled (20sec), a quicker alarm should be rescheduled.

@MASTER 100
0 REM save the address we conn
100 $4 = $3
0 REM debug message
102 IF Z = 0 THEN 105
103 PRINTS "master connected\n"
104 PRINTS $3
0 REM LED on
105 A = pioset 2
0 REM config 9600 baud, odd parity
106 A = uartcfg 35
0 REM link MASTER and UART
107 A = link 2
0 REM set state master connected
108 X = 3
0 REM reschedule ALARM quicker
109 ALARM 1
110 RETURN

Use the ALARM scheduler as the main control mechanism to control the whole application. All execution is initiated by @ALARM, depending on the state described above.
Because inquiries are expensive in processing time and in power consumption, wait when inquiries are occurring. Leave the processor as much time as possible to do inquiries.
The results are scheduled by calls to @INQUIRY. Inquiry routines are sensitive to interrupts by other inquiry results. To prevent interruption a semicolon can be placed after a BASIC instruction. Normally the BASIC interpreter executes one line of BASIC program and then allows other parts of the system to execute before it goes to the next line. At the moment it schedules the execution of each line for every 100ms. With the placement of the semicolon this wait does not happen, the BASIC interpreter executes the next line immediately without going back to the scheduler. Use the semicolon carefully.
@INQUIRY looks at the result and compares the name of the discovered Bluetooth device with the string in $2. If the name starts with that, it sets the state to "found", cancels any further inquiries, and prints some debug. (See a chapter at the end about debugging.)

0 REM process inquiry results
0 REM routine must have ';'
@INQUIRY 200
0 REM found peer, ignore other results
200 IF X >= 1 THEN 224;
201 $3 = $0;
202 $0 = $3[13];
0 REM only devices starting with this
203 B = strcmp $2;
204 IF B <> 0 THEN 220;

0 REM found, no more search
205 A = cancel;
206 X = 1;

0 REM debug
208 IF Z = 0 THEN 212;
209 S = status;
210 IF S = 1 THEN 213;
211 IF S = 10001 THEN 213;
212 RETURN

213 PRINTS "connecting "
214 PRINTS $0
215 PRINTS "\r\n"
216 RETURN

0 REM keep ';'
220 IF Z = 0 THEN 224;
221 S = status;
222 IF S = 1 THEN 225;
223 IF S = 10001 THEN 225;
224 RETURN

225 PRINTS "not "
226 PRINTS $0;
227 PRINTS "\r\n"
228 RETURN

As for @ALARM, a standard state engine switch is at the beginning and fans out from there to the different procedures depending on the state. Note that in every state we reschedule another ALARM within a few seconds to keep the system running. Switching state forward is mostly done in these procedures.

0 REM use ALARM routine to make master connections
@ALARM 400
0 REM wait until inquiry stops
400 S = status;
401 IF S < 10000 THEN 410;
0 REM still inquiring, schedule next ALARM
402 ALARM 2;
0 REM blink LED
403 B = pioset 2;
404 B = pioclr 2;
405 RETURN

0 REM state machine
410 IF X = 0 THEN 430;
411 IF X = 1 THEN 440;
412 IF X = 2 THEN 450;
413 IF X = 3 THEN 460;
415 IF X = 4 THEN 470;
0 REM should never happen
416 X = 0
417 GOTO 430

0 REM state 0: init, do inquiries
430 ALARM 6
0 REM blink LED
431 B = pioset 2;
432 B = pioclr 2
433 A = inquiry 11
434 RETURN

0 REM state 1: found
440 $0 = $3
441 IF Z <> 2 THEN 443
442 PRINTS $0
443 A = master $0
0 REM set state to "connecting"
444 X = 2
0 REM give it 20 seconds timeout
445 ALARM 20
0 REM blink LED
446 B = pioset 2;
447 B = pioclr 2
448 RETURN

0 REM state 2: timeout connecting, restart slave
450 X = 0
454 S = status
0 REM if we have a slave connection skip
455 IF S = 1 THEN 458
456 A = slave 1
457 Y = 1
0 REM safety, no master connections
458 A = disconnect 1
459 GOTO 410

0 REM state 3: master connected, restart slave
0 REM only if no slave connected
460 S = status
461 IF S = 11 THEN 464
462 A = slave 1
463 Y = 1
464 X = 4
465 ALARM 5
466 RETURN

0 REM state 4: connected, slave restarted
0 REM handle lost master connections
470 IF S < 1000 THEN 472
471 S = S - 1000
472 IF S < 100 THEN 474
473 S = S - 100
474 IF S < 10 THEN 480
0 REM still has master connection, blink reverse
475 ALARM 5
476 B = pioclr 2;
477 B = pioset 2
478 RETURN

0 REM lost master connection, reset state
480 A = unlink 2
481 X = 0
482 IF $4[0] = 0 THEN 485
483 X = 1
484 $3 = $4
485 GOTO 410

Extras

The last 2 routines @PIO_IRQ and @CONTROL deal with the handshake lines on the RS232 ports. If the DSR input on the DB9 connection changes, @PIO_IRQ is called. This was already initialized with the earlier instruction "A=pioirq "P00010000000" in the @INIT routine.
The function modemctl transmits a message through an established SPP connection to the other end. When the message is received it will schedule the @CONTROL routine to be executed. This example sets or resets the DTR pin of our RS232 connector.

@PIO_IRQ 490
0 REM local request for DSR
490 IF $0[4] = 48 THEN 483
0 REM modem control to other side
491 A = modemctl 1
492 RETURN
493 A = modemctl 0
494 RETURN

@CONTROL 495
0 REM remote request for DTR
495 IF $0[0] = 49 THEN 498
496 A=pioset 6
497 RETURN
498 A=pioclr 6
499 RETURN

Debugging

Debugging is much easier since version 24. A special variable Z is used to switch on debugging of the BASIC interpreter. Every line it executes is printed on one of the 3 data channels, UART, SPP-slave, or SPP-master. We use the SPP-slave port to send the BASIC lines executed and additional debugging information.
The debugging code in the program makes sure that data is not sent to a channel that is not connected. The AIRcable will buffer some data when there is no connection, but it will reduce memory and cause unnecessary processing.

Entire Program
Note: This is the entire program, with debugging switched on. To use the program in production without debugging, change line 26 to "Z = 0".

@ERASE

@INIT 10
0 REM LED output and on
10 A=pioout 2
11 A=pioset 2
0 REM RS232_off set
12 A=pioout 5
13 A=pioset 5
0 REM RS232_on set
14 A=pioout 3
15 A=pioset 3
0 REM DTR output clear
16 A=pioout 6
17 A=pioclr 6
0 REM DSR input
18 A=pioin 4
0 REM set DSR to IRQ so that PIO_IRQ is called
19 A=pioirq "P00010000000"
0 REM PIN code
20 $1 = "1234"
0 REM connect to devices starting with
21 $2 = "PUMP"
0 REM connection state variable X
0 REM 1=found, 2=connecting, 3=connected, 4=slave restarted
22 X = 0
23 IF $4[0] = 0 THEN 26
0 REM we have a saved connection address, try first
24 $3 = $4
25 X = 1
0 REM switch on debug
26 Z = 2
0 REM 25 A = disconnect 0
27 A = disconnect 1
28 A = unlink 1
29 ALARM 2
30 IF Z <> 2 THEN 32
31 PRINTS $3
32 RETURN

@IDLE 40
0 REM slave port stopped
40 Y = 0
0 REM give the processor time to
0 REM finish a pending master connection
0 REM this is in state 1, 2 and 3
41 IF X = 1 THEN 46;
42 IF X = 2 THEN 46;
43 IF X = 3 THEN 46;
REM in state 0 and 4: allow slave for 5 seconds
44 A = slave 5
0 REM slave port open now
45 Y = 1
46 RETURN

@SLAVE 90
0 REM slave connection to shell
90 A = pioset 2
91 A = shell
92 RETURN

@MASTER 100
0 REM save the address we conn
100 $4 = $3
0 REM debug message
102 IF Z = 0 THEN 105
103 PRINTS "master connected\n"
104 PRINTS $3
0 REM LED on
105 A = pioset 2
0 REM config 9600 baud, odd parity
106 A = uartcfg 35
0 REM link MASTER and UART
107 A = link 2
0 REM set state master connected
108 X = 3
0 REM reschedule ALARM quicker
109 ALARM 1
110 RETURN

0 REM process inquiry results
0 REM routine must have ';'

@INQUIRY 200
0 REM found peer, ignore other results
200 IF X >= 1 THEN 224;
201 $3 = $0;
202 $0 = $3[13];
0 REM only devices starting with this
203 B = strcmp $2;
204 IF B <> 0 THEN 220;

0 REM found, no more search
205 A = cancel;
206 X = 1;

0 REM debug
208 IF Z = 0 THEN 212;
209 S = status;
210 IF S = 1 THEN 213;
211 IF S = 10001 THEN 213;
212 RETURN

213 PRINTS "connecting "
214 PRINTS $0
215 PRINTS "\r\n"
216 RETURN

0 REM keep ';'
220 IF Z = 0 THEN 224;
221 S = status;
222 IF S = 1 THEN 225;
223 IF S = 10001 THEN 225;
224 RETURN

225 PRINTS "not "
226 PRINTS $0;
227 PRINTS "\r\n"
228 RETURN

0 REM use ALARM routine to make master connections
@ALARM 400
0 REM wait until inquiry stops
400 S = status;
401 IF S < 10000 THEN 410;
0 REM still inquiring, schedule next ALARM
402 ALARM 2;
0 REM blink LED
403 B = pioset 2;
404 B = pioclr 2;
405 RETURN

0 REM state machine
410 IF X = 0 THEN 430;
411 IF X = 1 THEN 440;
412 IF X = 2 THEN 450;
413 IF X = 3 THEN 460;
415 IF X = 4 THEN 470;
0 REM should never happen
416 X = 0
417 GOTO 430

0 REM state 0: init, do inquiries
430 ALARM 6
0 REM blink LED
431 B = pioset 2;
432 B = pioclr 2
433 A = inquiry 11
434 RETURN

0 REM state 1: found
440 $0 = $3
441 IF Z <> 2 THEN 443
442 PRINTS $0
443 A = master $0
0 REM set state to "connecting"
444 X = 2
0 REM give it 20 seconds timeout
445 ALARM 20
0 REM blink LED
446 B = pioset 2;
447 B = pioclr 2
448 RETURN

0 REM state 2: timeout connecting, restart slave
450 X = 0
454 S = status
0 REM if we have a slave connection skip
455 IF S = 1 THEN 458
456 A = slave 1
457 Y = 1
0 REM safety, no master connections
458 A = disconnect 1
459 GOTO 410

0 REM state 3: master connected, restart slave
0 REM only if no slave connected
460 S = status
461 IF S = 11 THEN 464
462 A = slave 1
463 Y = 1
464 X = 4
465 ALARM 5
466 RETURN

0 REM state 4: connected, slave restarted
0 REM handle lost master connections
470 IF S < 1000 THEN 472
471 S = S - 1000
472 IF S < 100 THEN 474
473 S = S - 100
474 IF S < 10 THEN 480
0 REM still has master connection, blink reverse
475 ALARM 5
476 B = pioclr 2;
477 B = pioset 2
478 RETURN

0 REM lost master connection, reset state
480 A = unlink 2
481 X = 0
482 IF $4[0] = 0 THEN 485
483 X = 1
484 $3 = $4
485 GOTO 410

@PIN_CODE 488
488 $0 = $1
489 RETURN

@PIO_IRQ 490
0 REM local request for DSR
490 IF $0[4] = 48 THEN 483
0 REM modem control to other side
491 A = modemctl 1
492 RETURN
493 A = modemctl 0
494 RETURN

@CONTROL 495
0 REM remote request for DTR
495 IF $0[0] = 49 THEN 498
496 A=pioset 6
497 RETURN
498 A=pioclr 6
499 RETURN
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License