пʼятниця, 16 січня 2015 р.

Апаратний ШІМ мікроконтролера Atmega8 + PWM+LCD







#include <mega8.h>
#include <delay.h>
#include <stdio.h>

#asm
   .equ __lcd_port=0x12 ;PORTD
#endasm
#include <lcd.h>

#define ADC_VREF_TYPE 0x40// Опорна напруга АЦП з ножки AVCC
//TCCR1B
#define CS0 0
#define CS1 1
#define CS2 2
// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
// global variables
unsigned int adc_code,old_code=1, pwm;
unsigned char string[10];//масив знаків для виводу на дисплей
void main(void)
{
// ADC initialization
// ADC Clock frequency: 250,000 kHz
// ADC Voltage Reference: AVCC pin
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x85;

DDRB=0xFF;//Порт Б на виход
//Fast PWM 8-bit
//OC1A встановлюється в 1 при свівпаданні (COM1A1=1, COM1A0=1)
//OC1B  скидається в 0 при свівпаданні (COM1B1=1, COM1B0=0)
TCCR1A=(1<<COM1A1)|(1<<COM1A0)|(1<<COM1B1)|(1<<WGM10);
TCCR1B=(1<<CS0)|(1<<WGM12);
// LCD module initialization
lcd_init(16);// Дисплей на 16 символів
lcd_clear();// Очищаємо дисплей
lcd_gotoxy(0,0);// Ставим курсор на 0 знак та 0 стрічку
lcd_putsf(" LCD PWM Control ");//Вивод повідомлення на нульову стрічку (першу)
lcd_gotoxy(0,1);// Ставим курсор на 0 знак та 0 стрічку
lcd_putsf("PWN 0-100%:    %");//Вивод повідомлення на нульову стрічку (першу)
delay_ms(100);
while (1)
      {
     adc_code=read_adc(0)/4;// Зчитуємо результат АЦП
     if(adc_code!=old_code) //Якщо значення з АЦП змінилось
    { 
     old_code=adc_code;
     pwm=(adc_code*100)/255;//Перетворення коду 0-1023 в 0-100% для виведення на lcd 
     sprintf(string, "%d",pwm);//формуємо стрінг із змінної "pwm" 
                                    //типу int в десятковому вигляді
     //заносим значення в регістри порівняння
       OCR1A=adc_code;   
       OCR1B=adc_code;
       lcd_gotoxy(12,1);               
       lcd_puts(string);// Виводимо значення змінної "pwm"
     }                  
     delay_ms(100);
       };
}







Аналого-цифровий перетворювач, вольтметр на базі мікроконтролера ADC Atmega8 CodeVision AVR





#include <mega8.h>
#include <delay.h>
#include <stdio.h>

#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>

#define ADC_VREF_TYPE 0x40// Опорна напруга АЦП з ножки AVCC

// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
// Delay needed for the stabilization of the ADC input voltage
delay_us(10);
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}

// global variables
float adc_code, voltage;
unsigned char string[4];//масив знаків для виводу на дисплей
void main(void)
{
// ADC initialization
// ADC Clock frequency: 250,000 kHz
// ADC Voltage Reference: AVCC pin
ADMUX=ADC_VREF_TYPE & 0xff;
ADCSRA=0x85;
// LCD module initialization
lcd_init(16);// Дисплей на 16 символів
lcd_clear();// Очищаємо дисплей
lcd_gotoxy(0,0);// Ставим курсор на 0 знак та 0 стрічку
lcd_putsf(" LCD voltmeter ");//Вивод повідомлення на нульову стрічку (першу)
lcd_gotoxy(0,1);// Ставим курсор на 0 знак та 0 стрічку
lcd_putsf(" Voltage:     V ");//Вивод повідомлення на нульову стрічку (першу)
delay_ms(100);
while (1)
      {
     adc_code=read_adc(0);// Зчитуємо результат АЦП
     voltage=(adc_code*5)/1024;// Перетворення в напругу
     sprintf(string, "%.2f",voltage);//формуємо стрінг із змінної "voltage" 
                                    //типу float з двома знаками після коми
     lcd_gotoxy(9,1);               
     lcd_puts(string);// Виводимо значення напруги                  
     delay_ms(100);
       };
}






четвер, 15 січня 2015 р.

Робота з рідиннокристалічним дисплеєм Atmega8 + LCD + CodeVision








#include <mega8.h>
#include <delay.h>
#include <stdio.h>
// Інціалізація підключення дисплея
#asm
   .equ __lcd_port=0x18 ; PORTB
#endasm
#include <lcd.h>
unsigned int temp = 32000;//змінна яку будемо виводити
unsigned char string[20];//масив знаків для виводу на дисплей
bit p=0;                //Для зміни рядків

void main(void)
{
 lcd_init(16);//Ініціалізуємо дисплей
 lcd_clear();// Очищаємо дисплей
 lcd_gotoxy(0,0);// Ставим курсор на 0 знак та 0 стрічку
 lcd_putsf("LCD test");//Вивод повідомлення на нульову стрічку (першу)
 lcd_gotoxy(0,1);       //Перехід курсора на першу стрічку
 lcd_putsf("CHISLO...");//Вивод інформаційного повідомлення на першу стрічку(другу)
 lcd_gotoxy(10,1);       // Вивід символів...
 lcd_putchar('A');
 lcd_gotoxy(12,1);
 lcd_putchar(0x41);
 delay_ms(3000);        //Висить заставка
 lcd_clear();          // Очищаємо дисплей
      
while (1)
      {  
    sprintf(string, "temp=%5d\n", temp);//формуємо стрінг із змінної
    lcd_gotoxy(0,p++);                  //%5 - 5 знаків d - в десяткове знакове число
                                         //\n - з переходом на наступну стрічку
    lcd_puts(string);                  
    delay_ms(1000);
     temp++;                             //  Нарощуємо змінну для виведення
      }
}



Матрична клавіатура 3х4 на Atmega8 CodeVision AVR





#define COLUMN 3  // Кількість колонок
#define ROW 4     // Кількість рядків
#include <mega8.h>
#include <delay.h>
unsigned char button;//Номер кнопки (позиційний)
//Значення що подаються для сканування рядків
unsigned char key_tab[ROW] ={0b11111110,
                             0b11111101,
               0b11111011,
        0b11110111};
// Функція опитування клавіатури
unsigned char scan_key(void)
{
unsigned char key_value = 0;
unsigned char i;

for(i = 0;i < ROW;i++)//Виводимо почерзі 0 в рядки
  {
  PORTB = key_tab[i]; 
  delay_us(100);

    switch (PINB & 0xF0)// Зчитуємо значення з порта В
    {                   //та обчислюємо номер кнопки
    case 0b11100000:
    key_value = 1 + i * COLUMN;
    return (key_value);
    case 0b11010000:
    key_value = 2 + i * COLUMN;
    return (key_value);
    case 0b10110000:
    key_value = 3 + i * COLUMN;
    return (key_value);
    break;
    }
  }
}
void main(void)
{
// Масив цифр для індикатора
unsigned char num[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

    DDRB |= (1 << 3)|(1 << 2)|(1 << 1)|(1 << 0); // Порт вихода
    DDRB &= ~(1 << 7)|(1 << 6)|(1 << 5)|(1 << 4); // Порт входа
    PORTB = 0xF0; // Встановлюємо лог. 1 в порт входа
    DDRD = 0xFF; // Виход на індикатор
    PORTD = 0x00;
    delay_us(100);
while(1)
{
for(button=1;button<10;button++)
    {
     if(scan_key()==button) // Виводим значення натисненої кнопки на індикатор
        PORTD = num[button];
    }
if(scan_key()==11)     // 0 на 11 кнопці
PORTD = num[0];
    }
}


середа, 14 січня 2015 р.

Семисегментний дисплей на MAX7219 Atmega8 CodeVision AVR







//****************************************************
#include <mega8.h>
#include <delay.h>
//----------------------------------------------------
// Задамо глобальні контанти та змінні
#define PIN_SCK PORTB5  // Тактовий вихід Master_SPI 
#define PIN_MOSI PORTB3 // Вихід даних Master_SPI 
#define PIN_SS PORTB2   // Вибір кристала (завантаження на вихід)  
#define PIN_SS1 PORTB1  // Вибір кристала для паралельного запису
                        //(можна використовувати для збільшення швидкодії)
#define ON 1            
#define OFF 0

#define cs_lo PORTB |= (1<<PIN_SS) // 1 на SS дані можуть записуватись в регістр зсуву (16-розрядн.)  
#define cs_hi PORTB &= ~(1<<PIN_SS)// 0 на SS активний вибір кристала 
                                //(завантаження записаних даних на порт)

#define cs1_lo PORTB |= (1<<PIN_SS1) //те саме для паралельного запису 
#define cs1_hi PORTB &= ~(1<<PIN_SS1)
//------------------------------------------------------
//Адреси регістрів настройки МАХ7219
//------------------------------------------------------
#define MAX7219_MODE_DECODE 0x09    // Декадний режим
#define MAX7219_MODE_INTENSITY 0x0A // Яскравість 
#define MAX7219_MODE_SCAN_LIMIT 0x0B// Розряднісь дисплея
#define MAX7219_MODE_POWER 0x0C     // Вимкнення живлення
#define MAX7219_MODE_TEST 0x0F      // Тестовий режим
#define MAX7219_MODE_NOOP 0x00      // Пуста операція
       //Адреси регістрів сегментів
const unsigned char MAX7219_DIGIT[8]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};

#define MAX7219_CHAR_BLANK 0xF     // пустота
#define MAX7219_CHAR_NEGATIVE 0xA  // мінус "-" 
#define BYTE_SYMBOL 0x08          //Максимальна кількість символів

unsigned char active_digit = 8;//к-ть активних розрядів дисплея 
unsigned char number_of_displays=2;//к-ть активних дисплеїв 
unsigned char i,j;// лічильники

//-------------------------------------------------------------------------
//Фукнція запису по SPI (апаратний)
//-------------------------------------------------------------------------
void spiSendByte (char databyte)
{
SPDR = databyte; //записуємо переданий байт в регістр SPI
while (!(SPSR & (1 << SPIF)));//Якщо все передали флаг встановлюється і ми виходими з функції
}
//-----------------------------------------------------------------------------------------
// Функція запису в MAX7219 (пакет 16-біт!!)
//-----------------------------------------------------------------------------------------
void MAX7219_writeData(char data_register, char data,unsigned char chip)
{
unsigned short k;

cs_hi; //Slave_SPI активний може приймати дані 
spiSendByte(data_register);//старші біти передаємо першими
spiSendByte(data);// їх потіснять на місця біти байта даних
for(k=0;k<2*chip;k++)// якщо кількість каскадованих чіпів більша 1(лічба з 0) 
{// то потрібно подати пару пустих байтів після посилки 
//вони продавлять дані на наступний чіп 
spiSendByte(MAX7219_MODE_NOOP);  // пустота операція 
}// Нічого не робить але зміщає все на один байт 
//не дивуйтесь, спочатку пишеться адреса а потім дані які є адресою? але це вже не грає ніякої ролі бо операція пуста!!
// головне зміщення на 2 байта а все інше не міняється! 
cs_lo; // Закриваємо Slave_SPI
}

/*Функція очистки дисплея може колись знадобиться...
void clear_display()
{
unsigned char i,j;
for(j=0;j<3;j++)
{
for(i=1;i<=active_digit;i++)
{
MAX7219_writeData(i, MAX7219_CHAR_BLANK,j);
}
}
}
  */   
//------------------------------------------------------------------------------------------
//Функція виводу даних на дисплей через MAX7219
//------------------------------------------------------------------------------------------  
void display(unsigned char data_disp)
{
for(j=0;j<BYTE_SYMBOL;j++){
MAX7219_writeData(MAX7219_DIGIT[j],data_disp,0);
MAX7219_writeData(MAX7219_DIGIT[j],data_disp+1,1);
   }
}
//--------------------------------------------------------------------------------------------
void main(void)
{
unsigned int j;
DDRB |= (1 << PIN_SCK) | (1 << PIN_MOSI) | (1 << PIN_SS)| (1 << PIN_SS1);//виходи для SPI
SPCR |= (1 << SPE) | (1 << MSTR)| (1<<SPR1);// Вмикаємо SPI, Режим мастер, старшим бітом вперед /Швидкість f/64 

for(j=0;j<number_of_displays;j++)// налаштування кожного чіпа МАХ7219
{
    MAX7219_writeData(MAX7219_MODE_DECODE,0xFF,j); // декадний режим включено
    MAX7219_writeData(MAX7219_MODE_SCAN_LIMIT,active_digit-1,j);// Всі сегменти скануються 0-7
    MAX7219_writeData(MAX7219_MODE_INTENSITY, 8,j); // яскравість на половину 17/34 
    MAX7219_writeData(MAX7219_MODE_POWER, ON,j);// вмикаємо всі табла
 }
while(1)
{
    display(i);// Виводимо змінну яку наращуємо кожну секунду
    delay_ms(1000);
    i++;
    if(i==10) i=0;
}
}



вівторок, 13 січня 2015 р.

Годинник на Atmega8 + DS1302


Схема годинника на DS1302 в Proteus




#asm // Асемблерна вставка ініціалізації підключення мікросхеми DS1302 до Atmega8
  .equ __DS1302_port=0x15;//Подключаем DS1302 к PORTС(0x15-адреса регістра порта)
  .equ __DS1302_io=2 ;//Другий пін для io
  .equ __DS1302_sclk=1 ;//Перший пін для sclk
  .equ __DS1302_rst=0 ;//Нульовий пін для rst
  #endasm
#include <bcd.h>
#include <mega8.h>
#include <delay.h>
#include <DS1302.h>

#define DIGITS 6   // Кількість розрядів індикатора
unsigned char chislo[DIGITS]; //масив чисел, що відповідають кожному розряду
unsigned char numder[]= //масив, в якому індексу будут відповідати біти на порту D для кожної цифри
{ 
0b00111111, //цифра 0
0b00000110, //цифра 1
0b01011011, //цифра 2
0b01001111, //цифра 3
0b01100110, //цифра 4
0b01101101, //цифра 5
0b01111101, //цифра 6
0b00000111, //цифра 7
0b01111111, //цифра 8
0b01101111, //цифра 9
0b00000000, //пусто
}; 
unsigned char hour_1, minute_1;//години та хвилини для наведення
unsigned char second, minute, hour, date, month, year;// змінні часу та дати
bit Button_1_state, Button_2_state;
unsigned short i=0; // змінна, що визначає номер розряду який виводиться
unsigned short k, dp; // змінні для крапки
//*****************************************************************************
// преривання по переповненню timer0
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
PORTB=0x3f; // погасимо індикатор
    if (i==2||i==4) dp=k;//виводимо крапку на 2 та 4 індикатор
    else  dp=0;
PORTD=((numder[ chislo[i]])|dp); // виведем поточне число    
PORTB=~(1<<i); // вмикаємо відповідний розряд індикатора
if (i++>DIGITS) i=0; // якшо не дійшли до старшого розряду готуєм виведення наступного
  }                    // якшо дійшли до старшого розряду готуєм виведення нульового
//*****************************************************************************
void out_1(unsigned short num)//виведення першої пари знаків (для секунд)
{   chislo[0]=num%10; //одиниці секунд
    chislo[1]=num/10; // десятки
    k=(~k)&0b10000000;// крапка засвічується та гасне кожну секунду
    }
      
void out_2(unsigned short num)//виведення другої пари знаків (для хвилин)
{   chislo[2]=num%10; //одиниці хвилин
    chislo[3]=num/10; // десятки
    }  
void out_3(unsigned short num)//виведення третьої пари знаків (для годин)
{   chislo[4]=num%10; //одиниці годин
    chislo[5]=num/10; // десятки
    }
//****************************************************************************
void main(void)
{
//Порт В
    PORTB=0x00;
    DDRB=0b00111111; //PB0-3 виходи (розряди)
//Порт С
    PORTC=0b00111111;      //включимо підтягуючі резистори на PС0-5
    DDRC=0b00000000; //PС0-3 входи (кнопки + DS1302)
//Порт Д
    PORTD=0b0000000;
    DDRD=0b11111111; //PD0-PD6 виходи (сегменти) 

    TCCR0=0x03; //встановлюємо подільник таймера 64
    rtc_init(0,0,0);//Ініціалізація DS1302 
            // кожне перерив приблизно 2 мс
    rtc_set_date(13,01,15);//встановлюємо дату 13.01.15         

    TIMSK=0x01;// переривання по переповненню таймера 0   
    #asm("sei")// глобальний дозвіл перерывання
//***************************************************************************
while (1) //основний цикл
{ 
rtc_get_time(&hour,&minute,&second);//зчитуємо години, хвилини, секунди
                                    // у відповідні змінні
    out_1(second);    //виводимо на індикатор секунди
    out_2(minute);    //хвилини
    out_3(hour);     // години    
   
 while (PINC.3==0){   //Поки натиснена кнопка збільшення хвилин
      k=0x80;// крапки засвічуються
  minute++;         // збільшуємо хвилини кожні 300мс в діапазоні 0-60
  if(minute==60)minute=0;
  minute_1 = bin2bcd(minute);// перетворюємо двійкове число в двійково-десяткове
  out_2(minute);//виводимо на індикатор хвилини які наводимо
  delay_ms(300);
  Button_1_state = 1;
      
  }
 while (PINC.4==0){ //Поки натиснена кнопка збільшення годин
      k=0x80;// крапки засвічуються
  hour++;      // збільшуємо години кожні 300мс в діапазоні 0-24
  if(hour==24)hour=0;
  hour_1 = bin2bcd(hour);
  out_3(hour);    //виводимо на індикатор
  delay_ms(300);
  Button_2_state = 1;
    }
    delay_ms(500);
    if (Button_1_state && PINC.3){// Якщо кнопка наведення хвилин була натиснена і її тільки що відпустили 
        ds1302_write(0b10000010, minute_1);//Записуємо нове виставлене значення хвилин в 1302
        ds1302_write(0b10000000, 0);//Скидаємо секунди в 0 в 1302
        Button_1_state = 0;// скидаємо фіксатор
        }
    if (Button_2_state && PINC.4){// Якщо кнопка наведення годин була натиснена і її тільки що відпустили 
        ds1302_write(0b10000100, hour_1);//Записуємо нове виставлене значення годин в 1302
        Button_2_state = 0;
        }
    while (PINC.5==0){ // Поки кнопка дати натиснена 
    k=0;// розділові крапки засвічуються
    rtc_get_date(&date, &month, &year); // зчитується дата
    out_1(year);    //виводимо на індикатор рік
    out_2(month);    //місяць
    out_3(date);     //число 
    delay_ms(500);
 }  
 }   
}

Sourse_file (Proteus in folder EXE)








понеділок, 12 січня 2015 р.

Годинник на індикаторі 7 segment AVR

Годинниковий кварцевий резонатор 
   Розглянемо приклад побудови цифрового годинника на семисегментному індикаторі, який буде відображати години : хвилини : секунди та розділові крапки між ними. Для написання програми скористаємось шаблоном який використовували для роботи із семисегментним індикатором (посилання). Для побудови проекту будемо використовувати шестирозрядний індикатор. Головна відмінність в тому, що для точного відрахування секунд не підходить вбудований RC-генератор за рахунок своєї нестабільності, в мікроконтроллері можна для забезпечення необхідної точності використати таймер 2 в асинхронному режимі коли він тактується від окремого кварцевого високостабільного генератора з частотою 32768 Гц. Такий генератор ми можемо отримати, якщо до виводів контролера підєднати резонатор з частотою  32768 Гц. Такі резонатори ще називають годинникові. Як видно 32768 = (2^15) це число можна розписати як 32768 = 256*128 запамятаємо це значення. Нижче наведений текст програми в CodeVisionAVR.  Налаштування таймера 2 в асинхронному режимі здійснюємо наступним чином: на вході ставимо подільник з коефіцієнтом ділення 256 коли вміст лічильника буде 127 (тобто 128 тіків від 0 до 127) мине рівно одна секунда, тоді генеруємо переривання в якому підраховуємо секунди, хвилини та години.  
    
#include <mega8.h>
#include <delay.h>
#define DIGITS 6   // Кількість розрядів індикатора
unsigned char chislo[DIGITS]; //масив чисел, що відповідають кожному розряду
unsigned char numder[]= //масив, в якому індексу будут відповідати біти на порту D для кожної цифри
{ 
0b00111111, //цифра 0
0b00000110, //цифра 1
0b01011011, //цифра 2
0b01001111, //цифра 3
0b01100110, //цифра 4
0b01101101, //цифра 5
0b01111101, //цифра 6
0b00000111, //цифра 7
0b01111111, //цифра 8
0b01101111, //цифра 9
0b00000000, //пусто
}; 
// змінні, години:хвилини:секунди які виводиться на дисплей
unsigned short old_sek=1, sek=0, old_min=1, min=0, old_chas=5, chas=0;
unsigned short i=0; // змінна, що визначає номер розряду який виводиться
unsigned short k, dp; // змінні для крапки

// преривання по переповненню timer0
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
PORTB=0x3f; // погасимо індикатор
    if (i==2||i==4) dp=k;//виводимо крапку на 2 та 4 індикатор
    else  dp=0;
PORTD=((numder[ chislo[i]])|dp); // виведем поточне число    
PORTB=~(1<<i); // вмикаємо відповідний розряд індикатора
if (i++>DIGITS) i=0; // якшо не дійшли до старшого розряду готуєм виведення наступного
  }                    // якшо дійшли до старшого розряду готуєм виведення нульового
// Переривання по співпаданню таймера 2 (відбувається кожну секунду) )
interrupt [TIM2_COMP] void timer2_comp_isr(void){
   sek++;// нарощення секунд
   if(sek==60){
     sek=0;
     min++;// нарощення хвилин
   } 
   if(min==60){
     min=0;
     chas++;// нарощення годин
   }
   if(chas==24){
     chas=0;
     }
   }
void out_1(unsigned short num)//виведення першої пари знаків (для секунд)
{   chislo[0]=num%10; //одиниці секунд
    chislo[1]=num/10; // десятки
    k=(~k)&0b10000000;// крапка засвічується та гасне кожну секунду
    }
      
void out_2(unsigned short num)//виведення другої пари знаків (для хвилин)
{   chislo[2]=num%10; //одиниці хвилин
    chislo[3]=num/10; // десятки
    }  
void out_3(unsigned short num)//виведення третьої пари знаків (для годин)
{   chislo[4]=num%10; //одиниці годин
    chislo[5]=num/10; // десятки
    }
void main(void)
{
//Порт В
PORTB=0x00;
DDRB=0b00111111; //PB0-3 виходи (розряди)
//Порт С
PORTC=0x0f;      //включимо підтягуючі резистори на PС0-3
DDRC=0b00000000; //PС0-3 входи (розряди)
//Порт Д
PORTD=0b0000000;
DDRD=0b11111111; //PD0-PD6 виходи (сегменти) 

TCCR0=0x03; //встановлюємо подільник таймера 64 
            // кожне перерив приблизно 2 мс         
//********* Налаштування таймера 2***************************
ASSR=0b00001000;// включили асинхронний режим
TCNT2=0x00;     //занулення регістра лічильника 
OCR2=127;// число з яким порівнюється вміст лічильника             
TCCR2=0b00000110;//подільник частоти з коеф. 256         
delay_ms(100);// затримка для введення асинхронного лічильника
TCCR2 |=(1<<WGM21);//обнулення лічильника при співпаданні
//************************************************************
TIMSK=0x81;//прерывания по переполнению таймера 0 та співпадання таймера 2
#asm("sei")// глобальний дозвіл перерывання
while (1) //основний цикл
{
if(old_sek!=sek) { //Якщо секунди змінились
    out_1(sek);    //виводимо на індикатор
    old_sek=sek;
    }
 if(old_min!=min) { //Якщо хвилини змінились
    out_2(min);    //виводимо на індикатор
    old_min=min;
    } 
 if(old_chas!=chas) {//Якщо години змінились
    out_3(chas);     //виводимо на індикатор
    old_chas=chas;
    }
 if (PINC.0==0){   //Якщо натиснена кнопка збільшення хвилин
    min++;         // збільшуємо хвилини кожні 200мс в діапазоні 0-60
 if(min==60)min=0;
   delay_ms(200);
  }
  if (PINC.1==0){ //Якщо натиснена кнопка збільшення годин
    chas++;      // збільшуємо години кожні 200мс в діапазоні 0-24
 if(chas==24)chas=0; 
   delay_ms(200);
  }
 }
}


Модель годинника в Proteus