I used static member in class and reference to assembly, in sperate files I got error, Is there anyway to solve it?

the original file is like
OkTest.ino (856 位元組)

#include "arduino.h"

class MyClass {

    public:
        MyClass();
        ~MyClass();
        void setVariable(int);
        static int MyVariable;
};

int __attribute__((used)) MyClass::MyVariable __asm__("MyVariable");

MyClass::MyClass(){
    MyVariable = 0;
};
MyClass::~MyClass(){
};

void MyClass::setVariable(int input){
    register uint8_t tmp;
    __asm__ __volatile__(
        "mov %0, %A1\n\t" \
        "sts MyVariable, %0 \n\t" \
        "mov %0, %B1\n\t" \
        "sts MyVariable+1, %0\n\t" \
        :
        :"r" (tmp), "r" (input) 
        :
    );
};

MyClass check;

void setup() {
    Serial.begin(9600);
    Serial.print("check before setVariable = "); Serial.println(check.MyVariable);
    check.setVariable(1000);
    Serial.print("check variable MyClass.MyVariable = "); Serial.println(check.MyVariable);
}
void loop() {
}

It works fine...

but when I separate them into individually file like this
ErrorTest.ino (283 位元組)

#include "MyClass.h"

void setup() {
    Serial.begin(9600);
    Serial.print("check before setVariable = "); Serial.println(check.MyVariable);
    check.setVariable(1000);
    Serial.print("check variable MyClass.MyVariable = "); Serial.println(check.MyVariable);}

void loop() {
}

MyClass.cpp (458 位元組)

#include "MyClass.h"

int __attribute__((used)) MyClass::MyVariable __asm__("MyVariable");

MyClass::MyClass(){
    MyVariable = 0;
};
MyClass::~MyClass(){
};

void MyClass::setVariable(int input){
    register uint8_t tmp;
    __asm__ __volatile__(
        "mov %0, %A1\n\t" \
        "sts MyVariable, %0 \n\t" \
        "mov %0, %B1\n\t" \
        "sts MyVariable+1, %0\n\t" \
        :
        :"r" (tmp), "r" (input) 
        :
    );
};

MyClass check;

MyClass.h (237 位元組)

#ifndef MyClass_H
#define MyClass_H
#include "arduino.h"

class MyClass {

    public:
        MyClass();
        ~MyClass();
        void setVariable(int);
        static int MyVariable;
};


extern MyClass check;    
    
#endif
     

then I got the error message
Arduino: 1.8.13 (Windows 10), Board: "Arduino Nano, ATmega328P (Old Bootloader)"
C:\Users\Tracker\AppData\Local\Temp\cckoPtOv.ltrans0.ltrans.o: In function setup': D:\Working\ErrorTest/ErrorTest.ino:5: undefined reference to MyClass::MyVariable'

D:\Working\ErrorTest/ErrorTest.ino:5: undefined reference to `MyClass::MyVariable'

D:\Working\ErrorTest/ErrorTest.ino:7: undefined reference to `MyClass::MyVariable'

D:\Working\ErrorTest/ErrorTest.ino:7: undefined reference to `MyClass::MyVariable'

collect2.exe: error: ld returned 1 exit status

exit status 1

Error compiling for board Arduino Nano.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

You need to initialize the static member in the .cpp file. Add this line at the top:

int MyClass::MyVariable = 0;

and see if that makes a difference.

1 Like

Thanks for your recommendation~
but it comes new error message
redefinition of 'int MyClass::MyVariable'
or what you mean is to leave this static member in global environment?

and I found that , the public member can be private or protected, if assign as public, it will be error as I try to.
new I have two way to do,

  1. put static member to private, and add function call in public to access that static member
  2. put static member to private, then add a variable pointer in public, in this case main program can access the static member in data pointer way..
    I just wandering is anyway to keep static member public in sperate file?

check something like this

if you want to use assembly it's more tricky, you'll probably have to keep a handle to the address of the class variable to be able to refer to it

1 Like

thanks for your point, I see, it's work for this program without reference to assembly ~
(and I misunderstanding that the static member can be public, and I find once I label it in assembly, the error occur, )
Is that possible pass MyVariable to assembly in MyClass::setVariable() like use asm("lds %0, MyVariable\n\t" "::"r" (tmp1):slight_smile: ?
because original I write assembly is for optimizing code purpose.. thanks

The assembly will struggle to find the variable you are referring to as its name is mangled by the C++ compiler

It would be easier if you had a global variable - I’m not sure what’s the purpose of the class there or the assembly language

1 Like

Thank you for your kindly help, the reason way to use individual file with class in assembly is because to optimize the performance and also keep program reusable, thanks for all your help, so far I'm using data pointer to do this, and work fine.. I put those file here in case someone needs it.
at least It's one solution
and maybe later we can find out better way to solve it .

thanks again.. to eveyone who helps me... :slight_smile:

the example files that I currently used, list as following...

ErrorTest.ino

#include "MyClass.h"

void setup() {
    Serial.begin(115200);
    Serial.print("check before setVariable = "); Serial.println(*check._MyVariable);
    check.setVariable(1000);
    Serial.print("check variable MyClass.MyVariable = "); Serial.println(*check._MyVariable);}

void loop() {
}

MyClass.h

#ifndef MyClass_H
#define MyClass_H
#include "arduino.h"

class MyClass {

    public:
        MyClass();
        ~MyClass();
        void setVariable(int);
        int *_MyVariable;
    private:
        static int  MyVariable;
};


extern MyClass check;    
    
#endif
     

MyClass.cpp

#include "MyClass.h"

int __attribute__((used)) MyClass::MyVariable __asm__("MyVariable");

MyClass::MyClass(){
    MyVariable = 0;
    _MyVariable = &MyVariable;
};
MyClass::~MyClass(){
};

void MyClass::setVariable(int input){

    register uint8_t tmp;
    __asm__ __volatile__(
        "mov %0, %A1\n\t" \
        "sts MyVariable, %0 \n\t" \
        "mov %0, %B1\n\t" \
        "sts MyVariable+1, %0\n\t" \
        ::"r" (tmp), "r" (input) :
    );
};

MyClass check;

Assembly is not really portable nor reusable easily and the compiler / optimizer will usually generate better code than you for most tasks…

1 Like

Thank you!

I can't see the code where you tried to fix it so I can't help with that error.

thank you,

finally I try this as temporary solution, please take a look
(of cause it's not only a way allow me to do next job)

I don't understand the point of this solution. If you want public access to MyVariable then just make it public. Originally you wanted it to be static. Which means it needs to be initialized.

Here is an example of a class with a static member that does what I think you're trying to do. timerClaimed and isrRegistry are both static members.

class GPT_Stepper {

protected:
	enum Direction_t {
		D_CCW, D_CW
	};
	enum gpt_channel_t {
		CHANNEL_A, CHANNEL_B
	};
	friend void GPT0_ISR();
	friend void GPT1_ISR();
	friend void GPT2_ISR();
	friend void GPT3_ISR();
	friend void GPT4_ISR();
	friend void GPT5_ISR();
	friend void GPT6_ISR();
	friend void GPT7_ISR();
	static GPT_Stepper *isrRegistry[8];
	void internalISR();
	uint8_t eventLinkIndex;

private:

	R_GPT0_Type *timer;
	gpt_channel_t channel;
	static bool timerClaimed[8];

	uint8_t directionPin;
	uint8_t stepPin;
	bool invert;
	volatile long position;
	volatile float speed;
	float requestedSpeed;
	float acceleration;

	void setupStepPin(uint8_t port, uint8_t pin);
	void setupTimer();
	void setupInterrupt(uint8_t ch, void (*isr)());
	void setCurrentSpeed(float stepsPerSecond);
	void setPeriod(uint32_t us);
	float getNewSpeed();
	void setDirection(Direction_t direction);

	void stopTimer();
	void startTimer();
	bool timerRunning();

	uint16_t getDivider();
	uint32_t getTimerResolution();

public:
	GPT_Stepper(uint8_t spin, uint8_t dpin) :
			GPT_Stepper(spin, dpin, 100.0) {
	}
	GPT_Stepper(uint8_t spin, uint8_t dpin, float acc) :
			GPT_Stepper(spin, dpin, acc, false) {
	}
	GPT_Stepper(uint8_t spin, uint8_t dpin, float acc, bool inv) :
			directionPin(dpin), stepPin(spin), acceleration(acc), invert(inv) {
	}
	bool init();
	void setAcceleration(float stepsPerSecondPerSecond);
	void setSpeed(float stepsPerSecond);
	void stop();
	long getPosition();
	void setHome();
	float getCurrentSpeed();
	float getAcceleration();
	bool atSpeed();

	GPT_Stepper() = delete;
	GPT_Stepper(const GPT_Stepper&) = delete;
	GPT_Stepper& operator =(const GPT_Stepper&) = delete;

};

Here's the top part of the implementation.


#include "GPT_Stepper.h"

const uint32_t clock_uHz = (F_CPU / 1000000);

bool GPT_Stepper::timerClaimed[8];
GPT_Stepper *GPT_Stepper::isrRegistry[8];

Where both of those static members get put it. After that it's just function definitions. See the whole thing here: GitHub - delta-G/GPT_Stepper: Control stepper pulses using GPT timer on Arduino UNO R4

allow me explain the whole issue post by my poor English... :slight_smile:

in the beginning I define a class with some function using public variable in assembler, the way I used variable in assembly is directly reference to label in assembly domain (global) like

`int attribute((used)) MyVariable asm("MyVariable");

so that I can access in assembly like this

`asm ("lds %0 MyVariable")

in this way I can optimize performance better, so it must be static, and also needs to be access at main program as public

when I put those class with main program together in one file (OK.ino) it works very will, then I try to sperate then into three files, (just put one certain function at two file (cpp and h) without disturb main program, and also easy to maintain and read), the error comes

please see the original post in the top ErrorTest.ino and MyClass.cpp MyClass.h

It looks like the compiler not allow the variable be access by program in outside file, (the variable has defined as static , public , and labeling in assembly (global))

if I do not labeling in assembly (global area), then the external program can access then, but assembly will not be able to access, and comes MyVariable not defined message,

then I can use different way to make inline assembly transfer parameter in, like this

` asm("ld %0, %A[MyVariable]\n\t" "ld %1, %B[MyVariable]\n\t" :: "r" (tmp1), "r" (tmp2), [MyVariable] "+r" (MyVariable) : )

but it takes CPU time for me in my case, and also I found other issue in this way, it look like MyVariable will be changed by other issue (I don't know why, I will not discuss this issue here, it become more complicated)

so finally I use public data pointer to access static private variable, and it work fine,
It's not a good solution, and I don't know why the original issue happen, but
now I can go forward.. it just fine
that is all about this post..

thanks for your kindly help. It's great for me,

Identifiers have two properties that you need to explore ➜ scope and linkage.

Scope is where an identifier is visible.
Linkage is a way of making different declarations of an identifier refer to the same object. There are three types of linkage: external, internal, and none.

If an identifier has internal linkage, it is not linked with identifiers in other translation units, so it's not accessible.

If an identifier has external linkage, it can be accessed in another translation unit by declaring an identifier with the same name and also with external linkage. When the codes are linked together, identifiers with external linkage are resolved by the linker so that they refer to the same storage. I suspect that's what you need for your assembly code.


that being said, I still don't get the idea of

this is usually not true nowadays. Compliers and optimisers have ways to look at your code that you don't suspect :slight_smile:

nor the idea of having a class and a class variable... Why don't you just use a global variable that is not restricted to a specific class anyway ?

the whole thing feels like an XY problem to me.

thank you, I will look at that, I am just newbie, it's lot more thing to learn about, thank you again~
by the way, I just try that out, and guess what, It works, only I need to do is to name the variable in different name for assembly, so that it will not be the same name as public member, thank you~
20240121 update, my mistake, the problem still, I will find another way to do...
thanks ....

Why do you need to go to assembly level?

combine IO control directly in byte not in bit, it's take a better performance..
and others ..
original I used C it takes 100uS only only doing thing , then in assembly do I/O Time period stuff together, it takes only 10uS, it's very good for me in real time small system programming..

If you need indeed close time control on what’s going on then indeed it might be useful

It `s very unusual. As a rule, if the use of assembler gives an advantage, it is by percentage, and not by several times.
Could you give a comparative example of C code and assembly code?