Измерение и логгирование температуры с FEZ Panda II
Я долго думал о чем написать следующий пост, debug, потоки (thread), прерывания и т.п для каждого поста как-то мало получится, а одним постом все в куче, тоже не красиво. Да и теории в принципе хватает, это вы и сами сможете почитать, а что непонятно — спросить в моем блоге.
Но тут подвернулась реальная задача измерения температуры нагрева катушки соленоида ЭМ-клапана в длительном промежутке времени. В дальнейшем я решил писать новые топики с практической реализацией и в каждом топике затрагивать немного теоретической части. Т.о. мы будем медленно, но верно продвигаться от более простых, к более сложным проектам.
Итак, имеем популярный цифровой датчик температуры DS18B20 (даташит). Подключил я его по трехпроводной схеме (с внешним питанием). Здесь можно посмотреть схему подключения к Arduino, у нас будет тоже самое подключение, единственное пин данных — D4.
У GHI хорошая техподдержка и сообщество, и для очень многих шилдов, сенсоров, LCD-экранчиков и т.п. уже имеются готовые библиотеки и примеры программ. Естественно, работа плат FEZ с таким популярным датчиком как DS18B20 там тоже освещена. Я не стал изобретать велосипед и воспользовался вот этим рабочим кодом.
Убрав, немного лишнего кода в итоге получилось вот что:
Вверху мы видим новую строчку: using GHIElectronics.NETMF.Hardware;
Это необходимо для работы с протоколом OneWire, по которому работает датчик.
Строкой public static OneWire ow = new OneWire((Cpu.Pin)FEZ_Pin.Digital.Di4); мы показываем, что устройство OneWire подключено к 4 выводу Panda II.
Однако, это еще не все. Если сделать новый проект и скопировать туда данный код, то вы увидите следующие ошибки:
Дело в том, что данную библиотеку необходимо включить в проект в обозревателе решений (ссылки)
Для этого, нажимаем правой клавишей мыши на "Ссылки" и выбираем "Добавить ссылку"
В открывшемся окне «AddReference» вы увидите большой список библиотек. Вам необходимо выбрать библиотеку GHIElectronics.NETMF.Hardware:
Теперь ошибок не будет, и код будет компилироваться без ошибок.
В коде есть такая строка: Thread.Sleep(1000); Как можно догадаться, это пауза. Задается в миллисекундах. Т.е. в данном случае происходит пауза при выполнении кода в 1 секунду. В нашем коде находится 3 таких строчки и т.к. код выполняется в постоянном цикле, то каждый цикл будет длиться 1+1+3=5 сек.
Еще одна полезная строчка кода: Debug.Print(«Temp: » + tempc.ToString(«F2»)); которая выводит измеренную температуру в окно отладки.
Debug.Print() очень полезная функция и она незаменима при отладке приложения.
В итоге, после заливки программы в контроллер мы увидим следующие данные в окне вывода:
Теперь немного усложним код. Для того, чтобы видеть, что измерение температуры сделано я решил задействовать встроенный LED. Для этого, я написал небольшую функцию:
Данная функция заставляет мигать встроенный на плату светодиод (объект «led») количество раз, равное «num». Пауза 50мс.
Теперь, вызвав данную функцию скажем так: BlinkLED(150), светодиод моргнет 150 раз.
Также, мы немного переделаем наш код, убрав измерение температуры из цикла в отдельную функцию. После модернизации код будет выглядеть так:
В код я добавил комментарии, поэтому думаю все понятно. Как видите, в основном цикле программы теперь всего 4 строчки кода. Последней строкой в цикле стоит пауза 3 сек, но учтите, что в функции GetTemperature() находится еще 2 паузы по 1 сек. Поэтому выполнение данной функции занимает 2 секунды.
Каждые 10 секунд я буду сохранять полученную информацию о температуре с датчика, в файл temp.csv. Помимо температуры, чтобы в дальнейшем построить наглядный график, необходимо сохранять и время. В Panda, есть встроенные часы реального времени RTC. Но, для того, чтобы они сохраняли время после перезагрузки и отключения питания, необходимо подключить внешнюю батарейку. В начале я их и хотел задействовать, но честно говоря немного поэкспериментировав не стал этого делать, да и для моей задачи они не нужны. Тем более в Excel для меня стала проблема построить график, где по X-были данные формата даты (ну не силен я в Excel, мне легче в PHP график сделать).
Поэтому я использовал обычный инкремент, каждые 10 секунд увеличивавшийся на 1. Вот конечный код:
Скриншот используемых библиотек в обозревателе:
По поводу записи на SD-карту, я не стал заморачиваться с сигналами Insert и Eject карты, проверкой наличия файла перед созданием и прочее, все это с примерами хорошо описано здесь
Тем более, программа хорошо работала и не прерывалась, даже если я на лету вынимал/вставлял карту в контроллер.
Прошу еще обратить внимание, что при создании объекта на файл, я использовал директиву FileMode.Append, которая дописывает в конец файла, но не создает его, поэтому файл уже должен быть создан на карте. Для создания файла используется FileMode.Create.
Правильно конечно было бы, проверить файл на существование и если нет, то создать его, если есть, то очистить и писать в него данные.
Итак, температурный датчик был примотан изолентой к испытуемому соленоиду, и контроллер запущен. После снятия показаний в файле появятся записи вида:
Как видим, всего, произведено 280 измерений, т.е. по времени заняло 280*10=2800 сек или 46 минут.
Но тут подвернулась реальная задача измерения температуры нагрева катушки соленоида ЭМ-клапана в длительном промежутке времени. В дальнейшем я решил писать новые топики с практической реализацией и в каждом топике затрагивать немного теоретической части. Т.о. мы будем медленно, но верно продвигаться от более простых, к более сложным проектам.
Итак, имеем популярный цифровой датчик температуры DS18B20 (даташит). Подключил я его по трехпроводной схеме (с внешним питанием). Здесь можно посмотреть схему подключения к Arduino, у нас будет тоже самое подключение, единственное пин данных — D4.
У GHI хорошая техподдержка и сообщество, и для очень многих шилдов, сенсоров, LCD-экранчиков и т.п. уже имеются готовые библиотеки и примеры программ. Естественно, работа плат FEZ с таким популярным датчиком как DS18B20 там тоже освещена. Я не стал изобретать велосипед и воспользовался вот этим рабочим кодом.
Убрав, немного лишнего кода в итоге получилось вот что:
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;
namespace DS18B20
{
public class Program
{
public static OneWire ow = new OneWire((Cpu.Pin)FEZ_Pin.Digital.Di4);
public static void Main()
{
while (true)
{
int temp;
int sign;
float tempc = 0;
byte[] romid = new byte[8];
byte[] readall = new byte[9];
ow.Reset();
ow.WriteByte(0x33);
ow.Read(romid, 0, 8);
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0x44); // Start temperature conversion
Thread.Sleep(1000); //wait for conversion to finish
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0xBE); // Read Scratchpad
Thread.Sleep(1000); //wait for conversion to finish
ow.Read(readall, 0, 9);
temp = readall[0]; // LSB
temp |= (ushort)(readall[1] << 8); // MSB
//determine if the reading is < 0
sign = temp & 0x8000;
if (sign != 0)
{ // value is < 0
temp = (temp ^ 0xFFFF) + 1;
tempc = (float)(((temp >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)) * -1.00); //See datasheet - way to increase resolution
}
else
{
tempc = (float)((temp >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)); //See datasheet - way to increase resolution
}
Debug.Print("Temp: " + tempc.ToString("F2"));
Thread.Sleep(3000);
}
}
}
}
Вверху мы видим новую строчку: using GHIElectronics.NETMF.Hardware;
Это необходимо для работы с протоколом OneWire, по которому работает датчик.
Строкой public static OneWire ow = new OneWire((Cpu.Pin)FEZ_Pin.Digital.Di4); мы показываем, что устройство OneWire подключено к 4 выводу Panda II.
Однако, это еще не все. Если сделать новый проект и скопировать туда данный код, то вы увидите следующие ошибки:
Дело в том, что данную библиотеку необходимо включить в проект в обозревателе решений (ссылки)
Для этого, нажимаем правой клавишей мыши на "Ссылки" и выбираем "Добавить ссылку"
В открывшемся окне «AddReference» вы увидите большой список библиотек. Вам необходимо выбрать библиотеку GHIElectronics.NETMF.Hardware:
Теперь ошибок не будет, и код будет компилироваться без ошибок.
В коде есть такая строка: Thread.Sleep(1000); Как можно догадаться, это пауза. Задается в миллисекундах. Т.е. в данном случае происходит пауза при выполнении кода в 1 секунду. В нашем коде находится 3 таких строчки и т.к. код выполняется в постоянном цикле, то каждый цикл будет длиться 1+1+3=5 сек.
Еще одна полезная строчка кода: Debug.Print(«Temp: » + tempc.ToString(«F2»)); которая выводит измеренную температуру в окно отладки.
Debug.Print() очень полезная функция и она незаменима при отладке приложения.
В итоге, после заливки программы в контроллер мы увидим следующие данные в окне вывода:
Теперь немного усложним код. Для того, чтобы видеть, что измерение температуры сделано я решил задействовать встроенный LED. Для этого, я написал небольшую функцию:
public static void BlinkLED(int num)
{
int count = 0;
num = num * 2;
bool ledState = false;
while (count < num)
{
ledState = !ledState;
led.Write(ledState);
count++;
Thread.Sleep(50);
}
led.Write(false);
}
Данная функция заставляет мигать встроенный на плату светодиод (объект «led») количество раз, равное «num». Пауза 50мс.
Теперь, вызвав данную функцию скажем так: BlinkLED(150), светодиод моргнет 150 раз.
Также, мы немного переделаем наш код, убрав измерение температуры из цикла в отдельную функцию. После модернизации код будет выглядеть так:
using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;
namespace DS18B20
{
public class Program
{
static float temp; //Температура
public static OneWire ow = new OneWire((Cpu.Pin)FEZ_Pin.Digital.Di4);
public static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false); //Инициализация LED
public static void Main()
{
while (true)
{
temp = GetTemperature(); //Считываем показания температуры с датчика DS18B20
Debug.Print("Temp: " + temp); //Вывод данных текущей температуры
BlinkLED(10); //Мигаем 10 раз встроенным LED
Thread.Sleep(3000); //Пауза 3 сек.
}
}
public static float GetTemperature()
{
int temp2;
int sign;
float tempc = 0;
byte[] romid = new byte[8];
byte[] readall = new byte[9];
ow.Reset();
ow.WriteByte(0x33);
ow.Read(romid, 0, 8);
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0x44); // Start temperature conversion
Thread.Sleep(1000); //wait for conversion to finish
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0xBE); // Read Scratchpad
Thread.Sleep(1000); //wait for conversion to finish
ow.Read(readall, 0, 9);
temp2 = readall[0]; // LSB
temp2 |= (ushort)(readall[1] << 8); // MSB
//determine if the reading is < 0
sign = temp2 & 0x8000;
if (sign != 0)
{ // value is < 0
temp2 = (temp2 ^ 0xFFFF) + 1;
tempc = (float)(((temp2 >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)) * -1.00); //See datasheet - way to increase resolution
}
else
{
tempc = (float)((temp2 >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)); //See datasheet - way to increase resolution
}
return tempc;
}
public static void BlinkLED(int num)
{
int count = 0;
num = num * 2;
bool ledState = false;
while (count < num)
{
ledState = !ledState; //Инвертируем предыдущее состояние
led.Write(ledState);
count++;
Thread.Sleep(50);
}
led.Write(false); //Чтобы не оставался включенным
}
}
}
В код я добавил комментарии, поэтому думаю все понятно. Как видите, в основном цикле программы теперь всего 4 строчки кода. Последней строкой в цикле стоит пауза 3 сек, но учтите, что в функции GetTemperature() находится еще 2 паузы по 1 сек. Поэтому выполнение данной функции занимает 2 секунды.
Логгирование
На плате FEZ Panda 2 присутствует встроенный слот для MicroSD карт. Его я и решил использовать для сохранение данных о температуре.Каждые 10 секунд я буду сохранять полученную информацию о температуре с датчика, в файл temp.csv. Помимо температуры, чтобы в дальнейшем построить наглядный график, необходимо сохранять и время. В Panda, есть встроенные часы реального времени RTC. Но, для того, чтобы они сохраняли время после перезагрузки и отключения питания, необходимо подключить внешнюю батарейку. В начале я их и хотел задействовать, но честно говоря немного поэкспериментировав не стал этого делать, да и для моей задачи они не нужны. Тем более в Excel для меня стала проблема построить график, где по X-были данные формата даты (ну не силен я в Excel, мне легче в PHP график сделать).
Поэтому я использовал обычный инкремент, каждые 10 секунд увеличивавшийся на 1. Вот конечный код:
using System;
using System.Threading;
using System.IO;
using System.Text;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.IO;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.IO;
namespace DS18B20
{
public class Program
{
static float temp; //Температура
public static OneWire ow = new OneWire((Cpu.Pin)FEZ_Pin.Digital.Di4);
public static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false); //Инициализация LED
public static PersistentStorage sdPS = new PersistentStorage("SD"); //Инициализация SD-cсота
public static void Main()
{
//DateTime time = new DateTime(2011, 10, 29, 17, 42, 00); //Задание времени
//Utility.SetLocalTime(time); //Установка времени
int cnt = 0;
while (true)
{
temp = GetTemperature(); //Считываем показания температуры с датчика DS18B20
//Debug.Print(DateTime.Now.ToString());
Debug.Print("Temp: " + temp); //Вывод данных текущей температуры
BlinkLED(10); //Мигаем 10 раз встроенным LED
cnt++;
WriteToSD(temp.ToString() + ";" + cnt); //Запись данных на SD карту
//WriteToSD(temp.ToString() + ";" + DateTime.Now.ToString());
Thread.Sleep(8000); //Пауза 8 сек.
}
}
public static float GetTemperature()
{
int temp2;
int sign;
float tempc = 0;
byte[] romid = new byte[8];
byte[] readall = new byte[9];
ow.Reset();
ow.WriteByte(0x33);
ow.Read(romid, 0, 8);
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0x44); // Start temperature conversion
Thread.Sleep(1000); //wait for conversion to finish
ow.Reset();
ow.WriteByte(0x55);
ow.Write(romid, 0, 8);
ow.WriteByte(0xBE); // Read Scratchpad
Thread.Sleep(1000); //wait for conversion to finish
ow.Read(readall, 0, 9);
temp2 = readall[0]; // LSB
temp2 |= (ushort)(readall[1] << 8); // MSB
//determine if the reading is < 0
sign = temp2 & 0x8000;
if (sign != 0)
{ // value is < 0
temp2 = (temp2 ^ 0xFFFF) + 1;
tempc = (float)(((temp2 >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)) * -1.00); //See datasheet - way to increase resolution
}
else
{
tempc = (float)((temp2 >> 1) - 0.25 + ((16.00 - readall[6]) / 16.00)); //See datasheet - way to increase resolution
}
return tempc;
}
public static void BlinkLED(int num)
{
int count = 0;
num = num * 2;
bool ledState = false;
while (count < num)
{
ledState = !ledState; //Инвертируем предыдущее состояние
led.Write(ledState);
count++;
Thread.Sleep(50);
}
led.Write(false); //Чтобы не оставался включенным
}
public static void WriteToSD(string TextToSD)
{
sdPS.MountFileSystem(); // Mount the file system
string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
FileStream FileHandle = new FileStream(rootDirectory + @"\temp.csv", FileMode.Append);
byte[] data = Encoding.UTF8.GetBytes(TextToSD+"\r\n");
FileHandle.Write(data, 0, data.Length); //Запись данных
FileHandle.Close();
sdPS.UnmountFileSystem();
//Thread.Sleep(Timeout.Infinite);
}
}
}
Скриншот используемых библиотек в обозревателе:
По поводу записи на SD-карту, я не стал заморачиваться с сигналами Insert и Eject карты, проверкой наличия файла перед созданием и прочее, все это с примерами хорошо описано здесь
Тем более, программа хорошо работала и не прерывалась, даже если я на лету вынимал/вставлял карту в контроллер.
Прошу еще обратить внимание, что при создании объекта на файл, я использовал директиву FileMode.Append, которая дописывает в конец файла, но не создает его, поэтому файл уже должен быть создан на карте. Для создания файла используется FileMode.Create.
Правильно конечно было бы, проверить файл на существование и если нет, то создать его, если есть, то очистить и писать в него данные.
Итак, температурный датчик был примотан изолентой к испытуемому соленоиду, и контроллер запущен. После снятия показаний в файле появятся записи вида:
33.1875;9Открываем файл в Excel и строим по полученным данным график:
33.5;10
33.8125;11
34.1875;12
34.5;13
34.75;14
35.0625;15
35.375;16
35.625;17
35.9375;18
36.1875;19
36.5;20
36.8125;21
37;22
37.25;23
Как видим, всего, произведено 280 измерений, т.е. по времени заняло 280*10=2800 сек или 46 минут.
4 комментария