Home
| pfodApps/pfodDevices
| WebStringTemplates
| Java/J2EE
| Unix
| Torches
| Superannuation
|
| About
Us
|
Mobile Phone Controlled Led Driver -- OBSOLETE |
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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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.
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.
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?
Contact Forward Computing and Control by
©Copyright 1996-2020 Forward Computing and Control Pty. Ltd.
ACN 003 669 994