Blackfin: практическая реализация загрузчика (bootloader) Печать
Добавил(а) microsin   

В этой статье описан загрузчик для процессора ADSP-BF538F [1], который позволяет перезаписывать основную программу через последовательный порт UART0 [2] по протоколу XMODEM [5]. Ниже на рисунке показано общее устройство системы с загрузчиком.

Blackfin initcode bootloader mainapp

Рис. 1. Общая структура программно-аппаратной системы на основе ADSP-BF538F с использованием загрузчика.

Все программное обеспечение состоит из 3 основных частей, которые записаны в LDR-формате (подробнее про этот формат и использование см. [3, 4]) во FLASH-память процессора ADSP-BF538F:

initcode. Неперезаписываемый стартовый код, который нужен для инициализации SDRAM, и для отслеживания необходимости запуска загрузчика (по условной комбинации нажатия кнопок на клавиатуре). Эта часть на рис. 1 показана как initcode.

bootloader. Загрузчик, на рис. 1 показан как bootloader. Это обычная программа Blackfin, которая будучи загруженной, обеспечивает сервисные функции по перепрошивке основного приложения системы mainapp. Эта часть программного обеспечения также неперезаписываемая, она нужна только для обновления основной программы.

mainapp. Основная программа системы, которую должен перезаписать код bootloader, на рис. 1 показана как mainapp. Основная программа запускается по умолчанию, если не сработало условие запуска bootloader.

Все эти 3 составляющие программного обеспечения записаны в память FLASH в формате LDR по заранее известным адресам, и рассчитаны на загрузку и запись встроенным проприетарным ROM-загрузчиком Blackfin. Начало кода программы initcode жестко фиксировано по адресу 0, а программы bootloader и mainapp могут размещаться в любом месте памяти FLASH, по выбору пользователя. Далее кратко описаны основные составляющие системы, показанные на рис. 1.

[initcode]

Это маленькое приложение размещается в LDR-формате по фиксированному адресу 0 памяти FLASH. Процессор Blackfin сконфигурирован выводами BMODE1 и BMODE0 (см. [1]) так, что при включении питания или сбросе будет BootROM загрузит из памяти FLASH и запустит именно приложение initcode. Приложение initcode выполняет 3 основные функции, описанные ниже.

Инициализация контроллера SDRAM. Поскольку в системе имеется память SDRAM, в которую должны загружаться приложения bootloader и mainapp, и штатный ROM-загрузчик Blackfin (так называемый BootROM) не умеет инициализировать эту память, то обязательно необходим некий код, который инициализирует SDRAM (подробнее пример подключения SDRAM и код инициализации см. в [6]).

Отслеживание условия запуска загрузчика. Программа initcode должна сделать выбор, какую из программ нужно загрузить и запустить после инициализации SDRAM - bootloader и mainapp. Этот выбор задается специальной комбинацией одновременного нажатия двух кнопок на клавиатуре при включении питания (сбросе). Если при сбросе удерживались нажатыми эти две кнопки, то initcode запустит bootloader. Иначе, если при сбросе не были нажаты эти две кнопки, будет запущена основная программа прибора.

Blackfin initcode start

Передача управления потоком загрузки коду BootROM. Программа initcode работает по принципу эксплуатации штатного ROM-загрузчика Blackfin (BootROM). Т. е. initcode знает адреса в памяти FLASH, по которым находятся подлежащие загрузке программы, и на выходе передает эти адреса ROM-загрузчику Blackfin. Далее ROM-загрузчик по указанному адресу загрузит нужную программу - или bootloader, или mainapp - и запустит эту программу на выполнение. По такому способу в памяти FLASH может находится несколько (даже более двух) приложений. Принцип подобного управления потоком загрузки подробно описан в [3, 4].

Основное требование к initcode - эта программа должна быть написана специальным образом, без инициализации стека и без модификации специальных регистров, которые использует ROM-загрузчика Blackfin. Кроме того, она должна для своего тела использовать только память SRAM (чтобы initcode мог сам корректно загрузиться ROM-загрузчиком Blackfin). Условие сохранения стека и регистров нужно для того, чтобы на выходе initcode мог корректно продолжиться стандартный процесс загрузки из FLASH по указанному адресу. По этой причине initcode в основном написан на ассемблере.

/////////////////////////////////////////////////////////////////
// blackfin-edinburgh-core
#include < sys/platform.h >
#include < sys/anomaly_macros_rtl.h >
 
.section/DOUBLEANY program;
 
.EXTERN _InitSDRAM__Fv;
   .file_attr requiredForROMBoot;
   .align 2;
 
/*******Секция Pre-Init*********************************************/
start:
   //Сохранение регистров в стеке:
   [--SP] = ASTAT;
   [--SP] = RETS;
   [--SP] = (r7:0);
   [--SP] = (p5:0);
   [--SP] = I0;
   [--SP] = I1;
   [--SP] = I2;
   [--SP] = I3;
   [--SP] = B0;
   [--SP] = B1;
   [--SP] = B2;
   [--SP] = B3;
   [--SP] = M0;
   [--SP] = M1;
   [--SP] = M2;
   [--SP] = M3;
   [--SP] = L0;
   [--SP] = L1;
   [--SP] = L2;
   [--SP] = L3;
/********************************************************************/
 
   R1 = SYSCFG;
   R4 = R0;       // Сохранение модифицированного списка
   BITSET(R1,1);
   SYSCFG = R1;   // Запуск счетчика циклов
 
/////////////////////////////////////////////////////////////////
// cplusplus
   CALL.X ___ctorloop; // запуск глобальных конструкторов C++
.extern ___ctorloop;
.type ___ctorloop,STT_FUNC;
 
/////////////////////////////////////////////////////////////////
// standard
   CALL _InitSDRAM__Fv;    //Инициализация памяти SDRAM
   CALL _main;             //Код main опросит клавиатуру, и в ячейку
                           // с адресо 0 положит адрес, откуда нужно
                           // продолжить загрузку (т. е. будет сделан
                           // выбор - либо грузить bootloader с адреса
                           // flash 0x20010000, либо mainapp с адреса
                           // flash 0x20030000)
   //В ячейке SDRAM с адресом 0 находится адрес продолжения загрузки.
   P1.H = 0x0;
   P1.L = 0x0;
   P0 = [P1];
   //Адрес загрузки записывается в регистр R0:
   R0 = P0;
 
   //Восстановление регистров из стека, кроме R0:
   L3 = [SP++];
   L2 = [SP++];
   L1 = [SP++];
   L0 = [SP++];
   M3 = [SP++];
   M2 = [SP++];
   M1 = [SP++];
   M0 = [SP++];
   B3 = [SP++];
   B2 = [SP++];
   B1 = [SP++];
   B0 = [SP++];
   I3 = [SP++];
   I2 = [SP++];
   I1 = [SP++];
   I0 = [SP++];
   (p5:0) = [SP++];
   //Регистр R0 не нужно вычитывать из стека:
   (r7:1) = [SP++];
   //Выталкивание из стека ненужного значения R0:
   RETS = [SP++];
   RETS = [SP++];
   ASTAT = [SP++];
/********************************************************************/
   RTS;
.start.end:
 
.global start;
.type start,STT_FUNC;
.extern _main;
.extern ldf_stack_end;
.extern __unknown_exception_occurred;
.type __unknown_exception_occurred,STT_FUNC;
 
/////////////////////////////////////////////////////////////////
// cplusplus
.section/DOUBLEANY ctor;
	.align 4;
___ctor_table:
	.byte4=0;
.global ___ctor_table;
.type ___ctor_table,STT_OBJECT;
.section/DOUBLEANY .gdt;
        .align 4;
___eh_gdt:
.global ___eh_gdt;
        .byte4=0;
.type ___eh_gdt,STT_OBJECT;
.section/DOUBLEANY .frt;
        .align 4;
___eh_frt:
.global ___eh_frt;
        .byte4=0;
.type ___eh_frt,STT_OBJECT;

[bootloader]

Это загрузчик для основного кода программы mainapp. По сути bootloader - обычное приложение, которое записано во FLASH в стандартном LDR-формате. Просто у него урезан функционал с целью предоставить функцию перезаписи области FLASH, где находится основная программа mainapp. Для пользователя предоставлена управляющая консоль (через подключение к виртуальному USB-COM порту, как это показано на рис. 1), в которой можно подавать команды и отслеживать их выполнение. Команды позволяют запустить загрузку файла прошивки (двоичный файл прошивки в LDR-формате, содержащий код основной программы mainapp, информацию о версии, дополнительный блок с контрольной суммой и размером прошивки), и другие сервисные дополнительные действия. Также предоставляется минимальный интерфейс пользователя через штатную клавиатуру и индикатор прибора.

Для передачи прошивки в прибор был выбран популярный протокол XMODEM - по той причине, что он хорошо задокументирован [5], и его поддерживают многие популярные консольные утилиты наподобие putty, HyperTerminal, SecureCRT и т. п. Ниже на скриншотах показан процесс передачи прошивки по протоколу XMODEM с помощью программы терминального клиента SecureCRT.

SecureCRT XMODEM update01 SecureCRT XMODEM update02

Программа bootloader для своей работы использует как память SRAM L1, так и SDRAM (SDRAM предварительно была проинициализирована кодом initcode). В SDRAM находятся буфер для загрузки данных приложения по протоколу XMODEM [5], и другие данные. После загрузки приложения mainapp через мост USB-UART код bootloader проверяет контрольную сумму приложения mainapp, и если все в порядке, может по команде пользователя обновить код основной программы, т. е. перезаписать во FLASH код программы mainapp.

#include < services_types.h >
#include < string.h >
#include < ctype.h >
#include "uart/BfDebugger.h"
#include "delay.h"
#include "xmodem.h"
#include "uart/UARTconsole.h"
#include "graphics.h"
#include "crc16.h"
#include "fwupdate.h"
#include "miscell.h"
 
#define XSOH 0x01 //передатчик -> приемнику: начало заголовка
#define XEOT 0x04 //передатчик -> приемнику: конец передачи
#define XETB 0x17 //передатчик -> приемнику: конец передачи блока
#define XCAN 0x18 //передатчик -> приемнику: отмена передачи
#define XACK 0x06 //приемник -> передатчику: OK
#define XNAK 0x15 //приемник -> передатчику: нужно повторить передачу пакета
 
// 133 байта стандартного пакета XModem:
static unsigned char xbuff[1+1+1+128+2];
//Буфер для приема прошивки:
section ("sdram0") u8 ldrbuffer [1024*1024];
TLdrBlock* ldrblock;
 
static bool _inbyte (char *ch)
{
   if (inRXconsole!=outRXconsole)
   {
      *ch = bufRXconsole[outRXconsole];
      //DEBUG(1, "-->%02X\n", bufRXconsole[outRXconsole]);
      outRXconsole++;
      outRXconsole &= UARTCONS_RXBUFMASK;
      return true;
   }
   else
      return false;
}
 
static void xputchar (char sym)
{
   bufTXconsole[inTXconsole] = sym;
   inTXconsole++;
   inTXconsole &= UARTCONS_TXBUFMASK;
}
 
static u8 crc8(u8 *buf, int sz)
{
   u8 crc = 0;
   
   for (int i=0; i < sz; i++)
      crc += buf[i];
   return crc;
}
 
void XmodemPoll (void)
{
   static clock_t xmodem_timeout;
   static u32 byteidx, nakcnt, packetcnt;
   static u8 bmpcnt;
   char c;
   static bool CANreceived;
 
   switch(consolestate)
   {
   case CONSXMODEM_RECEIVE_INIT:
      xmodem_timeout = 0;
      nakcnt = 0;   //будем считать NAK
      byteidx = 0;
      packetcnt = 0;
      memset(xbuff, 0xFF, sizeof(xbuff));
      memset(ldrbuffer, 0xFF, sizeof(ldrbuffer));
      CANreceived = false;
      msg("Ожидание начала приема\r\n");
      consolestate = CONSXMODEM_RECEIVE_WAIT;
      break;
   case CONSXMODEM_RECEIVE_WAIT:
/////////////////////////////////////////////////////////
// ждем стартового SOH, передаем каждую секунду NAK
      //bytecnt считает, сколько раз передали NAK
      if (nakcnt>30)
      {
         //если больше 30, то передатчик слишком долго не передает
         if('.' == textbuf[txtinsertline][0])
            msg("\r\n");
         msg("Слишком много попыток начать передачу\r\n");
         msg("STOP\r\n");
         consolestate = CONSCMDDECODE;
      }
      else if (_inbyte(&c))
      {
         //ждем наличия в буфере кода SOH
         if (XSOH == c)
         {
            xbuff[byteidx++] = c;
            xmodem_timeout = clock() + MS_TICKS * 1000;
            consolestate = CONSXMODEM_RECEIVE_DATA;
         }
         else if ((XEOT == c) || (XETB == c))
         {
            xputchar(XACK);
            consolestate = CONSXMODEM_RECEIVE_CHECK_FIRMWARE;
         }
         else if (XCAN == c)
         {
            xputchar(XACK);
            CANreceived = true;
            consolestate = CONSXMODEM_RECEIVE_CANCEL;
         }
      }   
      else if (xmodem_timeout < clock())
      {
         nakcnt++;
         //посылаем очередной NAK
         xputchar(XNAK);
         msg(".");
         xmodem_timeout = clock() + MS_TICKS * 1000;
      }
      break;
   case CONSXMODEM_RECEIVE_DATA:
      if (byteidx==132)
      {
/////////////////////////////////////////////////////////
// Прием блока завершен, проверка.
         if('.' == textbuf[txtinsertline][0])
            msg("\r\n");
         bool packetOK = true;
         //Проверка номера пакета:
         if (0xFF != (xbuff[1] + xbuff[2]))
         {
            if(isdigit(textbuf[txtinsertline][0]))
               msg("\r\n");
            msg("Ошибка номера пакета %i\r\n", packetcnt+1);
            packetOK = false;
         }
         //Проверка CRC:
         if (crc8(&xbuff[3], 128) != xbuff[3+128])
         {
            if(isdigit(textbuf[txtinsertline][0]))
               msg("\r\n");
            msg("Ошибка CRC пакета %i\r\n", packetcnt+1);
            packetOK = false;
         }
         if (packetOK)
         {
            xputchar(XACK);
            memcpy(&ldrbuffer[128*packetcnt], &xbuff[3], 128);
            if(!isdigit(textbuf[txtinsertline][0]))
               msg("\r\n");
            msg("%i байт\r", 128 * (packetcnt+1));
            packetcnt++;
         }
         else
         {
            xputchar(XNAK);
         }
         memset(xbuff, 0xFF, sizeof(xbuff));
         byteidx = 0;
         nakcnt = 0;   //будем считать NAK
         consolestate = CONSXMODEM_RECEIVE_WAIT;
      }
      else if (_inbyte(&c))
      {
         xbuff[byteidx++] = c;
         xmodem_timeout = clock() + MS_TICKS * 1000;
      }
      else if (xmodem_timeout < clock())
      {
         msg("Таймаут приема данных\r\n");
         msg("STOP\r\n");
         consolestate = CONSCMDDECODE;
      }
      break;
   case CONSXMODEM_RECEIVE_CANCEL:
      //Принудительный обрыв сеанса связи со стороны приемника.
      //Ждем очистки линии приема, отбрасывая принимаемые байты.
      if (_inbyte(&c))
      {
         xmodem_timeout = clock() + MS_TICKS * 1000;
      }
      else if (xmodem_timeout < clock())
      {
         //xputchar(XCAN);
         if (CANreceived)
         {
            msg("\r\nОтмена со стороны передатчика\r\n");
         }
         else
         {
            msg("\r\nОстановлено пользователем\r\n");
         }
         consolestate = CONSCMDDECODE;
      }
      break;
   case CONSXMODEM_RECEIVE_CHECK_FIRMWARE:
      ERRORCODE errcode;
      ldrblock = CheckFirmware(ldrbuffer, 128*packetcnt, &errcode);
      if (ERROR_NONE == errcode)
      {
         msg("%i байт, CRC %04X OK\r\n",
             ldrblock->sizeldrfile + sizeof(TLdrBlock),
             ldrblock->crc16);
         UartPuts(UART0, "OK\r\n");
         consolestate = MODE_FIRMWARE_UPDATE_QUESTION;
      }
      else
      {
         msg("\r\n");
         consolestate = CONSCMDDECODE;
      }
      msg(errormsg(errcode));
      UartPuts(UART0, (char*)errormsg(errcode));
      break;
   }
}

Чтобы завершить работу bootloader и запустить основную программу mainapp, достаточно передернуть питание или выполнить программный сброс процессора Blackfin. Прелесть в том, что при любой ошибке в основном приложении mainapp (или ошибке перезаписи FLASH секций mainapp) прибор никогда не останется "сырым кирпичом" - код bootloader не перезаписывается, так что всегда есть возможность заново запустить перепрошивку прибора или откатиться на старую версию, если код mainapp оказался запорченным.

[mainapp]

Это основное приложение системы, которое также находится во FLASH в формате LDR по заранее известному адресу. Оно будет загружено и запущено по умолчанию, если initcode не обнаружит условие запуска bootloader.

[FLASH]

Ниже на рисунке показан пример карты памяти FLASH процессора ADSP-BF538F. Размер памяти FLASH 1 мегабайт, организация 512K 16-битных ячеек, дотупных как байтовые ячейки в диапазоне адресов 0x20000000 .. 0x200FFFFF. В этой памяти FLASH размещены 3 описывамых приложения initcode, bootloader и mainapp.

Blackfin memory map Размеры секторов FLASH:

0:  0x00000000 .. 0x00003FFF   16 кбайт
1:  0x00004000 .. 0x00005FFF    8 кбайт
2:  0x00006000 .. 0x00007FFF    8 кбайт
3:  0x00008000 .. 0x0000FFFF   32 кбайт
4:  0x00010000 .. 0x0001FFFF   64 кбайт
5:  0x00020000 .. 0x0002FFFF   64 кбайт
6:  0x00030000 .. 0x0003FFFF   64 кбайт
...
18: 0x000F0000 .. 0x000FFFFF   64 кбайт

Обратите внимание, что адреса для загрузки трех приложений выбраны таким образом, чтобы они размещались в разных секциях памяти FLASH. Дело в том, что память FLASH можно перезаписывать исключительно секторами, при этом если нужно записать только один байт, то для этого надо обязательно сначала стереть весь соответствующий сектор целиком. Поэтому все 3 части программного обеспечения распределены по разным секторам, чтобы перезапись одного приложения не оказывало влияния на остальные приложения.

Например, когда программа bootloader обновляет область хранения для mainapp, она должна стереть сектора, начиная от сектора 6 и далее. Поскольку код bootloader хранится целиком в других секторах (4 и 5), то при обновлении mainapp код bootloader останется нетронутым в любом случае. Также останется нетронутым и код inutcode, который находится в секторе 0.

Библиотеки функций для доступа к памяти FLASH можно найти в среде разработки VisualDSP++ 5.0 (здесь в качестве примера показан файл adi_S29AL004D_8D.c с переведенными комментариями).

/******************************************************************************************/
/*   (C) Copyright 2004 - Analog Devices, Inc.  All rights reserved.                      */
/*                                                                                        */
/*   FILE: VisualDSP5.0\Blackfin\lib\src\drivers\flash\S29AL004D_8D\adi_S29AL004D_8D.c    */
/*                                                                                        */
/*   CHANGES:  1.00.0  - initial release                                                  */
/*                                                                                        */
/*   НАЗНАЧЕНИЕ:  Выполняет действия по перепрограммированию устройства памяти            */
/*              flash S29AL004D_8D (процессор ADSP-BF538F и т. п.) с использованием       */
/*              модели драйверов устройств adi.                                           */
/******************************************************************************************/
//---- подключаемые файлы ----//
#include < ccblkfn.h >
#include < drivers\flash\util.h >
#include < drivers\flash\adi_m29w320.h >
#include < drivers\flash\Errors.h >
 
//---- определения переменных -----//
static char *pFlashDesc;
static char *pDeviceCompany = "Spansion";
static int gNumSectors;
 
//---- Прототипы функций  ----//
static ERROR_CODE EraseFlash(unsigned long ulStartAddr);
static ERROR_CODE EraseBlock( int nBlock, unsigned long ulStartAddr );
static ERROR_CODE GetCodes(int *pnManCode, int *pnDevCode, unsigned long ulStartAddr);
static ERROR_CODE GetSectorNumber( unsigned long ulAddr, int *pnSector );
static ERROR_CODE GetSectorStartEnd( unsigned long *ulStartOff,
                                     unsigned long *ulEndOff, int nSector );
static ERROR_CODE PollToggleBit(unsigned long ulOffset);
static ERROR_CODE ReadFlash(unsigned long ulOffset, unsigned short *pusValue );
static ERROR_CODE ResetFlash(unsigned long ulStartAddr);
static ERROR_CODE WriteFlash(unsigned long ulOffset, unsigned short usValue );
static unsigned long GetFlashStartAddress( unsigned long ulAddr);
 
// Функция открывает устройство памяти
static u32 adi_pdd_Open(
   ADI_DEV_MANAGER_HANDLE ManagerHandle,     // device manager handle
   u32 DeviceNumber,                         // device number
   ADI_DEV_DEVICE_HANDLE DeviceHandle,       // device handle
   ADI_DEV_PDD_HANDLE *pPDDHandle,           // указатель на место размещения PDD handle
   ADI_DEV_DIRECTION Direction,              // направление передачи данных
   void *pEnterCriticalArg,                  // параметр входа в критическую область
   ADI_DMA_MANAGER_HANDLE DMAHandle,         // handle для DMA manager
   ADI_DCB_HANDLE DCBHandle,                 // callback handle
   ADI_DCB_CALLBACK_FN DMCallback            // функция callback для device manager
);
 
// Функция закрывает устройство памяти
static u32 adi_pdd_Close(
   ADI_DEV_PDD_HANDLE PDDHandle
);
 
// Чтение из устройства памяти
static u32 adi_pdd_Read(ADI_DEV_PDD_HANDLE PDDHandle,
                        ADI_DEV_BUFFER_TYPE BufferType,
                        ADI_DEV_BUFFER *pBuffer);
 
// Запись в устройство памяти
static u32 adi_pdd_Write(ADI_DEV_PDD_HANDLE PDDHandle,
                         ADI_DEV_BUFFER_TYPE BufferType,
                         ADI_DEV_BUFFER *pBuffer);
 
// Управление устройством памяти
static u32 adi_pdd_Control(ADI_DEV_PDD_HANDLE PDDHandle,
                           u32 Command,
                           void *pArg);
 
ADI_DEV_PDD_ENTRY_POINT ADIS29AL004D_8DEntryPoint =
{
   adi_pdd_Open,
   adi_pdd_Close,
   adi_pdd_Read,
   adi_pdd_Write,
   adi_pdd_Control
};
 
// ---- Функции API для доступа к физическому устройству ----//
 
//----------- adi_pdd_Open ----------//
// НАЗНАЧЕНИЕ
//    Эта функция открывает устройство памяти flash типа S29AL004D_8D
//    с целью его использования.
//
// Входные параметры:
//    ManagerHandle - device manager handle
//    DeviceNumber - номер устройства
//    DeviceHandle - device handle
//    PDDHandle - этот handle используется для идентификации устройства
//    Direction - направление перемещения данных
//    *pEnterCriticalArg - параметр входа в критическую область
//    DMAHandle - handle для DMA manager
//    DCBHandle - callback handle
//    DMCallback - функция callback для device manager
//
// Возвращаемое значение: код результата операции
u32 adi_pdd_Open(
   ADI_DEV_MANAGER_HANDLE ManagerHandle,
   u32 DeviceNumber,
   ADI_DEV_DEVICE_HANDLE DeviceHandle,
   ADI_DEV_PDD_HANDLE *pPDDHandle,
   ADI_DEV_DIRECTION Direction,
   void *pEnterCriticalArg,
   ADI_DMA_MANAGER_HANDLE DMAHandle,
   ADI_DCB_HANDLE DCBHandle,
   ADI_DCB_CALLBACK_FN DMCallback)
{
   // проверка на ошибки, если это требуется
#ifdef ADI_S29AL004B_8B_ERROR_CHECKING_ENABLED
   if (DeviceNumber > 0)                              // проверка номера устройства
      return (ADI_DEV_RESULT_BAD_DEVICE_NUMBER);
   if (Direction != ADI_DEV_DIRECTION_BIDIRECTIONAL)  // проверка направления данных
      return (ADI_DEV_RESULT_DIRECTION_NOT_SUPPORTED);
#endif
   return (NO_ERR);
}
 
//----------- adi_pdd_Close ----------//
// НАЗНАЧЕНИЕ
//    Эта функция закрывает устройство S29AL004D_8D (память flash).
//
// Входные параметры:
//    PDDHandle - хэндл, используемый для идентификации устройства.
//
// Возвращаемое значение: код результата операции
u32 adi_pdd_Close(ADI_DEV_PDD_HANDLE PDDHandle)
{
   return (NO_ERR);
}
 
//----------- adi_pdd_Read ----------//
// НАЗНАЧЕНИЕ
//    Предоставляет буферы для S29AL004D_8D flash device, чтобы
//    принять поступающие данные.
//
// Входные параметры:
//    PDDHandle - хэндл, используемый для идентификации устройства.
//    BufferType - аргумент, идентифицирующий тип буфера: одноразмерный,
//                 двухразмерный, или кольцевой.
//    *pBuffer - адрес буфера или первого буфера в цепочке буферов.
//
// Возвращаемое значение: код результата операции
u32 adi_pdd_Read(ADI_DEV_PDD_HANDLE PDDHandle,
                 ADI_DEV_BUFFER_TYPE BufferType,
                 ADI_DEV_BUFFER *pBuffer)
{
   ADI_DEV_1D_BUFFER *pBuff1D;      // указатель на буфер
   unsigned short *pusValue;        // значение, прочитанное из flash
   unsigned long *pulAbsoluteAddr;  // абсолютный адрес для чтения
   u32 Result;                      // возвращаемый код ошибки
 
   // приведение нашего буфера к одноразмерному буферу
   pBuff1D = (ADI_DEV_1D_BUFFER*)pBuffer;
   // получение адреса для данных в буфере
   pusValue = (unsigned short *)pBuff1D->Data;
   // получение смещения на данные
   pulAbsoluteAddr = (unsigned long *)pBuff1D->pAdditionalInfo;
   Result = ReadFlash( *pulAbsoluteAddr, pusValue );
   return(Result);
}
 
//----------- adi_pdd_Write ----------//
// НАЗНАЧЕНИЕ
//    Предоставляет буферы для S29AL004D_8D flash device, используемые
//    при передаче исходящих данных.
//
// Входные параметры:
//    PDDHandle - хэндл, используемый для идентификации устройства.
//    BufferType - аргумент, идентифицирующий тип буфера: одноразмерный,
//                 двухразмерный, или кольцевой.
//    *pBuffer - адрес буфера или первого буфера в цепочке буферов.
//
// Возвращаемое значение: код результата операции
u32 adi_pdd_Write(ADI_DEV_PDD_HANDLE PDDHandle,
                  ADI_DEV_BUFFER_TYPE BufferType,
                  ADI_DEV_BUFFER *pBuffer)
{
   ADI_DEV_1D_BUFFER *pBuff1D;         // указатель на буфер
   short *psValue;                     // значение, записываемое во flash
   unsigned long *pulAbsoluteAddr;     // абсолютный адрес для записи
   unsigned long ulFlashStartAddr;     // адрес начала flash
   u32 Result;                         // возвращаемый код ошибки
 
   // приведение нашего буфера к одноразмерному буферу
   pBuff1D = (ADI_DEV_1D_BUFFER*)pBuffer;
   // получение адреса для данных в буфере
   psValue = (short *)pBuff1D->Data;
   // получение смещения на данные
   pulAbsoluteAddr = (unsigned long *)pBuff1D->pAdditionalInfo;
   // получения адреса начала flash из абсолютного адреса
   ulFlashStartAddr = GetFlashStartAddress(*pulAbsoluteAddr);
   // разблокировка flash
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0xaa );
   WriteFlash( ulFlashStartAddr + 0x0554, 0x55 );
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0xa0 );
   // программирование нашего актуального значения
   Result = WriteFlash( *pulAbsoluteAddr, *psValue );
   // гарантируем, что запись была успешной
   Result = PollToggleBit(*pulAbsoluteAddr);
   return(Result);
}
 
//----------- adi_pdd_Control ----------//
// НАЗНАЧЕНИЕ
//    Эта функция устанавливает или считывает параметр конфигурации
//    для S29AL004D_8D flash device.
//
// Входные параметры:
//    PDDHandle - хэндл, используемый для идентификации устройства.
//    Command - идентификатор команды
//    *pArg - адрес параметра, зависящего от команды
//
// Возвращаемое значение: код результата операции
u32 adi_pdd_Control(ADI_DEV_PDD_HANDLE PDDHandle,
                    u32 Command,
                    void *pArg)
{
   ERROR_CODE ErrorCode = NO_ERR;
   COMMAND_STRUCT *pCmdStruct = (COMMAND_STRUCT *)pArg;
   // switch для обработки команды
   switch ( Command )
   {
   // Все стереть:
   case CNTRL_ERASE_ALL:
      ErrorCode = EraseFlash(pCmdStruct->SEraseAll.ulFlashStartAddr);
      break;
   // Стереть сектор:
   case CNTRL_ERASE_SECT:
      ErrorCode = EraseBlock( pCmdStruct->SEraseSect.nSectorNum,
                              pCmdStruct->SEraseSect.ulFlashStartAddr );
      break;
   // Получить код производителя и код устройства памяти:
   case CNTRL_GET_CODES:
      ErrorCode = GetCodes((int *)pCmdStruct->SGetCodes.pManCode,
                           (int *)pCmdStruct->SGetCodes.pDevCode,
                           (unsigned long)pCmdStruct->SGetCodes.ulFlashStartAddr);
      break;
   case CNTRL_GET_DESC:
      //Чтение описания устройства:
      pCmdStruct->SGetDesc.pDesc  = pFlashDesc;
      pCmdStruct->SGetDesc.pFlashCompany  = pDeviceCompany;
      break;
   case CNTRL_GET_SECTNUM:
      // Получить номер сектора в зависимости от адреса:
      ErrorCode = GetSectorNumber( pCmdStruct->SGetSectNum.ulOffset,
                                   (int *)pCmdStruct->SGetSectNum.pSectorNum );
      break;
   case CNTRL_GET_SECSTARTEND:
      // Получить адресов начала и конца сектора по его номеру:
      ErrorCode = GetSectorStartEnd( pCmdStruct->SSectStartEnd.pStartOffset,
                                     pCmdStruct->SSectStartEnd.pEndOffset,
                                     pCmdStruct->SSectStartEnd.nSectorNum );
      break;
   case CNTRL_GETNUM_SECTORS:
      // Получить количество секторов:
      pCmdStruct->SGetNumSectors.pnNumSectors[0] = gNumSectors;
      break;
   case CNTRL_RESET:
      // Сброс:
      ErrorCode = ResetFlash(pCmdStruct->SReset.ulFlashStartAddr);
      break;
   case ADI_DEV_CMD_SET_DATAFLOW:
      // Запустить поток данных (команда, требуемая для всех драйверов
      // устройств):
      ErrorCode = NO_ERR;
      break;
   case ADI_DEV_CMD_SET_DATAFLOW_METHOD:
      // Нет никаких действий, простой возврат:
      break;
   case ADI_DEV_CMD_GET_PERIPHERAL_DMA_SUPPORT:
      // Получить информацию о поддержке DMA (команда, требуемая для всех драйверов
      // устройств):
      (*(u32 *)pArg) = FALSE; // это устройство не поддерживается аппаратурой DMA
      break;
   default:
      // Отсутствие команды, или не известная команда:
      ErrorCode = UNKNOWN_COMMAND;
      break;
   }
   return(ErrorCode);
}
 
//----- Вспомогательные функции ----//
 
//----------- ResetFlash ----------//
// НАЗНАЧЕНИЕ
//    Посылает команду сброса для flash.
//
// Входные параметры:
//    unsigned long ulStartAddr - адрес начала flash
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE ResetFlash(unsigned long ulAddr)
{
   unsigned long ulFlashStartAddr;  //адрес начала flash
 
   // Получение начального адреса flash из абсолютного адреса
   // Значение ulAddr должно идеально указывать на начало
   // flash, однако здесь делается повторная проверка.
   ulFlashStartAddr = GetFlashStartAddress(ulAddr);
   // Отправка команды сброса во flash:
   WriteFlash( ulFlashStartAddr + 0x0554, 0xf0 );
   // Сброс должен всегда завершиться успешно
   return NO_ERR;
}
 
//----------- EraseFlash ----------//
// НАЗНАЧЕНИЕ
//    Посылает flash команду "стереть все".
//
// Входные параметры:
//    unsigned long ulStartAddr - адрес начала flash
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE EraseFlash(unsigned long ulAddr)
{
   ERROR_CODE ErrorCode = NO_ERR;
   int nBlock = 0;                  // индекс для каждого очищаемого блока
   unsigned long ulFlashStartAddr;  // адрес начала flash
 
   // Получение адреса начала flash по абсолютному адресу.
   // Значение ulAddr должно идеально указывать на начальный
   // адрес flash, однако здесь делается повторная проверка.
   ulFlashStartAddr = GetFlashStartAddress(ulAddr);
   // Очистка содержимого flash:
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0xaa );
   WriteFlash( ulFlashStartAddr + 0x0554, 0x55 );
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0x80 );
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0xaa );
   WriteFlash( ulFlashStartAddr + 0x0554, 0x55 );
   WriteFlash( ulFlashStartAddr + 0x0AAA, 0x10 );
   // Опрос с ожиданием завершения команды.
   ErrorCode = PollToggleBit(ulFlashStartAddr + 0x0000);
   return ErrorCode;
}
 
//----------- EraseBlock ----------//
// НАЗНАЧЕНИЕ
//    Посылает flash команду "очистить блок".
//
// Входные параметры:
//    int nBlock - номер блока (сектора) для стирания в нем данных
//    unsigned long ulStartAddr - адрес начала flash
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE EraseBlock( int nBlock, unsigned long ulAddr )
{
   ERROR_CODE ErrorCode   = NO_ERR;
   unsigned long ulSectStart = 0x0;    //хранит начальное смещение для сектора
   unsigned long ulSectEnd   = 0x0;    //хранит конечное смещение для сектора 
                                       // (хотя здесь это не используется)
   unsigned long ulFlashStartAddr;     //адрес начала flash
   // Получение адреса начала flash по абсолютному адресу.
   // Значение ulAddr должно идеально указывать на начальный
   // адрес flash, однако здесь делается повторная проверка.
   ulFlashStartAddr = GetFlashStartAddress(ulAddr);
   // Получение начального смещения сектора. Здесь мы автоматически
   //  также получим и конечное смещение сектора, хотя оно не нужно
   //  для актуальной очистки сектора.
   GetSectorStartEnd( &ulSectStart, &ulSectEnd, nBlock );
   // Отправка команды erase block:
   WriteFlash( (ulFlashStartAddr + 0x0AAA), 0xaa );
   WriteFlash( (ulFlashStartAddr + 0x0554), 0x55 );
   WriteFlash( (ulFlashStartAddr + 0x0AAA), 0x80 );
   WriteFlash( (ulFlashStartAddr + 0x0AAA), 0xaa );
   WriteFlash( (ulFlashStartAddr + 0x0554), 0x55 );
   // Последняя запись должна быть в пределах адресов блока.
   WriteFlash( (ulFlashStartAddr + ulSectStart), 0x30 );
   // Опрос с ожиданием завершения команды.
   ErrorCode = PollToggleBit(ulFlashStartAddr + ulSectStart);
   return ErrorCode;
}
 
//----------- PollToggleBit ----------//
// НАЗНАЧЕНИЕ
//    Опрашивает флаг завершения (toggle bit) памяти flash, чтобы определить
//    момент завершения текущей операции.
//
// Входные параметры:
//    unsigned long ulAddr - адрес в flash
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE PollToggleBit(unsigned long ulAddr)
{
   ERROR_CODE ErrorCode = NO_ERR;
   unsigned short sVal1;
   unsigned short sVal2;
 
   // Чтение flash 1 раз
   ReadFlash( ulAddr, &sVal1 );
   while( ErrorCode == NO_ERR )
   {
      // Чтение 2 раза
      ReadFlash( ulAddr, &sVal1 );
      ReadFlash( ulAddr, &sVal2 );
      // Операция XOR, чтобы определить побитную разницу.
      sVal1 ^= sVal2;
      // Определить, была ли смена состояния:
      if( !(sVal1 & 0x40) )
         break;
      // Проверка бита ошибки:
      if( !(sVal2 & 0x20) )
         continue;
      else
      {
         // Прочитать значение 2 раза
         ReadFlash( ulAddr, &sVal1 );
         ReadFlash( ulAddr, &sVal2 );
         // Операция XOR, чтобы определить побитную разницу.
         sVal1 ^= sVal2;
         // Определить, была ли смена состояния:
         if( !(sVal1 & 0x40) )
            break;
         else
         {
            ErrorCode = POLL_TIMEOUT;
            ResetFlash(ulAddr);
         }
      }
   }
   return ErrorCode;
}
 
//----------- GetCodes ----------//
// НАЗНАЧЕНИЕ
//    Посылает команду "auto select" устройству flash, которая
//    позволяет получить коды производителя и устройства.
//
// Входные параметры:
//    int *pnManCode - указатель для кода производителя
//    int *pnDevCode - указатель для кода устройства
//    unsigned long ulStartAddr - начальный адрес flash
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE GetCodes(int *pnManCode, int *pnDevCode, unsigned long ulAddr)
{
    unsigned long ulFlashStartAddr;    //начальный адрес flash
 
   // Получение адреса начала flash по абсолютному адресу.
   // Значение ulAddr должно идеально указывать на начальный
   // адрес flash, однако здесь делается повторная проверка.
   ulFlashStartAddr = GetFlashStartAddress(ulAddr);
   // Отправка команды auto select:
   WriteFlash( ulFlashStartAddr + 0x0aaa, 0xaa );
   WriteFlash( ulFlashStartAddr + 0x0554, 0x55 );
   WriteFlash( ulFlashStartAddr + 0x0aaa, 0x90 );
   // Теперь можно прочитать коды:
   ReadFlash( ulFlashStartAddr + 0x0000,(unsigned short *)pnManCode );
   *pnManCode &= 0x00FF;
   ReadFlash( ulFlashStartAddr + 0x0002, (unsigned short *)pnDevCode );
   *pnDevCode &= 0x00FF;
   if( *pnDevCode == 0x5B )
   {
      //Память flash размером 1 мегабайт (как у ADSP-BF538F)
      gNumSectors = 19;
      pFlashDesc = "S29AL008D(512 x 16)";
   }
   else
   {
      //Память flash размером 512 килобайт
      gNumSectors = 11;
      pFlashDesc = "S29AL004D(256 x 16)";
   }
   // Нужно послать какую-нибудь другую команду, и чтобы вывести устройство
   // из режима auto select, мы просто посылает команду сброс. Это возвратит
   // устройство обратно в режим чтения.
   ResetFlash(ulAddr);
   return NO_ERR;
}
 
//----------- GetSectorNumber ----------//
// НАЗНАЧЕНИЕ
//    Получение номера сектора по смещению.
//
// Входные параметры:
//    unsigned long ulAddr - абсолютный адрес
//    int *pnSector        - указатель на номер сектора
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE GetSectorNumber( unsigned long ulAddr, int *pnSector )
{
   int nSector = 0;
   int i;
   int error_code = 1;
   unsigned long ulMask;      // маска смещения
   unsigned long ulOffset;    // смещение
   unsigned long ulStartOff;
   unsigned long ulEndOff;
 
   ulMask = 0x3fffff;
   ulOffset = ulAddr & ulMask;
   for(i = 0; i < gNumSectors; i++)
   {
      GetSectorStartEnd(&ulStartOff, &ulEndOff, i);
      if ( (ulOffset >= ulStartOff)
         && (ulOffset < = ulEndOff) )
      {
         error_code = 0;
         nSector = i;
         break;
      }
   }
   // Если это допустимый сектор, установим его:
   if (error_code == 0)
      *pnSector = nSector;
   // Иначе это недопустимый сектор, возврат кода ошибки:
   else
      return INVALID_SECTOR;
   
   return NO_ERR;
}
 
//----------- GetSectorStartEnd ----------//
// НАЗНАЧЕНИЕ
//    Получение адресов начала и конца сектора по его номеру.
//
// Входные параметры:
//    unsigned long *ulStartOff - указатель на начальное смещение
//    unsigned long *ulEndOff - указатель на конечное смещение
//    int nSector - номер сектора
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE GetSectorStartEnd( unsigned long *ulStartOff,
                              unsigned long *ulEndOff,
                              int nSector )
{
   long lSectorSize = 0;
   long lChipNo;
   int nSectorOffsetInChip;
   long lChipSize;
 
   lChipNo = nSector/gNumSectors;
   nSectorOffsetInChip = nSector%gNumSectors;
   lChipSize = 1024 * 1024;
   if( nSectorOffsetInChip == 0 )
   {
      *ulStartOff = 0x0;
      *ulEndOff = (*ulStartOff + 0x4000) - 1;
   }
   else if( nSectorOffsetInChip == 1 )
   {
      *ulStartOff = 0x4000;
      *ulEndOff = (*ulStartOff + 0x2000) - 1;
   }
   else if( nSectorOffsetInChip == 2 )
   {
      *ulStartOff = 0x6000;
      *ulEndOff = (*ulStartOff + 0x2000) - 1;
   }
   else if( nSectorOffsetInChip == 3 )
   {
      *ulStartOff = 0x8000;
      *ulEndOff = (*ulStartOff + 0x8000) - 1;
   }
   else if( (nSectorOffsetInChip >= 4) && (nSectorOffsetInChip < = 18) )
   {
      *ulStartOff = (nSectorOffsetInChip - 3) * 0x10000;
      *ulEndOff = (*ulStartOff + 0x10000) - 1;
   }
   else
      return INVALID_SECTOR;
   return NO_ERR;
}
 
//----------- GetFlashStartAddress ----------//
// НАЗНАЧЕНИЕ
//    Получение начального адреса flash по абсолютному адресу.
//
// Входные параметры:
//    unsigned long ulAddr - абсолютный адрес
//
// Возвращаемое значение: адрес
unsigned long GetFlashStartAddress( unsigned long ulAddr)
{
   ERROR_CODE ErrorCode = NO_ERR;
   unsigned long ulFlashStartAddr;     //начальный адрес flash
   unsigned long ulSectStartAddr;      //адрес начала сектора
   unsigned long ulSectEndAddr;        //адрес конца сектора
   unsigned long ulMask;               //маска смещения
 
   // Получение начального адреса flash из абсолютного адреса
   GetSectorStartEnd( &ulSectStartAddr, &ulSectEndAddr, (gNumSectors-1));
   ulMask            = ~(ulSectEndAddr);
   ulFlashStartAddr  =  ulAddr & ulMask;
   return(ulFlashStartAddr);
}
 
//----------- ReadFlash ----------//
// НАЗНАЧЕНИЕ
//    Читает данные из flash по указанному адресу.
//
// Входные параметры:
//    unsigned long ulAddr - адрес, откуда нужно читать
//    u16* pnValue - указатель, куда нужно сохранить данные
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE ReadFlash( unsigned long ulAddr, unsigned short *pusValue )
{
   // Запрет прерываний перед выполнением операции load или store
   // [см. предупреждение на странице 6-71 руководства ADSP-BF533]
   unsigned int uiSaveInts = cli();
   // Установка нашего адреса flash, откуда хотим читать:
   unsigned short *pFlashAddr = (unsigned short *)(ulAddr);
   // Чтение значения:
   *pusValue = (unsigned short)*pFlashAddr;
   // Разрешение прерываний
   sti(uiSaveInts);
   return NO_ERR;
}
 
//----------- WriteFlash ----------//
// НАЗНАЧЕНИЕ
//    Запись значения по указанному адресу flash.
//
// Входные параметры:
//    unsigned long  ulAddr - адрес, куда нужно записывать
//    unsigned short nValue - значение для записи
//
// Возвращаемое значение:
//    ERROR_CODE - значение кода ошибки, если она была
//    NO_ERR     - или если ошибки не было
ERROR_CODE WriteFlash( unsigned long ulAddr, unsigned short usValue )
{
   // Запрет прерываний перед выполнением операции load или store
   // [см. предупреждение на странице 6-71 руководства ADSP-BF533]
   unsigned int uiSaveInts = cli();
   // Установка адреса
   unsigned short *pFlashAddr = (unsigned short *)(ulAddr);
   *pFlashAddr = usValue;
   // Разрешение прерываний
   sti(uiSaveInts);
   return NO_ERR;
}

void fwupdate_routine (void)
{
   static u8 *dst, *src;
   static u32 bytecnt, progresscnt;
   u32 portion;
   static int sector;
   ERRORCODE errcode;
   static clock_t fwtimestamp = 0;
    
   if (MODE_FIRMWARE_UPDATE_QUESTION == consolestate)
   {
      char *verinfo;
      //выводим версию прибора в прошивке
      msg("ПО в приборе    ");
      verinfo = findmarker((u8*)FLASH_ADDRESS_LDR_MAINAPP,
                           DSP_VERSION_MARKER,
                           FLASH_END-FLASH_ADDRESS_LDR_MAINAPP);
      if (verinfo)
      {
         msg("%s", VersionDecode(VersionEncode(verinfo)));
      }
      else
      {
         msg("???");
      }
      msg("\r\n")
      //выводим версию прибора в загруженном firmware
      msg("ПО для прошивки ");
      verinfo = findmarker(ldrbuffer, DSP_VERSION_MARKER, sizeof(ldrbuffer));
      if (verinfo)
      {
         msg("%s", VersionDecode(VersionEncode(verinfo)));
      }
      else
      {
         msg("???");
      }
      msg("\r\n")
      msg("    Перепрошить прибор?\r\n");
      fwtimestamp = clock() + MS_TICKS * 30000;
      consolestate = MODE_FIRMWARE_WAIT_USER_ACCEPT_UPDATE;
   }
   else if (MODE_FIRMWARE_WAIT_USER_ACCEPT_UPDATE == consolestate)
   {
      if(fwtimestamp < clock())
      {
         msg("Время ожидания истекло\r\n");
         fwtimestamp = clock() + MS_TICKS * 1000;
         consolestate = MODE_FIRMWARE_WAIT_EXIT;
      }
   }
   else if (MODE_FIRMWARE_WAIT_EXIT == consolestate)
   {
      if(fwtimestamp < clock())
      {
         msg("Выход\r\n");
#ifdef __RELEASE__
         SoftwareReset();
#else
         consolestate = CONSCMDDECODE;
#endif
      }
   }
   else if (MODE_FIRMWARE_SEEK_TO_DSP_DATA == consolestate)
   {
////////////////////////////////////////////////////////////////
// подготавливаем все для записи во flash
      ldrblock = CheckFirmware(ldrbuffer, 0, &errcode);
      if (ERROR_NONE==errcode)
      {
         bytecnt = ldrblock->sizeldrfile + sizeof(TLdrBlock);
         progresscnt = 0;
         src = ldrbuffer;
         dst = (u8*)FLASH_ADDRESS_LDR_MAINAPP;
         sector = 0;
         consolestate = MODE_FIRMWARE_WRITE;
      }
      else
         consolestate = CONSCMDDECODE;
      msg(errormsg(errcode));
   }
   else if (MODE_FIRMWARE_WRITE == consolestate)
   {
////////////////////////////////////////////////////////////////
// основной цикл записи во flash
      if (bytecnt)
      {
         if (bytecnt >= FLASH_SECTOR_SIZE64K)
            portion = FLASH_SECTOR_SIZE64K;
         else
            portion = bytecnt;
         u32 secStart, secEnd;
         //Поиск номера сектора, который бы соответствовал dst:
         do
         {
            GetSectorStartEnd(&secStart, &secEnd, sector);
            if ((u8*)(secStart+FLASH_BEGIN) == dst)
               break;
            else
               sector++;
         }while (sector < = FLASH_LAST_SEC_NUM);
         if (sector < = FLASH_LAST_SEC_NUM)
         {
            EraseBlock(sector);
            Write_Data((u32)dst, (u16*)src, portion/2);
            bytecnt -= portion;
            progresscnt  += portion;
            src += portion;
            dst += portion;
            msg("Записано %u байт\r", progresscnt);
         }
         else
         {
            msg("Ошибка: последний сектор %i\r\n", sector);
            fwtimestamp = clock() + MS_TICKS * 3000;
            consolestate = MODE_FIRMWARE_WAIT_EXIT;
         }
      }
      else
      {
         msg ("\r\nПроверка записанного кода...\r\n");
         CheckFirmware((u8*)FLASH_ADDRESS_LDR_MAINAPP, 0, &errcode);
         if (ERROR_NONE == errcode)
         {            
            msg("OK\r\n");
         }
         msg(errormsg(errcode));
         fwtimestamp = clock() + MS_TICKS * 3000;
         consolestate = MODE_FIRMWARE_WAIT_EXIT;
      }
   }
}

Обратите внимание на процедуру Write_Data, которая записывает данные в память FLASH. Здесь есть одна тонкость - шина данных у FLASH процессора ADSP-BF538F имеет ширину 16 бит, поэтому данные пишутся не побайтно, а словами по 16 бит. Соответственно процедура Write_Data принимает в качестве 3 параметра размер блока данных в словах, а не в байтах (по этой причине количество байт portion делится на 2). Важно помнить о том, что если portion окажется нечетным числом, то последний байт не запишется! В данном примере четность portion всегда выполняется, так что дополнительная проверка на четность не нужна.

Если же может быть ситуация, когда надо записать нечетное количество байт, то количество слов дожно быть получено как поделенное количество байт поделенное на 2 плюс единица. Количество записываемых во FLASH слов можно получить по общей формуле, автоматически учитывающей четность:

int sizewords = sizebytes/2 + sizebytes%2;

В этой формуле sizebytes равно количеству записываемых байт, а sizewords будет равно соответствующему количеству записываемых слов.

[Мост USB-UART]

Мост USB - UART может быть реализован на любой популярной микросхеме, например CH341A, CP210x, FT232RL, FT232X, FT2232H и т. п. Готовые аппаратные мосты в виде законченных модулей можно недорого приобрести на сайтах типа aliexpress, dx.com и ebay.

Некоторые из микросхем мостов USB - TTL UART реализуют режимы bit-bang [7], что позволит не только управлять через USB процессом перезагрузки и запуском загрузчика, можно даже реализовать интерфейс JTAG или ISP для полной перепрошивки прибора через USB. Тогда не нужны вообще никакие загрузчики для перепрошивки, нужно только подключение через USB. Но это уже совсем другая история.

[Подготовка прошивки mainapp для передачи через XMODEM]

Код bootloader, когда он получит прошивку (LDR-файл) по протоколу XMODEM, должен иметь возможность проверить отсутствие ошибок при передаче. Также bootloader должен отобразить на экране для пользователя версию загруженной прошивки, чтобы можно было принять решение для обновления прошивки.

Для этой цели к двоичному файлу в формате LDR в конец добавляется стандартный для формата LDR дополнительный блок, в котором содержится контрольная сумма всего загружаемого через XMODEM файла и информация о размере. На стадии формирования файла прошивки может быть реализовано шифрование по одному из известных алгоритмов (например, как это описано в [8]). В таком случае на стороне загрузчика bootloader должна быть реализована обратная дешифровка кода прошивки.

Ниже на скриншоте показан дополнительный LDR-блок, который благодаря стандартному формату распознает утилита LdrViewer [9]. Это блок типа IGNORE (см. [3, 4]), который не будет загружен в память загрузчиком BootROM, зато будет считан кодом bootloader. Маркер информационного блока в виде уникального текста "FLASHCRC16" предназначен для того, чтобы код мог легко этот блок найти.

LdrViewer added ignore block

Файл прошивки необязательно передавать через XMODEM, можно также воспользоваться обычным JTAG-программатором среды VisualDSP (через меню Tools -> Flash Programmer...).

VisualDSP Tools Flash Programmer

Для подготовки прошивки была написана утилита makefirmware.exe, которая принимает на входе LDR-файл основной программы mainapp, и генерирует на выходе другой LDR-файл, к которому в конец приписан стандартный информационный блок LDR-файла с контрольной суммой и данными о размере прошивки.

////////////////////////////////////////////////////////////////////////////
// makefirmware.exe: утилита добавляет к LDR-файлу хвост с контрольной
// суммой и информацией о размере прошивки (консольная программа,
// созданная в среде VisualStudio C++ 2010).
#include "stdafx.h"
#include "crc16.h"
 
//Коды ошибок
#define ERRCODE_OK      0
#define ERRCODE_MFC     1
#define ERRCODE_BADARG  2
#define ERRCODE_BADSRC  3
#define ERRCODE_MALLOC  4
#define ERRCODE_READSRC 5
 
int exiterrcode = ERRCODE_OK;
CWinApp theApp;
CString csSRCfile = CString("");    //Имя входного LDR-файла.
CString csDSTfile = CString("");    //Имя выходного файла.
 
#pragma pack(push)
#pragma pack(1)
struct TLdrBlock
{
   u32 ADDRESS;
   u32 COUNT;
   u16 FLAG;
   char FLASHCRC16 [10];
   u32 sizeldrfile;
   u16 crc16;
};
#pragma pack(pop)
 
static void Usage (void)
{
   CString csExe;
   csExe = AfxGetAppName();
   printf("Utility for add CRC (CRC16 CCITT) to Blackfin LDR-file. Usage:\n");
   printf("%s srcfile.ldr [firmware.bin]\n", CStringA(csExe.GetString()));
   exiterrcode = ERRCODE_BADARG;
}
 
static u64 filesize (CString filename)
{
   u64 result = 0;
   try
   {
      CStdioFile file( filename,
         CFile::modeRead|CFile::typeBinary );
      result = file.GetLength();
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
   }
   return result;
}
 
static UINT fileread (u8* data, CString filename, UINT size)
{
   UINT result = 0;
   try
   {
      CStdioFile file( filename,
         CFile::modeRead|CFile::typeBinary );
      result = file.Read(data, size);
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
   }
   return result;
}
 
static bool filewrite (u8* data, CString filename, UINT size)
{
   bool result = true;
   try
   {
      CStdioFile file( filename,
         CFile::modeCreate|CFile::modeWrite|CFile::typeBinary );
      file.Write(data, size);
      file.Close();
   }
   catch(CFileException* pe)
   {
      TRACE(_T("File could not be opened, cause = %d\n"),
         pe->m_cause);
      pe->Delete();
      result = false;
   }
   return result;
}
 
static void ParseCommandLine (int argc, TCHAR* argv[])
{
   if (1==argc)
   {
      Usage();
      printf("source file?");
      exiterrcode = 1;
   }
   else if (2 == argc)
   {
      csSRCfile=CString(argv[1]);
      csDSTfile="firmware.bin";
   }
   else if (3 <= argc)
   {
      csSRCfile=CString(argv[1]);
      csDSTfile=CString(argv[2]);
   }
}
 
int _tmain(int argc, _TCHAR* argv[])
{
   u8* data;
 
   TLdrBlock ldrblock =
   {
      0xFF800060,
      16,
      0x0012,
      {'F','L','A','S','H','C','R','C','1','6'},
      0,    //size
      0     //crc16
   };
 
   // initialize MFC and print and error on failure
   if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
   {
      _tprintf(_T("Fatal Error: MFC initialization failed\n"));
      exiterrcode = ERRCODE_MFC;
   }
   else
   {
      while(true)
      {
         csExeName = AfxGetAppName();
         ParseCommandLine(argc, argv);
         if (exiterrcode)
            break;
         //Вычисление размера входного файла:
         ldrblock.sizeldrfile = (u32)filesize(csSRCfile);
         if (0==ldrblock.sizeldrfile)
         {
            printf("BAD SRC file: %s, not exist?", csSRCfile);
            exiterrcode = ERRCODE_BADSRC;
            break;
         }
         printf("input file size: %u, ", ldrblock.sizeldrfile);
         //Выделение памяти под массив данных файла:
         size_t datasize = ldrblock.sizeldrfile + sizeof(TLdrBlock);
         data = (u8*)malloc(datasize);
         if (NULL == data)
         {
            printf("malloc %u bytes error", datasize);
            exiterrcode = ERRCODE_MALLOC;
            break;
         }
         //Чтение файла:
         UINT readed = fileread (data, csSRCfile, ldrblock.sizeldrfile);
         if (ldrblock.sizeldrfile != readed)
         {
            //log   ("read file %s error, readed %u", csSRCfile, readed);
            printf("read file %s error, readed %u", csSRCfile, readed);
            exiterrcode = ERRCODE_READSRC;
            break;
         }
         //Копирование блока контрольной суммы в конец:
         memcpy(&data[ldrblock.sizeldrfile], &ldrblock, sizeof(TLdrBlock));
         //Вычисление CRC16 от всего массива, кроме последних 2 байт:
         ldrblock.crc16 = CRC_START_VAL;
         AddCRC16(data, ldrblock.sizeldrfile + sizeof(TLdrBlock) - 2, &ldrblock.crc16);
         printf("CRC16 %04X", ldrblock.crc16);
         //Поместим контрольную сумму в блок данных
         memcpy(&data[ldrblock.sizeldrfile+sizeof(TLdrBlock)-2], &ldrblock.crc16, 2);
         //Сохранение файла:
         if (!filewrite(data, csDSTfile, datasize))
         {
            printf("write file %s error", csDSTfile);
            exiterrcode = ERRCODE_READSRC;
            break;
         }
         free(data);
         break;
      }
   }
   return exiterrcode;
}

[Ссылки]

1. Blackfin ADSP-BF538.
2. ADSP-BF538: интерфейс UART.
3. Как происходит загрузка ADSP-BF533 Blackfin.
4. Blackfin: утилита elfloader.exe.
5. Обзор протокола XMODEM.
6. ADSP-BF538: блок интерфейса внешней шины.
7. Режимы BitBang для микросхем FT232R и FT245R.
8. Atmel AVR230: DES Bootloader. Atmel AVR231: AES Bootloader.
9. Утилита просмотра файла загрузки Blackfin.