 // Файл с API функциями работы с FLASH-чипом AT45DB161E
 // v 1.61 от 14/10/19
 // Автор: Сычев А.
 #define AT45DB161E_API_C
 #define AT45DB161E_APILEVEL   // использование интерфейса низкоуровневых функций
 #include "drivers\flash\at45\config\AT45DB161E_CONF.h"
 #include "drivers\flash\base\flash_api_error.h"
 #include "drivers\flash\base\flash_api_types.h"
 #include "drivers\flash\base\flash_api_base.h"
 #include "drivers\flash\at45\lowlevel\AT45DB161E_GLOB.h"
 #include "drivers\flash\at45\lowlevel\AT45DB161E_LL_func.h"
 #include "drivers\flash\at45\common\AT45DBXXX_HAL.h"

 static const char AT45FLASH_TEXTUAL_DESCRIPTION[] = "AT45DB161E";

 static const flash_properties_t FlashProperties_AT45DB161E =
 {
    .minAddress  = AT45FLASH_MINIMUM_ADDRESS,
    .maxAddress  = AT45FLASH_MAXIMUM_ADDRESS,
    .maxSectors  = AT45FLASH_SECTORS,
    .sectorSize  = (AT45FLASH_PAGES_PER_SECTOR * AT45FLASH_PAGE_SIZE),
    .pChipDescription = AT45FLASH_TEXTUAL_DESCRIPTION
 };

 // flash_api_initialize - инициализация драйвера и API работы с устройством
 static flash_err_t flash_api_initialize( bool detectOnly );

 // flash_api_getready - проверка готовности устройства (со стандартным таймаутом)
 static flash_err_t flash_api_getready();

 // flash_api_service - функция получения общих сведений об флеш-памяти.
 static flash_err_t flash_api_service( flash_service_info_t si, void * pdata, __FLASH_WORD * pbufsize );

 // flash_api_protect - установить защиту секторов
 static flash_err_t flash_api_protect( flash_api_protect_t * content );

 // flash_api_protect - снять защиту секторов
 static flash_err_t flash_api_unprotect( flash_api_protect_t * content );

 // flash_api_protect_ex - установить/снять защиту секторов
 static flash_err_t flash_api_protect_ex( flash_api_protect_t * content, flash_api_protect_bits mode );

 // flash_api_getprotect - получить информацию о защищенных секторах.
 static flash_err_t flash_api_getprotect( flash_api_getprotect_t * content );

 // flash_api_erase - мультирежимное стирание данных
 static flash_err_t flash_api_erase( flash_erase_mode_t mode, __FLASH_DWORD start, __FLASH_DWORD count );

 // flash_api_write - мультирежимная запись данных по адресу
 static flash_err_t flash_api_write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size, flash_write_mode_t mode );

 // flash_api_read - чтение данных по адресу
 static flash_err_t flash_api_read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );

 // flash_api_sleepmode - переход в режим пониженного энергопотребления
 static flash_err_t flash_api_sleepmode();

 // flash_api_wakeup - выход из режима пониженного энергопотребления
 static flash_err_t flash_api_wakeup();

 // flash_api_finalize - деинициализация драйвера
 static flash_err_t flash_api_finalize( bool bHibernate, bool bForce );

 // дескриптор API
 const flash_api_descriptor_t AT45DB161E_API = 
 {
   .routines = {
     .flashInitialize = flash_api_initialize,
     .flashGetReady   = flash_api_getready,
     .flashService    = flash_api_service,
     .flashProtect    = flash_api_protect,
     .flashUnprotect  = flash_api_unprotect,
     .flashProtectEx  = flash_api_protect_ex,
     .flashGetProtect = flash_api_getprotect,
     .flashErase      = flash_api_erase,
     .flashWrite      = flash_api_write,
     .flashRead       = flash_api_read,
     .flashSleepMode  = flash_api_sleepmode,
     .flashWakeup     = flash_api_wakeup,
     .flashFinalize   = flash_api_finalize,
   },

   .flashProperties = &FlashProperties_AT45DB161E
 };

// Enter() и Return(.) - макросы блокировки ресурса в многозадачных ОС
// При входе в критический участок кода - выполнение API функции - вызывается Enter(),
// блокируя ресурс, при выходе - Leave() или Return(.) ( для выхода из функции )
#define Enter()              { __FLASH_LOCK();   }
#define Leave()              { __FLASH_UNLOCK(); }
#define Return(code)         { __FLASH_UNLOCK(); return (code); }

//#define ret                  __FLASH_UNLOCK(); return

 // Done:
 //   flash_api_initialize(...)  // Высокоуровневая инициализация памяти (установка размера страницы, установка параметров защиты секторов...)
 //   flash_api_erase(...)       // стирание памяти (блоками, страницами или секторами)
 //   flash_api_read(...)        // чтение памяти по адресу
 //   flash_api_protect(...)     // защита секторов (модификация конфигурации защищаемых секторов)
 //   flash_api_unprotect(...)   // снятие защиты (модификация конфигурации защищаемых секторов)
 //   flash_api_service(...)     // получение информации о производителе, расширенной информации, размере памяти, размере страницы, количестве секторов, блоков, страниц и т.п.
 //   flash_api_write(...)       // запись памяти по адресу

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_getready - проверка готовности устройства (со стандартным таймаутом)
 //
 // # Функция возвращает FLERR_SUCCESS, еслиу устройство готово, иначе FLERR_TIMEOUT
 static flash_err_t flash_api_getready()
 {
     Enter();

     if( AT45_LL_Routines.smart_waitms( AT45DBXXX_API_TIMEOUT )  )
     {
         Return(FLERR_SUCCESS);
     }

     Return(FLERR_TIMEOUT);
 }

 static flash_err_t flash_api_getready_time( __FLASH_WORD timeout )
 {
     Enter();

     if( AT45_LL_Routines.smart_waitms( timeout )  )
     {
         Return(FLERR_SUCCESS);
     }

     Return(FLERR_TIMEOUT);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_initialize - инициализация драйвера и API работы с устройством
 // * Если @detectOnly=true, производится только детектирование наличия чипа!
 // # Функция возвращает FLERR_SUCCESS. Только в этом случае гарантируется нормальная работа.
 // # Функция возвращает ошибку инициализации при появлении сбоев или при непрохождении проверки (см файл конфигурации AT45DB161E_CONF.h)
 static flash_err_t flash_api_initialize( bool detectOnly )
 {
    // производится определение наличия чипа на шине
    if( detectOnly )
    {
        return AT45_LL_Routines.startupDetect();
    }

    // производим нисзкоуровневую инициализацию: подаем питание и будим, проверяем наличие чипа, серию, производителя, размер...
    flash_err_t success = AT45_LL_Routines.initialize();

    if( FLASH_ERROR(success) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    // отключаем защиту секторов при инициализации (общий бит защиты)
    success = flash_api_unprotect( NULL );

    if( FLASH_ERROR(success) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    // считываем статус
    if( ! AT45_LL_Routines.smart_waitms( AT45DBXXX_API_TIMEOUT ) )
    {
        // что-то пошло не так: ай печаль-печаль.
        return(success);
    }

    return(FLERR_SUCCESS);
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_finalize - безопасная де-инициализация драйвера
 // Функция завершает работу с флеш-памятью, отключаяя ее или переводя в режим гибернации
 //  @bHibernate: булевый флаг, показывает, что нужно перевести флеш-память в режим гибернации,
 //    если false - драйвер попытается отключить питание микросхемы, если разрешено управление питанием (AT45DBXXX_POWER_MANAGEMENT)
 //    если false и управление питанием запрещено, будет возвращена ошибка FLERR_INVALID_PARAMETER.
 //  @bForce: насильное выполнение финализации в случае, если флеш-память занята незаконченной операцией.
 //    если false и драйвер детектирует незаконченную операцию, будет возвращена ошибка FLERR_UNEXPECTED_BUSY.
 // Если выбран режим гибернации, после выдачи команды функция проверяет, уснула ли микросхема через чтение статуса.
 // Если в этом случае обнаруживается, что статус все еще считывается, генерируется ошибка FLERR_GENERIC_ERROR.
 // Данную проверку можно отключить опцией AT45DBXXX_DONOTCHECK_HIBERNATE=1.
 // Внимание: проверка не реализуется если bHibernate=false.
 // # Функция возвращает FLERR_SUCCESS всегда, за исключением вышеописанных случаев.
 static flash_err_t flash_api_finalize( bool bHibernate, bool bForce )
 {
     Enter();

     // подготовка к деинициализации
     flash_err_t status = AT45_LL_Routines.finalize_prepare();

     if( FLASH_ERROR(status) )
     {
         // ошибка, обнаружена проблема - устройство не готово

         // проверяем, разрешено ли принудительное завершение?
         if( bForce )
         {
             // разрешено, сначала производим сброс

             // доступно управление сигналом сброса?
             #if AT45DBXXX_RESET_MANAGEMENT
                 // да, производим сброс чипа
                 AT45_LL_Routines.reset_pulse();
             #else
                 // нет, а доступно управление питанием?
                 #if AT45DBXXX_POWER_MANAGEMENT
                 // да, производим быстрый сброс чипа по питанию
                 AT45_LL_Routines.power_pulse( 0, 0 );
                 #else
                 // нет, управление питанием тоже недоступно
                 // возвращаем ошибку FLERR_INVALID_PARAMETER - невозможно выполнить принудительное отключение
                 Return(FLERR_INVALID_PARAMETER);
                 #endif
             #endif
         }
         else
         {
             // нет, принудительное завершение запрещено
             // возвращаем ошибку
             Return(status);
         }
     }

     // все нормально, продолжаем

     // выбран режим гибернации?
     if( bHibernate )
     {
         // отправляем микросхему поспать
         AT45_LL_Routines.sleepmode();

         #if AT45DBXXX_DONOTCHECK_HIBERNATE == 0
         // проверяем, ответит ли микросхема?
         if( AT45_LL_Routines.smart_waitms( AT45DBXXX_API_TIMEOUT ) )
         {
             // отвечает - бит готовности прочитался...
             // Может это просто подтянутый сигнал к +VCC?
             // попробуем по другому:

             if( FLASH_SUCCESS(AT45_LL_Routines.dirtyDetect()) )
             {
                 // ответила! значит точно не ложилась спать - ошибка
                 Return(FLERR_DEVICE_MALFUNCTION);
             }
         }
         #endif
     }
     else
     {
         // доступно управление питанием?
         #if AT45DBXXX_POWER_MANAGEMENT == 0
         // нет, недоступно, нечего делать, генерируем ошибку
         Return(FLERR_INVALID_PARAMETER);
         #else
         // Доступно, отключаем питание.
         AT45_LL_Routines.power_off();

         // не проверяем доступность, доверяем функции отключения питания
         #endif
     }

     Return(FLERR_SUCCESS);
 }

 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_service - функция получения общих сведений об флеш-памяти.
 // Полный список возможных запросов приведен ниже:
 //        ЗАПРОС        РАЗМЕР   ТИП       ТИП ИНФОРМАИИ
 //                      БУФЕРА   ЗНАЧНИЯ
 //   fsvc_getVendorId      2   __FLASH_WORD            ID производителя
 //   fsvc_getDeviceId      2   flash_deviceid_t        ID устройства
 //   fsvc_getExtInfo       *   __FLASH_BYTE[]          расширенную информацию производителя
 //   fsvc_getExtInfoLen    1   __FLASH_BYTE            длинну расширенной информации производителя
 //   fsvc_getCapacity      4   __FLASH_DWORD           заявляемая по документации емкость устройства (MBit-s)
 //   fsvc_getFullCapacity  4   __FLASH_DWORD           полная емкость устройства (в байтах)
 //   fsvc_getDataCapacity  4   __FLASH_DWORD           доступная емкость устройства (в байтах)
 //   fsvc_getProtect       1   __FLASH_BYTE            информацию о защите секторов (общий бит защиты 0x00 или 0x01)
 //   fsvc_getSectorsCount  2   __FLASH_WORD            количество секторов
 //   fsvc_getPagesCount    2   __FLASH_WORD            количество страниц
 //   fsvc_getBlocksCount   2   __FLASH_WORD            количество блоков
 //   fsvc_getPageSize      2   __FLASH_WORD            размер страницы
 //   fsvc_getReadyState    1   __FLASH_BYTE            готовность устройства
 //   fsvc_getMinAddress    4   __FLASH_DWORD           минимально возможный линейный адрес
 //   fsvc_getMaxAddress    4   __FLASH_DWORD           максимально возможный линейный адрес

 //
 // * Параметр @si задает тип запроса (см. flash_service_info_t)
 // * Параметр @pdata должен указывать на заранее выделенный буфер памяти для сохранения
 //   получаемых данных. Размер буфера зависит от типа запроса. Параметр не может быть NULL
 // * Параметр @pbufsize должен указывать на размер буфера, на который указывает pdata.
 //   Параметр не может быть 0. Значение по указателю @pbufsize не может быть 0.
 //   При успешном выполнении функции, по указателю @pbufsize будет записано фактическое количество
 //   записанных в буфер данных.
 //
 // * Функция НЕ ОБНУЛЯЕТ память по указателю @pdata в случае, если передан слишком большой буфер.
 //
 // # Функция вернет ошибку FLERR_INVALID_PARAMETER при некорректном запросе @si
 // # Функция вернет ошибку FLERR_INVALID_PARAMETER если @pdata или @bufsize равны 0.
 // # Функция вернет ошибку FLERR_INVALID_BUFFER если размер буфера @size не достаточен для сохранения данных
 //
 // # Функция вернет FLERR_SUCCESS при отсутствии ошибок
 //
 static flash_err_t flash_api_service( flash_service_info_t si, void * pdata, __FLASH_WORD * pbufsize )
 {
    if( !pdata || !pbufsize || !*pbufsize ) return(FLERR_INVALID_PARAMETER);

    Enter();

    switch( si )
    {
       case fsvc_getVendorId:        // ID производителя
       case fsvc_getDeviceId:        // ID устройства
       case fsvc_getCapacity:        // заявляемая по документации емкость устройства (MBit-s)
       case fsvc_getDataCapacity:    // доступная емкость устройства
       case fsvc_getFullCapacity:    // полная емкость устройства
       {
            __FLASH_DWORD flashID =  AT45_LL_Routines.manufactureridread( 0 );

            switch( si )
            {
                 case fsvc_getVendorId:     if( sizeof( __FLASH_WORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_WORD*)(pdata)) = ((__flash_rvid_t*) &flashID)->FullId;
                 *pbufsize                 = sizeof(__FLASH_WORD);

                 Return(FLERR_SUCCESS);
                 //-------------------------
                 case fsvc_getDeviceId:     if( sizeof( __FLASH_WORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((flash_deviceid_t*)(pdata)) = ((__flash_rvid_t*) &flashID)->DevId;
                 *pbufsize                 = sizeof(__FLASH_WORD);

                 Return(FLERR_SUCCESS);
                 //-------------------------
                 case fsvc_getCapacity:     if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = __FLASH_DENSITY2CAPACITY( ((flash_deviceid_t*)&((__flash_rvid_t*) &flashID)->DevId)->Density );
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);

                 //-------------------------
                 case fsvc_getDataCapacity: if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = (AT45FLASH_MAXIMUM_ADDRESS - AT45FLASH_MINIMUM_ADDRESS);
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);

                 //-------------------------
                 case fsvc_getFullCapacity: if( sizeof( __FLASH_DWORD ) > *pbufsize )
                                            Return(FLERR_INVALID_BUFFER);

                 *((__FLASH_DWORD*)(pdata)) = (AT45FLASH_MAXIMUM_ADDRESS);
                 *pbufsize                  = sizeof(__FLASH_DWORD);

                 Return(FLERR_SUCCESS);
            }
       }
       break;

       case fsvc_getMinAddress:
       {
            if( sizeof( __FLASH_DWORD ) > *pbufsize )
            Return(FLERR_INVALID_BUFFER);

            *((__FLASH_DWORD*)(pdata)) = (AT45FLASH_MINIMUM_ADDRESS);
            *pbufsize                  = sizeof(__FLASH_DWORD);

            Return(FLERR_SUCCESS);
       }
       break;

       case fsvc_getMaxAddress:
       {
            if( sizeof( __FLASH_DWORD ) > *pbufsize )
            Return(FLERR_INVALID_BUFFER);

            *((__FLASH_DWORD*)(pdata)) = (AT45FLASH_MAXIMUM_ADDRESS);
            *pbufsize                  = sizeof(__FLASH_DWORD);

            Return(FLERR_SUCCESS);
       }
       break;

       //-------------------------
       case fsvc_getExtInfoLen:      // длинну расширенной информации производителя
       case fsvc_getExtInfo:         // расширенную информацию производителя
       {
            __flash_id_t  mid;

            switch( si )
            {
                 case fsvc_getExtInfoLen:

                       if( sizeof( __FLASH_BYTE ) > *pbufsize )
                       Return(FLERR_INVALID_BUFFER);

                       mid.pExtBf = 0;
                       mid.ExtLen = 0;

                       // получение ДЛИННЫ расширенной информации
                       AT45_LL_Routines.manufactureridread( &mid );

                       *((__FLASH_BYTE*)(pdata)) = mid.ExtLen;
                       *pbufsize                 = sizeof(__FLASH_BYTE);

                 Return(FLERR_SUCCESS);

                 case  fsvc_getExtInfo:

                       if( 1 * sizeof( __FLASH_BYTE ) > *pbufsize )
                       Return(FLERR_INVALID_BUFFER);

                       mid.pExtBf = pdata;
                       mid.ExtLen = *pbufsize;

                       // получение расширенной информации
                       AT45_LL_Routines.manufactureridread( &mid );

                       *pbufsize = mid.ExtLen;

                 Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getReadyState:      // готовность устройства
       {
            if( sizeof( __FLASH_BYTE ) <= *pbufsize )
            {
                *((__FLASH_BYTE*)(pdata)) = ( FLASH_SUCCESS( AT45_LL_Routines.getreadyfast() ) )? 0x01: 0x00;

                *pbufsize = sizeof(__FLASH_BYTE);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getProtect:         // информацию о защите секторов
       {
            if( sizeof( __FLASH_BYTE ) <= *pbufsize )
            {
                flash_err_t status = flash_api_getprotect(0);

                switch( status )
                {
                    case FLERR_PROTECT_DISABLED:

                        *pbufsize = sizeof(__FLASH_BYTE);

                        *((__FLASH_BYTE*)(pdata)) = 0;

                    Return(FLERR_SUCCESS);

                    case FLERR_PROTECT_ENABLED:

                        *pbufsize = sizeof(__FLASH_BYTE);

                        *((__FLASH_BYTE*)(pdata)) = 1;

                    Return(FLERR_SUCCESS);
                }

                Return(status);  // Ошибка?
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getPageSize:        // размер страницы
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = AT45FLASH_PAGE_SIZE;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getSectorsCount:    // количество секторов
       {
            if( sizeof( __FLASH_WORD ) <= *pbufsize ) // 16.04.19 Bug Fixed: changed condition ">=" to "<="
            {
                *((__FLASH_WORD*)(pdata)) = AT45FLASH_SECTORS;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getPagesCount:      // количество страниц
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = AT45FLASH_PAGES;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);

       case fsvc_getBlocksCount:     // количество блоков
       {
            if( sizeof( __FLASH_WORD ) >= *pbufsize )
            {
                *((__FLASH_WORD*)(pdata)) = AT45FLASH_BLOCKS;
                *pbufsize = sizeof(__FLASH_WORD);

                Return(FLERR_SUCCESS);
            }
       }
       Return(FLERR_INVALID_BUFFER);
    }

    Return(FLERR_INVALID_PARAMETER);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_protect_ex - установить/снять защиту секторов
 // Поробности см. описание flash_api_protect() / flash_api_unprotect()
 //
 // @mode: flash_sector_unprotected  - вызов из flash_api_unprotect()
 // @mode: flash_sector_protected    - вызов из flash_api_protect()
 //
 static flash_err_t flash_api_protect_ex( flash_api_protect_t * content, flash_api_protect_bits mode )
 {
     Enter();

     // проверяем устройство на занятость
     if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

     // проверяем на NULL переданный параметр
     if( !content )
     {
         if( mode == flash_sector_unprotected )
         {
            // параметр NULL - значит СНИМАЕМ общий бит защиты
            AT45_LL_Routines.writeprotect_disable();

            // проверяем, сборсился ли бит защиты
            if( AT45_LL_Routines.getprotectstate() != FLERR_PROTECT_DISABLED )
            Return(FLERR_GENERIC_ERROR);
         }
         else
         {
            // параметр NULL - значит УСТАНАВЛИВАЕМ общий бит защиты
            AT45_LL_Routines.writeprotect_enable();

            // проверяем, установился ли бит защиты
            if( AT45_LL_Routines.getprotectstate() != FLERR_PROTECT_ENABLED )
            Return(FLERR_GENERIC_ERROR);
         }

         Return(FLERR_SUCCESS);
     }
     else
     {
         // режим модификации общего бита защиты
         switch( content->protection_modify )
         {
           case flash_sector_protect_nomodify:

                // не трогаем общий бит защиты

           break;
           case flash_sector_protect_modify:

                // flash_sector_protected
                if( content->protection_enabled )
                {
                   // устанавливаем защиту
                    AT45_LL_Routines.writeprotect_enable();

                    if( AT45_LL_Routines.getprotectstate() != FLERR_PROTECT_ENABLED )
                    Return(FLERR_GENERIC_ERROR);
                }
                // flash_sector_unprotected
                else
                {
                   // снимаем защиту
                    AT45_LL_Routines.writeprotect_disable();

                    if( AT45_LL_Routines.getprotectstate() != FLERR_PROTECT_DISABLED )
                    Return(FLERR_GENERIC_ERROR);
                }

           break;

           default:
                // неправильный параметр в поле protection_modify
                Return(FLERR_INVALID_PARAMETER);
         }

         // получаем указатель на буфер драйвера
         __flash_protectionregister_t * pReg = AT45_LL_Routines.internal_getbuffer_protect();

         // зануляем память
         __FLASH_ZEROMEMORY( pReg, sizeof(__flash_protectionregister_t) );

         // считываем байты защиты
         AT45_LL_Routines.protectregister_read( pReg );

         //------------------------------------------------------
         // модифицируем значения регистра защиты

         // индивидуально для сектора 0a,0b
         if( content->sectors.s0 )  // flash_sector_protect_pick
         {
             // сектор выбран: устанавливаем или снимаем биты защиты
             if( mode == flash_sector_unprotected )
             {
                pReg->Sector0.prot_0a = __FLASH_PROTECTION_REG_S0_DISABLE;
                pReg->Sector0.prot_0b = __FLASH_PROTECTION_REG_S0_DISABLE;
             }
             else
             {
                pReg->Sector0.prot_0a = __FLASH_PROTECTION_REG_S0_ENABLE;
                pReg->Sector0.prot_0b = __FLASH_PROTECTION_REG_S0_ENABLE;
             }
             pReg->Sector0.reserved= 0;
         } else { /* не модифицировать статус защиты сектора */ }

         // модифицируем байты защиты
         for( int s = 1; s < __SECTORS_TYPICAL; ++s )
         {
              if( (content->sectors.bytes[ s/8 ])  &  ( 1<<(s%8)) )
              {
                   // сектор выбран: устанавливаем или снимаем биты защиты
                   if( mode == flash_sector_unprotected )
                      pReg->Sectors[ s ] = __FLASH_PROTECTION_REG_DISABLE;
                   else
                      pReg->Sectors[ s ] = __FLASH_PROTECTION_REG_ENABLE;

              } else { /* не модифицировать статус защиты сектора */ }
         }

         //------------------------------------------------------
         //
         // стираем регистр защиты
         AT45_LL_Routines.protectregister_erase();

         // проверяем готовность устройства
         #if AT45DB161E_BKGOPERATIONS
         if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_PGERS_ms ) )
         #else
         if( FLERR_TIMEOUT == flash_api_getready() )
         #endif
         {
             // фатальная ошибка: регистр защиты стерт
             // устройство занято. продолжать не можем
             //
             // для восстановления регистра защиты необходимо
             // вызвать функцию flash_error_restore()
             //
             Return(FLERR_UNEXPECTED_BUSY);
         }

         // записываем регистр защиты
         AT45_LL_Routines.protectregister_write( pReg );

         //------------------------------------------------------
         //
         // проверяем готовность устройства
         #if AT45DB161E_BKGOPERATIONS
         if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_PGPRG_ms ) )
         #else
         if( FLERR_TIMEOUT == flash_api_getready() )
         #endif
         {
             // Ошибка: запись длится дольше положенного времени
             Return(FLERR_TIMEOUT);
         }

         //------------------------------------------------------

         {  // №№№№№№ ПРОВЕРКА №№№№№№
            // считываем байты защиты
            AT45_LL_Routines.protectregister_read( pReg );

             // индивидуально для сектора 0a,0b
             if( content->sectors.s0 )  // flash_sector_protect_pick
             {
                 // сектор выбран: проверяем биты защиты
                 if( mode == flash_sector_unprotected )
                 {
                    if( pReg->Sector0.prot_0a != __FLASH_PROTECTION_REG_S0_DISABLE  ||
                        pReg->Sector0.prot_0b != __FLASH_PROTECTION_REG_S0_DISABLE )
                    {
                        Return(FLERR_GENERIC_ERROR);
                    }
                 }
                 else
                 {
                    if( pReg->Sector0.prot_0a != __FLASH_PROTECTION_REG_S0_ENABLE  ||
                        pReg->Sector0.prot_0b != __FLASH_PROTECTION_REG_S0_ENABLE )
                    {
                        Return(FLERR_GENERIC_ERROR);
                    }
                 }
             }

             // проверяем байты защиты
             for( int s = 1; s < __SECTORS_TYPICAL; ++s )
             {
                  if( (content->sectors.bytes[ s/8 ])  &  ( 1<<(s%8)) )
                  {
                       // сектор выбран: проверяем биты защиты
                       if( mode == flash_sector_unprotected )
                       {
                          if( pReg->Sectors[ s ] != __FLASH_PROTECTION_REG_DISABLE )
                          Return(FLERR_GENERIC_ERROR);
                       }
                       else
                       {
                          if( pReg->Sectors[ s ] != __FLASH_PROTECTION_REG_ENABLE )
                          Return(FLERR_GENERIC_ERROR);
                       }
                  }
             }
         }

         //------------------------------------------------------
         // успех.
         Return(FLERR_SUCCESS);
     }
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_protect - установить защиту секторов
 //
 // Функция модифицирует флаги защиты сектров выбранных пользователем в состояние "защищен".
 // Установите соответствующие флаги в структуре @content в состояние "1" для защиты сектора.
 // Функция также может модифицировать общий флаг защиты выбранных секторов. Для этого установите
 // желаемое состояние защиты в поле protection_enabled ( 0 - не защищено, 1 - защищено ) и установите поле
 // protection_modify в состояние "flash_sector_protect_modify" для того, чтобы общий флаг защиты был
 // изменен.
 //
 // * Для того, чтобы модифицировать только флаги защиты секторов без изменения общего флага защиты,
 //   установите поле protection_modify в состояние "flash_sector_protect_nomodify".
 //
 // * параметр @content может быть равен NULL
 //
 // * Для того, чтобы модифицировать только общий бит защиты
 //   БЕЗ модификации частных флагов защиты сектров:
 //      - Для УСТАНОВКИ общего бита защиты передайте NULL вместо параметра @content
 //      - Для УСТАНОВКИ/СНЯТИЯ общего бита защиты установите ВСЕ поля частных флагов защиты
 //        секторов (s0...s63) в состояние flash_sector_protect_nomodify, поле  protection_modify
 //        установите в "flash_sector_protect_modify", а поле protection_enabled в желаемое
 //        состояние флага защиты: flash_sector_protected или flash_sector_unprotected
 //
 // # Функция вернет FLERR_SUCCESS, если в процессе операции не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER в случае, если обнаружит ошибку в переданном параметре
 // # Функция вернет FLERR_UNEXPECTED_BUSY в случае, если процесс выполнения был прерван из-за неожиданного отказа устройства
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если общий бит защиты не был установлен из-за ошибки
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если хотя бы один из выбранных битов защиты не был установлен/сброшен
 //
 static flash_err_t flash_api_protect( flash_api_protect_t * content )
 {
     return( flash_api_protect_ex( content, flash_sector_protected ));
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_unprotect - снять защиту секторов. Функция аналогичная flash_api_protect(),
 // только имеет обратный эффект - снятие защиты с выбранных секторов.
 //
 // Функция модифицирует флаги защиты сектров выбранных пользователем в состояние "не защищен".
 // Установите соответствующие флаги в структуре @content в состояние "1" для снятия защиты сектора.
 // Функция также может модифицировать общий флаг защиты выбранных секторов. Для этого установите
 // желаемое состояние защиты в поле protection_enabled ( 0 - не защищено, 1 - защищено ) и установите поле
 // protection_modify в состояние "flash_sector_protect_modify" для того, чтобы общий флаг защиты был
 // изменен.
 //
 // * Для того, чтобы модифицировать только флаги защиты секторов без изменения общего флага защиты,
 //   установите поле protection_modify в состояние "flash_sector_protect_nomodify".
 //
 // * параметр @content может быть равен NULL
 //
 // * Для того, чтобы модифицировать только общий бит защиты
 //   БЕЗ модификации частных флагов защиты сектров:
 //      - Для СНЯТИЯ общего бита защиты передайте NULL вместо параметра @content
 //      - Для УСТАНОВКИ/СНЯТИЯ общего бита защиты установите ВСЕ поля частных флагов защиты
 //        секторов (s0...s63) в состояние flash_sector_protect_nomodify, поле  protection_modify
 //        установите в "flash_sector_protect_modify", а поле protection_enabled в желаемое
 //        состояние флага защиты: flash_sector_protected или flash_sector_unprotected
 //
 // # Функция вернет FLERR_SUCCESS, если в процессе операции не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER в случае, если обнаружит ошибку в переданном параметре
 // # Функция вернет FLERR_UNEXPECTED_BUSY в случае, если процесс выполнения был прерван из-за неожиданного отказа устройства
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если общий бит защиты не был установлен из-за ошибки
 // # Функция вернет FLERR_GENERIC_ERROR в случае, если хотя бы один из выбранных битов защиты не был установлен/сброшен
 //
 static flash_err_t flash_api_unprotect( flash_api_protect_t * content )
 {
     return( flash_api_protect_ex( content, flash_sector_unprotected ));
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_getprotect - получить информацию о защищенных секторах.
 // * Указатель @content может быть NULL.
 // # Возвращаемое значение - общий бит защиты protection_enabled.
 //   Если защита установлена - будет возвращен код FLERR_PROTECT_ENABLED
 //   Если защита снята - будет возвращен код FLERR_PROTECT_DISABLED
 // Если указатель @content не NULL, по адресу будет записана структура типа flash_api_getprotect_t,
 // содержащая информацию о защите сектора (один сектор на один бит), а поле protection_enabled
 // будет содержать общий бит защиты - flash_sector_protected (установлена) или flash_sector_unprotected (снята).

 static flash_err_t flash_api_getprotect( flash_api_getprotect_t * content )
 {
     Enter();

     __flash_status_t status;

     // пользователь хочет получить детализацию по защите секторов
     if( content )
     {
         if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

         // получаем указатель на буфер драйвера
         __flash_protectionregister_t * pReg = AT45_LL_Routines.internal_getbuffer_protect();

         // зануляем память
         __FLASH_ZEROMEMORY( pReg, sizeof(__flash_protectionregister_t) );

         // считываем байты защиты
         AT45_LL_Routines.protectregister_read( pReg );

         if( pReg->Sector0.prot_0a == __FLASH_PROTECTION_REG_S0_ENABLE ||
             pReg->Sector0.prot_0b == __FLASH_PROTECTION_REG_S0_ENABLE )
               content->sectors.s0 = 1;
         else  content->sectors.s0 = 0;

         for( int s = 1; s < __SECTORS_TYPICAL; ++s )
         {
              if( pReg->Sectors[ s ] == __FLASH_PROTECTION_REG_ENABLE )
              content->sectors.bytes[ s/8 ]  |=  ( 1<<(s%8) );
              else
              content->sectors.bytes[ s/8 ]  &= ~( 1<<(s%8) );
         }
     }

     AT45_LL_Routines.statusread(&status);

     if( status.bProtect )
     {
         if( NULL != content ) // fixed: 22/05/19
         content->protection_enabled = flash_sector_protected;

         Return(FLERR_PROTECT_ENABLED);
     }

     if( NULL != content ) // fixed: 22/05/19
     content->protection_enabled = flash_sector_unprotected;

     Return(FLERR_PROTECT_DISABLED);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_erase - мультирежимное стирание данных
 // * Параметр @mode задает режим стирания:
 //      mode==fem_byaddress - начальное смещение задается адресом в параметре @start, длинна отрезка - @count в байтах
 //                            При этом минимальный стираемый диапазон - размер страницы.
 //                            Страница, которой принадлежит адрес @start стирается полностью
 //                            Страница, которой принадлежит адрес @start + @count также стирается полность
 //                            @start не может быть меньше AT45FLASH_MINIMUM_ADDRESS
 //      mode==fem_bypage    - начальное смещение задается номером страницы в параметре @start.
 //                            Количество стираемых страниц задается параметром @count
 //                            @start не может быть меньше AT45FLASH_PAGES_PER_SECTOR
 //
 //      mode==fem_byblock   - начальное смещение задается номером блока в параметре @start
 //                            Количество стираемых блоков задается параметром @count
 //                            @start не может быть меньше AT45FLASH_BLOCKS_PER_SECTOR
 //
 //      mode==fem_bysector  - начальное смещение задается номером сектора в параметре @start
 //                            Количество стираемых секторов задается параметром @count.
 //                            @start не может быть равен 0.
 //
 // Адресное пространство с 0 по AT45FLASH_MINIMUM_ADDRESS является зарезервированным. Попытка стирания элементов
 // в этом диапазоне приведет к ошибке.
 //
 // * Параметр @count не может быть 0.
 //   Параметр @start не должен указывать на зарезервированную область.
 //
 // ! Функция НЕ проверяет защиту секторов !
 //
 // # Функция возвращает FLERR_SUCCESS если не произошло ошибок.
 // # Функция вернет FLERR_INVALID_PARAMETER если @start указывает на зарезервированную область.
 // # Функция вернет FLERR_INVALID_PARAMETER если диапазон стирания выходит за границу адресного пространства
 // # Функция вернет FLERR_INVALID_PARAMETER если @count равняется 0.
 // # Функция вернет FLERR_INVALID_PARAMETER если @mode имеет значение, отличное от перечисленных.
 // # Функция вернет FLERR_UNEXPECTED_BUSY если во время операции устройство перестало отвечать
 //
 static flash_err_t flash_api_erase( flash_erase_mode_t mode, __FLASH_DWORD start, __FLASH_DWORD count )
 {
    if( !count || !start ) return(FLERR_INVALID_PARAMETER);

    Enter();

    switch( mode )
    {
       case  fem_byaddress:   // стирание по адресу

             if( start <  AT45FLASH_MINIMUM_ADDRESS )         Return(FLERR_INVALID_PARAMETER);
             if( start + count >  AT45FLASH_MAXIMUM_ADDRESS ) Return(FLERR_INVALID_PARAMETER);

       case  fem_bypage:     // стирание по страницам
       {
             __FLASH_DWORD PagesToErase=0;
             __FLASH_DWORD PageBegin   =0;

             if( mode == fem_byaddress )
             {
                 // стирание по адресу
                 PagesToErase = __FLASH_PAGESINRANGE( start , count );
                 PageBegin    = __FLASH_ADDRESS2PAGE( start );
             }
             else
             {
                 // стирание по страницам
                 if( start < AT45FLASH_PAGES_PER_SECTOR )  Return(FLERR_INVALID_PARAMETER);
                 if( start + count >  __FULL_PAGES  )  Return(FLERR_INVALID_PARAMETER);

                 PagesToErase = count;
                 PageBegin    = start;
             }

             while( PagesToErase )
             {
                    // проверяем, нельзя ли использовать блочное стирание?
                    if( __FLASH_PAGE_ARRAGED_BY_BLOCK ( PageBegin ) &&     // проверяем выравниваение
                      ( PagesToErase >= AT45FLASH_PAGES_PER_BLOCK )  )         // проверяем количество страниц
                    {
                        // можно!

                        // стираем блок - это быстрее
                        AT45_LL_Routines.block_erase( __FLASH_PAGE2BLOCK( PageBegin ) );

                        #if AT45DB161E_BKGOPERATIONS
                        if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_BKERS_ms ) )
                        #else
                        if( FLERR_TIMEOUT == flash_api_getready() )
                        #endif
                        {
                            // фатальная ошибка: устройство занято дольше обычного
                            Return(FLERR_UNEXPECTED_BUSY);
                        }

                        PageBegin    += __PAGES_PER_BLOCK;
                        PagesToErase -= __PAGES_PER_BLOCK;
                    }
                    else
                    {
                        // нельзя
                        AT45_LL_Routines.page_erase( PageBegin );

                        #if AT45DB161E_BKGOPERATIONS
                        if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_PGERS_ms ) )
                        #else
                        if( FLERR_TIMEOUT == flash_api_getready() )
                        #endif
                        {
                            // фатальная ошибка: устройство занято дольше обычного
                            Return(FLERR_UNEXPECTED_BUSY);
                        }

                        PageBegin    ++;
                        PagesToErase --;
                    }
             }
       }
       break;

       case  fem_byblock:    // стирание по блокам
       {
             if( start < AT45FLASH_PAGES_PER_SECTOR )  Return(FLERR_INVALID_PARAMETER);
             if( start + count >  __BLOCKS      )  Return(FLERR_INVALID_PARAMETER);

             while( count )
             {
                  AT45_LL_Routines.block_erase( start );

                  #if AT45DB161E_BKGOPERATIONS
                  if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_BKERS_ms ) )
                  #else
                  if( FLERR_TIMEOUT == flash_api_getready() )
                  #endif
                  {
                      // фатальная ошибка: устройство занято дольше обычного
                      Return(FLERR_UNEXPECTED_BUSY);
                  }

                  start ++;
                  count --;
             }
       }
       break;

       case  fem_bysector:   // стирание по секторам
       {
             if( start == 0 )                  Return(FLERR_INVALID_PARAMETER);   // сектор 0 - зарезервирован
             if( start + count >  __SECTORS  ) Return(FLERR_INVALID_PARAMETER);

             while( count )
             {
                  AT45_LL_Routines.sector_erase( start );

                  #if AT45DB161E_BKGOPERATIONS
                  if( FLERR_TIMEOUT == flash_api_getready_time( _TIME_SCERS_ms ) )
                  #else
                  if( FLERR_TIMEOUT == flash_api_getready() )
                  #endif
                  {
                      // фатальная ошибка: устройство занято дольше обычного
                      Return(FLERR_UNEXPECTED_BUSY);
                  }

                  start ++;
                  count --;
             }
       }
       break;

    default: Return(FLERR_INVALID_PARAMETER);
    }

    // успех.
    Return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_write - мультирежимная запись данных по адресу
 //
 // * Параметр @address  указывает линейный адрес в адресном пространстве чипа.
 //   Область 0 - AT45FLASH_MINIMUM_ADDRESS является зарезервированной, все попытки записи в эту область вызовут ошибку.
 // * Параметр @pBuffer - должен указывать на буфер-источник данных для записи. Не может быть NULL.
 // * Параметр @size - должен указывать на размер данных на запись. Не может быть равен 0.
 // * Параметр @mode - задает режим записи. Доступны следующий режимы:
 //   - fwm_safewrite - режим безопасной записи. Медленный режим, когда запись/стирание происходит
 //                     постранично с верификацией. В случае ошибки верификации запись прекращается,
 //                     остальная (незаписанная) часть данных не переписывается/не стрирается,
 //                     При этом функция возвращает ошибку FLERR_VERIFY_ERROR
 //
 //   - fwm_fastwrite - режим быстрой записи. Стирание происходит по блокам (8 станиц, см. заголовочный файл)
 //                     Запись постраничная. Верификация данных не проводится.
 //
 //   - fwm_compatible  режим ускоренной записи. НЕ РЕАЛИЗОВАН. ИСПОЛЬЗУЕТСЯ fwm_fastwrite.
 //                     Нечто среднее между режимамаи безопасной и быстрой записи.
 //                     Стирание происходит по блокам, запись по страницам. По завершении записи/стирания
 //                     всего объема данных происходит верификация. При обнаружении ошибки возвращается код FLERR_VERIFY_ERROR.
 //
 //
 // # Функция возвращает FLERR_SUCCESS, если не произшло никаких ошибок.
 // # Функция возвращает FLERR_UNEXPECTED_BUSY, если во время записи произошел отказ устройства.
 // # Функция возвращает FLERR_VERIFY_ERROR, если обнаружена ошибка верификации данных.
 // # Функция возвращает FLERR_INVALID_PARAMETER, если указан неверный параметр:
 //   - @address указывает на зарезервированную область, либо область записи выходит за границу адресного пространства;
 //   - @pBuffer или @size равны 0;
 //   - @mode имеет значение, отличное от перечисленных
 //
 // # Для быстрой работы требует включения режима AT45DB161E_BKGOPERATIONS
 // Функция использует три различные монорежимные функции записи:
 static flash_err_t flash_write_fast( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );
 static flash_err_t flash_write_safe( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );
 static flash_err_t flash_write_comp( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size );

 static flash_err_t flash_api_write( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size, flash_write_mode_t mode )
 {
      if( address < AT45FLASH_MINIMUM_ADDRESS )         return(FLERR_INVALID_PARAMETER);
      if( !size || !pBuffer )                           return(FLERR_INVALID_PARAMETER);
      if( address + size > AT45FLASH_MAXIMUM_ADDRESS )  return(FLERR_INVALID_PARAMETER);

      switch( mode )
      {
        case fwm_safewrite:  //режим безопасной записи.
        // > Данные страницы прочитываются в буфер 1 чипа ( если запись страницы неполная )
        // > Данные страницы прочитываются в буфер 2 чипа ( если запись страницы неполная )
        // > Производится модификация данных в буфере 1 (запись в буфер со смещением)
        // > Используется команда стирание+запись страницы (буфер 1)
        // > Производится модификация данных в буфере 2 (пока идет запись)
        // > Производится сравнение записанной страницы с буфером 2
        // > Если данные совпадают - запись без ошибок
        /*   ______________________
            /                      \
            | Slow  Moderate  Fast |
            |    \     |      /    |
            |      \               |
            |       \              |
            |        \             |
            |       __\____        |
             \_____________________/

        */
        return(flash_write_safe( address, pBuffer, size ));

        case fwm_compatible: //режим ускоренной записи
        // > Данные блока считываются во временный буфер контроллера
        // > Производится стирание блока
        // > Пока блок стирается, модифицируем данные в буфере
        // > Постранично записываем данные в чип
        // > Поочередно загружаем в буфер 1 и буфер 2 страницы для сравнения
        // > Пока один буфер производит сравнение, записываем данные во второй
        // > При обнаружении расхождения генерируется ошибка
        /*   ______________________
            /                      \
            | Slow  Moderate  Fast |
            |    \     |      /    |
            |          |           |
            |          |           |
            |          |           |
            |       ___|___        |
             \_____________________/

        */
        return(flash_write_comp( address, pBuffer, size ));


        case fwm_fastwrite:  //режим быстрой записи.
        // > Данные страницы прочитываются в буфер 1 чипа ( если запись страницы неполная )
        // > Если возможно, производится стирание блока
        // > Пока блок стирается, модифицируем данные в буфере 1
        // > Постранично записываем данные в чип (если требуется, стриание+запись)
        // > Готово.
        /*   ______________________
            /                      \
            | Slow  Moderate  Fast |
            |    \      |     /    |
            |               /      |
            |              /       |
            |             /        |
            |       _____/_    [!] |
             \_____________________/

        */
        return(flash_write_fast( address, pBuffer, size ));

        default:;
      }

      return(FLERR_INVALID_PARAMETER);
 }
 //----------
 // flash_write_safe() - обеспечивает режим записи fwm_safewrite
 // # НЕ ПРОВЕРЯЕТ ПЕРЕДАННЫЕ ПАРАМЕТРЫ
 // # Для быстрой работы требует включения режима AT45DB161E_BKGOPERATIONS
 static flash_err_t flash_write_safe( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
        // > Данные страницы прочитываются в буфер 1 чипа ( если запись страницы неполная )
        // > Данные страницы прочитываются в буфер 2 чипа ( если запись страницы неполная )
        // > Производится модификация данных в буфере 1 (запись в буфер со смещением)
        // > Используется команда стирание+запись страницы (буфер 1)
        // > Производится модификация данных в буфере 2 (пока идет запись)
        // > Производится сравнение записанной страницы с буфером 2
        // > Если данные совпадают - запись без ошибок

        __FLASH_DWORD pageBegin    = __FLASH_ADDRESS2PAGE( address );           // вычисляем номер первой страницы
        __FLASH_DWORD PagesToWrite = __FLASH_PAGESINRANGE( address, size );     // вычисляем количество затрагиваемых страниц
        __FLASH_DWORD BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );    // вычисляем для первой страницы

        Enter();

        do
        {
         //---------------------
         // ПЕРВАЯ и ПОСЛЕДНЯЯ страницы могут записываться частично: требуется предварительное чтение
            if( __FLASH_ADDRESSOFFSETPAGE( address )  || PagesToWrite<=1 )
            {
         // Данные страницы прочитываются в буфер 1 чипа
                AT45_LL_Routines.pagetobuffer1( pageBegin );
                #if AT45DB161E_BKGOPERATIONS
                __FLASH_WAITus( _TIME_BLDCM_us );
                #endif
                if( FLASH_ERROR( AT45_LL_Routines.getreadyfast() ) )           Return(FLERR_UNEXPECTED_BUSY);

         // Данные страницы прочитываются в буфер 2 чипа
                AT45_LL_Routines.pagetobuffer2( pageBegin );
                #if AT45DB161E_BKGOPERATIONS
                __FLASH_WAITus( _TIME_BLDCM_us );
                #endif
                if( FLASH_ERROR( AT45_LL_Routines.getreadyfast() ) )           Return(FLERR_UNEXPECTED_BUSY);
            }
         //---------------------
            if( size < BytesToWrite ) BytesToWrite = size;
         //---------------------
         // Производится модификация данных в буфере 1 (запись в буфер со смещением)
            AT45_LL_Routines.buffer1_write( __FLASH_ADDRESSOFFSETPAGE( address ),   // смещение в буфере
                                        pBuffer,                                // данные
                                        BytesToWrite );                         // количество данных на запись
         //---------------------
         // идет запись: СТИРАНИЕ + ПРОГРАММИРОВАНИЕ
            AT45_LL_Routines.buffer1_program( pageBegin );

            #if AT45DB161E_BKGOPERATIONS == 0
            if( FLASH_ERROR( AT45_LL_Routines.getreadyfast() ) )           Return(FLERR_UNEXPECTED_BUSY);
            #endif
         //---------------------
         // Производится модификация данных в буфере 2 (запись в буфер со смещением)
            AT45_LL_Routines.buffer2_write( __FLASH_ADDRESSOFFSETPAGE( address ),   // смещение в буфере
                                        pBuffer,                                // данные
                                        BytesToWrite );                         // количество данных на запись
         //---------------------
         // дожидаемся завершения записи
            #if AT45DB161E_BKGOPERATIONS
            if( FLASH_ERROR( flash_api_getready_time( _TIME_PGEPR_ms ) ) ) Return(FLERR_UNEXPECTED_BUSY);
            #endif
         //---------------------
         // Производится сравнение записанной страницы с буфером 2
            AT45_LL_Routines.buffer1_compare( pageBegin );

            #if AT45DB161E_BKGOPERATIONS
            __FLASH_WAITus( _TIME_BLDCM_us );
            #endif
         //---------------------
         // Если данные совпадают - запись без ошибок
            if( FLASH_ERROR( AT45_LL_Routines.getcompareresult() ) )       Return(FLERR_VERIFY_ERROR);
            //---------------------
            pageBegin++;
            address += BytesToWrite;
            pBuffer += BytesToWrite;
            size    -= BytesToWrite;

            BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );
        }
        while( --PagesToWrite );

    Return(FLERR_SUCCESS);
 }
 //----------
 // flash_write_safe() - обеспечивает режим записи fwm_fastwrite
 // # НЕ ПРОВЕРЯЕТ ПЕРЕДАННЫЕ ПАРАМЕТРЫ
 // # Для быстрой работы требует включения режима AT45DB161E_BKGOPERATIONS
 static flash_err_t flash_write_fast( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
        // > Данные страницы прочитываются в буфер 1 чипа ( если запись страницы неполная )
        // > Если возможно, производится стирание блока
        // > Пока блок стирается, модифицируем данные в буфере 1
        // > Постранично записываем данные в чип (если требуется, стриание+запись)
        // > Готово.
        __FLASH_DWORD pageBegin    = __FLASH_ADDRESS2PAGE( address );           // вычисляем номер первой страницы
        __FLASH_DWORD PagesToWrite = __FLASH_PAGESINRANGE( address, size );     // вычисляем количество затрагиваемых страниц
        __FLASH_DWORD BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );    // вычисляем для первой страницы
        __FLASH_DWORD PagesErased  = 0;                                         // количество заранее стретых страниц (блочное стирание)

        Enter();

        do
        {
         //---------------------
         // ПЕРВАЯ и ПОСЛЕДНЯЯ страницы могут записываться частично: требуется предварительное чтение
            if( __FLASH_ADDRESSOFFSETPAGE( address )  || PagesToWrite<=1 )
            {
         // Данные страницы прочитываются в буфер 1 чипа
                AT45_LL_Routines.pagetobuffer1( pageBegin );
                #if AT45DB161E_BKGOPERATIONS
                __FLASH_WAITus( _TIME_BLDCM_us );
                #endif
                if( FLASH_ERROR( AT45_LL_Routines.getreadyfast() ) )       Return(FLERR_UNEXPECTED_BUSY);
            }
         //---------------------
            if( size < BytesToWrite ) BytesToWrite = size;
         //---------------------

         // Производится стирание блока
            if( __FLASH_PAGE_ARRAGED_BY_BLOCK( pageBegin )  &&  PagesToWrite >=  AT45FLASH_PAGES_PER_BLOCK )
            {
                AT45_LL_Routines.block_erase( __FLASH_PAGE2BLOCK( pageBegin ) );

                PagesErased =  AT45FLASH_PAGES_PER_BLOCK;      // запоминаем, что много-много страниц стерто
            }
         //---------------------
         // Производится модификация данных в буфере 1 (запись в буфер со смещением)
            AT45_LL_Routines.buffer1_write( __FLASH_ADDRESSOFFSETPAGE( address ),   // смещение в буфере
                                        pBuffer,                                // данные
                                        BytesToWrite );                         // количество данных на запись


            #if AT45DB161E_BKGOPERATIONS
         //---------------------
         // дожидаемся окончания стирания блока
            if( AT45FLASH_PAGES_PER_BLOCK == PagesErased )   // Только если только что стерли.
            if( FLASH_ERROR( flash_api_getready_time( _TIME_BKERS_ms ) ) ) Return(FLERR_UNEXPECTED_BUSY);
            #endif
         //---------------------
         // если стерто много страниц (блочное стирание )
            if( PagesErased )
         // идет запись: ПРОГРАММИРОВАНИЕ БЕЗ СТИРАНИЯ
            {
                AT45_LL_Routines.buffer1_program_wo_erase( pageBegin );

                PagesErased--;
            }
            else
         // идет запись: СТИРАНИЕ + ПРОГРАММИРОВАНИЕ
                AT45_LL_Routines.buffer1_program( pageBegin );

            #if AT45DB161E_BKGOPERATIONS == 0
            if( FLASH_ERROR( AT45_LL_Routines.getreadyfast() ) )           Return(FLERR_UNEXPECTED_BUSY);
            #endif

         //---------------------
         // дожидаемся завершения записи
            #if AT45DB161E_BKGOPERATIONS
            if( FLASH_ERROR( flash_api_getready_time( _TIME_PGEPR_ms ) ) ) Return(FLERR_UNEXPECTED_BUSY);
            #endif

            // ***************************************************************
            // * NOTE: строго говоря, можно было еще больше соптимизировать  *
            // * и записывать в свободный второй буфер во время записи пер-  *
            // * вого. Но это потребует переключения команд записи страниц   *
            // * ( __flash_hal__buffer1_program_wo_erase и                   *
            // * __flash_hal__buffer1_program), а также команд записи        *
            // * в буфер __flash_hal__pagetobuffer1, на ходу, потребуется    *
            // * запоминать, какой буфер свободен.                           *
            // * В целях упрощения решено так не делать.                     *
            // ***************************************************************

            pageBegin++;

            address += BytesToWrite;
            pBuffer += BytesToWrite;
            size    -= BytesToWrite;

            BytesToWrite = __FLASH_DATAREMAININGINPAGE( address );
        }
        while( --PagesToWrite );

     Return(FLERR_SUCCESS);
 }
 //----------
 // flash_write_safe() - обеспечивает режим записи fwm_compatible
 // # НЕ ПРОВЕРЯЕТ ПЕРЕДАННЫЕ ПАРАМЕТРЫ
 // # Для быстрой работы требует включения режима AT45DB161E_BKGOPERATIONS
 //
 // # РЕЖИМ НЕ РЕАЛИЗОВАН
 //
 #warning Режим записи fwm_fastwrite не реализован
 static flash_err_t flash_write_comp( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
        // > Данные страницы прочитываются в буфер 1 чипа ( если запись страницы неполная )
        // > Если возможно, производится стирание блока
        // > Пока блок стирается, модифицируем данные в буфере 1
        // > Постранично записываем данные в чип (если требуется, стриание+запись)
        // ---------------------------------
        // Вызываем алгоритм быстрой записи fwm_fastwrite
        flash_err_t success = flash_write_fast( address, pBuffer, size );
        // если были ошибки - сообщаем
        if( FLASH_ERROR( success ) ) return(success);
        // ---------------------------------
        // Приступаем к верификации:
        // > Поочередно загружаем в буфер 1 и буфер 2 страницы для сравнения
        // > Пока один буфер производит сравнение, записываем данные во второй
        // > При обнаружении расхождения генерируется ошибка
        //
        // ВОПРОС: а будет ли это быстрее, чем сравнивать на ходу?! А может просто прочитать массив на борт CPU и использовать memcmp() ?
        //
        // функторы действий
        // void ( * buffer_write   )(__FLASH_WORD,__FLASH_BYTE*,__FLASH_WORD) = __flash_hal__buffer1_write;      // сначала выбираем буфер 1
        // void ( * buffer_compare )(__FLASH_WORD)                            = __flash_hal__buffer1_compare;    //

    return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_read - чтение данных по адресу
 //
 // * Функция проверяет правильность переданных параметров
 // * Параметр @address задает линейный адрес внутри адресного пространства устройства.
 //    Адресное пространство с адреса 0 до AT45FLASH_MINIMUM_ADDRESS (не включительно) является
 //    зарезервированным. При попытке чтения из этого диапазона функция вернет ошибку.
 //   Параметр @pvBuffer должен указывать на буфер-приемник для прочитанных данных.
 //   Параметр @size должен указывать на размер буфер-приемника. Параметр показывает
 //    количество байт для чтения в буфер.

 // # Если параметр @address указывает на адрес в диапазоне 0...AT45FLASH_MINIMUM_ADDRESS, функция вернет ошибку FLERR_INVALID_PARAMETER
 // # Если параметр @pvBuffer равен NULL, функция вернет ошибку FLERR_INVALID_PARAMETER
 // # Если параметр @size равен 0, функция вернет ошибку FLERR_INVALID_PARAMETER

 static flash_err_t flash_api_read( flash_address_t address, __FLASH_BYTE * pBuffer, flash_address_t size )
 {
      // проверяем входные параметры
      if( address < AT45FLASH_MINIMUM_ADDRESS ) return(FLERR_INVALID_PARAMETER);
      if( !pBuffer || !size )                   return(FLERR_INVALID_PARAMETER);

      #ifndef AT45DB161E_EMU512
      // проверяем диапазон чтения
      if( address + size >  __FULL_CHIP_SIZE ) return(FLERR_INVALID_PARAMETER);
      #endif

      // получаем начальную страницу
      #ifdef AT45DB161E_EMU512
      __FLASH_DWORD PagesToRead = __FLASH_PAGESINRANGE( address , size );
      #endif
      __FLASH_DWORD PageBegin   = __FLASH_ADDRESS2PAGE( address );
      __FLASH_DWORD PageOffset  = __FLASH_ADDRESSOFFSETPAGE( address );

      Enter();

      // проверям готовность устройства
      if( FLERR_TIMEOUT == flash_api_getready() ) Return(FLERR_TIMEOUT);

      #ifndef AT45DB161E_EMU512
      // работа без эмуляции страниц по 512 байт

           AT45_LL_Routines.array_read( PageBegin, PageOffset, pBuffer, size );

           Return(FLERR_SUCCESS);
      #else
      // эмуляция размера страниц 512 байт

           // проверяем диапазон чтения
           if( PagesToRead + PageBegin > __FULL_PAGES ) Return(FLERR_INVALID_PARAMETER);

           // постраничное чтение
           while( size && PagesToRead )
           {
                  __FLASH_DWORD BytesToRead = __PAGE_SIZE_USER - __FLASH_ADDRESSOFFSETPAGE( address );

                  if( BytesToRead > size ) BytesToRead = size;  // 22/09/15 - ErrorFix

                  AT45_LL_Routines.page_read( PageBegin, PageOffset, pBuffer, BytesToRead );

                  pBuffer += BytesToRead;

                  address += BytesToRead;

                  size    -= BytesToRead;

                  PageBegin++;

                  PagesToRead--;

                  PageOffset = 0;
           }

           Return(FLERR_SUCCESS);
      #endif
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_sleepmode - переход в режим пониженного энергопотребления
 // # переводит устройство в режим, когда все команды, кроме flash_api_wakeup игнорируются
 static flash_err_t flash_api_sleepmode()
 {
    Enter();

    AT45_LL_Routines.sleepmode();

    Return(FLERR_SUCCESS);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№
 // flash_api_wakeup - выход из режима пониженного энергопотребления
 // # выводит устройство из режима, когда все команды, кроме flash_api_wakeup игнорируются
 static flash_err_t flash_api_wakeup()
 {
    Enter();

    AT45_LL_Routines.wakeup();

    if( AT45_LL_Routines.smart_waitms( AT45DBXXX_API_TIMEOUT )  )
    {
         Return(FLERR_SUCCESS);
    }

    Return(FLERR_TIMEOUT);
 }
 // №№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№