Add external binary file to linker - how to?

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

What I would like to do is add one (or possibly more) files to a compiled HEX file for upload to an AVR.  Note that I am using avr-gcc and a Makefile.  What I also need is some way to know WHERE (what address) the added file starts at and ends at (or starts at and it's size).

 

For example, let's say I had a small program that did  pgm_read_byte_far() calls to read the data contained in the externally added file and sent that data to a port which was connected to a DAC (to generate audio).

 

I need the finished HEX file to load into the AVR (in this order):

 

* Regular program code

* Start address of additional file (accessible by the program)

* End address or size of additional file (accessible by the program)

* All the bytes of the additional file (loaded flash) of course

 

The additional file format can be Intel HEX, raw binary or anything else - I don't care (but would prefer raw binary).

 

I cannot simply convert the file into a PROGMEM array because there is a SIGNED 16 bit size limit to the pointer (that is, the array cannot be more than 32767 bytes long) and the data can be (and usually is) much larger - up to filling all of the flash in a MEGA2560.

 

I have already successfully done this by uploading just the program, seeing how large it is, then converting the additional file into a HEX file (with a load offset so that it doesn't overwrite the program itself). The  pgm_read_byte_far() function works fine to access all of the data and send it to the DAC.

 

But, it's a manual method and a pain in the rear... having to see how large the sketch is, then manually converting the additional file to HEX with the proper offset, then loading it manually.  Also, the flash has to be erased before each go-around or else the new code overwrites the old without erasing the old, making a mess (i.e. doesn't run).

 

And, getting the erase / program / program sequence right is a pain, otherwise I end up erasing the sketch or erasing the sound file.

 

If it were all integrated into the Makefile, I could just edit / compile / upload / repeat with ease.

 

I've already looked at this info: Atmel Website but I just don't get it (or at least I can't seem to make it work).

 

Here is the code I'm currently using if it's of any help to understand what I am looking for:

 

#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#define DAC_OUT PORTK
#define DAC_DDR DDRK
#define DAC_INP PINK // we don't use this

#define RATE 8000 // sound data is at 8 khz.

volatile uint8_t play;
volatile uint8_t busy;
volatile uint32_t addr;
volatile uint32_t count;

ISR (TIMER1_COMPA_vect)
{
   if (count) { // setting count begins countdown
      count--;
      if (play) { // disable DAC output if ISR used for timing
         DAC_OUT = pgm_read_byte_far (addr++);
      }
   } else {
      play = 0; // disable DAC output
      busy = 0; // done, flag "not busy"
   }
}

void delay_msec (uint32_t msec)
{
   while (msec--) { // for each msec
      sei (); // wait for not busy
      while (busy);
      cli (); // interrupts off to reset values
      busy = 1; // flag ISR is busy
      play = 0; // flag "disable DAC"
      count = (RATE / 1000); // count for 1 millisecond
   }
}

void io_init (uint32_t rate)
{
   const uint16_t div[] = { // clock divider constants
      0, 1, 8, 64, 256, 1024, // 0 is a dummy to align bit value
   };

   uint8_t n, x;
   uint32_t divider;

   cli(); // interrupts off

   busy = 0; // init ISR vars
   addr = 0;
   count = 0;
   play = 0;

   n = (sizeof (div) / sizeof (*div));

   for (x = 1; x < n; x++) { // scale so divider fits into 16 bits
      divider = (uint32_t)(((F_CPU / rate / div[x]) - 1) + 0.5);
      if (divider < 65536) { // fits in 16 bits, use it
         break;
      }
   }

   DAC_DDR = 0xFF;

   TCCR1A = 0x00;
   TCCR1B = ((1 << WGM12) | x); // timer mode 4 and divider _BITS_
   TCCR1C = 0x00;
   OCR1A = divider; // divider
   TIMSK1 = _BV(OCIE1A); // enable timer

   sei(); // interrupts on
}

void send_data (uint32_t data, uint32_t datsize)
{
   cli(); // interrupts off
   count = datsize; // file size
   busy = 1; // flag ISR is busy
   play = 1; // enable DAC output
   addr = data; // address of data start in PROGMEM
   sei(); // interrupts on
   while (busy); // wait for data to be done playing
}

int main (void)
{
   io_init(RATE); // setup timer 1

   while (1) {
      // note address and size manually calculated for now
      send_data(0x1000, 234893); // play data address, data size
      delay_msec (3000); // wait 3 seconds
   }

   return 0; // make compiler happy
}

 

Any ideas how to do this will be greatly appreciated.
 

Thanks!

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Sat. Dec 31, 2016 - 09:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Take a look at this:

        How to add a raw binary image to linker output (Link).

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Chuck99 wrote:

Take a look at this:

        How to add a raw binary image to linker output (Link).

 

 

That's the same link I mentioned in my original post......

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Krupski wrote:
That's the same link I mentioned in my original post......

 

Oops!  Sorry about that.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What do you mean by "just don't get it"? Using objcopy is surely the easiest solution? Having said that I'm a great fan of using xxd to convert binary back to an array in C source.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

What do you mean by "just don't get it"? Using objcopy is surely the easiest solution? Having said that I'm a great fan of using xxd to convert binary back to an array in C source.

 

By "just don't get it" I mean that I try the examples and can't get them to work. As far as converting the binary into C source, I can very easily, in many different ways, generate a source file that contains the bytes of my audio file.

 

A typical file looks like this (obviously just a TINY example with 16 bit data):

 

static const uint16_t shutter[] PROGMEM = {
    // header
    0x8060, 0x0018,
    // data
    0x8030, 0x0018, 0x8018, 0x0018, 0x8030, 0x0018, 0x8030, 0x0018, // B
    0x8018, 0x0018, 0x8030, 0x0018, 0x8018, 0x0018, 0x8018, 0x0018, // 4
    0x8030, 0x0018, 0x8018, 0x0018, 0x8030, 0x0018, 0x8030, 0x0018, // B
    0x8030, 0x0018, 0x8018, 0x0018, 0x8018, 0x0018, 0x8018, 0x0018, // 8
    0x8030, 0x0018, 0x8030, 0x0018, 0x8030, 0x0018, 0x8030, 0x01C8, // F
};

 

Problem is that the pointer to the data is signed 16 bit, meaning that the array (which is made up of 8 bit uint8_t values) cannot have more than 32767 bytes in it. I want to load something like 200K or more bytes into flash and then read them out.

 

I could break up the file into however many 32K blocks it would require, but being audio I don't want a "glitch" as I move the pointers from the first block to the second, the second to the third, etc.... it has to be one contiguous block.

 

Currently, I "get it to work" by taking my audio data, converting it from raw binary to an Intel HEX file (using a utility I wrote) then load it into the AVR after loading the small "player" code piece.  But, I SHOULD be able to do it all in one operation using some magic in my Makefile... that's what I currently don't know how to do.  sad

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Sun. Jan 1, 2017 - 10:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You won't get a glitch moving between 32K blocks, the pointer change will happen in a fraction of a microsecond, that's of no consequence at audio sample rates.
.
Why write a utility to convert bin to hex? objcopy does that anyway.
.
But as the avr-libc manual tells you it can just as easily convert bin straight to avr-elf32 and it even auto assigns a linkable label to the block. But what are you going to do about RAMPD at the 64K boundaries for the ELPM?
.
(thinks: why do I need fullstop on blank lines to separate paras when typing on mobile?)

Last Edited: Sun. Jan 1, 2017 - 11:11 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

(1) You won't get a glitch moving between 32K blocks, the pointer change will happen in a fraction of a microsecond, that's of no consequence at audio sample rates.
.
(2) Why write a utility to convert bin to hex? objcopy does that anyway.
.
(3) But as the avr-libc manual tells you it can just as easily convert bin straight to avr-elf32 and it even auto assigns a linkable label to the block. But what are you going to do about RAMPD at the 64K boundaries for the ELPM?
.

 

(1) Yeah, probably have more than enough time between 8 khz intervals to move pointers around, but I shouldn't have to do that... I should be able to just add an arbitrary file and have it "merged into" the final HEX.

 

(2) I wrote the utility because:

   (a) At the time I didn't know that objcopy would do it for me

   (b) I wanted to be able to add arbitrary load offsets to the file

 

(3) I know that I CAN do it, I need to know HOW to do it.  As far as RAMPD/ELPM/ETC... I don't care about them. I use pgm_read_word_far() to access the data, and that function crosses 64K boundaries no problem. I can read a 200K+ file from flash contiguously just by using pgm_read_word_far().

 

Concerning the utility that I wrote, it's used like this:

 

bin2hex infile.bin outfile.hex optional_load_offset optional_record_length

 

Or, for real:  bin2hex sound.raw sound.hex 0x1000

 

That converts "sound.raw" to "sound.hex" and sets the load address to 0x1000 (aka 4096). I can also specify the offset in decimal. The "0x" prefix makes it parse hex.

 

If the file crosses a 64K boundary, then the "extended linear address" line is generated and sent to tell AVRDUDE (or anyone else) that the following code loads into the next 64K block.

 

Here is an actual piece of a 239K sound file (hex file) showing the address boundary change:

 

:10FFD000818D897B7375838078757D867C72728252
:10FFE0008E8682828B8A7D7C828F8C7F76757F7DE8
:10FFF00078767A847B716E7D8F8A84838E90807B05
:020000040001F9
:10000000818E8B7B74737B7A78797D867F75707B2C
:100010008D8B827F8A91837A7E8D8C7C74757F7BB9
:10002000767B7F88827973748386817D8590877CD7

 

See? "Record type 04" means "use this 64K block" and the "0001" means "the block from 64K to 128K".  Note that there is no need to send a record type 4 with a value of 0000 (i.e. the first 64K block) because that is assumed to be the default (although it hurts nothing TO send that data).

 

Gentlemen may prefer Blondes, but Real Men prefer Redheads!

Last Edited: Sun. Jan 1, 2017 - 11:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can specify a base offset in objcopy too. I seem to remember it is --change-address

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Krupski

here is the line you add to Makefile 

 

avr-objcopy.exe  -O ihex -I binary  12.wav     output.hex

 

12.wav is the binary to load into atmega (up to 240k)

 

output.hex is the file you will use for a programmer

 

line above will generate hex from 000000 address, so you can tell hex loader bias that you need

you may use   --change-addresses 8192 in addition to make file itself biased for 8192 (can be any value)

You need to correct your code to use appropriate pointer to get a byte from prom -- atmega works in the space

of 2 mega bytes correctly with auto increment.