Home | pfodApps/pfodDevices | WebStringTemplates | Java/J2EE | Unix | Torches | Superannuation | | About Us
 

Forward Logo (image)      

Mobile Phone Controlled Led Driver -- OBSOLETE
See Andriod Controlled Led Driver for replacement

A Tutorial Part 2 - More User Friendly Interface

by Matthew Ford 1st August 2012 (original 12th April 2010)
© Forward Computing and Control Pty. Ltd. NSW Australia
All rights reserved.

Note: The Btterm J2ME application used here does not appear to be available any more.
This page is for historical interest only and as an introduction to Andriod Controlled Led Driver using pfodApp which provides many more features then Btterm.

Enhancing the code to provide a more user friendly interface

As you have seen in Part 1 of this Tutorial, the user interface for remote control of the Led driver is not particularly user friendly, having been designed for development debugging. This part of the tutorial will cover providing more user friendly feedback to the user and simple instructions if they enter an invalid command.


Outline of the Tutorial

Part 1 – Connecting to your Mobile Phone

Part 2 – More User Friendly Interface

The User Interface

The RemoteLedDriver code still accepts the same user commands u, d, 0, 1, 2, 3, but as you can see in the above screen shot of the mobile phone, if you enter say '1' the driver responds current level and % light (current).

1
>
> Level 4
> Light 2.6%

While if you enter an invalid command, such as 'j', then the drive responds with a prompt of the valid commands, in addition to the current level and level.

j
>
> Level 4
> Light 2.6%
>
> >use keys
> u d 0 1 2 3

Unlike the previous bluetooth drive there is no continuous streaming of the ADC reading level. Although you can use the undocumentd 'i' command to request it. To see what the current setting is just enter any invalid command character.

If you just want to use the driver then downloaded the RemoteLedDriver.asm and set up a new Atmel Studio4 project (as described in “Writing the Program to the uC” on Build a Basic uC Led Driver). You will need to change the .hex file to point to the compiled RemoteLedDriver.hex file. Note: there is no EEPROM file for the RemoteLedDrive, so leave that part of the dialog blank as shown in Writing the Program to the uC”. The previous BluetoothDriver.asm had an EEPROM section, see below for how this data was moved to the code file (.hex)

Note on NewLine format

Different computer operating systems have different ideas of what a new line is. The choices are a single LineFeed char 10 or single CarrageReturn char 13 or the pair CarrageReturn followed by a LineFeed char (13, 10). Most terminal software will allow you to configure which chars are interpreted as newlines. Alternatively the RemoteLedDriver.asm code has a macro NEW_LINE which it uses to send the appropriate new line chars. The BTTERM mobile phone terminal software used here expects new lines to be sent as char 13 and cannot be configure so the NEW_LINE macro is set to send char 13. If you are using the code with some other terminal program you may need to either configure that terminal software to expect CarrageReturn, char 13 as the new line or change the NEW_LINE macro to suit the terminal program.

If you want more information on how the code works then read on.

RemoteLedDriver Code Features

The complete code for this version of the Led Driver is in RemoteLedDriver.asm (see “Writing the Program to the uC” on Build a Basic uC Led Driver for how to set up an Atmel Studio4 project to compile and load this code). This code provides a number of extra features:-
a) If the user sends an invalid command, the driver returns a short message containing a list of valid command chars.

b) When the level is changed, the driver returns the level number and % of max power for that level.

c) When going up and down, logarithmic steps are used to more closely match the human eye's perceived change in brightness.

d) Every 10secs the Led driver send an IamAlive message.

User Help Message

When the user sends an invalid command character to the driver, it would be helpful to let them know which commands are valid ones. When an invalid command is received by the driver, it sends back a two line message

> >use keys
> u d 0 1 2 3 

In the previous BluetoothDriver.asm code, the message constants where stored in EEPROM. An alternative is to store the constants in program space. The advantage of storing the constants in the program space is that they can be loaded more quickly and easily. The disadvantages are that they use up program space and the values cannot be changed by the code. Neither of these disadvantages are problems in this case.

To define the message in the program space you use

// .db line must have an even number of chars
// first value is the number of chars to load
Error_Msg_Line1:
//   0   1   2   3    4    5    6   7    8     9   10  11
//           >   u    s    e        k    e     y   s
.db 11, 32, 62, 117, 115, 101, 32, 107, 101, 121, 115, 32
Error_Msg_Line2:
//   0   1    2   3   4    5   6   7   8   9  10  11  12  13
//           u        d        0       1       2       3
.db 13, 32, 117, 32, 100, 32, 48, 32, 49, 32, 50, 32, 51, 32 

.db defines must have an even number of bytes each. In this code the first byte is used to specify the number of bytes in the message so it can be easily loaded by the program.

LOAD_CHARS_FROM Macro

Here is a routine that loads Error_Msg_Line1 into the RS232 send buffer. It assumes XH,XL register pair has been initialized to the address of the RS232 buffer before this routine is called. e.g.

    ldi XH, high(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below
    ldi XL, low(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below

The routine first sets the Z register pair to the address of the message (note the 2* to convert the address in words to bytes). The lpm instruction then loads one byte at a time into the Temp register and then stores it in the RS232 buffer using the st instruction. The first byte loaded is special, it holds the number of bytes in the message and is not stored in the RS232 buffer but used to set the loop counter Temp2. Note that the Z register pair is incremented after each load and the X register pair is decremented BEFORE each store.

//-----------------------------------------------------
// LOAD_CHARS
// loads chars 
// On entry XH,XL is assumed to point to the RS232 Buffer 
//-----------------------------------------------------
.def CharCount = r15 // temp reg
.def Temp     = r16// Temporary register
LOAD_CHARS:
    push CharCount
    push Temp
    push ZH
    push ZL

    ldi ZH, high(2*Error_Msg_Line1)
    ldi ZL, low(2*Error_Msg_Line1)     // Set pointer to RAM data
    lpm Temp2, Z+    // this is the number of chars to load
Loop:
    lpm Temp, Z+
    st -X, Temp
    dec Temp2
    brne LOOP

END_LOAD_CHARS:
    pop ZL
    pop ZH   
    pop Temp
    pop CharCount
ret
//------------------------------------------------------

This routine of fine for loading the bytes of one message into the RS232 buffer, but there are a number of messages that the program needs to load. One approach would be to push the current contents of the Z register before call setting it to the message address and then calling the routine to load the message, and then on return popping the the Z register to restore its previous contents. An alternative approach which avoids these extra push and pops for each call is to make the routine into a macro where the Message label is passed as an argument. Macros have been discussed earlier in the RS232 code, you can pass arguments to the macro and access them in the code using the @ syntax. For example if the macro is called LOAD_CHARS_FROM then the following 'statement' (actually a preprocessor instruction)

LOAD_CHARS_FROM Error_Msg_Line1

sets @0 to the text string Error_Msg_Line1

so

    ldi ZH, high(2*Error_Msg_Line1)
    ldi ZL, low(2*Error_Msg_Line1)     // Set pointer to RAM data

becomes, in the macro code

    ldi ZH, high(2* @0)
    ldi ZL, low(2* @0)     // Set pointer to RAM data

and the pre-compiler replaces the @0 with the text passed as the first argument to the macro before it compiles the code.

Compiler Generated Macro

This part of converting the routine to a macro straightforward. The problem comes with converting the Loop: label. You cannot just leave this label as is because the second time you use the macro the label will be defined again and the compiler will (rightly) complain. A simple way round this problem is to let the compiler work out for you the jump count and then hard code this into the macro. To do this, in RemoteLedDriver.asm, uncomment the routine LOAD_CHARS and then compile the file.

Then from the Debug menu, click on “Select Platform and Device..” and select AVR Simulator as the “Debug Platform”.


Then choose Debug → Start Debugging. Once the debugger has started you can select the View menu and then open the Disassembler window. This shows the original source code together with the the compiled assembly instructions. Scroll down until you get to the LOAD_CHAR routine and copy and past the entire routine into to top of the source file.


Then stop the debugger. In the source file, comment out all the source lines and remove the code offset numbers and instruction codes at the beginning of each compiled assembly line to leave just the assembly instructions. Finally add the .macro name (LOAD_CHARS_FROM) and .endmacro lines and replace Error_Msg_Line1 with @0 to get the completed macro

.macro LOAD_CHARS_FROM // one argument 
//1849:         push CharCount
PUSH      R15          //  Push register on stack
//1850:         push Temp
PUSH      R16        //    Push register on stack
//1851:         push ZH
PUSH      R31        //    Push register on stack
//1852:         push ZL
PUSH      R30        //    Push register on stack
//1854:         ldi ZH, high(2*Error_Msg_Line1)
LDI       R31,high(2*(@0))    //   Load immediate
//1855:         ldi ZL, low(2*Error_Msg_Line1)     // Set pointer to RAM data
LDI       R30,low(2*(@0))     //  Load immediate
//1856:         lpm Temp2, Z+    // this is the number of chars to load
LPM       R15,Z+      //   Load program memory and postincrement
//@000002F7: Loop
//:         lpm Temp, Z+
LPM       R16,Z+      //   Load program memory and postincrement
//1859:         st -X, Temp
ST        -X,R16      //   Store indirect and predecrement
//1860:         dec Temp2
DEC       R15         //   Decrement
//1861:         brne LOOP
BRNE      PC-0x03     //   Branch if not equal
//@000002FB: END_LOAD_CHARS
//1864:         pop ZL
POP       R30        //    Pop register from stack
//1865:         pop ZH   
POP       R31        //    Pop register from stack
//1866:         pop Temp
POP       R16       //     Pop register from stack
//1867:         pop CharCount
POP       R15       //     Pop register from stack
.endmacro

Note that the brne LOOP statement has been replaced by BRNE PC-0x03 by the compiler. There are no longer any labels in the macro so it can be used multiple times. e.g.

    ldi XH, high(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below
    ldi XL, low(RS232_BUFFER_1+1) // add 1 due to -X in  ST -X,Temp below

    NEW_LINE
    LOAD_CHARS_FROM Error_Msg_Line1
    NEW_LINE
    LOAD_CHARS_FROM Error_Msg_Line2
    NEW_LINE

Here NEW_LINE is another macro that puts the new line chars into the RS232 buffer.

The final step in loading the RS232 send buffer with the message to be sent is to set the RS232_SEND_COUNT by subtracting the current XL from this initial value.

    //set number of chars
    ldi Temp, low(RS232_BUFFER_1+1)
    sub Temp, XL
    // only need to do the low byte as buffer size < 255
    // and sub result above will wrap round to the correct difference
    sts RS232_SEND_COUNT, Temp // set number of chars to send

The LOAD_CHARS_FROM macro is used repeatedly in the code to put messages into the RS232 send buffer.

Level and Percent Light Display

In order to send back the current level and % of max power for that level, the code needs to be able to convert from binary to Ascii chars to send to the mobile phone display. CONVERT_13BITS provides this functionality.

A Binary to ASCII formatter (CONVERT_13BITS)

CONVERT_13BITS converts up to 13 binary bits into an ascii string to send via RS232. 13 binary bits is enough to hold any ADC reading or set point. This routine is not the fastest or the smallest, but it is simple to understand and flexible to modify to return different formats. An outline follows here.

CONVERT_13BITS uses a simple subtraction method to do the conversion. It first tests if the number is negative and if so outputs a '-' char and negates the number to make the number positive. The routine then subtracts 1000's, 100's, 10's and 1's in four loops to find the number in each position. For example for the 1000's loop, Temp hold the number of 1000's in the number. Subtract 1000 and if the result is not negative increment temp and loop. If the result is negative then add 1000 back, store the count as an ascii character and go on to the 100's loop

    clr Temp
CONVERT_13BITS_LOOP_1000:
    subi ZL, low(1000)
    sbci  ZH, high(1000)
    brmi CONVERT_13BITS_ADD_BACK_1000
    inc Temp // else inc temp
    rjmp CONVERT_13BITS_LOOP_1000 // try again
CONVERT_13BITS_ADD_BACK_1000:
    subi ZL, low(-1000)
    sbci  ZH, high(-1000)
CONVERT_13BITS_STORE_1000:
    rcall CONVERT_13BITS_STORE_DIGIT

The ascii char is given by subi temp, -48 i.e temp - (-48) == temp + 48, where temp holds the count.

There formats are catered for by defines

// #define CONVERT_13BITS_LEFT
//  left justifies the result  eg |-21 and |2
// #define CONVERT_13BITS_RIGHT
//  right justifies the result eg |  -21 and |    2
// #define CONVERT_13BITS_ZEROS
//  right justifies with leading zeros eg |-0021 and | 0002
// if you uncomment more then one you will get a compile error (duplicate label)
 

These defines control which block of code is used in the routine CONVERT_13BITS_STORE_DIGIT, see the source code for the details. The RemoteLedDriver uses the #define CONVERT_13BITS_LEFT to give left justified numbers with no padding.

Finally for sending the Light Level reading as a % of maximum as slightly modified CONVERT_13BITS_PERCENT routine is used to format 1000 as 100.0%, see the code for details.

Logarithmic Levels

There are 11 predefined levels in the code, 0 to 10. These are provide an approximately logarithmic increase in light level which more closely matches the response of the human eye so each change is level produces a noticeable change in light level. In the previous BluetoothDriver.asm code, the u and d commands just changed the setpoint by 10 each time, but because of the response of the human eye, the step from 20 to 10 was much more noticeable then the step from 100 to 90. The step from 20 to 10 halved the light, while the step from 90 to 100 only decreased the light by 10%. This meant that the top 3 or 4 levels produced little visible change in light. To fix this the levels where changed to approximately logarithmic intervals. No attempt has been made to make the setpoints for each level exact. They depend on the exact response of the human eye and the light output of the LED at various currents. Feel free to vary them to suit you own perception of what are 'even' steps.

The levels are defined by a .dw statement as 11 words (2 bytes == 16 bits). The 0, 1, 2 3 commands are mapped to levels 0, 4,7 and 10, while the u and d commands move up or down one level at a time.

Log_SetPoint:
     // 0 == 0, 1==4    2==7   3 == 10
     // Levels
     // 0  1  2  3  4  5   6    7   8     9    10 
.dw     0, 2, 6, 12,26,57,129, 273, 509, 794, 1000 // words 

These 2 bytes values are used to set the setpoint for the led current controller for the corresponding level. They are loaded into the SP_Low and SP_High registers by the LOAD_SETPOINT_FROM_LEVEL routine.

LOAD_SETPOINT_FROM_LEVEL

This routine assumes the Temp register contains the level number, 0 to 10 and then uses it to index into the data array of levels.

LOAD_SETPOINT_FROM_LEVEL:
  // limit level to Level_Max
  push Temp
  tst Level
  brpl LOAD_SETPOINT_FROM_LEVEL_TEST_MAX
  clr Level // set to zero if negative

LOAD_SETPOINT_FROM_LEVEL_TEST_MAX:
  ldi Temp, Level_Max
  cp Level, Temp
  brmi LOAD_SETPOINT_FROM_LEVEL_LOAD // Level < Level_Max
  mov Level, Temp //set to Level_Max if too largeLOAD_SETPOINT_FROM_LEVEL_LOAD:
  push ZH
  push ZL
  ldi ZH, high(2*Log_SetPoint)
  ldi ZL, low(2*Log_SetPoint)
  // add level offset
  mov Temp, Level
  lsl Temp // 2 times
  add ZL, Temp // add Level to addres to get word to load
  sbci ZH, 0  // sub 0 with carry
 
  lpm Temp, Z+
  mov SP_Low, Temp
  lpm Temp, Z+
  mov SP_High, Temp
  pop Temp
  pop ZL
  pop ZH
END_LOAD_SETPOINT_FROM_LEVEL:
ret

Note in the code above that because each level is 2 bytes we need to double Temp before adding it to the Z registers to get the correct byte address to load from. The lsl (logical shift left) statement is used to double the Temp register. After the doubled Temp to the low byte of the Z register pair, any carry (overflow) generated is added to the high Z byte using the sbci statement. Subtracting 0 means only the carry is applied to ZH.

Finally the lpm statement is used to load first the Low byte of the setpoint and then the high byte of the setpoint. The .dw statement stores the low byte of the 2 byte word at the lower address. The Z+ form of the lpm statement automatically increments ZH,ZL by one after each load.

I Am Alive Message

The final feature of the code I will mention is the IamAlive message. The idea behind this is that every so often, say about every 8sec, the Led Controller will broadcast it current level and light output if it has not received a command in that time. This means that every 8secs or so your mobile phone will display the current level and light output to tell you the controller is still running and available. It also means that within 8secs after you start your mobile phone BTTERM you should get a message from the driver with the current level and light output which is useful if you just want to check on the status.

The code has a timer and flag for this function. The flag is TRIGGER_IamAlive. This is set by a counter 8sec after the last RS232 transmit. The code routine that uses this flag to send the level and % light is not in the source code provided here and its implementation is left as an exercise for the reader. Things you should consider in implementing this feature what will happen if the user send a command key at the same time the timer goes off. What mixture of messages are sent back to the user? Would they be confusing? Does it matter?


Forward home page link (image)

Contact Forward Computing and Control by
©Copyright 1996-2020 Forward Computing and Control Pty. Ltd. ACN 003 669 994