Контроль версии прошивки прямо на вашем MCS-51

По контролю версий исходников во время разработки есть много статей, и не только на нашем ресурсе. Но, когда прошивка уже начинает активную обкатку на железе, следить за тем, какая именно компиляция бинарника сидит в МК, а какая из архивных была собрана в прошлую пятницу, бывает весьма кстати. А если у вас намечается еще и неединичный экземпляр устройства, ну вы поняли.
Поэтому сегодня я расскажу о том, как сам решаю эту задачу, постараюсь привести внятный паттерн для быстрого встраивания в микропрограмму.
Происходить действо будет на МК Silabs, с использованием uVision 4 и соответственно Keil'овского компилятора Cx51.

1. Создание и хранение

Начинается вся свистопляска собственно с компилятора. Нам необходимо явно указать линкеру, чтобы определенные данные он поместил в нужной нам области памяти.
Смотрим гайд и в его глубинах находим страничку с пояснением:
Linker Location Controls
Окей, мы поняли, что линковщик поддается нашему ментальному манипулированию. Отложим пока что это в сторону и займемся непосредственно кодом.
Создаем несколько глобальных переменных, которые будут хранить информацию о версии. Я предпочитаю представлять эту информацию в виде «название проекта+дата+время», но вообще в этих переменных можно хранить и номер ревизии, который можно автоматом выдергивать из того же SVN с помощью ключевых слов ($Revision$), и просто вашу собственную идентификацию сборки.
Переменные представляют собой строки, то есть массива unsigned char'ов нужной длинны. Длина имени проекта остается на собственной совести, а вот «дата» и «время» — тут длина диктуется компилятором, об этом позже.
Итак GLB.H
typedef unsigned char U8;          //для удобства нотации

#define PRJ_TEXT_SIZE                  16
#define DATE_SIZE		    	8
#define TIME_SIZE		    	8 

extern U8 code Prj_Name[ PRJ_TEXT_SIZE ];
extern U8 code Prg_Date[ DATE_SIZE ];
extern U8 code Prg_Time[ TIME_SIZE ];

Переменные объявляем в кодовой области (квалификатор code) с внешним связыванием (спецификатор extern). При этом описывать каждую переменную необходимо в отдельных файлах.
PRJ_NAME.C
#include "glb.h"

U8 code Prj_34x_Name[ PRJ_TEXT_SIZE ] = "Prj:frmw_34x => ";

PRG_DATE.C
#include "glb.h"

U8 code Prg_34x_Date[ DATE_SIZE ] = __DATE2__;

PRG_TIME.C
#include "glb.h"

U8 code Prg_34x_Time[ TIME_SIZE ] = __TIME__;

Это нужно, чтобы линковщик отдельно обрабатывал каждую переменную и смог собрать их в указанном месте.
Теперь о __DATE2__ и __TIME__ — это встроенные макросы, тут все просто.
Predefined Macros
Необходимый минимум в коде у нас написан — можно все это дело компилировать. Это предварительная компиляция, поскольку нам хорошо бы узнать размер кода.
Build Output
Пришло время вернуться к непосредственным манипуляциям линковщиком. Расположить переменные я предлагаю в самом конце бинарника (для этого и нужно было узнавать размер кода выше) друг за другом. Следовательно начальные байты для расположения таковы
TIME = размер кода — TIME_SIZE;
DATE = размер кода — DATE_SIZE;
ИМЯ_ПРОЕКТА = размер кода — PRJ_TEXT_SIZE.
Команда для линковщика следующая: ?CO?Prj_Name(4819), ?CO?Prg_Date(4835), ?CO?Prg_Time(4843)
В uVision заходим в опции проекта и в секции «BL51 Locate» прописываем команду.
Options for target
Пересобираем проект в .hex, конвертируем его в .bin и смотрим:
.bin info
Все по-честному.

2. Контроль версии «ON AIR»

Вторая задача на повестке — это возможность сделать версию прошивки доступной для контроля прямо на работающем устройстве. Задача достаточно тривиальная.
Для начала создадим тип данных, который будет транспортировать переменные. Дописываем в тот же h'ник
GLB.H
...
// - Тип данных для контроля версий ---------------
typedef struct {
 U8        Prj[ PRJ_TEXT_SIZE ]; // Имя Проекта
 U8           Date[ DATE_SIZE ]; // Дата компиляции
 U8           Time[ TIME_SIZE ]; // Время компиляции
} VER_BASE;
// ------------------------------------------------ 

Теперь функционал
VERSION.С
#include < string.h >             // memset и т.п.
#include "Inc/glb.h"

VER_BASE xdata VerInfo;           // Переменная-транспорт

U8 version( void )
{
 // инициализируем пробелами
 memset( &VerInfo.Prj, ' ', ( PRJ_TEXT_SIZE + DATE_SIZE + TIME_SIZE ) );
 // заполняем сообщение версией
 memcpy( &VerInfo.Prj, Prj_Name, PRJ_TEXT_SIZE );
 memcpy( &VerInfo.Date, Prg_Date, DATE_SIZE );
 memcpy( &VerInfo.Time, Prg_Time, IME_SIZE );

// какая-то функция обмена по UART1
return	ua1_send( &VerInfo.Prj, sizeof( VerInfo ) );
}

Теперь достаточно в принимающей программе на «верхнем» уровне создать такую же структуру и просматривать версию в удобном виде.
Вот как это работает в моем терминале (запрашиваю версию у сети из двух контроллеров)
Terminal

Комментарии (17)

RSS свернуть / развернуть
Гм…
1) А зачем по файлу на переменную, что мешает их сразу собрать в структуру?
2)
TIME = размер кода — TIME_SIZE;
DATE = размер кода — DATE_SIZE;
ИМЯ_ПРОЕКТА = размер кода — PRJ_TEXT_SIZE.
Но ведь эти цифры придется же править при каждой сборке. Почему бы не запихнуть совсем в конец прошивки — flash_size-VER_BASE_size?
0
  • avatar
  • Vga
  • 09 декабря 2011, 17:48
Все верно, я взял такое приближение для простоты объяснения.
0
Чето-то не догнал смысла этих манипуляций.
Если нам нужно узнать версию прошивки, то для этого можно сделать спец функцию и неважно где эта информация хранится.
0
  • avatar
  • a9d
  • 09 декабря 2011, 18:19
по всей видимости, ему надо иметь возможность узнать версию прошивки, не зашивая её в прибор. Хотя ИМХО для этого проще ввести некий маркер байт эдак на 4-8 в начале структуры и делать по нему поиск. И пофиг тогда, в каком месте прошивки она хранится.
0
Для этого не занимаются шаманством, а делаю соответствующее название прошивки.
0
Господа, я не навязываю вам этот подход. Вы можете вообще болт положить на именование прошивок и версий, или вести вам одним понятную нумерацию какими-то там маркерами.
В статье объяснен общий подход и некоторые тонкости компилятора. Верю, что кто-нибудь найдет ее полезной.
0
Если посмотреть, то версия прошивки указана в названии файла прошивки. Скачай прошивку на принтер от Samsung, Canon, роутеры D-Link.
В твоем способе нужно использовать спец. софт чтоб определить версию.

Нумеруют, прошивку так чтоб версия была понятна только разработчикам. Потребителю глубоко пофигу какая там версия, главное чтоб работало.
0
Заметьте уважаемый a9d! часто функционал и интерфейс связаны именно с версией прощивки ( BIOSы, версии ОS смартфонов, роутеров и т.д. ) и к пользователю, желающему получить новый функционал или устранить недостатки оборудования, обращено требование если у вас BIOS версии 210 обновите прошивку на 215 или если GSMART имеет версию 1.8 обновите на 1.9.
Как следствие этого, мы можем видеть FAQ: "… а как мне узнать версию программы в моем устройстве ...?"
Считаю, — наличие такой функции полезной,… иногда нужно и имя автора добавить и контрольную сумму…
0
О боги. Причем здесь это?
Узнать версию прошивки можно используя 100500 способов.
Но зачем так усложнять столь простую операцию?
Зачем размещать по конкретному адресу? Какой в этом смысл?
0
Ну одна причина есть — чтобы можно было версию файла хоть в блокноте посмотреть. Тогда как int8_t version(void){return 23;} требует предварительной прошивки, а имя файла может быть утеряно.
0
А по какой причине имя файла может быть утеряно?
Никакая ОС сама по себе имена не меняет. Разве, что умышленно.
Но умышленно можно все, что угодно подправить.
0
Ну я видел (правда во времена Win98) папку, полную наименований вида $$abg$$$.mp3 — кириллические имена слетели из-за какого-то конфликта. К тому же, файл можно переименовать, в том числе случайно.
Также на сайтах всяких девайсов прошивка часто попадается в виде fw.zip с скажем fw.bin внутри и ни намека на версию, кроме как в ссылке «Скачать прошивку v. 1.86».
0
Так в версии прошивки кириллицу не используют.
Я не видел, чтоб когда-то имена слетали. Даже в 95 винде.
Случайно может произойти все, что угодно. Вплоть до форматирования винчестера с последующим выбрасыванием его в форточку.

Да и ктомуже пользователь получает бинарник. На нем нет опознавательных знаков. И каким это образом он должен узнать версию? Он должен догадаться открыть его в хексовом редакторе?
0
Бывает, имена файлов обрезаются.

Он должен догадаться открыть его в хексовом редакторе?
Это вроде вообще само собой разумеющееся, когда нет никакой другой информации о файле.)
0
Когда я вижу какой-то непонятный файл, я обычно открываю его блокнотом и нередко содержащиеся в нем строки посняют что это.
Хотя в целом это излишество, да.
Кстати, макросы TIME и DATE2 в других компиляторах доступны?
0
мы все правы:
— если программируем для удовольствия — имя файла-исходника, папка в которой он лежит и т.д. однозначно подскажут, что это за версия, и покопавшись в голове, можно вспомнить о чем это, а если найти исходник!… да ещё и с комментариями — тогда проблем нет.
— если создаем програмный продукт или программно-аппаратный комплекс, который, — по определению, — должен быть отчужден от создателя, то есть существует отдельно от него, и функционирует далеко от места своего рождения, и модернизируется людьми никак не участвовавшими в его создании, то такой программный продукт должен быть максимально идентифицирован.

Вообще, как мне кажется, пост о возможностях линкера, а нравится такой вариант или не нравится, — дело личное. Кому не нравится — может предложить иные варианты, -воспользуемся.
0
yy1 снял все слова с языка (хм, для плюсования не хватает какой-то силы и рэйтинга).
Даже не думал при написании, что кто-то будет рассматривать этот подход для личных любительских проектов, я конечно имел ввиду уже программный продукт, который буду использовать другие люди.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.