FriendlyARM. Работаем с АЦП на C#

ARM
С цифровыми портами ввода/вывода разобрались, но что делать, если нам охота прикрутить к плате устройства с аналоговым выходом (датчики температуры, акселерометры, гироскопы) или просто покрутить имеющийся на плате переменный резистор и увидеть меняющиеся цифры? Нам нужен драйвер для работы с АЦП. В образе системы, который шел в комплекте такой драйвер судя по всему имеется, поскольку в одной из предустановленных демопрограмм можно увидеть работу АЦП. Однако наши узкоглазые товарищи не озаботились поставкой вменяемой документации и исходников демопрограмм для WinCE 6.0 (для WinCE 5.0 есть, но там все немножко по-другому). Посему, будем курить мануалы, форумы и медитировать над системными вызовами.

После энного количества неудачных попыток удалось получить образ с драйвером gpio, который поддерживает и цифровые ноги и аналоговые. Интеграция драйвера в образ не совсем тривиальна. Делаем все то же самое, что и при сборке образа со старым драйвером, кроме следующих шагов:
1) в файл \\WINCE600\\PLATFORM\\Mini2440\\FILES\\platform.reg еще добавляем следующее:
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\ADC]
 "Prefix"="ADC"
 "Dll"="GPIO.DLL"
 "Order"=dword:200
 "Index"=dword:0
 "FriendlyName"="ADC+GPIO Driver"

Затем запускаем команду Build-Advanced Build Commands->Build Project..., после окончания процесса идем в папку c:\\WINCE600\\OSDesigns\\Mini2440\\Mini2440\\RelDir\\Mini2440_ARMV4I_Release\\, ищем там файл gpio.dll и заменяем его файлом gpio.dll, который приложен к статье. После этого в студии запускаем команду Build-Advanced Build Commands->Sysgen. На выходе получим образ с нужными драйверами.
Теперь посмотрим, как с ним работать. В части работы с цифровими ногами все осталось без изменений, а вот для работы с АЦП нало сделать следующее:
Создаем новый проект в студии(см. предыдущую статью), на форму кидаем компоненты, чтобы получилось примерно так (не забываем добавить таймер)

Далее переходим к коду.
Добавляем к стандартным namespace
using System.Runtime.InteropServices;

Подготавливаем необходимые переменные и вызовы функции. Поскольку для этого драйвера нет специального класса, как для цифровых GPIO, необходимо использовать системные функции.
//импортируем необходимые функции из системных библиотек ОС
        // нам нужны три CreateFile, DeviceIoControl, CloseHandle
        [DllImport("coredll.dll")]
        public static extern IntPtr CreateFile(String lpFileName, UInt32 dwDesiredAccess, UInt32 dwShareMode, IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition, UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile);

        [DllImport("coredll.dll")]
        public static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, UInt32 nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, ref int lpBytesReturned, IntPtr lpOverlapped);

        [DllImport("coredll.dll")]
        public static extern bool CloseHandle(IntPtr hDevice);

        //объявляем структуру для обращения к АЦП
        [StructLayout(LayoutKind.Explicit)]
        internal struct GPIO_GET_ADC_VALUE
        {
            [FieldOffset(0)]
            public Channel chNumber;
            [FieldOffset(4)]
            public int adcValue;
        }
        // указатель на буфер
        private IntPtr _pointerToBuffer;
        private const int IOCTL_GPIO_GET_ADC_VALUE = 262156;

        // объявляем рабочие константы для избавления от магических цисел в коде
        const UInt32 OPEN_EXISTING = 3;
        const UInt32 GENERIC_READ = 0x80000000;
        const UInt32 GENERIC_WRITE = 0x40000000;
        const Int32 INVALID_HANDLE_VALUE = -1;

        //объявляем "перечисление" - тип данных, содержащий список констант
        //в данном случае перечисляем здесь каналы АЦП, которых 4-ре штуки.
        public enum Channel : int
        {
            CHANNEL_0 = 0, //AIN0
            CHANNEL_1 = 1, //AIN1
            CHANNEL_2 = 2, //AIN2
            CHANNEL_3 = 3, //AIN3
        }


        //в этой переменной будет храниться указатель на устройство (АЦП)
        //которое мы откроем с помощью функции CreateFile
        private IntPtr hPort;


Теперь необходимо создать две функции, которые позволят нам считывать значения на каналах АЦП. Первая функция обеспечивает низкоуровневое обращение к железу через драйвер и использует неуправляемый код. В событии Form1() необходимо определить буфер _pointerToBuffer = Marshal.AllocHGlobal(8);
private int ReadADCValueFromDevice(Channel ch)
        {

            // создаем структуры с для трансляции данных через вызов DeviceIoControl
            GPIO_GET_ADC_VALUE adcValueStructureToSend = default(GPIO_GET_ADC_VALUE);
            GPIO_GET_ADC_VALUE adcValueStructureReturned = default(GPIO_GET_ADC_VALUE);

            adcValueStructureToSend.chNumber = (ch);
            adcValueStructureToSend.adcValue = 0;

            //Копируем структуру в блок памяти (используем ранее объявленный буфер)
            Marshal.StructureToPtr(adcValueStructureToSend, _pointerToBuffer, false);

            //Делаем низкоуровневый вызов 
            int bytesReturned = 0;
            DeviceIoControl(hPort, IOCTL_GPIO_GET_ADC_VALUE, _pointerToBuffer, 8, IntPtr.Zero, 0, ref bytesReturned, IntPtr.Zero);
                
            //и копируем в блок памяти, то что вернулось                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
            adcValueStructureReturned = (GPIO_GET_ADC_VALUE)Marshal.PtrToStructure(_pointerToBuffer, typeof(GPIO_GET_ADC_VALUE));

            //возвращяем полученные данные пользователю
            return  adcValueStructureReturned.adcValue;
        }

Теперь надо описать вторую функцию, с помощью которой мы сможем считывать данные с АЦП, указывая нужный нам канал и получать данные в виде строки (которая в случае надобности преобразуется в int16 или int32 с помощью функции Convert.ToInt16(32).
private string getAdc(Channel ch)
        {
            UInt32 rOutput = 0;

            //Проверяем, открыто ли устройство (АЦП)
            if (hPort != (IntPtr)INVALID_HANDLE_VALUE)
            {
                rOutput = (uint)ReadADCValueFromDevice(ch);
                //если все пучком, вызываем низкоуровневую функцию
            }
            else rOutput = 0xffff;//если драйвер не работает, вернем число 65535, хотя это можно убрать
            //и в формате строки возвращаем циферку, соответствующую напряжению на канале АЦП
            return rOutput.ToString();
        }

Вот и все. Теперь в событии Form1_Load() открываем наше устройство через драйвер
//При загрузке главного окна приложения
            //открываем устройство GPI0 которое, согласно драйверу, соответствует АЦП
            hPort = CreateFile("GPI0:", GENERIC_READ | GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
            if (hPort == (IntPtr)INVALID_HANDLE_VALUE)
            {
                //если драйвер не ответил, ругаемся
                MessageBox.Show("Open GPI0+ADC Driver Fail");
            }

И можно с ним работать используя функцию getAdc() передав ей нужный нам канал. Вот так это выглядит у меня. Когда мы жмем кнопку «Start ADC», запускается таймер и в текстбоксы на форме выводятся значения на ногах АЦП. К AIN0 (CHANNEL_0) подключен переменный резистор, который есть на самой плате, к AIN1 (CHANNEL_1) — переменный резистор на макетке, средняя нога на AIN1, крайние на 3.3В и GND соответственно. Все входы АЦП расположены на колодке CON4 а плате:

Видео, демонстрирующее полученный результат:


Ссылки:
Библиотека драйвера:
http://rghost.ru/19221921
Образ WinCE 6.0 c нужными дровами (пока китайский, с новым bsp для английского непонятные траблы при сборке)
http://rghost.ru/19310801
Архив с проектом для Visual Studio 2008
http://rghost.ru/19222081

Комментарии (8)

RSS свернуть / развернуть
Спасибо за статью. Пока нет возможности опробовать самому. А этот gpio.dll совместим с платами на 6410? Кстати, прикрепите его к статье.
0
  • avatar
  • ACE
  • 25 августа 2011, 16:09
Видимо ссылки без текста, их не видно.
0
Ссылки поправил, ссылку на образ выложу попозже, забыл залить его из дома, а на работе его нет. Насчет совместимости с другими платами не знаю, если проц тот же, тогда должно работать.
0
Ну всё.Вся клава слюнями заляпана уже.Надеюсь когда я выйду на работу после отпуска, мой mini2440 уже будет лежать на моём столе.
0
  • avatar
  • lejay
  • 25 августа 2011, 18:18
Да, забавно видеть свой код в откоментированном виде по русски )))
Сразу скажу что драйвер от 2440 на 6410 не подойдет в силу иного адресного пространства.

Пример можно взять здесь.

www.friendlyarm.net/forum/attachment/9299

За основу был взять драйвер Доменика (www.domodom.fr) и добавлен ADC. Кстати ему так же можно написать если есть какие-то вопросы.
0
  • avatar
  • iskra
  • 20 октября 2011, 12:35
Ну я в общем-то и делал все опираясь на форум и общаясь с Домиником, чтобы вся полезная инфа была в одном месте и на русском. Спасибо за дрова к АЦП.
0
Кстати странный эффект, сигнал не стоит ровно, а «бегает», сейчас самая большая головная боль. Причем если закоротить 2 АЦП, то на них РАЗНЫЕ показания.
Кстати пример с GPIO для этого же драйвера можно стащить тут www.domodom.fr/documentsJointsSpip/GPIOTest%20csharp.zip
0
  • avatar
  • iskra
  • 20 октября 2011, 17:01
На www.friendlyarm.net/forum/attachment/9299 можно так же найти отдельный драйвер ADC если кому нужно, вместе с исходниками к нему.
0
  • avatar
  • iskra
  • 20 октября 2011, 17:03
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.