Сбор данных от датчиков на плате Марсоход2

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

Я хочу сделать совсем простой, такой мини-проект: использовать плату Марсоход2 как плату сбора данных. Она же, плата Марсоход2, будет эти данные передавать на компьютер. На компьютере нужно будет как-то отображать на экране полученные результаты.

Плата Марсоход2 с подключенным к АЦП переменным делителем напряжения и датчиком температуры

На плате Марсоход2 есть 2 кнопочки – это будут первые два датчика с бинарным состоянием «включено»-«выключено». Еще на плате Марсоход2 есть восьмибитный АЦП (Аналогово-Цифровой Преобразователь). Это будет второй датчик. Значения от него будут передаваться в компьютер в диапазоне от 0 до 255. Ко входу АЦП подключу переменный резистор, как делитель напряжения. Четвертый будет датчик температуры ds18b20 – такой же как использовался в другом нашем проекте.

Плата Марсоход2 подключается к ПК через микросхему FTDI, которая имеет два канала приемопередачи и реализует управление ПЛИС через JTAG и еще последовательный порт. Вот показания датчиков и буду посылать через последовательный порт.

Собственно датчик температуры ds18b20 устанавливается на плату Марсоход2 вот так:

Датчик температуры ds18b20Датчик температуры ds18b20 установленный в плату Марсоход2

У датчика всего три ножки: питание, двунаправленные данные, земля. Питание подключено к контакту IO[10], данные передаются через контакт IO[8] платы Марсоход2.

Шилд делителя напряжения подключается к АЦП платы. Выглядит этот шилд вот так:

Делитель напряжения на переменном резисторе.Делитель напряжения на переменном резисторе установленный на плату Марсоход2.

Теперь расскажу о протоколе передаваемых данных.

Я недолго думал о протоколе передаваемых данных в ПК.  Хорошо бы, чтоб протокол был простым и легко расширялся при увеличении количества датчиков. Я решил, что проще всего передавать показания датчиков в текстовом виде. Например, строки «but0=1» или «but1=1» могут описывать состояние бинарных датчиков, кнопочек, концевиков. Показание АЦП может передаваться в виде строки «adc0=A3». Строка состоит из «имени» датчика и его «значения». «Имя» датчика и его «значение» разделены символом «=». Каждое показание любого из датчиков – это одна текстовая строка. Строки разделены последовательностью байт «перевод каретки» или 0x0D 0x0A. Такой подход позволит на первом этапе сосредоточиться на проекте в плате, а в качестве программной части просто использовать текстовый терминал вроде Putty или TeraTerm. Таким образом, не нужно будет одновременно писать две программы: для ПЛИС сбор данных от датчиков и для ПК программу визуализации. Позже, когда плата заработает, тогда начну делать программу для ПК.

Собственно проект для ПЛИС в плате Марсоход2 выглядит вот так (топ-модуль в графическо дизайне):

Схема топ модуля сбора информации от датчиков в проекте Altera Quairtus II

Схему можно увидеть покрупнее, если кликнуть по ней левой кнопкой мышки.

Модуль txtgen – из показаний датчиков создает поток текста и пишет его в ФИФО. К модулю txtgen подключены входы кнопок KEY0 и KEY1, а так же, вход с АЦП ADC_D[7..0]. Кроме этого, в проекте есть модуль termometer. Он передает в модуль txtgen показания температуры от внешнего датчика в виде 16-ти битного знакового числа. Нужно сказать, что модуль termometr общается с внешним датчиком температуры по одному двунаправленному (inout) проводу IO[8]. Используется подключение с открытым коллектором (open drain), поэтому на схеме можно увидеть перед выходом IO[8] элемент OPNDRN. Еще одно важное замечание – питание на датчик температуры подается через пин IO[10] – просто так было удобнее подключать датчик к плате. Поэтому на схеме выход IO[10] подключен к VCC. Вся описанная выше часть схемы тактируется частотой 1МГц с выхода c0 от PLL.

Модуль serial тактируется частотой 115200 (выход c1 PLL) – на этой скорости ведется передача данных в ПК. Модуль serial читает имеющиеся данные из FIFO и передает их через выходной пин FTDI_BD1 в микросхему FTDI и далее в ПК.

Как работает модуль txtgen? Модуль txtgen написан на языке описания аппаратуры Verilog HDL. В модуле есть счетчик, который делит входную частоту 1МГц на 8192:


reg [12:0]poll_cnt;
always @(posedge clk)
    poll_cnt<=poll_cnt+1'b1;

wire poll; assign poll=(poll_cnt==0);


Сигнал poll получается коротким импульсом с частотой примерно 122Гц (1000000/8192=122,…) – это и есть период опроса датчиков.

Поскольку я знаю сколько и каких датчиков у меня имеется в наличии, то я могу представить себе длину текста, для представления данных этой строки: «b0=1^b1=1^t0=0550^a0=23^». Здесь:

  • b0 и b1 – имена кнопочек
  • t0 – имя датчика температуры
  • a0 – имя датчика АЦП
  • символ «^» - это перевод строки, два байта 0x0D и 0x0A.

Всего символов в строке, включая переводы строки: 28. Поэтому в модуле появляется сдвиговый регистр, в котором как бы движется сигнал poll:


reg [27:0]shift;
always @(posedge clk)
    shift <= {shift[26:0],poll};


Запись в FIFO происходит если хотя бы один бит из сдвигового регистра не нулевой:


assign wr = (shift!=0);


Собственно положение ненулевого бита в сдвиговом регистре определяет какой именно символ записывается в данный момент в FIFO. Описан такой большой мультиплексор передаваемых данных:


assign byte_wr =  
   shift[0] ? 8'h62 : //b
   shift[1] ? 8'h30 : //0

   shift[2] ? 8'h3D : //=
   shift[3] ? { 7'h18,~btn0} : //0 or 1
   shift[4] ? 8'h0D : //
   shift[5] ? 8'h0A : //
 
   shift[6] ? 8'h62 : //b
   shift[7] ? 8'h31 : //1
   shift[8] ? 8'h3D : //=
   shift[9] ? { 7'h18,~btn1} : //0 or 1
   shift[10] ? 8'h0D : //
   shift[11] ? 8'h0A : //

   shift[12] ? 8'h74 : //t
   shift[13] ? 8'h30 : //0
   shift[14] ? 8'h3D : //=
   shift[15] ? hex( tdata_[15:12] ) :
   shift[16] ? hex( tdata_[11: 8] ) :
   shift[17] ? hex( tdata_[ 7: 4] ) :
   shift[18] ? hex( tdata_[ 3: 0] ) :
   shift[19] ? 8'h0D : //
   shift[20] ? 8'h0A : //

   shift[21] ? 8'h61 : //a
   shift[22] ? 8'h30 : //0
   shift[23] ? 8'h3D : //=
   shift[24] ? hex( adc_data[7:4] ) :
   shift[25] ? hex( adc_data[3:0] ) :
   shift[26] ? 8'h0D : //
   8'h0A
   ;


В Verilog модуле txtgen так же определена полезная функция для преобразования шестнадцатеричной тетрады в ASCII символ:


function [7:0] hex;
input [3:0] val;
    begin
     if(val<10) hex = val+8'h30;  //8'h30 mean symbol "0"
     else hex = val + 8'h41 - 10; //8'h41 mean symbol "A"
    end
endfunction


Вот этой функцией пользуюсь для представления числа в виде текста.

Проект выполнен в среде Altera Quartus II Web Edition. Его можно взять здесь:

В проекте дополнительно имеются модули SignalTap – для отладки проекта. Так, можно посмотреть как текст записывается в FIFO:

Использование Altera SignalTap для отладки проектов Quartus II

И еще можно увидеть, как он передается в последовательный порт:

Использование Altera SignalTap для отладки проектов Quartus II

Можно посмотреть, как происходит общение с датчиком температуры через сигнал IO[8]:

Использование Altera SignalTap для отладки проектов Quartus II

В общем, SignalTap дает большие возможности заглянуть внутрь ПЛИС и увидеть разные сигналы.

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

Выбор пал на язык Python. В питон уже встроена графическая библиотека Tk() и он работает и под Windows и под Linux. Несмотря на то, что я почти не знаю этого языка, мне удалось довольно быстро написать на нем вполне работающую программу.

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

Поверх фоновой картинки размещаю «объекты» отвечающие за каждый из датчиков. Объект «двоичный датчик», такой как кнопка, в нужных координатах окна показывает одну из двух заданных картинок. Что нарисовать в этих картинках – это уже ваше дело.

Всего я написал три класса:

  1. BinSensor – отображает одну из дкух картинок
  2. vBarSensor – отображает датчик в виде столбика: значения от минимального до максимального 
  3. GridDisplay – отображает изменение параметра во времени

Ну вот класс BinSensor, совсем простой:


#!/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)


У класса есть только конструктор с параметрамми: имя датчика, две GIF картинки и позиция, координаты x и y.

Функция set() меняет отображаемую на экране картинку соответственно значения датчика.

Главная программа на питоне создает экземпляры классов датчиков поверх фоновой картинки, открывает последовательный порт и читает из него строки. Разбирая строки можно понять какому классу нужно передавать значение датчика. Для этого просматривается список датчиков (list) и сравниваются строки имя датчика и имя параметра из принятой строки.

Вот эта программа:


#!/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()


 

Впрочем, весь проект можно выкачать на нашем сайте – я уверен вы разберетесь, как все это работает. Запустить программу из среды Python27: команда «import alls».

Внешний вид программы отображения получился вот такой:

Демо

А вот и видео демонстрация проекта:

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

 


Добавить комментарий