Using a microcontroller (PIC18F4580), I need to collect data and send it to an SD card for later analysis. The data it collects will have values between 0 and 1023, or 0x0 a
There's certainly a much faster way: have an array of 1024 pre-computed strings. Then you can just do bounds checking, followed by an index into the array.
It's unclear from your question whether your code is running on the microcontroller though. If that's the case, you may not have enough memory for this approach.
The obvious solution is not to convert the data to ASCII at all but store it in binary format. That way all you need to worry about is the endianness of the data. If the system performing the later analysis is far more powerful than your embedded target, then it would make sense to let that deal with the conversion and and byte order.
On the other hand, it is possible that the execution time of the / and % is insignificant compared to the time taken to transfer the data to the SD card; so make sure that you are optimising the right thing.
I've replaced my previous answer with a better one. This code creates a 4-character string in the proper order, most significant digit in output[0] to least significant in output[3] with a zero terminator in output[4]. I don't know anything about your PIC controller or C compiler, but this code doesn't require anything more than 16-bit integers, addition/subtraction, and shifting.
int x;
char output[5];
output[4] = 0;
x = 1023;
output[3] = '0' + DivideByTenReturnRemainder(&x);
output[2] = '0' + DivideByTenReturnRemainder(&x);
output[1] = '0' + DivideByTenReturnRemainder(&x);
output[0] = '0' + x;
The key to this is the magical function DivideByTenReturnRemainder
. Without using division explicitly it's still possible to divide by powers of 2 by shifting right; the problem is that 10 isn't a power of 2. I've sidestepped that problem by multiplying the value by 25.625 before dividing by 256, letting integer truncation round down to the proper value. Why 25.625? Because it's easily represented by powers of 2. 25.625 = 16 + 8 + 1 + 1/2 + 1/8. Again, multiplying by 1/2 is the same as shifting right one bit, and multiplying by 1/8 is shifting right by 3 bits. To get the remainder, multiply the result by 10 (8+2) and subtract it from the original value.
int DivideByTenReturnRemainder(int * p)
{
/* This code has been tested for an input range of 0 to 1023. */
int x;
x = *p;
*p = ((x << 4) + (x << 3) + x + (x >> 1) + (x >> 3)) >> 8;
return x - ((*p << 3) + (*p << 1));
}
Are you required to use an ASCII string of the decimal representation? It would be much easier to store it in hexadecimal format. No division required, only (relatively cheap) shift operations. Excel should be able to read it if you prepend a '0x' to each number.
There's a way of doing it using subtractions, but I am not convinced it's faster than using subtractions and modulus on a "normal" CPU (may be different in an embedded environment).
Something like this:
char makedigit (int *number, int base)
{
static char map[] = "0123456789";
int ix;
for (ix=0; *number >= base; ix++) { *number -= base; }
return map[ix];
}
char *makestring (int number)
{
static char tmp[5];
tmp[0] = makedigit(&number, 1000);
tmp[1] = makedigit(&number, 100);
tmp[2] = makedigit(&number, 10);
tmp[3] = makedigit(&number, 1);
tmp[5] = '\0';
return tmp;
}
Then, a call to makestring()
should result in a (static, so copy it before overwriting) string with the converted number (zero-prefixed, at 4 characters width, as the original assumption is a value in the 0-1023 range).
If the values are correctly in range (0..1023), then your last conversion is unnecessarily wasteful on the divisions; the last line could be replaced with:
temp[3] = 1023 / 1000;
or even:
temp[3] = 1023 >= 1000;
Since division is repeated subtraction, but you have a very special case (not a general case) division to deal with, I'd be tempted to compare the timings for the following code with the division version. I note that you put the digits into the string in 'reverse order' - the least significant digit goes in temp[0]
and the most in temp[4]
. Also, there is no chance of null-terminating the string given the storage. This code uses a table of 8 bytes of static data - considerably less than many of the other solutions.
void convert_to_ascii(int value, char *temp)
{
static const short subtractors[] = { 1000, 100, 10, 1 };
int i;
for (i = 0; i < 4; i++)
{
int n = 0;
while (value >= subtractors[i])
{
n++;
value -= subtractors[i];
}
temp[3-i] = n + '0';
}
}
This platform is probably not representative of your microcontroller, but the test shows that on this platform, the subtraction is considerably slower than the division.
void convert_by_division(int value, char *temp)
{
temp[0] = (value % 10) + '0';
temp[1] = (value % 100) / 10 + '0';
temp[2] = (value % 1000) / 100 + '0';
temp[3] = (value % 10000) / 1000 + '0';
}
void convert_by_subtraction(int value, char *temp)
{
static const short subtractors[] = { 1000, 100, 10, 1 };
int i;
for (i = 0; i < 4; i++)
{
int n = 0;
while (value >= subtractors[i])
{
n++;
value -= subtractors[i];
}
temp[3-i] = n + '0';
}
}
#include <stdio.h>
#include <timer.h>
#include <string.h>
static void time_convertor(const char *tag, void (*function)(void))
{
int r;
Clock ck;
char buffer[32];
clk_init(&ck);
clk_start(&ck);
for (r = 0; r < 10000; r++)
(*function)();
clk_stop(&ck);
printf("%s: %12s\n", tag, clk_elapsed_us(&ck, buffer, sizeof(buffer)));
}
static void using_subtraction(void)
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
convert_by_subtraction(i, temp1);
}
}
static void using_division(void)
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
convert_by_division(i, temp1);
}
}
int main()
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
char temp2[4];
convert_by_subtraction(i, temp1);
convert_by_division(i, temp2);
if (memcmp(temp1, temp2, 4) != 0)
printf("!!DIFFERENCE!! ");
printf("%4d: %.4s %.4s\n", i, temp1, temp2);
}
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
return 0;
}
Compiling with GCC 4.5.1, and working in 32-bit, the average timings were (optimization '-O
'):
0.13
seconds using division0.65
seconds using subtractionCompiling and working in 64-bit, the average timings were:
0.13
seconds using division0.48
seconds using subtractionClearly, on this machine, using subtraction is not a winning proposition. You would have to measure on your machine to make a decision. And removing the modulo 10000 operation will only skew results in favour of the division (it knocks about 0.02 seconds off the time with division when replaced with the comparison; that's a 15% saving and worth having).