/* Двухбанковый регистратор событий нагрузки во внешней RAM. Русские пояснения сохранены в UTF-8. */ /* Подключает "DSP2833x_Device.h": поставляемое TI описание Device для семейства C2833x. */ #include "DSP2833x_Device.h" /* Подключает "filter_bat2.h": накопительный фильтр измерительных каналов. */ #include "filter_bat2.h" /* Подключает "measure.h": обработка датчиков, вычисление величин и миллисекундный такт системы. */ #include "measure.h" /* Подключает "package.h": адреса устройств, типы плат и псевдонимы технологических регистров. */ #include "package.h" /* Подключает "RS485.h": приём и передача SCI-A/SCI-B, упаковка команд и FIFO первого порта. */ #include "RS485.h" /* Подключает "message.h": обработка Modbus, заводские параметры и сохранение настроек. */ #include "message.h" /* Подключает "peripher.h": перемычки режима платы, дискретные входы и выходы. */ #include "peripher.h" /* Подключает "DAC.h": выход ЦАП и автомат защиты нагрузки с проверкой напряжения и свежести CAN. */ #include "DAC.h" /* Подключает "ADC.h": настройка АЦП, обработка преобразований и первичная фильтрация измерений. */ #include "ADC.h" /* Подключает "log_to_mem.h": двухбанковый регистратор событий нагрузки во внешней RAM. */ #include "log_to_mem.h" /* Объявляет no_write — no запись. Начальное значение задаётся справа от знака равенства. */ int no_write = 0, /* Записывает never_write значением 0; never запись. */ never_write = 0; // Флаги, чтобы не писать (если что) /* Указание компилятору DATA_SECTION(logs_block,".logg");: размещение данных или настройка обработки объявлений. */ #pragma DATA_SECTION(logs_block,".logg"); /* Объявляет logs_block — выделенный массив внешней RAM для двух банков регистратора. */ unsigned int logs_block[0xFA00]; /* Объявляет Log — данные Log. */ LOG Log; /* Объявляет flog — данные flog. Начальное значение задаётся справа от знака равенства. */ unsigned int flog=0; // Очищение памати, где логи лежат /* Обнуляет выделенный диапазон внешней RAM журнала и задаёт границы кольцевого буфера. */ void clear_mem() /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет i — данные i. */ unsigned long i; /* Записывает Log.Start значением LOG_PAGE_START; данные Start. */ Log.Start = LOG_PAGE_START; /* Записывает Log.Finis значением LOG_PAGE_START + LOG_PAGE_LEN; данные Finis. */ Log.Finis = LOG_PAGE_START + LOG_PAGE_LEN; /* Записывает Log.Adres значением Log.Start; данные Adres. */ Log.Adres = Log.Start; /* Записывает Log.Circl значением 0; данные Circl. */ Log.Circl = 0; /* Цикл for (i=Log.Start; i= UMP_LOG_CAPACITY - UMP_POST_POINTS); последующий оператор выполняется только при истинном условии. */ if (ump_count >= UMP_LOG_CAPACITY - UMP_POST_POINTS) /* Записывает ump_count значением UMP_LOG_CAPACITY - UMP_POST_POINTS - 1; число сохранённых точек в активном банке. */ ump_count = UMP_LOG_CAPACITY - UMP_POST_POINTS - 1; /* Записывает ump_post значением UMP_POST_POINTS; данные ump_post. */ ump_post = UMP_POST_POINTS; /* Устанавливает биты reason значением 0x100; данные reason. */ reason |= 0x100; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Записывает ump_prev_commands значением commands; ump_prev_inputs = inputs; ump_prev_outputs = outputs; данные ump_prev_commands. */ ump_prev_commands = commands; ump_prev_inputs = inputs; ump_prev_outputs = outputs; /* Every stored point counts, including CAN and I/O, except the trigger. */ /* Проверяет if (ump_post и !(reason & 0x100) и --ump_post == 0) reason = 5; последующий оператор выполняется только при истинном условии. */ if (ump_post && !(reason & 0x100) && --ump_post == 0) reason = 5; /* Записывает p значением logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS; данные p. */ p = logs_block + ((unsigned long)ump_bank * UMP_LOG_CAPACITY + ump_next) * UMP_LOG_WORDS; /* Сохраняет 32-битное миллисекундное время младшим словом вперёд; это первые два слова записи v4. */ p[0] = (unsigned int)ump_time; p[1] = (unsigned int)(ump_time >> 16); /* Событие сохраняет причину и метку отключения; от команд остаются только нужные дискретные пуск и сброс, биты 7/6. */ p[2] = reason; p[3] = commands & 0xC0U; /* Оставляет логические уровни входов и выходов для графиков START, STOP, OMEGA и разрешения нагрузки. */ p[4] = inputs; p[5] = outputs; /* Задание тока берётся из регистра 0x1C в единицах 0,1 мА; это расчётная величина, а не измерение физического тока. */ p[6] = modbus[0x1C]; /* Сохраняет оставшиеся выбранные начальную и конечную уставки в целых мА. */ p[7] = DAC_go; p[8] = DAC_stop; /* Оставляет оба ЗПТ в вольтах: CAN-индекс 305 относится к ЗПТ1, индекс 8 — к ЗПТ2. */ p[9] = ump_pm67_voltage[0]; p[10] = ump_pm67_voltage[1]; /* Резервное нулевое слово делает длину записи чётной: служебный CAN передаёт по два 16-битных слова за фрагмент. */ p[11] = 0; /* Изменяет счётчик в выражении ump_next++; на один для следующего шага. */ ump_next++; /* Проверяет if (ump_next == UMP_LOG_CAPACITY) ump_next = 0; последующий оператор выполняется только при истинном условии. */ if (ump_next == UMP_LOG_CAPACITY) ump_next = 0; /* Проверяет if (ump_count < UMP_LOG_CAPACITY) ump_count++; последующий оператор выполняется только при истинном условии. */ if (ump_count < UMP_LOG_CAPACITY) ump_count++; /* Изменяет счётчик в выражении ump_sequence++; на один для следующего шага. */ ump_sequence++; /* Проверяет if (reason == 5) ump_freeze(); последующий оператор выполняется только при истинном условии. */ if (reason == 5) ump_freeze(); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Завершённый банк больше не изменяется до следующего отключения. * Номер поколения позволяет GUI обнаружить замену архива во время чтения. */ /* Замораживает заполненный банк и переключает рабочий банк, сохраняя доступный архив. */ static void ump_freeze(void) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Записывает ump_archive_bank значением ump_bank; индекс замороженного банка, доступного для чтения. */ ump_archive_bank = ump_bank; /* Записывает ump_archive_count значением ump_count; ump_archive_next = ump_next; ump archive число. */ ump_archive_count = ump_count; ump_archive_next = ump_next; /* Изменяет счётчик в выражении ump_generation++; на один для следующего шага. */ ump_generation++; /* Инвертирует биты ump_bank значением 1; индекс активного банка регистратора. */ ump_bank ^= 1; /* Записывает ump_count значением 0; ump_next = 0; ump_post = 0; число сохранённых точек в активном банке. */ ump_count = 0; ump_next = 0; ump_post = 0; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Продвигает время регистратора в Timer1 и снимает записи с заданным периодом. */ void ump_log_tick(void) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет saved — данные saved. Начальное значение задаётся справа от знака равенства. */ unsigned int saved = __disable_interrupts(); /* Проверяет if (ump_state == 1); последующий оператор выполняется только при истинном условии. */ if (ump_state == 1) { /* Изменяет счётчик в выражении ump_time++; на один для следующего шага. */ ump_time++; /* Проверяет if (++ump_divider >= ump_period); последующий оператор выполняется только при истинном условии. */ if (++ump_divider >= ump_period) { ump_divider = 0; ump_capture(1); } /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Вызывает __restore_interrupts с аргументами (saved) в текущей последовательности операций. */ __restore_interrupts(saved); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Дополнительный снимок только при изменении команд, входов или выходов. * Это не заменяет периодическую запись аналоговых величин в таймере. */ /* Обслуживает изменения дискретных входов и выходов из главного цикла. */ void ump_log_io(void) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет saved — данные saved. */ unsigned int saved; /* Проверяет if (ump_state != 1 или Desk != dsk_LOAD) return; последующий оператор выполняется только при истинном условии. */ if (ump_state != 1 || Desk != dsk_LOAD) return; /* Записывает saved значением __disable_interrupts(); данные saved. */ saved = __disable_interrupts(); /* Проверяет if (Commands != ump_prev_commands или ump_inputs() != ump_prev_inputs или; последующий оператор выполняется только при истинном условии. */ if (Commands != ump_prev_commands || ump_inputs() != ump_prev_inputs || /* Вызывает ump_outputs. Собирает фактические состояния GPIO выходов START/STOP/разрешения нагрузки. */ ump_outputs() != ump_prev_outputs) ump_capture(3); /* Вызывает __restore_interrupts с аргументами (saved) в текущей последовательности операций. */ __restore_interrupts(saved); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Вызывается после применения слов принятого CAN-кадра: снимок содержит * новые команды, а также исходный адрес/маску и все три слова кадра. */ /* Обновляет последние CAN-данные и напряжения для следующего снимка регистратора. */ void ump_log_can(unsigned int header, unsigned int a, unsigned int b, unsigned int c) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет saved — данные saved; i — данные i; address — адрес; mask — маска; data — данные. */ unsigned int saved, i, address, mask, data[3]; /* Проверяет if (Desk != dsk_LOAD) return; последующий оператор выполняется только при истинном условии. */ if (Desk != dsk_LOAD) return; /* Записывает saved значением __disable_interrupts(); данные saved. */ saved = __disable_interrupts(); /* Записывает data[0] значением a; data[1] = b; data[2] = c; данные. */ data[0] = a; data[1] = b; data[2] = c; /* Записывает address значением header & 0x1FFFU; адрес. */ address = header & 0x1FFFU; /* Записывает mask значением 0x8000U; маска. */ mask = 0x8000U; /* Track reception even while recording is stopped. Each mask bit * refers to its own address, including disabled words in between. */ /* Цикл for (i = 0; i < 3; i++, address++, mask >>= 1) ; задаёт начальное значение, условие продолжения и шаг перебора. */ for (i = 0; i < 3; i++, address++, mask >>= 1) { /* Проверяет if (!(header & mask)) continue; последующий оператор выполняется только при истинном условии. */ if (!(header & mask)) continue; /* Проверяет if (address == 305U) ump_pm67_voltage[0] = data[i]; последующий оператор выполняется только при истинном условии. */ if (address == 305U) ump_pm67_voltage[0] = data[i]; /* Проверяет if (address == 8U) ump_pm67_voltage[1] = data[i]; последующий оператор выполняется только при истинном условии. */ if (address == 8U) ump_pm67_voltage[1] = data[i]; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Проверяет if (ump_state != 1); последующий оператор выполняется только при истинном условии. */ if (ump_state != 1) { __restore_interrupts(saved); return; } /* Вызывает ump_capture. Создаёт одну запись состояния: входы, выходы, команды, напряжения и параметры тока нагрузки. */ ump_capture(2); /* Вызывает __restore_interrupts с аргументами (saved) в текущей последовательности операций. */ __restore_interrupts(saved); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Регистрирует изменение слова команд после записи по последовательному интерфейсу. */ void ump_log_rs(void) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет saved — данные saved. Начальное значение задаётся справа от знака равенства. */ unsigned int saved = __disable_interrupts(); /* Вызывает ump_capture. Создаёт одну запись состояния: входы, выходы, команды, напряжения и параметры тока нагрузки. */ ump_capture(4); /* Вызывает __restore_interrupts с аргументами (saved) в текущей последовательности операций. */ __restore_interrupts(saved); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Управление журналом отделено от технологических команд: начало записи * НЕ запускает УМП. Выбор архива не останавливает текущую запись. */ /* Применяет допустимые команды управления регистратором, сохраняя ограничения адресов. */ int ump_log_write(unsigned int address, unsigned int value) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет saved — данные saved; valid — данные valid. Начальное значение задаётся справа от знака равенства. */ unsigned int saved, valid = 1; /* Записывает saved значением __disable_interrupts(); данные saved. */ saved = __disable_interrupts(); /* Проверяет if (address == UMP_LOG_BASE и value == 0); последующий оператор выполняется только при истинном условии. */ if (address == UMP_LOG_BASE && value == 0) { /* Записывает ump_state значением 3; ump_post = 0; данные ump_state. */ ump_state = 3; ump_post = 0; /* Проверяет else if (address == UMP_LOG_BASE и value == 1 и Desk == dsk_LOAD и READY и ump_state != 1); последующий оператор выполняется только при истинном условии. */ } else if (address == UMP_LOG_BASE && value == 1 && Desk == dsk_LOAD && READY && ump_state != 1) { /* Записывает ump_count значением 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0; число сохранённых точек в активном банке. */ ump_count = 0; ump_next = 0; ump_cursor = 0; ump_divider = 0; ump_post = 0; /* Записывает ump_source значением 0; данные ump_source. */ ump_source = 0; /* Записывает ump_prev_commands значением Commands; данные ump_prev_commands. */ ump_prev_commands = Commands; /* Записывает ump_prev_inputs значением ump_inputs(); ump_prev_outputs = ump_outputs(); данные ump_prev_inputs. */ ump_prev_inputs = ump_inputs(); ump_prev_outputs = ump_outputs(); /* Записывает no_write значением 0; never_write = 0; no запись. */ no_write = 0; never_write = 0; /* Записывает ump_state значением 1; данные ump_state. */ ump_state = 1; /* Вызывает ump_capture. Создаёт одну запись состояния: входы, выходы, команды, напряжения и параметры тока нагрузки. */ ump_capture(1); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Проверяет else if (address == UMP_LOG_BASE+1 и ump_state != 1 и value >= 1 и value <= 1000); последующий оператор выполняется только при истинном условии. */ else if (address == UMP_LOG_BASE+1 && ump_state != 1 && value >= 1 && value <= 1000) /* Записывает ump_period значением value; период записи регистратора в миллисекундных тактах. */ ump_period = value; /* Проверяет else if (address == UMP_LOG_BASE+2 и value < UMP_LOG_CAPACITY); последующий оператор выполняется только при истинном условии. */ else if (address == UMP_LOG_BASE+2 && value < UMP_LOG_CAPACITY) /* Записывает ump_cursor значением value; данные ump_cursor. */ ump_cursor = value; /* Проверяет else if (address == UMP_LOG_BASE+3 и value <= 1) ump_source = value; последующий оператор выполняется только при истинном условии. */ else if (address == UMP_LOG_BASE+3 && value <= 1) ump_source = value; /* Выполняет альтернативу, если предыдущее условие if оказалось ложным. */ else valid = 0; /* Вызывает __restore_interrupts с аргументами (saved) в текущей последовательности операций. */ __restore_interrupts(saved); /* Завершает ump_log_write и возвращает valid. */ return valid; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Преобразование хронологического индекса в физическую позицию кольца. * Для архива сохранены собственные count/next, независимые от живого банка. */ /* Начинает функцию ump_word; назначение модуля — двухбанковый регистратор событий нагрузки во внешней RAM. */ static unsigned int ump_word(unsigned int archived, unsigned int record, unsigned int word) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет count — число; ump_count — число сохранённых точек в активном банке. Начальное значение задаётся справа от знака равенства. */ unsigned int count = archived ? ump_archive_count : ump_count; /* Объявляет next — данные next; ump_next — данные ump_next. Начальное значение задаётся справа от знака равенства. */ unsigned int next = archived ? ump_archive_next : ump_next; /* Объявляет bank — данные bank; ump_bank — индекс активного банка регистратора. Начальное значение задаётся справа от знака равенства. */ unsigned int bank = archived ? ump_archive_bank : ump_bank; /* Объявляет slot — данные slot. */ unsigned int slot; /* Проверяет if (record >= count) return 0; последующий оператор выполняется только при истинном условии. */ if (record >= count) return 0; /* Записывает slot значением next + UMP_LOG_CAPACITY - count + record; данные slot. */ slot = next + UMP_LOG_CAPACITY - count + record; /* Повторяет или ожидает, пока выполняется while (slot >= UMP_LOG_CAPACITY) slot -= UMP_LOG_CAPACITY;. */ while (slot >= UMP_LOG_CAPACITY) slot -= UMP_LOG_CAPACITY; /* Завершает ump_word и возвращает logs_block[((unsigned long)bank * UMP_LOG_CAPACITY + slot) * UMP_LOG_WORDS + word]. */ return logs_block[((unsigned long)bank * UMP_LOG_CAPACITY + slot) * UMP_LOG_WORDS + word]; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Весь Modbus-блок читается вызывающим кодом под одной блокировкой IRQ. * Поэтому timestamp/данные и заголовок онлайн-блока относятся к одному * состоянию кольца, даже если в этот момент приходит новая CAN-команда. */ /* Проверяет область чтения регистратора и возвращает слова состояния либо архивной записи. */ unsigned int ump_log_read(unsigned int address) /* Открывает составной блок или описание полей; следующие строки принадлежат этому уровню вложенности. */ { /* Объявляет offset — данные offset; count — число. */ unsigned int offset, count; /* Объявляет first — данные first. */ unsigned long first; /* Вызывает switch с аргументами (address) { в текущей последовательности операций. */ switch (address) { /* Ветвь выбора для UMP_LOG_BASE; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE: return 0x554D; /* Ветвь выбора для UMP_LOG_BASE+1; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+1: return 4; /* Ветвь выбора для UMP_LOG_BASE+2; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+2: return ump_source ? 3 : ump_state; /* Ветвь выбора для UMP_LOG_BASE+3; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+3: return ump_source ? ump_archive_count : ump_count; /* Ветвь выбора для UMP_LOG_BASE+4; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+4: return UMP_LOG_WORDS; /* Ветвь выбора для UMP_LOG_BASE+5; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+5: return UMP_LOG_CAPACITY; /* Ветвь выбора для UMP_LOG_BASE+6; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+6: return ump_period; /* Ветвь выбора для UMP_LOG_BASE+7; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+7: return (unsigned int)ump_time; /* Ветвь выбора для UMP_LOG_BASE+8; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+8: return (unsigned int)(ump_time >> 16); /* Ветвь выбора для UMP_LOG_BASE+9; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+9: return Desk; /* Ветвь выбора для UMP_LOG_BASE+10; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+10: return Mode; /* Ветвь выбора для UMP_LOG_BASE+11; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+11: return (unsigned int)READY_FREQ; /* Ветвь выбора для UMP_LOG_BASE+12; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+12: return ump_generation; /* Ветвь выбора для UMP_LOG_BASE+13; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+13: return ump_archive_count; /* Preserve the legacy millisecond status field as an upper estimate. * Additional event records may finish the post interval sooner. */ /* Ветвь выбора для UMP_LOG_BASE+14; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+14: /* Записывает first значением (unsigned long)ump_post * ump_period; данные first. */ first = (unsigned long)ump_post * ump_period; /* Завершает ump_log_read и возвращает first > 65535UL ? 65535U : (unsigned int)first. */ return first > 65535UL ? 65535U : (unsigned int)first; /* Ветвь выбора для UMP_LOG_BASE+15; выполняется при совпадении селектора switch. */ case UMP_LOG_BASE+15: return ump_state; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Проверяет if (address >= UMP_LOG_DATA и address < UMP_LOG_DATA+120 и (ump_source или ump_state != 1)); последующий оператор выполняется только при истинном условии. */ if (address >= UMP_LOG_DATA && address < UMP_LOG_DATA+120 && (ump_source || ump_state != 1)) { /* Записывает offset значением address - UMP_LOG_DATA; данные offset. */ offset = address - UMP_LOG_DATA; /* Завершает ump_log_read и возвращает ump_word(ump_source, ump_cursor + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS). */ return ump_word(ump_source, ump_cursor + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Последние UMP_LOG_LIVE_RECORDS записей для онлайн-графика, без остановки и без * изменения курсора архивного чтения. Первые два слова — их номер. */ /* Проверяет if (address >= UMP_LOG_LIVE и address < UMP_LOG_LIVE+124); последующий оператор выполняется только при истинном условии. */ if (address >= UMP_LOG_LIVE && address < UMP_LOG_LIVE+124) { /* Записывает count значением ump_count < UMP_LOG_LIVE_RECORDS ? ump_count : UMP_LOG_LIVE_RECORDS; число. */ count = ump_count < UMP_LOG_LIVE_RECORDS ? ump_count : UMP_LOG_LIVE_RECORDS; /* Записывает first значением ump_sequence - count; данные first. */ first = ump_sequence - count; /* Записывает offset значением address - UMP_LOG_LIVE; данные offset. */ offset = address - UMP_LOG_LIVE; /* Проверяет if (offset == 0) return (unsigned int)first; последующий оператор выполняется только при истинном условии. */ if (offset == 0) return (unsigned int)first; /* Проверяет if (offset == 1) return (unsigned int)(first >> 16); последующий оператор выполняется только при истинном условии. */ if (offset == 1) return (unsigned int)(first >> 16); /* Проверяет if (offset == 2) return count; последующий оператор выполняется только при истинном условии. */ if (offset == 2) return count; /* Проверяет if (offset == 3) return ump_generation; последующий оператор выполняется только при истинном условии. */ if (offset == 3) return ump_generation; /* Уменьшает offset значением 4; данные offset. */ offset -= 4; /* Проверяет if (offset / UMP_LOG_WORDS < count); последующий оператор выполняется только при истинном условии. */ if (offset / UMP_LOG_WORDS < count) /* Завершает ump_log_read и возвращает ump_word(0, ump_count - count + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS). */ return ump_word(0, ump_count - count + offset / UMP_LOG_WORDS, offset % UMP_LOG_WORDS); /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ } /* Завершает ump_log_read и возвращает 0. */ return 0; /* Закрывает текущий блок или список полей; следующие операторы относятся к внешнему уровню. */ }