Измерение и логгирование температуры с FEZ Panda II

Я долго думал о чем написать следующий пост, debug, потоки (thread), прерывания и т.п для каждого поста как-то мало получится, а одним постом все в куче, тоже не красиво. Да и теории в принципе хватает, это вы и сами сможете почитать, а что непонятно — спросить в моем блоге.
Но тут подвернулась реальная задача измерения температуры нагрева катушки соленоида ЭМ-клапана в длительном промежутке времени. В дальнейшем я решил писать новые топики с практической реализацией и в каждом топике затрагивать немного теоретической части. Т.о. мы будем медленно, но верно продвигаться от более простых, к более сложным проектам.

Итак, имеем популярный цифровой датчик температуры 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:
Подключение библиотеки 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
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
Открываем файл в Excel и строим по полученным данным график:
График изменения температуры
Как видим, всего, произведено 280 измерений, т.е. по времени заняло 280*10=2800 сек или 46 минут.

4 комментария

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.