UUEncode in C

Originally posted on www.letsmakerobots.com

I want to take a moment to thank Bdk6. The man is a walking Stack Overflow, with more patience for stupid. I doubt I’d understand any of this without his guidance.

I thought I’d take some time away from coding my LPC1114 Uploader and verbally process a few things I’ve learned. As always, feel free to critique any of it; it’ll only serve to make my code more robust in the end.

This post will be a series of post leading up to the large post I’ll make on writing the uploader. All posts will rely on the GCC compiler.

Setting Up the GCC Compiler

I setup a C environment as basic I could. There may be easier ways to go about this, but I wanted to use GCC to compile.

To setup the environment:

1. I downloaded and setup MinGW32.

2. I added these includes to make the code go.

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <windows.h>
#include <windef.h>
#include <winnt.h>
#include <winbase.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h>

I used this line to build it:

$ gcc -o main main.c

As for editing, I’ve really grown to love Sublime Text 2.

If you have issues**, make sure directory containing your files is in your PATH environment variable **(I go over how to add the directory to your environment variables in this post).

How to Convert Hex Data to UUE

“What is ‘UUEncoding’?”

Unix-to-Unix encoding (UUE) is the process where binary data is converted to a form which can be represented using ASCII character values from space (32) to underscore (95). These 64 characters allow us to express any binary stream of data.

I will not spend a lot of time explaining UUEncoding since the Wikipedia article is excellent.

“Why UUEncode?”

Have you written a program to look for a particular value? Like this,

int i;
char tCollection[32];
char c;``

if( c == 'T')
{
    Serial.print("I found a T!");
    tCollection[i] = c;
else if (c == '\r')
{
    exit();
}

You begin running your program and everything seems fine. It is inspecting data looking for the letter T (0x54), but then, all of a sudden your program exits without reason. You’re upset, because the sensor sending you the data didn’t send the exit code, which is a carriage return (‘\r’, or 0x13), but still, your program ended like it did.

Really, we know the reason, your program came across a random 0x13, which it interpreted as an exit command. Maybe a different special character?

But you realize, if you are dealing with a 8-bit data stream any ASCII character might be found in the incoming stream. So, how can the communicating devices know when it is getting data, versus, when it is receiving a command?

This is where UUE comes in. As I stated earlier, UUE is a way to represent the same 8-bit data using only hex values 0x32 through 0x95 (the two middle columns in the ASCII chart above). This means characters 0x00 through 0x1F and 0x60 through 0x7F are free to be used for command functions.

Returning to the above example, this means we could now us the CR value to signal the end-of-transmission, since CR is 0x13.

Ok, I hope I’ve sold you on UUE’s utility, let me attempt to explain the basics of how hexadecimal data is converted into a form which may be represented with only 64 values.

UUE conversion works with three bytes of data at a time and follows this algorithm.

  1. The individual bits of 3 HEX bytes are put into a series.
  2. The 24-bit series is then divided into four bytes of 6-bits.
  3. Then, 32 is added to the decimal value representing each of the 6-bit bytes.
  4. The resulting four values are UUEncoded bytes.

Confused as hell? I was too. Pictures are good. Let’s follow the Wiki example and use: Cat

The first step is to take the binary values for each ASCII character.

  • ‘C’ = 01000011
  • ‘a’ = 01100001
  • ‘t’ = 01110100

This means the resulting 24-bit binary series is,

24-bit: 010000110110000101110100

This is then divided into four 6-bit bytes,

  • 6-bit Byte: 1 2 3 4
  • Binary: 010000 110110 000101 110100

The new decimal values are,

  • 6-bit Byte: 1 2 3 4
  • Decimal: 16 54 5 52

At this point the 6-bit (senary) byte gave me a little trouble. I didn’t understand how 6-bits were treated by the 8-bit variable I was putting them in. For example, how could I get an int variable to take only 6 bits, not 8? The trick is understanding the 8-bit variable is only the width of the allotted space provided in a register, it has no influence on what you put in it. It finally dawned on me, I didn’t need to worry about the empty bits in a register.

Examples are good:

010000 = 16 in Decimal

00010000 = 16 in Decimal

010000 = 00010000

Anyway, this is how I finally made sense of it. As long as when I did my bit manipulations I kept unused bits of the register towards the “left” side, the my 6-bit values could be put into a 8-bit register and there value would remain the same.

Alright, back to our example.

Our next step is to add 32 to each of the decimal value of our new four bytes.

  • 6-bit Byte: 1 2 3 4
  • Decimal: 16 54 5 52
  • Add 32 +32 +32 +32 +32
  • New Dec. 48 86 37 84
  • UUE char: 0 V % T

And…that’s it. Your data is now UUEncoded. When it is sent through whatever transmission medium it wont be bothered with special character protocals. For our Cat, we have transformed it into: 0V%T

Let’s hope for the Cat’s sake, there is decoding algorithm.

Those sharper than myself may have already noticed a couple of problems. For instance, what if our data doesn’t come in increments of threes? For example, how do we send Cats?

The answer? We make nothing up. In the instance of Cats, we simply pad the end of the character series with two nulls on the end. For example,

  • ‘C’ = 01000011
  • ‘a’ = 01100001
  • ‘t’ = 01110100
  • ’s’ = 01110011
  • NUL = 00000000
  • NUL = 00000000

48-bit: 010000110110000101110100011100110000000000000000

  • 6-bit Byte: 1 2 3 4 5 6 7 8
  • Binary: 010000 110110 000101 110100 011100 110000 000000 000000

The new decimal values are,

  • 6-bit Byte: 1 2 3 4 5 6 7 8
  • Decimal: 16 54 5 52 28 48 0 0

  • 6-bit Byte: 1 2 3 4 5 6 7 8
  • Decimal: 16 54 5 52 28 48 0 0
  • Add 32 +32 +32 +32 +32 +32 +32 +32 +32
  • New Dec. 48 86 37 84 60 80 32 32
  • UUE char: 0 V % T < P SPC SPC

We have turned “Cats” into “**0V%T<P **” Well, almost, we aren’t quite done here.

Uh-oh. Another problem. The whole point of UUE was to stay away from special characters like the space character (0x32). But now we have two of them in our transmission. Well, the UUE protocol addresses this. It states,

  • If the result of the 6-bit encoding process is the space character, we convert this to the grave character, **’ ` ‘. **(The grave accent character is 0x60 in hexadecimal, by the way).

Therefore, our “Cats” actually becomes.

  • Almost UUE char: 0 V % T < P SPC SPC
  • UUE char: 0 V % T < P ` `

Finally! We have encoded “Cats”

We’ve turned:

into ————-> ** 0V%T<P `` Now, that’s science folks!**

**Hmm, what else are you going to need to know? Oh, right, how the UUE data is stored.**

UUE stores and sends data in lines. A line of UUE data consist of a start character, which represents how many bytes have been encoded in the line (not how many UUE characters are in the line) by using a 6-bit number stored as an ASCII char. The line of UUE data ends with a new line character (i.e., ‘\n’). Lastly, a UUE line is limited to 45 bytes of data. This means, the maximum amount of data characters in on line of UUE should be no more than 60. Or, 62, if you count the start character and the end character.

Again, examples are good. For our Cats, the line would look something like this,

  • $ 0V%T<P `` \n

Let me take a moment to describe how we get the start character. Basically, we count how many bytes we are sending, in our case 4, and we add 32. This gives us the decimal representation of the ASCII character we will use as our start character. Therefore,

  • 4 + 32 = 36 as ASCII = $

Confusing? It’ll probably make more sense when we look at the code.

Speaking of which, I think I’ve covered the basics, time to jump into implementation.

Implementing UUEncoding in C

Well, here it is. My shoddy implementation of a UUEncoder in C.

The function takes several variables.

  1. UUE_data_array is a pointer to an uint8_t array where the encoded characters will be stored.
  2. hex_data_array is a pointer to an uint8_t array containing the hexadecimal values to be encoded (to learn where I get my hexadecimal data, checkout another one of this inglorious post: Intel HEX File to Array).
  3. hex_data_array size is an integer representing how many bytes of data might be found in the hex_data_array.
  4. After the function is complete, it returns how many ASCII UUE characters were created. This is meant for parsing the UUE array at a later time.
int UUEncode(uint8_t * UUE_data_array, uint8_t * hex_data_array, int hex_data_array_size)
{
	// 1. Add char for characters per line.
	// 2. Load 3 bytes into an array.
	// 3. Encode array.
	// 4. Add padding.
	// 5. Replace ' ' with '''
	// 6. Return UUE data array (implicit) and size.
	uint8_t byte_to_encode[3];
	uint8_t uue_char[4];

	int UUEncoded_array_index = 0;
	int uue_length_char_index = 45;
	int padded_index = 0;
	int bytes_left = 0;

	// 1. Add char for characters per line.
	if(hex_data_array_size < 45)
	{
		 UUE_data_array[UUEncoded_array_index] = ((hex_data_array_size & 0x3f) + ' ');
	}
	else
	{
		UUE_data_array[UUEncoded_array_index] = 'M';
	}

	UUEncoded_array_index++;

	// Encode loop.
	for (int hex_data_array_index = 0; hex_data_array_index < hex_data_array_size; hex_data_array_index)
	{
		// 2. Load 3 bytes into an array.
		for (int i = 0; i < 3; ++i)
		{
			// Load bytes into array
			if (hex_data_array_index < hex_data_array_size)
			{
				byte_to_encode[i] = hex_data_array[hex_data_array_index];
				hex_data_array_index++;
			}
			else
			{
				// 4. Add padding.
				byte_to_encode[i] = 0;
				padded_index++;
			}
			uue_length_char_index--;
		}

		// 3. Encode array.
		uue_char[0] = ((byte_to_encode[0] >> 2) & 0x3f);
		uue_char[1] = (((byte_to_encode[0] << 4) | ((byte_to_encode[1] >> 4) & 0x0f)) & 0x3f);
		uue_char[2] = (((byte_to_encode[1] << 2) | ((byte_to_encode[2] >> 6) & 0x03)) & 0x3f);
		uue_char[3] = (byte_to_encode[2] & 0x3f);

		for (int i = 0; i < 4; i++)
		{
			// 5. Replace ' ' with '''
			if (uue_char[i] == 0x00)
			{
				UUE_data_array[UUEncoded_array_index] = 0x60;
			}
			else
			{
				UUE_data_array[UUEncoded_array_index] = (uue_char[i] + ' ');
			}

			UUEncoded_array_index++;
		}

		// Data bytes left.
		bytes_left = (hex_data_array_size - hex_data_array_index);

		if (uue_length_char_index == 0 && bytes_left > 0)
		{
			// NOTE: Could be simplified to include first char
			// and additional characters, using a positive index.
			// 1. Add char for characters per line.
			UUE_data_array[UUEncoded_array_index] = '\n';
			UUEncoded_array_index++;

			if(bytes_left < 45)
			{
				// Find how many characters are left.
				UUE_data_array[UUEncoded_array_index] = ((bytes_left & 0x3f) + ' ');
			}
			else
			{
				UUE_data_array[UUEncoded_array_index] = 'M';
			}
			UUEncoded_array_index++;
			uue_length_char_index = 45;
		}

	} // End UUE loop
	UUE_data_array[UUEncoded_array_index] = '\n';

	// 6. Return UUE data array (implicit) and size.
	return UUEncoded_array_index;
}

int check_sum(uint8_t * hex_data_array, int hex_data_array_size)
{
	int check_sum = 0;
	int char_index = 0;

	while(char_index < hex_data_array_size)
	{
		check_sum += hex_data_array[char_index];
		char_index++;
	}
	return check_sum;
}
  • 3-8: Here, I outline in pseudo-code what I wanted to get done in this function.
  • 17-25: I deal with the start character of the first line. I do this by checking if hex data we were handed is more than the UUE line limit, 45 bytes. If it is, I place an M as the start character (45 + 32 = 77 = ASCII M). If the data we’ve been handed is less than 45 bytes, let’s calculate the start character. We take 65 bits of the 8-bit number representing how many bytes are here, then add 32, this will give us our start character.
  • 30-96: This is the main loop where the work is done. We loop through all the hexadecimal data provided us, encoding as we go.
  • 33-48: The loop here deals with 3 bytes of data at a time. It also checks to see if we have less than 3 bytes left, if so, it pads the remaining space with 0 (null).
  • 47: This index is used in combination with the if statement found one lines 82-90. It is in essence repeating the beginning if statement where we determined what the start character for this line will be.
  • 51-54: This is where the magic happens. Here, we are turning the 3 bytes of 8 bits, into 4 bytes of 6 bits. We store the resulting bits in an 8-bit variable. But remember, we can put 6 bit data in a 8 bit jar, as long as we remember to be careful how we place the bits.
  • 56-69: The resulting 6-bit characters are checked to see if they are a space character (0x20), if they are, we turn them into a grave ‘ ‘ ‘ character (0x60). If they are not a space, we add 32 to the decimal value (‘ ‘ = 32 in decimal), this completes the encoding process.
  • 72: We calculate how many data bytes are left, in preparation for calculating the next line’s start character.
  • 74-96: This loop serves two purposes. One, to place a new-line character (‘\n’) at the end of our last encoded line. Two, to calculate and load the next line’s start character.
  • 96: When we’ve reached the end of our data, we place a new-line character to mark the end.
  • 112: We return the number of ASCII characters used to represent our encoded data.

And there you go. UUE!

Here are some additional resources I found helpful,

  1. Wikipedia’s article on UUEncoding
  2. NXP’s Application Note on UUEncoded for their uCs
  3. Bdk6
Intel Hexfile to Array

Originally posted on www.letsmakerobots.com my_uC_icon_5_300x300.png

Not All Those Who Wander are Lost (but I am)

I thought I’d take some time away from coding my LPC1114 Uploader and verbally process a few things I’ve learned. As always, feel free to critique any of it; it’ll only serve to make my code more robust in the end.

This post will be a series of post leading up to the large post I’ll make on writing the uploader. All posts will rely on the GCC compiler.

**

Setting Up the GCC Compiler**

I setup a C environment as basic I could. There may be easier ways to go about this, but I wanted to use GCC to compile.

To setup the environment:

1. I downloaded and setup MinGW32.

2. I added these includes to make the code go.

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <windows.h>
#include <windef.h>
#include <winnt.h>
#include <winbase.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h>

I used this line to build it:

$ gcc -o main main.c

As for editing, I’ve really grown to love Sublime Text 2.

If you have issues, make sure directory containing your files is in your PATH environment variable (I go over how to add the directory to your environment variables in this post).

Intel Hexfile to An Array Based on Data Address

To load data from an Intel HEX format file I used several functions, open_file() to create a data stream, more commonly know as a file pointer, from the file I wanted to read. And hex_file_to_array(), to parse the hex file and extract the data.

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
MAIN.C

int main(int argc, char *argv[])
{
	//If the user fails to give us two arguments yell at him.
	if ( argc != 2 ) {
		fprintf ( stderr, "Usage: %s <readfile1>\n", argv[0] );
		exit ( EXIT_FAILURE );
	}
	// Data array
	uint8_t HEX_array[32768];

	// Bytes read into array.
	int HEX_array_size;

	//File to be loaded.
	FILE *hex_file;

	//Open file using command-line info; for reading.
	hex_file = open_file (argv[0], "rb" );

	// Load the data from file
	HEX_array_size = hex_file_to_array(hex_file, HEX_array);


} // END PROGRAM
  • 6: Let’s check the number of arguments passed in by the user. If there is no file name, then we exit.
  • 11: Declare a unsigned array for the data. I’ve set it arbitrarily, but it will need to be large enough for the amount of data to be extracted from the hexfile.
  • 17: Here we create a pointer to a file data stream.
  • 20: We pass the pointer to the data stream to the open_file function. We are setting up to only read the file in binary. We pass it the file we wish to open and it returns the opened file.
  • 23: We pass hex_file_to_array a file pointer and pointer to an an array. This function reads the hex file, parses it, extracting the data and placing them into the the uint8_t array based on the data’s address found in the hexfile. The function then returns the number of data bytes found in the hex file.

open_file()

This function takes the name of a file and the mode under which to open it, then attempts to open a file pointer to this file. If it is is successful, it returns the pointer to the now open file. If it it fails, the program exits with an error code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MAIN.C

//Open file for reading, function.
FILE *open_file ( uint8_t *file, uint8_t *mode )
{
  FILE *fileOpen = fopen ( file, mode );

  if ( fileOpen == NULL ) {
    perror ( "Unable to open file" );
    exit (EXIT_FAILURE);
  }

  return fileOpen;
}

To understand this function it pays to understand well the Intel HEX file format.

Parsed HEX file:

Let's take a look at the raw data,

:10010000214601360121470136007EFE09D2190140
:100110002146017E17C20001FF5F16002148011928
:10012000194E79234623965778239EDA3F01B2CAA7
:100130003F0156702B5E712B722B732146013421C7
:00000001FF

Parsed HEX file:

: 11 2222 33 44444444444444444444444444444444 55 \n
  1. ':' = Start Code.
  2. 11 = Byte Count
  3. 2222 = Address
  4. 33 = Data Type
  5. 44 = Data
  6. 55 = Check Sum
  7. '\n' = End Code

All of the information in the file is important, but we are only looking to put the Data into the array. To extract this data we are going to use three sub-routines:

  1. read_byte_from_file()
  2. Ascii2Hex()
  3. clear_special_char()

read_byte_from_file()

One bit to understand about hex files is the data is actually stored as ASCII characters. When we open a file pointer to these ASCII characters, we can’t just read the bytes, since they’d simply be an ASCII character representing the nibble read. To make the conversion we get a character, store it as a binary nibble A, get another character and store it as binary nibble B. We then combine nibble A and B into a single byte.

The function takes three parameters: the file pointer, a uint8_t pointer for storing the complete byte, and the total_chars_read, which allows us to track how far we are into the file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DATA.C

uint8_t read_byte_from_file(FILE * file, uint8_t * char_to_put, int * total_chars_read)
{
	//Holds combined nibbles.
	uint8_t hexValue;
	//Get first nibble.
	*char_to_put = fgetc (file);
	clear_special_char(file, char_to_put, total_chars_read);
	//Put first nibble in.
	hexValue = (Ascii2Hex(*char_to_put));
	//Slide the nibble.
	hexValue = ((hexValue << 4) & 0xF0);
	//Put second nibble in.
	*char_to_put = fgetc (file);
	clear_special_char(file, char_to_put, total_chars_read);
	//Put the nibbles together.
	hexValue |= (Ascii2Hex(*char_to_put));
	//Return the byte.
	*total_chars_read += 2;

	return hexValue;
}
  • 6: Declaring a 8-bit unsinged integer to hold the finished byte.
  • 8: Get an ASCII character from the file pointer.
  • 9: Here we call the cleaer_special_char function to remove ‘\n’ and ‘\r’ found in the hex file.
  • 11: We then convert the ASCII character into a true binary nibble. The result is stored in the string. (I will cover the Ascii2Hex function below.)
  • The above steps are repeated for nibble B.
  • 18: We combine the string of nibbles into a byte.
  • 26: We increment two ASCII characters read from the file pointer.

clear_special_char()

The clear special character function is simply meant to remove the ‘:’, ‘\n’, and ‘\r’ characters from the data stream. It simply looks through the character pulled from the data stream. If it is not a special character, it does nothing. If it is, it increments the character counter and discards the character.

1
2
3
4
5
6
7
8
9
10
DATA.C

void clear_special_char(FILE * file, uint8_t * charToPut, int * totalCharsRead)
{
	//Removes CR, LF, ':'  --Bdk6's
	while (*charToPut == '\n' || *charToPut == '\r' || *charToPut ==':'){
		(*charToPut = fgetc (file));
		*totalCharsRead++;
	}
}

Ascii2Hex()

Another fairly simple function. Here, we simply find the numeric value of the ASCII character and convert it to its binary equivalent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DATA.C

//Copied in from lpc21isp.c
uint8_t Ascii2Hex(uint8_t c)
{
	if (c >= '0' && c <= '9')
	{
		return (uint8_t)(c - '0');
	}
	if (c >= 'A' && c <= 'F')
	{
		return (uint8_t)(c - 'A' + 10);
	}
	if (c >= 'a' && c <= 'f')
	{
        return (uint8_t)(c - 'a' + 10);
	}

	return 0;  // this "return" will never be reached, but some compilers give a warning if it is not present
}

This function is pretty simple, if you keep in mind each character is actually an integer. For example, the if-statements could be re-written as follows,

1
2
3
4
5
6
7
8
if (c >= 0 && c <= 9)
   { return (uint8_t)(c - 0) }

if (c >= 65 && c <= 70)
   { return (uint8_t)(c - 65 + 10)}

if (c >= 97 && c <= 102)
   {return (uint8_t)(c - 97 + 10)}

You can use an ASCII reference table to determine how a character read will be interpreted. For instance, ‘D’ or ‘d’ would be 68 or 100. 68 - 65 + 10 = 13. We know D is hexadecimal for 13 (0 = 0, 1 = 1, 1 = 2, etc… A = 10, B, = 11, C = 12, D = 13, E = 14, F = 15).

This brings us to the main function,

read_line_from_hex_file()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
DATA.C

bool read_line_from_hex_file(FILE * file, uint8_t line_of_data[], long int * combined_address, int * bytes_this_line)
{
		int data_index = 0;
		uint8_t char_to_put;
		int total_chars_read = 0;

		//To hold file hex values.
		uint8_t byte_count;
		uint8_t datum_address1;
		uint8_t datum_address2;
		uint8_t datum_record_type;
		uint8_t datum_check_sum;

		//BYTE COUNT
		byte_count = read_byte_from_file(file, &char_to_put, &total_chars_read);

		// No need to read, if no data.
		if (byte_count == 0){return false;}

		//ADDRESS1 //Will create an 8 bit shift. --Bdk6's
		datum_address1 = read_byte_from_file(file, &char_to_put, &total_chars_read);

		//ADDRESS2
		datum_address2 = read_byte_from_file(file, &char_to_put, &total_chars_read);

		//RECORD TYPE
		datum_record_type = read_byte_from_file(file, &char_to_put, &total_chars_read);

		// No need to read, if not data.
		if (datum_record_type != 0){return false;}

		*combined_address = ((uint16_t)datum_address1 << 8) | datum_address2;

		// DATA
		while(data_index < byte_count)
		{
			line_of_data[data_index] = read_byte_from_file(file, &char_to_put, &total_chars_read);
			data_index++;
		}
		*bytes_this_line = data_index;

		// CHECKSUM
		datum_check_sum = read_byte_from_file(file, &char_to_put, &total_chars_read);

		return true;
}

The above code parses exactly one line of hex data from the file pointer.

  • 17: We read the first byte of a line. This should be the ‘:’ character, but remember our clear_special_char() should skip this and read the next two bytes ‘1’ and ‘0’ (green). The “10” is how many bytes of data (blue) found on this line. Note, 10 is not a decimal number, it’s hexadecimal. Meaning, there should be 16 bytes of data found on this line.
  • 20: We check if there was any data on this line. If there are zero data, we return false.
  • 23: Take the first byte of the data address (purple).
  • 26: Take the second byte of the data address (purple).
  • 29: Get the byte (red) identifying the type of information found on this line. We are only looking for data (‘00’). The other types are explained well at the ole’ Wiki article: Intel HEX record types.
  • 32: If the record type is not data, we don’t want it. We return false.
  • 34: Combine the two 8-bit address bytes into one 16-bit address.
  • 37: Let’s get all the data found on this line and put it into the array we provided the function.
  • 42: We have to keep track of how many bytes are on each line, to complete our address of the data. Therefore, we pass it back to hex_file_to_array().
  • 45: I read the checksum, but I don’t do anything with it. I probably should.

hex_file_line_count()

To properly parse the hexfile we need to know how many lines are found in the the file. We can find this information several ways, but I counted the number of line start characters ‘:’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MAIN.C

int hex_file_line_count(FILE * file_to_count)
{
	int line_count = 0;
	char got_char;

	while(got_char != EOF)
	{
		got_char = fgetc(file_to_count);
		if (got_char == ':'){line_count++;}
	}
	rewind(file_to_count);
	return line_count;
}
  • 8: Loops until the end-of-file character is reached.
  • 10: Gets a ASCII character from the file pointer.
  • 11: We check to see if the character we go was line start character ‘:’.
  • 13: This function iterates through the entire file, but we want to start pulling data from the beginning of the file, so we rewind the file to the first character.
  • 14: We return the number of lines.

hex_file_to_array()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
DATA.C

int hex_file_to_array(FILE * file, uint8_t hex_data[])
{
	// 1. Get line count.
	// 2. Read a line. From ':' to '\n'
	// 3. Parse a line.
	//   Repeat for all lines.

	// Data per line.
	uint8_t line_of_data[32];
	long int combined_address[4096];

	// Indices and counters
	int hex_line_index = 0;
	int chars_this_line = 0;
	int total_chars_read = 0;
	// How many lines in the hexfile?
	int hex_lines_in_file = 0;
	int bytes_this_line[4096];

	// Let's count how many lines are in this file.
	hex_lines_in_file = hex_file_line_count(file);

	// Indices for parsing.
	int line_index = 0;
	int byte_index = 0;
	bool read_line_ok = false;

	// Parse all lines in file.
	while(line_index < hex_lines_in_file)
	{
		read_line_ok = read_line_from_hex_file(file, line_of_data, &combined_address[line_index], &bytes_this_line[line_index]);
		if (!read_line_ok)
		{
			printf("Line#: %i. Dude, that's not data!\n", line_index);
			read_line_ok = true;
		}
		while(byte_index < bytes_this_line[line_index])
		{
			hex_data[combined_address[line_index] + byte_index] = line_of_data[byte_index];
			line_of_data[byte_index] = '\0';
			byte_index++;
		}
		byte_index = 0;
		line_index++;
	}

	// Print out parsed data.
	int k = 0;
	int j = 0;
	int printed_bytes = 0;
	while (k < hex_lines_in_file)
	{
		while(j < bytes_this_line[k])
		{
			printf("%02X ", hex_data[j+(printed_bytes)]);
			j++;
		}
		printed_bytes += bytes_this_line[k];
		j=0;
		printf("\n");
		k++;
	}

	return total_chars_read;
} // End hex_file_to_array
  • 23: We count the number of lines in the file we wish to extract data.
  • 31: This is the work-horse loop. We loop until the we have read through all the lines we counted.
  • 33: We pass read_line_from_hex() our variables we wish to fill. The hex file we want to parse (file), the buffer we hold the line data in, the int array which will serve to hold the address of this line of data, a variable to hold the number of bytes in this line. If the function was got data, it will return true. Otherwise, it will return false. We store this flag to make sure we got something.
  • 34: We check to see if we actually got data from our attempt.
  • 39: Here, we move the line of data from the buffer into the final array.
  • 41: We place the data into the array based upon the address we pulled from the line (address1 + address2) and the byte number.
  • 42: Reset the buffer to nil.
  • 49-64: Finally, we print out the data. The k-loop goes through each line we extracted; the j-loop goes through each byte found on the respective line.

And that’s it. Note, 49-64 is meant to demonstrate the data is properly extracted. These lines could be moved to another function where the data may be used as needed.