Запись регистров по маске
- Войдите на сайт для отправки комментариев
Доброго всем!
Изучать МК начал не так давно, многое ещё не понятно, и вот опять наткнулся на подводные камни.
Есть вот такой кусок кода на 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));
Сорри, опечатался в последнем вопросе - нужно было указать только один регистр, у меня же в третьей строке другой регистр указан. Надо было так:
Как стереть несколько битов в регистре - одной строкой?
Можно так
void adc_init(uint8_t num_chanl) { uint8_t temp_reg = mux[num_chanl]; // ADMUX = temp_reg; ADMUX = ((1<<REFS1)| // Reference Selection Bits (1<<REFS0)| // 2.56V Internal (0<<ADLAR)| // ADC Left Adjust Result (1<< MUX4)| (0<< MUX3)| // Gain / Channel Selection Bits (0<< MUX2)| (0<< MUX1)| // (1) 10000 ADC8<->ADC9 Gain = 1x. (0<< MUX0)); ADCSRA = ((1<< ADEN)| // 1 = ADC Enable (0<< ADSC)| // ADC Start Conversion (1<<ADATE)| // 1 = ADC Auto Trigger Enable (0<< ADIF)| // ADC Interrupt Flag (0<< ADIE)| // ADC Interrupt Enable (1<<ADPS2)| (0<<ADPS1)| // ADC Prescaler : 1 MHz. (0<<ADPS0)); ADCSRB = ((1<< MUX5)| // Always =1 for 8-15 channels (1<<ADTS2)| // Sets Auto Trigger source Timer/Counter1 Compare Match B (0<<ADTS1)| (1<<ADTS0)); DIDR2 = 0xFF; }Про ADIF не заморачивайся, флаг прерывания тебе ни к чему, раз самого прерывания в программе нету .
CBI & SBI работают с байтами, т.е. 0xFF.
http://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html
ADMUX &= ~(_BV(REFS1));
Как стереть несколько битов в регистре - одной строкой?
Спасибо! Вечером попробую, сейчас ардуины нет под рукой.
Решил поиграться сразу в AVR-Studio - так нагляднее.
Предложенный вариант:
Можно так
void adc_init(uint8_t num_chanl) { uint8_t temp_reg = mux[num_chanl]; // ADMUX = temp_reg; ADMUX = ((1<<REFS1)| // Reference Selection Bits (1<<REFS0)| // 2.56V Internal (0<<ADLAR)| // ADC Left Adjust Result (1<< MUX4)| (0<< MUX3)| // Gain / Channel Selection Bits (0<< MUX2)| (0<< MUX1)| // (1) 10000 ADC8<->ADC9 Gain = 1x. (0<< MUX0)); ADCSRA = ((1<< ADEN)| // 1 = ADC Enable (0<< ADSC)| // ADC Start Conversion (1<<ADATE)| // 1 = ADC Auto Trigger Enable (0<< ADIF)| // ADC Interrupt Flag (0<< ADIE)| // ADC Interrupt Enable (1<<ADPS2)| (0<<ADPS1)| // ADC Prescaler : 1 MHz. (0<<ADPS0)); ADCSRB = ((1<< MUX5)| // Always =1 for 8-15 channels (1<<ADTS2)| // Sets Auto Trigger source Timer/Counter1 Compare Match B (0<<ADTS1)| (1<<ADTS0)); DIDR2 = 0xFF; }мне не совсем подходит. здесь жёстко заданы все состояния битов. Мне же хотелось сохранить предыдущее состояние регистра, изменить один-два бита, произвести некоторые действия и затем вернуть предыдущее состояние регистра. При этом нужно было учесть маской биты, в которые нельзя записывать "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
~/Softvari/arduino-1.8.1/hardware/arduino/avr/cores/arduino void pinMode(uint8_t pin, uint8_t mode) { uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); volatile uint8_t *reg, *out; if (port == NOT_A_PIN) return; // JWS: can I let the optimizer do this? reg = portModeRegister(port); out = portOutputRegister(port); if (mode == INPUT) { uint8_t oldSREG = SREG; cli(); *reg &= ~bit; *out &= ~bit; SREG = oldSREG; } else if (mode == INPUT_PULLUP) { uint8_t oldSREG = SREG; cli(); *reg &= ~bit; *out |= bit; SREG = oldSREG; } else { uint8_t oldSREG = SREG; cli(); *reg |= bit; SREG = oldSREG; } }То что нужно! Спасибо за ссылки! Помню что читал у 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);":
pinMode(LED_BUILTIN, OUTPUT); 0000011A LDI R22,0x01 Load immediate Записываем "1" в R22 - этим будет DDRB7 назначен как выход 0000011B LDI R24,0x0D Load immediate Записываем "13(0x0D)" в R24 - это номер пина с LEDом 0000011C RJMP PC+0x017C Relative jump 00000298 PUSH R28 Push register on stack Сохраняем R28 в стек 00000299 PUSH R29 Push register on stack Сохраняем R29 в стек uint8_t bit = digitalPinToBitMask(pin); 0000029A LDI R25,0x00 Load immediate Обнуляем R25 0000029B MOVW R30,R24 Copy register pair Копируем пару R24:R25 в R30:R31 0000029C SUBI R30,0xB2 Subtract immediate Из R30=13(0x0D) вычитаем 0xB2, получаем 5B 0000029D SBCI R31,0xFE Subtract immediate with carry Из 0х00 вычли (-2) с учётом предыдущего заёма (флаг С) - получили 0х01 в R31. А R30:R31 - это у нас регистр Z! 0000029E LPM R18,Z Load program memory Загружаем из FLASH-памяти в регистр R18 число 0x80, находящееся по адресу, указанному в регистровой паре Z = 0x015B uint8_t port = digitalPinToPort(pin); 0000029F MOVW R30,R24 Copy register pair Копируем пару R24:R25 (0x000D) в R30:R31 000002A0 SUBI R30,0x6C Subtract immediate Из R30=13(0x0D) вычитаем 0x6C, получаем A1 000002A1 SBCI R31,0xFE Subtract immediate with carry Из 0х00 вычли (-2) с учётом предыдущего заёма (флаг С) - получили 0х01 в R31. Итого: 0x01A1 000002A2 LPM R24,Z Load program memory Загружаем из FLASH-памяти в регистр R24 число 0x02, находящееся по адресу, указанному в регистровой паре Z = 0x01A1 if (port == NOT_A_PIN) return; 000002A3 TST R24 Test for Zero or Minus Проверяем регистр на "0" или "-". Если он там есть - стави флаг Z. Сейчас R24=0x02 000002A4 BREQ PC+0x2D Branch if equal Если есть флаг Z - переход по метке. reg = portModeRegister(port); 000002A5 LDI R25,0x00 Load immediate Обнуляем R25 000002A6 LSL R24 Logical Shift Left Умножаем на 2 регистр R24->будет R24=0x04 000002A7 ROL R25 Rotate Left Through Carry Умножаем на 2 регистр R25 c учётом флага C 000002A8 MOVW R30,R24 Copy register pair Копируем пару R24:R25=0x0004 в R30:R31 000002A9 SUBI R30,0x0C Subtract immediate Из R30=0x04 вычитаем 0x0C, получаем 0xF8 000002AA SBCI R31,0xFE Subtract immediate with carry Из 0х00 вычли (-2) с учётом предыдущего заёма (флаг С) - получили 0х01 в R31. Итого: 0x01F8 000002AB LPM R28,Z+ Load program memory and postincrement Загружаем из FLASH-памяти в регистр R28 число 0x24, находящееся по адресу, указанному в регистровой паре Z = 0x01F8 и увеличиваем адрес в Z-регистре на 1 000002AC LPM R29,Z Load program memory Загружаем из FLASH-памяти в регистр R29 число 0x00, находящееся по адресу, указанному в регистровой паре Z = 0x01F9 out = portOutputRegister(port); 000002AD MOVW R30,R24 Copy register pair Копируем пару R24:R25=0x0004 в R30:R31 000002AE SUBI R30,0x26 Subtract immediate Из R30=0x04 вычитаем 0x26, получаем 0xDE 000002AF SBCI R31,0xFE Subtract immediate with carry Из 0х00 вычли (-2) с учётом предыдущего заёма (флаг С) - получили 0х01 в R31. Итого: 0x01DE 000002B0 LPM R26,Z+ Load program memory and postincrement Загружаем из FLASH-памяти в регистр R26 число 0x25, находящееся по адресу, указанному в регистровой паре Z = 0x01DE и увеличиваем адрес в Z-регистре на 1 000002B1 LPM R27,Z Load program memory Загружаем из FLASH-памяти в регистр R27 число 0x00, находящееся по адресу, указанному в регистровой паре Z = 0x01DF if (mode == INPUT) { 000002B2 CPSE R22,R1 Compare, skip if equal Сравниваем два регистра Если они равны - то пропускаем следующую инструкцию. У нас: R22=0x01, R1=0x00 000002B3 RJMP PC+0x000A Relative jump -------------------- Этот блок мы перепрыгнули по условию --------------------- // uint8_t oldSREG = SREG; //000002B4 IN R25,0x3F In from I/O location // cli(); //000002B5 CLI Global Interrupt Disable // *reg &= ~bit; //000002B6 LDD R24,Y+0 Load indirect with displacement //000002B7 COM R18 One's complement //000002B8 AND R24,R18 Logical AND //000002B9 STD Y+0,R24 Store indirect with displacement // *out &= ~bit; //000002BA LD R30,X Load indirect //000002BB AND R18,R30 Logical AND //000002BC RJMP PC+0x000C Relative jump -------------------- Этот блок мы перепрыгнули по условию --------------------- } else if (mode == INPUT_PULLUP) { 000002BD CPI R22,0x02 Compare with immediate Сравниваем регистр с константой: R22 и 0x02 000002BE BRNE PC+0x0D Branch if not equal Прыжок, если не равны R22 и 0x02 (если Z=0) -------------------- Этот блок мы перепрыгнули по условию --------------------- // uint8_t oldSREG = SREG; //000002BF IN R25,0x3F In from I/O location // cli(); //000002C0 CLI Global Interrupt Disable // *reg &= ~bit; //000002C1 LDD R19,Y+0 Load indirect with displacement //000002C2 MOV R24,R18 Copy register //000002C3 COM R24 One's complement //000002C4 AND R24,R19 Logical AND //000002C5 STD Y+0,R24 Store indirect with displacement // *out |= bit; //000002C6 LD R30,X Load indirect //000002C7 OR R18,R30 Logical OR //000002C8 ST X,R18 Store indirect // SREG = oldSREG; //000002C9 OUT 0x3F,R25 Out to I/O location //000002CA RJMP PC+0x0007 Relative jump -------------------- Этот блок мы перепрыгнули по условию --------------------- uint8_t oldSREG = SREG; 000002CB IN R24,0x3F In from I/O location Сохраняем статусный регистр SREG в R24 cli(); 000002CC CLI Global Interrupt Disable Запрещаем прерывания *reg |= bit; 000002CD LDD R30,Y+0 Load indirect with displacement В регистре Y=R28:r29 - число 0x24. По этому адресу находится регистр DDRB. 000002CE OR R18,R30 Logical OR Складываем по ИЛИ записанное значение регистра DDRB=0x00 (до этого ни один бит DDRB не был установлен) с маской R18=0x80 (это как раз для порта PB7). В итоге R18=0x80 000002CF STD Y+0,R18 Store indirect with displacement Изменяем биты в DDB7 в соответствии с R18 SREG = oldSREG; 000002D0 OUT 0x3F,R24 Out to I/O location Восстанавливаем статусный регистр SREG из R24 } 000002D1 POP R29 Pop register from stack Восстанавливаем R29 из стека 000002D2 POP R28 Pop register from stack Восстанавливаем R28 из стека 000002D3 RET Subroutine return Выходим из подпрограммы