Skip to content

Commit

Permalink
Merge pull request #20 from Ho-Ro/fixes
Browse files Browse the repository at this point in the history
adding missing ihex sources - sorry
  • Loading branch information
sarnau authored Sep 13, 2024
2 parents af6291a + 5ac3fa1 commit 2390351
Show file tree
Hide file tree
Showing 5 changed files with 871 additions and 0 deletions.
192 changes: 192 additions & 0 deletions kk_ihex.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* kk_ihex.h: A simple library for reading and writing the Intel HEX
* or IHEX format. Intended mainly for embedded systems, and thus
* somewhat optimised for size at the expense of error handling and
* generality.
*
* USAGE
* -----
*
* The library has been split into read and write parts, which use a
* common data structure (`struct ihex_state`), but each can be used
* independently. Include the header `kk_ihex_read.h` for reading, and/or
* the header `kk_ihex_write.h` for writing (and link with their respective
* object files). Both can be used simultaneously - this header defines
* the shared data structures and definitions.
*
*
* READING INTEL HEX DATA
* ----------------------
*
* To read data in the Intel HEX format, you must perform the actual reading
* of bytes using other means (e.g., stdio). The bytes read must then be
* passed to `ihex_read_byte` and/or `ihex_read_bytes`. The reading functions
* will then call `ihex_data_read`, at which stage the `struct ihex_state`
* structure will contain the data along with its address. See the header
* `kk_ihex_read.h` for details and example implementation of `ihex_data_read`.
*
* The sequence to read data in IHEX format is:
* struct ihex_state ihex;
* ihex_begin_read(&ihex);
* ihex_read_bytes(&ihex, my_input_bytes, length_of_my_input_bytes);
* ihex_end_read(&ihex);
*
*
* WRITING BINARY DATA AS INTEL HEX
* --------------------------------
*
* In order to write out data, the `ihex_write_at_address` or
* `ihex_write_at_segment` functions are used to set the data location,
* and then the binary bytes are written with `ihex_write_byte` and/or
* `ihex_write_bytes`. The writing functions will then call the function
* `ihex_flush_buffer` whenever the internal write buffer needs to be
* cleared - it is up to the caller to provide an implementation of
* `ihex_flush_buffer` to do the actual writing. See the header
* `kk_ihex_write.h` for details and an example implementation.
*
* See the declaration further down for an example implementation.
*
* The sequence to write data in IHEX format is:
* struct ihex_state ihex;
* ihex_init(&ihex);
* ihex_write_at_address(&ihex, 0);
* ihex_write_bytes(&ihex, my_data, length_of_my_data);
* ihex_end_write(&ihex);
*
* For outputs larger than 64KiB, 32-bit linear addresses are output. Normally
* the initial linear extended address record of zero is NOT written - it can
* be forced by setting `ihex->flags |= IHEX_FLAG_ADDRESS_OVERFLOW` before
* writing the first byte.
*
* Gaps in the data may be created by calling `ihex_write_at_address` with the
* new starting address without calling `ihex_end_write` in between.
*
*
* The same `struct ihex_state` may be used either for reading or writing,
* but NOT both at the same time. Furthermore, a global output buffer is
* used for writing, i.e., multiple threads must not write simultaneously
* (but multiple writes may be interleaved).
*
*
* CONSERVING MEMORY
* -----------------
*
* For memory-critical use, you can save additional memory by defining
* `IHEX_LINE_MAX_LENGTH` as something less than 255. Note, however, that
* this limit affects both reading and writing, so the resulting library
* will be unable to read lines with more than this number of data bytes.
* That said, I haven't encountered any IHEX files with more than 32
* data bytes per line. For write only there is no reason to define the
* maximum as greater than the line length you'll actually be writing,
* e.g., 32 or 16.
*
* If the write functionality is only occasionally used, you can provide
* your own buffer for the duration by defining `IHEX_EXTERNAL_WRITE_BUFFER`
* and providing a `char *ihex_write_buffer` which points to valid storage
* for at least `IHEX_WRITE_BUFFER_LENGTH` characters from before the first
* call to any IHEX write function to until after the last.
*
* If you are doing both reading and writing, you can define the maximum
* output length separately as `IHEX_MAX_OUTPUT_LINE_LENGTH` - this will
* decrease the write buffer size, but `struct ihex_state` will still
* use the larger `IHEX_LINE_MAX_LENGTH` for its data storage.
*
* You can also save a few additional bytes by disabling support for
* segmented addresses, by defining `IHEX_DISABLE_SEGMENTS`. Both the
* read and write modules need to be build with the same option, as the
* resulting data structures will not be compatible otherwise. To be honest,
* this is a fairly pointless optimisation.
*
*
* Copyright (c) 2013-2019 Kimmo Kulovesi, https://arkku.com/
* Provided with absolutely no warranty, use at your own risk only.
* Use and distribute freely, mark modified copies as such.
*/

#ifndef KK_IHEX_H
#define KK_IHEX_H

#define KK_IHEX_VERSION "2019-08-07"

#include <stdint.h>

#ifdef IHEX_USE_STDBOOL
#include <stdbool.h>
typedef bool ihex_bool_t;
#else
typedef uint_fast8_t ihex_bool_t;
#endif

typedef uint_least32_t ihex_address_t;
typedef uint_least16_t ihex_segment_t;
typedef int ihex_count_t;

// Maximum number of data bytes per line (applies to both reading and
// writing!); specify 255 to support reading all possible lengths. Less
// can be used to limit memory footprint on embedded systems, e.g.,
// most programs with IHEX output use 32.
#ifndef IHEX_LINE_MAX_LENGTH
#define IHEX_LINE_MAX_LENGTH 255
#endif

enum ihex_flags {
IHEX_FLAG_ADDRESS_OVERFLOW = 0x80 // 16-bit address overflow
};
typedef uint8_t ihex_flags_t;

typedef struct ihex_state {
ihex_address_t address;
#ifndef IHEX_DISABLE_SEGMENTS
ihex_segment_t segment;
#endif
ihex_flags_t flags;
uint8_t line_length;
uint8_t length;
uint8_t data[ IHEX_LINE_MAX_LENGTH + 1 ];
} kk_ihex_t;

enum ihex_record_type {
IHEX_DATA_RECORD,
IHEX_END_OF_FILE_RECORD,
IHEX_EXTENDED_SEGMENT_ADDRESS_RECORD,
IHEX_START_SEGMENT_ADDRESS_RECORD,
IHEX_EXTENDED_LINEAR_ADDRESS_RECORD,
IHEX_START_LINEAR_ADDRESS_RECORD
};
typedef uint8_t ihex_record_type_t;

#ifndef IHEX_DISABLE_SEGMENTS

// Resolve segmented address (if any). It is the author's recommendation that
// segmented addressing not be used (and indeed the write function of this
// library uses linear 32-bit addressing unless manually overridden).
//
#define IHEX_LINEAR_ADDRESS( ihex ) ( ( ihex )->address + ( ( (ihex_address_t)( ( ihex )->segment ) ) << 4 ) )
//
// Note that segmented addressing with the above macro is not strictly adherent
// to the IHEX specification, which mandates that the lowest 16 bits of the
// address and the index of the data byte must be added modulo 64K (i.e.,
// at 16 bits precision with wraparound) and the segment address only added
// afterwards.
//
// To implement fully correct segmented addressing, compute the address
// of _each byte_ with its index in `data` as follows:
//
#define IHEX_BYTE_ADDRESS( ihex, byte_index ) \
( ( ( ( ihex )->address + ( byte_index ) ) & 0xFFFFU ) + ( ( (ihex_address_t)( ( ihex )->segment ) ) << 4 ) )

#else // IHEX_DISABLE_SEGMENTS:

#define IHEX_LINEAR_ADDRESS( ihex ) ( ( ihex )->address )
#define IHEX_BYTE_ADDRESS( ihex, byte_index ) ( ( ihex )->address + ( byte_index ) )

#endif

// The newline string (appended to every output line, e.g., "\r\n")
#ifndef IHEX_NEWLINE_STRING
#define IHEX_NEWLINE_STRING "\n"
#endif

// See kk_ihex_read.h and kk_ihex_write.h for function declarations!

#endif // !KK_IHEX_H
171 changes: 171 additions & 0 deletions kk_ihex_read.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* kk_ihex_read.c: A simple library for reading the Intel HEX (IHEX) format.
*
* See the header `kk_ihex.h` for instructions.
*
* Copyright (c) 2013-2019 Kimmo Kulovesi, https://arkku.com/
* Provided with absolutely no warranty, use at your own risk only.
* Use and distribute freely, mark modified copies as such.
*/

#include "kk_ihex_read.h"

#define IHEX_START ':'

#define ADDRESS_HIGH_MASK ( (ihex_address_t)0xFFFF0000U )

enum ihex_read_state {
READ_WAIT_FOR_START = 0,
READ_COUNT_HIGH = 1,
READ_COUNT_LOW,
READ_ADDRESS_MSB_HIGH,
READ_ADDRESS_MSB_LOW,
READ_ADDRESS_LSB_HIGH,
READ_ADDRESS_LSB_LOW,
READ_RECORD_TYPE_HIGH,
READ_RECORD_TYPE_LOW,
READ_DATA_HIGH,
READ_DATA_LOW
};

#define IHEX_READ_RECORD_TYPE_MASK 0x07
#define IHEX_READ_STATE_MASK 0x78
#define IHEX_READ_STATE_OFFSET 3

void ihex_begin_read( struct ihex_state *const ihex ) {
ihex->address = 0;
#ifndef IHEX_DISABLE_SEGMENTS
ihex->segment = 0;
#endif
ihex->flags = 0;
ihex->line_length = 0;
ihex->length = 0;
}

void ihex_read_at_address( struct ihex_state *const ihex, ihex_address_t address ) {
ihex_begin_read( ihex );
ihex->address = address;
}

#ifndef IHEX_DISABLE_SEGMENTS
void ihex_read_at_segment( struct ihex_state *const ihex, ihex_segment_t segment ) {
ihex_begin_read( ihex );
ihex->segment = segment;
}
#endif

void ihex_end_read( struct ihex_state *const ihex ) {
uint_fast8_t type = ihex->flags & IHEX_READ_RECORD_TYPE_MASK;
uint_fast8_t sum;
if ( ( sum = ihex->length ) == 0 && type == IHEX_DATA_RECORD ) {
return;
}
{
// compute and validate checksum
const uint8_t *const eptr = ihex->data + sum;
const uint8_t *r = ihex->data;
sum += type + ( ihex->address & 0xFFU ) + ( ( ihex->address >> 8 ) & 0xFFU );
while ( r != eptr ) {
sum += *r++;
}
sum = ( ~sum + 1U ) ^ *eptr; // *eptr is the received checksum
}
if ( ihex_data_read( ihex, type, (uint8_t)sum ) ) {
if ( type == IHEX_EXTENDED_LINEAR_ADDRESS_RECORD ) {
ihex->address &= 0xFFFFU;
ihex->address |= ( ( (ihex_address_t)ihex->data[ 0 ] ) << 24 ) | ( ( (ihex_address_t)ihex->data[ 1 ] ) << 16 );
#ifndef IHEX_DISABLE_SEGMENTS
} else if ( type == IHEX_EXTENDED_SEGMENT_ADDRESS_RECORD ) {
ihex->segment = (ihex_segment_t)( ( ihex->data[ 0 ] << 8 ) | ihex->data[ 1 ] );
#endif
}
}
ihex->length = 0;
ihex->flags = 0;
}

void ihex_read_byte( struct ihex_state *const ihex, const char byte ) {
uint_fast8_t b = (uint_fast8_t)byte;
uint_fast8_t len = ihex->length;
uint_fast8_t state = ( ihex->flags & IHEX_READ_STATE_MASK );
ihex->flags ^= state; // turn off the old state
state >>= IHEX_READ_STATE_OFFSET;

if ( b >= '0' && b <= '9' ) {
b -= '0';
} else if ( b >= 'A' && b <= 'F' ) {
b -= 'A' - 10;
} else if ( b >= 'a' && b <= 'f' ) {
b -= 'a' - 10;
} else if ( b == IHEX_START ) {
// sync to a new record at any state
state = READ_COUNT_HIGH;
goto end_read;
} else {
// ignore unknown characters (e.g., extra whitespace)
goto save_read_state;
}

if ( !( ++state & 1 ) ) {
// high nybble, store temporarily at end of data:
b <<= 4;
ihex->data[ len ] = b;
} else {
// low nybble, combine with stored high nybble:
b = ( ihex->data[ len ] |= b );
// We already know the lowest bit of `state`, dropping it may produce
// smaller code, hence the `>> 1` in switch and its cases.
switch ( state >> 1 ) {
default:
// remain in initial state while waiting for :
return;
case ( READ_COUNT_LOW >> 1 ):
// data length
ihex->line_length = b;
#if IHEX_LINE_MAX_LENGTH < 255
if ( b > IHEX_LINE_MAX_LENGTH ) {
ihex_end_read( ihex );
return;
}
#endif
break;
case ( READ_ADDRESS_MSB_LOW >> 1 ):
// high byte of 16-bit address
ihex->address &= ADDRESS_HIGH_MASK; // clear the 16-bit address
ihex->address |= ( (ihex_address_t)b ) << 8U;
break;
case ( READ_ADDRESS_LSB_LOW >> 1 ):
// low byte of 16-bit address
ihex->address |= (ihex_address_t)b;
break;
case ( READ_RECORD_TYPE_LOW >> 1 ):
// record type
if ( b & ~IHEX_READ_RECORD_TYPE_MASK ) {
// skip unknown record types silently
return;
}
ihex->flags = ( ihex->flags & ~IHEX_READ_RECORD_TYPE_MASK ) | b;
break;
case ( READ_DATA_LOW >> 1 ):
if ( len < ihex->line_length ) {
// data byte
ihex->length = len + 1;
state = READ_DATA_HIGH;
goto save_read_state;
}
// end of line (last "data" byte is checksum)
state = READ_WAIT_FOR_START;
end_read:
ihex_end_read( ihex );
}
}
save_read_state:
ihex->flags |= state << IHEX_READ_STATE_OFFSET;
}

void ihex_read_bytes( struct ihex_state *restrict ihex, const char *restrict data, ihex_count_t count ) {
while ( count > 0 ) {
ihex_read_byte( ihex, *data++ );
--count;
}
}
Loading

0 comments on commit 2390351

Please sign in to comment.