Как стать автором
Обновить

Сбор показаний датчиков и их отображение

Время на прочтение7 мин
Количество просмотров83K


Людям нравятся красивые презентации. Красивые картинки, немного текста, меняющиеся слайды. Красивая картинка позволяет быстро передать информацию человеку, сообщить самое важное. Мы все это знаем. Я вот думаю, как «скрестить ежа и ужа»?

Как наглядно на мониторе компьютера представить процессы, происходящие внутри микроконтроллера или ПЛИС? Или как показать, что происходит внутри всей системы автоматики, реализованной на микроконтроллере или ПЛИС?

Вообще-то правильный ответ я знаю – нужно использовать SCADA системы.
SCADA – это supervisory control and data acquisition, диспетчерское управление и сбор данных. Но мы не ищем легких путей, мы хотим немножко изобрести своего велосипеда.

Хочу поделиться своим простым методом отображения данных, получаемых от датчиков и сенсоров из платы управления.

Тут, прежде всего, нужно разделить три компоненты:
  • протокол передачи данных. Нужно как-то кодировать передаваемую от контроллера к компьютеру информацию.
  • firmware в системе автоматики, в микроконтроллере или ПЛИС. Этот модуль должен собарать показания датчиков и передавать их на компьютер для отображения в «красивом виде»
  • программное обеспечение визуализации. Показывает состояние и значения датчиков. Может строит какие-то графики.


Вот так, по порядку попробую рассказать.

Протокол передачи данных.


В настоящее время физических возможностей подключить какое-то устройство к компьютеру или ноутбуку фактически осталось только две: сетевое подключение через Ethernet/WiFi или USB. Практически ушли в прошлое «настоящие» параллельные и последовательные порты. С ними было просто. Конечно их еще можно найти, если поискать. Но лучше в эту сторону и не думать.

Ethernet пока отставляю в сторону. Для передачи по сети нужно в контроллере иметь драйвера стека TCP/IP, как правило это тянет за собой наличие ОС, обычно Linux или ucLinux. Потом потребуется интерфейс настройки сети: а какой IP адрес? А статический или динамический? А какая маска и gateway? В общем не очень просто в реализации и настройке.

USB кажется гораздо проще, хотя и тут много подводных камней: а какой класс/субкласс устройства? А нужно ли ему драйвера или используются стандартные драйвера той же Windows?
И опять возвращаемся «на круги своя» — проще всего использовать последовательный порт через USB. В простейшем случае есть шнуры типа USB2Serial. Ну или как отличный вариант для разработчика плат и контроллеров – различные микросхемы FTDI.

ОК, все же выбираем последовательный порт через USB. А раз так, то значит пересылка данных может быть в виде последовательности символов. Значит дальше еще проще: показания датчиков можно передавать в виде строк вида «НАЗВАНИЕ_ДАТЧИКА=ЗНАЧЕНИЕ»

При таком подходе мы сможем легко увеличивать количество опрашиваемых сенсоров, и легко менять их тип. Состояние концевика или геркона будет передваться, например, в виде строк «but0=1» или «but1=0». Значение температуры можно передавать в виде строки «t0=36,6». Строки проще всего разделять символами «перевода каретки»: 0x0D 0x0A.

Так, на первых порах даже и программа визуализации на компьютере не нужна. Можно просто запустить программу терминала вроде Putty и смотреть на показания датчиков из контроллера.

Контроллер.


Мой контроллер выполнен на базе ПЛИС Altera Cyclone III. На самом деле это известная разработчикам плата Марсоход2. Я уже писал про некоторые проекты, выполненные на ней. Например, когда-то мы сделали на этой плате на чистой ПЛИС FM радио передатчик. А еще мы сделали на ней USB Tracker. Есть и другие проекты.

Вот такая плата:



На плате уже есть 2 кнопочки – это первые два датчика для моих экспериментов.
Еще я подключил микросхему термометра ds18b20 – это второй датчик.
Можно еще использовать АЦП платы для измерения чего-то-пока-не-знаю-чего. Пока здесь просто переменный резистор вместо датчика.

Важно, что на плате уже стоит микросхема FTDI FT2232HL, которая обеспечивает связь с компьютером через USB в виде виртуального последовательного порта. Скорость передачи аж 12Мбит/сек. Это условно 1,2 Мбайта/сек. Если, например, плата опрашивает датчики каждые 100 миллисекунд, то получается можно за каждый опрос передавать компьютеру более 100Кбайт данных. Вполне прилично.

Сейчас не буду рассказывать о проекте для платы и ПЛИС Cyclone III. Для этого есть отдельная статья. В этой статье подробно рассказано, как опрашиваются данные и передаются результаты в компьютер через последовательный порт. Лучше перейдем к рассмотрению 3-ей компоненты – программы визуализации значений датчиков, которая работает на компьютере.

Программа визуализации данных.


Тут хотелось все сделать быстро и просто. На чем писать программу, чтоб было просто написать, менять, дополнять?

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

Итак, поскольку программа будет графическая, то попробую встроенный в питон Tkinter. Для работы с последовательным портом буду использовать pyserial.

Хочется написать эдакий набор классов — для каждого типа датчиков свой класс.

Самый простой класс – двоичный датчик. Это может быть кнопка, концевик, геркон. Его значения 0 или 1. Соответствующий этому датчику питоновский класс BinSensor отображает всего 2 состояния. Предлагаю каждому состоянию нарисовать свое изображение. Изображение прикрепляется к фиксированным координатам окна программы поверх фонового изображения.



Как только пришло значение «0» показываем первую картинку. Если пришло значение «1», то показываем вторую картинку. Изображения могут быть любыми — все зависит от нашей фантазии.

Вот этот класс:
#!/usr/bin/env python

import Tkinter
from Tkinter import *

root = Tk()

class BinSensor:
  def __init__(self,name,img0,img1,x,y):
    self.name=name
    self.x=x
    self.y=y
    self.img0=PhotoImage(file=img0)
    self.img1=PhotoImage(file=img1)
    self.val=0
    self.label_img=Label(root,image=self.img0)
    self.label_img.place(x=self.x,y=self.y)
  def set(self,state):
    if(self.val==state): return
    self.val=state
    if( int(state)==0 ):
      self.label_img.configure(image=self.img0)
    else:
      self.label_img.configure(image=self.img1)

В функцию __init__, которая вызывается при создании экземпляра класса, передаются параметры:
Name – название датчика
Img0 и img1 – имена файлов картинок, используемых для отображения состояния датчика.
X и y – координаты окна, где будет отображаться датчик.

При создании объекта датчика сразу создается Label с картинкой и размещается в окне Tkinter.

Функция set принимает параметр строку – это новое состояние датчика «0» или «1». В зависимости от нового значения картинка внутри Label переконфигурируется, меняется на другую. В общем это и все.

Аналогичным образом реализуется второй класс vBarSensor.
class vBarSensor:
  def __init__(self,name,scale,min,max,x,y,w,h):
    self.name=name
    self.scale=scale
    self.x=x
    self.y=y
    self.h=h
    self.val=min
    self.min=min
    self.max=max
    self.delta=max-min
    h1=self.h*(self.val-self.min)/self.delta
    h0=self.h-h1
    self.canv0 = Canvas(root, width = w, height = h0, bg = "lightblue", bd=1, relief='ridge')
    self.canv1 = Canvas(root, width = w, height = h1, bg = "red", bd=1, relief='ridge')
    self.barLabel = Label(root, text = "0")
    self.canv0.place(x=self.x,y=self.y)
    self.canv1.place(x=self.x,y=self.y+h0)
    self.barLabel.place(x=self.x,y=self.y+h+5)
  def set(self,newval):
    #newval is signed hex string like "83A5"
    val=int(newval,16)
    if(val>0x7fff): val=-val
    val=val/self.scale
    if(self.val==val): return
    self.val=val
    h1=self.h*(self.val-self.min)/self.delta
    h0=self.h-h1
    self.barLabel.configure(text=str(self.val))
    self.canv0.configure(height = h0)
    self.canv1.configure(height = h1)
    self.canv1.place(y=self.y+h0)

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

Столбик термометра как бы состоит из двух частей нижняя красная и верхняя светлая.
Можно было бы создать один Tkinter canvas и на нем нарисовать эти столбики, но почему-то я сделал не так. Сделал два canvas разного цвета и в функции set() меняю им вертикальный размер. В принципе это не важно. Работает. Кстати, если хочется видеть именно изображение термометра в окне визуализации, то его можно нарисовать на фоновом изображении окна, а поверх него разместить экземпляр vBarSensor.



Наверное будет симпатично.

Написал еще один класс GridDisplay для отображения показаний датчика и изменении их во времени. Его исходный код приводить здесь не буду, чтобы не перегружать статью излишними подробностями. Кому будет нужно скачает с сайта весь проект, вместе с исходниками для ПЛИС для Altera Quartus II.

А вот главную программу alls.py пожалуй покажу. Здесь не очень много всего:
#!/usr/bin/env python

import sensor
from sensor import *

import serial
from serial import *

class AllSensors:
  def __init__(self):
    #open serial port
    self.s=serial.Serial("COM27",115200,timeout=10)
    #load background image
    self.bgnd=PhotoImage(file="bgnd.gif")
    self.label_bgnd=Label(root,image=self.bgnd)
    self.label_bgnd.place(x=0,y=0)
    #add all sensors and indicators
    self.all=[]
    self.all.append( BinSensor("b0","f0.gif","f1.gif",32,32) )
    self.all.append( BinSensor("b1","f0.gif","f1.gif",32,128) )
    self.all.append( vBarSensor("a0",1,0,255,128,32,32,160) ) 
    self.all.append( GridDisplay("t0",16,-55,125,10,16,180,32,256,160) ) 
  def set(self,name,val):
    for sens in self.all:
      if(sens.name==name):
        sens.set(val)
        return
  def setline(self,line):
    p=line.split("=")
    if(len(p)==2):
      self.set( p[0], p[1] )
  def run(self):
    while(1):
      line=self.s.readline()
      line=line.rstrip()
      #print(line)
      self.setline(line)
      root.update()

a=AllSensors()
a.run()

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

Запустить программу из Python легко: «import alls», где alls — это имя главной программы файл alls.py. Вот так сейчас выглядит моя программа:



Вот видео, которое показывает, как все это работает (только не пугайтесь, я там фен включаю для нагрева датчика температуры, так что звук лучше прикрутить):



Теперь, когда «скелет» приложения работает, то можно приступать к детальному рисованию плана помещения и установленных в нем датчиков.
Теги:
Хабы:
+24
Комментарии16

Публикации

Истории

Работа

Data Scientist
63 вакансии
Python разработчик
142 вакансии

Ближайшие события