Запись регистров по маске
- Войдите на сайт для отправки комментариев
Доброго всем!
Изучать МК начал не так давно, многое ещё не понятно, и вот опять наткнулся на подводные камни.
Есть вот такой кусок кода на Arduino Mega2560:
//================================================================ // http://habrahabr.ru/post/141442/ #define FASTADC 1 // defines for setting and clearing register bits #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif //================================================================ void setup() { // put your setup code here, to run once: } void loop() { // put your main code here, to run repeatedly: uint8_t adcsrb = ADCSRB; Serial.print("ADCSRB = "); Serial.println(adcsrb, BIN); // Сохраняем прежние настройки регистра ADCSRA: uint8_t oldADCSRA = ADCSRA; // set prescale to 128 --> делитель частоты процессора. Чем выше это число (чем медленнее измерение) // - тем точнее измерение. sbi(ADCSRA, ADPS2) ; sbi(ADCSRA, ADPS1) ; sbi(ADCSRA, ADPS0) ; adcsrb = ADCSRB; Serial.print("ADCSRB = "); Serial.println(adcsrb, BIN); //░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ //╔═════════════════════════════════════════╗ //║ Код для Arduino MEGA ║ //╚═════════════════════════════════════════╝ // устанавливаем: Voltage Reference Selections for ADC = AVCC with external capacitor at AREF pin // - в этом случае: REFS1 = 0, REFS0 = 1 // устанавливаем: Input Channel Selections = 1.1V (VBG), // - в этом случае: MUX5..0 = 011110, т.е. MUX5 = 0, MUX4 = 1, MUX3 = 1, MUX2 = 1, MUX1 = 1, MUX0 = 0 ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); ADMUX &= ~(_BV(REFS1)); ADMUX &= ~(_BV(MUX0)); ADCSRB &= ~(_BV(MUX5)); delay(100); // Wait for Vсс to settle //if (bit_is_set(ADCSRB, MUX5)) for (int az = 0; az < 10; az++) { // Остальные строки точно такие же, что и для Arduino UNO: ADCSRA |= _BV(ADSC); // начало преобразований while (bit_is_set(ADCSRA, ADSC)); // измерение uint8_t low = ADCL; // сначала нужно прочесть ADCL - это запирает ADCH uint8_t high = ADCH; // разлочить оба float result = (high << 8) | low; result = (1.141 * 1023.0) / result; // Результат Vcc в Вольтах // Коэффициент 1.141 - подбирается вручную. В идеале это должен быть ровно 1.1 Вольта, // но на практике - это всегда не так :) Хотя и попадает в указанный в даташите допуск = 10% } // set prescale to 32 //sbi(ADCSRA, ADPS2) ; //cbi(ADCSRA, ADPS1) ; //sbi(ADCSRA, ADPS0) ; ADCSRA = oldADCSRA; adcsrb = ADCSRB; Serial.print("ADCSRB = "); Serial.println(adcsrb, BIN); }
Вкратце: для ускорения работы АЦП выставил по умолчанию в регистре ADCSRA предделитель на 32 (ADPS2..0 = 101). Но в некоторый момент времени нужно вернуть предделитель на 128 для более точного измерения. И всё вроде было хорошо, пока чёрт не дёрнул меня посмотреть что творится в регистре ADCSRA после изменения битов. Прописал я строки
uint8_t adcsrb = ADCSRB; Serial.print("ADCSRB = "); Serial.println(adcsrb, BIN);
в разных местах и увидел следующую картину: в самом начале программы имеем, например, регистр ADCSRA = 10010101
После того как изменяю биты в регистре командами:
sbi(ADCSRA, ADPS2) ; sbi(ADCSRA, ADPS1) ; sbi(ADCSRA, ADPS0) ;
то наблюдаю следующую картину: регистр ADCSRA = 10000111. Т.е. биты ADPS2..0 установились как положено, но при этом сбросился бит ADIF!!! В даташите сказано, что при записи в него "1" или при работе с командами SBI и CBI - он сбрасывается.
Вот и возникло у меня несколько вопросов:
1. как можно корректно изменить значение некоторых регистров (возможно - по маске??), в которых часть битов может сбрасываться при записи в него "1" или при работе с командами SBI и CBI
2. Команды SBI и CBI вроде должны работать только с адресами $00 - $0f, а как тогда работают здесь данные команды? Ведь адрес регистра ADCSRA = 0x7A.
3. Подскажите ещё - как в одну строку записать код:
ADMUX &= ~(_BV(REFS1)); ADMUX &= ~(_BV(MUX0)); ADCSRB &= ~(_BV(MUX5));
Сорри, опечатался в последнем вопросе - нужно было указать только один регистр, у меня же в третьей строке другой регистр указан. Надо было так:
Как стереть несколько битов в регистре - одной строкой?
Можно так
Про ADIF не заморачивайся, флаг прерывания тебе ни к чему, раз самого прерывания в программе нету .
CBI & SBI работают с байтами, т.е. 0xFF.
http://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html
ADMUX &= ~(_BV(REFS1));
Как стереть несколько битов в регистре - одной строкой?
Спасибо! Вечером попробую, сейчас ардуины нет под рукой.
Решил поиграться сразу в AVR-Studio - так нагляднее.
Предложенный вариант:
Можно так
мне не совсем подходит. здесь жёстко заданы все состояния битов. Мне же хотелось сохранить предыдущее состояние регистра, изменить один-два бита, произвести некоторые действия и затем вернуть предыдущее состояние регистра. При этом нужно было учесть маской биты, в которые нельзя записывать "1" если он может от этого сброситься. Я не имею в виду конкретно регистр ADCSRA и его бит ADIF - с нми всё ясно - сбросится этот ADIF - ну и ладно. бог с ним. Но в будущем, возможно возникнет подобная ситуация с другими регистрами, где будет критичен непреднамеренный сброс подобных битов. Конкретно сейчас мне это нужно только для изучения разных приёмов программирования МК.
В итоге придумал данную конструкцию:
Понятно, то что Вас интересует называется битовые операции, гугл полон исчерпывающего материала, верхних два линка
http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-chast-4.html
http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-chast-4.html
http://ph0en1x.net/news/81-howto-work-with-ports-register-bits-in-microcontroller.html
http://ph0en1x.net/news/81-howto-work-with-ports-register-bits-in-microcontroller.html
Да, и ещё примеры можно посмотреть в самих ардуиновских фаилах, ищите wiring_digital.c
То что нужно! Спасибо за ссылки! Помню что читал у DiHalt-а в курсе AVR про работу с битами, но чёт листал-листал и так и не нашёл где.
Вот жил раньше - не тужил, программировал себе в Arduino IDE и забот не знал. А как поставил AtmelStidio - так и ужаснулся.
Вот есть две формы записи:
Обе вроде одно и то же делают - инициализируют пин PB7 как выход. А разница:
DDRB |= (1<<DDB7); - выполняется всего за 2 такта, по времени это ~0,12мкс (при частоте 16МГц)
pinMode(LED_BUILTIN, OUTPUT); - выполняется за 73 такта, по времени это ~4,56мкс.
Ужас! Но за всё в этом мире нужно платить. в т.ч. и за удобство программирования.
Вот как это делает запись "DDRB |= (1<<DDB7);"
А вот так - "pinMode(LED_BUILTIN, OUTPUT);":