-
Content Count
3,399 -
Joined
-
Last visited
-
Days Won
146
Reputation Activity
-
spirilis reacted to monsonite in An Even Smaller MSP430FR2433 Dev Board
Spirilis,
I could have omitted one of the SPI memory devices, and put it on the back of the pcb - which would make it even smaller - possibly even 0.3" x 0.3" - but then you have the problem of getting the connections out sensibly.
As it stands its like a wide soic with 1.27mm (0.05") lead pitch - which in my book makes it now appear on the large size of what can effectively be soldered by hand.
As an interesting aside, the MSP430FR2433 seems to hand itself for being built into multi processor arrays. The 3 comms ports present themselves neatly on the west, south and east sides of the IC - with the northside devoted to power, clock and SBW pins.
With a reduced footprint I am reasonably convinced that you could get a 10 x 10 array of these - plus memory onto a cheap 10 x 10 cm pcb.
But then you have the same old problem we have had since the transputer in the mid 1980s- how do you effectively co-ordinate and control a parallel array of processors. How do you allocate tasks across the array?
Steve Ciarcia did a Circuit Cellar (Byte Magazine) article in the late 1980s for a fractal computer - where he took 256 off Intel 8748 microcontrollers and built them into an array .
Whilst Green Arrays can put 144 processor cores onto a single chip - I'm not sure that the world is beating a path to their door yet. http://www.greenarraychips.com/
.
I have now been trying out Jean Michel Thoorens "Fast Forth" https://gitlab.com/Jean-Michel/FastForthForMSP430fr5xxx which ports to most of the FRAM devices including 'FR57xx, 'FR58xx, 'FR69xx
It uses a machine code implementation of Forth and Uart communications at 921600 baud, so it that respect is pretty fast off the blocks.
Ken
-
spirilis reacted to Rickta59 in tiny msp430 preemptive multitasking system
I've been spending way too much time looking at this thing. So thanks for that. I did some timing testing with the msp430g2553 running at 8MHz, it seems like it takes about 8 to 10 microseconds to do the context switch. There is some overhead, however I was able to compensate for extra cycles by over clocking the DCO.
On the msp430g2553, I made the slight change to increase the DCO clock:
BCSCTL1 = CALBC1_8MHZ; // Set DCO to factory calibrated 8MHz DCOCTL = CALDCO_8MHZ; DCOCTL += 13; // overclock 103% or 8.22MHzSpeeding it up this much seems to account for the context switch overhead. Any thoughts?
BTW: This was with 2 tasks. Splitting the 8MHz clock evenly between the 2 task I used 4MHz to calculate my delay cycle calls:
#include "common.h" void task1(void) { volatile unsigned count=0; // 4 bytes on stack P1DIR |= BIT0; P1OUT &= ~BIT0; while(1){ P1OUT ^= BIT0; #if 1 __delay_cycles(50*(4000000/1000)); #else sys_delay(50); #endif P1OUT ^= BIT0; #if 1 __delay_cycles(450*(4000000/1000)); #else sys_delay(450); // 4+2 bytes of stack used #endif ++count; } -rick -
spirilis reacted to monsonite in An Even Smaller MSP430FR2433 Dev Board
Last week, I put together a design for a lightweight dev-board using the MSP4302433, known as "ChipStick". ChipStick is a 20 pin DIL module that can plug into the socket on a G2 Launchpad.
In the week whilst waiting for the prototype boards to arrive, I have prototyped the design using a SMT adaptor - allowing it to be built on a breadboard. Today I have refined the original concept, to produce a second design which, being small I have called Nanode (very small node).
Nanode is effectively the same circuit as ChipStick, but trimmed to make a smaller form factor. The main difference is that it is pinned out to a 1.27mm pitch 2 x 10 pin connector - rather than the 2.54mm pitch DIL socket. I have also dispensed with the detachable programmer section. "Where we're going Marty, we don't need programmers"
I have featured it on my recent blogpost,
http://sustburbia.blogspot.co.uk/2016/03/digital-dust-further-journey-into-nano.html
Once I have working examples, as usual I will make the design CAD files available.
Ken
-
spirilis reacted to tonyp12 in tiny msp430 preemptive multitasking system
preemptive means that the code (e.g.each task that is pretty much its own main.c) does not know it is sharing a single mcu core.
it does not need to say "i take a pause now so next task can go on", you can even run two instances of the same task (though not really useful)
A higher power takes over and switch tasks.
Sure there is stuff to add like system_sleep and priority and maybe even new task and end task etc.
I plan to show how to use a 512Hz mems osc to NMI pin, so task switching can not be overridden by (maybe mistakenly) disabling GIE.
?wiki: Preemptive multitasking
wiki: Cooperative multitasking
P.S I also have a Cooperative multitasking system that uses a Event Machine in main.c and each (up to 16) tasks are all State Machines (switch/case) as that is a good time slicer.
?What I like about that is that I know I have exclusivity while I'm doing my thing so no race conditions.
?And very little overhead so better for battery-operation as task that needs to use a time-constant can sleep from 1ms to 64sec independently keeping in LPM3 95% of the time
-
-
spirilis got a reaction from Fmilburn in Ethernet Booster Pack v3
G2955 specifically... 5th generation of value line (note the 5 at the end). The 4th and 5th gen Value Line chips almost "quietly" appeared a couple years ago in TSSOP & QFN-only packages, but they are relatively easy to solder if you're comfortable with SMD work (TI's "DA" package, used by the MSP430F5172 too, is a 38-pin TSSOP package in 0.65mm aka 25-mil pitch)
Peripherals on the G2xx4/G2xx5 work very similarly to the G2553, they use USCI for comms and BCS for clocking but the newer ones do have a Timer_B in addition to their Timer_A peripherals.
-
spirilis got a reaction from Fmilburn in Ethernet Booster Pack v3
Fwiw, I bet the MSP430G2955 would be perfect for Wiznet work. 56K flash, 4KB SRAM.... much more comfortable.
Just for kicks, this is the kind of BS I had to do to get DHCP working on a G2553, and it still leaves little room for error (probability of stack overflowing into the heap):
int dhcp_loop_configure(uint8_t *dnsaddr, struct DHCPrenew *lease) { uint8_t scratch[34]; ... int dhcp_send_packet(uint8_t sockfd, uint8_t *scratch, uint8_t dhcp_msgtype) { uint8_t *ourmac = scratch+SOFF_SIADDR_ACK; // Overloading SIADDR_ACK + optcode + optlen to hold the MAC address uint8_t *yiaddr = scratch+SOFF_YIADDR; uint8_t *siaddr = scratch+SOFF_SIADDR; ... // Read initial DHCP information int dhcp_read_header(uint8_t sockfd, uint8_t *scratch, uint8_t *ciaddr, uint8_t *yiaddr, uint8_t *siaddr, uint8_t *giaddr, uint8_t *chaddr) { uint8_t *xid = scratch+SOFF_XID; Basically the primary function dhcp_loop_configure includes a stack-allocated scratch buffer 34 bytes long, and passes it to subjugate functions where it might reuse some of the bytes or abuse them in kind. There was some tetris dancing involved where I had to assume (and analyze to validate it's safe) that certain scratch array positions would be reusable across different function calls based on the overall flow and sequence of the protocol.
-
spirilis got a reaction from Fmilburn in Ethernet Booster Pack v3
Memory (SRAM) can become a problem in a real hurry with those. Buffer sizes with the Wiznet is something like 2KB yet you only have 512b to work with...
-
spirilis reacted to greeeg in Pi day - 3.14 USD freight on all TI store orders today (03/14)
Thanks! This one isn't a code, so it will likely be disabled after today. Worked for me in AUS. (which funnily enough Pi day was yesterday for us. Also, aside. 3/14/16 [3.1416] is actually closer to the real value of pi than last year [3.1415])
-
spirilis got a reaction from tripwire in double vs float in TM4C123
Float is handled with native hardware FPU instructions for speed. Double is handled in software (not sure if it's capable of using the FPU but I think not). Float would be much faster.
-
spirilis reacted to dubnet in communication between two UART on Tiva C
Another suggestion is to test a Windows PC instead of the Linux machine to determine if it is contributing to the problem. Also, if you have a logic analyzer (e.g. Saleae) that would let you see what the transmissions actually look like. If you don't have one, and you think you will do even a modest amount of microcontroller communications work, a good logic analyzer is worth its weight in gold.
-
spirilis got a reaction from cde in Stupidest Thing you had to Troubleshoot?
Those darned MSP430's are almost unreasonably difficult to kill! LOLMakes the FRAM "zombie MCU" idea ever more relevant...
-
spirilis reacted to roadrunner84 in Parsing "strings" in C - Attempting to parse MQTT payload
I made this version, it doesn't take a callback, instead it returns a pointer to the value (the key is always at the start of a token anyway) and the key and value lengths in parameters, as well as the start of the next token as return value.
It may be easier to use in a state machine. Also, keys without values are allowed, as well as values without keys, but two = signs cause abortion of the function. empty tokens result in a key and value length of 0.
const char *getKeyValue(const char *const input, int *key_len, const char **value, int *value_len) { const char *iter; *value = input; for (iter = input; (*iter != '&') && (*iter != '\0'); ++iter) { if (*iter == '=') { if (*value != input) // second '=' found, abort parsing { *key_len = 0; *value_len = 0; return NULL; } *key_len = iter - input; *value = iter + 1; } } *value_len = iter - *value; if (*value == input) { *key_len = *value_len; *value_len = 0; } return *iter == '\0' ? NULL : iter + 1; } #include <stdio.h> #include <string.h> char *test[] = { "cmd=led&color=red&state=on", "cmd=&bla=true", "cmd==false&blue=grey", "x&&&&&g=t", "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&" }; int main() { int key_len, value_len; const char *key, *value, *next; char key_str[20], value_str[20]; int n; for(n = 0; n < 5; ++n) { next = test[n]; printf("%s\n", next); do { const char *current = next; next = getKeyValue(current, &key_len, &value, &value_len); memcpy(key_str, current, key_len); key_str[key_len] = '\0'; memcpy(value_str, value, value_len); value_str[value_len] = '\0'; printf("%s, %s\n",key_str, value_str); } while (next != NULL); } return 0; } Oh, and it doesn't touch the input string at all and uses practically no memory at all, it does require the user to iterate over all tokens though, but that's by design (since, no callback).
-
spirilis reacted to yyrkoon in Have you experienced a chilling effect?
Write a book . . . that should make everyone happy, if it's a good one.
-
spirilis got a reaction from greeeg in Have you experienced a chilling effect?
All that said, I do wonder if you have "tiers" of information you could feel comfortable giving out, or if it's all just one sacred space for you... I think "tiering" the level of advice and knowledge is key to managing a public footprint that's open enough to keep others excited about what you do, but having a reasonably firm line protecting what you consider pay-worthy in terms of time commitment.
Some of us are in different situations where everything we do in this realm is for hobby, but some aren't.
-
spirilis got a reaction from tripwire in Parsing "strings" in C - Attempting to parse MQTT payload
Also for kicks, I ran the first code (test.c) with some extra quirks to test its limits:
char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&";
Here's the output:
ebrundic@spock:~/inst$ ./test key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] key=[wtf], value=[wtf] The only output that seems odd is the "=wtf" case, since the code took the value and pointed both key and value pointers to the value. A state variable (called no_key here) could protect against this:
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <string.h> void run_callback(const char *, const char *); int main(int argc, char *argv[]) { size_t i; char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&"; size_t tlen = strlen(text); char *key=NULL, *value=NULL; int no_key = 0; for (i=0; i < tlen; i++) { if (text[i] == '&') { // end of key/value pair text[i] = '\0'; if (key != NULL) { run_callback(key, value); } key = NULL; value = NULL; no_key = 0; continue; } if (text[i] == '=') { // end of key, start of value no_key = 1; text[i] = '\0'; value = &text[i+1]; continue; } if (!no_key && key == NULL) { key = &text[i]; } // otherwise do nothing, keep incrementing i } // end of string, we might have a loaded key/value pair to process at the very end if (key != NULL) { run_callback(key, value); } return(0); } void run_callback(const char *key, const char *value) { if (key == NULL || value == NULL) { printf("key=ptr[%p], value=ptr[%p]\n", key, value); } else { printf("key=[%s], value=[%s]\n", key, value); } } which produces:
key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] -
spirilis reacted to Fmilburn in Have you experienced a chilling effect?
I got involved with this community essentially knowing nothing about microcontrollers or C/C++ when I retired as an engineering manager with a mechanical background (in Calgary by the way - one of my favorite places). Microcontrollers were just something that caught my interest after viewing a TED video on Arduino. And on average, the quality of the projects and the help / discussion just seemed to be on a higher level on 43oh than with the Arduino crowd.
For me, learning this stuff beyond the superficial on my own outside of a classroom setting and without colleagues is hard. But it has been rewarding and a great experience. And I have learned something from each of you who have posted above. So, my thanks to you.
This is just a hobby for me, and it is unlikely anyone developing a commercial product is going to gain much from my advice . Having said that, I try to give as much as I take. And a good way to learn and hopefully help others has been to read the problems others are having and see if I can solve them. For those who would like to continue getting that kind of help, here are some tips:
Don't abuse the goodwill of 43oh members in the manner described above Search and make a real effort to solve it yourself first Post sufficient information for someone to help solve the problem but be as succinct as possible and don't post a 100 lines of code Use the thanks button when someone helps - really, how much effort does that take? Somebody just spent personal time to help you for free. When your problem is solved, consider editing your first post and put [sOLVED] in the title, or at least follow up with a post that you finally got it to work and how. The next person with that problem will thank you. My son-in-law has a masters in EE and is now a patent attorney. I asked him a while back about the practicalities of protecting intellectual property for the small guy or hobbyist. My interpretation of that conversation was that unless you have money and/or time it is difficult. A shame, the result is that you must do something like Spirilis suggests and carefully consider/tier responses, help, and what is revealed.
I appreciate the help past and future, and enjoy hearing about the projects. But, I also understand the sentiments expressed above and don't want to see livelihoods threatened.
-
spirilis reacted to monsonite in New Project with MSP430FR4133 and MSP430i2xxx
Hi All,
After a week of assessing the MSP430FR and new MSP430i series, I am now happy to commence a new commercial project development based on the MSP430FR4133 and MSP430i2xxx.
It's a handheld instrument that needs to make some precision analogue measurements. (No - it's not a tricorder )
The MSP430FR4133 will basically run an LCD (either segmented or Sharp 400x200) and the user interface of a few soft buttons.
There may be the need to move to a larger FRAM size when using the Sharp 400x256 - as that will use 12K of fram as the display buffer. I am also considering '5969 and '6969 for this role.
The MSP430i2xxx - which I only discovered over the weekend will do the analogue measurements with its SD24 ADC, filter and scale and then pass the data to the '4133 for display.
This is a particularly economical implementation, and likely to half the BOM cost of the existing instrument which is based on a MSP430F437 and an external 16-bit ADC.
I can't talk much about the exact application at the moment - but I believe that the combination of an analogue processor and a display processor will be a powerful one.
The low power operation of the MSP is ideal for this cost sensitive battery powered instrument.
Development starts in full tomorrow - when I receive my '4133 LaunchPad.
On a separate topic, I am also developing a tiny Forth-like language, which runs out of about 2Kbytes and 512B of RAM. This will be the subject of a separate post. It's not commercially sensitive - so I'm free to discuss it with other interested parties here.
regards
Ken
London
-
spirilis got a reaction from zeke in Have you experienced a chilling effect?
All that said, I do wonder if you have "tiers" of information you could feel comfortable giving out, or if it's all just one sacred space for you... I think "tiering" the level of advice and knowledge is key to managing a public footprint that's open enough to keep others excited about what you do, but having a reasonably firm line protecting what you consider pay-worthy in terms of time commitment.
Some of us are in different situations where everything we do in this realm is for hobby, but some aren't.
-
spirilis got a reaction from zeke in Have you experienced a chilling effect?
Sounds like you're getting old ;-)
*ducks*
-
spirilis got a reaction from Rickta59 in Have you experienced a chilling effect?
Sounds like you're getting old ;-)
*ducks*
-
spirilis got a reaction from dubnet in Parsing "strings" in C - Attempting to parse MQTT payload
Also for kicks, I ran the first code (test.c) with some extra quirks to test its limits:
char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&";
Here's the output:
ebrundic@spock:~/inst$ ./test key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] key=[wtf], value=[wtf] The only output that seems odd is the "=wtf" case, since the code took the value and pointed both key and value pointers to the value. A state variable (called no_key here) could protect against this:
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <string.h> void run_callback(const char *, const char *); int main(int argc, char *argv[]) { size_t i; char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&"; size_t tlen = strlen(text); char *key=NULL, *value=NULL; int no_key = 0; for (i=0; i < tlen; i++) { if (text[i] == '&') { // end of key/value pair text[i] = '\0'; if (key != NULL) { run_callback(key, value); } key = NULL; value = NULL; no_key = 0; continue; } if (text[i] == '=') { // end of key, start of value no_key = 1; text[i] = '\0'; value = &text[i+1]; continue; } if (!no_key && key == NULL) { key = &text[i]; } // otherwise do nothing, keep incrementing i } // end of string, we might have a loaded key/value pair to process at the very end if (key != NULL) { run_callback(key, value); } return(0); } void run_callback(const char *key, const char *value) { if (key == NULL || value == NULL) { printf("key=ptr[%p], value=ptr[%p]\n", key, value); } else { printf("key=[%s], value=[%s]\n", key, value); } } which produces:
key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] -
spirilis got a reaction from Shiv in Parsing "strings" in C - Attempting to parse MQTT payload
Anyway, that was fun... I'm glad I spent some time to write all that. I'll end up using it later
edit: Never did test the &key& scenario - that would produce a key that is equal to the whole rest of the string and NULL pointer for value. Probably need if (key != NULL && value != NULL) before doing the run_callback stuff.
-
spirilis got a reaction from Shiv in Parsing "strings" in C - Attempting to parse MQTT payload
Also for kicks, I ran the first code (test.c) with some extra quirks to test its limits:
char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&";
Here's the output:
ebrundic@spock:~/inst$ ./test key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] key=[wtf], value=[wtf] The only output that seems odd is the "=wtf" case, since the code took the value and pointed both key and value pointers to the value. A state variable (called no_key here) could protect against this:
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <string.h> void run_callback(const char *, const char *); int main(int argc, char *argv[]) { size_t i; char text[] = "cmd=load&&&&&&&&&&&&&color=red&bg=blueα=0.56&&blah=&&&&=wtf&&&&"; size_t tlen = strlen(text); char *key=NULL, *value=NULL; int no_key = 0; for (i=0; i < tlen; i++) { if (text[i] == '&') { // end of key/value pair text[i] = '\0'; if (key != NULL) { run_callback(key, value); } key = NULL; value = NULL; no_key = 0; continue; } if (text[i] == '=') { // end of key, start of value no_key = 1; text[i] = '\0'; value = &text[i+1]; continue; } if (!no_key && key == NULL) { key = &text[i]; } // otherwise do nothing, keep incrementing i } // end of string, we might have a loaded key/value pair to process at the very end if (key != NULL) { run_callback(key, value); } return(0); } void run_callback(const char *key, const char *value) { if (key == NULL || value == NULL) { printf("key=ptr[%p], value=ptr[%p]\n", key, value); } else { printf("key=[%s], value=[%s]\n", key, value); } } which produces:
key=[cmd], value=[load] key=[color], value=[red] key=[bg], value=[blue] key=[alpha], value=[0.56] key=[blah], value=[] -
spirilis got a reaction from Shiv in Parsing "strings" in C - Attempting to parse MQTT payload
Here's a simple custom tokenizer that modifies data in-place (I wrote this in C for execution at the command line in Linux, but, the code can be ripped into an MCU just the same):
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <string.h> void run_callback(const char *, const char *); int main(int argc, char *argv[]) { size_t i; char text[] = "cmd=load&color=red&bg=blueα=0.56"; size_t tlen = strlen(text); char *key=NULL, *value=NULL; for (i=0; i < tlen; i++) { if (text[i] == '&') { // end of key/value pair text[i] = '\0'; if (key != NULL) { run_callback(key, value); } key = NULL; value = NULL; continue; } if (text[i] == '=') { // end of key, start of value text[i] = '\0'; value = &text[i+1]; continue; } if (key == NULL) { key = &text[i]; } // otherwise do nothing, keep incrementing i } // end of string, we might have a loaded key/value pair to process at the very end if (key != NULL) { run_callback(key, value); } return(0); } void run_callback(const char *key, const char *value) { // do something with key & value - replace these with something more relevant to your application if (key == NULL || value == NULL) { printf("key=ptr[%p], value=ptr[%p]\n", key, value); } else { printf("key=[%s], value=[%s]\n", key, value); } } If it's absolutely mandatory that the original text NOT be modified (you should qualify this restriction though, since if it can be modified, this solution works fine), you can modify this tokenizer to copy the key & value into buffers:
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <string.h> void run_callback(const char *, const char *); #define KEYVALUE_BUFFER_MAXLEN 32 int main(int argc, char *argv[]) { size_t i; char text[] = "cmd=load&color=red&bg=blueα=0.56"; size_t tlen = strlen(text); char *key=NULL, *value=NULL; size_t keystart=0, valuestart=0, tmpsz; char keybuf[KEYVALUE_BUFFER_MAXLEN], valuebuf[KEYVALUE_BUFFER_MAXLEN]; for (i=0; i < tlen; i++) { if (text[i] == '&') { // end of key/value pair // copy value into buffer tmpsz = i-valuestart; if (tmpsz > KEYVALUE_BUFFER_MAXLEN-1) { tmpsz = KEYVALUE_BUFFER_MAXLEN - 1; // minus 1 to provide space for the '\0' terminator } memcpy(valuebuf, value, tmpsz); valuebuf[tmpsz] = '\0'; if (key != NULL) { run_callback(keybuf, valuebuf); } key = NULL; value = NULL; continue; } if (text[i] == '=') { // end of key, start of value // copy key into buffer tmpsz = i-keystart; if (tmpsz > KEYVALUE_BUFFER_MAXLEN-1) { tmpsz = KEYVALUE_BUFFER_MAXLEN - 1; } memcpy(keybuf, key, tmpsz); keybuf[tmpsz] = '\0'; value = &text[i+1]; valuestart = i+1; continue; } if (key == NULL) { key = &text[i]; keystart = i; } // otherwise do nothing, keep incrementing i } // end of string, we might have a loaded key/value pair to process at the very end if (key != NULL) { // copy value into buffer tmpsz = i-valuestart; if (tmpsz > KEYVALUE_BUFFER_MAXLEN-1) { tmpsz = KEYVALUE_BUFFER_MAXLEN - 1; } memcpy(valuebuf, value, tmpsz); valuebuf[tmpsz] = '\0'; run_callback(keybuf, valuebuf); } return(0); } void run_callback(const char *key, const char *value) { // do something with key & value - replace these with something more relevant to your application if (key == NULL || value == NULL) { printf("key=ptr[%p], value=ptr[%p]\n", key, value); } else { printf("key=[%s], value=[%s]\n", key, value); } } Note the cost here .... extra code. Being able to modify the data in-place tidies things up a bit. What I wrote up above could be reworked to use strtok, but I don't see much point since replicating strtok's code is reasonably compact anyhow (and we're doing a purpose-built version that acts as a state machine unique to your particular data).