Как сделать обновляемую прошивку для ARM в Keil μVision 5. Часть 3

Взаимодействие нескольких прошивок в рамках единого проекта

На функционирование прошивок необходимо наложить следующие условия :

- возможность раздельного обновления прошивок ( программных ядер )

- вызов функций одной прошивки из другой ( и наоборот ), несмотря на то, что адреса функций будут изменяться после перелинковок проекта

- использование общих для прошивок переменных

План такой :

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

Спасибо за внимание. Пишите вопросы, если что непонятно. Или поправляйте, если я где-то ошибся