Как мне приостановить/остановить настройку задачи timer1 в этой библиотеке, чтобы я мог использовать timer1 для чего-то еще в своем коде
Поэтому я хочу использовать эту библиотеку, которая использует timer1 для выборки сэмплов с аналогового входа на контакте A0. Он отлично работает, и поэтому после правильного обнаружения я хотел бы выполнять всевозможные задачи. Но для тех, кому мне снова нужен timer1, и я думаю, что эта библиотека поддерживает непрерывную работу подпрограммы timer1. (Я прав?)
(EDIT: уточнение) Я думаю, поэтому другая библиотека, использующая timer1, не будет работать должным образом. Вот почему после выполнения задачи первой библиотеки я хотел бы освободить timer1 для второй библиотеки.)
/*
Original text created by Jacob Rosenthal:
The Goertzel algorithm is long standing so see
http://en.wikipedia.org/wiki/Goertzel_algorithm для полного описания.
It is often used in DTMF tone detection as an alternative to the Fast
Fourier Transform because it is quick with low overheard because it
is only searching for a single frequency rather than showing the
occurrence of all frequencies.
This work is entirely based on the Kevin Banks code found at
http://www.embedded.com/design/configurable-systems/4024443/Алгоритм-Гертцеля
so full credit to him for his generic implementation and breakdown. I've
simply massaged it into an Arduino library. I recommend reading his article
for a full description of whats going on behind the scenes.
Created by Jacob Rosenthal, June 20, 2012.
Released into the public domain.
Modifications 6v6gt 09.09.2019
1. Restructure instance variables to permit multiple instances of class.
2. Make sample array static to share it between instances
3. Pass in sample array as pointer so it can be reused outside class.
4. Drive ADC by timer1 instead of polling ADC in loop()
and reduce resolution to 8 bits.
5. Separate coeeficient calculation from constructor because it
now relies on data unknown at invocation (sample rate)
5. Some consolidation of methods.
6. Software "as is". No claims made about its suitability for any use.
Use at your own risk. Special care required if you use other
analog inputs or an AVR chip other than ATmega328P (Uno etc.).
*/
#include "Arduino.h"
#include "Goertzel.h"
// устанавливается Goertzel::init()
uint8_t * Goertzel::testData; // статическое объявление в .h
uint16_t Goertzel::N ; // то же самое
uint16_t Goertzel::SAMPLING_FREQUENCY ; // то же самое
volatile bool Goertzel::testDataReady = false ; // статическое объявление в .h
// Процедура обслуживания прерывания АЦП
ISR(ADC_vect) {
// загрузить буфер сэмпла при преобразовании семпла.
static uint16_t sampleIndex = 0 ;
if ( ! Goertzel::testDataReady ) {
if ( sampleIndex < Goertzel::N ) {
*(Goertzel::testData + sampleIndex++ ) = ADCH ; // 8 бит. Прямая адресация в байтовый буфер
}
else {
Goertzel::testDataReady = true ; // делаем буфер доступным для потребителя
sampleIndex = 0 ;
}
}
TIFR1 = _BV(ICF1); // сброс флага прерывания
}
// конструктор
Goertzel::Goertzel(float TARGET_FREQUENCY )
{
_TARGET_FREQUENCY = TARGET_FREQUENCY; //должно быть целым числом SAMPLING_RATE/N
}
//статический метод
void Goertzel::init( uint8_t *sampleArray , uint16_t sampleArraySize, uint16_t sampleFrequency ) {
// установить массив выборок, количество выборок и частоту выборки.
// загружаем статические переменные класса
testData = sampleArray ;
N = sampleArraySize ;
SAMPLING_FREQUENCY = sampleFrequency ;
// инициализируем АЦП и Timer1. Timer1 запускает АЦП на частоте SAMPLING_FREQUENCY.
// ISR(ADC_vect) вызывается после завершения преобразования.
cli() ;
// Настройка Timer1 для выбранной частоты дискретизации.
TCCR1A = 0;
TCCR1B = _BV(CS10) | // Бит 2:0 – CS12:0: Выбор часов = без предделителя
_BV(WGM13) | // WGM 12 = CTC ICR1 Немедленный МАКС.
_BV(WGM12); // WGM 12 то же самое
ICR1 = ( (F_CPU ) / SAMPLING_FREQUENCY ) - 1;
// Настраиваем АЦП на запуск по timer1; 8-битное разрешение; Аналоговый порт PC0 (контакт A0) ADMUX
ADMUX = _BV(REFS0) ; // Фиксированное опорное напряжение AVcc для ATMega328P
ADMUX |= _BV(ADLAR) ; // левая корректировка результата преобразования в ADCH (8 бит)
DIDR0 |= _BV(ADC0D); // DIDR0 Регистр отключения цифрового входа 0
ADCSRB = _BV(ADTS2) | // Бит 2:0 ADTS[2:0]: источник автоматического запуска АЦП
_BV(ADTS1) | // Событие захвата Таймера/Счетчика1
_BV(ADTS0); // то же самое
ADCSRA = _BV(ADEN) | // Бит 7 ADEN: Включение АЦП
_BV(ADSC) | // Бит 6 ADSC: запуск преобразования АЦП
_BV(ADATE) | // Бит 5 ADATE: Включение автоматического запуска АЦП
_BV(ADIE) | //
_BV(ADPS0) | // Биты 2:0 ADPS[2:0]: Биты выбора предварительного делителя АЦП (дел. 8)
_BV(ADPS1); // то же самое
sei() ;
}
// метод экземпляра
void Goertzel::getCoefficient( void ) {
// ранее в конструкторе. Теперь SAMPLING_FREQUENCY неизвестна во время вызова.
float omega = (2.0 * PI * _TARGET_FREQUENCY) / SAMPLING_FREQUENCY;
coeff = 2.0 * cos(omega);
}
// метод экземпляра
float Goertzel::detect()
{
Q2 = 0;
Q1 = 0;
for ( uint16_t index = 0; index < N; index++)
{
// образец байта ( *( testData + index ) );
float Q0;
Q0 = coeff * Q1 - Q2 + (float) ( *( testData + index ) - 128 ) ; // 128 для 8 бит; 512 для 10-битного разрешения.
Q2 = Q1;
Q1 = Q0;
}
/* standard Goertzel processing. */
float magnitude = sqrt(Q1 * Q1 + Q2 * Q2 - coeff * Q1 * Q2);
return magnitude ;
}
Я новичок в использовании таймеров на Arduino, но хочу понять и узнать об этом больше. Так что я читаю об этом. Видя, что это не самая простая тема для моей головы, я хотел бы спросить некоторые направления.
На этой великолепной странице я собрал несколько примеров остановки/отключения timer1.
TCCR1B = 0; ТИМСК1 = 0; // отключить прерывание от Таймера 1
страница с информацией о таймере Ника Гэммонса
Однако я пока не совсем понимаю использование библиотечных имен таймеров, регистров, переменных. И поэтому я не уверен, какая часть кода что именно делает, т.е. где, как и когда запускается таймер. И поэтому я в равной степени не уверен, как остановить его, когда задача будет выполнена, и запустить его снова, когда потребуется новое обнаружение. Кажется, есть TCCR1A, TCCR1B, cli() и sei(), которые как-то связаны с этим и ISR(ADC_vect). Кто-то из вас наверняка знает ;)
РЕДАКТИРОВАТЬ: прочитав предлагаемое техническое описание ATmega328p, я обнаружил подробнее о следующих сокращениях:
cli // отключить прерывания во время последовательности по времени __disable_interrupt();
sei // Установить глобальное разрешение прерывания __enable_interrupt();
TCNT1 // Таймер/счетчик (установка 0 сбрасывает)
TIFR1 // Регистр флага прерывания таймера
TCCR1A // Регистр управления таймером/счетчиком 1a (установка 0 сбрасывает)
TCCR1B // Регистр управления таймером/счетчиком 1b (установка 0 сбрасывает)
TIMSK1 // Регистр маски прерывания таймера
ICR1 // Входной регистр захвата
ICF1 // Входной флаг захвата
ICP1 // Входной контакт захвата
WGM // Режим генерации сигнала
CS // Биты выбора часов (предварительный делитель??)
F_CPU // Скорость процессора (какое значение по умолчанию? 8000000??? 1000000??? 16000000???)
DIDR1/DIDR0 // Регистры отключения цифрового входа (если оставить их включенными, они будут использовать чрезмерную мощность, когда аналоговый вход плавает или близок к VCC/2)
ADMUX // Регистр выбора мультиплексора АЦП
REFS1 и усилитель; REFS0 // Выбор опорного напряжения
ADLAR // Результат регулировки левого края АЦП
МУКС
ADEN // Включение АЦП
ADSC // Начало преобразования АЦП
ADATE // Включение автоматического запуска АЦП
ADTS // Выбор источника запуска АЦП
АДКСРБ //
ADIE // Разрешение прерывания завершения преобразования АЦП
ADPS0 и усилитель; ADPS0 // Биты выбора предварительного делителя АЦП
Но это не совсем облегчило мне жизнь. Что такое _BV? например.
EDIT: я думаю, что начинаю понимать это лучше. Я здесь(??):
cli // disable interrupts during timed sequence __disable_interrupt();
sei // Set global Interrupt Enable __enable_interrupt();
TCNT1 // Timer/Counter (setting 0 will reset)
TIFR1 // Timer Interrupt Flag Register
TCCR1A // Timer/Counter Control Register 1a (setting 0 will reset)
TCCR1B // Timer/Counter Control Register 1b (setting 0 will reset)
TIMSK1 // Timer Interrupt Mask Register
ICR1 // Input Capture Register
ICF1 // Input Capture Flag
ICP1 // Input Capture Pin
WGM // Waveform Generation Mode
CS // Clock Select Bits (prescaler??)
F_CPU // The CPU speed (What is the default value? 8000000??? 1000000??? 16000000???)
DIDR1/DIDR0 // Digital Input Disable Registers (if left enabled they will use excessive power when Analog input is floating or close to VCC/2)
ADMUX // ADC Multiplexer Selection Register
REFS1 & REFS0 // Voltage Reference Selection
ADLAR // ADC Left Adjust Result
MUX
ADEN // ADC Enable
ADSC // ADC Start Conversion
ADATE // ADC Auto Trigger Enable
ADTS // ADC Trigger Source Select
ADCSRB //
ADIE // ADC Conversion Complete Interrupt Enable
ADPS0 & ADPS0 // ADC Prescaler Select Bits
Понимание всего этого было бы здорово, но может быть ненужным. Если бы я только знал, как я могу остановить и освободить использование Timer1 для других целей. Думаю, начать все заново было бы проще ;)
@Gaai, 👍0
Обсуждение3 ответа
Лучший ответ:
В последнее время время не на моей стороне. Но вот код, который я придумал до сих пор. Все еще нуждается в тестировании, чтобы увидеть, решит ли он все мои проблемы, но что касается моего первоначального вопроса, это, кажется, содержит ответ, который я искал. Позже я мог бы добавить больше информации/ссылок на настройки регистров, шестнадцатеричные/бинарные/ascii-таблицы, побитовые операции и то, что они все делают. Но руководство — ваш лучший друг.
Хорошо. В первую очередь пригодится эта функция печати:
// Удобно при экспериментировании со всеми различными побитовыми операциями и настройками
void printRegSettings() {
const String regNames[8] = {"TCCR1A", "TCCR1B", "ICR1", "ADMUX", "DIDR0", "ADCSRA", "ADCSRB", "TIFR1"}; //другое: "TCNT1", "OCR1A", "OCR1B", "TIMSK1"
uint8_t regVars[8] = {TCCR1A, TCCR1B, ICR1, ADMUX, DIDR0, ADCSRA, ADCSRB, TIFR1}; //другое: TCNT1, OCR1A, OCR1B, TIMSK1
for (uint8_t index = 0; index < sizeof(regVars)/sizeof(regVars[0]); index++) {
Serial.print(regNames[index]+" = 0b");
for (uint8_t i = 8; i > 0; i--) {
Serial.print(bitRead(regVars[index], i-1));
}
Serial.write('\n');
}
}
Затем поместите все настройки библиотечных регистров в функцию, обратите внимание на логическое значение в конце, для очистки ISR (описано позже):
void dtmfTimerSetup () {
TCCR1A = 0;
TCCR1B = _BV(CS10) | // Бит 2:0 – CS12:0: Выбор часов = без предделителя
_BV(WGM13) | // WGM 12 = CTC ICR1 Немедленный МАКС.
_BV(WGM12); // WGM 12 то же самое
ICR1 = ( (F_CPU ) / 9600 ) - 1;
ADMUX = _BV(REFS0) ; // Фиксированное опорное напряжение AVcc для ATMega328P
ADMUX |= _BV(ADLAR) ; // левая коррекция результата преобразования в ADCH (8 бит)
DIDR0 |= _BV(ADC0D); // DIDR0 Регистр отключения цифрового входа 0
ADCSRB = _BV(ADTS2) | // Бит 2:0 ADTS[2:0]: источник автоматического запуска АЦП
_BV(ADTS1) | // Событие захвата Таймера/Счетчика1
_BV(ADTS0); // то же самое
ADCSRA = _BV(ADEN) | // Бит 7 ADEN: Включение АЦП
_BV(ADSC) | // Бит 6 ADSC: запуск преобразования АЦП
_BV(ADATE) | // Бит 5 ADATE: Включение автоматического запуска АЦП
//_BV(ADIE) | //
_BV(ADPS0) | // Биты 2:0 ADPS[2:0]: Биты выбора предварительного делителя АЦП (дел. 8)
_BV(ADPS1); // то же самое
Goertzel::dtmfListenerISR = true;
}
Далее добавьте функцию для сброса всех этих настроек. Насколько я проверял, начальные значения регистров равны 0. Поэтому установка их на ноль делает работу. На этот раз снова обратите внимание на логическое значение false.
void clearTimerSettings() {
// Пустая процедура прерывания Goertzel dtmf ISR
Goertzel::dtmfListenerISR = false;
// сбрасываем все измененные регистры в начальное значение 0
Serial.println(F("***CLEARING ALL BIT SETTINGS***"));
TCCR1A = TCCR1B = ICR1 = ADMUX = DIDR0 = ADCSRA = ADCSRB = TIFR1 = 0;
// приостановить, но не стирать текущие настройки прерывания:
// bitClear(ADCSRA, ADIE); //возможна интерференция, когда другая библиотека adc таймера снова устанавливает бит
}
В Goertzel.h добавьте это объявление static bool в общедоступный класс Goertzel:
static volatile bool Goertzel::dtmfListenerISR;
Добавьте логическое объявление в Goertzel.cpp и добавьте оператор if внутри ISR (ADC_vect), чтобы прерывание выполнялось только в том случае, если dtmfListenerISR имеет значение true.
volatile bool Goertzel::dtmfListenerISR;
// Процедура обслуживания прерывания АЦП
ISR(ADC_vect) {
//если dtmfListenerISR ложно, этот код пропускается, освобождая timer1 для других целей
if (Goertzel::dtmfListenerISR) {
// загрузить буфер сэмпла при преобразовании семпла.
static uint16_t sampleIndex = 0 ;
if ( ! Goertzel::testDataReady ) {
if ( sampleIndex < Goertzel::N ) {
*(Goertzel::testData + sampleIndex++ ) = ADCH ; // 8 бит. Прямая адресация в байтовый буфер
}
else {
Goertzel::testDataReady = true ; // делаем буфер доступным для потребителя
sampleIndex = 0 ;
}
}
TIFR1 = _BV(ICF1); // сброс флага прерывания
}
}
Наконец-то используйте эти функции в своем скетче. Настройка может быть выполнена в программе установки, но очистка, конечно же, должна происходить в вашем пустом цикле, как только библиотека и прерывание таймера закончат свою работу, и вы захотите, чтобы другая библиотечная/таймерная процедура вступила во владение. Просто для тестирования я поместил их все в установку и простой скетч.
void setup() {
Serial.begin(115200);
//настройка/инициализация процедуры прерывания таймера:
dtmfTimerSetup();
//ДЛЯ ТЕСТИРОВАНИЯ: проверьте и распечатайте текущие настройки:
printRegSettings();
// очистить процедуру прерывания таймера;
clearTimerSettings();
printRegSettings(); //только для целей тестирования
}
void loop() {
// поместите сюда ваш основной код для многократного запуска:
}
Конечно, для других библиотек, использующих timer1, вы должны сделать то же самое: настроить функции с настройками регистра/очисткой и объявить/установить логическое значение для части ISR. Здесь может быть какой -то перебор, но пока я не нашел более элегантного способа добиться всего этого.
Я не совсем понимаю, зачем вообще нужно останавливать таймер. Таймеры — это просто вещи, которые считают «тики». который можно запрограммировать на достижение различных скоростей (обычно это некоторое деление скорости процессора).
Представьте, что у вас на кухне есть часы. И представьте, что вы печете торт. Вы наблюдаете время, когда вы ставите торт в духовку. Затем вы отмечаете, в какое время торт будет готов. Между тем, те же часы можно использовать для измерения времени варки яйца. Вам не нужно останавливать или «сбрасывать» часы, чтобы добиться этого.
Просто дайте часам идти и делать свое дело, а тем временем отмечайте, сколько времени должно пройти, чтобы что-то было "сделано".
Тем временем у Uno (и Nano) есть другие таймеры. Вы можете использовать их. Это как владеть тремя часами, которые могут работать с разной скоростью или, точнее, с разным уровнем точности.
Это потому, что я хотел бы использовать другую (более сложную) библиотеку, написанную не мной, которая использует timer1. И кажется, что их использование противоречит друг другу. Думаю, потому что они оба хотят использовать свои любимые кухонные часы ;) И я еще не дипломат-кодировщик, чтобы убедить их в обратном., @Gaai
вкратце: я не хотел останавливать таймер (как в часах), а скорее задачу (прерывание / isr), связанную с ним. Возможно, я мог бы лучше сформулировать свой вопрос, но раньше не мог, потому что не знал правильных слов. Кажется, люди используют «настроить таймер», когда на самом деле имеют в виду: «настроить процедуру прерывания ISR» или что-то в этом роде., @Gaai
Я отвечаю здесь лишь на малую часть вопроса:
ADMUX = (0b00 << REFS0); // сдвиг 0 не имеет никакого эффекта, поэтому его можно опустить ADMUX |= (1 << ADLAR); ADMUX |= (0b00000 << MUX0); // сдвиг 0 не имеет никакого эффекта, поэтому его можно опустить
То же самое, что и:
ADMUX = (1 << REFS0); // можно опустить ADMUX |= (1 << ADLAR); ADMUX |= (1 << MUX0); // можно опустить
Нет, это не то же самое.
Начиная с первой строки: (0b00 << REFS0)
означает "ноль
со сдвигом влево на REFS0 (т.е. шесть) бит». Это не нулевой сдвиг, т.к.
указано в комментарии: это 6-битный сдвиг. Результирующее значение равно нулю,
потому что сдвигаемое число с самого начала равно нулю, и потому что
освободившиеся биты заполняются нулями при сдвиге влево.
Напротив, (1 << REFS0)
означает "один со сдвигом влево на биты REFS0".
Полученное значение:
2REFS0 = 26 = 0b01000000 = 64
В первом фрагменте кода ADMUX заканчивается только битом ADLAR. поставил. Во втором фрагменте все три бита (REFS0, ADLAR и MUX0) равны установить.
Правка: объяснение понятия «освобожденные биты заполняются нулями». во время левого смещения». Предположим, что x = 0bABCDEFGH — это байт, где A…H являются отдельными битами («0b» — это префикс, означающий «следующее находится в бинарный»). Сдвиг этого байта влево дает следующее:
x << 0 = 0bABCDEFGH
x << 1 = 0bBCDEFGH0
x << 2 = 0bCDEFGH00
x << 3 = 0bDEFGH000
...
Под «освобожденными битами заполняются нулями» я подразумеваю, что биты, которые находятся за пределами
последний исходный бит (после H) заменяется нулями. Обратите внимание, что ноль
shift «не имеет эффекта» в том смысле, что x<<0
совпадает с x
. Примечание
также то, что сдвиг нуля значения на любую величину всегда дает ноль.
В этом конкретном коде значение, записываемое в ADMUX, может быть вычисляется следующим образом:
1 << REFS0 = 1 << 6 = 0b00000001 << 6 = 0b01000000
1 << ADLAR = 1 << 5 = 0b00000001 << 5 = 0b00100000
1 << MUX0 = 1 << 0 = 0b00000001 << 0 = 0b00000001
──────────────────────────────────────────────────
(1<<REFS0) | (1<<ADLAR) | (1<<MUX0) = 0b01100001
Спасибо, что нашли время, чтобы посмотреть на это. Я полагаю, мне есть еще что почитать. И посмотрим, смогу ли я найти источник этого комментария., @Gaai
не могли бы вы уточнить это: «освобожденные биты заполняются нулями во время сдвига влево». Я запутался, потому что думал, что операция сдвига влияет только на сдвинутый бит, но теперь я читаю, что вся строка битов может быть сдвинута влево (или вправо). Тогда есть также разница между логическим ИЛИ или И. В случае смещения нуля с ИЛИ, это вообще имеет эффект? И 1 смена с ИЛИ? Влияет ли это на освободившиеся биты?, @Gaai
@Gaai: Что вы подразумеваете под «сдвигом нуля»? Сдвиг некоторого числа на ноль бит или сдвиг числа ноль на некоторое количество бит? Что вы имеете в виду под "иметь эффект"? **Каждое выражение** имеет эффект или возвращает некоторое значение. Re OR против AND: пожалуйста, посмотрите таблицы истинности этих операторов самостоятельно. В остальном см. измененный ответ., @Edgar Bonet
Имея эффект я имел в виду: собственно установка/изменение/запись в бит. Вместо того, чтобы просто читать, что тоже является эффектом, как вы заметили, конечно., @Gaai
Я нашел эту строку кода с комментарием на форуме. Я не использовал это правильно. Но я изо всех сил пытался понять, почему я не видел настройку бита для A0 в качестве входного контакта. В то время как когда другие контакты используются (где-то другими людьми), они устанавливают их с помощью (MUX0, MUX1, MUX2 или другими способами, хотя кажется, что всегда есть больше способов сделать то же самое в битовых настройках). Во всяком случае, я подумал, что это могло быть связано с тем, что A0 был установлен какой-то нулевой установкой в другом месте (называемой нулевым сдвигом), что означало, что его можно было опустить... Теперь я лучше знаю, как это делается, благодаря вам и некоторому дальнейшему чтению. Это запутанное путешествие ;), @Gaai
поэтому я попробовал ADMUX = _BV(0); // установка 0b00000001 а затем ADMUX = _BV(7); // ожидая, что приведенное выше сдвинется влево, чтобы получить 11000000. Но bitRead дает мне: 01000000. Сначала установив ADMUX на 11111111, а затем выполнив _BV(7), я все еще получаю 01000000., @Gaai
@Gaai: я не понимаю вашего комментария., @Edgar Bonet
ADMUX = 0b00000001, за которым следует: ADMUX = _BV(7); Выдает: 0b01000000. Хотя, если бы он был сдвинут влево, я бы ожидал 0b11000000. Читаю ваше объяснение., @Gaai
- avrdude ser_open() can't set com-state
- Как отправить команду AT на sim800l с помощью SoftwareSerial
- Ведомое устройство Arduino с двумя мастерами, использующими одну и ту же шину I2C?
- Arduino Uno: avrdude: stk500_recv(): программатор не отвечает
- В чем разница между delay() и delaymicroseconds()
- Программирование Arduino на Паскале?
- Как синхронизировать несколько ардуино?
- Arduino nano KiCad символы
Вы пытались посмотреть техническое описание Atmega328p (это микроконтроллер Uno/Nano)? Имена регистров (например, TCCR1A) взяты оттуда. Он также описывает функцию каждого бита в каждом регистре., @chrisl
Делаю это сейчас., @Gaai
Относительно «_Что такое \_BV?_»: как указано в [документации:](https://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html),
#define _BV(bit) ( 1 << (бит))
, @Edgar BonetВ чем разница между: TIFR1 = _BV(ICF1); И TIFR1 |= бит (OCF1A); В комментариях к коду они оба очищают флаг захвата ввода или регистр флага прерывания..., @Gaai
Относительно
_BV()
иbit()
:_BV()
исходит из avr-libc, тогда какbit()
[из ядра Arduino](https://www.arduino.cc/reference /ru/language/functions/bits-and-bytes/bit/). В остальном они в основном одинаковы, за исключением того, что [bit()
возвращаетunsigned long
](https://github.com/arduino/ArduinoCore-avr/blob/1.8.5/cores/arduino/Arduino.h #L124)., @Edgar BonetРебята, комментарии под вопросом предназначены для **уточнения вопроса**. Не за то, что ответил. Видите это большое поле «ответ»? Вот куда идут ответы. :), @Nick Gammon
Мой был в поле ответа первым, бит сдвинулся. Между тем, говоря так, ваш ответ, возможно, должен был быть комментарием?, @Gaai