Нет универсального c способа. Это зависит от того, какой компилятор вы используете. В дальнейшем я приму avr-g++
, потому что он распространен и находится в свободном доступе.
Спойлер: На AVR это сложнее, чем на большинстве других машин.
Предположим, у вас действительно есть uint32_t
адрес, который будет байтовым адресом. Функциональные указатели в avr-g++
на самом деле являются адресами слов, где слово имеет 16 битов. Следовательно, вам нужно сначала разделить адрес байта на 2, чтобы получить адрес слова; затем приведите его к указателю на функцию и вызовите его:
#include <stdint.h>
typedef void (*func_t)(void);
void callFunctionAt (uint32_t byte_address)
{
func_t func = (func_t) (byte_address >> 1);
func();
}
Если вы начали с адреса слова, вы можете вызывать его без лишних слов:
void callFunctionAt (uint32_t address)
{
((func_t) word_address)();
}
Это будет работать только на устройствах с объемом памяти до 128 КБ fla sh! Причина в том, что адреса в avr-g++
имеют длину 16 бит, ср. макет void*
согласно avr-g cc ABI . Это означает, что использование скалярных адресов на устройствах с fla sh> 128 КБ вообще не будет работать , например, когда вы запускаете callFunctionAt (0x30000)
на ATmega2560.
На таких устройствах 16 -битный адрес в регистре Z
, используемый инструкцией EICALL
, расширяется на значение, содержащееся в регистре специальной функции EIND
, и вы не должны изменять EIND
после ввода main
. В документации avr-g ++ об этом ясно сказано .
Важным моментом здесь является то, как вы получаете адрес. Во-первых, чтобы правильно вызвать и передать его, используйте указатель на функцию:
typedef void (*func_t)(void);
void callFunctionAt (func_t address)
{
address();
}
void func (void);
void call_func()
{
func_t addr = func;
callFunctionAt (addr);
}
Я использую аргумент void
в объявлении, потому что именно так вы и поступите в C. Или, если вам не нравится typedef:
void callFunctionAt (void (*address)(void))
{
address();
}
void func (void);
void call_func ()
{
void (*addr)(void) = func;
callFunctionAt (addr);
}
Если вы хотите вызвать функцию по указанному адресу c слова, например, 0x0
для «сброса» 1 µ C, вы могли бы
void call_0x0()
{
callFunctionAt ((func_t) 0x0);
}
, но будет ли это работать, зависит от того, где находится ваша векторная таблица, или, более конкретно, от того, как EIND
был инициализирован кодом запуска. То, что всегда будет работать, - это использование символа и определение его с помощью -Wl,--defsym,func=0
при связывании со следующим кодом:
extern "C" void func();
void call_func ()
{
void (*addr)(void) = func;
callFunctionAt (addr);
}
Большая разница по сравнению с использованием 0x0
непосредственно в том, что компилятор будет переносить символ func
с модификатором символа gs
, который он не будет делать при непосредственном использовании 0x0
:
_Z9call_funcv:
ldi r24,lo8(gs(func))
ldi r25,hi8(gs(func))
jmp _Z14callFunctionAtPFvvE
Это необходимо, если адрес выходит за рамки EIJMP
, чтобы рекомендовать компоновщику сгенерировать заглушка.
1 Это не приведет к сбросу аппаратного обеспечения. Лучший подход к принудительному сбросу - позволить сторожевому таймеру (WDT) выполнить сброс для вас.
Methods
Еще одна ситуация, когда вам нужен адрес не статичного c метода класса, потому что вам также нужен указатель this
в этом случае:
class A
{
int a = 1;
public:
int method1 () { return a += 1; }
int method2 () { return a += 2; }
};
void callFunctionAt (A *b, int (A::*f)())
{
A a;
(a.*f)();
(b->*f)();
}
void call_method ()
{
A a;
callFunctionAt (&a, &A::method1);
callFunctionAt (&a, &A::method2);
}
2-й аргумент callFunctionAt
указывает, какой метод (из данного прототипа) вы хотите, но вам также нужен объект (или указатель на него) для его применения. avr-g++
будет использовать gs
при получении адреса метода (при условии, что следующие вызовы не могут быть встроены), поэтому он также будет работать для всех устройств AVR.