На функционирование прошивок необходимо наложить следующие условия :
- возможность раздельного обновления прошивок ( программных ядер )
- вызов функций одной прошивки из другой ( и наоборот ), несмотря на то, что адреса функций будут изменяться после перелинковок проекта
- использование общих для прошивок переменных
План такой :
1. Определяем структуру с адресами общих функций (векторами) и общими переменными
2. Определяем переменную типа структуры ( см. п. 1 ) с векторами и переменными в определённой области памяти, т.е. по абсолютному адресу, для простого доступа к переменным из любого ядра.
3. При старте ядра servcore надо заполнить вектора функций из него ( основного ядра servcore ), для корректного вызова их из других ядер.
4. Вызвать функцию ( с закреплённым адресом, неизменяемым при перелинковке ) из xxxxcore, которая заполнит вектора уже, в свою очередь, своих функций.
И далее вызывать функции одного ядра из другого, только через заполненные вектора, и использовать общие переменные тоже только через абсолютную адресацию.
Сейчас приведу исходные тексты, станет попонятнее ;-)
common.h
#pragma pack(push,1)
struct InterCoresInterface
{
int iCommonVariable1;
char *sCommonVariable2;
char strBuffer[1024];
char const* (*functionFromXxxxCore1)(int);
double (*functionFromXxxxCore2)(char*);
int (*functionFromXxxxCore3)(void);
int (*functionFromServiceCore1)(char*);
char const* (*functionFromServiceCore2)(int,int);
unsigned int (*strlen)(char const*);
int (*strcmp)(const char*, const char*);
int (*sprintf)(char* const, char const* const, ...);
};
#pragma pack(pop)
typedef struct InterCoresInterface InterCoresInterface_TypeDef;
Структура между "#pragma pack(puhs,1)" и "#pragma pack(pop)" не выравнивается по словам по итогам компиляции и линковки.
Это позволяет точно знать размер структуры и во-вторых экономит память, особенно, когда структуры объёмные.
main.c
// main.c ( ServCore )
#include <string.h>
#include <printf.h> // для printf(..)
#include "common.h"
#include "xxxxcore.h"
InterCoresInterface_TypeDef ici __attribute__((section(".ARM.__at_0x20000000")));
int sc_function1(char* s)
{
return atoi(s);
}
char buf2[256];
char const* sc_function2(int i1, int i2)
{
sprintf(buf2,"%d-%d",i1,i2);
return (char const*)(buf2);
}
void sc_initICI(void)
{
memset(&ici, 0, sizeof(struct InterCoresInterface)); // init to 0/NULL
ici.functionFromServiceCore1 = sc_function1;
ici.functionFromServiceCore2 = sc_function2;
ici.strlen = strlen;
ici.strcmp = strcmp;
ici.sprintf = sprintf;
xc_initICI(); // xxxxcore прописывает вектора своих функций
}
int main(void)
{
sc_initICI();
printf( "Test...\n" );
printf( "XxxxCore.functionFromXxxxCore1(1) : '%s'\n",ici.functionFromXxxxCore1(1) );
printf( "XxxxCore.functionFromXxxxCore2(\"3.1415926\") : %g\n",ici.functionFromXxxxCore2("3.1415926") );
printf( "XxxxCore.functionFromXxxxCore3() : %d\n",ici.functionFromXxxxCore3() );
while(1) ; return 0;
}
main вызывает sc_initICI(), там инициализируются вектора функций, в т.ч. и из прошивки xxxxcore.
потом вызывает функции из xxxx (которые, в свою очередь, вызывают функции ядра servcore ) через вектора функций в ici
xxxx.h
void xc_initICI(void) __attribute__((section(".ARM.__at_0x00080000"))); // c половины мегабайта начинается прошивка XxxxCore,
// и в самом начале именно эта ф-ция, её адрес меняться не должен
Здесь задаётся, что функция xc_initICI будет всегда располагаться с адреса 0x80000, другие же функции могут менять свои адреса после перекомпиляций
и перелинковок.
xxxx.c
// xxxx.c ( XxxxCore )
#include "common.h"
#include "xxxcore.h"
#define ici (*((InterCoresInterface_TypeDef*)0x20000000))
char const* xc_function1(int i)
{
ici.sprintf(ici.strBuffer,"Number %d",i); // ф-ция sprintf "лежит" в другом "ядре", безопасно вызвать её можно только так
return (char const*)(ici.strBuffer);
}
double xc_function2(char* s)
{
return ici.atof(s); // ф-ция atof "лежит" в другом "ядре", безопасно вызвать её можно только так
}
int xc_function3(void)
{
int num1 = ici.functionFromServiceCore1("100500");
return ici.strcmp( ici.functionFromServiceCore2(1,2),"3-4" )+num1;
}
void xc_initICI(void)
{
ici.functionFromXxxxCore1 = xc_function1;
ici.functionFromXxxxCore2 = xc_function2;
ici.functionFromXxxxCore3 = xc_function3;
}
Ядро xxxxcore не использует "стандартные" функции sprintf, atof, strcmp и т.д. так как неизвестно где они будут расположены при следующей сборке servcore.
Все вызовы идут через вектора функций в InterCoreInterface
Нюансы
1. Деление или умножение ( а также остаток от деления и, возможно, другие операции ) с double или float компилируется в функции, которые, по-умолчанию, располагаются в главном ядре.
Поэтому такие операции заменяйте и пишите "ici.double_div( d1,d2 )" вместо "d1 / d2".
2. Строковые константы. Keil, даже при отключенной оптимизации ( уровень 0 ), объединяет одинаковые строковые константные данные. Поэтому, чтобы они не перепутывались, в разных ядрах делайте так :
ядро 1 : printf("Привет '%s' !\n",privet );
ядро 2 : printf("Привет '%s' !\n\0",privet );
для Keil это уже будут разные строки.
Как настроить по адресации сам проект и файлы проекта смотрите здесь Часть 1
Спасибо за внимание. Пишите вопросы, если что непонятно. Или поправляйте, если я где-то ошибся
Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 3
Взаимодействие нескольких прошивок в рамках единого проекта
Микроконтроллеры