MCUdude / MicroCore

A light-weight Arduino hardware package for ATtiny13

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

badArg("Digital pin must be a constant")

TheSpade69 opened this issue · comments

I'm writing a short scetch for an analog dice and i can't get it to compile:

uint8_t x;
void setup() {
}
void loop() {
  if(digitalRead(4)){       //rol the dice with a pushbutton
    x = random(5);
    
    digitalWrite(0,(x%2));  //1st bit LED
      x = ((x-(x%2))/2);
    digitalWrite(1,(x%2));
      x = ((x-(x%2))/2);
    digitalWrite(2,(x%2));
      x = ((x-(x%2))/2);
      
    delay(5000);            //LEDs stay on for 5sec
    
    digitalWrite(0,0);      //all LEDs off
    digitalWrite(1,0);
    digitalWrite(2,0);
  }
}

The Linker says:

In function 'check_valid_digital_pin'
inlined from 'digitalWrite' at ...MicroCore\hardware\avr\2.0.1\cores\microcore\wiring_digital.c:32:3:
                                              ...MicroCore\hardware\avr\2.0.1\cores\microcore\Arduino.h:134:7:
                                              error: call to 'badArg' declared with attribute error: badArg("Digital pin must be a constant");
commented

Just for reference, here is the code, but a little more tidy:

const uint8_t led0 = 0;
const uint8_t led1 = 1;
const uint8_t led2 = 2;
const uint8_t button = 4;

void setup()
{
  pinMode(led0, OUTPUT);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(button, INPUT);
}
void loop()
{
  static uint8_t x = 0;
  
  if(digitalRead(button)) //roll the dice with a pushbutton
  {       
    x = random(5);
    
    digitalWrite(led0, (x%2));  //1st bit LED
      x = ((x-(x%2))/2);
    digitalWrite(led1, (x%2));
      x = ((x-(x%2))/2);
    digitalWrite(led2, (x%2));
      x = ((x-(x%2))/2);
      
    delay(5000);            //LEDs stay on for 5sec
    
    digitalWrite(led0, 0);      //all LEDs off
    digitalWrite(led1, 0);
    digitalWrite(led2, 0);
  }
}

@nerdralph I'm able to reproduce this issue. It looks very much like #118. But since digitalWrite() compiles down to a single instruction, inlining this isn't a problem, right?

commented

The problem is that I haven't been able to figure out how to inline digitalWrite without the compiler complaining by leaving warnings or errors.

EDIT: I was able inline digitalWrite (and other functions that uses check_valid_digital_pin(), but is it not possible to utilize check_valid_digital_pin() in a function without it being inline @nerdralph?

In order to compile to a single sbi or cbi instruction, both the pin and the state (HIGH or LOW) must be known at compile time. In the given example the pin is known, but the state is not.
To allow the state to be determined at runtime while maintaining the compile-time pin checking would require require some thought.

commented

In order to compile to a single sbi or cbi instruction, both the pin and the state (HIGH or LOW) must be known at compile time. In the given example the pin is known, but the state is not.

In many situations, it's not necessarily possible to know the pin state at compile time.

To allow the state to be determined at runtime while maintaining the compile-time pin checking would require some thought.

Do you have any idea how this could be achieved in an elegant way without throwing warnings or errors?

To allow the state to be determined at runtime while maintaining the compile-time pin checking would require some thought.

Do you have any idea how this could be achieved in an elegant way without throwing warnings or errors?

I think it can be done with a wrapper function or macro. In the wrapper, use __builtin_constant_p to check if the pin is a compile-time constant. If it is, verify that it is in range, and then modify the pin state. If not, call an internal function that doesn't do the range checks.

commented

I think it can be done with a wrapper function or macro. In the wrapper, use __builtin_constant_p to check if the pin is a compile-time constant. If it is, verify that it is in range, and then modify the pin state. If not, call an internal function that doesn't do the range checks.

Thanks! I have an example I can use here.

But this basically means that check_valid_digital_pin() can't be used in a function that isn't inlined (with inline + attribute always_inline), right? For instance, the function pulseIn() is way too big to inline, so this means that I can't check if the pinout pin is valid or not without inlining it, because the compiler will throw an error if it is used three or more times.

I'll create a PR in this repo when I have something I believe works. Since the flash memory is so limited on the T13, size is usually more important than execution speed.

Hey Guys
I'm by no means a professional or anywhere close to your level of programming but as far as i can tell you're on it xD. If i can contribute in any way let me know.

commented

Hey Guys
I'm by no means a professional or anywhere close to your level of programming but as far as i can tell you're on it xD. If i can contribute in any way let me know.

No worries! I'm just trying to figure out what the best solution might be and ask a few questions to @nerdralph, which is the real guru here.

Okay
just wanted to let you know, i'm here and thankfull for every bit of help i can get :)

commented

Okay
just wanted to let you know, i'm here and thankfull for every bit of help i can get :)

While I/we are figuring this out, you can use the v1.0.7 previous version of MicroCore, which as none of these issues 👍

I think it can be done with a wrapper function or macro. In the wrapper, use __builtin_constant_p to check if the pin is a compile-time constant. If it is, verify that it is in range, and then modify the pin state. If not, call an internal function that doesn't do the range checks.

Thanks! I have an example I can use here.

But this basically means that check_valid_digital_pin() can't be used in a function that isn't inlined (with inline + attribute always_inline), right? For instance, the function pulseIn() is way too big to inline, so this means that I can't check if the pinout pin is valid or not without inlining it, because the compiler will throw an error if it is used three or more times.

I'll create a PR in this repo when I have something I believe works. Since the flash memory is so limited on the T13, size is usually more important than execution speed.

I'm not suggesting inlining everything, I'm suggesting a wrapper like your digitalWriteFast that calls the non-inline version if the pin and value isn't constant.

I've noticed that a number of Arduino libraries are so poorly designed, wrapping pin assignments in a class constructor, that even though the pin is a compile-time constant, lto doesn't figure that out. To get digitalWrite to still peform reasonably fast in such cases, I may have to implement it (the non-inline version) in assembler.

commented

I think I'll remove the compile-time checks altogether. In the example below, the compiler "creates" two separate digitalWrite functions. One where both parameters are known, and one where the pin state isn't known.
The compiler is able to figure out which function calls are known at compile-time, and which are not. The user won't get notified if one or more parameters aren't known at compile-time, but on the other side, it won't be a pain to maintain the code. I'm

void setup() {
  pinMode(0, INPUT);
  pinMode(2, OUTPUT);
}


void loop() {
  digitalWrite(2, HIGH); // Increases the compiled size by 2 bytes
  delay(1000); 
  digitalWrite(2, LOW);  // Increases the compiled size by 2 bytes
  delay(1000);                    
  digitalWrite(2, digitalRead(0)); // Increases the compiled size by 10 bytes
  delay(1000);
}

Current digitalRead implementation:

void digitalWrite(uint8_t pin, uint8_t val)
{
if(val)
PORTB |= _BV(pin); // Set pin high
else
PORTB &= ~_BV(pin); // Set pin low
}

commented

@TheSpade69 MicroCore v2.0.2 is now available through boards manager. This should have the issues you faced fixed 🙂