The Problem with Picaxe BASIC
When writing a large microcontroller program, it's very important that you write your code as clearly as possible. You should be able to figure out, without a great deal of effort, exactly what it's supposed to do, and precisely how it's supposed to do it. If not, you won't be able to maintain your program, and will invariably introduce bugs that are hard to find.
The problem is particularly acute when you go back to one of your own programs a year after you've written it, or if you try to figure out how someone else's program works.
Picaxe BASIC, as it stands, isn't suited for large programs. Not even one little bit. With processors having limited program memory, this usually doesn't create insurmountable problems; you won't be writing large programs. But with the new breed supporting programs of several thousand lines, readability and maintainability become important issues.
So, when I write a large micro program, I invariably choose the C language. C, like most modern high-level languages, is well suited for large programs for a number of reasons. In part, because it supports long, descriptive function ("subroutine") and variable names. Also, it supports parameter passing and local variables in its functions. (And, for microcontroller purposes, C is a good language because it generates fairly compact and efficient machine code, as does Picaxe BASIC.)
Local variables are particularly important. A local variable is used solely within its function, and isn't visible to the rest of the program. A local variable may even share the name as another local does in another function, but these locals are completely independent of one another. When programming a Picaxe, however, no local variables means that it's just too easy to inadvertently change the value of a variable used elsewhere in your program.
These (and other) features contribute to C's ease of programming, readability, and maintainability.
Here's an example of a C function that takes a parameter at call ("mode") and uses a local variable ("wrk"):
Both the value contained in "wrk," and even the name "wrk" are unique to this function. I could place a variable called "wrk" in another function, the the two values and names would be totally independent of one another.
Commands followed by parentheses, such as 'Clear()' or 'SendStr(PwrBusStr,0x82),' are equivalent to Picaxe Basic's subroutine calls: Clear() would be equivalent to 'gosub Clear.'
My last attempt at writing a large 28X1 program was an utter disaster. I wasn't (and am still not) comfortable with attempting major tasks in the Picaxe environment. I found myself writing code that I couldn't understand even the next day.
Plus, no local variables killed me. With several levels of subroutine calls, I found it just about impossible to keep track of my use of the byte and word registers. It was just too easy to write code that changed the value of, say, b0 in one subroutine, that was used elsewhere in the program for entirely different purposes. Voila! Instant untraceable bug!
The program was becoming impossible to write, understand, and debug. So I gave up, switched to a processor that I could program in C, and all was sweetness and light. (Almost a year later, I have no trouble at all reading and understanding that C program. Even better, I recently ran across some Delphi Object Pascal code I wrote in the last century, and had no problem following the code that I wrote back in the stone age.)
A year after my aborted attempt at a large Picaxe program, I'm once again trying to write a large 28X1 program. Going in, though, I realized that if I tried to write it "the Picaxe way," I'd be doomed to failure once again. So even before I started designing my program, I put a lot of thought into solving some of the shortcomings of Picaxe BASIC.
I think I've been able to address, at least partially, the local variable problem and the descriptive variable name problem. And some planned usage of the different types of Picaxe memory goes a long way toward solving some other problems, too.
Trying to Fix Some Problems
There's no true way of deriving local variables in Picaxe Basic. The solution is to be found in intelligent usage of the memory already present in the 28X1.
We have four different types of memory available to us in the X1 series:
a. EEPROM memory
b. register variables: example - "b0" "w0"
c. scratchpad RAM
d. Table ROM
(and, technically a fifth type, program memory, but I don't include it since it's not something we can really use, other than the Table ROM that resides in program memory.)
Solving the local variable problem means that you set some rules for yourself while you're designing your program, and you stick to them. You must know your rules well, and never violate them. Not even once. Not even a little bit.
My own rules are these:
1. Use EEPROM and Table memory for all program constants. These are things like display strings and fixed constant data.
2. Use scratchpad RAM for all, or almost all, global variable data. Stuff you put in here will be any more-or-less permanent variable storage that will have a long life within your program, but must be changed. Also, by the use of pointers to scratchpad RAM, you can easily derive array data types.
When using scratchpad RAM, try to use your own pointers for most stuff. Use @ptr, @ptrinc, and @ptrdec only when using these facilities will be clear, unambiguous, and where these pointer operations can't be corrupted by other code in your program. A more maintainable method would be to manage your own pointers stored as register variables, and use the get and put commands.
When you assign scratchpad RAM, use fixed, descriptive addresses declared with 'symbol' statements. And, when declaring a block of scratchpad RAM, specify both a start and end address for that block:
It's much easier to figure out what 'for b0 = FreqBuff to BuffLast' means than 'for b0 = 20 to 29.' Also, if you change a scratchpad block assignment, using descriptive address tags within your program rather than absolute addresses means that all you have to do is change the assignment within the symbol declaration block in your program's preamble. You won't have to try to find and update every instance of 'for b0 = 20 to 29' in your listing.
(continued in next post)
When writing a large microcontroller program, it's very important that you write your code as clearly as possible. You should be able to figure out, without a great deal of effort, exactly what it's supposed to do, and precisely how it's supposed to do it. If not, you won't be able to maintain your program, and will invariably introduce bugs that are hard to find.
The problem is particularly acute when you go back to one of your own programs a year after you've written it, or if you try to figure out how someone else's program works.
Picaxe BASIC, as it stands, isn't suited for large programs. Not even one little bit. With processors having limited program memory, this usually doesn't create insurmountable problems; you won't be writing large programs. But with the new breed supporting programs of several thousand lines, readability and maintainability become important issues.
So, when I write a large micro program, I invariably choose the C language. C, like most modern high-level languages, is well suited for large programs for a number of reasons. In part, because it supports long, descriptive function ("subroutine") and variable names. Also, it supports parameter passing and local variables in its functions. (And, for microcontroller purposes, C is a good language because it generates fairly compact and efficient machine code, as does Picaxe BASIC.)
Local variables are particularly important. A local variable is used solely within its function, and isn't visible to the rest of the program. A local variable may even share the name as another local does in another function, but these locals are completely independent of one another. When programming a Picaxe, however, no local variables means that it's just too easy to inadvertently change the value of a variable used elsewhere in your program.
These (and other) features contribute to C's ease of programming, readability, and maintainability.
Here's an example of a C function that takes a parameter at call ("mode") and uses a local variable ("wrk"):
Code:
void ShowBusVoltage(byte mode)
{
// Display the power bus voltage for 47k + 10k voltage divider,
// 5.0 v Aref
long wrk;
HideCurs();
Clear();
SendStr(PwrBusStr,0x82);
CursorTo(0xc4);
wrk = (long) analogRead(PwrBus) * 50 * 57 / 1024;
if ((wrk / 1000) > 0)
{
Display(wrk / 1000 + '0');
wrk = wrk % 1000;
}
Display(wrk / 100 + '0');
wrk = wrk % 100;
Display('.');
Display(wrk / 10 + '0');
SendStr(VDCStr,0);
Mode = mode;
}
Commands followed by parentheses, such as 'Clear()' or 'SendStr(PwrBusStr,0x82),' are equivalent to Picaxe Basic's subroutine calls: Clear() would be equivalent to 'gosub Clear.'
My last attempt at writing a large 28X1 program was an utter disaster. I wasn't (and am still not) comfortable with attempting major tasks in the Picaxe environment. I found myself writing code that I couldn't understand even the next day.
Plus, no local variables killed me. With several levels of subroutine calls, I found it just about impossible to keep track of my use of the byte and word registers. It was just too easy to write code that changed the value of, say, b0 in one subroutine, that was used elsewhere in the program for entirely different purposes. Voila! Instant untraceable bug!
The program was becoming impossible to write, understand, and debug. So I gave up, switched to a processor that I could program in C, and all was sweetness and light. (Almost a year later, I have no trouble at all reading and understanding that C program. Even better, I recently ran across some Delphi Object Pascal code I wrote in the last century, and had no problem following the code that I wrote back in the stone age.)
A year after my aborted attempt at a large Picaxe program, I'm once again trying to write a large 28X1 program. Going in, though, I realized that if I tried to write it "the Picaxe way," I'd be doomed to failure once again. So even before I started designing my program, I put a lot of thought into solving some of the shortcomings of Picaxe BASIC.
I think I've been able to address, at least partially, the local variable problem and the descriptive variable name problem. And some planned usage of the different types of Picaxe memory goes a long way toward solving some other problems, too.
Trying to Fix Some Problems
There's no true way of deriving local variables in Picaxe Basic. The solution is to be found in intelligent usage of the memory already present in the 28X1.
We have four different types of memory available to us in the X1 series:
a. EEPROM memory
b. register variables: example - "b0" "w0"
c. scratchpad RAM
d. Table ROM
(and, technically a fifth type, program memory, but I don't include it since it's not something we can really use, other than the Table ROM that resides in program memory.)
Solving the local variable problem means that you set some rules for yourself while you're designing your program, and you stick to them. You must know your rules well, and never violate them. Not even once. Not even a little bit.
My own rules are these:
1. Use EEPROM and Table memory for all program constants. These are things like display strings and fixed constant data.
2. Use scratchpad RAM for all, or almost all, global variable data. Stuff you put in here will be any more-or-less permanent variable storage that will have a long life within your program, but must be changed. Also, by the use of pointers to scratchpad RAM, you can easily derive array data types.
When using scratchpad RAM, try to use your own pointers for most stuff. Use @ptr, @ptrinc, and @ptrdec only when using these facilities will be clear, unambiguous, and where these pointer operations can't be corrupted by other code in your program. A more maintainable method would be to manage your own pointers stored as register variables, and use the get and put commands.
When you assign scratchpad RAM, use fixed, descriptive addresses declared with 'symbol' statements. And, when declaring a block of scratchpad RAM, specify both a start and end address for that block:
Code:
symbol FreqBCD = 0 ' 10 bytes. 10 digit unpacked BCD freq
' Example: 12,345,678.90 Hz
symbol FreqLast = 9
symbol ModifiedFreq = 10 ' 10 bytes. Holds modified display frequency
symbol ModLast = 19
symbol FreqBuff = 20 ' 10 bytes. Unpacked BCD freq work register
symbol BuffLast = 29
symbol FreqBuff1 = 30 ' 10 bytes. Extra 10-byte work register
symbol Buff1Last = 39
symbol TuningWord = 40 ' 5 bytes: Addr 40 = Least Sig Byte
' sent to AD9851 LSByte first, LSBit first
symbol TWLast = 44
(continued in next post)
Last edited: