Перейти к содержанию
    

Как зарезервировать страницу памяти в середине кода

11 hours ago, jcxz said:

Чтобы записать несколько байт, не нужно копировать сектора.

Записывается может до 32К зараз. После записи может быть изменение нескольких байт. Вот где подвох.

 

11 hours ago, jcxz said:

В том, что при неосторожном выключении питания в момент модификации данных, вся FS может слететь вместе с настройками?

Такое может произойти при записи в любой флэш, хоть внутренний, хоть внешний.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, tonyk_av сказал:

Такое может произойти при записи в любой флэш, хоть внутренний, хоть внешний.

Нет, если реализована работа минимум с двумя областями стирания/записи (секторами) с кольцевым способом дозаписи.

О чем уже написал jcxz.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

15 часов назад, tonyk_av сказал:

То есть нужно очистить сектор в 128К, скопировать туда сектор в 16К...

Вообще не так все. Писать нужно в тот же (по возможности) сектор, но чистую область.

Цитата

Ну и ресурс у внешней флэш W25Qxx на порядок больше, чем у флэш МК, 100К против 10К.

Открываю приведенный F446. Сектора 6 и 7 по 128К занимаем под эту самую "программу пользователя" в 32К, итого имеем 80К циклов стирания минимум. Уж если кому-то в голову придет 10 раз на день менять прошивку пользователя, то это ресурс на 8000 дней, а это почти 22 года безответственного насилования блока, который, уверен, к тому моменту потеряет срок гарантийных обязательств, а как максимум, истечет назначенный срок эксплуатации.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 часов назад, tonyk_av сказал:

Такое может произойти при записи в любой флэш, хоть внутренний, хоть внешний.

Если иметь голову на плечах и следовать правилу: "Никогда не писать поверх старых данных!" (только в новое место) - то никогда не произойдёт (до физического износа флеша конечно). Количество элементов стирания (секторов) должно быть >=2. Как уже писал выше Arlleex.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 hours ago, Arlleex said:

Нет, если реализована работа минимум с двумя областями стирания/записи (секторами) с кольцевым способом дозаписи.

Так у меня сейчас реализовано очень похоже, причём на разных физических носителях. Сначала всё пишется и изменяется в файле на внешней микросхеме флэш-памяти, а перед запускам прошивается во внутренний флэш МК.

18 hours ago, jcxz said:

В чём "удобства" то?

Не упомянул, что на контроллере есть ещё FTP-сервер, поэтому все настройки просто загружаются в контроллер по ftp. Кстати, ёмкость W25Q позволяет сохранять в контроллере весь проект с программой пользователя и схемами оборудования, где стоит контроллер.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Лет 20 назад я придумал интересную систему хранения настроек, и за все время я не потерял ни одного параметра. Конечно за это время она модернизировалась по C++11, флэшки с ECC и флэшки у которых стертое состояние является 0x00, но принцип остался неизменным.
Система не занимает ни одного (заранее зарезервированного) байта ОЗУ, кроме стека;
Масштабируемая, самовосстанавливающаяся, автоматическая;
Работает на любых носителях (с которыми я сталкивался);
Журналируемая (по возможности), можно поднять хронологию изменений;
Относительно компактная;
Щадящая ресурс носителя, увеличивая ресурс в десятки-сотни раз;
Устойчивая даже к тому, что если залить совсем другую программу с другими настройками, и даже что-то в ней сохранить, а потом вернуть оригинальную прошивку, то вероятнее всего ничего не потеряется.

Если комку интересно - могу описать

Из недостатков: иногда может занять длительное время для дефрагментации и тем самым задержать ответ по какому нибудь протоколу. но это решается отложенной обработкой сохранения

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 3/4/2024 at 10:45 AM, Ivan. said:

Если комку интересно - могу описать

Мне интересно :blush:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пожалуйста, только не надо трепа, а нафига и что за глупость.


Последнюю версию выдать не могу, не потому, что жалко, а потому, что она писалась под специфический TMS контроллер и не оформлена (руки не доходят).

Система разделена на 2 части:
PropSaver - Занимается записью/чтением данных с виртуальной флэшки (это может быть даже файл в файловой системе)
PropDesc - Формирует списки сохраняемых параметров для автоматизации процесса
 

PropSaver 
//Компонента хранения параметров.
//Компонента требует 2 области памяти для надежного хранения данных.
//Памяти могут быть разного размера и различной реализации.
//Хранение данных производится путем добавления нового блока данных в конец.
//При инициализации, компонента проходит по всей памяти и находит конец данных по пустому идентификатору
//
//Каждый блок данных занимает 4 байта и имеет структуру:
//Младший бит ----------- Старший бит
//Data#### ######## ######## TpCrc###
//Каждый блок данных содержит:
//3 байта полезной нагрузки (Data);
//2 бита флагов (Tp[0-удалена, 1-Идентификатор, 2-Значение]);
//6 бит контрольной суммы (Crc)
//
//Каждая запись состоит из нескольких блоков данных:
//1 блок данных с идентификатором записи
//N блоков с данными
//Т.е. для хранения 2 байт данных потребуется 8 байт данных:
//Id###### ######## ######## 10Crc###
//Value### ######## ........ 01Crc###
//Для хранения большого объема данных блоки добавляются, например, для хранения 10 байт данных структура будет выглядеть следующим образом:
//Id###### ######## ######## 10Crc###
//Value### ######## ######## 01Crc###
//Value### ######## ######## 01Crc###
//Value### ######## ######## 01Crc###
//Value### ........ ........ 01Crc###
//При сохранении битовых полей данные сохраняются без уплотнения, например, при сохранении 5 бит данных начиная с 4 бита они разместятся следующим образом:
//Id###### ######## ######## 10Crc###
//....Valu e....... ........ 01Crc###
//При чтении/сохранении битовых полей указатель должен быть указан на первый байт данных, а номер бита в диапазоне от 0 до 7
//
//При стирании записи в блоке идентификатора стирается тип записи на 00 (это позволяет не перезаписывать всю страницу, а только дозаписать несколько бит данных).
//Удаленная запись будет иметь вид:
//Id###### ######## ######## 00Crc###
//Value### ######## ........ 01Crc###
//Даже после удаления данных можно проследить последовательность изменения, пока память не будет дефрагментирована.
//
//При загрузке параметра компоненте указывается идентификатор параметра и длинна данных в битах, которую необходимо загрузить.
//Если цепочка блоков не позволяет загрузить все данные - загрузчик возвращает ошибку.
//Благодаря идентификаторам - хранение параметров возможно в произвольном порядке.
//Можно сохранять только те параметры, которые били изменены относительно значений по умолчанию, а остальные параметры загружать значениями по умолчанию.
//При вводе новых параметров в новых версиях программы - старые параметры остаются доступными и не теряются.
//
//Последовательность сохранения данных:
//1. Сперва производится добавление новой записи в первую память;
//1а. Если в этот момент происходит сбой (сброс программы), то при загрузке программы данные будут загружены из второй памяти.
//2. Производится стирание одной последней записи до только что записанной (дальнейший поиск совпадений не требуется);
//2а. Если в этот момент происходит сбой, то после перезагрузки программы находит новые данные, удаляет все неудаленные блоки данных с таким же идентификатором, после чего дублирует запись во вторую память (тем самым завершает незаконченную процедуру сохранения).
//3. Далее производит добавление новой записи во вторую память;
//3а. Если в этот момент происходит сбой - компонента выполнит восстановление по пункту 2а.
//4. Последним этапом компонента стирает во второй памяти один последний идентификатор до только что сохраненного;
//4а. Если в этот момент произойдет сбой - компонента выполнит восстановление по пункту 2а. т.е. данные в первой памяти являются более актуальными.
//
//Последовательность загрузки данных:
//1. Ищет первый с конца соответствующий идентификатор в первой памяти;
//2. Удаляет все недоудаленные такие же идентификаторы до текущей позиции;
//3. Производит загрузку данных;
//3а. Если данные были загружены полностью:
//3а1. Ищет такой же идентификатор с конца второй памяти;
//3а2. Удаляет все недоудаленные такие же идентификаторы до текущей позиции;
//3а3. Сверяет данные с теми, что были загружены из первой памяти;
//3а3а. Если данные совпадают:
//3а3а1. Возвращает положительный результат.
//3а3б. Если данные отличаются:
//3а3б1. Сохраняет данные во вторую память;
//3а3б2. Возвращает положительный результат не зависимо от того удалось ли сохранить данные во вторую память.
//3б. Если не удалось загрузить данные из первой памяти:
//3б1. Ищет первый с конца соответствующий идентификатор во второй памяти;
//3б2. Удаляет все недоудаленные такие же идентификаторы до текущей позиции;
//3б3. Производит загрузку данных;
//3б3а. Если данные были загружены полностью:
//3б3а1. Сохраняет данные в первую память;
//3б3а2. Возвращает положительный результат не зависимо от того удалось ли сохранить данные в первую память.
//3б3б. Если не удалось загрузить данные из второй памяти:
//3б3б1. Возвращает ошибку загрузки.
//
//Данные в каждой памяти хранятся асинхронно.
//При заполнении одной из памяти она полностью стирается и производится полное копирование данных из резервной памяти в ту, которая только что была очищена.
//Перед упаковкой одной памяти в другую компонента рассчитывает сколько места потребуется для добавления новой записи.
//Если новая запись не влезает - компонента выполняет 1 из вариантов действий, который был указан при инициализации компоненты:
//1. Возврат ошибки записи нового значения;
//2. Удаление одной или нескольких самых старых записей, чтобы влезла новая (т.е. самые ранние сохраненные данные могут быть потеряны и при следующем запуске быть загруженными по умолчанию);
//3. Удаление целой страницы данных наиболее давней записи;
//4. Удаление половины памяти, но не менее одной страницы.

В данном варианте блок данных занимает 4 байта, что не подходит для памяти с минимальным блоком данных 8 байт (не помню, по-моему у L4 такая память)
В новой реализации я сделал блок данных по 8 байт, в которые входят:
 

//Value### ######## ######## ######## Id###### ######## ######## 10Crc###
//Value### ######## ######## ######## ######## ######## ######## 01Crc### 

Это позволило эффективнее хранить параметры. Первый блок имеет 3-х байтовый идентификатор и 4-х байтовую переменную
Каждый последующий блок расширяет данные на еще 7 байт

PropDesc 
//Система сохранения / загрузки списка параметров.
//Данная компонента позволяет сформировать список всех полей данных для сохранения, автоматической загрузки всех параметров и восстановления значений по умолчанию при неудачной загрузке.
//В список инициализации можно указывать любые поля данных, как глобальных переменных, так и отдельные поля объектов в любом порядке.
//Можно указывать как целые структуры данных, так отдельные переменные вплоть до единичных битов.
//Компонента позволяет хранить любые объекты размером от 1 бита до 8К байт (в 16 битных системах) и до 512М байт (в 32 битных системах).
//Также компонента позволяет хранить массивы данных от 0 до 255 элементов.
//Компонента самостоятельно создает идентификатор переменной на основе хеш суммы строкового представления имени параметра.
//То есть при указании переменной, которую требуется сохранить компонента преобразует имя переменной в строку, вычислит трехбайтную хеш сумму этой строки и сохранит данные под данным идентификатором.
//Это позволяет формировать список в любом порядке, пересортировывать его и вводить новые параметры по мере их появления.
//Идентификаторы формируются на этапе компиляции и не расходуют программные ресурсы. Также весь список может быть сохранен в кодовую часть памяти не расходуя оперативную память.
//Трехбайтный идентификатор получается своего рода случайным числом.
//Вероятность совпадения идентификаторов у разных переменных является достаточно малой для небольших списков порядка 1000 элементов.
//Для проверки совпадений идентификаторов и наложения областей памяти существует функция checkOverlap().
//Данную проверку можно выполнять единоразово, перед выпуске релиза и потом исключить из основной программы.
//Для восстановления недозагруженных параметров необходимо указать область константной памяти с копией объекта заполненного значениями по умолчанию.
//Для эмбеддед архитектуры нет необходимости указывать дефолтный объект, т.к. компонента самостоятельно найдет его в слепке памяти инициализации оперативной памяти.
//Если дефолтный объект не указан, или для эмбеддед архитектуры объект находится в области noinit или init0, то при необходимости восстановления он заполняется 0.
//
//Пример создания списка параметров (в частности пример приведен для эмбеддед архитектуры, где не нужно указывать значения по умолчанию:
//Допустим у нас есть некие переменные и объекты:
//int16_t var1 = 10; //Единичная переменная имеет значение по умолчанию 10.
//int16_t var2[5] = {1, 2, 3, 4, 5}; //Массив переменных из 5 элементов.
//int16_t var3; //Переменная var3 не проинициализрована и имеет значение по умолчанию 0.
//char str1[10] = "str1"; //Строка из 9 символов и 0 терминатором в конце.
//struct Obj { //Структура данных в которой все поля данных проинициализированы значениями по умолчанию.
//	int16_t field1 = 1;
//	int8_t field2 = 2;
//	union {
//		uint16_t bitFields_1_2 = 0; //Вспомогательное поле для удобства взятия указателя на битовые поля, т.к. данные в структуре могут быть выравнены согласно архитекруте.
//		struct {
//			uint16_t bitField1 :5; //Битовое поле размером 5 бит. Битовые поля нельзя инициализировать значениями по умолчанию в месте их объявления.
//			uint16_t bitField2 :11; //Для этого можно их инициализировать во вспомогательной переменной или constexpr конструкторе.
//		};
//	};
//	union {
//		uint8_t bitArray_4_2 = 0; //Вспомогательное поле для массива битовых полей из 2 элементов по 4 бита каждый.
//		struct { //Нельзя создавать битовые массивы, для этого представим массив из независимых битовых полей.
//			uint8_t bitArrayElement0 :4;
//			uint8_t bitArrayElement1 :4;
//		};
//	};
//} obj1, obj2; //Создадим 2 объекта данной структуры.
//
//Инициализация списка сохраняемых параметров:
//DESCLIST(PropDescList1, //Имя типа списка инициализации.
//	VARDESC(var1); //Элемент списка, указывающий на переменную var1. хеш сумма вычисляется из строки "var1".
//                 //В случае невозможности загрузки, переменная будет проинициализирована значением 10.
//	VARDESC(var2); //Элемент списка, указывающий на массив переменных var2. Каждый элемент массива будет сохранена под своим идентификатором.
//                 //Первый элемент будет сохранен под идентификатором "var2.0", второй под "var2.1" и т.д.
//	VARDESC(var3); //Переменная var3. В случае неудачной загрузки будет заполнена 0 т.к. она не была помещена в область инициализированных переменных.
//	VARDESC(str1); //Переменная str1 будет сохраняться единым объектом размером 10 байт, а не массивом из 10 переменных т.к. для строковых параметров предусмотрена специальная реализация.
//	VARDESC(obj1.field1); //Элемент списка указывающий на поле field1 объекта obj1. Переменная будет сохранена под идентификатором "obj1.field1".
//	BITDESC("obj1.bitField1", &obj1.bitFields_1_2, 0, 5); //Битовое поле bitField1 будет сохранено под идентификатором "obj1.bitField1", которое формируется не на основе имени переменной, а указывается вручную первым аргументом.
//                                                        //Битовое поле будет размещаться возле переменной obj1.bitFields_1_2 с 0 бита размером 5 бит.
//	BITDESC("obj1.bitField2", &obj1.bitFields_1_2, 5, 11); //Битовое поле bitField2 будет сохранено под идентификатором "obj1.bitField2".
//                                                         //Битовое поле будет размещаться возле переменной obj1.bitFields_1_2 с 5 бита размером 11 бит.
//	BITDESC("obj1.bitArray1", &obj1.bitArray_4_2, 0, 4, 2); //Указатель на массив битовых полей расположенных возле переменной obj1.bitArray_4_2 начиная с 0 бита размером каждого элемента в 4 бита и в количестве 2 элементов.
//	VARDESC(obj2); //Объект obj2 будет загружаться и сохранятся полностью при изменении любого поля объекта.
//	VARDESC(obj2.field2); //Элемент указывающий на obj2.field2 бессмысленен, т.к. он ни когда не будет сохранен отдельно, а будет сохраняться целой структурой описанной предыдущей строкой.
//);
//
//Создание списка сохраняемых параметров.
//const PropDescList1 propDescList1; //Переменная автоматически разместиться в кодовой памяти для AVR архитектуры, т.к. к структуре применен атрибут PROGMEM.
//
//Загрузка всех параметров:
//propDescList1.load(propSaver1); //компонента загрузит все указанные в списке переменные и восстановит их при необходимости.
//
//Сохранение отдельных переменных:
//propDescList1.save(propSaver1, &obj1.field2); //Компонента найдет какому из элементов списка принадлежит данная память и сохранит ее с помощью объекта PropSaver.
//propDescList1.save(propSaver1, &obj2.bitField_1_2, 8); //Компонента найдет какому из элементов списка принадлежит данная память и сохранит ее.
//                                                       //Данные по адресу obj2.bitField_1_2 начиная с 8 бита принадлежат полю obj2.bitField2 начиная с 5 бита размером 11 бит
//                                                       //и могли бы быть сохранены отдельно для этой переменной в случае с объектом obj1,
//                                                       //но объект obj2 описан для сохранения целиком и будет сохранен полностью всем объектом.

 

Изменено пользователем Ivan.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вся компиляция укладывается примерно в 3кБ (на STM32)

Вместо PropDesc у меня есть более сложный компонент: ValueDesc.
Это уже глобальная структура всех переменных в системе, к которой могут ссылаться протоколы связи, графические меню, хранители параметров, журналы событий.
Имеет такую структуру:
 

//3        2        1        0
//Caption# ######## ######## ######## - Caption: Заголовок
//Ptr##### ######## ######## ######## - Ptr: Указатель на переменную (объект)
//Array### Hash#### ######## ######## - Array: Массив, Hash: Идентификатор
//Для переменной
//1SEacBit Limits## Bits#### ######## - S: Сохраняемый, Eac: Уровень доступа для изменения, Bit: Стартовый бит, Limits: Количество лимитов, Bits: Размер в битах
//Для объекта
//0Size### ######## ######## ######## - Size: Размер в байтах
//
//Viewtype ######## ######## ######## - Viewtype: Указатель на тип отображения (на ValueDescList для объекта)
//[                                   - Лимиты
//Bits#Bit MAcAdd## ######## ######## - Bits: Размер переменной в битах, Bit: Стартовый бит, M: Максимальное ограничение, Ac: Максимальный уровень ограничения, Add: Коррекция лимита (не действует, если константа)
//Lim##### ######## ######## ######## - Lim: Указатель на переменную ограничения (Константа, если Bit==7, Bits==31)
//]

Эта структура работает с той же PropSaver, но имеет древовидную систему, проверяет валидность загруженных данных (минимальное, максимальное значение) может иметь сложные зависимости лимитов с гистерезисом, например: var1 не может быть больше var2 - 10.

И все это конечно формируется на этапе компиляции, т.е. не занимает ни одного байта кодовой памяти, кроме самой таблицы

Изменено пользователем Ivan.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Описание структуры переменных имеет примерно такой вид:

VALUELIST(ValueDescList,
//             AC,             NAME,             VALUE,            CAPTION,            VIEWTYPE,             LIMITS...
    VALUE_DESC(vdNoSaved,      currentValue,     currentValue,     "currentValue",     VTDec::vt<3>(),       {lacAbsoluteMin, 0_mA},    {lacAbsoluteMax, 24_mA});
    VALUE_DESC(vdSavedService, calib4mA,         calib4mA,         "calib4mA",         VTDec::vt<3>(),       {lacAbsoluteMin, 2_mA},    {lacAbsoluteMax, 8_mA});
    VALUE_DESC(vdSavedService, calib20mA,        calib20mA,        "calib20mA",        VTDec::vt<3>(),       {lacAbsoluteMin, 16_mA},   {lacAbsoluteMax, 30_mA});
    ...
//           NAME,             OBJ,                                CAPTION
    OBJ_DESC(hartParserSlave,  hartParserSlave,                    "Hart HartParserSlave");
    OBJ_DESC(variables,        variables,                          "Hart Variables");
    OBJ_DESC(identifier,       identifier,                         "Hart Identifier");
);
extern const ValueDescList valueDescList;

 

Изменено пользователем Ivan.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 3/4/2024 at 11:52 AM, Ivan. said:

Пожалуйста, только не надо трепа, а нафига и что за глупость.

Спасибо !

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, Ivan. сказал:

Система не занимает ни одного (заранее зарезервированного) байта ОЗУ, кроме стека;
Масштабируемая, самовосстанавливающаяся, автоматическая;
Работает на любых носителях (с которыми я сталкивался);
Журналируемая (по возможности), можно поднять хронологию изменений;
Относительно компактная;
Щадящая ресурс носителя, увеличивая ресурс в десятки-сотни раз;
Устойчивая даже к тому, что если залить совсем другую программу с другими настройками, и даже что-то в ней сохранить, а потом вернуть оригинальную прошивку, то вероятнее всего ничего не потеряется.

Система хранения, базирующаяся на обычном кольце секторов, умеет всё то же самое + не требует никакой дефрагментации.

И не требует "2 памятей". Нужна только одна цепочка секторов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 минуту назад, jcxz сказал:

Система хранения, базирующаяся на обычном кольце секторов, умеет всё то же самое + не требует никакой фрагментации.

И сколько секторов она занимает?
Если Вам потребуется сохранить новую переменную, Вы ее будете писать в следующий сектор или стирать и писать заново в один единственный сектор? В моем случае это по одному-два сектора на каждом носителе.
Если стирать - то ее нужно где-то временно хранить. Допустим - это вторая копия, тогда Вы будете каждый раз ее копировать из одной в другую флэшку?
А если флэшки разного размера? одна 16к, а втора EEPROM на 2к?
Даже просто перезаписать целую страницу - уйдет в десятки раз больше времени, чем дописать новы блок в 8 байт.
А ресурс? самое страшное для памяти - это не запись, а стирание страницы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

28 минут назад, jcxz сказал:

И не требует "2 памятей". Нужна только одна цепочка секторов.

И здесь не обязательно 2 независимых носителя. можно взять 2 сектора на одном носителе

31 минуту назад, jcxz сказал:

Система хранения, базирующаяся на обычном кольце секторов, умеет всё то же самое

А можно краткое описание?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

15 минут назад, Ivan. сказал:

И сколько секторов она занимает?

Сколько нужно пользователю: от 2-х до всех секторов носителя.

15 минут назад, Ivan. сказал:

Если Вам потребуется сохранить новую переменную, Вы ее будете писать в следующий сектор или стирать и писать заново в один единственный сектор?

В систему хранения сохраняется структура настроек целиком, как единый блок. Не пилю её на множество переменных. Хотя можно и так сделать, при необходимости.

На носителе структура занимает почти столько байт, сколько весит сама (плюс небольшой оверхед = CRC + ещё неск. байт).

15 минут назад, Ivan. сказал:

В моем случае это по одному-два сектора на каждом носителе.

На каждую переменную???  :shok:

15 минут назад, Ivan. сказал:

Если стирать - то ее нужно где-то временно хранить. Допустим - это вторая копия, тогда Вы будете каждый раз ее копировать из одной в другую флэшку?
А если флэшки разного размера? одна 16к, а втора EEPROM на 2к?

Ещё раз: Кольцу нужен только один носитель. Никаких "двух памятей" не нужно. И никакого "временно хранить" не нужно. Зачем? В кольце всегда есть предыдущая копия структуры (и даже скорей всего не одна, а множество). Которая и будет прочитана если во время сохранения новой структуры произойдёт сбой питания.

15 минут назад, Ivan. сказал:

А ресурс? самое страшное для памяти - это не запись, а стирание страницы.

Ресурс тоже определяется пользователем: Чем больше секторов он выделит для кольца, тем больше будет мультипликатор ресурса.

8 минут назад, Ivan. сказал:

А можно краткое описание?

Я уже описывал здесь на форуме ранее. Поищите. И не кратко, а развёрнуто. Поищите.

 

PS: Вот оно: 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...