Цифровой датчик Холла

Piorun
Offline
Зарегистрирован: 02.04.2012

Хочу через матлаб управлять вентилятором (при помощи цапа), который будет дуть на другой вентилятор и разгонять его. Со второго винта снимать частоту. Меняя напряжение на 1 винте добиться требуемых оборотов на 2м.

Вопрос - по какому протоколу работает датчик холла? OneWire? или что то другое, может есть библиотеки? Не хотелось бы мудрить с аналоговым.

maksim
Offline
Зарегистрирован: 12.02.2012

Ни по какому. Практически все продаваемые датчики Холла цифровые, имеют 3 провода, два из них питание и сигнальный. В двух словах большинство работает так: когда магнитное поле отсутствует на сигнальном проводе напряжение питания (т.е. логическая 1), когда датчик находится в магнитном поле на сигнальном проводе 0 вольт (т.е. логический 0). Вы какой датчик холла используете? Каким напряжением питаете датчик? Вот этот код проверяли на датчике Холла (5В) и на ИК-датчике:

#define RPMpin 2 // датчик Холла
long microsold = 0;
int rpm = 0;

void setup() { 
  Serial.begin(9600);
  digitalWrite(RPMpin, 1);
  attachInterrupt(0, RPM, FALLING);
}

void loop() {
  Serial.println(rpm, DEC); // об/мин
}
void RPM (){
  rpm = (1000000.0/(micros() - microsold))*60;
  microsold = micros();
}

 

Piorun
Offline
Зарегистрирован: 02.04.2012

Еще даже не купил датчик, пока только решаем все тонкости проекта.

maksim
Offline
Зарегистрирован: 12.02.2012

Ну если датчик покупной, то при правильной установке и подключении этот код должен работать.

maksim
Offline
Зарегистрирован: 12.02.2012

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

Piorun
Offline
Зарегистрирован: 02.04.2012

Спасибо за подсказку!

katafey
Offline
Зарегистрирован: 19.09.2012

Делается на лодку прибамбас который будет отображать на семисегментнике(4разряда), температуру, обороты двигателя итд.итп.

Лодочный двигатель Вихрь30. 

Собственно сейчас ковыряюсь с тахометром, датчик холла будет стоять под маховиком на котором есть 4 магнита в каком порядке они стоят видно на фото макета .

Решил использовать ваш скетч для тахометра и здесь появились вопросы

1. После запуска моторчика обороты показывает но после полной остановки не становится в ноль (956)

2. оч большой разброс в оборотах при стабильном питании 1.5V  моторчика

 

Данные из монитора:
2797
2698
2698
2698
2698
2797
2797
2797
2696
2696
2696
2696
2789
2789
2789
2685
2685
2685
2685
2725
2725
2725
2725
2454   Питание отключено но еще вертится
2454
2454
2454
2315
2315
2315
2315
2315
1998
1998
1998
1998
1998
1781
1781
1781
1781
1781
1781
1781
1399
1399
1399
1399
1399
1399
1399
1399
1399
1399
956
956
956
956   Мотор остановлен
 
Фото макетки 

зы, скетч юзал не изменяя, деление не делал, все как есть

maksim
Offline
Зарегистрирован: 12.02.2012

1. Это нормально, потому как нужно дописывать сброс оборотов в 0 при отсутствии импульсов.

2. Питание может и стабильно, а вот обороты двигателя могу и скакать + к этому при таком способе измерения оборотов очень важно равномерное расположение магнитов на валу относительно друг друга и оси вращения, небольшое биение вала приведет к неверным данным. Нужно дописывать сглаживание или применять другой способ подсчета оборотов.

А датчик холла у вас сработывает одинакого что на S что на N ? или только на один полюс? Данный расчет предназначен для одного срабатывания датчика за один оборот вала.

katafey
Offline
Зарегистрирован: 19.09.2012

Спасибо за ответ.

Датчик срабатывает так: северный полюс - имеем на выходе лог1, южный лог0 и так далее. В итоге rpm нужно будет поделить на 4 если не ошибаюсь.

Piorun
Offline
Зарегистрирован: 02.04.2012

Делай прерывания по времени, думаю так проще.

#include <AFMotor.h>
#include <MsTimer2.h>  //библиотека таймера - гугли ее
AF_DCMotor motor(3);
char inChar;
unsigned int state=0;
unsigned int rmp=0;
unsigned int time=0;
int mspeed=255;
unsigned long timpause=5000;

void flash() //обработка прерывания таймера 
{              //будет вызыватся по истечении времени указанного в timpause
  MsTimer2::stop(); //остановить таймер
  detachInterrupt(1); //остановить прерывания
  Serial.print("Speed: ");
  Serial.print(mspeed);
  Serial.print("\t");
  Serial.print("rmp:");
  Serial.println(rmp*12);
  rmp=0;
  MsTimer2::set(timpause, flash);  //установка таймера
  attachInterrupt(1, blink, RISING ); //вкл прерывания
  MsTimer2::start();//вкл таймера
 
}

void setup() {
Serial.begin(9600);
attachInterrupt(1, blink, RISING ); //прерывания на 3м порту - с датчика холла
  motor.setSpeed(mspeed);
  motor.run(RELEASE);
  motor.run(FORWARD);
  delay(500);
  MsTimer2::set(5000, flash); // 5 сек
  MsTimer2::start(); //включаем прерывания по таймеру
}

void loop() {
  if (Serial.available()){
     inChar = (char)Serial.read(); 
  if (inChar=='1')
    mspeed+=10;
  else
    mspeed-=10;
    
 motor.setSpeed(mspeed);
 
  }

}

void blink()  //прерывания с датчика холла
{
  ++rmp;  //увеличиваем счетчик на 1
}

 

leshak
Offline
Зарегистрирован: 29.09.2011

Piorun пишет:

Делай прерывания по времени, думаю так проще.

Не проще. Разве если нужна точность до "микросекунд" (но на интервалах в 5 сек, это вряд ли).

Имхо намного проще в loop сделать так:

void loop(){
  static unsigned long lastReportTime=0;
  if(millis()-lastReportTime>=5000){ // прошло 5 sec с прошлого отчета
      lastReportTime=millis();
      Serial.print("Speed: "); Serial.print(mspeed);
      Serial.print("\trmp:"); Serial.println(rmp*12);
       rmp=0;
  }
  
  // тут логика чтения serial из предыдущего примера и установка скорости мотора
}

И еще, желательно не забывать про ключевое слово volatile при объявлении переменных меняющихся в обработчике прерываний (  rpm  )

leshak
Offline
Зарегистрирован: 29.09.2011

А можно сочинить  такую "помогалку дефайн" (разместить где-то в начале скетча):

#define EVERY_var(time,var,action) { static unsigned long var=0;  if(millis()-var>=time){var=millis(); action}}  
#define EVERY(time,action) EVERY_var(time,lastActionTime__LINE__,action) // выполняет действие action каждые time миллисекунд (если регулярно ее вызывать)

Тогда в Loop() все будет вообще выглядить "чисто и красиво"

 

void loop(){

 EVERY(5000, // выполняется каждые 5 сек.
      Serial.print("Speed: "); Serial.print(mspeed);
      Serial.print("\trmp:"); Serial.println(rmp*12);

  );

 EVERY(10000, 
    // тут еще что-то делаем, каждые 10 sec

  );


}

 

 

Piorun
Offline
Зарегистрирован: 02.04.2012

И в каждом цикле тратим время

Сперва на millis()

Затем вычитание millis()-lastReportTime

Ну и сравнение конечно millis()-lastReportTime>=5000

Только после чего совершается пропуск ненужного фрагмента.
Выполнение этого кода еще все время обрывается внешними прерываниями...

Ну и конечно нельзя забывать про очень быстрый рост размера переменной lastReportTime  - Это же милисекунды как ни как!

Запустить на 10 минут или на пару часов - есть разница?

Плюс, за все это время прилетит еще парочку прерываний - точность тоже никчерту.

leshak
Offline
Зарегистрирован: 29.09.2011

Piorun пишет:

И в каждом цикле тратим время

Сперва на millis()

Затем вычитание millis()-lastReportTime

Ну и сравнение конечно millis()-lastReportTime>=5000

Только после чего совершается пропуск ненужного фрагмента.

Ой. Не смешите меня. Это микросекунды (даже не мили). 5 sec времени, по сравнению с этим - вечность. Ну какая разница прийдет отчет в ровно через 5 sec, или 5.000005 секунды? Сколько там лишнего успеет накопить RPM за это время? В процентах от его текущего значения это сколько будет? Комаринный писк?

Да по сравнению с тем что занимает вывод в сериал это вообще ничто (ой, а мы его еще постоянно проверяем, нестрашно что на это тоже тратится время?)

Да и вообще, мы что платим деньги за каждый такт процессора? Он же у нас, один черт ничем не занимается больше. Чем крутить пустой loop будет лучше?

Что лучше, экономить такты процессора, которые, вданном случае не нужны, или подрубасить либу, потратить дефецитную память, заюзать дефецитный ресурс "таймер" (который может пригодится для задачь которые действительно требуют "точности")? Ну а как вам нужно будет 5-ть "псевдо-одновременных" действий? Дополнительные контроллеры начнете ставить?

Кстати вход/выход в обработчик таймера - тоже не бесплатная операция. нужно запомнить "текущие состояние проца", потом востановить. Отработать все ваши stop/start. В итоге еще не известно кто больше тиков rpm пропустит.

Piorun пишет:

Выполнение этого кода еще все время обрывается внешними прерываниями...

И что с того? Для того прерывания и предуманы. Негативных эффектов от этого - нет.

Piorun пишет:

Ну и конечно нельзя забывать про очень быстрый рост размера переменной lastReportTime  - Это же милисекунды как ни как!

А взять калькулятор и посчитать сколько влезет в unsigned long? На 49-дней хватит. Более того, даже после этого - ничего страшного не случится :) Код нормально отработает и в случае переполнения. 

Вообщем опять пришили: и что с того? лично у меня нет страха перед "большими числами".

Piorun пишет:

Запустить на 10 минут или на пару часов - есть разница?

Смотря что запускать. Если сердечника в финскую сауну - есть,  если деньги на ветер - то нет.

Piorun пишет:

Плюс, за все это время прилетит еще парочку прерываний - точность тоже никчерту.

И опять "и что с того"? По сравнению с тем что мы насчитали в rpm за 5 sec - это фигня. Между прочим операции с делением это тоже "не точная" штука :)

Кстати, вы почему-то упустили что в вашем коде вы тоже на время отчета делаете detachInterrupt. 

Вообщем заявить "точность ни к черту" - это круто. А вот цифрами показать что ваш код на два порядка более точный сможете?

IMHO он нифига не точнее, более ресурсоемкий, менее читабельный.

Если уж очень сильно озаботились точностью, то нужно брать в зубы даташит на ATMEGA  (или искать другие библиотеки работы с таймером, где это реализовано) и искать как настроить таймер и компаратор, что-бы он сам считал/увеличивал rpm. Без обработчика прерыаний, не отвлекая процессор даже на это, на железном уровне (ой... а ATMEGA даже такое умеет), так же как он не отвлекает камень на генерацию PWM.

Но обычно, на это заморачиватся имеет смысл только если речь идет о необходимой точности в микросекунды (не милли).

leshak
Offline
Зарегистрирован: 29.09.2011

 

leshak пишет:

 заюзать дефецитный ресурс "таймер" (который может пригодится для задачь которые 

Кстати, похоже долго ждать этого не нужно AFMotor тоже использует таймеры. Причем разные, в зависимости от того под какую плату компилим. Так что можно поймать очень классную головную боль. Из серии почему на mega/uno по разному себя ведет или вообще не работает.

Да и подключение любых других Lib - тоже становится "ходьбой по минному полю" (Tone и т.п.) нужно внимательно следить какие таймеры уже заняты.

leshak
Offline
Зарегистрирован: 29.09.2011

 

katafey пишет:

1. После запуска моторчика обороты показывает но после полной остановки не становится в ноль (956)

Логично. rpm у вас пересчитывается только в момент сработки датчика. А при остановленном движке - сработок нет. Функция RPM() - не вызывается.

Поэтому нежно в loop(), поверять если с последней сработки прошло слишком много времени - насильно обнулять rpm

void loop(){
...
if(micros() - microsold>КАКОГО_ТО_ЗНАЧЕНИЯ)rpm=0;
...
}

и да... microsold лучше, от греха объявить так:

volatile unsigned long microsold = 0;

а не просто long.

katafey пишет:

2. оч большой разброс в оборотах при стабильном питании 1.5V  моторчика

Можете немного "усреднить". Заведите дополнительную переменную counter. И в RPM() - увеличивайте ее. И только когда она накопит, например 20-ть сработок, обнуляем ее и пересчитываем rpm переменную (естественно учтя что у нас теперь замеряно время не между каждой сработкой, а межку каждой 20-той сработкой).

offtop: заметил на фото "полиморфус". Реально круто/удобная штука ;) для подобных игр.

katafey
Offline
Зарегистрирован: 19.09.2012

Извиняюсь, что пропал. Неожиданно в больницу загремел.

Всем спасибо за советы, нарыл темку и сварганилскопипастил :) тахометр из нее http://robocraft.ru/blog/electronics/594.html 

по тестам работает норм вот только сравнить бы с каким нибудь эталоном.

вот что вышло в предварительном варианте

/* На лодку
Функции:
вывод данных о температуре "Блок температуры" занят пин МК А0
к пину А1 подключен фоторезистор для динамической регулировки яркости дисплея
TO DO: Пин А5 подключен к датчику холла для подсчета оборотов двигателя и отображения на дисплее "Блок тахометра"
Пин 2 кнопка переключения режимов, температура или тахометр

TODO: пин 13 ардуины использовать для индикации чтения данных
7й сегмент индикатора используется обычными светодиодами для индикации режима работы
*/

//We always have to include the library
#include "LedControl.h"

/*
 Now we need a LedControl to work with.
 ***** These pin numbers will probably not work with your hardware *****
 pin 12 is connected to the DataIn (18 нога МК) 
 pin 11 is connected to the CLK (17 нога МК)
 pin 10 is connected to LOAD (16 нога МК)
 We have only a single MAX72XX.
 */
LedControl lc=LedControl(12,11,10,1);

/* we always wait a bit between updates of the display */

const int analogInPin = A1;
int sensorValue = 0;
int outputValue = 0;        //   можно убрать      // value output to the PWM (analog out)
int sw1 = 3; // номер пина ардуины для кнопки
int sw1_state = 1; // статус и счетчик нажатий кнопки SW1
double Temp;  // Переменная температуры

/* Пины, к которым подключен энкодер */
enum { ENC_PIN1 = 2, ENC_PIN2 = A5 };

enum { FORWARD = 1, BACKWARD = -1 };

/* Если что, revolutions здесь и далее - обороты, а не революции (: */
long revolutions = 0, revolutions_at_last_display = 0;
int direction = FORWARD;
uint8_t previous_code = 0;

/* Реакция на событие поворота */
void turned(int new_direction)
{
  if (new_direction != direction)
  {
    revolutions = 0;
    revolutions_at_last_display = 0;
  }
  else
    ++revolutions;

  direction = new_direction;
}

/* Объеденил чтение кода Грея с энкодера с его декодированием */
uint8_t readEncoder(uint8_t pin1, uint8_t pin2)
{
  uint8_t gray_code = digitalRead(pin1) | (digitalRead(pin2) << 1), result = 0;

  for (result = 0; gray_code; gray_code >>= 1)
    result ^= gray_code;

  return result;
}


void setup() {
  pinMode(sw1, INPUT); // установка пина(3) на чтение (кнопка)
  digitalWrite(sw1, HIGH); //установка лог 1 пине 3
  /*
   The MAX72XX is in power-saving mode on startup,
   we have to do a wakeup call
   Отключение дисплея
   */
  lc.shutdown(0,false);
  /* Set the brightness to a medium values
   Яркость дисплея 0-15 */
  lc.setIntensity(0,0);
  /* and clear the display
   Очистка дисплея */
  lc.clearDisplay(0);

  Serial.begin(9600); // убрать в окончательном варианте
  pinMode(13, OUTPUT);
  
  pinMode(ENC_PIN1, INPUT);
  pinMode(ENC_PIN2, INPUT);
}

// Преобразование температуры от датчика в нормальный вид
double Thermister(int RawADC) {
  //double Temp;
  // See <a data-cke-saved-href="http://en.wikipedia.org/wiki/Thermistor" href="http://en.wikipedia.org/wiki/Thermistor" rel="nofollow">http://en.wikipedia.org/wiki/Thermistor</a> for explanation of formula
  Temp = log(((10240000/RawADC) - 10000));
  Temp = 1 / (0.001129148 + (0.000234125 * Temp) + (0.0000000876741 * Temp * Temp * Temp));
  Temp = Temp - 273.15;           // Convert Kelvin to Celcius
  return Temp;
}
//============================================================================================


void loop() { 
  
  /*-------------------
  Блока обработки нажатия кнопки
  --------------------*/  
    if(sw1_state==3) // если счетчик достиг переполнения
  {
    sw1_state=0; // сброс счетчика в 0
  }
  if(!digitalRead(sw1)) // чтение состояния кнопки
  {
    delay(10); //задержка для устрания дребезга
    if(!digitalRead(sw1))// продолжаем если все нормально
    {
        sw1_state++;  //увеличиваем на единицу
        lc.clearDisplay(0);       // очистка дисплея
        do{
        }while(!digitalRead(sw1));
    }
  }
  
  /*-------------------
  Конец блока обработки нажатия кнопки
  --------------------*/
  
  
  
    //digitalWrite(13, HIGH);
    
    /*Фоторезистор*/
    sensorValue = analogRead(analogInPin);                //Читаем показания датчика фоторезистора
    lc.setIntensity(0,map(sensorValue, 0, 1023, 0, 15));  //Устанавилваем яркость дисплея, чем светлее на улице тем ярче дисплей
    /*Конец фоторезистора*/
    
    
    
    
    if(sw1_state==0){ // оператор выбора состояния кнопки и запуск соответствующего блока
    //
    //-----------------------------
    // Блок температуры
    //-----------------------------
    double temp = Thermister(analogRead(0));              // Читпем данные с термодатчика
    int temp1 = temp;
    
    if (temp1>95) // == Превышение температуры 95 градусов - пишем HELP и мигаем дисплеем
                  // до тех пор пока температура не установится меньше 95 градусов
    {
      lc.shutdown(0,true);      // отключаем дисплей
      lc.setRow(0,0,B00110111); // symbol H
      lc.setRow(0,1,B01001111); // symbol E
      lc.setRow(0,2,B00001110); // symbol L
      lc.setRow(0,3,B01100111); // symbol P
      lc.setRow(0,7,B01110000); // также мигаем светодиодами режима работы
      delay(500);               // задержка полсекунды
      lc.shutdown(0,false);     // Включаем дисплей
      delay(500);               // задержка полсекунды
      lc.clearDisplay(0);       // очистка дисплея
    }else{                      // Температура в номе? Да, отображаем ее на дисплее
    lc.setDigit(0,0,(temp1/10),false);             // В сегмент 1 десятки
    lc.setDigit(0,1,(temp1-((temp1/10)*10)),true); // В сегмент 2 единицы + зажигаем десятичную точку
    lc.setDigit(0,2,((temp-temp1)*10),false);      // В сегмент 3 деситичную долю
    lc.setRow(0,3,B01000011);                      // в сегмент 4 выводим символ температу "с"
    lc.setRow(0,7,B01000000);                      // в Сегмент 7 зажечь 3 светодиода ABC (тест)
    Serial.println(temp1);
    delay(300); // Задержка в полсекунды
    //digitalWrite(13, LOW);
    delay(300); // Задержка в полсекунды
    }
    /*---------------------------
    Конец блока температуры
    -----------------------------*/
    }


    if(sw1_state==1){ // если состояние 1 то выполнение блока тахометра
    
    
    /*----------------------------
    Блок тахометра
    -----------------------------*/
    //lc.clearDisplay(0);       // очистка дисплея
    lc.setRow(0,7,B00100000);
      /* Читаем значение с энкодера */
    uint8_t code = readEncoder(ENC_PIN1, ENC_PIN2);

    /* Обрабатываем его */
    if (code == 0)
    {
      if (previous_code == 3)
        turned(FORWARD);
      else if (previous_code == 1)
        turned(BACKWARD);
    }

    previous_code = code;

    /* Раз в секунду выводим накопленную информацию */

    static unsigned long millis_at_last_display = 0;

    if (millis() - millis_at_last_display >= 1000)
    {
      /* ... скорость вращения в оборотах в секунду */
      Serial.print(revolutions - revolutions_at_last_display);Serial.print(" <<SEC ---- MINUTE >>> ");Serial.println(((revolutions - revolutions_at_last_display)*60)/2);
      int RPM = ((revolutions - revolutions_at_last_display)*60)/4;
      lc.setDigit(0,0,(((RPM-(RPM%1000))%10000)/1000),false);             // В сегмент 1, тысячи
      lc.setDigit(0,1,(((RPM-(RPM%100))%1000)/100),false);             // В сегмент 2, сотни
      lc.setDigit(0,2,(((RPM-(RPM%10))%100)/10),false);             // В сегмент 3, десятки
      lc.setDigit(0,3,(RPM%10),false);             // В сегмент 1, тысячи
      
      Serial.println(RPM%10);
      Serial.println (((RPM-(RPM%10))%100)/10);
      Serial.println (((RPM-(RPM%100))%1000)/100);
      Serial.println (((RPM-(RPM%1000))%10000)/1000);
      millis_at_last_display = millis();
      revolutions_at_last_display = revolutions;
  }
    
    
    
    /*----------------------------
    Конец блока тахометра
    -----------------------------*/
    }
    
    
    if(sw1_state==2){  // если состояние 2 то выполнение блока резерва
    /*----------------------------
    Резервный Блок 
    -----------------------------*/
    //lc.clearDisplay(0);       // очистка дисплея
    lc.setRow(0,7,B00010000);
    /*----------------------------
    Конец резервного блока
    -----------------------------*/
    }
    

}

PS: Да Вы правы полиморфус классная штука не жалею что купил :)

katafey
Offline
Зарегистрирован: 19.09.2012

как можно удалять свои сообщения? Дубляж вышел