RTOS и управление режимами энергосбережения.

В предыдущем посте про litenkjerne-430
спрашивали про krn_uthread_idle и где-же в ней энергосбережение. На тот момент это была просто заглушка и я обещал в скором времени запостить пример.
Посмотрим, как же ОС помогает нам управлять энергосбережением.

Для начала выберем стратегию энергосбережения и реализуем логику в нити krn_uthread_idle


void krn_uthread_idle (void)
{
        int16_t sys_timeout;
        sys_timeout = krn_timer_cnt + KRN_FREQ * 10;
        while(1){
                if(sys_timeout >  krn_timer_cnt) {
                        _BIS_SR(LPM0_bits + GIE);
                } else {
                        P1OUT |= BIT6;
                        DCOCTL  = CALDCO_1MHZ;
                        _BIS_SR(LPM4_bits + GIE);
                        DCOCTL  = CALDCO_16MHZ;
                        sys_timeout = krn_timer_cnt + KRN_FREQ * 10;
                }
        }
}


Здесь задумано так: нити отдают управление планировщику, если им нечего делать или они ожидают ввод-вывод, планировщик отдаёт управление в krn_uthread_idle.
krn_uthread_idle переключает MCU в LPM0, и через 10 сек. переключает MCU в LPM4.

Перед переходом в LPM4 и по выходу из него выполняется переключение частоты DCO, иначе MCU зависает. Это описано в какой-то эррате.
Выход из LPM4 осуществляется при нажатии юзерской кнопки на ланчпаде. Вот код обработчика (обратите внимание — _BIC_SR_IRQ а не _BIC_SR):


#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
        if((P1IFG & BIT3) == BIT3) {
                P1OUT &= ~BIT6;
                _BIC_SR_IRQ(LPM4_bits);
        } else {
        }
        P1IFG = 0x00;   // clear interrupt flags
}


Ну, и наконец, чтоб бесконечный while в krn_uthread_idle работал, надо где-то выводить MCU из LPM0, иначе наша нить управлением энергосбережением будет всё время спать после входа в LPM0 и никогда не увидит, что наступило время войти в LPM4 :) Сделаем это, конечно же, в обработчике таймера планировщика (в ltkrn.c, на самом деле, добавлена только одна строчка):


int timer_cnt;

#define TA0_DELTA1 (16000000 / 8 / 5  / KRN_FREQ)
#define TA0_DELTA2 (16000000 / 8 / 10 / KRN_FREQ)
#define TA0_DELTA0 (16000000 / 8 / 20 / KRN_FREQ)

#pragma vector = TIMER0_A1_VECTOR
__interrupt void TA0_tick() {
	int int_source = TAIV;
	if(int_source == 2) {
		timer_cnt ++;
		krn_timer_warp --;
		if (krn_timer_warp == 0) {
			_BIC_SR_IRQ(LPM0_bits);
			krn_timer_cnt ++;
			krn_timer_current ++;
			krn_timer_warp = 5;
			krn_dispatch();
		}
#ifdef HD44780_TX
		if(hd44780_flags & HD44780_TX) hd44780_tx_proc();
#endif
		TACCR1 += TA0_DELTA1;
	} else if(int_source == 4) {
		TACCR2 += TA0_DELTA2;
	}
}

#pragma vector = TIMER0_A0_VECTOR
__interrupt void TA0_highspeed() {
	TACCR0 += TA0_DELTA0;
}


Вот так, относительно сложная стратегия энергосбережения реализуется в одном, специально отведённом для этого месте — в idle thread.
Это небольшой пример, можно сделать и что-то более актуальное в реальном мире. Побольше разных флагов, можно ещё менять частоту DCO и UART на ходу но суть та же.

В следующий раз, наверное, покажу DS18B20 на прерываниях.
  • +3
  • 22 октября 2013, 11:01
  • scaldov

Комментарии (20)

RSS свернуть / развернуть
DS18B20 на прерываниях
любопытно. один/несколько? шлейфом/по одному на пин?
0
попробую для нескольких. ну и шлейфом, конечно.
0
Как раз самое интересное в том, как нити определяют, что им нечего делать, как отдают управление планировщику и как он разбирается в этом, и в том, а не произошли ли асинхронные события побудки в момент вызова krn_uthread_idle. А то если мы только таймер назначаем для побудки, то ещё ничего, а если кнопка, мы не можем предугадать, когда это произойдёт. Т.е. мы можем разрешить прерывание по кнопке и соответствующая нить должна вызваться (например, через вызов планировщика прям в обработчике) и ход выполнения функции krn_uthread_idle, вероятно, каким-то образом от этого должен измениться.
0
если нить стоит в ожидании ввода-вывода — то ей нечего делать. планировщик, когда все нити в ожидании, запускает нить krn_uthread_idle. В данном примере разрешено прерывание по кнопке.

Если хотите другие источники, можно сделать другие обработчики прерываний и флаги. В krn_uthread_idle будете проверять флаги, в зависимости от которых вводить MCU в один из режимов LPM.
0
Меня беспокоит именно как krn_uthread_idle узнает, не произошли ли асинхронные события побудки в момент вызова krn_uthread_idle? А как krn_uthread_idle разберётся, что делать, если одной нити достаточно LPM3, а другой нужно не менее LPM0, а третья ждёт с чем-то типа yield()?
0
как krn_uthread_idle узнает, не произошли ли асинхронные события побудки в момент вызова krn_uthread_idle
— вот тут не совсем понятно. если события произошли (неважно когда), их обработали обработчики IRQ и выставили какие-то флаги. Если обработчик не разбудит другую нить, а все остальные спят, то начнёт работать krn_uthread_idle. Если она видит флаг, говорящий, что нельзя делать ничего кроме LPM0, то она не будет уходить в глубокий сон.

Поконкретнее ситуацию опишите.
0
Возьмём одну нить. Допустим, что она решила ждать прихода символа по УАРТ, ей достаточно LPM3. Наконец-то диспетчер отдал управление krn_uthread_idle, которая должна усыпить в LPM3. Но на скорости 400кБод прилетает символ и за один такт до выполнения _BIS_SR(LPM0_bits + GIE) поднимается нить и решает, что нужно запустить встроенный АЦП, а для этого нужно LPM0. На следующем такте выполнения krn_uthread_idle проц благополучно уйдёт в LPM3 и при отсутствии иных источников побудки, кроме АЦП, не проснётся вовсе, а при наличии — проснётся не вовремя и прочая. Можно запрещать прерывания перед выполнением команды усыпления, но тогда нужно анализировать флаги возникшие на момент запрещения прерываний. Но это требует, ИМХО, других подходов
0
описка. имелось в виду _BIS_SR(LPM3_bits + GIE)
0
так запускайте АЦП по расписанию, например. В нужный временной интервал ставите флаг, после этого снимаете его. внутри этого интервала krn_uthread_idle не будет уходить в LPM3.
0
Не нужно по расписанию — нужно по приходу байта. Что делать?
0
АЦП по приходу байта запускать??
пришёл байт — в обработчике прерывания uart выключаете LPM3 и всё. Дальше отрабатывает АЦП (не забываем про флаги), и всё.
0
Ещё нет LPM3 — krn_uthread_idle вот-вот выполнит _BIS_SR(LPM3_bits + GIE)…
0
о.к. предложите свой вариант решения.

я предложил АЦП по расписанию.
0
Лучше подскажите, запрещены ли прерывания krn_uthread_idle. Если запрещаются, то можно через проверку соответствия софтовых флагов после запрета сделать (работает, только в других условиях). Но такое за собой тянет немало и не уверен, что при вытеснении реализуемо.
0
запрещены ли прерывания krn_uthread_idle
— не совсем понял.
0
Извините, пожалуйста, описка. запрещены ли прерывания перед krn_uthread_idle
0
при переключении на нить статус прерываний возврящается на тот, что был у нити, на которую происходит переключение.
0
Привык, что если используется усыпление с побудкой, то прерывания перед усыплением разрешены. Но я спрашивал о другом. В реальной программе при усыплении мне часто в основном нужен уровень энергопотребления LPM3, но иногда LPM0. ЗадачиНити ничего друг о друге не знают. А ещё приходят запросы прерываний не синхронизированные с таймером. Одна задача вдруг захотела LPM0, остальные просят LPM3. Кто-то этим всем хозяйством должен рулить. Как это сделать в кооперативке я знаю, но как при вытеснении — не знаю и хотел увидеть. Проблему вижу в том, что, КМК, можно потерять либо событие порождаемое запросом прерывания, либо уйти спать не в том режиме.
0
вы действительно описали проблему, но я предлагаю её решать либо запуском по расписанию, либо флагами.
т.е. нити выставляют скажем допустимый LPM в своих переменных (или в каком-нибудь массиве), а krn_uthread_idle входит в минимальный из запрошенных. Пробуждение по расписанию возможно на генераторе LFXT1 в LPM3.
0
я думаю, надо всё решать исходя из задачи, из точных условий. всё-же энергосбережение — довольно специфичная область.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.