2
\$\begingroup\$

I need to output 8-bit integers via parallel (i.e. using 8 pins on the same port) at 1024hz. From what I can see, I have two options:

1) Write to each pin individually using DigitalOut.

2) Avoid the mbed library altogether, and do something like:

void sendByte(char byte)
{ 
    DataPort|= (((byte >>0x00) & 0x01) << LCD_D0);
    DataPort|= (((byte >>0x01) & 0x01) << LCD_D1);
    DataPort|= (((byte >>0x02) & 0x01) << LCD_D2);
    DataPort|= (((byte >>0x03) & 0x01) << LCD_D3);
    DataPort|= (((byte >>0x04) & 0x01) << LCD_D4);
    DataPort|= (((byte >>0x05) & 0x01) << LCD_D5);
    DataPort|= (((byte >>0x06) & 0x01) << LCD_D6);
    DataPort|= (((byte >>0x07) & 0x01) << LCD_D7);
}

I've heard that DigitalOut is rather slow, although I'm not sure of what frequency it can handle. Is the second option likely to be faster? Can anyone compare the two methods?

SIDE QUESTION: I'll be using the output with a DAC to produce an analog signal. Will I need to use a buffer in between, or are the pins guaranteed to output simultaneously?

\$\endgroup\$
5
  • 3
    \$\begingroup\$ If you care about performance, why not arrange the connections so that the data lines are connected to pins mapped to adjaced bits in the register; e.g. port A pins 3..10? Then you can just write DataPort |= (byte & 0xFF) << LCD_D0; \$\endgroup\$
    – jms
    May 29, 2018 at 16:25
  • \$\begingroup\$ Ok, so writing to the port all at once? Would that actually guarantee that they would output simultaneously? \$\endgroup\$
    – 19172281
    May 29, 2018 at 16:35
  • 1
    \$\begingroup\$ Yes. You can output them simultaneously even when the data lines aren't connected to sequential pins, as long as all data lines share the same I/O register. You can accomplish that simply by doing one write to the ouput register instead of eight. \$\endgroup\$
    – jms
    May 29, 2018 at 16:41
  • \$\begingroup\$ By I/O register you mean port? So I wouldn't need a buffer between the MCU and DAC? I've seen this being done when controlling a motor, but can't seem to figure out why it's necessary. \$\endgroup\$
    – 19172281
    May 29, 2018 at 16:45
  • \$\begingroup\$ Consider using DMA (you are basically limited only by the peripheral bus speed). \$\endgroup\$
    – filo
    May 29, 2018 at 18:57

2 Answers 2

1
\$\begingroup\$

SIDE QUESTION: I'll be using the output with a DAC to produce an analog signal. Will I need to use a buffer in between, or are the pins guaranteed to output simultaneously?

You don't need a buffer in between, if you update all bits with just a single write to the special memory location (also known as register) controlling that I/O port.

The code in your question would not write all bits at the same time, but this will:

void sendByte(char value)
{ 
    uint32_t temp = DataPort;
    //clear the pins connected to D0..D7
    temp &= ~((1 << LCD_D0)
            | (1 << LCD_D1)
            | (1 << LCD_D2)
            | (1 << LCD_D3)
            | (1 << LCD_D4)
            | (1 << LCD_D5)
            | (1 << LCD_D6)
            | (1 << LCD_D7));
    //Write the value to the pins
    temp |= (((value >> 0) & 1) << LCD_D0)
          | (((value >> 1) & 1) << LCD_D1)
          | (((value >> 2) & 1) << LCD_D2)
          | (((value >> 3) & 1) << LCD_D3)
          | (((value >> 4) & 1) << LCD_D4)
          | (((value >> 5) & 1) << LCD_D5)
          | (((value >> 6) & 1) << LCD_D6)
          | (((value >> 7) & 1) << LCD_D7);
    //apply the new value to the I/O register. All pins are updated at once.
    DataPort = temp;
}  

You could also make the code more efficient by connecting the data lines to I/O pins that are mapped to sequential bits in the I/O port registers (e.g. D0 to PORT_A_3, D1 to PORT_A_4 ... D7 to PORT_A_10)

Then you could simplify that function to just

#define LCD_D0 3
void sendByte(char value)
{ 
    DataPort = (DataPort & ~(0xFF << LCD_D0)) | (value & 0xFF) << LCD_D0);
}  

When it comes to the overhead MBED, I have no idea.

\$\endgroup\$
0
\$\begingroup\$

The mbed code seems to be this:

void port_write(port_t *obj, int value) {
    *obj->reg_out = (*obj->reg_in & ~obj->mask) | (value & obj->mask);
}

So it's not in this case using any bit-banding, and includes the overhead of read/modify/write. That makes it non-optimal for cases where you know the last written value.

By calculating the byte value directly, you save 7 writes, 8 reads, and the arithmetic operations. You also get synchronous updates of the whole byte at once, rather than changing each bit in sequence. This is rather critical for your DAC application, and even then you need to consider the jitter between individual lines.

You can probably use the mbed framework to set up the GPIO, and provide the register addresses (making your code a little more portable).

\$\endgroup\$
5
  • 1
    \$\begingroup\$ I agree, but keep in mind that bit banding is only a feature on the higher-end ARM cores (cortex-M3 and cortex-M4). \$\endgroup\$
    – jms
    May 29, 2018 at 18:47
  • \$\begingroup\$ In the CPU, maybe, but some vendors implement it in the system (or peripheral). \$\endgroup\$ May 29, 2018 at 18:50
  • \$\begingroup\$ "That makes it non-optimal for cases where you know the last written value." - What do you mean by the last written value? I would have though that it doesn't matter if you're rewriting the entire port. \$\endgroup\$
    – 19172281
    May 30, 2018 at 9:37
  • \$\begingroup\$ Transforming a register value is 1 cycle/1 instruction. Reading a value from a peripheral and transforming it is 2 instructions, and more than 3 cycles. Reads (or volatile variables as they are seen in C) are expensive \$\endgroup\$ May 30, 2018 at 9:40
  • \$\begingroup\$ The mbed code is always read/modify/write - that is precisely why it is 'slow' \$\endgroup\$ May 30, 2018 at 9:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.