Is this brilliant, wicked, or horrible?
-
Programming straight C, for an embedded system, I need to define a number of tunes such that they can be identified by number. To play a tune, the system needs to have a pointer to the data and the length (note that 00 bytes are valid within tune data, so strlen is not usable).
/* For each tune, include an enum name and a string with the appropriate tune
data. Be sure to include a backslash at the end of each line, and leave a
blank line after the last tune definition. */// The real code contains an explanation of the hex codes
#define ALL_TUNES \
TUNE(INTRO_TUNE, "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30") \
TUNE(BEEP1, "\x4B\x60") \
TUNE(BEEP2, "\x4B\x68") \
TUNE(BEEP3, "\x4B\x70") \
TUNE(RAZZ, "\xFB\x1C\x00") \#define TUNE(x,y) x,
enum {ALL_TUNES TUNE_COUNT};
#undef TUNE#ifdef MAKE_TUNE_DAT
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
ALL_TUNES;
#undef TUNE#define TUNE(x,y) TUNEDAT_##x,
char const * const tunes_dat[] = {ALL_TUNES 0};
#undef TUNE#define TUNE(x,y) sizeof(TUNEDAT_##x)-1,
ub const tunes_len[] = {ALL_TUNES 0};
#undef TUNE#endif // MAKE_TUNE_DAT
I would have liked to define the tune data using {1,2,3}-style initializers (I'd save a wasted zero byte on each one), but the only way to make the macro handler go along with that would be to use something like "x" instead of commas and #define that as a separator; that seems a bit too ugly for my taste. Sometimes when using this technique, I put everything in the ALL_TUNES definition in a standalone file, so I can #include it rather than expanding a macro; that obviates the need for all the ugly backslashes, but multiply including the same file is a bit ugly and is incompatible with #pragma once. If I didn't use "funny" macros, what would be the best way to keep initializers lined up with enum definitions? I really don't like the style:
enum {HARRY, RON, HERMIONE};
const char const *names[] = {"Harry", "Ron", "Hermione"};since it's very easy for the enum list and the array initialization to get out of sync.
-
Programming straight C, for an embedded system, I need to define a number of tunes such that they can be identified by number. To play a tune, the system needs to have a pointer to the data and the length (note that 00 bytes are valid within tune data, so strlen is not usable).
/* For each tune, include an enum name and a string with the appropriate tune
data. Be sure to include a backslash at the end of each line, and leave a
blank line after the last tune definition. */// The real code contains an explanation of the hex codes
#define ALL_TUNES \
TUNE(INTRO_TUNE, "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30") \
TUNE(BEEP1, "\x4B\x60") \
TUNE(BEEP2, "\x4B\x68") \
TUNE(BEEP3, "\x4B\x70") \
TUNE(RAZZ, "\xFB\x1C\x00") \#define TUNE(x,y) x,
enum {ALL_TUNES TUNE_COUNT};
#undef TUNE#ifdef MAKE_TUNE_DAT
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
ALL_TUNES;
#undef TUNE#define TUNE(x,y) TUNEDAT_##x,
char const * const tunes_dat[] = {ALL_TUNES 0};
#undef TUNE#define TUNE(x,y) sizeof(TUNEDAT_##x)-1,
ub const tunes_len[] = {ALL_TUNES 0};
#undef TUNE#endif // MAKE_TUNE_DAT
I would have liked to define the tune data using {1,2,3}-style initializers (I'd save a wasted zero byte on each one), but the only way to make the macro handler go along with that would be to use something like "x" instead of commas and #define that as a separator; that seems a bit too ugly for my taste. Sometimes when using this technique, I put everything in the ALL_TUNES definition in a standalone file, so I can #include it rather than expanding a macro; that obviates the need for all the ugly backslashes, but multiply including the same file is a bit ugly and is incompatible with #pragma once. If I didn't use "funny" macros, what would be the best way to keep initializers lined up with enum definitions? I really don't like the style:
enum {HARRY, RON, HERMIONE};
const char const *names[] = {"Harry", "Ron", "Hermione"};since it's very easy for the enum list and the array initialization to get out of sync.
-
PIEBALDconsult wrote:
See this[^] fine article. Big Grin
Ah, but that requires running a separate tool in addition to the compiler. With my approach, a simple compile will take care of everything. Actually, I like the approach rather a lot, though there are some annoyances. It would be much nicer if I could figure a good way to avoid wasting an item at the end of the list without having to special-case it (e.g. define an LTUNE macro for the last tune only), include array initializations that contain commas, or have some array items without associated enumerations (would be handy for generating overlapped constant arrays). As it is, avoiding wasted items at the end of the array requires using special-case macros, the best I can do for having array initializations contain commas is to define separate macros for those (e.g. #define SEVEN_ZEROES {0,0,0,0,0,0,0}), and I sometimes have to manually create unique bogus identifiers which serve no purpose but to fill up gaps in an enumeration.
-
Programming straight C, for an embedded system, I need to define a number of tunes such that they can be identified by number. To play a tune, the system needs to have a pointer to the data and the length (note that 00 bytes are valid within tune data, so strlen is not usable).
/* For each tune, include an enum name and a string with the appropriate tune
data. Be sure to include a backslash at the end of each line, and leave a
blank line after the last tune definition. */// The real code contains an explanation of the hex codes
#define ALL_TUNES \
TUNE(INTRO_TUNE, "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30") \
TUNE(BEEP1, "\x4B\x60") \
TUNE(BEEP2, "\x4B\x68") \
TUNE(BEEP3, "\x4B\x70") \
TUNE(RAZZ, "\xFB\x1C\x00") \#define TUNE(x,y) x,
enum {ALL_TUNES TUNE_COUNT};
#undef TUNE#ifdef MAKE_TUNE_DAT
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
ALL_TUNES;
#undef TUNE#define TUNE(x,y) TUNEDAT_##x,
char const * const tunes_dat[] = {ALL_TUNES 0};
#undef TUNE#define TUNE(x,y) sizeof(TUNEDAT_##x)-1,
ub const tunes_len[] = {ALL_TUNES 0};
#undef TUNE#endif // MAKE_TUNE_DAT
I would have liked to define the tune data using {1,2,3}-style initializers (I'd save a wasted zero byte on each one), but the only way to make the macro handler go along with that would be to use something like "x" instead of commas and #define that as a separator; that seems a bit too ugly for my taste. Sometimes when using this technique, I put everything in the ALL_TUNES definition in a standalone file, so I can #include it rather than expanding a macro; that obviates the need for all the ugly backslashes, but multiply including the same file is a bit ugly and is incompatible with #pragma once. If I didn't use "funny" macros, what would be the best way to keep initializers lined up with enum definitions? I really don't like the style:
enum {HARRY, RON, HERMIONE};
const char const *names[] = {"Harry", "Ron", "Hermione"};since it's very easy for the enum list and the array initialization to get out of sync.
whats the advantage of the above method over a lookup table #pragma placed in flash?
#pragma DEFINED_FLASH_SEG __no_init const unsigned char* INTRO_TUNE = "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30"; ... #pragma DEFAULT int main() { ... unsigned char* tune_to_play = (unsigned char*)INTRO_TUNE; ... }
the define matrix seems to complicate things to me, i am stupid so i like things simple -
whats the advantage of the above method over a lookup table #pragma placed in flash?
#pragma DEFINED_FLASH_SEG __no_init const unsigned char* INTRO_TUNE = "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30"; ... #pragma DEFAULT int main() { ... unsigned char* tune_to_play = (unsigned char*)INTRO_TUNE; ... }
the define matrix seems to complicate things to me, i am stupid so i like things simplekillabyte wrote:
whats the advantage of the above method over a lookup table #pragma placed in flash?
The advantages in this particular case come from being able to use a build-independent byte to reference the different tunes instead of a 2-byte pointer and in being able to use math to select a tune (e.g. PLAY_TUNE(TUNE_ALARM0 + alarm_number)). I don't think tune references will have to persist across rebuilds, though alarm numbers might (in the form of log files). Not particularly huge advantages, but the approach (expanding a macro multiple times, processing its arguments differently) is applicable in many other scenarios. As another example, suppose I want to define a data structure along with a default instance. If my data-structure-item macro takes a type and field name, along with a value, I can define the structure and its default value together in the source code. For example:
#define MAKE_FNORD_RECORD(x,y) \
x(ub byte_field, 5) y\
x(ub another_byte,23) y\
x(ui short_field, 1234) y\
x(ul long_field, 59944) y\
x(char name[16],"FRED") y\
x(ub byte_array[4], {1 z 2 z 3 z 4}The last line doesn't have the "y" after it, since there shouldn't be a trailing delimiter. The "x" macro will return either the first or second part depending upon whether MAKE_FNORD_RECORD is being used to define the structure or the default instance; the "y" macro will be a semicolon or comma; the "z" will be a comma unconditionally (I suppose I could probably have used y since the second part will be ignored except when "y" is a comma). I used to have separate definitions for my structures and their default values, but the initializations kept getting out of sync. Having a routine to code:
memset(default_fnord, 0, sizeof(default_fnord));
default_fnord.byte_field = 5;
default_fnord.another_byte = 23;
default_fnord.short_field = 1234;
default_fnord.long_field = 59944;
strcpy(default_fnord.name, "FRED JONES");
default_fnord.byte_array[0] = 1;
default_fnord.byte_array[1] = 2;
default_fnord.byte_array[2] = 3;
default_fnord.byte_array[3] = 4;might have been kinda sorta okay, but every byte initialized in that fashion requires four byte of code (as compared to one when the byte is part of a const struct). A few hundred bytes worth of initialization in a small micro that's cramped for code space may be significant.
-
killabyte wrote:
whats the advantage of the above method over a lookup table #pragma placed in flash?
The advantages in this particular case come from being able to use a build-independent byte to reference the different tunes instead of a 2-byte pointer and in being able to use math to select a tune (e.g. PLAY_TUNE(TUNE_ALARM0 + alarm_number)). I don't think tune references will have to persist across rebuilds, though alarm numbers might (in the form of log files). Not particularly huge advantages, but the approach (expanding a macro multiple times, processing its arguments differently) is applicable in many other scenarios. As another example, suppose I want to define a data structure along with a default instance. If my data-structure-item macro takes a type and field name, along with a value, I can define the structure and its default value together in the source code. For example:
#define MAKE_FNORD_RECORD(x,y) \
x(ub byte_field, 5) y\
x(ub another_byte,23) y\
x(ui short_field, 1234) y\
x(ul long_field, 59944) y\
x(char name[16],"FRED") y\
x(ub byte_array[4], {1 z 2 z 3 z 4}The last line doesn't have the "y" after it, since there shouldn't be a trailing delimiter. The "x" macro will return either the first or second part depending upon whether MAKE_FNORD_RECORD is being used to define the structure or the default instance; the "y" macro will be a semicolon or comma; the "z" will be a comma unconditionally (I suppose I could probably have used y since the second part will be ignored except when "y" is a comma). I used to have separate definitions for my structures and their default values, but the initializations kept getting out of sync. Having a routine to code:
memset(default_fnord, 0, sizeof(default_fnord));
default_fnord.byte_field = 5;
default_fnord.another_byte = 23;
default_fnord.short_field = 1234;
default_fnord.long_field = 59944;
strcpy(default_fnord.name, "FRED JONES");
default_fnord.byte_array[0] = 1;
default_fnord.byte_array[1] = 2;
default_fnord.byte_array[2] = 3;
default_fnord.byte_array[3] = 4;might have been kinda sorta okay, but every byte initialized in that fashion requires four byte of code (as compared to one when the byte is part of a const struct). A few hundred bytes worth of initialization in a small micro that's cramped for code space may be significant.
supercat9 wrote:
use math to select a tune (e.g. PLAY_TUNE(TUNE_ALARM0 + alarm_number)).
i have always been a fan of
switch(alarm_number){case TUNE_ALARM0:}
style of things.supercat9 wrote:
I don't think tune references will have to persist across rebuilds, though alarm numbers might (in the form of log files). Not particularly huge advantages, but the approach (expanding a macro multiple times, processing its arguments differently) is applicable in many other scenarios.
i have used a kinda similar marco arrangment to assign different key board functions to the same keypad HW across the different builds(we put a diff sticker on the front and call it a dif product haha). the FNORD example given is #definitly magic i like it :thumbsup: we embedded devs always have to watch the code space we use, not many other devs even consider such things, although lately i am using micros that are more powerfull than the first PC i owned!!! so it is becoming less and less a problem for me.
-
PIEBALDconsult wrote:
See this[^] fine article. Big Grin
Ah, but that requires running a separate tool in addition to the compiler. With my approach, a simple compile will take care of everything. Actually, I like the approach rather a lot, though there are some annoyances. It would be much nicer if I could figure a good way to avoid wasting an item at the end of the list without having to special-case it (e.g. define an LTUNE macro for the last tune only), include array initializations that contain commas, or have some array items without associated enumerations (would be handy for generating overlapped constant arrays). As it is, avoiding wasted items at the end of the array requires using special-case macros, the best I can do for having array initializations contain commas is to define separate macros for those (e.g. #define SEVEN_ZEROES {0,0,0,0,0,0,0}), and I sometimes have to manually create unique bogus identifiers which serve no purpose but to fill up gaps in an enumeration.
I'd prefer a microgrammar over XML, but otherthan that, including extgernal tools in the build process is rusualyl easy. It increases setup time for the environment, though.
Don't attribute to stupidity what can be equally well explained by buerocracy.
My latest article | Linkify!| FoldWithUs! | sighist -
supercat9 wrote:
use math to select a tune (e.g. PLAY_TUNE(TUNE_ALARM0 + alarm_number)).
i have always been a fan of
switch(alarm_number){case TUNE_ALARM0:}
style of things.supercat9 wrote:
I don't think tune references will have to persist across rebuilds, though alarm numbers might (in the form of log files). Not particularly huge advantages, but the approach (expanding a macro multiple times, processing its arguments differently) is applicable in many other scenarios.
i have used a kinda similar marco arrangment to assign different key board functions to the same keypad HW across the different builds(we put a diff sticker on the front and call it a dif product haha). the FNORD example given is #definitly magic i like it :thumbsup: we embedded devs always have to watch the code space we use, not many other devs even consider such things, although lately i am using micros that are more powerfull than the first PC i owned!!! so it is becoming less and less a problem for me.
killabyte wrote:
we embedded devs always have to watch the code space we use, not many other devs even consider such things, although lately i am using micros that are more powerfull than the first PC i owned!!! so it is becoming less and less a problem for me.
From time to time, I've pondered whether it might make sense to implement some sort of p-code compiler for the PIC18Fx series, and then write a large portion of a program using that. To allow linking with portions written in "normal C", the P-code compiler could produce as output an assembly-code file which would contain 'db' and 'dw' directives using a mixture of decimal and symbolic constants. For example, a 32-bit addition (var3 = var1 + var2) might hypothetically be coded as something like:
db >myvariable1 + 0xF0 ; Push 12-bit constant onto stack (upper 4 bits)
db <myvariable1 ; Lower 8 bits of constant
db 40 ; Fetch 32-bit operand from specified address
db >myvariable2 + 0xF0 ; Push 12-bit constant onto stack (upper 4 bits)
db <myvariable2 ; Lower 8 bits of constant
db 40 ; Fetch 32-bit operand from specified address
db 59 ; Add two 32-bit operands
db >myvariable3 + 0xF0 ; Push 12-bit constant onto stack (upper 4 bits)
db <myvariable3 ; Lower 8 bits of constant
db 41 ; Store 32-bit operandTen bytes. If the three variables were all accessible from the current bank, such an operation would take 24 bytes of direct code. If they were all in different non-shared banks (or weren't all known to be in the same bank), it would take more (how much more would depend upon whether and how the compiler would use index registers). It might be interesting to design such a P-code system, but I'm sure some systems exist for a variety of micros and I wouldn't particularly want to re-invent the wheel. Is something like gcc set up to target p-code systems? Any idea how adaptable it is?
-
Programming straight C, for an embedded system, I need to define a number of tunes such that they can be identified by number. To play a tune, the system needs to have a pointer to the data and the length (note that 00 bytes are valid within tune data, so strlen is not usable).
/* For each tune, include an enum name and a string with the appropriate tune
data. Be sure to include a backslash at the end of each line, and leave a
blank line after the last tune definition. */// The real code contains an explanation of the hex codes
#define ALL_TUNES \
TUNE(INTRO_TUNE, "\x4B\x8F\x32\x40\x3A\x44\x40\x4C\x40\x44\x38\x40\x30") \
TUNE(BEEP1, "\x4B\x60") \
TUNE(BEEP2, "\x4B\x68") \
TUNE(BEEP3, "\x4B\x70") \
TUNE(RAZZ, "\xFB\x1C\x00") \#define TUNE(x,y) x,
enum {ALL_TUNES TUNE_COUNT};
#undef TUNE#ifdef MAKE_TUNE_DAT
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
ALL_TUNES;
#undef TUNE#define TUNE(x,y) TUNEDAT_##x,
char const * const tunes_dat[] = {ALL_TUNES 0};
#undef TUNE#define TUNE(x,y) sizeof(TUNEDAT_##x)-1,
ub const tunes_len[] = {ALL_TUNES 0};
#undef TUNE#endif // MAKE_TUNE_DAT
I would have liked to define the tune data using {1,2,3}-style initializers (I'd save a wasted zero byte on each one), but the only way to make the macro handler go along with that would be to use something like "x" instead of commas and #define that as a separator; that seems a bit too ugly for my taste. Sometimes when using this technique, I put everything in the ALL_TUNES definition in a standalone file, so I can #include it rather than expanding a macro; that obviates the need for all the ugly backslashes, but multiply including the same file is a bit ugly and is incompatible with #pragma once. If I didn't use "funny" macros, what would be the best way to keep initializers lined up with enum definitions? I really don't like the style:
enum {HARRY, RON, HERMIONE};
const char const *names[] = {"Harry", "Ron", "Hermione"};since it's very easy for the enum list and the array initialization to get out of sync.
I'd put the list of enum definitions into a separate file, and then work with
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
#include "tunedef.h"
#undef TUNEenum ETunes
{
#define TUNE(x,y) TUNEDAT_##x,
#include "tunedef.h"
#undef TUNE
};(additional decoration missong..)
Personally, I love the idea that Raymond spends his nights posting bad regexs to mailing lists under the pseudonym of Jane Smith. He'd be like a super hero, only more nerdy and less useful. [Trevel]
| FoldWithUs! | sighist -
I'd put the list of enum definitions into a separate file, and then work with
#define TUNE(x,y) char const TUNEDAT_##x[] = y;
#include "tunedef.h"
#undef TUNEenum ETunes
{
#define TUNE(x,y) TUNEDAT_##x,
#include "tunedef.h"
#undef TUNE
};(additional decoration missong..)
Personally, I love the idea that Raymond spends his nights posting bad regexs to mailing lists under the pseudonym of Jane Smith. He'd be like a super hero, only more nerdy and less useful. [Trevel]
| FoldWithUs! | sighistpeterchen wrote:
I'd put the list of enum definitions into a separate file [and #include it multiple times]
I sometimes use that technique. It wreaks havoc with some compilers' efforts to optimize #include file performance (e.g. it prevents the use of #pragma once) but it has the advantage of eliminating a lot of annoying semicolons. Both the multi-#include and the multi-line #define approaches have a certain clunkiness to them, but I've not decided which bit of clunkiness I dislike more. BTW, on one project, it was necessary for a somewhat substantial piece of performance-critical code to operate essentially identically on two data structures. The normal approach would have been to have a routine take a pointer to a structure, and then perform all operations using that structure. An alternative approach would have been for the routine to have a static copy of the structure; to update both structures the code could then copy structure #1 into that static copy, operate upon it, and copy it back, then do likewise for structure #2. This would probably have offered better performance than doing all operation using pointers (on the Z80, using 16-bit integers and pointers would have been rather slow):
/* foo.i1 = foo.i2 + foo.i3; */
/* About 16+16+8+16 = 56 cycles I think */
ld hl,(foo.i2)
ld de,(foo.i3)
add hl,de
ld (foo.i1),hl
/* bar->i1 = bar->i2 + bar->i3 */
/* About 20+14+14+14+14+14+14 = 104 cycles I think */
/* Note I doubt the compiler would produce code this good */
ld iy,(foo)
ld a,(iy+.i2)
add a,(iy+.i3)
ld (iy+.i1),a
ld a,(iy+.i2+1)
adc a,(iy+.i3+1)
ld (iy+.i1),abut the performance overhead for having to do copy the data structure four times for every system event cycle would still have been substantial. Instead, I #included the code for the routine twice, with a few #define macros set differently for each invocation. (BTW, I just realized that since I defined the PLD for memory addressing, I could perhaps have tweaked it so that a 4K slice of address space could point to two different blocks of memory, and then put the data structure there. Water under the bridge now.)