Can anybody explain me why this simple Arduino sketch doesn't work as expected? - Page 1
Forum section: electronics / general

author: egirland
author_rank: Contributor
timestamp: February 07, 2022, 08:38:44 am
content: Arduino IDE 1.8.19.Platform: Arduino Micro and Arduino UNO (replacing PORTF with PORTD as UNO doesn't have PORTF..).This sketch should simply make one shot writes to the higher nibbles on PORTB and PORTF.The first writes work... the second nope (..and so the 3rd, the 4th, ...).void setup() {Serial.begin(115200);while (! Serial);DDRB = DDRB | 0b11110000; // high nibble as OUTDDRF = DDRF | 0b11110000; // high nibble as OUT// print initial PORTs stateSerial.print(F("PORTB,PORTF = "));Serial.print(PORTB,HEX);Serial.print(",");Serial.println(PORTF,HEX);//set PORTs to the desired valuesPORTB |= 0x30;PORTF |= 0x50;//verify: OKSerial.print(F("PORTB,PORTF = "));Serial.print(PORTB,HEX);Serial.print(",");Serial.println(PORTF,HEX);//set PORTs again with new valuesPORTB |= 0x40;PORTF |= 0x60;//verify: FAILS!!Serial.print(F("PORTB,PORTF = "));Serial.print(PORTB,HEX);Serial.print(",");Serial.println(PORTF,HEX);}void loop() {return;}The output is:PORTB,PORTF = 1,0PORTB,PORTF = 31,50PORTB,PORTF = 71,70Nothing changes if the code is run in loop().Thank you for your help!Emanuele.

author: Ian.M
author_rank: Super Contributor
timestamp: February 07, 2022, 08:55:40 am
content: What else do you expect? You are bitwise ORing your data value with the previous contents of the port register, and an OR operation on a register can only change existing '0' bits to '1' bits (or leave them unchanged).

author: egirland
author_rank: Contributor
timestamp: February 07, 2022, 09:09:31 am
content: Expected output is:PORTB,PORTF = 1,0PORTB,PORTF = 31,50PORTB,PORTF = 41,60 <<===

author: Nusa
author_rank: Super Contributor
timestamp: February 07, 2022, 09:18:58 am
content: Quote from: egirland on February 07, 2022, 09:09:31 amBitwise OR:0x31 | 0x40 = 0x71 (b00110001 | b01000000 = b01110001)0x50 | 0x60 = 0x70 (b01010000 | b01100000 = b01110000)

author: egirland
author_rank: Contributor
timestamp: February 07, 2022, 09:59:55 am
content: aaah!!The ones accumulate in the nibbles! Got it!To get what I wanted the new value assignments must look like this:```//set PORTs the desired valuesPORTB = (PORTB & 0x0F) | 0x30;PORTF = (PORTF & 0x0F) | 0x50;```Sorry for the noise!Thank you.

author: Ian.M
author_rank: Super Contributor
timestamp: February 07, 2022, 10:28:50 am
content: That's *EXACTLY* what you need to do if you only have a single thread of execution modifying the port. However it breaks badly if an ISR modifies the port during evaluation of the expression, after the state of the pins to preserve has been read and before the combined value has been written back to the port, by loosing the changes made by the ISR.You can get around that by XOR masking - basically build a map of bits to flip then XOR it with the port, which lets an ISR use other bits of the port outside your AND mask without any risk of loosing them if the interrupt hits in the middle of your expression. You could also simply disable interrupts for the duration of the statement, but in a large program that can get fugly if you do not know the prior interrupt enable state.Here's my approach to XOR masking with a macro to do the bit-twiddling:Usage:MASKIN(reg,mask,value);Define the macro as:Code:#define MASKIN(reg,mask,x) (reg^=(reg^(x))&(mask))Themaskindicates (by set bits) which bits inregto replace with the corresponding bit fromvalue.It is not just restricted to registers but can be used on variables as well. You can even use it on variables longer than a byte.It is thread-safe provided only one thread at a time writes to any specific bit or group of bits.Groups of bits that fit within the MCU's word size without straddling word boundaries will change simultaneously.

author: egirland
author_rank: Contributor
timestamp: February 07, 2022, 10:58:29 am
content: Wonderful!For my application I've carefully chosen the ports to use just to avoid conflicts with "Alternate functions" and bits used by the ISR routine attached to the encoder I use..As I need 8 lines for my actuators I had initially chosen to use PORTF only but, after implementing the code(!), I discovered that (on MICRO) pin17 (PORF-0) is defined but not usable because connected to the RX LED and Serial comms interferes... so I split the I/O lines as you see in my code.Sure enough you are a "bitwise" guru!Thank you very much for your notes and suggestions.Emanuele.

author: MarkF
author_rank: Super Contributor
timestamp: February 07, 2022, 12:23:41 pm
content: Code:void setup() {Serial.begin(115200);while (! Serial);DDRB = DDRB | 0b11110000; // high nibble as OUTDDRF = DDRF | 0b11110000; // high nibble as OUT// print initial PORTs stateSerial.print(F("PORTB,PORTF = "));Serial.print(PORTB,HEX);Serial.print(",");Serial.println(PORTF,HEX);//set PORTs to the desired valuesPORTB |= 0x30;PORTF |= 0x50;Very poor initialization habit to get intoto relay on the processor reset to set initial values.Being used or not. You know the definition of 'assume'? "You make an a$$ of you and me!"Just set the values WITHOUT or'ing with assumed values. Goes for ALL initial values.I've beenburnedbefore by someone who didn't initialize variables.Code:void setup() {Serial.begin(115200);while (! Serial);DDRB = 0b11110000; // high nibble as OUT, low nibble as IN <-------------------DDRF = 0b11110000; // high nibble as OUT, low nibble as IN <-------------------// print initial PORTs stateSerial.print(F("PORTB,PORTF = "));Serial.print(PORTB,HEX);Serial.print(",");Serial.println(PORTF,HEX);//set PORTs to the desired valuesPORTB = 0x30; <-------------------PORTF = 0x50; <-------------------

author: egirland
author_rank: Contributor
timestamp: February 07, 2022, 12:43:30 pm
content: "Very poor initialization habit to get into to relay on the processor reset to set initial values."...You are correct, I don't usually rely on processors reset values and always I initialize variables , possibly at declaration time.But my habit is to leave the unused stuff alone.In this case what I wanted was to leave the lower nibbles alone as I don't use them and some "alternate function" can be active on them (e.g. PORTF.0, pin 17 on MICRO, is used by the system for the RX LED).So my code now reads as follow:....DDRB = (DDRB & 0x0F) | 0b11110000; // sets bits PB4-PB7 (arduino pins D8-D11) as outputs while leaving the others aloneDDRF = (DDRF & 0x0F) | 0b11110000; // sets bits PF4-PF7 (arduino pins D21-D18) as outputs while leaving the others alone..//set PORTs the desired values (high nibble only)PORTB = (PORTB & 0x0F) | 0x30;PORTF = (PORTF & 0x0F) | 0x50;..Ciao.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 07, 2022, 02:20:27 pm
content: Quote from: MarkF on February 07, 2022, 12:23:41 pmJust not true. Reset values of registers are guaranteed by the manufacturer's IC design. There are many more register that are inaccessible. If you can't trust the reset, then you can't trust the CPU at all.This being said, in OP's case, this simple application benefits from always writing the registers fully - if nothing else, better performance and smaller code size, and smaller risk of the exact type of bug evidenced here.Sometimes you still want to read-modify-write the registers. Example would be a larger firmware, where modularity is used, i.e., each "driver" or code module does its own initialization. Sometimes they need to access different bits in the same register, and you don't want the code modules to depend on each other (or turn the other one off).

author: MarkF
author_rank: Super Contributor
timestamp: February 07, 2022, 02:39:28 pm
content: And if in another function you think you have 5 bits available and do this.......// using lower 5 bits of Port B (two input and three output)DDRB = (DDRB & 0b11100000) | 0b00000111;DDRF = (DDRF & 0b11100000) | 0b11100111;...// set PORTs the desired valuesPORTB = (PORTB & 0b11100000) | 0x03;PORTF = (PORTF & 0b11100000) | 0x05;// read the two bitstemp = (PORTB & 0b00011000) >> 3;What happens to your original function when adding this second one?I'd like to see the entire port direction bits set at one place for both functions.It just reduces the chance of errors.

author: mikerj
author_rank: Super Contributor
timestamp: February 07, 2022, 05:19:06 pm
content: Quote from: Siwastaja on February 07, 2022, 02:20:27 pmRegister values can survive across resets (apart from POR) on some devices, so it is true to some extent.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 07, 2022, 05:42:11 pm
content: Quote from: mikerj on February 07, 2022, 05:19:06 pmThis is all documented normally. There's no magic or handwaving in it. Usually reset values are explicitly given, and the reset tree shows which reset sources do full device reset.Anyway, it will be a very odd device, or special purpose registers that are obvious given the purpose. For example on AVR, there is at least one register that survives the reset cycle, namely the one which contains the cause for reset...Peripheral registers do reset, basically always. Give an example of otherwise if you know any. It would be a total coding nightmare, because there can be thousands of peripheral registers and setting them in the right state takes time, during which the MCU would misbehave. As a reset can be initiated by power glitches and register state is uncertain, that would require A LOT of initialization code.That would also mean there are two internal reset networks, the "actual" one for CPU core and other hidden registers, and another one for those exposed registers, because a digital IC simply does not work without reset mechanism and known register states, even to a point where you can start executing code. If you have ever written HDL, you know the drill with clock and reset networks, and reset signal generators.So my guess is, you remember just some very very esoteric special case - or more likely, don't know what you are talking about.Also the idea where this whole thing started from is completely nonsensical. No one suggests setting DDRJ to zero for example, if you use no pins from PORTJ. Assuming DDRB does not reset reliably just because you "use" it later, but DDRJ resets reliably because it's not accessed, is funny. If you don't trust the values, you need to manually set every single register in the device, and absolutely no one does that!