Скорость работы программы - термометр

repeat
Offline
Зарегистрирован: 24.01.2016
#include <OneWire.h>

OneWire ds(52);

void setup(void)
{
 Serial.begin (9600);
}

void loop(void) 
{
 byte i;
 byte present = 0;
 byte type_s;
 byte data[12];
 byte addr[8];
 float celsius, fahrenheit;
 if(!ds.search(addr)) 
 {
   ds.reset_search();
   //delay(250);
   return;
 }
 
 
 ds.reset();
 ds.select(addr);
 ds.write(0x44,1);
 
 //delay(1000);
 
 present = ds.reset  ();
 ds.select(addr); 
 ds.write(0xBE); 

 
 for  (i = 0; i < 9; i++) data[i] = ds.read  ();
 

 unsigned int raw =  (data[1] << 8) | data[0];
 if  (type_s) 
 {
   raw = raw << 3;
   if(data[7] == 0x10) raw = (raw & 0xFFF0) + 12 - data[6];
 } 
 else 
 {
   byte cfg =  (data[4] & 0x60);
   if(cfg == 0x00) raw = raw << 3; 
   else if(cfg == 0x20) raw = raw << 2;
   else if(cfg == 0x40) raw = raw << 1; 
 }
 celsius =  (float)raw / 128.0;
 fahrenheit = celsius * 1.8 + 32.0;

 Serial.print  (" Temperature =");
 Serial.print  (celsius);
 Serial.print  (" C,");
 Serial.print  (fahrenheit);
 Serial.println  (" F");
} 

Это код термометра. Если результаты выводить на 4х сегментный индикатор, то заметны мигания, хотя в программе никаких принудительных задержек нет. Обнаружил, что ds.reset ds.select ds.write сильно тормозят выполнение программы, из-за чего и заметны мерцания индикатора. Может есть способ сделать код быстрее или другим способом получать данные от ds18b20?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:
Это код термометра. Если результаты выводить на 4х сегментный индикатор, то заметны мигания, хотя в программе никаких принудительных задержек нет. Обнаружил, что ds.reset ds.select ds.write сильно тормозят выполнение программы, из-за чего и заметны мерцания индикатора. Может есть способ сделать код быстрее или другим способом получать данные от ds18b20?

По даташиту, ds18b20 в максимальном разрешении складывает температуру до 750 миллисекунд и ничего с этим не сделаешь.

Только вот чего я не понял, так это каким боком сюда притянут индикатор и как он зависит от скорости измерения температуры. Он не должен от неё зависеть, а если зависит - перепишеите выводл на него.

rekrut007
Offline
Зарегистрирован: 19.10.2014

Если будете использовать один датчик и фаренгейты не нужны, то вместо строк 18-54 можно использовать следующий

ds.reset(); 
ds.write(0xCC);                                  
ds.write(0x44);                                   
delay(50);                  // если питание паразитное (750)
ds.reset();
ds.write(0xCC);                                 
ds.write(0xBE);                                  
data[0] = ds.read();                              
data[1] = ds.read();                              
celsius = ((data[1] << 8) | data[0])/16.0;   

Если результаты выводите на индикатор, то после отладки уберите вывод данных в серийный порт.

Также на индикатор можно выводить данные в том случае, если они изменились.

repeat
Offline
Зарегистрирован: 24.01.2016

У меня динамическая индикация и если в программе к примеру поставить задержку 100 мс, то будут видны мерцания. Чтобы получить показатель термометра используются функции ds.reset ds.write ds.select, которые выполняются довольно долго, вот отсюда и задержка программы. Просто видел видео где на ардуино с помощью ds18b20 делают на 4х сегментном индикаторе термометр и мерцаний не видно. Думаю что нужно как то быстрее извлечь данные из ds18b20 чтобы добится желаемого результата. Вот только как?

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

строка 30 в приведённом коде нужна для работы датчика
но этот простой можно убрать применением millis()
раз в секунду вызывать блок кода
{ опросить датчик , т.е считать блокнот;
   послать запрос на новое измерение;
}
...тогда ардуине не надо будет ждать данные от датчика
и эту секунду она сможет выполнять другие задачи

rekrut007
Offline
Зарегистрирован: 19.10.2014

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

NeiroN
NeiroN аватар
Offline
Зарегистрирован: 15.06.2013

Динамическая индикация ЗЛО ...

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

В китае 74hc595 и uln2003 по 3...4 руб штука, у нас по 20 - тоесть максимум за 160 руб можно избавить себя от этого гемороя(если конечно дисплей не матричного типа).

repeat
Offline
Зарегистрирован: 24.01.2016
#include <OneWire.h>
#include <DallasTemperature.h>

OneWire oneWire(52);
 DallasTemperature sensors(&oneWire);

int K=10;
int p[4]={0,0,0,0};
int N[4]={0,0,0,0};
int time=0;
bool point11=0;

void setup() 
{
  for(int i=10;i<=13;i++) pinMode(i,OUTPUT);
  for(int i=22;i<=29;i++) pinMode(i,OUTPUT);
  sensors.begin();
}
void num(int n,bool p=0);
void num(int n,bool p) 
{
  int CODE,POINT;
  if(n==0)   CODE=B00111111;
  if(n==1)   CODE=B00000110;
  if(n==2)   CODE=B01011011;
  if(n==3)   CODE=B01001111;
  if(n==4)   CODE=B01100110;
  if(n==5)   CODE=B01101101;
  if(n==6)   CODE=B01111101;
  if(n==7)   CODE=B00000111;
  if(n==8)   CODE=B01111111;
  if(n==9)   CODE=B01101111;
  if(n==9)   CODE=B01101111;
  if(p) POINT=B10000000; else POINT=B00000000;
  PORTA=CODE+POINT;
}
float _r(float n)
{
	int sign=(n>=0 ? 1 : -1);
	float i=(int)(abs(n)+0.5f);
	float e=i-abs(n);
	return e<0.001f ? sign*i : n;
}
void val(float n)
{
  p[0]=0;p[1]=0;p[2]=0;p[3]=0;
  if(n<1000) p[3]=1;
  if(n<100)  p[2]=1;
  if(n<10)   p[1]=1;
  float D=n,E;
  int j=0;
  while(D>=10.0f) D/=10.0f, E=D-(int)D, E*=10.0f, E=_r(E), N[j]=E, D=(int)D, j++;
  N[j]=(int)D;
}


void loop() 
{
  
  time++; if(time>10000) time=0,sensors.requestTemperatures(),val(sensors.getTempCByIndex(0));
  
  K++; if(K>13) K=10;
 
  
  for(int i=10,j=0;i<=13;i++,j++) digitalWrite(i,1);
  
  if(K==13){ num(N[0]); digitalWrite(13,0);}
  if(K==12){ num(N[1]); digitalWrite(12,p[1]);}
  if(K==11){ num(N[2],point11); digitalWrite(11,p[2]);}
  if(K==10){ num(N[3]); digitalWrite(10,p[3]);}

}

Вот полноценный код. Тут немного по другому опрашивается датчик, но довольно долго тоже. Если делать опрос раз в 10000(незнаю сколько это по времени, но примерно 5сек), то каждые 5 сек цифры на индикаторе будут мерцать. Те кто писал программу скорее всего расчитовали на вывод в консоль, где не настолько важна частота работы кода. Думаю, что функция опроса датчика содержит дополнительные действия связанные с контрольной суммой или еще чем то подобным.

rekrut007
Offline
Зарегистрирован: 19.10.2014

Попробуйте сделать прогу без библиотеки считывания температуры (когда только начал изучать ардуино, попробовал её, не понравилось и никогда не использую). Если периодичность чтения данных с датчика не критична, увеличьте интервал до 30 сек. или более.

repeat
Offline
Зарегистрирован: 24.01.2016

Если просто нужно вывести число, то прога работает нормально. Скорости выполнения одной итерации хватает чтобы глаз не замечал мерцания. Периодичность считывания не поможет. Каждые 30сек все равно будет мерцать. Мне вообще интересно было бы самому написать функцию чтения, обмена данными с датчиком, но не могу найти подобного примера.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:

У меня динамическая индикация и если в программе к примеру поставить задержку 100 мс, то будут видны мерцания. Чтобы получить показатель термометра используются функции ds.reset ds.write ds.select, которые выполняются довольно долго, вот отсюда и задержка программы. Просто видел видео где на ардуино с помощью ds18b20 делают на 4х сегментном индикаторе термометр и мерцаний не видно. Думаю что нужно как то быстрее извлечь данные из ds18b20 чтобы добится желаемого результата. Вот только как?

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

Ой, уже показали, ну, нет, так не пойдёт. Динамическая индикация делается по-другому. Сейчас поздно, завтра могу показать.

repeat
Offline
Зарегистрирован: 24.01.2016

В 7м посту в главном цикле переменная K пробегает значения от [10,13], т.е. четыре разряда. Каждую итерацию загорается соответствующий индикатор(разряд). Пробовал код другой динамической индикации, такая же проблема с мерцанием, если использовать функции считования данных с датчика.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:

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

Вам нужен пример? Без DallasTemperature и без OpenWire? Могу дать, только Вы не читаете постов. Я Вам ещё утром написал, что у этого датчика время 750мс по даташиту! И никак Вы его не ускорите. А Вы весь день талдычите "ускорить бы, но не знаю как" - никак. Нужно просто правильно написать динамическую индикацию. Если она написано правильно, датчик никак ей помешать не может.

Нужен пример работы с датчиком без библиотек?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:

В 7м посту в главном цикле переменная K пробегает значения от [10,13], т.е. четыре разряда. Каждую итерацию загорается соответствующий индикатор(разряд). Пробовал код другой динамической индикации, такая же проблема с мерцанием, если использовать функции считования данных с датчика.

Давайте, договоримся, сейчас я не могу, а завтра мы сделаем Вам правильную динамическую индикацию Окей?

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016

есть небольшой опыт программирования, как я избавлялся от мерцания:

завожу переменную и потом сравниваю если значение плученое с датчика не = переменной то перезапишим ее туда и выведем на экран ил на дисплей..., все работает отлично.

ну а если равно то оставляемтак как есть. надеюсь помогло.

rekrut007
Offline
Зарегистрирован: 19.10.2014

Не менее 750 мс необходимо для считывания данных с датчика при паразитном питании. За это время внутренний конденсатор успевает зарядиться. А если в термометре используется не паразитное питание, то эту временную задержку можно свести к минимуму. Я использую 50 мс. Все работает. 

repeat, во 2-м посту я привел 10 строчек кода для считывания данных с датчика при условии, если он один. Попробуйте их использовать вместо библиотеки dallastemperature. Опытным путем выберите минимальную паузу в строке 4.

 

 

repeat
Offline
Зарегистрирован: 24.01.2016

Все же вы правы, все дело в задержке 750мс.  сам индикатор обрабатывается за 0-1 мс и + 750мс на считывания данных. Вот только не понял как уменьшить эти 750 до 50. Использую не паразитное.

rekrut007
Offline
Зарегистрирован: 19.10.2014

repeat, попробуйте, используя функцию micros(), определить время, затрачиваемое на считывание данных с датчика и их преобразование в температуру с использованием библиотеки dallastemperature и без нее с минимальной паузой или без этой паузы. Если это сделаете, поделитесь, пжл, результатами.

repeat
Offline
Зарегистрирован: 24.01.2016

Считование данных занимает(без delay(50)) 20мс + индикатор 1мс. Это много. Мерцания видны. Нужно чтобы вся программа занимала не более 2мс. Я думаю это возможно, если распараллелить код. НО для этого нужно полностью с нуля писать функции считвания данных, а не работать с готовыми.

rekrut007
Offline
Зарегистрирован: 19.10.2014

Только, что проверил на Uno. На приведенный мной во 2-м посте код по считыванию и преобразованию температуры без задержки затрачивается примерно 5,5 мс. Без задержки температура считывается без проблем.

repeat
Offline
Зарегистрирован: 24.01.2016

6мс и меньше на глаз смотрится нормально. Каждая из функций ds.reset ds.write занимает 1мс, поэтому как раз на пределе. НО есть еще вот такая вещь for  (i = 0; i < 9; i++) data[i] = ds.read  (); которая занимает тоже 5мс. Поэтому от дрожаний неизбавится. Нужно как-то распараллелить, или переделать сами эти функции(хотя не уверен что их можно ускорить). 

rekrut007
Offline
Зарегистрирован: 19.10.2014

repeat, ниже приведен код. Можете сами его проверить.

#include <OneWire.h>
OneWire ds(2);
 
void setup() 
{
Serial.begin(115200); 
}
 
void loop()
{
  byte adres[8];
  float temperatura;
  byte data[2];
  unsigned long time1, time2;
  time1 = micros();
  ds.reset(); 
  ds.write(0xCC);                                   // игнорирование кодов (адресов) датчиков для последующей команды
  ds.write(0x44);                                   // запуск преобразования температуры датчиком. Если подключено несколько датчиков, то эту команду выполнят все.
  //delay(1);
  ds.reset();
  ds.write(0xCC);                                 // игнорирование кодов (адресов) датчиков для последующей команды. Если подключен один датчик. Можное использовать эту команду.
  ds.write(0xBE);                                   // чтение двух байт из памяти датчика с данными температуры
  data[0] = ds.read();                              // присваивание переменной data[0] данных первого байта, полученного от датчика
  data[1] = ds.read();                              // присваивание переменной data[0] данных первого байта, полученного от датчика
  temperatura = ((data[1] << 8) | data[0])/16.0;   // преобразование температуры из полученных от датчика двух байтов
  time2 = micros();
  Serial.println(temperatura);
  Serial.println(time2-time1);
  delay(5000);
}

 

rekrut007
Offline
Зарегистрирован: 19.10.2014

Я думаю, что быстрее на ардуино не получится. Если только вместо библиотеки OneWire сделать свое.

rekrut007
Offline
Зарегистрирован: 19.10.2014

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

repeat
Offline
Зарегистрирован: 24.01.2016

Да, получилось. Спасибо. 

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

repeat пишет:

Все же вы правы, все дело в задержке 750мс.  сам индикатор обрабатывается за 0-1 мс и + 750мс на считывания данных. Вот только не понял как уменьшить эти 750 до 50. Использую не паразитное.

в ДШ DS18B20 указано время преобразования температуры в код , чем больше точность - тем дольше,
750 миллисекунд - это при максимальном разрешении
это время не зависит от питания ( паразитное или прямое )

ещё раз - через миллис() сделать вызов блока ...... каждые 750...1000 миллисекунд
и в блоке сначала считывать датчик ( это быстро , три байта надо переслать ) ,
потом запускать датчик на очередное преобразование и выходить из блока

.....первый опрос датчика вернёт 4095.8 градусов С , дальше всё нормально будет читаться

считывание датчика и запуск его для очередного замера Т забирает мало времени ,
не нужно ждать когда датчик произведёт конвертацию температуры
( он работает примерно как АЦП на сдвиговом регистре с последовательным приближением )
 

bwn
Offline
Зарегистрирован: 25.08.2014

У датчика DS18B20 независимый регистр для хранения значений. 750мС - время в течении которого температура еще не перезаписана. Соответственно можете подать сигнал на конвертацию, а считывание результата производить хоть через час, но не ранее 750мС для 12бит режима. Постоянное дерганье датчика ведет к повышению его собственной температуры и как следствие искажению показаний (для медленных динамических процессов, типа контроля температуры помещения, достаточно интервала в 1-5минут).

Здесь у dimax интересно.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Никакой разницы по типам питания нет. Время записывания температуры зависит только от разрешения датчика.

Можно идти, как Вам здесь советуют: давать команду, а потом считывать. А можно настроить таймер, чтобы дисплей обновлялся всегда регулярно и тогда ему (дисплею) плевать на Ваши датчики и на всё остальное, хоть delay(100500) ставьте. Это самый правильный путь работы с дисплеем потому что, если с ним работать в общем loop, то не датчик, так ещё кто - всегда что-нибудь вылезет. Как обещал вчера, могу помочь с настройкой дисплея через таймер. Надо?

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

bwn пишет:

1У датчика DS18B20 независимый регистр для хранения значений. 750мС - время в течении которого температура еще не перезаписана. Соответственно можете подать сигнал на конвертацию, а считывание результата производить хоть через час, но не ранее 750мС для 12бит режима. Постоянное дерганье датчика ведет к повышению его собственной температуры и как следствие искажению показаний (для медленных динамических процессов, типа контроля температуры помещения, достаточно интервала в 1-5минут).
2 - 
Здесь у dimax интересно.

1 - ................
2 - спасибо ! не видел, не читал раньше :(

 

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

ЕвгенийП пишет:

Как обещал вчера, могу помочь с настройкой дисплея через таймер. Надо?

не знаю как для ТС , а мне надо :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

SU-27-16, если достаточно описать "идейно" я могу хоть сейчас. Если же Вам нужен полностью работающий пример, то это вечером, сейчас у меня нет под рукой ни ардуины, ни дисплея. чтобы проверять. Давайте, может я идейно сейчас напишу, а Вы мне скажете, всё ли понятно и нужен ли полностью работающий пример. Пойдёт?

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

ЕвгенийП пишет:

Если же Вам нужен полностью работающий пример, то это вечером, сейчас у меня нет под рукой ни ардуины, ни дисплея. чтобы проверять. Давайте, может я идейно сейчас напишу, а Вы мне скажете, всё ли понятно и нужен ли полностью работающий пример. Пойдёт?

"Можно и туда" - "Служебный роман"....
....мине ВСЁ пойдёт , лишь бы Вас не нагружать....
:)

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

ЕвгенийП пишет:

...ни дисплея. чтобы проверять. Давайте, может я идейно сейчас напишу, а Вы мне скажете, всё ли понятно и нужен ли полностью работающий пример. Пойдёт?

пойдёт про датчик , про его опрос.....
про динамОтображ - не нано !!!!

 

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

ЕвгенийП пишет:

SU-27-16, если достаточно описать "идейно" я могу хоть сейчас. 

если у Вас есть время на такое - я готов слушать-читать....

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Идейно, это делается вот так: показ готовых цифр на экране полностью отрывается от основной программы. Они просто независимы. Показ работает сам по себе с фиксированной частотой, а основная программа может делать что хочет, хоть в вечном делэе сидеть. Внимательно прочитайте код и все комментарии к нему.

#define	TOTAL_DIGITS	3	//	Количество знаков на экране

#define	COMMON_CATHODE	HIGH	//	HIGH - если экран с общим катодом и LOW - если с общим анодом

///////////////////////////////////////
//
//	Процесс обновления экрана состоит из последовательного включения цифр.
//	Каждая цифра должна некоторое время гореть, затем она гаснет и загорается следующая.
//	И так по кругу постоянно. 
// При этом полный цикл (все цифры по разу посветились) должен укладываться в 20мс.
//	Тогда частота обновления экрана составит 50Гц и мерцания заметно не будет
//
//	Ниже мы определяем задержку между переключением цифр (время горения одной цифры)
//	в зависмости от количства разрядов задержка INTER_DIGIT_DELAY равна 5, 6 или 9 мс.
//
#if TOTAL_DIGITS == 3
#	define	INTER_DIGIT_DELAY	6
#elif TOTAL_DIGITS == 4
#	define	INTER_DIGIT_DELAY	5
#elif TOTAL_DIGITS == 2
#	define	INTER_DIGIT_DELAY	9
#endif	//	определение INTER_DIGIT_DELAY

//
//	Здесь определены пины для общих анодов/катодов цифр
//
uint8_t commonDigitPins [TOTAL_DIGITS] = { 10, 11, 12 };

//
//	Определение пинов для сементов дисплея (a - g) я опускаю, 
//	но сделать это, конечно надо
//

////////////////////////////////////////////////////////
//
//	Это класс, содежащий описание физического экрана
//	Определена текущая (горящая) цифра и TOTAL_DIGITS байтов
//	для хранения значений всех цифр.
//
typedef struct {
	uint8_t currentDigit, digits[TOTAL_DIGITS];
	PhysicalScreen() { currentDigit = 0; memset(digits, 0, sizeof(digits)); }
} PhysicalScreen;

// Глобальная переменная - физический экран и указатель на неё - логический экран
volatile PhysicalScreen screen, * LogicalScreen = screen;

//////////////////////////////////////////////////////////
//
//	Эта функция должна вызываться по прерыванию от таймера
//	точно каждые INTER_DIGIT_DELAY миллисекунд
//	
void DisplayRefresh(void) {
	digitalWrite(commonDigitPins[LogicalScreen->currentDigit], COMMON_CATHODE);	//	погасить старую текущую цифру
	LogicalScreen->currentDigit = (LogicalScreen->currentDigit + 1) % TOTAL_DIGITS;	//	назначить новую текущую цифру
	//
	//	В этом месте необходимо отобразить цифру, хранящуюся в LogicalScreen->digits[LogicalScreen->currentDigit]
	// Т.е. необходимо зажечь/погасить соответсвующие сегменты
	// Для того, чтобы зажечь сегмент на пине pin необходимо подать на него COMMON_CATHODE,
	//	а чтобы погасить - !COMMON_CATHODE. Т.е.
	//	digitalWrite(pin, COMMON_CATHODE); // ЗАЖИГАЕМ
	//	digitalWrite(pin, !COMMON_CATHODE); // ГАСИМ
	//
	//	Как именно её отображать, зависит от того, как складывали (см. комментарий ниже)
	//
	digitalWrite(commonDigitPins[LogicalScreen->currentDigit], !COMMON_CATHODE);	//	зажечь новую текущую цифру
}

///////////////
//
//	Всё, что написано выше, касалось обновления экрана. Если это реализовать, то в самой основной программе
//	останется только складывать в массив LogicalScreen->digits цифры для отображения. Изменилось что-то - просто записал
//	туда и не паришься об отображении. Оно само всё отобразится при ближайшем выполнении функции DisplayRefresh, а она
//	выполняется часто.
//
//	Как именно складывать цифры? Как удобнее. Например, можно писать туда готовые маски для показа, условившись, что 
//	бит 0 отвечает за сегмент a, бит 1 - за семент b, ..., ,бит 7 - за десятичную точку.
//	Можно по-другому - главное, чтобы "как складывал, так показывать" :)
//	В общем случае надо стремиться, чтобы показ (функция DisplayRefresh) отрабатывал как можно быстрее.
//
//	Например, я иногда складываю в digits просто биты порта ввода/вывода МК (правда тогда digits надо делать
//	16-разрадными, т.к. нужно два порта на цифру. В этом случае обновление всех восьми сегментов цифры
//      сводится к двум побитовым операциям с портами
//
//	Ещё раз, реализуем то, что написано выше (функция DisplayRefresh и её вызов каждые INTER_DIGIT_DELAY миллисекунд)
//	и она живёт своей жизнью, без участия нашей основной программы. Основная программа просто складывает значения
//	в массив LogicalScreen->digits и совсем не парится об их отображении.
//
///////////////////////////////

void setup() {
}

void loop () {}

Осталось два момента:

1.
Как организовать вызов DisplayRefresh каждые INTER_DIGIT_DELAY миллисекунд? Я вижу два способа: лучше бы через таймер, но здесь может быть засада - доступных таймеров всего два и многие библиотеки любят их исользовать - подерёмся. Второй способ - с использованием техники, которую я описал в одном из моих "этюдов". Там собственно тоже через таймер, но там мы его монопольно не занимаем.

2.
Несколько искусственно выглядят танцы с бубнами вокруг класса, глобальной переменной, да и ещё указателя на неё в строках 40-46. Да, можно было сделать гораздо проще. Намного проще. Но! Здесь это сделано "на вырост". Я сейчас не буду пудрить мозги, пока не скажете, что с кодом всё понятно и Вы запросто можете такое воспроизвести. А вот, когда Вам будет всё понятно, я дополнительно покажу как сделать пачку виртуальных экранов на базе одного реального. И вот там нам эти танцы с бубнами пригодятся ещё как. Т.е. это "на вырост".

 

repeat
Offline
Зарегистрирован: 24.01.2016

Конечно хотелось бы узнать как реализуются параллельные вычисления через прерывания.

функция memet как я понял обнуляет массив?

что такое volatile?

классный прием: LogicalScreen->currentDigit = (LogicalScreen->currentDigit + 1) % TOTAL_DIGITS; возьму на заметку.

Я почему-то думал у ардуино язык С (где нет классов), а тут С++.

Евгений, на пальцах рассказать можете как делаеются эти прерывания, ну т.е. на примере индикатора?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat,

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

А вот, что очень важно: в другой теме и по другому поводу у меня случайно вылез отличный пример, который убедительно показывает почему нельзя делать обновление экрана через loop и почему обязательно делать через прерывания (иначе всегда будет не мигать, так "подминивать иногда"). Вот почитайте здесь.

repeat
Offline
Зарегистрирован: 24.01.2016

Вобщем сделал индикатор с прерыванием по таймеру. Заметил странную вещь:

Пусть переменная R=74 объявлена как глобальная(и инициализирована 1 раз).

У меня прерывание срабатывает каждую милисекунду, и если писать в loop'е так:

...
...
int R=74;
...
...
void loop()
{
   val(74);
}

ISR(TIMER1_COMPA_vect) 
{
    //обработчик прерывания на индикатор
}

или так:

...
...
int R=74;
...
...
void loop()
{
   R=74;
   val(R);
}

ISR(TIMER1_COMPA_vect) 
{
    //обработчик прерывания на индикатор
}

то число 74 нормально отображается, но если так:

...
...
int R=74;
...
...
void loop()
{
   val(R);
}

ISR(TIMER1_COMPA_vect) 
{
    //обработчик прерывания на индикатор
}

то второй разряд иногда не загорается(частота его загорания примерно в 2 раза реже 1-го разряда +-500мкс)(как будто в функцию передалось не 74 а 4).

С чем это связано? Может адресс переменной R чем-то забивается, а потом возвращается на место(ну или как правильно это сказать)?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

1. Не нужно каждую миллисекунду. Сколько у Вас цифр на индикаторе? Скажем N. Весь индикатор должен сменяться с частотой не менее 50Гц, т.е. за 20мс. Значит Вам нужно дергать таймер не каждую миллисекунду, а каждые 20/N миллисекунд. Т.е. каждые 20/N миллисекунд Вы должны погасить старую цифру и зажечь новую и так по кругу. Мерцания никакого не будет. А будете гасить чаще - они будут менее ярко выглядеть, т.к. горят меньше времени.

20/N равно: для трёх цифр - 6, для четырёх - 5, для пяти - 4 и т.д.

2. Про Вашу цифру ничего сказать не могу, т.к. не вижу скетча.

repeat
Offline
Зарегистрирован: 24.01.2016
#include <IRremote.h>
#include <OneWire.h>
OneWire ds(52);

bool point=0;

float R=74;
int K=30;
int p[4]={1,1,1,1},pp[4]={0,0,0,0};
int N[4]={0,0,0,0};

volatile long cntr;


void setup() 
{
  for(int i=30;i<=33;i++) pinMode(i,OUTPUT);
  for(int i=22;i<=29;i++) pinMode(i,OUTPUT);
 
  TCCR1A = B00000000;
  TCCR1B = B00000001;
  TCNT1=0;
  OCR1A = 16000;
  TIMSK1 |= (1 << OCIE1A);
}

void num(int n,bool p=0);
void num(int n,bool p) 
{
  int CODE,POINT;
  if(n==-2)  CODE=B01000000;
  if(n==-1)  CODE=B01000110;
  if(n==0)   CODE=B00111111;
  if(n==1)   CODE=B00000110;
  if(n==2)   CODE=B01011011;
  if(n==3)   CODE=B01001111;
  if(n==4)   CODE=B01100110;
  if(n==5)   CODE=B01101101;
  if(n==6)   CODE=B01111101;
  if(n==7)   CODE=B00000111;
  if(n==8)   CODE=B01111111;
  if(n==9)   CODE=B01101111;
  if(n==9)   CODE=B01101111;
  if(p) POINT=B10000000; else POINT=B00000000;
  PORTA=CODE+POINT;
}
float _r(float n)
{
	int sign=(n>=0 ? 1 : -1);
	float i=(int)(abs(n)+0.5f);
	float e=i-abs(n);
	return e<0.001f ? sign*i : n;
}

void val(float n)
{
  n=R;                     //Вот даже так в n записывается иногда не полное число R, а его часть
                           //Все что ниже можно не читать, все равно буду делать по красивее
  if(n<=-999) n=-999;    if(n>=9999) n=9999;
  int j=0;
  p[0]=0;p[1]=1;p[2]=1;p[3]=1;
  pp[0]=0;pp[1]=0;pp[2]=0;pp[3]=0;
  if((n-(int)n)==0)
  {
    if(n>=0)
    {
      if(n>1000) p[3]=0;
      if(n>100)  p[2]=0;
      if(n>10)   p[1]=0;
    }
    else
    {
      if(n>-1000) j=3;
      if(n>-100)  j=2;
      if(n>-10)   j=1;
      if(n<-1000) p[3]=0;
      if(n<-100)  p[2]=0;
      if(n<-10)   p[1]=0;
    }
  }
  else
  {
    if(n>0)
    {
      if(n<100) 
      {
        if(n<10) p[3]=1;
        pp[2]=1,n=n*100;
      }
      else if(n>=100 && n<1000) pp[1]=1,n=n*10; 
    }
    else
    {
      if(n>-999 && n<=-100) j=3;
      if(n>-100 && n<-10) pp[1]=1,n=n*10,j=3;  
      else if(n>-10) pp[2]=1,n=n*100,j=3;     
    }
  }
  int nn=abs(_r(n));
  N[0]=nn%10;  nn/=10;
  N[1]=nn%10;  nn/=10;
  N[2]=nn%10;  nn/=10;
  N[3]=nn;
  if(n<0) N[j]=-2, p[j]=0; 
}

float GetTemp()
{
  byte adres[8];
  float temperatura;
  byte data[2];
  
  ds.reset();
  ds.write(0xCC);                                   
  ds.write(0x44);                                  
  delay(1000);
  ds.reset();
  ds.write(0xCC);                                 
  ds.write(0xBE);                                  
  data[0] = ds.read();                              
  data[1] = ds.read(); 
  return ((data[1] << 8) | data[0])/16.0;   // преобразование температуры из полученных от датчика двух байтов 
}

void loop() 
{
  val(5);
}  

ISR(TIMER1_COMPA_vect) 
{
  TCNT1=0;
  cntr++;
  if(cntr>=1) 
  {
    for(int i=30,j=0;i<=33;i++,j++) digitalWrite(i,1);
    num(N[K-30],pp[K-30]); digitalWrite(33-(K-30),p[K-30]);
    K++; if(K>33) K=30;
    cntr = 0;
  }
}

Даже специально передал в функцию значение R  и все равно мерцает 2й разряд

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Как-то всё сложно и мудрёно. Вечером посмотрю, а сейчас так сходу ничего особо и не скажешь. Ну, из того что бросается в глаза:

1. Все глобальные переменные, которые используются и в val и ISR (p, pp, N, ...) необходимо объяить с аттрибутом volatile. Проблема с цифрой вполне может быть оттуда. А вот cntr как раз не обязательно иметь этот аттрибут, т.к. она используется только в ISR. Кстати, а почему бы её прямо там и не объявить (static, конечно)

2. Для чего нужна нигде и никак неиспользуемая j в строке 136?

3. По хорошему подготовку надо проводить не в обработчике прерывания. Всё, что у Вас делает вызываемый из обработчика num(), должно быть готово заранее, а там  только показать и всё.

repeat
Offline
Зарегистрирован: 24.01.2016

Оказывается нельзя обновлять в главном цикле число на индикаторе, пока не покажет он все цифры. Вот и решилась проблема.

Достаточно написать перед так: if(K==30) val(R);

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:

Оказывается нельзя обновлять в главном цикле число на индикаторе, пока не покажет он все цифры. Вот и решилась проблема.

Достаточно написать перед так: if(K==30) val(R);

да, нет, надо всё аккуратно объявить volatile и не считать в прерывании, как я Вам писал. И обновлять когда нужно. Это же идиотизм, чтобы у главной программы ещё болела голова о проблемах индикатора. Его дело показывать, а главная программа обновляет когда её надо.

repeat
Offline
Зарегистрирован: 24.01.2016

Не совсем понял что Вы имеете ввиду? По подробнее можете обяснить с кодом обработчика?

num() только модифицирует порт, чтобы загорелось определенное число(на всех индикаторах), а ненужные разряды я гашу. как еще проше это сделать?

repeat
Offline
Зарегистрирован: 24.01.2016

ЕвгенийП пишет:

И обновлять когда нужно. Это же идиотизм, чтобы у главной программы ещё болела голова о проблемах индикатора. Его дело показывать, а главная программа обновляет когда её надо.

дело в том, что когда программа входит в функцию val(R) функция чето там себе считает, изменяет значения разрядов и в этот момент срабатывает прерывание и показывает черти что. 

получается обработчик прерывания не должен знать о изменении какого либо разряда пока не покажет все 4 цифры.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

repeat пишет:

Не совсем понял что Вы имеете ввиду? По подробнее можете обяснить с кодом обработчика?

num() только модифицирует порт, чтобы загорелось определенное число(на всех индикаторах), а ненужные разряды я гашу. как еще проше это сделать?

Функция Num делает выбор из 14 вариантов и только потом присваивает значение порту. Да ещё перед вызовом функцией num в обработчике у Вас дохрена чего делается.

Я уж не говорю, что Вам незачем гасить все пины порта А (стр. 136), если Вы всё равно в него потом пишете присваиванием (строка 45)! Для чего у Вас в программе строка 136? Чтобы во все биты порта А записать нули, так? А что они туда строкой 45 не запишутся? ОТлично запишутся куда надо нули, а куда надо - единицы.

Теперьт как можно было проще. Обработчик прерывания НЕ ДОЛЖЕН заниматься выбором масок и т.п. Для него всё уже должно быть готово. Вот как примерно должен выглядеть обработчик прерывания в Вашем случае:

const uint8_t commonAnodes[4] = {.....}; // пины общих анодов цифр
volatile uint8_t digits[4];
volatile int8_t current_digit = 0;

.............

ISR(TIMER1_COMPA_vect) {
   TCNT1=0;
   digitaWrite(commonAnodes[current_digit++ ], LOW); // погасить текущую цифру
   if (current_digit == 4) current_digit =0;  // уточнить новый номер текущей цифры
   PORTA = digits[current_digit];  // актуализировать сегменты
   digitaWrite(commonAnodes[current_digit ], HIGH); // зажечь новую текущую цифру
}

Больше в обработчике не должно быть ничего! В digits должны быть уже готовые к загрузке в порт маски без необходимости что-то выбирать и вычислять. В массиве commonAnodes должны сидеть уже готовые номера пинов для каждой цифры, опять же ничего выбирать не надо.

Всеми делами, которыми у Вас занимается num, должна заниматься функция типа showNumber(), которая вызывается из основной программы и её дело сложить в digits ГОТОВЫЕ к загрузке в порт значения.

Обработчик должен вызываться (для четырёх знаков, как у Вас) раз в 5мс. Чаще не нужно. Всё будет отлично работать.

теперь по поводу того, что Вы говорите: во время записи числа происходит прерывание. Поставьте  volatile  и не бойтесь этого. Всё равно у Вас реально показывается только одна цифра в каждый момент времени. Запись цифры как таковой не прервётся (т.к. volatile) а чего ещё бояться? 

repeat
Offline
Зарегистрирован: 24.01.2016

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

loop()
{
  digitalWrite(13,0);
  digitalWrite(13,1);
  digitalWrite(13,0);
  digitalWrite(13,1);
  digitalWrite(13,0);
  digitalWrite(13,1);
  digitalWrite(13,0);
}

то светодиод на 13 пине не загорится ни разу, т.к последняя команда записывает в ножку 0.

Кстати у меня индикатор загорается если подавать 0, а не 1.

ppbinag
Offline
Зарегистрирован: 12.01.2015

Может просто сделать опрос датчика рас в секунду, а на дисплей выводить сохраненное значение. Сам так делал, никаких мерцаний.

repeat
Offline
Зарегистрирован: 24.01.2016

И еще одну важную вещь понял: выражение PORTA=.... нужно писать только 1 раз в программе(ну в обработчике), иначе будет каша.

Опять же, это присваивание сразу после этой команды на ножки подает сигнал. Я же думал подается лишь последнее присваивание в конце функции/итерации.

Евгений, спасибо, разобрался.