000. Протопотоки. Делаем часы. Динамическая индикация.

Здравствуйте. Моя очередь мигать светодиодами. Только теперь светодиоды будут зажигаться не благодаря всяким _delay_us и иже с ним, а с помощью протопотоков.
Многие скажут, что протопотоки/макросы и прочие куски кода только утежеляют НЕХ и вообще, это зачастую заставляет перелезть на более дорогостоящий контроллер, но не стоит забывать, что сейчас мега 8 по цене мало отличается от тини 2313, поэтому примем во внимание тот факт, что зачастую время разработчика важнее пары килобайт памяти. Протопотоки позволяют воплощать идею быстрее и проще, в ущерб быстродействию конечно, но различие в пару десятков тактов будут видны лишь в конкретных случаях, допустим при генерации контроллером видеосигнала или быстрой обработке каких-либо данных.
Для примера я решил ваять часики по типу китайских, но на светодиодных индикаторах.
Начнём с того, что я взял с собой на работу, где сейчас и нахожусь, мою отладку, но не кинул к ней широкий джампер, соединяющий пины контроллера с программатором, поэтому пока без фото и видео, но я позже запилю.

Платформа- мега32, тактуемая от внутреннего осцилятора на 8 МГц, куски кода- часто чужие, где буду знать- поставлю копирайты (весь код был выложен в свободный доступ).
Язык- чистый С (avr-gcc + AVR Studio 4).
Ссылки по теме
www.sics.se/~adam/pt/ — автор рассматриваемой библиотеки. Там же можно и скачать саму библиотеку.
bsvi.ru/protopotoki-protothreads/ — собственно, в этой статье рассматриваются протопотоки. Что, где, как и в какое место. Перед прочтением моей статьи настоятельно рекомендую посетить эту страничку.
snippets.crisp-studio.com/view/136/primer-raboty-s-protothreads-na-gcc-avr — страничка с аналогичным моему примером. Точнее это мой пример аналогичен рассмотренному :).
И кстати, я тоже использую для понимания системный таймер (потом перепилю, чтобы без переполнений) и возможно поюзаю эти забавные макросы, выдранные из scmRTOS, и впихну вот этот участок кода by psv, поэтому включу их в заголовочный файл, допустим так.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "pt/pt.h"

/*----------------------------------------------------------------------------------------------
 *	pin_macros.h  (AVR/avr-gcc)
 *----------------------------------------------------------------------------------------------
 *
 * Author:  Oleksandr Redchuk aka ReAl (real@real.kiev.ua)
 *
 * Description: port bits access macros for AVR uC family (avr-gcc compiler)
 *
 * Based on macros by Ascold Volkov, Andy Mozzevilov, Aleksey Musin
 *----------------------------------------------------------------------------------------------
 */

/*
examples

#define Pin1 A,2,H
#define Pin2 B,3,L
			 ^ ^ ^
		  port | |
			 bit \active level

void main(void)
{
	DRIVER(Pin1,OUT);	//as output
	DRIVER(Pin2,IN);		//as input
	DRIVER(Pin2,PULLUP);	// with pullup
	ON(Pin1);
	if (ACTIVE(Pin2)) OFF(Pin1);
	if (LATCH(Pin1)) { //if active level presents on Pin1 for drive something
		//do something
	}
}
*/

#ifndef PIN_MACROS_H
#define PIN_MACROS_H

// assume chip has PORT_TOGGLE_BY_PIN_WRITE feature by default (all new AVR chips)
// check only old-style chips

#if	defined(__AVR_AT94K__) \
	|| defined(__AVR_AT43USB320__) || defined(__AVR_AT43USB355__) \
	|| defined(__AVR_AT76C711__) || defined(__AVR_AT86RF401__) \
	|| defined(__AVR_AT90C8534__) || defined(__AVR_AT90S4434__) \
	|| defined(__AVR_AT90S1200__) || defined(__AVR_AT90S2343__) \
	|| defined(__AVR_AT90S2333__) || defined(__AVR_AT90S2323__) \
	|| defined(__AVR_AT90S2313__) || defined(__AVR_AT90S4433__) \
	|| defined(__AVR_AT90S4414__) \
	|| defined(__AVR_AT90S8515__) || defined(__AVR_AT90S8535__) \
	|| defined(__AVR_ATmega8515__) || defined(__AVR_ATmega8535__) \
	|| defined(__AVR_ATmega103__) \
	|| defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__) \
	|| defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) \
	|| defined(__AVR_ATmega32__) \
	|| defined(__AVR_ATtiny11__) || defined(__AVR_ATtiny12__) \
	|| defined(__AVR_ATtiny15__) || defined(__AVR_ATtiny28__) \
	|| defined(__AVR_ATtiny22__) || defined(__AVR_ATtiny26__) \
	|| defined(__AVR_ATmega161__) || defined(__AVR_ATmega162__) \
	|| defined(__AVR_ATmega163__) || defined(__AVR_ATmega323__) \
	|| defined(__AVR_ATmega8HVA__) || defined(__AVR_ATmega16HVA__)
#  define PORT_TOGGLE_BY_PIN_WRITE 0
#else
#  define PORT_TOGGLE_BY_PIN_WRITE 1
#endif

#define PM_BITNUM(port,bit,val) (bit)
#define BITNUM(x) PM_BITNUM(x)
#define BITMASK(x) (1<<PM_BITNUM(x))

#define PM_SETOUT(port,bit) (DDR##port |= (1<<(bit)))
#define PM_SETIN(port,bit) (DDR##port &= ~(1<<(bit)))
#define PM_SETPULLUP(port,bit) (PORT##port |= (1<<(bit)))
#define PM_SETHIZ(port,bit) (PORT##port &= ~(1<<(bit)))
#define PM_DRIVER(port,bit,val,mode) PM_SET##mode(port,bit)
/* dmode = OUT or IN, PULLUP or HIZ */
#define DRIVER(x,mode) PM_DRIVER(x,mode)

#define PM_SETL(port,bit,dummy) (PORT##port &= ~(1<<(bit)))
#define PM_SETH(port,bit,dummy) (PORT##port |= (1<<(bit)))
#define PM_SET(port,bit,val) PM_SET##val(port,bit,dummy)
#define ON(x) PM_SET(x)
#define SET(x) PM_SETH(x)
#define CLR(x) PM_SETL(x)

#define PM_CLRL(port,bit,dummy) PM_SETH(port,bit,dummy)
#define PM_CLRH(port,bit,dummy) PM_SETL(port,bit,dummy)
#define PM_CLR(port,bit,val) PM_CLR##val(port,bit,dummy)
#define OFF(x) PM_CLR(x)

#define PM_PINH(port,bit,dummy) (PIN##port & (1<<(bit)))
#define PM_PINL(port,bit,dummy) !PM_PINH(port,bit,dummy)
#define PM_PIN(port,bit,val) PM_PIN##val(port,bit,dummy)
#define ACTIVE(x) PM_PIN(x)
#define PIN_H(x) PM_PINH(x)
#define PIN_L(x) PM_PINL(x)

#define PM_LATCHH(port,bit,dummy) (PORT##port & (1<<(bit)))
#define PM_LATCHL(port,bit,dummy) !PM_LATCHH(port,bit,dummy)
#define PM_LATCH(port,bit,val) PM_LATCH##val(port,bit,dummy)
#define LATCH(x) PM_LATCH(x)
#define LATCH_H(x) PM_LATCHH(x)
#define LATCH_L(x) PM_LATCHL(x)


#if PORT_TOGGLE_BY_PIN_WRITE
#  define PM_CPL(port,bit,val)      (PIN##port |= (1 << (bit)))
#else
#  define PM_CPL(port,bit,val)      (PORT##port ^= (1 << (bit)))
#endif

#define CPL(x) PM_CPL(x)
#define TOGGLE(x) PM_CPL(x)

#endif	// PIN_MACROS_H





/*
Выдержка из http://easyelectronics.ru/avr-uchebnyj-kurs-ispolzovanie-shim.html

Например, надо нам прерывание каждую миллисекунду. И чтобы вот точно. Как
это реализовать проще? Через Режим СТС! Пусть у нас частота 8Мгц.

Прескалер будет равен 64, таким образом, частота тиков таймера составит 125000 Гц.
А нам надо прерывание с частотой 1000Гц. Поэтому настраиваем прерывание по совпадению с числом 125.

Дотикал до 125 — дал прерывание, обнулился. Дотикал до 125 — дал прерывание, обнулился. И так бесконечно, пока не выключим.

Вот вам и точная тикалка.

Нет, конечно, можно и вручную. Через переполнение, т.е. дотикал до переполнения,
загрузил в обработчике прерывания заново нужные значение TCNTх=255-125,
сделал нужные полезные дела и снова тикать до переполнения. Но ведь через СТС красивей! :)

У TIMER0 CTC нет, поэтому делаем вручную:
8000000 / 64 = 125000 / 125 = 1000 Hz = 1 ms
*/



//#include "st.h"

#define ST_CTC_HANDMADE 255-125

volatile static uint32_t st_timer0_millis;

ISR(TIMER0_OVF_vect)
{
	st_timer0_millis++;
	TCNT0 = ST_CTC_HANDMADE;
}

uint32_t st_millis(void)
{
	uint32_t m;

	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
	{
		m = st_timer0_millis;
	}

	return m;
}

void inline St_Init(void)
{
	// Set prescaler to 64
	TCCR0 |= (_BV(CS01) | _BV(CS00));

	// Enable interrupt
	TIMSK |= _BV(TOIE0);

	// Set default value
	TCNT0 = ST_CTC_HANDMADE;
}

#ifndef sega
#define sega 0
#define segb 1
#define segc 2
#define segd 3
#define sege 4
#define segf 5
#define segg 6
#define segdot 7
#endif

#define SEG_NULL 10
#define SEG_DOT 11

const unsigned char segment[12] = {
(1<<sega)|(1<<segb)|(1<<segc)|(1<<segd)|(1<<sege)|(1<<segf),
(1<<segb)|(1<<segc),
(1<<sega)|(1<<segb)|(1<<segd)|(1<<sege)|(1<<segg),
(1<<sega)|(1<<segb)|(1<<segc)|(1<<segd)|(1<<segg),
(1<<segb)|(1<<segc)|(1<<segf)|(1<<segg),
(1<<sega)|(1<<segf)|(1<<segg)|(1<<segc)|(1<<segd),
(1<<sega)|(1<<segf)|(1<<segg)|(1<<segd)|(1<<sege)|(1<<segc),
(1<<sega)|(1<<segb)|(1<<segc),
(1<<sega)|(1<<segb)|(1<<segc)|(1<<segd)|(1<<sege)|(1<<segf)|(1<<segg),
(1<<sega)|(1<<segb)|(1<<segc)|(1<<segd)|(1<<segf)|(1<<segg),
0, // 10 - пустое место
(1<<segdot), // 11 - точка
};



Тем самым мы убили таймер 0 на ненужное занятие и подключили протопотоки. Кстати, заметьте, что протопотоки находятся в папке pt.

Сваял я схему. Вот такую.


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

Итак, с чего начинаются часы? Нет, не с таймера. С индикации. Её обработку и поручим протопотоку. Назовём его, допустим, SegDyn. Я считаю, все поймут. Для инициализации протопотоков мне не жалко выделить отдельную функцию, так проще следить за структурой программы. Он будет называться InitProtothreads(). А собственно, сама структура протопотока, чтобы было понятно, назовётся SegDyn_pt.
Теперь подключаем наш заголовочный файл, объявляем наш первый протопоток и делаем int main (void)
// Наш заголовочный файл
#include "example.h"

// Почти без изменений копирую код с сайта BSVi
static struct pt SegDyn_pt;





//================================== Инициализация протопотоков
void InitProtothreads(){

PT_INIT(&SegDyn_pt); // динамическая индикация

}


//================================== Динамическая индикация
static PT_THREAD(SegDyn(struct pt *pt))
{
static uint32_t last_timer; // Заготовка для динамической индикации

PT_BEGIN(pt);

PT_END(pt);
}

//==================================  Тело программы
int main (void)
{ // Инициализация протопотока
  InitProtothreads();
  // Инициализация системного таймера
  St_Init();
  // Инициализация портов
  DDRA=  		0b11111111;
  DDRB=  		0b00001111;
  PORTA= PORTB= 0b00000000;

	// Любимый всеми нами бесконечный цикл
	while (1)
	{
	SegDyn(&SegDyn_pt); //Динамическая индикация
	}

}


Теперь протопоток будет исполняться в цикле. По сути, сейчас надо подумать об индикации. Объявим массив со слепком экрана- с четырьмя цифрами. Допустим так.

volatile unsigned char ToLedSegs[4]	= {1,2,3,4};


Это поможет нам во всяких менюшках, будь то установка часов или будильника.
Для раскидывания по сегментам цифры у нас есть такой массив как segment[], вот его и используем.

// Наш заголовочный файл
#include "example.h"

// Почти без изменений копирую код с сайта BSVi
static struct pt SegDyn_pt;

volatile unsigned char ToLedSegs[4]	= {1,2,3,4};




//================================== Инициализация протопотоков
void InitProtothreads(){

PT_INIT(&SegDyn_pt); // динамическая индикация

}


//================================== Динамическая индикация
static PT_THREAD(SegDyn(struct pt *pt))
{
static uint32_t last_timer; // Заготовка для динамической индикации
static unsigned char cathode = 0;

PT_BEGIN(pt);


while (cathode<=3)
{
PT_WAIT_UNTIL(pt, (st_millis() - last_timer) >= 3); // подождать 3 мс
last_timer = st_millis();
PORTB = ~0; // Катоды на 0
PORTA = segment[ToLedSegs[cathode]]; // Вывести число на сегменты
PORTB = ~(1 << cathode); // Выставить нужный катод
cathode++;
}
cathode=0;
PT_END(pt);
}

//==================================  Тело программы
int main (void)
{ // Инициализация протопотока
  InitProtothreads();
  // Разрешение прерываний
  sei(); 
  // Инициализация системного таймера
  St_Init();
  // Инициализация портов
  DDRA=  		0b11111111;
  DDRB=  		0b00001111;
  PORTA= PORTB= 0b00000000;

	// Любимый всеми нами бесконечный цикл
	while (1)
	{
	SegDyn(&SegDyn_pt); //Динамическая индикация
	}

}


Как видите, в протопотоке у нас динамическая индикация, и протопоток использован только для того, чтобы посветить сегментами 3 мсек. Я не стал делать гашение оставшихся пинов для простоты. Теперь мы можем наблюдать это.


Как видно, наша динамическая индикация работает. Время за часами. Асинхронный таймер и пара переменных. Можно всё считать в одну переменную, но в массив будет проще.

Итак, заведём массив с текущим временем
volatile int CurrentTime[3]	= {8,20,57};
enum {HOUR=0,MINS,SEC};

Придумаем то, как нам обрабатывать это время. Сделаем новую функцию, точнее протопоток. Заодно придумаем, как пихать секунды в наш массив, как разбивать его по цифрам и напишем обработчик прерывания.
//================================== Инкрементация секунды
void SeqInc()
{
CurrentTime[SEC]++;

if (CurrentTime[SEC]>=60) { CurrentTime[SEC] = 0; CurrentTime[MINS]++;}
if (CurrentTime[MINS]>=60) { CurrentTime[MINS] = 0; CurrentTime[HOUR]++;}
if (CurrentTime[HOUR]>=24) { CurrentTime[HOUR] = 0;}

}

//================================== Переполнение раз в секунду

ISR(TIMER2_OVF_vect)
{
TCNT1=0;
SeqInc();
}

//================================== Индикация --- ЧАСЫ ---
static PT_THREAD(DrawClock(struct pt *pt))
{
PT_BEGIN(pt);
// Получение слепка экрана.
for (int clear = 0; clear<=3; clear++) {ToLedSegs[clear]=0;}
ToLedSegs[3] = CurrentTime[MINS]; while (ToLedSegs[3]>=10) {ToLedSegs[3]-=10;ToLedSegs[2]++;} // на десятки и единицы
ToLedSegs[1] = CurrentTime[HOUR]; while (ToLedSegs[1]>=10) {ToLedSegs[1]-=10;ToLedSegs[0]++;} // на десятки и единицы
// Гашение первого нуля в часах.
if (ToLedSegs[0] == 0) {ToLedSegs[0] = SEG_NULL;}

PT_END(pt);
}


Пропишем инициализацию нового протопотока
PT_INIT(&DrawClock_pt); // Часы
, в main в бесконечном цикле затолкнём
DrawClock(&DrawClock_pt); //Часы
, да и к объявлениям переменных допишем через запятую DrawClock_pt.
И теперь можно завести асинхронный таймер.
Выделю отдельную функцию.

void CLTimer_Init(void) ///Code by DI HALT
{						// Если все чисто, то можно запускать прерывания

TIMSK &=~(1<<OCIE2 | 1<< TOIE2);	// Запрещаем прерывания таймера 2
ASSR  = 1<<AS2;						// Включаем асинхронный режим
TCNT2 = 0;
TCCR2 = 5<<CS20; 					// Предделитель на 128 на 32768 даст 256 тиков в секунду
									// Что даст 1 прерывание по переполнению в секунду.
TIMSK |=(1<< TOIE2);
return;	
}


И пропишу её после инициализации системного таймера.
В итоге код принял вид.
// Наш заголовочный файл
#include "example.h"

// Почти без изменений копирую код с сайта BSVi
static struct pt SegDyn_pt, DrawClock_pt;

volatile unsigned char ToLedSegs[4]	= {1,2,3,4};
volatile int CurrentTime[3]	= {8,20,59};
enum {HOUR=0,MINS,SEC};


//================================== Инициализация протопотоков
void InitProtothreads(){

PT_INIT(&SegDyn_pt); // динамическая индикация
PT_INIT(&DrawClock_pt); // Часы
}


//================================== Динамическая индикация
static PT_THREAD(SegDyn(struct pt *pt))
{
static uint32_t last_timer; // Заготовка для динамической индикации
static unsigned char cathode = 0;

PT_BEGIN(pt);


while (cathode<=3)
{
PT_WAIT_UNTIL(pt, (st_millis() - last_timer) >= 3); // подождать 3 мс
last_timer = st_millis();
PORTB = ~0; // Катоды на 0
PORTA = segment[ToLedSegs[cathode]]; // Вывести число на сегменты
PORTB = ~(1 << cathode); // Выставить нужный катод
cathode++;
}
cathode=0;
PT_END(pt);
}

//================================== Инкрементация секунды
void SeqInc()
{
CurrentTime[SEC]++;

if (CurrentTime[SEC]>=60) { CurrentTime[SEC] = 0; CurrentTime[MINS]++;}
if (CurrentTime[MINS]>=60) { CurrentTime[MINS] = 0; CurrentTime[HOUR]++;}
if (CurrentTime[HOUR]>=24) { CurrentTime[HOUR] = 0;}

}

void CLTimer_Init(void) ///Code by DI HALT
{						// Если все чисто, то можно запускать прерывания

TIMSK &=~(1<<OCIE2 | 1<< TOIE2);	// Запрещаем прерывания таймера 2
ASSR  = 1<<AS2;						// Включаем асинхронный режим
TCNT2 = 0;
TCCR2 = 5<<CS20; 					// Предделитель на 128 на 32768 даст 256 тиков в секунду
									// Что даст 1 прерывание по переполнению в секунду.
TIMSK |=(1<< TOIE2);
return;	
}

//================================== Переполнение раз в секунду

ISR(TIMER2_OVF_vect)
{
TCNT1=0;
SeqInc();
}

//================================== Индикация --- ЧАСЫ ---
static PT_THREAD(DrawClock(struct pt *pt))
{
PT_BEGIN(pt);
// Получение слепка экрана.
for (int clear = 0; clear<=3; clear++) {ToLedSegs[clear]=0;}
ToLedSegs[3] = CurrentTime[MINS]; while (ToLedSegs[3]>=10) {ToLedSegs[3]-=10;ToLedSegs[2]++;} // на десятки и единицы
ToLedSegs[1] = CurrentTime[HOUR]; while (ToLedSegs[1]>=10) {ToLedSegs[1]-=10;ToLedSegs[0]++;} // на десятки и единицы
// Гашение первого нуля в часах.
if (ToLedSegs[0] == 0) {ToLedSegs[0] = SEG_NULL;}

PT_END(pt);
}

//==================================  Тело программы
int main (void)
{ // Инициализация протопотока
  InitProtothreads();
  // Разрешение прерываний
  sei(); 
  // Инициализация системного таймера
  St_Init();
  // Инициализация асинхронного таймера
  CLTimer_Init();
  // Инициализация портов
  DDRA=  		0b11111111;
  DDRB=  		0b00001111;
  PORTA= PORTB= 0b00000000;

	// Любимый всеми нами бесконечный цикл
	while (1)
	{
	SegDyn(&SegDyn_pt); //Динамическая индикация
	DrawClock(&DrawClock_pt); //Часы
	}

}

Теперь часы идут. Осталось сделать мигающую точку. Воткну в обработку динамической индикации. Зажигать буду по переполнению секундного таймера, гасить- в протопотоке =). Гулять так гулять!
Объявим переменную для точки.
short FLAG_Secdot = 0;

Итак, сперва впихнём саму точку в динамическую индикацию. Прямо в цикл.
if (cathode == 1) { PORTA |= (FLAG_Secdot<<segdot); }; // Точка

Теперь в прерывание по переполнению впихнём это.
FLAG_Secdot = 1;
А затем придумаем и сам протопоток, по аналогии его везде пропишем.
//================================== Индикация --- ГАШЕНИЕ ТОЧКИ ---
static PT_THREAD(DotsFlagsClear(struct pt *pt))
{

static uint32_t last_timer;
PT_BEGIN(pt);

PT_WAIT_UNTIL(pt, (FLAG_Secdot == 1) );
last_timer = st_millis();
PT_WAIT_UNTIL(pt, (st_millis() - last_timer) >= 300);
FLAG_Secdot = 0;
PT_END(pt);
}

Вот тут протопоток уже больше пригодился. Получается, что мы ждём, пока точка зажжётся и после этого выставляем временную выдержку для её гашения. В этом примере я почти не использовал возможности протопотоков и юзал всего один макрос.
Теперь код работает как надо. Часы идут, точка мигает. В следующей статье — про меню и обработку кнопок. И, под конец, полный исходник.

// Наш заголовочный файл
#include "example.h"

// Почти без изменений копирую код с сайта BSVi
static struct pt SegDyn_pt, DrawClock_pt, DotsFlagsClear_pt;

volatile unsigned char ToLedSegs[4]	= {1,2,3,4};
volatile int CurrentTime[3]	= {8,20,59};
enum {HOUR=0,MINS,SEC};
short FLAG_Secdot = 0;

//================================== Инициализация протопотоков
void InitProtothreads(){

PT_INIT(&SegDyn_pt); // Динамическая индикация
PT_INIT(&DrawClock_pt); // Часы
PT_INIT(&DotsFlagsClear_pt); // Гашение точки
}


//================================== Динамическая индикация
static PT_THREAD(SegDyn(struct pt *pt))
{
static uint32_t last_timer; // Заготовка для динамической индикации
static unsigned char cathode = 0;

PT_BEGIN(pt);


while (cathode<=3)
{
PT_WAIT_UNTIL(pt, (st_millis() - last_timer) >= 3); // подождать 3 мс
last_timer = st_millis();
PORTB = ~0; // Катоды на 0
PORTA = segment[ToLedSegs[cathode]]; // Вывести число на сегменты
PORTB = ~(1 << cathode); // Выставить нужный катод
if (cathode == 1) { PORTA |= (FLAG_Secdot<<segdot); }; // Точка
cathode++;
}
cathode=0;
PT_END(pt);
}

//================================== Инкрементация секунды
void SeqInc()
{
CurrentTime[SEC]++;

if (CurrentTime[SEC]>=60) { CurrentTime[SEC] = 0; CurrentTime[MINS]++;}
if (CurrentTime[MINS]>=60) { CurrentTime[MINS] = 0; CurrentTime[HOUR]++;}
if (CurrentTime[HOUR]>=24) { CurrentTime[HOUR] = 0;}

FLAG_Secdot = 1;
}

void CLTimer_Init(void) ///Code by DI HALT
{						// Если все чисто, то можно запускать прерывания

TIMSK &=~(1<<OCIE2 | 1<< TOIE2);	// Запрещаем прерывания таймера 2
ASSR  = 1<<AS2;						// Включаем асинхронный режим
TCNT2 = 0;
TCCR2 = 5<<CS20; 					// Предделитель на 128 на 32768 даст 256 тиков в секунду
									// Что даст 1 прерывание по переполнению в секунду.
TIMSK |=(1<< TOIE2);
return;	
}

//================================== Переполнение раз в секунду

ISR(TIMER2_OVF_vect)
{
TCNT1=0;
SeqInc();
}

//================================== Индикация --- ГАШЕНИЕ ТОЧКИ ---
static PT_THREAD(DotsFlagsClear(struct pt *pt))
{

static uint32_t last_timer;
PT_BEGIN(pt);

PT_WAIT_UNTIL(pt, (FLAG_Secdot == 1) );
last_timer = st_millis();
PT_WAIT_UNTIL(pt, (st_millis() - last_timer) >= 300);
FLAG_Secdot = 0;
PT_END(pt);
}

//================================== Индикация --- ЧАСЫ ---
static PT_THREAD(DrawClock(struct pt *pt))
{
PT_BEGIN(pt);
// Получение слепка экрана.
for (int clear = 0; clear<=3; clear++) {ToLedSegs[clear]=0;}
ToLedSegs[3] = CurrentTime[MINS]; while (ToLedSegs[3]>=10) {ToLedSegs[3]-=10;ToLedSegs[2]++;} // на десятки и единицы
ToLedSegs[1] = CurrentTime[HOUR]; while (ToLedSegs[1]>=10) {ToLedSegs[1]-=10;ToLedSegs[0]++;} // на десятки и единицы
// Гашение первого нуля в часах.
if (ToLedSegs[0] == 0) {ToLedSegs[0] = SEG_NULL;}
PT_END(pt);
}

//==================================  Тело программы
int main (void)
{ // Инициализация протопотока
  InitProtothreads();
  // Разрешение прерываний
  sei(); 
  // Инициализация системного таймера
  St_Init();
  // Инициализация асинхронного таймера
  CLTimer_Init();
  // Инициализация портов
  DDRA=  		0b11111111;
  DDRB=  		0b00001111;
  PORTA= PORTB= 0b00000000;

	// Любимый всеми нами бесконечный цикл
	while (1)
	{
	SegDyn(&SegDyn_pt); //Динамическая индикация
	DrawClock(&DrawClock_pt); //Часы
	DotsFlagsClear(&DotsFlagsClear_pt); // Гашение точки
	}

}
  • 0
  • 15 августа 2011, 15:39
  • sania_3

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

RSS свернуть / развернуть
Лучше бы рассказали про сами протопотоки… С объяснением: что это, зачем это и как работает… Думаю было бы полезнее))
Кстати говорят, что это мощная штука
0
Да, штука достойная. Допустим, этот код занимает 1,2 Кб. В тиньку 2313 влезет. Это при том, что я себе ни в чём не отказывал. :) Но всё достаточно разжёвано (на том же BCVi), а вот примеров конкретного применения мало. Я же просто объясняю рождение кода. Попутно пишу себе часы. Другие макросы уже буду жевать. :)
0
может bsvi?) А так да, но там как то не оч расписано… По крайне мере мне так показалось…
0
К своему стыду, опечатался. Но про протопотоки там очень хорошо расписаны начальные понятия. А вот где можно применить тот или иной макрос- уже вопрос. Многие из них выглядят неюзабельными. Вот я и буду их юзать.
0
Система наркоманская донельзя, но мне она нравится! Жаль что не видел ничего подобного в универе, нашему преподу я бы мозги этой херью на куски разнес. Он и так тупил неслабо, а тут бы вообще взрыв мозга был.
0
Прикольно, что код из моих статей (со snippets.crisp-studio.com) заработал не только у меня. :)
0
#define ST_CTC_HANDMADE 255-125

В этих кыржиках совсем не разбираюсь, но если сделать
...256-125
, формирование интервала 1'000мкс будет гораааздо точнее. Извините.
0
  • avatar
  • akl
  • 16 августа 2011, 19:52
Ну тогда почему бы не сделать 255-124, по сути это даст тот же интервал в 256-125=131. И чем же это повысит точность? Tогда уж давайте сделаем (1024/(log2(16)) — (600/0b00000101)). В принципе точность нам тут не нужна. Таймер только для протопотоки, а протопотоки только обслуживают часы и асинхронный таймер.
0
Спокойнее, тезка. Вы не первый и, наверняка, не последний путающий максимальное значение 8-разрядного счетчика с числом его состояний. Хотя, если устраивают ходики, безнадежно отстающие на 1,44 секунды за 3 минуты, ничего не имею против.
0
  • avatar
  • akl
  • 17 августа 2011, 07:18
>>>безнадежно отстающие на 1,44 секунды за 3 минуты, ничего не имею против.
Часики, пардон, от другого таймера работают.
0
Тут это все равно, на самом деле. Т.к. когда поток захочет тогда и отпустит к диспетчеру, так что таймеру быть предельно точным не обязательно быть. Он не сможет при любом раскладе.
0
я просто хотел обратить внимание sania_3 (да и Ваше тоже, раз уж он Вас цитирует) на распространенную ошибку. В качестве иллюстрации разместил вложение в своем сообщении на форуме forum.easyelectronics.ru/viewtopic.php?p=17892#p17892, где на сканах показаны результаты работы студии для двух режимов «255-125» и «256-125»
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.