Mapping segments and memory allocation on a segment LCD driver
The trick here is to employ both a local memory buffer, and digit masking.

Step one: create a pair of masks that define which bits of the memory are associated with which digit.

Imagine you have your digits wired in this order in memory. For simplicity I am working with 16-bit values, but there is no reason why you can't split them into two 8 bit values each if you find that easier (P is the decimal point. Lower case is the left digit, upper case the right digit):

      MSB                         LSB
      a C d g E B p A c D f b e G F P


You would then create a pair of corresponding 16-bit values that represent which bits are associated with each digit.

      MSB                         LSB
      a C d g E B p A c D f b e G F P
Left  1 0 1 1 0 0 1 0 1 0 1 1 1 0 0 0 
Right 0 1 0 0 1 1 0 1 0 1 0 0 0 1 1 1 


The right is just the inverse of the left, so for right you can actually use ~left and save yourself 2 bytes.

So that would end up as:

uint16_t leftDigit = 0xB2B8;
uint16_t rightDigit = 0x4D47;


Now you create yourself a table of which segments create which numbers. Now the magic here is that you don't want individual digits. Not yet. What you want is a table of 16-bit values that represent the numbers 00, 11, 22, 33, 44, 55, 66, 77, 88, 99 (and optionally two spaces, but that's just 0x0000, so you may not need to store that explicitly).

You may end up with something like:

uint16_t digits[] = {
    0b1110110111111010, // 00
    0b0100010010010000, // 11
    0b1011110101011100, // 22
    // ... etc ...
};


Now the real magic happens.

You need a memory buffer that mirrors the contents of your driver memory.  You don't directly modify the driver memory - you modify your memory buffer, and then send both bytes of it to the display driver at once, updating all the segments together.

uint16_t buffer = 0;


Now to set a digit you first need to erase whatever is in the bits that correspond to the digit you want.  Let's say we want to display the number 12.  We'll start with the left digit.

// Step one, erase the left digit
buffer &amp;= ~leftDigit;
// Step two, place into it the bits for 1 with the left digit mask imposed:
buffer |= (digits[1] &amp; leftDigit);

// Step three, erase the right digit
buffer &amp;= ~rightDigit;
// Step four, place into it the bits for 1 with the left digit mask imposed: 
buffer |= (digits[2] &amp; leftDigit);


That is actually quite a long winded way. But it does allow you to just change one digit without affecting the other.  If you want to just replace the entire content of the display (both digits at once) then you don't need as much work:

buffer = (digits[1] &amp; leftDigit) | (digits[2] &amp; rightDigit);


I'll just dismantle that for you so you can see what it does.

            MSB                         LSB
            a C d g E B p A c D f b e G F P
 digits[1]: 0 1 0 0 0 1 0 0 1 0 0 1 0 0 0 0
 leftDigit: 1 0 1 1 0 0 1 0 1 0 1 1 1 0 0 0
       AND: 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 // b and c enabled

 digits[2]: 1 0 1 1 1 1 0 1 0 1 0 1 1 1 0 0
rightDigit: 0 1 0 0 1 1 0 1 0 1 0 0 0 1 1 1
       AND: 0 0 0 0 1 1 0 1 0 1 0 0 0 1 0 0 // A B D E and G enabled

OR of ANDS: 0 0 0 0 1 1 0 1 1 1 0 1 0 1 0 0 // b c A B D E G enabled


And map those segments on to the standard layout of a 7 segment display:



And you form the number 12.

Then you take the buffer value and write it out to your display memory.  You can split the 16 bit value into 8 bit values with bit masking (&amp; 0xFF) and bit shifting (&gt;&gt; 8).

uint8_t b1 = buffer &amp; 0xFF; // Lower byte of memory
uint8_t b2 = (buffer &gt;&gt; 8) &amp; 0xFF; // Upper byte of memory