Specifically to resolve the question of binding variables to specific addresses in flash memory on the PIC18 with the C18 compiler, please reference the section "Pragmas" in the hlpC18ug.chm in the doc directory where the compiler is installed.
To do this you need to define a new "section" in memory and bind it to a start address so
#pragma romdata serial_no_section=0x1700
This creates a new section called "serial_no_section" that starts at address 0x1700 in flash (program) memory (because we defined "romdata" in the #pragma).
Directly after the #pragma line, define your variable(s) so:
#pragma romdata serial_no_section=0x1700
const rom int mySerialNumber = 0x1234;
#pragma romdata
Now you have 0x12 at address 0x1700 and 0x34 at address 0x1701 in memory (because PIC18 uses the little-endian model). The "const rom" ensure that the compiler knows that this is a const variable type, and that the variable lies in "rom" memory and thus need to be accessed via table read instructions.
The final #pragma romdata
statement ensures that any following variable declarations are linked to default memory sections as the linker sees fits rather than following on in the "serial_no_section" section.
Now all code can simply reference the variable "mySerialNumber", and you know exactly what address the serial number can be found at in memory.
Editing the HEX code can be a bit challenging as you need to calculate the checksum for each line you edit. I am working on a C++ class to decode and encode Intel HEX files which should make this easier, but it is not finished yet. Decoding files works, encoding again is not yet implemented. The project (if you are interested) is here https://github.com/codinghead/Intel-HEX-Class
Hope this helps
I have found over the years that except in speed-critical or multi-master applications, it's actually easier to bit-bang an I2C master than to try to use the I2C facilities built into many chips.
Note that if a device uses clock stretching, any time you release SCK, you must wait for it to actually go high. For simplicity, such delays are omitted from the following descriptions, but should be included if appropriate in your "release_SCK()" routine.
To start an I2C transaction, release SCK (if it isn't already) and, if the data line is low, assert SCK (drive it low), release SDA (if it isn't already), and release the SCK. Repeat this process up to nine times until SDA is high. If SDA is still low after nine repetitions, the bus is unusable.
To output each byte (including the address byte), assert SDA, and then for each bit repeat the sequence (assert SCK; set SDA high or low to match next bit of data; release SCK) eight times. After the last bit, assert SCK, release SDA, and release SCK. If SDA is low, a slave is acknowledging; if SDA is high, no slave is acknowledging and the transaction should be aborted.
When all output is complete, assert SCK, then SDA, and then release SCK, then SDA.
To input each byte, assert SDA, then release SCK if it isn't already (it will be for the first byte, but not others). Then reassert SCK, release SDA, and repeat the sequence (release SCK, read data bit, assert SCK) eight times. Note that at the end of this sequence, unlike when outputting a byte, SCK will be left asserted.
When all input is complete, release SDA (it should already already be released) and SCK.
Note that because the clock is left asserted after inputting each byte, it's not necessary to specify whether the byte should be ack'ed or nak'ed. If you read another byte, the last byte read will be nak'ed. If you terminate the read, it will be nak'ed.
Start; send address; write one byte, finish
SCK - -__-__-__-__-__-__-__-__-__-- -__-__-__-__-__-__-__-__-__--- -__--
SDA(M) - __777666555444333222111___--- --777666555444333222111000---- --__-
SDA(S) - -------------------------??AA A------------------------??AAA A----
Start; send address; read two bytes; finish
SCK - -__-__-__-__-__-__-__-__-__--- -__--_--_--_--_--_--_--_--__ -__--_--_--_--_--_--_--_--__ _-
SDA(M) - __777666555444333222111------- __-------------------------- __-------------------------- --
SDA(S) - -------------------------??AAA A??77?66?55?44?33?22?11?00?? -??77?66?55?44?33?22?11?00?? ?-
Best Answer
The code calls
doSomething
three times, first with the highest byte, second with the middle byte, and third with the lowest byte.