Пульт для пульта - нажиматель кнопок на сервомашинке

Тема в разделе "Глядите, что я сделал", создана пользователем Onkel, 5 янв 2016.

  1. Onkel

    Onkel Гуру

    pourquoi pas?

    Первый, даже нулевой и вынесенный за скобки вопрос – зачем лентяйке лентяйка? Поясню. Во- первых, это даже не «прибор выходного дня», а «прибор свободного часа», а во вторых – вы уехали все отдыхать – лентяйка лентяек (или благородно RemoteofRemotes) вместе с просто лентяйкой могут включать и выключать что-либо (тв, радио, двд, люстру), имеющее пульт. Ну и сугубо личное – у меня в год уходит больше двух килобаксов на отопление – и солярка ныне дороже 95го, и у газпрома на нас, живущих в 30 км от МКАДа газа не хватает, но есть специфика – ночной тариф, в три раза дешевле дневного. Поскольку я, как типичный эмбеддер[1] / свободный агент, работаю до 3 ночи, а сплю потом до 10 утра, график «ночного тарифа» (с 23 до 7) меня совершенно не устраивает, но победить наследников Чуйбаса[2] я не могу. Ну тут как с горой- не идет постчубайская вертикаль навстречу – пойду я. Вот я включаю все электронагреватели в 11 вечера, а лентяйка лентяек (далее Л2) выключате в 7 утра. Выключаю кнопкой «выключить все». Модули эти уже давно стоят, они просто втыкаются в розетку, держат достаточно ампер и прошиваются в пульт персонально. К Л2 можно добавить второй палец, удвоив код (но с другим портом) – и тогда можно будет не только выключать все, но и включать, а затем выключать. С вопросом «Кто виноват» у нас давно ясно, теперь к вопросу «что делать?».

    Конструкция
    Я взял ардуину (совместимую) нано v.3 на контроллере Atmega328p(они вроде все на этом чипе, но укажем для ясности), и самую дешевую сервомашинку с не самым простым названием «TowerPro SG90 Mini Gear Micro Servo 9g», запитал все аккумулятором 6 В 12 А*ч[3]. Вот оно как, без аккумулятора, что его фотографировать[4] :

    [​IMG]

    Рисунок 1 Л2. Пояснения в тексте.

    Питание на Vin, выход стабилизатора платы стабилизирован элеткролитом 3300 мкФ. Теперь по деталям – выход сервы подключен к порту B1. Cервомашинки работают так- на управляющий вход нужно послать импульст шириной от 1 до 2 (зависит от конкретной машинки) мс, и она соответсвенно встает в -90° или +90°. Сервомашинку можно взять самую простую и дешевую, но обязательно рабочую. Сервомашинка смонтироана пластиковым креплением швеллеру из детского конструктора. Между швеллером и сервомашинкой впихивается пульт таким образом, чтобы рычажек сервомашинки (идет в комплекте) нажимал нужную кнопку. Чтобы моточик сервомашинки не вызывал ресет микроконтроллера из-за падения напряжения на стабилизаторе (он дохловат, если честно), к выходу +5 В, он же Vccподключен электролит 3300 на 16 В. Больше не рекомендуем, посадит платный (в смесле установленный на плате) стабилизатор +5 В, ну или тогда надо его включать через цепь диод/резистор, чтобы он умел быстро разряжаться, но заряжался умеренным током, без садизма. Из деталек еще отмечу 6 канальный dip выключатель для выбора времени задержки выключения. Смонтирована плата микроконтроллера и вспомогательные детальки на макетке сколько-то –там – не помню – сколько дырок, взял маленькую. Печатку делать не стал, все равно буду думать более глобально – электричество обещают поднять в цене до уровня 7 руб/кВт*ч, чтобы население платило как юрлица. Засим к мягкому, т.е. к софту



    Как нам запрограммировать мк


    Исторически сложилось, что я пишу код для мк на CodeVision. Ну просто много много лет назад другое (а другое тоже достойно) не пошло, а CodeVisionпошел. Весь код давать не буду (мой реальный код – дауншифт кода для системы управления 8 сервомашинками с отчетами, обратными связями и т.д., я не стал его щипать, так и оставил,только подправив инициализацю). Для начала объявим, что порты D2-D7 будут входами, подтянутыми к +5 через внутренние резисторы, порты D0-D1 по традиции не трогаем – это USART:

    PORTD=0b11111100;

    DDRD=0;

    К портам D2-D7 подключен 6 – канальный дип свич для выбора времени задержки, и поскольку нулевая задержка мне не нужна (может кому нужна –замените 64 на 63), то определение времени задержки в секундах выглядит так (это делается один раз при инициации)

    OffTime= 3600* (64-(PIND>>2));

    Соответсвенно выбор от 1 до 64 часов задержки. Энтузиасты могут подключить свичи еще и к другим свободным портам и сделать задержку любого диапазона.

    Порт B0 (он собственно и генерит импульс управления сервомашинкой) – выход с начальным состоянием 0:

    DDRB|=0x01;

    PORTB&=~0x01;

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


    ASSR=0x00; // синхронизация - кварц

    TCCR2A=0x02; //прерывание по совпадению OCR2A

    TCCR2B=0x06; // Коэффициент предделителя =256

    TCNT2=0x00; // Сброс счетчика таймера

    OCR2A=0xAA;//это все равно поставим как надо в коде

    OCR2B=0x00;//это все равно поставим как надо в коде



    При наступлении нужного времени мы запускаем сервомашинку, начиная импульс:

    {

    TCNT2=0; // Cбрасываемсчетчиктаймера

    OCR2A= Angle; // выставляемвремяимпульса

    PORTB.0 = 0x01;// Импульспошел

    GTCCR|=0x02;// сбрасываем предделитель таймера. ОЧЕНЬ ВАЖНО!!!

    TIFR2|=0x02; // установка прерывания по совпадению OCR2A

    TIMSK2=0x02; // разрешить прерывание по совпадению OCR2A

    }

    Народ, или, как говорят пухлолицые топ-менеджеры федеральных каналов, пипл часто жалуется на дрожание сервомашинок, борется , борется и сдается. Так вот сдаваться не надо, а надо сбрасывать предделитель командой CTCCR|=0x02. Поясню – при запуске таймера в предделителе может быть любое число от 0 до k-1 (k- коэффициент деления предделителя), cсоответственно сервомашинка и будет дрожать с амплитудой единицы младшего разряда.Т.е. если у вас OCR2A=10, то дрожание будет в пределах от 9 до 10, что составляет 10%.

    Закончить импульс проще, чем начать

    interrupt [TIM2_COMPA] void timer2_compa_isr(void)

    {

    PORTB.0 = 0x00;

    TIMSK2=0x00; // Disabel Timer2 Interruptions

    }

    После команды TIMSK2=0x00; // DisabelTimer2 Interruptionsфункция обработки прерывания по таймеру 2 не будет вызываться, пока мы снова не разрешим это прерывание командой TIMSK2 = 0x02;// Go!.

    Ну и желательно посматривать, чтобы кто- то не влез с прерыванием по OCR2A, в свободное (а это 99.99%) от нажатия кнопок время можно использовать таймер 2, запретив прерывание по OCR2A.И следить, чтобы разные функции не пытались одновременно использовать прерывание по таймеру 2. И тогда у вас сервомашинка будет работать как часы!Но эти советы – в общем-то, для начинающих. Если вы умеете использовать одно прерывание для управления парой дюжин сервомашинок, сеткой 1wire, да еще и софтовый uart на нем гонять – то вам мои заметки и читать не надо, будет только вред.


    [1] An embedded system is a special-purpose system in which the computer is completely encapsulated by the device it controls. Однако если вы встроили болт в унитаз – вы еще не эмбеддер.

    [2] Чубайс – скорее уже жупел, нежели реальный исторический персонаж. Поскольку отношения читателей к нему могут быть диаметрально противоположны, ничего я вам про него не скажу.

    [3] Реально напряжение аккумулятора 6.9 – 7.2 В.

    [4] Согласно современной тенденции в любой книге по микроконтроллерам более половины рисунков служат лишь цели заполнения объема, приведу характерные примеры – фотографии пассатиж, паяльника, банки с хлорным железом (на фото не видно, что это хлорное железо, не говоря уж о том, что непонятно – это шестиводное или обезвоженное хлорное железо, а это две большие разницы – всегда берите шестиводное и ни в коем случае не обезвоженное) и как апофеоз – фотография ножниц (да-да , обычных офисных ножниц) на полстраницы. Мы не будем придерживаться современных тенденций, поскольку гнаться за модой – не наше.
     
    Толик Иванов, ИгорьК и padre нравится это.
  2. Onkel

    Onkel Гуру

    пы сы не успел обнародовать (20150105)- вчера (20150107) по "Эхо Москвы"- у Баблаян "Корейские старт-аперы запустили кик-старт по автономному нажимателю кнопок", отличается от моего немногим- у него еще bluеtooth, но нет переключателя - таймера.
    Кто меня сдал корейцам?
     
    ИгорьК нравится это.
  3. ИгорьК

    ИгорьК Гуру

    Корейцы внимательно читают этот форум. Да и вообще все такого рода форумы.
     
    Onkel нравится это.
  4. ИгорьК

    ИгорьК Гуру

    Подскажите, пульт, который нажимается, ИК? Не лучше ли было сграбить сигнал и стрелять с ИК диода?
     
  5. Onkel

    Onkel Гуру

    нет, радио. У меня по дому расставлены нагреватели через управляемые по радио розетки, этой системе уже лет 15, все руки не доходили протянуть нормальные провода до них и поставить управляемые от системы. Ну собсвенно я уже писал - нагреватели выключаю в 7 утра, когда тариф вырастает в 3 раза. Но, поскольку наследники чуйбаса обещают выровнять тарифы чл и фл (сейчас в области 7 руб с копейками за квт*ч), конечно систему более четкого реагирования сделаю, чтобы и нагреватели управлялись, и клапана на батареи поставлю (сейчас на батареях регуляторы с биметаллом), под это дело были куплены радиомодули sim20 a, модули уже включены в розетки и показывают температуру по комнатам, осталось поставить по мосфиту для управления клапаном (который тоже нужно еще купить и врезать, ну врезать я и сам врежу) и по симистору для управления (только вкл/выкл) электронагревателем. Поднимут цену на эл-во - буду на солярке, ну и т.д.
     
  6. ИгорьК

    ИгорьК Гуру

    Радио, тоже хорошо. Видимо, 433 МГц?
    Планирую в ближайшее время освоить модуль RFM69. А за одно поразвлекаться реверс инжинирингом протоколов. В первую очередь повторить разборки noolite: http://m.habrahabr.ru/company/contactless/blog/216023/. Понятно, задействовать малину.
    Времени катастрофически не хватает.
     
  7. Onkel

    Onkel Гуру

    ноолайт ребята с contactless.ru реверснули. У них олимексино на линуксе им управляет, можете посмотреть.
     
  8. ИгорьК

    ИгорьК Гуру

    Я на их отчёт ссылку и запостил. Но для повторения надо ещё хорошо покопаться в установке RFM69 на Малину и ещё кое в чём.
    Но есть и простые протоколы. Я rc-switch https://github.com/sui77/rc-switch подменил управление одним своим устройством с пульта. Немножко удивлён, что Вы с вашим уровнем компетенций пошли по пути нажимания кнопок.
    А по ссылке отчёт контактлесс о том как они ломали протокол, и, главное, показали методику такой ломки. В НооЛайт много команд, а повторить команду одной кнопки полагаю гораздо проще - не надо вникать в протокол, записал и повторил.
    Посмотрите: https://github.com/sui77/rc-switch/wiki
     
  9. Onkel

    Onkel Гуру

    так вопрос еще того , что есть. радиомодули у меня только simcom, они только друг с другом (манчестер там и все такое) говорят, а у меня был модуль на 8 сервомашинок. Вот я на нем и слепил, до весны пусть поработает.
    По радио я возможно впрягусь, но я хочу реверснуть протокол x10, у меня полно пультов этого производителя (и протокола), и какая - то шантрапа в Рязани или Туле делает и даже продает, хотя копирайт и все такое, я бы стреманулся. Но у них модуль рф вроде самопал, посмотрю- подумаю, может кто делает. Хотя вроде бы протокол и открытый х10, да это неважно во времена интернетов и трекеров.
    А про contactless я знаю, я сам их знаю хорошо, из одной альмы матери.
     
  10. akl

    akl Гуру

    Привет! Вроде не замечал какого-то дрожжания при использовании библиотеки серво, но все равно хотелось бы понять.
    Я не понял - на каком именно этапе надо сбрасывать предделитель?
    то есть хотел бы понять применительно к библиотеке серво, может ее можно улучшить?

    вот например я делал на основе библиотеки серво ее упрощенный и немного урезанный вариант.
    в прерывании циклически перемежаются сервы - на предыдущей сигнал обнуляется, на следующей поднимается и т.д.
    Код (C++):
    ISR(TIMER1_COMPA_vect){
       if (s<0){
       TCNT1 = 0;
       s++;
       OCR1A = TCNT1+ServoTicks[s];
       if (ServoEnable[s]>0){WriteServo_on(s);}}
       else {
       WriteServo_off(s);
       s++;
       if (s<allservos){
       OCR1A = TCNT1+ServoTicks[s];
       if (ServoEnable[s]>0){WriteServo_on(s);}}
       else {
       OCR1A = TCNT1 + (uint16_t)usToTicks(REFRESH_INTERVAL)-ticks;
       s=-1;}}
    }
    вот код целиком (че он там делает точно уже не помню, как-то крутит одну из 4х серв - всего их может быть до 8ми) - делитель там настраивается один раз только в самом начале (в "сетапе")
    TCCR1B = 0b00000010; // set prescaler of 8
    то есть его надо устанавливать еще где-то в процессе? не могу понять где и зачем.
    Код (C++):

    #include <avr/io.h>
    #define F_CPU 8000000UL
    #include <avr/interrupt.h>
    #include <util/delay.h>

    #define setH(port,bit)  do{port |= (1 << bit);}while(0)
    #define setL(port,bit)  do{port &= ~(1 << bit);}while(0)
    #define readP(reg,bit)  (reg & (1 << bit))
    #define Led_port PORTB
    #define Led_reg PINB
    #define Led_bit 7

    #define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
    //millis
    #define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
    #define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
    #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
    #define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
    #define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
    #define FRACT_MAX (1000 >> 3)
    volatile unsigned long timer0_overflow_count = 0;
    volatile unsigned long timer0_millis = 0;
    static unsigned char timer0_fract = 0;
    //servos
    #define usToTicks(_us)  (( clockCyclesPerMicrosecond()* _us) / 8)
    #define ticksToUs(_ticks) (( (unsigned)_ticks * 8)/ clockCyclesPerMicrosecond() )
    #define MIN_PULSE_WIDTH  544  // the shortest pulse sent to a servo
    #define MAX_PULSE_WIDTH  2400  // the longest pulse sent to a servo
    #define DEFAULT_PULSE_WIDTH  1500  // default pulse width when servo is attached
    #define REFRESH_INTERVAL  20000  // minumim time to refresh servos in microseconds

    typedef struct pin_t
    {
       volatile uint8_t *port;
       uint8_t bit;
    }Pin;

    Pin Servo0 = {&PORTB, 1<<0};
    Pin Servo1 = {&PORTB, 1<<1};
    Pin Servo2 = {&PORTB, 1<<2};
    Pin Servo3 = {&PORTB, 1<<3};

    Pin Led = {&PORTB, 1<<7};

    Pin* ServoArray[] = {
    &Servo0,
    &Servo1,
    &Servo2,
    &Servo3
    };
    #define allservos 4 // 8 max



    void WriteServo_on(uint8_t num)
    {
      Pin* pin = ServoArray[num];
      *pin->port |= pin->bit;
    }

    void WriteServo_off(uint8_t num)
    {
      Pin* pin = ServoArray[num];
      *pin->port &= ~pin->bit;
    }



    uint8_t ServoEnable[allservos]={1,1,1,1};
    uint16_t ServoTicks[allservos]={
       usToTicks(DEFAULT_PULSE_WIDTH),
       usToTicks(DEFAULT_PULSE_WIDTH),
       usToTicks(DEFAULT_PULSE_WIDTH),
       usToTicks(DEFAULT_PULSE_WIDTH)};
    uint16_t ticks = usToTicks(DEFAULT_PULSE_WIDTH)*allservos;
    uint16_t sum;
    int8_t s = -1;

    void WriteServo_us(uint8_t num, uint16_t val){
      uint8_t se;
       if (val>=MIN_PULSE_WIDTH && val<=MAX_PULSE_WIDTH) {
      val=usToTicks(val);
       se=1;}
       else if (val>MAX_PULSE_WIDTH) {
       val=usToTicks(MAX_PULSE_WIDTH);
       se=1;}
       else if (val!=0) {
       val=usToTicks(MIN_PULSE_WIDTH);
       se=1;}
       else {
       val=usToTicks(DEFAULT_PULSE_WIDTH);
       se=0;}
       uint8_t oldSREG = SREG;
      cli();
       ServoTicks[num]=val;
       ServoEnable[num]=se;
       SREG = oldSREG;
      for(uint8_t i=0; i<allservos; i++){
       sum = sum + ServoTicks[i];}
       ticks = sum;
       sum = 0;}

    uint16_t ReadServo_us(uint8_t num){
      uint16_t ms;
      uint8_t oldSREG = SREG;
      cli();
      ms=ServoTicks[num];
      SREG = oldSREG;
      return ms;}
    unsigned long millis(){
       unsigned long m;
       uint8_t oldSREG = SREG;
       SREG &= ~(0b10000000);
       m = timer0_millis;
       SREG = oldSREG;
       return m;}


    ISR(TIMER0_OVF_vect){
       unsigned long m = timer0_millis;
       unsigned char f = timer0_fract;
       m += MILLIS_INC;
       f += FRACT_INC;
       if (f >= FRACT_MAX) {
         f -= FRACT_MAX;
         m += 1;}
       timer0_fract = f;
       timer0_millis = m;
       timer0_overflow_count++;}


    ISR(TIMER1_COMPA_vect){
       if (s<0){
       TCNT1 = 0;
       s++;
       OCR1A = TCNT1+ServoTicks[s];
       if (ServoEnable[s]>0){WriteServo_on(s);}}
       else {
       WriteServo_off(s);
       s++;
       if (s<allservos){
       OCR1A = TCNT1+ServoTicks[s];
       if (ServoEnable[s]>0){WriteServo_on(s);}}
       else {
       OCR1A = TCNT1 + (uint16_t)usToTicks(REFRESH_INTERVAL)-ticks;
       s=-1;}}
    }

    unsigned long time = 0;


    static inline uint16_t AdcRead(uint8_t analog_pin){  //ADC
      ADMUX = 0b01000000 | analog_pin; //VCC
       ADCSRA = 0b11000110; // СК/64 (125KHZ)
       while ((ADCSRA & (1 << ADIF)) == 0);
       uint16_t adcw;
       adcw= ADCL|(ADCH<<8);
      ADCSRA |= (1 << ADIF);
      return adcw;
    }


    int main(void) {



      TCCR0 = 0b00000011;
      TIMSK |= (1<<0);

      TCCR1A = 0;  // normal counting mode
      TCCR1B = 0b00000010;  // set prescaler of 8
      TCNT1 = 0;  // clear the timer count
      TIFR |= (1<<4);  // Atmega8 clear any pending interrupts;
      TIMSK |= (1<<4);  // Atmega8 enable the output compare interrupt
    //  TIFR1 |= _BV(OCF1A);  // clear any pending interrupts;
    //  TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt
      DDRB=0b10001111;
      OCR1A = TCNT1 + (uint16_t)usToTicks(REFRESH_INTERVAL);
      TIFR |= (1<<4);
      SREG |= (1<<7);

      uint8_t but1 = 0;
      uint8_t but2 = 0;
      uint16_t h = 2100;
      uint8_t d = 0;




    uint16_t adc_w;

    while (1==1) {

      adc_w = AdcRead(1);
      _delay_ms(1);



      if (h>2000) {d = 0;}
      if (h<800) {d = 1;}

      if(readP(PINB,6) && !but1){
      but1 = 1;
        if (d==0) {h=h-100;}
        else {h=h+100;}
      WriteServo_us(1,h);
      }
      if(!readP(PINB,6) && but1) {but1 = 0;}

      if(readP(PINB,5) && !but2){
      but2 = 1;
      WriteServo_us(1,0);
      }
      if(!readP(PINB,5) && but2) {but2 = 0;}

      uint8_t led = readP(Led_reg, Led_bit);
      if (millis()-time > 10) {
      time = millis();
      if (led==0){
      setH(Led_port,Led_bit);}
      else{
      setL(Led_port,Led_bit);}}

      }
     
       return 0;}
     

    в симуляторе это выглядит вот так примерно, живьем этоеще не пробовал, но как уже упоминал - с самой библиотекой серво не замечал дрожания (или просто не знаю что конкретно надо замечатьи в каких условиях оно возникает)
    [​IMG]
     
    Последнее редактирование: 13 дек 2017
  11. Onkel

    Onkel Гуру

    Сбрасывать предделитель нужно перед включением таймера на отсчет импульса управления сервомашинкой. Дрожание импульса, обусловленное остатком в предделителе, видно на осциллографе. Команда для таймера 2, если у Вас другой таймер то см. в даташите. Команда должна стоять до начала отсчета (разрешения прерывания ) перед каждым импульсом.


    GTCCR|=0x02;// сбрасываем предделитель таймера. ОЧЕНЬ ВАЖНО!!!
     
  12. akl

    akl Гуру

    а вот нашел - в CPU / SFIOR лежит нулевой бит PSR10 - Но написано что он сбрасывает предделитель и первого таймера и таймера 0. но на таймере 0 сидит миллис - это же должно нарушить его работу? видимо поэтому в библиотеке серво это не делается. :eek:

    выходит либо пересаживаться на таймер-2 либо мириться с дребезжанием? хотя оного я на живых сервах не особо замечал.
     
    Последнее редактирование: 13 дек 2017
  13. Onkel

    Onkel Гуру

    А для чего Вам нужен millis() и важна ли его точность, или точность millis() важнее чем точность импульсов на сервомашинки?
     
  14. akl

    akl Гуру

    миллис очень удобен. надо будет посмотреть что с ним станет при постоянном сбросе предделителя. если систематическая ошибка не будет накапливаться, то и пофиг, +- пара миллисекунд не принципиально.