Port manipulation Arduino Uno

Could someone please have a look at the following code, and confirm if I've written the port manipulation equal to the digitalWrite format?

Thank you!
Dave

  DDRD = DDRD | B11111100;  //  sets pins 2 to 7 as outputs
	                  // without changing value of pins 0 & 1, which are RX & TX 
  PORTD = PORTD & ~B00000000;// set pins 4&7 LOW
  delayMicroseconds(2);
  PORTD = B10010000; // sets pins 7,4 HIGH
  delayMicroseconds(5);
  PORTD = PORTD & ~B00000000;// set pins 4&7 LOW
  
  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
 
  DDRD = DDRD &~ B11111100; // //  sets pins 2 to 7 as inputs*/
  pinMode(pingPin1, OUTPUT);
  pinMode(pingPin2, OUTPUT);
  digitalWrite(pingPin1, LOW);
  digitalWrite(pingPin2, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin1, HIGH);
  digitalWrite(pingPin2, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin1, LOW);
  digitalWrite(pingPin2, LOW);
        
  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin1, INPUT);
  pinMode(pingPin2, INPUT);

You are failing to set the pins low - the same bit mask is required in the "and-not"
lines as in the "or" statements. A bitmask of zero does nothing.

BTW you have to be careful with direct port manipulation like this if using
interrupt routines that write to the same ports.... You

this

PORTD &= ~(_BV(PD4)|_BV(PD7));

instead of this

PORTD = PORTD & ~B00000000;// set pins 4&7 LOW

PORTD = PORTD & ~B00000000;// set pins 4&7 LOW

PORTD = PORTD & B01101111; // 4 & 7 low, rest untouched

and
PORTD = B10010000; // sets pins 7,4 HIGH

PORTD = PORTD | B10010000; // sets pins 7,4 HIGH, rest untouched

Once pins are outputs, can also write to the PINx register to toggle the bit:

PIND = 0b1001000; // toggle bits 4 & 7 only by writing a 1 to the input register:

"Three I/O memory address locations are allocated for each port, one each for the Data Register – PORTx, Data Direction Register – DDRx, and the Port Input Pins – PINx. The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register, will result in a toggle in the corresponding bit in the Data Register."

Hello - I hope I'm not hijacking this thread, but what I want to do seems very similar. In my case, digital pins 10,11,12 and 13 correspond to the 4 bit data lines on my OLED. Here's the original library LiquidCrystal code that works:

void OLEDFourBit::write4bits(uint8_t value) {
	for (int i = 0; i < 4; i++) {
                pinMode(_data_pins[i], OUTPUT);
		digitalWrite(_data_pins[i], (value >> i) & 0x01);
	}	
[...]

Here's my feeble and failed attempt to make it faster - as I understand it, digitalWrite is slower than port manipulation.
So if I multiply whatever delay that adds times the 4 loops for the 4 bits, then hopefully, this should mitigate the slight "tearing" effect between rows 1 and 2 as I scroll the graphics - but either way, faster plotting is a good start!)

void OLEDFourBit::write4bits(uint8_t value) {
	DDRB = DDRB | B00111100; // Leave pins 8,9,14,15 alone, set 10-13 as output
        PORTB = value << 2;  // shift 'value' two to left so its bits affect 10-15 and dump it on pins 10-13
[...]
}

As I understand the docs on bitwise and, in the working code, taking the line

digitalWrite(_data_pins[i], (value >> i) & 0x01);

the & 0x01 just takes the lowest bit on each iteration of the loop and writes it onto that pin(?).

Clearly I've failed to understand it!

Any guidance would be greatly appreciated :slight_smile:

The piece of code

digitalWrite(_data_pins[i], (value >> i) & 0x01);

seems to shift a value to the right one bit at a time and write the rightmost bit to a different pin on each iteration.

It's not a good idea to replace all of the bits on a port because some of the bits have special uses. Just write the bits you need to write using OR (to set them) or AND (to clear them) (hope I have that the right way round).

Have you checked that you are using the correct bits in PortB for the Arduino pins you want to write to?

...R

Are you changing the IO direction during the sketch?
If not, just user regular pinMode command in setup for these
DDRB = DDRB | B00111100; // Leave pins 8,9,14,15 alone, set 10-13 as output

and then use
PORTD = PORTD | 0b00111100; // to set the 4 bits high.
and
PORTD = PORTD & 0b11000011; // to clear the 4 bits low.

If you want to manipulate those bits seperately,
dataBits = 0b00xxxx00;
then you could clear the 4 bits in PORTD and OR your data in:
PORTD = PORTD & 0b11000011; // to clear the 4 bits low.
PORTD = PORTD | dataBits;

Hello both - apologies, I didn't see the notification of a reply. Anyway, between then an now, I appear to have got it going...

Robin2:
It's not a good idea to replace all of the bits on a port because some of the bits have special uses.

OK, I'm intrigued. I understand what you're saying if you meant just "be careful you're not turning the wrong pins on", but If there's nothing connected to a port, what "special uses" do these bits have?

Robin2:
Have you checked that you are using the correct bits in PortB for the Arduino pins you want to write to?

Yup! Just to be sure, I wired 6 LEDs on 8-13 and ran the following:

void setup() {
  // put your setup code here, to run once:
        DDRB = DDRB | B00111100; // Leave pins 8,9,14,15 alone, set 10-13 as output
        byte i;
        for (i = 0; i < 16 ; i++)
       {
        PORTB = i << 2;  // shift 'value' two to left so its bits affect 10-15 and dump it on pins 10-13
delay(200);
}

And it "counted" (in binary) just the way I was expecting/had hoped.

So, I've now turned this:

void OLEDFourBit::write4bits(uint8_t value) {
	for (int i = 0; i < 4; i++) {
		pinMode(_data_pins[i], OUTPUT);
		digitalWrite(_data_pins[i], (value >> i) & 0x01);
	}
	delayMicroseconds(1); // data setup/hold = 40/20 nanoseconds
	pulseEnable();
	for (int i = 0; i < 5; i++) {
	pinMode(_data_pins[i], OUTPUT);
	digitalWrite(_data_pins[i], LOW);
	} 
}

into this

void OLEDFourBit::write4bits(uint8_t value) {
	DDRB = DDRB | B00111100;
	PORTB = value << 2;
	delayMicroseconds(1); // data setup/hold = 40/20 nanoseconds
	pulseEnable();
	PORTB = B00000000; // set pins 8 to 13 (data) to zero
}

EDIT: Actually, I just read that each Arduino instruction takes at least 65ns, so as the data setup/hold time is 40ns, I tried removing that line, and it still works just fine. Reasonably safe thing to do?

I'm not exactly sure what I did difference, apart from start from scratch with a fresh copy of the original library.

Anyway, thanks for your advice. I'm quite chuffed because not only is this the first bit of "low level" stuff I've done, but it really had increased the write speed by over 2x, which makes all the difference when you're writing 400 bytes at a time and trying to smooth-scroll across a 100 column display. Or to put it another way, I now have fast, smooth, tearing-free scrolls thanks to port manipulation :slight_smile:

CAVEAT AND ACKNOWLEDGEMENT: Yes, I KNOW it makes my code entirely un-portable, and I've read all the warnings, but I think in this case, it really is in the league of "time matters" :slight_smile:

delayMicroseconds(1); // data setup/hold = 40/20 nanoseconds

I believe the info page also says this is only reliable down to 3-4uS.

This:
pulseEnable();
is calling some other function? That adds time too.
Just toggle the bit in question. Say it was on PORTB, bit 2, then:
PINB = 0b00000100; // flip bit 2
PINB = 0b00000100; // flip bit 2 back

and then you've got the whole outer function thing:
void OLEDFourBit::write4bits(uint8_t value)
which takes time to call and return.

If you really want speed, inline whole thing and get rid of the functions.
Is your code soooooooo big that you really need to save a few bytes making all those functions? Probably not.

Robin2:
The piece of code

digitalWrite(_data_pins[i], (value >> i) & 0x01);

seems to shift a value to the right one bit at a time and write the rightmost bit to a different pin on each iteration.

not exactly : it shifts value by i bits .
When i=0 -> no shift
when i = 1 -> shifts one bit
etc....

@alnath, you are perfectly correct. And I think my overall description was reasonable even though it skated over some of the detail (which I shouldn't have done).

...R

Ooof! I thought I was starting to get the hang of all this, but I'm starting to question my learning now after reading your post.

I'm still a bit green, so let me take it nice and slow, bit by bit - bear with me!

CrossRoads:

delayMicroseconds(1); // data setup/hold = 40/20 nanoseconds

I believe the info page also says this is only reliable down to 3-4uS.

I was just getting my info from AVRfreaks forum where I read:

The fastest xmega runs at 32 MHZ, which means that the shortest delay possible (one NOP) is 31.25ns.

Therefore, I was assuming that my 16Mhz Pro Mini would a minimum "one NOP" clock cycle of around 64ns.

But in any case, if the data setup/hold time is 40/20 nanoseconds, that's a total of 60 nanoseconds, there are 1,000 nS in a uS, so even if "it" (whatever "it is") is 3-4us, isn't that still around 66 times longer than my minimum 64ns, and therefore "safe"?

CrossRoads:
Just toggle the bit in question. Say it was on PORTB, bit 2, then:
PINB = 0b00000100; // flip bit 2
PINB = 0b00000100; // flip bit 2 back

Uh oh! Again, I've gone from clarity back to confusion! From the docs:

PINx is used to READ the logic state of a port (x = A,B,...)
PORTx is used to set (OUTPUT) the logical output state of a port.
DDRx is used to control how the port behaves as an output (or input).

or

DDRB - The Port B Data Direction Register - read/write
PORTB - The Port B Data Register - read/write
PINB - The Port B Input Pins Register - read only

If PINC is readonly, how can you set it? I'm going to assume that might have been a typo(?) but even if you meant PORTB, how does setting it twice to be the same thing "flip" it? Wouldn't you need a & or an | in there somewhere?

PINB = 0b00000100;

Ah. I didn't know you could do that - the docs on integer constants only say

Binary is base two. Only characters 0 and 1 are valid.
Example:
B101 // same as 5 decimal ((1 * 2^2) + (0 * 2^1) + 1)
The binary formatter only works on bytes (8 bits) between 0 (B0) and 255 (B11111111).
myInt = (B11001100 * 256) + B10101010; // B11001100 is the high byte

and I can't find this alternative define listed in the binary constants header file.

But your method works - I'm guessing the compiler actually takes care of this? AND it's MUCH easier for me as it fits better with the 0x notation of hex, and google calc uses the 0b notation too, so, much easier if I'm just checking something there. Thanks!

CrossRoads:
This:
pulseEnable();
is calling some other function? That adds time too.

and then you've got the whole outer function thing:
void OLEDFourBit::write4bits(uint8_t value)
which takes time to call and return.

If you really want speed, inline whole thing and get rid of the functions.

Pretty much since I've been first learning about this, most of what I've read (rightly or wrongly) has been about making the code modular and understandable as possible, use the single responsibility principle, and let the compiler decide on whether to inline or not.

CrossRoads:
Is your code soooooooo big that you really need to save a few bytes making all those functions? Probably not.

Well, the LiquidCrystal library I'm using which comes with the Arduino IDE was written by MIT's David Mellis, (one of the Arduino founders), and Limor Fried of Adafruit, so I was trusting their judgement on whether they made them into functions or not :slight_smile:

delayMicroseconds(1) is not at all the same as a single NOP instruction. You can put assembler instructions into your code if you need that level of control.

The use of PINB as recommended to flip a bit by @CrossRoads seems to be a special use of the instruction - the bible in all such cases is the Atmel datasheet for the MCU you are using.

...R

From 14.1 for '328P:
Three I/O memory address locations are allocated for each port, one each for the Data Register – PORTx, Data Direction Register – DDRx, and the Port Input Pins – PINx. The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register,
will result in a toggle in the corresponding bit in the Data Register.

(I added the bolding, Nick Gammon pointed this out to me originally).

And from the Referecence page on delayMicroseconds:

Caveats and Known Issues
This function works very accurately in the range 3 microseconds and up. We cannot assure that delayMicroseconds will perform precisely for smaller delay-times.

Re: the compiler knowing whether to inline stuff or not: I don't know when it does.
I do know it's not smart enough to inline for:loops, for example, so I don't trust it to inline anything else either. For example, this:

digitalWrite(csPin, LOW);
for (x=0; x<10; x=x+1){
SPI.transfer(dataByte[x]);
}
digitalWrite (csPin, HIGH);

takes much longer to run than:

digitalWrite(csPin, LOW);
SPI.transfer(dataByte[0]);
SPI.transfer(dataByte[1]);
SPI.transfer(dataByte[2]);
SPI.transfer(dataByte[3]);
SPI.transfer(dataByte[4]);
SPI.transfer(dataByte[5]);
SPI.transfer(dataByte[6]);
SPI.transfer(dataByte[7]);
SPI.transfer(dataByte[8]);
SPI.transfer(dataByte[9]);
digitalWrite (csPin, HIGH);

because the for:loop adds many uS to jump back & execute.

I had forum issues copy/pasting from the reference page to get this far, had to restart my PC and then log in with a bunch of failed attempts, so I kind of lost my train of thought here.

CrossRoads:
The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register will result in a toggle in the corresponding bit in the Data Register.[/quote]
You are, of course, absolutely right, and I do apologise for even suggesting it might have been a typo :fearful:
This flashes as expected - or rather, as not expected by me until now!
> void setup() {
> DDRB = DDRB | 0b00001100;
> }
>
> void loop() {
> PINB = 0b00001100;
> delay(500);
> PINB = 0b00001100;
> delay(500);
> PORTB = 0b00000000;
> delay(500);
> PORTB = 0b00001100;
> delay(500);
> }
I hope you can understand why I was confused though, given the "read only" part of the docs. Copying and pasting that quote into Google revealed a fair amount of people as confused as myself. One person on AVRfreaks says:
> if DDRx is set, PINx will follow PORTx unless the outside world is forcing the PIN otherwise.
Does that mean something external has the ability to "flip" that bit with a pulse? Does that mean a read only isn't a read only and an external trigger can flip a state? Can't even quite get my head round that! That's one I'm going to have to pursue with more googling as a learning exercise. Along with the full datasheet and several giant mugs of tea.
OK, so let me make sure I've understood this:
PINB will flip whatever the last PINB state was. PORTB will set it to a known absolute state.
Is there any speed advantage to PINB for my case? I think, for ease of looking back in the future, for my particular case I'll stick to
** **  PORTB = 0b00000000;  PORTB = 0b00001100;** **
rather than
** **  PINB = 0b00001100;  PINB = 0b00001100;** **
purely for the fact that I'd prefer "known" states. Persuade me otherwise!
(Another thing I learnt is that if you're doing anything involving PORTC, you'll need to explicitly set pinmode as output otherwise the internal resistors kick in, whereas just DDRX is OK for ports B and C).
Anyway, yet again, thanks for teaching me something new, and apologies or doubting you!
Later this week I'll make a simplified version of that OLED library, and run two tests bouncing a square back and forth 20 times and compare the original "pin by pin digitalwrite" version with a version optimized with all your tips. I'm pretty sure the latter version is going to fly along nicely!

Like you, I figure PINB = whatever; is not really known, so I do things like this:
PORTB = 0b11111011; // clear bit 2, leave the others as is
PORTB = 0b00000100; // set bit2, leave the others as is

Not quite the absolute fastest (PINB= is); but way faster than digitalWrite.
And I don't have to remember what all the << commands do, or what the EXOR command is.

I thought this would have to be

PORTB = PORTB & 0b11111011; // clear  bit 2, leave the others as is
PORTB = PORTB |  0b00000100; // set bit2, leave the others as is

????

...R

CrossRoads:
Like you, I figure PINB = whatever; is not really known, so I do things like this:
PORTB = 0b11111011; // clear bit 2, leave the others as is
PORTB = 0b00000100; // set bit2, leave the others as is

Not quite the absolute fastest (PINB= is); but way faster than digitalWrite.
And I don't have to remember what all the << commands do, or what the EXOR command is.

Yes, you are correct Robin2. That's what I meant, but did not manage to type :blush: