Сейчас в моде всякие системы типа «умный дом». Предполагается, что дом или квартира или другое помещение напичканы разными датчиками и сенсорами. Информация от датчиков должна поступать в центр принятия решения – это может быть компьютер или какой-то специальный контроллер. Ну а он уже на основании показаний датчиков подает команды на исполнительные устройства: свет зажигает или температуру в помещении регулирует или еще что-то.
Я хочу сделать совсем простой, такой мини-проект: использовать плату Марсоход2 как плату сбора данных. Она же, плата Марсоход2, будет эти данные передавать на компьютер. На компьютере нужно будет как-то отображать на экране полученные результаты.
На плате Марсоход2 есть 2 кнопочки – это будут первые два датчика с бинарным состоянием «включено»-«выключено». Еще на плате Марсоход2 есть восьмибитный АЦП (Аналогово-Цифровой Преобразователь). Это будет второй датчик. Значения от него будут передаваться в компьютер в диапазоне от 0 до 255. Ко входу АЦП подключу переменный резистор, как делитель напряжения. Четвертый будет датчик температуры ds18b20 – такой же как использовался в другом нашем проекте.
Плата Марсоход2 подключается к ПК через микросхему FTDI, которая имеет два канала приемопередачи и реализует управление ПЛИС через JTAG и еще последовательный порт. Вот показания датчиков и буду посылать через последовательный порт.
Собственно датчик температуры ds18b20 устанавливается на плату Марсоход2 вот так:
У датчика всего три ножки: питание, двунаправленные данные, земля. Питание подключено к контакту IO[10], данные передаются через контакт IO[8] платы Марсоход2.
Шилд делителя напряжения подключается к АЦП платы. Выглядит этот шилд вот так:
Теперь расскажу о протоколе передаваемых данных.
Я недолго думал о протоколе передаваемых данных в ПК. Хорошо бы, чтоб протокол был простым и легко расширялся при увеличении количества датчиков. Я решил, что проще всего передавать показания датчиков в текстовом виде. Например, строки «but0=1» или «but1=1» могут описывать состояние бинарных датчиков, кнопочек, концевиков. Показание АЦП может передаваться в виде строки «adc0=A3». Строка состоит из «имени» датчика и его «значения». «Имя» датчика и его «значение» разделены символом «=». Каждое показание любого из датчиков – это одна текстовая строка. Строки разделены последовательностью байт «перевод каретки» или 0x0D 0x0A. Такой подход позволит на первом этапе сосредоточиться на проекте в плате, а в качестве программной части просто использовать текстовый терминал вроде Putty или TeraTerm. Таким образом, не нужно будет одновременно писать две программы: для ПЛИС сбор данных от датчиков и для ПК программу визуализации. Позже, когда плата заработает, тогда начну делать программу для ПК.
Собственно проект для ПЛИС в плате Марсоход2 выглядит вот так (топ-модуль в графическо дизайне):
Схему можно увидеть покрупнее, если кликнуть по ней левой кнопкой мышки.
Модуль 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:
И еще можно увидеть, как он передается в последовательный порт:
Можно посмотреть, как происходит общение с датчиком температуры через сигнал IO[8]:
В общем, SignalTap дает большие возможности заглянуть внутрь ПЛИС и увидеть разные сигналы.
Теперь вкратце расскажу о программе отображения значений датчиков на экране компьютера. Хотелось бы быстро написать такую программу, которую было бы легко настраивать под разное число датчиков и разные их типы. Чтобы ее было легко исправлять и запускать.
Выбор пал на язык Python. В питон уже встроена графическая библиотека Tk() и он работает и под Windows и под Linux. Несмотря на то, что я почти не знаю этого языка, мне удалось довольно быстро написать на нем вполне работающую программу.
Идея была такая. В окне программы отображается фоновая картинка – это может быть, например, план помещения или схема какой-то промышленной установки с вашими датчиками.
Поверх фоновой картинки размещаю «объекты» отвечающие за каждый из датчиков. Объект «двоичный датчик», такой как кнопка, в нужных координатах окна показывает одну из двух заданных картинок. Что нарисовать в этих картинках – это уже ваше дело.
Всего я написал три класса:
- BinSensor – отображает одну из дкух картинок
- vBarSensor – отображает датчик в виде столбика: значения от минимального до максимального
- 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 меняются картинки на окне программы. Когда кручу ручку переменного резистора столбик соответствующего индикатора в программе меняется вместе с поворотом. Потом грею датчик температуры феном и видно, как изменяется график показаний температуры в программе.
Подробнее...