включение нагрузки 1 кнопкой без фиксации, на МК

Обсуждаем контроллеры компании Atmel.
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение OKF »

Да, смотря какие цели. Проще на делай. Если хочется накрутить - с таймером и даже с прерыванием.

Код: Выделить всё

#include <avr/io.h>


#define KEY           1                 //биты
#define LED           2

#define key_init()    (PORTB |= 1<<KEY) //порты
#define get_key()     (!(PINB & 1<<KEY))
#define led_init()    (DDRB  |= 1<<LED)
#define led_toggle()  (PORTB ^= 1<<LED)
// #define timer_init()  (настройка таймера)
// #define sys_tick()    (тик от таймера)

#define DEBOUNCE_TIME 30                //дребезг


uint8_t keyExe(void)
{
  static uint8_t key;
  static uint8_t timer;
 
  if (key != get_key()) {
    key = !key;
    timer = DEBOUNCE_TIME;
  }
  else if (timer && !--timer)
    return key;
  return 0;
}


int main(void) {
  key_init();
  led_init();
  while (1) {
    delay(1);
    // if (sys_timer())                 //если не хочется делай
    if (keyExe())
      led_toggle();
  }
}
Аватара пользователя
sergo80zxc
Встал на лапы
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение sergo80zxc »

OKF не знаю как вы все это легко читаете, тут столько дефайнов)) жесть) все вроде красиво но читаю со слезами истерики)) привык что вижу порт такой то бит такой то)
глаза боятся, а руки что то не делают))
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение OKF »

Ну так логика от железа должны отделяться. Вы написали один раз led_init() и забыли. Вы видите что это макро настраивает светодиод. Ну и остальные аналогично. В принципе, это можно делать и через функции, даже не инлайн - компиляторы сейчас умные, но здесь всё в одно действие, поэтому сложно ошибиться.) Ну а вот уже настройка таймера можно и через функцию - там чуть побольше будет.
Просто вы потом берёте этот исходник и правите только ввод-вывод в шапке, а всё остальное без изменений. Или вообще все эти пины/порты размещаете а отдельном файле и правите только его. И не важно потом на каком МК вы его используете - хоть вообще на другом семействе, не обязательно AVR. Эта программка маленькаая, поэтому тут и не заметно какого либо преимущества, ну а дальше то будет поболее.)
Аватара пользователя
sergo80zxc
Встал на лапы
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение sergo80zxc »

OKF ну спорить тут не приходится, переносимость рулит, спасибо за помощь)
глаза боятся, а руки что то не делают))
NStorm
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение NStorm »

Простой пример из реального моего проекта на атмега8:
Спойлер

Код: Выделить всё

// IO Mappings (PIN defines)
// PORT B
#define WS2812_PORT PORTB
#define WS2812 PB0
#define AIN_2_PORT PORTB
#define AIN1_2_PORT PORTB
#define AIN1_2 PB1
#define AIN2_2_PORT PORTB
#define AIN2_2 PB2
#define PWMA_PORT PORTB
#define PWMA PB3

// PORT C
#define AIN_PORT PORTC
#define AIN1_PORT PORTC
#define AIN1 PC0
#define AIN2_PORT PORTC
#define AIN2 PC1
#define STBY_PORT PORTC
#define STBY PC2
#define NSLEEP_PORT PORTC
#define NSLEEP PC3
#define REED_PORT PORTC
#define REED_PIN PINC
#define REED PC4

// PORT D
#define RX_PORT PORTD
#define RX_PIN PIND
#define RX PD0
#define TX_PORT PORTD
#define TX PD1
#define BTN_PORT PORTD
#define BTN_PIN PIND
#define BTN PD2
#define ACDET_PORT PORTD
#define ACDET_PIN PIND
#define ACDET PD3
#define MSW_PORT PORTD
#define MSW_PIN PIND
#define M1SW1 PD4
#define M1SW2 PD5
#define M2SW1 PD6
#define M2SW2 PD7


Задействованы почти все пины. Я бы каждый раз задолбался бы в схему лезть смотреть что к чему подключено. А так один раз написал дефайны и пользуюсь ими. В редакторе кода есть автодополнение, я всегда легко найду нужный мне дефайн, нужного мне пина с нужной функцией.
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение OKF »

Очень много букв.) А так?

Код: Выделить всё

#include "pin_macro.h"  //макро пишутся один раз

//подключение
#define KEY    B,1,L   //port, bit, level
#define LED    B,2,L   //активный уровень H либо L, в зависимости от подключения

//работа
pullup(KEY);  //подтянули
off(LED);       //выключили
out(LED);      // настроили на выход
if (pin(KEY))  //при нажатии
  on(LED);    // включаем и неважно что и как подключено - чистая логика!

Лазить в текст программы вообще плохой тон. Всё подключение и настройки ТОЛЬКО в хедерах!
Аватара пользователя
sergo80zxc
Встал на лапы
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение sergo80zxc »

на 2 кнопки и на 2 светодиода написал код, работает только половина( порт РВ1 работает включается и выключается, а РВ3 не работает)

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


Код: Выделить всё

//тини13
//
//
//
//обработка кнопки выведена в процедуру (button_processing), обработка кнопки повесили на таймер, как срабатывает таймер вызывается
//обработчик прерывания, который в свою очередь вызывает процедуру обработки кнопки (button_processing). в процедуре используются глобальные
//переменные, объявленые перед функцией майн как (volatile) (если переменные оббъявить локальными то каждый раз при выходе из функции
//они будут обнулятся)
//биты используемые в процедуре обзавем через дефайны  своими именами( дабы не править потом сам код при изменении битов или всего мк)
//
//

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1200000 UL

#define key PB0      // пин В0 теперь зовется key
#define LED PB1      // пин В1 порта В зовется LED

#define key1 PB4     // пин В4 теперь зовется key1
#define LED1 PB3     // пин В3 порта В зовется LED1




volatile unsigned char i= 0, k= 0,m= 0, n= 0; //объявляем восьмибитные переменные счеткики.

volatile unsigned char x= 0, y= 0;            //объявляем восьмибитные переменные. промежуточные аккумуляторы

//----------

void button_processing(unsigned char a,unsigned char b,unsigned char c,unsigned char d )  // объявим процедуру (button_processing),
{                                                                                           
                                     //обработчик кнопки. Следом за именем функции в скобках, пишут типы и количество аргументов
                                     //(параметров) функции. Т.е. значения которые передаются в функцию .
                                     // а---отслеживаемый пин, b---управляемый пин, с---счетчик нажатой кнопки,d---счетчик отпущеной кнопки

  if (~PINB & (1 << a))              // Если нулевой разряд порта В сброшен (т.е. кнопка нажата, на пине лог. 0) выполняется КОД1.
  {
    if (c < 254)                       // КОД1. И ЕСЛИ i меньше 254, то увеличиваем i на 1 (i++) (это условие от переполнения счетчика i )
   {
      c++;
    }                                  // то есть счетчик может доходить до 254. (i < 254) то есть  253 максимальное число возможное в
  }                                    // этом условии и постинкремент +1 = 254




   if (c >= 5)                        // если счетчик  равен 5 то,  кнопка нажата  была при всех опросах, то ловим отпускание
  {
      if (PINB & (1 << a))          // ---------- Проверка разряда на наличие логической единицы (установки) с if
     {                                // Если 0 разряд порта  PВ установлен (единица), то выполняется Код1, если нет , то Код2

          if (d < 254)                // И ЕСЛИ k меньше 254, то увеличиваем k на 1 (i++) (это условие от переполнения счетчика k )
         {
             d++;                     // (Код1) пин PB0 установлен(кнопка не нажата) прибавим 1 к переменной счетчику 
                  
              if (d > 5)              // если насчитали больше 5 не нажатых состояний, то считаем , что кнопку отпустили
            {

               PORTB ^= (1 << b);   // инвертируем состояние пина PB1
               c = 0;                 // обнуляем счетчик
               d = 0;                 // обнуляем счетчик
             }
         }
     }
  }

               x = c;                 // считываем значение счетчика (c) и заносим его в аккумулятор (x)
               y = d;                 // считываем значение счетчика (d) и заносим его в аккумулятор (y)



}


//----------

int main(void)
{
  DDRB = 0b11111110;     // бит РВ0 конфигурируем как вход
  PORTB = 0b00000001;    // входы подтянем

  TIMSK0 = 0b00000010;   // бит РВ1(TOIE0) устанавливаем в 1, разрешения прерывания по переполнению таймера /счетчика Т0
  TCCR0A = 0b00000000;   // биты РВ0(WGM00) и РВ1(WGM01) сбрасываем в 0,  настройка режима Normal
  TCCR0B = 0b00000011;   // настраиваем предделитель на 64 (биты  CS02=0,CS01=1,CS00=1),бит В3(WGM02) сбрасываем в 0,режим Normal
  GTCCR = 0b00000001;    // бит РВ0(PSR10) устанавливаем в 1, Сброс предделителя TO (сбрасываем пред. в конце настроек)

  sei();                 // Разрешить прерывания

    while (1)
   {                     // Основной цикл программы, он пуст, так как вся работа в прерывании
   }

}

//----------

ISR(TIM0_OVF_vect)                     // обработчик прерывания по таймеру Т0
{                                       
   button_processing (key,LED,i,k);                //вызываем процедуру обработки кнопки
     
               i = x;                 // пишем значения из аккумуляторов обратно в глобальные переменные
               k = y;                 // иначе при выходе из функции обработаные данные сотрутся

   button_processing (key1,LED1,m,n);              //вызываем процедуру обработки кнопки

               m = x;                 // пишем значения из аккумуляторов обратно в глобальные переменные
               n = y;                 // иначе при выходе из функции обработаные данные сотрутся
}
глаза боятся, а руки что то не делают))
NStorm
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение NStorm »

Потому что у вас PB4 как выход настроен.
Но код во многом очень неправилен. Ну нельзя так подходить. Плохо неосмысленно называть переменные. Все эти abcdxyz потом не читаются. Запись через промежуточные переменные... тут я понимаю вы про указатели еще не слышали, тут расскажу сейчас чуть позже.

Добавлено after 19 minutes 33 seconds:
В общем делаем так:
void button_processing(unsigned char a,unsigned char b,unsigned char *c,unsigned char *d )

Это значит, что на с и d мы передаем указатели (pointer), а не значения. Указатель - это адрес в памяти, где лежит само значение переменной.
В самой функции обращаемся к ним как *c, *d - это оператор т.н. разименовывания (dereference). Т.е. когда по указателю мы обращаемся к значениям переменной, распположенной по этому указателю.
Для вызова функции и передачи указателя нужно делать так:
button_processing (key,LED,&i,&k);
& - оператор ссылки. Обратное от *. Тут как раз такие мы получаем указатель (на адрес в памяти, где находится значение).
Т.о. передав функции адрес, в самой функции меняя *c, *d изменится и i с k сразу. При этом экономится память и флэш - не надо промежуточных переменных и тратить время на копирование значений.

В остальном код всё еще сильно плох )

Добавлено after 2 minutes 32 seconds:
Вот онлайн компилятор, можно посмотреть и потренироваться: https://godbolt.org/z/gjAXD2

Добавлено after 6 minutes 38 seconds:
Кстати уже не раз говорил про запись магических чисел, что это плохо. Вот вы и попались на этом.

Код: Выделить всё

  DDRB = 0b11111110;     // бит РВ0 конфигурируем как вход
  PORTB = 0b00000001;    // входы подтянем

Ни что иное, как магические числа.
Если написали сразу

Код: Выделить всё

  DDRB = ~(1 << key);
  PORTB = 1 << key;

Это тоже самое, но НАГЛЯДНО. Сразу видно где косяк в программе!

Ну и почему у вас key маленькими написано, а LED большими? Общепринято все define записывать большими буквами. И раз у вас есть 2 штуки, то не надо одни без цифр записывать, другую с цифрой 1. Вы в лифте когда-нибудь видели, что цоколь был не подписан, а 2ой этаж назывался 1ым? KEY1, LED1, KEY2, LED2. Не ленитесь записывать сразу нормально. Потом сами же путаетесь, вон на лицо пример.
Аватара пользователя
sergo80zxc
Встал на лапы
Сообщения: 113
Зарегистрирован: Пт апр 04, 2014 09:34:08
Откуда: Санкт-Петербург

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение sergo80zxc »

NStorm спасибо, все по полочкам разложили) до указателей и правда еще не добрался, а как глупо я весь вечер искал причину не рабочего кода из за не выставленого на выход бита РВ4))))) код заработал, но вижу 2 кнопками 30 процентов флешь сожрал)

не могу понять как в онлайн компилятор код вставить
глаза боятся, а руки что то не делают))
NStorm
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение NStorm »

Как-то так, но логика работы мне всё-равно сильно не нравится.

Спойлер

Код: Выделить всё

//тини13
//
//
//
//обработка кнопки выведена в процедуру (button_processing), обработка кнопки повесили на таймер, как срабатывает таймер вызывается
//обработчик прерывания, который в свою очередь вызывает процедуру обработки кнопки (button_processing). в процедуре используются глобальные
//переменные, объявленые перед функцией майн как (volatile) (если переменные оббъявить локальными то каждый раз при выходе из функции
//они будут обнулятся)
//биты используемые в процедуре обзавем через дефайны  своими именами( дабы не править потом сам код при изменении битов или всего мк)
//
//

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1200000 UL

#define KEY1 PB0      // пин В0 теперь зовется key
#define LED1 PB1      // пин В1 порта В зовется LED

#define KEY2 PB4     // пин В4 теперь зовется key1
#define LED2 PB3     // пин В3 порта В зовется LED1




volatile unsigned char i= 0, k= 0,m= 0, n= 0; //объявляем восьмибитные переменные счеткики.

void static inline button_processing(unsigned char key, unsigned char led, volatile unsigned char *c, volatile unsigned char *d)  // объявим процедуру (button_processing),
{
                                     //обработчик кнопки. Следом за именем функции в скобках, пишут типы и количество аргументов
                                     //(параметров) функции. Т.е. значения которые передаются в функцию .
                                     // а---отслеживаемый пин, b---управляемый пин, с---счетчик нажатой кнопки,d---счетчик отпущеной кнопки

  if (~PINB & (1 << key))              // Если нулевой разряд порта В сброшен (т.е. кнопка нажата, на пине лог. 0) выполняется КОД1.
  {
    if (*c < 254)                       // КОД1. И ЕСЛИ i меньше 254, то увеличиваем i на 1 (i++) (это условие от переполнения счетчика i )
   {
      *c++;
    }                                  // то есть счетчик может доходить до 254. (i < 254) то есть  253 максимальное число возможное в
  }                                    // этом условии и постинкремент +1 = 254

   if (*c >= 5)                        // если счетчик  равен 5 то,  кнопка нажата  была при всех опросах, то ловим отпускание
  {
      if (PINB & (1 << key))          // ---------- Проверка разряда на наличие логической единицы (установки) с if
     {                                // Если 0 разряд порта  PВ установлен (единица), то выполняется Код1, если нет , то Код2
          if (*d < 254)                // И ЕСЛИ k меньше 254, то увеличиваем k на 1 (i++) (это условие от переполнения счетчика k )
         {
             *d++;                     // (Код1) пин PB0 установлен(кнопка не нажата) прибавим 1 к переменной счетчику
             if (*d > 5)              // если насчитали больше 5 не нажатых состояний, то считаем , что кнопку отпустили
             {
               PORTB ^= (1 << led);   // инвертируем состояние пина PB1
               *c = 0;                 // обнуляем счетчик
               *d = 0;                 // обнуляем счетчик
             }
         }
     }
  }
}


//----------

int main(void)
{
  DDRB = ~((1 << KEY1) | (1 << KEY2));     // бит РВ0 конфигурируем как вход
  PORTB = (1 << KEY1) | (1 << KEY2);    // входы подтянем

  TIMSK0 = 0b00000010;   // бит РВ1(TOIE0) устанавливаем в 1, разрешения прерывания по переполнению таймера /счетчика Т0
  TCCR0A = 0b00000000;   // биты РВ0(WGM00) и РВ1(WGM01) сбрасываем в 0,  настройка режима Normal
  TCCR0B = 0b00000011;   // настраиваем предделитель на 64 (биты  CS02=0,CS01=1,CS00=1),бит В3(WGM02) сбрасываем в 0,режим Normal
  GTCCR = 0b00000001;    // бит РВ0(PSR10) устанавливаем в 1, Сброс предделителя TO (сбрасываем пред. в конце настроек)

  sei();                 // Разрешить прерывания

    while (1)
   {                     // Основной цикл программы, он пуст, так как вся работа в прерывании
   }

}

//----------

ISR(TIM0_OVF_vect)                     // обработчик прерывания по таймеру Т0
{
   button_processing (KEY1,LED1,&i,&k);                //вызываем процедуру обработки кнопки
   button_processing (KEY2,LED2,&m,&n);              //вызываем процедуру обработки кнопки
}
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение ARV »

NStorm писал(а):но логика работы мне всё-равно сильно не нравится
согласен. вы явно не делаете скридку на то, что топикстартер - начинающий. какие там указатели?! его надо научить стройно излагать свои мысли скудными средствами языка, а вы сразу к указателям... извините, но вся тема явно заведет sergo80zxc в тупик.

для чего ему насоветовали для задержек применять прерывания?! ну блин, в первом классе говорят: нельзя от меньшего отнимать более! НЕЛЬЗЯ - и точка. а в пятом классе говорят - а на самом деле МОЖНО. но в пятом! по мере того, как растут навыки, расширяется и круг тем, которые стоит предлагать к восприятию. если в первом классе огорошить задачей "У Пети было 3 яблока, а Вася забрал у него 5 яблок. Сколько осталось у Пети яблок?" и ответом на нее "осталось МИНУС 2 яблока", то первоклассник нахрен убежит из дому и будет в лесу среди волков жить, как маугли - там у него точно из имеющихся 3 никто 5 не отнимет...

sergo80zxc, я вам настоятельно рекомендую почитать мою статью: https://simple-devices.ru/articles/7-so ... 0-16-40-05
к моему глубочайшему сожалению, владелец сайта забил на модуль врезки кода, и потому примеры кода в статье выглядят ужасно, но если читать буквы внимательно, то смысл статьи будет понятен. если будут вопросы - готов ответить.

ваша задача сводится к следующей: необходимо узнавать, какая кнопка нажата, и в зависимости от этого включать и выключать разные нагрузки по принципу кнопки с фиксацией. итак, задача состоит из трех действий: определение нажатой кнопки, выбора по ней нужной нагрузки и управлению этой нагрузкой "триггерным" способом.запишем это прямо сразу так, как только что было по-русски, но в переводе на язык Си, т.е. подбирая обычным словам наиболее подходящие аналогии из Си:

Код: Выделить всё

while(1){ // главный цикл - никуда не деться, т.к. управление кнопками непрерывное
   button = get_pressed_buttons(); // считаем, что у нас есть готовая функция, которая вернет нам нажатую кнопку
   // при этом не напрягаемся тем, что такой функции еще нет - мы её сделаем позже
   
   switch(button){ // это наиболее удбный способ анализировать какое-то число в Си
   case BTN_1 : trigger_load(LOAD1); break; // точно так же считаем, что у нас есть готовая функция для управления нагрузкой
   case BTN_2 : trigger_load(LOAD2); break; // и эта функция просто должна получить нагрузку, которой она будет управлять
   // вариантов нажатых кнопок может быть сколько угодно, включая и одновременное нажатие
   case BTN_1_AND_BTN_2: trigger_load(LOAD3);
   }
}
посмотрите на получившийся код: он РЕШАЕТ ВАШУ ЗАДАЧУ! в нем есть абсолютно все, что вы хотели! более того, он построен так, что просто произнеся написанное (на каком-то гибриде русско-английского языка), вы получите точное описание того, что вы изначально излагали в качестве постановки задачи!
получается, это уже есть готовый код, который вы и хотели получить. только надо добавить в него все то, чего еще нет, в частности, описать, что такое BTN_1, BTN_2, LOAD1 и т.д. - то есть описать все переменные и константы. если с этим затруднения - можно сделать так же, как с нашими несуществующими функциями, т.е. написать что-то так, как будто они описаны уже и правильно (для этого использовать комментарии):

Код: Выделить всё

#define BTN_0 // BTN_1 - код кнопки 1, возвращаемый функцией get_pressed_buttons(), если кнопка нажата
#define BTN_1 1 // BTN_2 - код кнопки 2, возвращаемый функцией get_pressed_buttons(), если кнопка нажата
#define BTN_1_AND_BTN_2 2 // BTN_1_AND_BTN_2 - код, возвращаемый функцией get_pressed_buttons(), если нажаты сразу обе кнопки 1 и 2
// значения всех констант пока можно задавать любыми, лишь бы были - потом можно будет уточнить всегда, по мере понимания процесса

// нагрузки определяем точно так же - лишь бы были
#define LOAD1 0 // нагрузка 1
#define LOAD2 0 // нагрузка 2
#define LOAD3 0 // нагрузка 3

// заводим переменную для хранения кода кнопки
int button;

// и описываем наши функции

// возвращает код нажатых кнопок
int get_pressed_buttons(void);

// переключает указанную нагрузку
void trigger_load(int load);


если объединить эти оба участка кода, то получится вполне компилируемый код, который только сообщит, что отсутствуют 2 упомянутые функции.
давайте начнем с переключателя нагрузок.

нагрузка у нас - это определенный порт и пин, так? триггерность будет обеспечена простой инверсией состояния этого пина в нужном порту.
поэтому можно было бы описывать нагрузку именно парой порт-пин, например PORTB, PB0 - согласны?
если мы решим, что так удобно, придется переделать то, что только что сделали, а именно переделать определения нагрузок и функцию trigger_load в макрос trigger_load - внешне они будут одинаковы, но макрос боле вольно обращается со своими параметрами:

Код: Выделить всё

// описываем нагрузки теперь так
#define LOAD1 PORTB, PB0 // нагрузка 1
#define LOAD2 PORTB, PB1 // нагрузка 2
#define LOAD3 PORTB, PB2 // нагрузка 3

// строчку void trigger_load(int load); удаляем, а вместо неё пишем так
#define trigger_load(x,y) x ^= (1<<(y))

если вы затрудняетесь с пониманием этой записи, разъясняю:
в коде у нас было trigger_load(LOAD1);
при подстановке макроса LOAD1 получится такая запись: trigger_load(PORTB, PB0);
у нашего макроса triger_load два параметра: первый х, второй y
поэтому наш макрос превратится в PORTB ^= (1<<(PB0))
эта запись по сути означает именно инверсию указанного бита в указанном порту - что и было надо.


итак, изначальная функция trigger_load в ходе нашего погружения в код превратилась в макрос, но основной код (главный цикл) никак не изменился! то есть ГОТОВОЕ РЕШЕНИЕ задачи не поменялось от того, что В НЮАНСИКАХ произошли изменения! более того, со временем вам захочется управлять нагрузкой дистанционно - вы снова превратите trigger_load в функцию (конечно, скорее всего и макросы LOAD1...LOADn наверняка тоже придется подправить), а внутри этой функции реализуете отправку команд по интернету в Южную Америку - и основной код начнет управлять нагрузками на другом конце океана... красиво? ;)


теперь беремся за функцию определения кодов нажатых кнопок. как должна эта функция работать? начинаем действовать точно так же, как и при решении основной задачи: описываем предельно кратко, но точно, что эта функция должна делать: опросить порт, убедиться, что нет дребезга, разобрать битовое состояние порта и вернуть коды кнопок по состояниям битов. теперь точно так же записываем это "на языке Си", заменяя, при необходимости, сложные действия обращением к функциям или макросам (который считаем, как и ранее, у нас есть)... и не бойтесь задержек! задержка в 10 мс достаточна, чтобы гарантировать подавление дребезга, и абсолютно незаметна при решении вашей задачи! у вас получится что-то типа такого кода:

Код: Выделить всё

int get_pressed_buttons(void){
   int code; // переменная для сохранения состояния порта
   code = ! BTN_PIN; // BTN_PIN - это макрос для регистра пинов порта с кнопками
   // кнопки наверняка будут "гасить" подтяжки на землю, поэтому код будет инвертироваться - потому и знак !
   _delay_ms(10); // задержка для подавления дребезга 10 мс (магическое число - лучше превратить в макрос)
   // теперь проверяем, поменялось ли состояние пинов
   if (code == (! BTN_PIN)){
      // не поменялось: надо обработать код
      switch(code){
      case 0b00000001: return BTN_1; // магическое число лучше заменить на макрос
      // я не буду расписывать, как вернуть остальные коды - это ведь и так понятно?
      }
   } else {
      // поменялось:
      return NO_BUTTON; // вернем код отсутствия нажатых кнопок
   }
}
теперь, когда заготовка функции готова, надо дополнить её макросами и т.п. - как и ранее.

вот и все.

почти :)

потому что в нашем коде нет одного важного момента: когда мы ставили задачу, мы забыли важный момент: изменять состояние нагрузок надо только тогда, когда состояние кнопки поменялось, а когда состояние кнопки осталось неизменным, переключать нагрузку не надо! сейчас наш код будет моргать нагрузкой все время, пока мы держим кнопку нажатой! то есть в коде главного цикла нам надо добавить участок, который сравнит полученный код нажатой кнопки с тем кодом, который был в прошлый раз, и, если оба кода одинаковы, не будет ничего анализировать.

но, надеюсь, теперь это вы сможете сделать и самостоятельно.

извините, что почти пересказал рекомендованную к прочтению статью... но, надеюсь, способ решения задачи ОТ ОБЩЕГО К ЧАСТНОМУ вы уловили. выливается он в такой цикл разработки: подумал, изложил, написал, подумал, скорректировал написанное, подумал, изложил, написал, подумал, скорректировал написанное...
трудоемкий процесс, но для начинающего - самое то! хорошая тренировка. рекомендую! :)))
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение OKF »

Всегда поражаюсь как можно так много говорить.) В хорошем смысле. "Это ж какую психику нужно иметь...")) Всегда считал что если человек нормальный/адекватный/думающий, то он поймёт с первого раза, из намёка даже. А не поймёт, так спростит. И в следующий раз уже будет делать по нормальному. Ну а няньчиться уж точно не стОит - значит это всё не его.)
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение ARV »

OKF писал(а):Ну а няньчиться уж точно не стОит - значит это всё не его
есть такая хорошая неизвестно чья цитатка: когда ребенок учится ходить, он падает по сто раз на день, но никому не приходит в голову сказать "возможно, ходить - это просто не его"... :)))
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
OKF
Это не хвост, это антенна
Сообщения: 1385
Зарегистрирован: Вт июн 07, 2011 08:03:18

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение OKF »

Да, с ребёнком всё верно. Но тут уже взрослый, и вроде как, понимающий человек. Поэтому мимо.)
NStorm
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение NStorm »

[uquote="sergo80zxc",url="/forum/viewtopic.php?p=3830640#p3830640"]не могу понять как в онлайн компилятор код вставить[/uquote]
А что там вставлять? По моей ссылке если перейти - в левой панельке и есть исходник на C. Посреднине дизасм AVR (особо не нужно, но оно есть), а справа вывод программы рабочей на ПК. Поэтому справа компилятор выбран GCC для x86_64, а не AVR. Потому что эмулятора там нет, если выбрать avr-gcc то никаких средств для отладки особо не будет, кроме может кода возврата. Но язык C, он один. Поэтому именно какие-то концепции языка можно проверять и на программах для ПК. А тут уже доступен printf() для вывода отладки и результата, легко смотреть что сделали и что получили в итоге.
Demiurg
Это не хвост, это антенна
Сообщения: 1480
Зарегистрирован: Ср июн 25, 2008 15:19:44
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение Demiurg »

Понаписали херню всякую...

kbd_drv.h
Спойлер

Код: Выделить всё

//==================
#ifndef KBD_DRV_H

#define KBD_DRV_H

#include "kbd_drv.h"

#include "main_def_func.h"
//==================

//==================
#ifdef __PROJECT_MODE_WORK__
#define DEBOUNCE_DELAY 30
#endif

#ifdef __PROJECT_MODE_DEBUG__
#define DEBOUNCE_DELAY 3
#endif
//==================

//==================
#define KEY_PIN PINB
#define KEY     3
//==================

//==================
//#define KEY1_PRESSED() ((KEYS1_PIN & (1<<BIT_KEY1) == 0)
//#define KEY1_UNPRESSED() ((KEYS1_PIN & (1<<BIT_KEY1) == 1)

#define Set_Is_Key_Pressed()   check_bit (KEY_PIN, KEY)    // High level.
#define Set_Is_Key_UnPressed() !(check_bit (KEY_PIN, KEY)) // Low level.

// При работе с буфером входов (расширение ввода-вывода):
// #define Set_Is_Key_Stop_Pressed()   check_bit (inputs_buf [0], KEY_STOP)
//==================

//==================
typedef enum _kbd_drv
{
   KBD_DRV_NONE = 0,
   KBD_DRV_WAIT_DOWN,
   KBD_DRV_DOWN,
   KBD_DRV_WAIT_UP,
} kbd_drv_t;
//==================

//==================
void kbd_drv (void);
//==================

#endif


kbd_drv.c
Спойлер

Код: Выделить всё

//==================
#include "kbd_drv.h"
//==================

//==================
void kbd_drv (void)
{
   static kbd_drv_t _kbd_drv;
   static soft_timer ST_KBD;

   switch (_kbd_drv)
   {
      case KBD_DRV_NONE:
         if (Set_Is_Key_Pressed ())
         {
            set_soft_timer (ST_KBD, DEBOUNCE_DELAY);
            _kbd_drv = KBD_DRV_WAIT_DOWN;
         }
         break;

      case KBD_DRV_WAIT_DOWN:
         if (handle_soft_timer (ST_KBD)) // Таймер подавления дребезга.
         {
            if (Set_Is_Key_Pressed ())
            {
               Set_Event (EV_ID_KEY_PRESSED, USER_EVENT);
               _kbd_drv = KBD_DRV_DOWN;
            }
            else
               _kbd_drv = KBD_DRV_NONE;
         }
         break;

      case KBD_DRV_DOWN:
         if (Set_Is_Key_UnPressed ())
         {
            set_soft_timer (ST_KBD, DEBOUNCE_DELAY);
            _kbd_drv = KBD_DRV_WAIT_UP;
         }
         break;

      case KBD_DRV_WAIT_UP:
         if (handle_soft_timer (ST_KBD)) // Таймер подавления дребезга.
         {
            if (Set_Is_Key_UnPressed ())
            {
               Set_Event (EV_ID_KEY_UNPRESSED, USER_EVENT);
               _kbd_drv = KBD_DRV_NONE;
            }
            else
               _kbd_drv = KBD_DRV_DOWN;
         }
         break;
   }
}
//==================


soft_timers.h
Спойлер

Код: Выделить всё

//==================
#ifndef SOFT_TIMERS_H

#define SOFT_TIMERS_H

#include "soft_timers.h"

#include "main_def_func.h"
//==================

//==================
// Выбор: с прерыванием или с опросом флага OCFx.
#define __ST_NO_INTERRUPT__ // __ST_NO_INTERRUPT__ // __ST_INTERRUPT__

// Состав структуры: bool status, u08 sys_tick_prev, u16 cnt, u16 period.
// Итого 6 байтов на один программный таймер. Если убрать элемент period,
// то теперь будет 4 байта на таймер.
#define __ST_NO_PERIOD__ // __ST_PERIOD__ // __ST_NO_PERIOD__
//==================

//==================
#define ST_TCNT         TCNT0
#define ST_TIMSK        TIMSK0
#define ST_OCIE         OCIE0A
#define ST_OCR          OCR0A
#define ST_TCCR         TCCR0B
#define ST_CS0          CS00
#define ST_CS1          CS01
#define ST_CS2          CS02

#define ST_TIFR         TIFR0
#define ST_OCF          OCF0A
//==================

//==================
#define SYS_TICK        (F_CPU/64/1000)
//==================

//==================
typedef struct soft_timer
{
   bool status;
   u08 sys_tick_prev;
   u16 cnt;       // Counter.

#ifdef __ST_PERIOD__
   u16 period;    // Period.
#endif
} soft_timer;
//==================

//==================
void init_soft_timers (void);

#define handle_soft_timer(a) _handle_soft_timer(&a)
bool _handle_soft_timer(soft_timer *ptr_timer);

#ifdef __ST_PERIOD__
#define set_soft_timer(a,b,c) _set_soft_timer (&a, b, c)
void _set_soft_timer (soft_timer *ptr_timer, u16 time, u16 period);
#endif

#ifdef __ST_NO_PERIOD__
#define set_soft_timer(a,b) _set_soft_timer (&a, b)
void _set_soft_timer (soft_timer *ptr_timer, u16 time);
#endif

#define reset_soft_timer(a) _reset_soft_timer (&a)
void _reset_soft_timer (soft_timer *ptr_timer);

void proc_sys_tick (void);
//==================

#endif


soft_timers.c
Спойлер

Код: Выделить всё

//==================
// Реализация программных таймеров.
// Примечание. Важно!!!!!!!!!!!!!!!
// При смене состояния конечного автомата нужно сбросить
// все таймеры, которые используются в этом состоянии. Иначе может быть
// следующая ситуация: если таймер уже использовался в
// текущем состоянии и флаг EN установлен, то по окончании времени
// выполнится соответствующий код.
//---------- Пример использования ----------
// set_soft_timer (ST_LED_2_BLINK, 50, 50); // Установка таймера.
// if (handle_soft_timer (ST_LED_2_BLINK))  // Проверка таймера.
//==================

//==================
#include "soft_timers.h"
//==================

//==================
static u08 sys_tick;
//==================

//==================
#ifdef __ST_INTERRUPT__
#pragma vector = TIMER2_COMP_vect
__interrupt void SysTimerComp (void)
{
   ST_OCR += SYS_TICK;
   sys_tick++;
}
#endif
//==================

//==================
void init_soft_timers (void)
{
   sys_tick = 0;
   ST_TCNT = 0;
   ST_OCR = SYS_TICK;
   ST_TCCR = ((1<<ST_CS1) | (1<<ST_CS0));

#ifdef __ST_INTERRUPT__
   set_bit (ST_TIMSK, ST_OCIE);
#endif
}
//----------
#ifdef __ST_PERIOD__
void _set_soft_timer (soft_timer *ptr_timer, u16 time, u16 period)
#else
void _set_soft_timer (soft_timer *ptr_timer, u16 time)
#endif
{
   ptr_timer -> status = true;

   if (time == 0)
      ptr_timer -> sys_tick_prev = ~sys_tick;
   else
      ptr_timer -> sys_tick_prev = sys_tick;

   ptr_timer -> cnt = time;

#ifdef __ST_PERIOD__
   ptr_timer -> period = period;
#endif
}
//----------
bool _handle_soft_timer (soft_timer *ptr_timer)
{
   if (ptr_timer -> status)
   {
      if (ptr_timer -> sys_tick_prev != sys_tick)
      {
         ptr_timer -> sys_tick_prev = sys_tick;

         if (ptr_timer -> cnt == 0)
         {
            #ifdef __ST_PERIOD__
            if (ptr_timer -> period != 0)
               ptr_timer -> cnt = ptr_timer -> period;
            #endif

            return true;
         }
         else
         {
            ptr_timer -> cnt--;

            if (ptr_timer -> cnt == 0)
            {
               #ifdef __ST_PERIOD__
               if (ptr_timer -> period == 0)
                  ptr_timer -> status = false;
               else
                  ptr_timer -> cnt = ptr_timer -> period;
               #else
               ptr_timer -> status = false;
               #endif

               return true;
            }
         }
      }
   }

   return false;
}
//----------
void _reset_soft_timer (soft_timer *ptr_timer)
{
   ptr_timer -> status = false;
   ptr_timer -> sys_tick_prev = 0;
   ptr_timer -> cnt = 0;

#ifdef __ST_PERIOD__
   ptr_timer -> period = 0;
#endif
}
//----------
#ifdef __ST_NO_INTERRUPT__
void proc_sys_tick (void)
{
   static u08 _proc_sys_tick;

   switch (_proc_sys_tick)
   {
      case 0:
         init_soft_timers ();
         _proc_sys_tick = 1;
         break;

      case 1:
         if (ST_TIFR & (1<<ST_OCF))
         {
            ST_TIFR = (1<<ST_OCF);
            ST_OCR += SYS_TICK;
            sys_tick++;
         }
         break;
   }
}
#endif
//==================



events.h
Спойлер

Код: Выделить всё

//==================
#ifndef EVENTS_H
#define EVENTS_H

#include "events.h"

#include "main_def_func.h"
//==================

//==================
extern u08 Events [];

typedef enum num_event
{
   EV_ID_KEY_PRESSED,
   EV_ID_KEY_UNPRESSED,

   MAX_ID_EVENTS,
} num_event;

#define NORMAL_EVENT 1
#define USER_EVENT   3
//==================

extern void Init_Events (void);
extern bool Get_Event (u08 event);
extern void Set_Event (u08 event, u08 status);
extern void Clr_Event (u08 event);
extern void Process_Events (void);

#endif


events.c
Спойлер

Код: Выделить всё

//==================
#include "events.h"
//==================

//==================
u08 Events [MAX_ID_EVENTS];
//==================

//==================
void Init_Events (void)
{
   u08 i = 0;

   while (i < MAX_ID_EVENTS)
   {
      Events [i++] = 0;
   }
}

bool Get_Event (u08 event)
{
   if (Events [event] == 2 || Events [event] == USER_EVENT)
   {
      Events [event] = 0;
      return true;
   }
   return false;
}

void Set_Event (u08 event, u08 status)
{
   if (Events [event] == 0)
      Events [event] = status;
}

void Clr_Event (u08 event)
{
   Events [event] = 0;
}

void Process_Events (void)
{
   u08 i = 0;

   while (i < MAX_ID_EVENTS)
   {
      switch (Events [i])
      {
         case 0:
            break;

         case 1:
            Events [i] = 2;
            break;

         case 2:
            Events [i] = 0;
            break;
      }

      i++;
   }
}
//==================


main.c
Спойлер

Код: Выделить всё

//==================
#include "main_def_func.h" // Заголовочные файлы, глобальные определения, общие функции.
//==================

//==================
__C_task main (void)
{
   wdt_enable (WDTO_15_MS);

// Иницилизация программных таймеров. Прерывание аппаратного таймера включено.
#ifdef __ST_INTERRUPT__
   init_soft_timers (); // Иницилизация программных таймеров
#endif

   Init_Events ();

   __enable_interrupt ();

   while (1)
   {
      __watchdog_reset ();

// Обработчик счетчика системных тиков. Прерывание аппаратного таймера отключено.
#ifdef __ST_NO_INTERRUPT__
      proc_sys_tick ();
#endif

      proc_device ();
      kbd_drv ();

      Process_Events ();
   }
}
//==================


main_def_func.h
Спойлер

Код: Выделить всё

//==================
#ifndef MAIN_DEF_FUNC_H

#define MAIN_DEF_FUNC_H

#include "main_def_func.h"
//==================

//==================
#include <ioavr.h>
#include <intrinsics.h>
#include <stdbool.h>
#include <math.h>

#include <pgmspace.h>

#include "avrlibtypes.h"
#include "macros.h"

#include "events.h"
#include "soft_timers.h"

#include "kbd_drv.h"

#include "proc_device.h"

#include "proc_errors.h"
//==================

//==================
#define F_CPU 9600000UL
//----------
#define __PROJECT_MODE_WORK__ // __PROJECT_MODE_WORK__ // __PROJECT_MODE_DEBUG__ //
//==================

//==================
#define LOW    0
#define HIGH   1
//==================

//==================
#define WDTO_15_MS 0
#define WDTO_30_MS 1
#define WDTO_60_MS 2
#define WDTO_120_MS 3
#define WDTO_250_MS 4
#define WDTO_500_MS 5
#define WDTO_1_S 6
#define WDTO_2_S 7

void wdt_enable (u08 a);

void wdt_disable (void);

void enable_interrupt (void);

void disable_interrupt (void);

void ext_int0_init (void);

void sleep_mode_init (void);
void sleep_mode_enable (void);
//==================

//==================
typedef void (*FUNC)(void);
//==================

//==================
extern void empty_func (void);

#define EMPTY_FUNC 0
//==================

//==================
#define END_TABLE 0xFF
//==================

//==================
extern void sram_copy (u08 *s_1, u08 *s_2, u08 n);
//==================

//==================
void proc_fsm_func (FUNC __flash *ptr_func, u08 s);
//==================

//==================
void io_periphery_init (void);
//==================

#endif


main_def_func.с
Спойлер

Код: Выделить всё

//==================
#include "main_def_func.h"
//==================

//==================
void wdt_enable (u08 a)
{
   __watchdog_reset ();

#ifdef __IOTINY13_H
   WDTCR = (1<<WDCE) | (1<<WDE) | a;
#endif

#ifdef __IOM8535_H
   WDTCR = (1<<WDCE) | (1<<WDE) | a;
#endif

#ifdef __IOM16_H
   WDTCR = (1<<WDTOE) | (1<<WDE) | a;
#endif

#ifdef __IOM32_H
   WDTCR = (1<<WDTOE) | (1<<WDE) | a;
#endif
}

void wdt_disable (void)
{
   __watchdog_reset ();

#ifdef __IOTINY13_H
   WDTCR = (1<<WDCE) | (1<<WDE);
#endif

#ifdef __IOM8535_H
   WDTCR = (1<<WDCE) | (1<<WDE);
#endif

#ifdef __IOM16_H
   WDTCR = (1<<WDTOE) | (1<<WDE);
#endif

#ifdef __IOM32_H
   WDTCR = (1<<WDTOE) | (1<<WDE);
#endif

   WDTCR = 0x00;
}
//----------

//----------
void sleep_mode_init (void)
{
   MCUCR |= (1<<SE);
}

void sleep_mode_enable (void)
{
   asm("SLEEP");
   asm("NOP");
}
//==================

//==================
void empty_func (void)
{
}
//==================

//==================
extern void sram_copy (u08 *s_1, u08 *s_2, u08 n)
{
   while (n--)
   {
      *s_1++ = *s_2++;
   }
}
//==================

//==================
void proc_fsm_func (FUNC __flash *ptr_func, u08 s)
{
   ptr_func [s] ();
}
//==================


Пример реакции на нажатую кнопку
Спойлер

Код: Выделить всё

//==================
#include "proc_device.h"
//==================

//==================
void proc_device (void)
{
   static u08 _proc_device;
   static soft_timer ST_PROC_DEVICE;

   switch (_proc_device)
   {
      case 0:
         OUTPUTS_DDR |= ((1<<OUT_1) | (1<<OUT_2) | (1<<OUT_3));
         _proc_device = 1;
         break;

      case 1:
         if (Get_Event (EV_ID_KEY_PRESSED))
         {
            set_soft_timer (ST_PROC_DEVICE, TIME_1); // 5 секунд.
            _proc_device = 2;
         }
         break;

      case 2:
         if (handle_soft_timer (ST_PROC_DEVICE))
         {
            out_1_on ();
            set_soft_timer (ST_PROC_DEVICE, TIME_2); // 30 секунд.
            _proc_device = 3;
         }
         break;


Цикл статей Татарчевского

Мой подход:
Модульность.
Максимальная инкапсуляция.
Активное использование конечных автоматов.
Все процессы дробятся. Условиями, флагами, состояниями конечных автоматов.
Исключение долгих циклов. Итерация основного цикла должна с запасом выполняться за системный тик (скажем, 1 мс). Так как используются программные таймеры.

Такой подход в разы сокращает время на создание проекта. Проекты собираются как конструктор из модулей.

Проект для поиграться. В папке есть файл для AVR-Studio. Запустите симулятор, посмотрите как работает программа.
Вложения
SOFT_START.rar
(294.45 КБ) 125 скачиваний
Аватара пользователя
ARV
Ум, честь и совесть. И скромность.
Сообщения: 18544
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение ARV »

Demiurg писал(а):Понаписали херню всякую...
ваш подход - самое то для начинающего. особенно, ему будет наверняка приятно увидеть, что решение его задачи для одной кнопки и одной лампочки требует всего-лишь 9 файлов исходных текстов... я понимаю, вы наверняка сдерживались... могли же и 18 файлов предложить... :)))
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Demiurg
Это не хвост, это антенна
Сообщения: 1480
Зарегистрирован: Ср июн 25, 2008 15:19:44
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение Demiurg »

Я показал подход. И дал проект для примера. Чтобы начинающие сразу приучались к модульность как минимум.
Так же я написал, что при моем подходе проекты собираются как конструктор. Если мне потребуется зажечь одну лампочку от одной кнопки, то минимальный набор файлов будет именно такой. Я давно уже пережил период экономии байтиков. Время для меня дороже. Основная программа в main, если программа занимает меньше экрана монитора. В противном случае в шаблоне proc_device.c и proc_device.h. Файлы-модули-кубики.
NStorm
Поставщик валерьянки для Кота
Сообщения: 1978
Зарегистрирован: Ср июл 17, 2013 13:55:57

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение NStorm »

))))) А мне тут еще написали, что рано ТС в указатели. Хотя я расписал азы по ним максимально доступно, несмотря на то, что это уже сто раз везде описано )
Из крайности в крайность. Demiurg, бросьте. ТС на его уровне и близко не поймет подобное пока что. Да и рано это всё.
Demiurg
Это не хвост, это антенна
Сообщения: 1480
Зарегистрирован: Ср июн 25, 2008 15:19:44
Контактная информация:

Re: включение нагрузки 1 кнопкой без фиксации, на МК

Сообщение Demiurg »

Я одну вещь вам скажу. Очень сожалею, что статьи Татарчевского мне не попались раньше, когда я сам начинал. И считаю, что новичкам нужно практически сразу и с него начинать. Проблема начинающих однотипна. На всех известных мне форумах по микроконтроллерам.
Ответить

Вернуться в «AVR»