Запись регистров по маске

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Доброго всем!

Изучать МК начал не так давно, многое ещё не понятно, и вот опять наткнулся на подводные камни.

Есть вот такой кусок кода на 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));
KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Сорри, опечатался в последнем вопросе - нужно было указать только один регистр, у меня же в третьей строке другой регистр указан. Надо было так:

ADMUX &= ~(_BV(REFS1));
ADMUX &= ~(_BV(MUX0));

Как стереть несколько битов в регистре - одной строкой?

Волшебник
Offline
Зарегистрирован: 22.12.2016

Можно так

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

Волшебник
Offline
Зарегистрирован: 22.12.2016

KoJIxo3Huk пишет:

ADMUX &= ~(_BV(REFS1));

ADMUX &= ~(_BV(MUX0));

Как стереть несколько битов в регистре - одной строкой?

ADMUX &= ~((1<<REFS1) | (1<<MUX0));

 

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Спасибо! Вечером попробую, сейчас ардуины нет под рукой.

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Решил поиграться сразу в 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 - ну и ладно. бог с ним. Но в будущем, возможно возникнет подобная ситуация с другими регистрами, где будет критичен непреднамеренный сброс подобных битов. Конкретно сейчас мне это нужно  только для изучения разных приёмов программирования МК.

В итоге придумал данную конструкцию:

uint8_t mask = 0b11101111;
uint8_t oldRegVal = ADCSRA;
oldRegVal &= mask;
uint8_t newRegVal = 0;
newRegVal = (_BV(ADPS2) | _BV(ADPS0));
newRegVal &= mask; //На всякий случай, чтобы в будущем по запарке засетив ADIF - сбросить его тут.
ADCSRA = newRegVal;
// Делаем что-нибудь полезное.. или бесполезное :)
ADCSRA = oldRegVal;

 

Волшебник
Offline
Зарегистрирован: 22.12.2016

Понятно, то что Вас интересует называется битовые операции, гугл полон исчерпывающего материала, верхних два линка

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;
	}
}

 

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

То что нужно! Спасибо за ссылки! Помню что читал у DiHalt-а в курсе AVR про работу с битами, но чёт листал-листал и так и не нашёл где.

KoJIxo3Huk
Offline
Зарегистрирован: 07.02.2017

Вот жил раньше - не тужил, программировал себе в Arduino IDE и забот не знал. А как поставил AtmelStidio - так и ужаснулся.

Вот есть две формы записи: 

DDRB |= (1<<DDB7);
pinMode(LED_BUILTIN, OUTPUT);

Обе вроде одно и то же делают - инициализируют пин PB7 как выход. А разница:

DDRB |= (1<<DDB7); - выполняется всего за 2 такта, по времени это ~0,12мкс (при частоте 16МГц)

pinMode(LED_BUILTIN, OUTPUT); - выполняется за 73 такта, по времени это ~4,56мкс.

Ужас! Но за всё в этом мире нужно платить. в т.ч. и за удобство программирования.

Вот как это делает запись "DDRB |= (1<<DDB7);"

  DDRB |= (1<<DDB7);
0000011A  SBI 0x04,7		Set bit in I/O register 

А вот так - "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 			Выходим из подпрограммы