Подключаем 4 АЦП к Cubieboard по SPI

Как уже говорилось, модуль ядра — лучший путь для подключения специфического оборудования и использования его в собственном ПО. Продолжая тему программирования модулей ядра, сегодня решим уже реальную задачу: научимся инициализировать регистры ввода-вывода, использовать их и создадим интерфейс модуля.

Задача

Есть платка с 4 АЦП MCP3201. На штыри выведено: общий CS, общий CLK и 4 выхода данных от АЦП. Сделано именно так, потому что мне было необходимо минимизировать время между измерениями всех 4х каналов, т.к. эти напряжения будут использоваться для мат. обработки. Хотим мерять напряжение и получать его в нашей проге на одноплатнике.

Стандартные подходы?

SPI
На штыри Cubieboard2 выведен аппаратный SPI. Но наш SPI очень нестандартный, поэтому не подходит.

МК+UART
Путь начинающего радиолюбителя. Ставим какой-нибудь мелкий МК, который читает показания АЦП и шлет данные по UART. Внутри одноплатника все просто — обычный COM-порт. Благо аппаратные UART тоже есть и на разъем выведено несколько штук.

GPIO
Ручной бит-банг нашего модифицированного SPI с помощью sunxi-gpio. Максимальная частота которую мне реально удалось получить — около 100 КГц. Что в принципе и норм, но наше приложение постоянно будут тормозить всякие web-сервера(мы же захотим мониторить напряжения через браузер со смартфона?), SSH, и другие запущенные процессы. А это скажется на временных диаграммах нашего SPI.

/dev/mem
Прямо как в микроконтроллере пишем прямиком в регистры процессора. Гораздо быстрее чем GPIO, но нас так-же прерывают посторонние процессы + использование /dev/mem пользовательскими приложениями требует root-прав и крайне не рекомендуется. Фу-фу-фу вобщем.

Что делать?

Использование /dev/mem выглядит наиболее привлекательным, но из приложения использовать не хотим. Помимо самого приложения в Linux есть ядро. Функциональность ядра можно расширить модулями. Ядру можно пользоваться всеми доступными регистрами системы. + По сути мы пишем драйвер для устройства (АЦП с определенным интерфейсом), т.е. будем использовать модули ядра по прямому их предназначению.

Решение

В качестве интерфейса с АЦП нам необходимо 6 выводов, 2 выхода и 4 входа. Так же АЦП надо питать напряжением 3.3 В, чтобы не ставить преобразователи уровней. Поэтому от Cubieboard к плате подключаем выводы на колодке возле SATA:

26 PD21 (LCDD21) — D1 — in
28 PD23 (LCDD23) — D2 — in
29 PD24 (LCDCLK) — CS — out
32 PD25 (LCDDE) — clk — out
30 PD26 (LCDHSYNC)-VGA-HSYNC — D3 — in
27 PD27 (LCDVSYNC)-VGA-VSYNC — D0 — in
44 3.3V (nc in 2012-08-08). У меня ревизия 09-09, поэтому напряжение есть.
38 Ground


Т.е. видим, что заняли некоторые выводы интерфейсов LCD и VGA. В script.fex их надо на всякий случай отключить. Так как я не планировал в этом устройстве наличие экрана, то ничего не потеряно. Вы же можете выбирать любые другие пины, единственное что для удобства стоит использовать только один порт(например только PORTD, т.к. он используется в первую очередь для подключения LCD по параллельному интерфейсу, а это надо не многим) и ниже мы увидим почему.

Разберемся с устройством GPIO
Откроем описания процессора из предыдущей статьи на странице 240. Там начинается описание контроллера портов ввода-вывода. Там же написано что имеются 32 входа с генерацией прерывания, но они нас пока не интересуют. Далее идет таблица размещения функций на выводах. Самое интересное начинается со странице 247: тут указан адрес блока памяти, в котором расположены управляющие регистры — 0x01C20800. Эта константа нам понадобится для обращения к регистрам, т.к. в даташите они заданы виде смещений относительно начала блока. Ниже видим, что для каждого порта есть 4 вида регистров:
  • Pn_CFG0-Pn_CFG3 — регистры мультиплексирования: указывается либо специальная функция, либо вход или выход. до 8 вариантов для каждого вывода.
  • Pn_DAT — состояние порта. Тот самый регистр, с которым будем работать.
  • Pn_DRV0-Pn_DRV1 — настройка выходного тока. В зависимости от значения двух битов(0-3) на выход, ток короткого замыкания устанавливается на уровне 10 — 40 мА соответственно.
  • Pn_PUL0-Pn_PUL1 — настройка подтяжки входов.

На каждый вывод в регистрах Pn_CFG зарезервировано 3 бита и 1 бит из каждой тетрады не используется. Видимо сделано для удобства: тетрада на вывод. Значение 0 для всех выводов используется как логический вход, 1 — выход. Остальные значения — выбор специальной функции, для каждого вывода свой набор.
Нас интересует PORTD — листаем на страницу 268 и находим в каких регистрах находятся настройки выбранных выводов и смотрим смещения этих регистров. Это регистры: PD_CFG2 — 0x74 и PD_CFG3 — 0x78. В них надо будет записать значения:

PD_CFG2 = 0x0X0XXXXX;
PD_CFG3 = 0xXXXX0011;

Где X — значение которое необходимо сохранить. Учитывая, что мы работаем напрямую с железом и порты используются системой (для общения с флешкой, с контроллером Ethernet, по I2C подключен контроллер питания), то изменять что попало и как попало нельзя.
Подтяжку и выходной ток перенастраивать не будем.
Доступ к регистрам
Просто так записать значение в регистр по какому-либо адресу не выйдет. В Linux адресация памяти — довольно сложная и хитрая штука, одни области отражаются в другие с изменением адресации и пр. Даже из модуля ядра к произвольному регистру просто так не обратиться. Но нет ничего невозможного, для этих целей предусмотрены функции чтения и записи регистров ввода-вывода. Так как наши регистры 32 разрядные — нам понадобятся 32 разрядные функции:
int reg = ioread32(void *addres);
iowrite32(int data, void *addres);

Из имен функций думаю все очевидно: это чтение значения по адресу в переменную, и наоборот — запись значения переменной в регистр с адресом. Вот тут то нам и понадобятся наши адрес страницы с параметрами портов и смещения регистров.
Но сначала необходимо отобразить страницу в область памяти модуля:

#define SW_PA_PORTC_IO_BASE 0x01C20800  //найденный ранее нами адрес начала блока
static void * gpio_map; //указатель на первый байт отображаемой страницы
gpio_map =ioremap(SW_PA_PORTC_IO_BASE, 4096); // это производим перед обращением к регистрам 

Первый параметр ioremap — физический адрес начала отображаемой области, второй — требуемая длинна в байтах. 4096 почти для всех страниц в нашем процессоре.
Теперь у нас есть указатель на начало области памяти, в которую отображаются регистры. В принципе с этим указателем можно работать и без функций io*(), однако такой путь не рекомендуется, так как считается не переносимым и с ним идет борьба. Так же после работы обязательно необходимо освободить память с помощью функции:
iounmap(void* addres);


Для чтения состояния регистра порта D нам необходимо выполнить:
int PD=ioread32(gpio_map+0x7C);

а для записи:
iowrite32(PD,gpio_map+0x7C);

При этом никто не запрещает определить значения смещений с помощью define или вообще:
void* PORTDptr =ioremap(SW_PA_PORTC_IO_BASE+0x7C, 4);
...
iowrite32(PD,PORTDptr);

Такой подход удобен для обращения к регистру с которым будем часто работать, а при настройке удобнее работать через указатель+смещение.
Все эти функции становятся доступными при использовании
#include <asm/io.h>


Интерфейс

Рекомендуемым к применению и распространенным является интерфейс через виртуальную файловую систему sysfs. Это структура каталогов в директории /sys. С файлами можно работать в текстовом режиме, записывая в них и отправляя команды в модуль, либо читать, получая значение из модуля. Работа с этим интерфейсом описана в Модули ядра Linux. Олег Цилюрик: интерфейс /sys. Так же есть пример в директории, которая после первой статьи лежит по адресу: /root/Kexamples.BOOK/sys/:
cd /root/Kexamples.BOOK/sys/
nano xxx.c

Пример несколько избыточен, т.к. написан для работы с различными версиями ядра. Нам же это не надо, поэтому я его чуть-чуть упрощу для понимания и снабжу подробными комментариями.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <linux/version.h>

//создаем буфер для сообщения с фиксированной максимальной длинной.
#define LEN_MSG 160
static char buf_msg[ LEN_MSG + 1 ] = "Hello from module!\n";

//функция x_show вызывается при чтении файла, через который предоставляется интерфейс. 
//для каждого файла назначается своя функция обработчик
// *buf - указатель на буфер, куда положить результат
static ssize_t x_show( struct class *class, struct class_attribute *attr, char *buf ) {
   strcpy( buf, buf_msg );
   printk( "read %d\n", strlen( buf ) );
   return strlen( buf ); // необходимо вернуть длину передаваемого сообщения
}

//аналогично функции x_show, но вызывается при записи в файл интерфейса
// * buf - указатель на буфер входного параметра, count - длинна буфера
static ssize_t x_store( struct class *class, struct class_attribute *attr, const char *buf, size_t count ) {
   printk( "write %d\n" , count );
   strncpy( buf_msg, buf, count );
   buf_msg[ count ] = '\0';
   return count;
}

//декларация интерфейса, здесь мы описали название файла, права доступа, 
//передали указатели на функции - обработчики
CLASS_ATTR( xxx, 0666, &x_show, &x_store);

//указатель на структуру в /sys/class
static struct class *x_class;

int __init x_init(void) {
   int res;
   //создаем класс и указываем имя интерфейса, которое и будет именем каталога в /sys/class
   x_class = class_create( THIS_MODULE, "x-class" );
   if( IS_ERR( x_class ) ) printk( "bad class create\n" );
   //создаем файл интерфеса. x_class - структура класса, в котором расположен интерфейс
   //class_attr_xxx - создается макросом CLASS_ATTR. 
   //xxx на конце - имя объекта, переданное первым параметром в макрос.
   //x_class - имя класса, где создается интерфейс (т.к. у модуля их может быть несколько)
   res = class_create_file( x_class, &class_attr_xxx );
   printk("'xxx' module initialized\n");
   return 0;
}

void x_cleanup(void) {
   //удаляем интерфейс
   class_remove_file( x_class, &class_attr_xxx );
   //и сам класс
   class_destroy( x_class );
   return;
}

module_init( x_init );
module_exit( x_cleanup );
MODULE_LICENSE( "GPL" );

Если скопировать все это в xxx.c и запустить
make
insmod xxx.ko

то в /sys/class должна появиться папка x-class с единственным файлом xxx.
Его можно читать и писать, работает как буфер.

Напишем сами

Теперь мы в принципе готовы написать наш модуль, т.к. все что необходимо изучено. Писать модуль будем так же на основе вышеприведенного примера. Для этого нам понадобится:
  • Изменить имена интерфейса
  • Реализовать настройку выбранных GPIO в функции инициализации
  • Не забыть iounmap() при выгрузке модуля
  • Очистить функцию записи из файла в модуль, т.к. наше устройство Read-Only принципиально.
  • Реализовать функцию чтения показаний АЦП
  • Добавить ее вызов в функцию чтения из модуля
  • Преобразовать полученные показания в строку
Реализацию данного таск-листа я увидел так:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/pci.h>
#include <linux/version.h>
#include <asm/io.h>  //заголовочник для работы с ioread/iowrite
#include <linux/delay.h> //функции задержек

#define SW_PA_PORTC_IO_BASE 0x01C20800

#define LEN_MSG 160
static char buf_msg[ LEN_MSG + 1 ]="";
static void* gpio_map; // общий указатель
static void* PDptr; //указатель на регистр порта D

//в этих переменных хранится последнее значение АЦП
static int ADC0=0;
static int ADC1=0;
static int ADC2=0;
static int ADC3=0;

//собственно функция чтения АЦП
void readADC(){
    //CS PD24 - out
    //clk PD25 - out
    //D1 PD21 - in
    //D2 PD23 - in
    //D3 PD26 - in
    //D0 PD27 - in
    int i;
    int PD = ioread32(PDptr); // Читаем старое значение
    PD=PD&(~(0x1<<24));  //CS = 0;
    PD=PD&(~(0x1<<25));  //CLK = 0; //формируем новое
    iowrite32(PD,PDptr);  // и записываем обратно
    udelay(1); //задержка на обработку CS АЦП
    ADC0=0;
    ADC1=0;
    ADC2=0;
    ADC3=0;
    for (i=14;i>=0;i--){
        PD = ioread32(PDptr);
        PD=PD|(0x1<<25);//CLK=1
        iowrite32(PD,PDptr);
        udelay(1); //задержка для формирования высокого уровня тактов частотой 500 КГц
        ADC1=ADC1<<1;
        ADC2=ADC2<<1;
        ADC3=ADC3<<1;
        ADC0=ADC0<<1;
        PD=ioread32(PDptr); // читаем входы
        // и обновляем значения АЦП
        if (PD&(0x1<<21)){
            ADC1=ADC1|1;
        }
        if (PD&(0x1<<23)){
            ADC2=ADC2|1;
        }
        if (PD&(0x1<<26)){
            ADC3=ADC3|1;
        }
        if (PD&(0x1<<27)){
            ADC0=ADC0|1;
        }
        PD = ioread32(PDptr);
        PD=PD&(~(0x1<<25));//CLK = 0;
        iowrite32(PD,PDptr);
        udelay(1); //формирование низкого уровня тактового сигнала
    }
    // пересчитываем значения в миливольты. (формулы не очевидные, т.к. на входах делители)
    ADC0=((ADC0&0x00000FFF)*1000)/61;
    ADC1=((ADC1&0x00000FFF)*1000)/61;
    ADC2=((ADC2&0x00000FFF)*1000)/61;
    ADC3=((ADC3&0x00000FFF)*1000)/61;
    PD = ioread32(PDptr);
    PD=PD|((0x1<<24));//CS=1
    iowrite32(PD,PDptr);
}

//чтение из модуля
static ssize_t x_show( struct class *class, struct class_attribute *attr, char *buf ) {
    readADC(); //обновляем значения из АПЦ
    sprintf(buf_msg, "ADC: %d %d %d %d mV\n",ADC0,ADC1,ADC2,ADC3); //Формируем строку - ответ
    strcpy( buf, buf_msg ); //копируем ее в буфер результата
    return strlen( buf );
}

// запись в модуль
static ssize_t x_store( struct class *class, struct class_attribute *attr, const char *buf, size_t count ) {
// ничего не делаем
    return count;
}

CLASS_ATTR( ADCs, 0666, &x_show, &x_store);

static struct class *x_class;

int __init x_init(void) {
    int res;
    int PD_CFG2;
    int PD_CFG3;
    x_class = class_create( THIS_MODULE, "inputADC" );
    if( IS_ERR( x_class ) ) printk( "bad class create\n" );
    res = class_create_file( x_class, &class_attr_ADCs );
    printk("ADC: interface init ok\n");

    // инициализируем GPIO
    gpio_map =ioremap(SW_PA_PORTC_IO_BASE, 4096);
    PDptr = ioremap(SW_PA_PORTC_IO_BASE+0x7C,4);
    //сохраняем начальные значения
    PD_CFG2 = ioread32(gpio_map+0x74);
    PD_CFG3 = ioread32(gpio_map+0x78);
    //CS PD24 - out
    //clk PD25 - out
    //D3 PD26 - in
    //D0 PD27 - in
    //PD_CFG3 весь наш, поэтому можно писать полностью
    PD_CFG3=0x00000011;
    //D1 PD21 - in
    //D2 PD23 - in
    //сбрасываем тетрады требуемых входов, сохраняя остальные настройки
    PD_CFG2=PD_CFG2&0x0F0FFFFF;
    // пишем настройки в регистры
    iowrite32(PD_CFG2,gpio_map+0x74);
    iowrite32(PD_CFG3,gpio_map+0x78);
    printk("ADC module initialized\n");
    iounmap(gpio_map); // этот указатель больше не понадобится
    return 0;
}

void x_cleanup(void) {
    iounmap(PDptr); //забываем указатель на регистр порта D
    //удаляем интерфейс
    class_remove_file( x_class, &class_attr_ADCs );
    class_destroy( x_class );
    return;
}

module_init( x_init );
module_exit( x_cleanup );
MODULE_LICENSE( "GPL" );

Этот код вставляем в исходный xxx.c примера и компилируем.
make

У меня компилируется :)

Результат

Для экспериментов сделал себе такой вот переходник на стандартный шаг штырей:

В качестве теста подключил к одному из АЦП 2 банки Li-Pol батареи:
[root@alarm adcs]# insmod xxx.ko
[root@alarm adcs]# cat /sys/class/inputADC/ADCs
ADC: 0 0 0 7901 mV
[root@alarm adcs]# cat /sys/class/inputADC/ADCs
ADC: 0 0 0 7934 mV
[root@alarm adcs]# cat /sys/class/inputADC/ADCs
ADC: 0 0 0 7918 mV
Китайский мультиметр показал 7.96В, что в принципе неплохо для АЦП, рассчитанного на работу до 65В(те самые делители на входе).

Что в этом коде плохо
Код работает, АЦП меряет, но есть минимум 3 проблемы:
  • не совсем корректно реализована инициализация. Если что-то вдруг идет не так, то все сделанные изменения в системе в модулях ядра принято отменять в обратном порядке с момента где произошла ошибка. Этого нет. Но т.к. я сам себе злой буратино, работает и хорошо
  • Используются задержки, не освобождающие ядро. С этим надо что-то делать, т.к. для гигагерцного камня 1 микросекунда — 1000 тактов. Не так уж и много, конечно, но в будущем могут потребоваться задержки и длиннее, а там уже единственный выход — прерывания. я сейчас с этим разбираюсь, но там есть загвоздка… Как разберусь — будет статья по таймерам и прерываниям.
  • Игнорируется возможность попытки многопоточного доступа. С реализацией тут есть варианты, а мое приложение работать будет только в единственном экземпляре и потоке, поэтому это тоже пока остается как есть.

А пока я разбираюсь с прерываниями от таймеров, может кому-то интересно что-то еще?

  • +14
  • 03 августа 2014, 11:21
  • kest

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

RSS свернуть / развернуть
Вы задумали полезное дело, написать цикл статей по модулям Linux.

Единственно, т. к. статьи носят обучающий характер, старайтесь избегать неточностей. Немного критики, их того, что бросилось в глаза:

помеченные __init и _exit — все очевидно, функции выполняемые при инициализации и выгрузке модуля. Макросы module_* ниже так же указывают какая функция должна выполняться при старте, а какая при выгрузке.

__init говорит в какой секции будет помещен код функции, а вот макрос module_init() формирует точку входа.

printk — аналог функции printf, но для пространства ядра. Она печатает сообщение в системный лог.

Она печатает в буфер, а в лог данные попадают через  /proc/kmsg, klogd, syslogd. Проще всего логи ядра смотреть командой dmesg, она читает напрямую из буфера

При этом использование «стандартных» библиотек Си недоступно.


Допустимо, но не все функции. Вот, например, вы же используете sprintf(), strcpy();

Самое главное замечание:
Рекомендуемым к применению и распространенным является интерфейс через виртуальную файловую систему sysfs.

Sysfs, по своей задумке, используется для чтения/записи конфигурационных параметров ядра. Обычно данные через этот интерфейс не передают. Но, в качестве примера работы, конечно, можно.

Для обмена данными с модулем используются ноды устройств (devfs)

В любом случае, спасибо за статью и удачи вам в конкурсе.
+2
  • avatar
  • e_mc2
  • 03 августа 2014, 19:02
О, таки есть люди «в теме».
По поводу неточностей — виноват, будем стараться:)
А касаемо sysfs: тоже читал, что оно для настроек ядра… Но модуль sunxi-gpio(там вся работа ведется через /sys/class), в котором я подсматривал некоторые моменты, слишком уж убедил, что sysfs это хорошо:)
0
Идеология интерфейса sysfs не предусматривает, что файл будет открыт одновременно из нескольких потоков/процессов и каждый туда будет что-то асинхронно писать, и хотеть получить ответ именно на свой запрос. Работать через него можно, но для очень примитивных случаев.

А вот в стандартном интерфейсе, вы «ведите», что кто-то «открыл» устройство, записал данные. Под каждую сессию, обычно, создается контекст и вы можете общаться с каждым обратившимся к устройству процесоом адресно и индивидуально.
+1
А Вы тоже занимаетесь разработкой аппаратуры под линукс? Откуда информацию берете? :)
0
Я занимался, когда-то давно. С тех пор очень многое забылось, очень многое в уже поменялось. Книг тогда особо не было (т. к. написание драйверов было под линукс было жуткой экзотикой, как сейчас, например написание под QNX). В основном — помогал гугл и просмотр исходников других модулей.
0
если кто не читал, то вот habrahabr.ru/post/206148/
там описан процесс создания драйвера + файла устройства

я старый пердун уже и давно новые линупсы не ковырял, был удивлен, что автор там какие-то классы использует и доступ не через /dev/adc/adc0
0
да-да, эту статью видел. И ссылку из нее к The Linux Kernel Module Programming Guide читал.
0
Скажите плиз, как называются гнезда под разъемы кубика?
0
0
Спасибо)
0
есть существенно ближе :D
0
О, так его зовут ZL265-40DG. Еще лучше. Еще спасибее
0
Да, в этом плане у космоса провал — нужно знать в каком разделе искать. А чтобы знать где искать — хоть один раз нужно прошерстить раздел.
0
МК+UART — это не путь начинающего радиолюбителя. Это как-раз самое простое и надежное промышленное решение — весь реал-тайм на МК, потом неторопливый обмен с Линухом. Дальнейшее развитие этой идеи — МК заменяется на FPGA (когда скорости не хватает) или ASIC (для совсем уж жирных фирм).
0
  • avatar
  • dev
  • 03 августа 2014, 23:13
static ssize_t x_show( struct class *class, struct class_attribute *attr, char *buf )
Немного странно выглядит интерфейс. Как я понимаю, буфер выделяет вызывающая сторона, но почему в таком случае в x_show не передается длина буфера?

Алсо, разве не надо операции read-modify-write с регистрами защищать чем-то вроде критических секций?
0
  • avatar
  • Vga
  • 03 августа 2014, 23:18
Немного странно выглядит интерфейс

В кернел API, есть много подобных моментов, когда интерфейс кажется нелогичным.

В этом случае, обмен данными происходит через промежуточный буфер, размером в PAGE_SIZE. В примере проверка на размер буфера сознательно упущена.

Да, это нельзя понять, можно только запомнить. Но такова спецификация внутреннего АПИ, там есть много моментов, которые позволяют «выстрелить себе в ногу». Считается, что раз ты пишешь модуль — значит ты знаешь, что ты делаешь…
0
В этом случае,

Имеется виду конкретный интерфейс

sysfs allocates a buffer of size (PAGE_SIZE) and passes it to the method.
0
В примере проверка на размер буфера сознательно упущена. 


Хотя пример какой-то неправильный.

static ssize_t x_store( struct class *class, struct class_attribute *attr, const char *buf, size_t count ) {
   printk( "write %d\n" , count );
   strncpy( buf_msg, buf, count );
   buf_msg[ count ] = '\0';
   return count;
}


Код

strncpy( buf_msg, buf, count );


позволяет выйти за границы

static char buf_msg[ LEN_MSG + 1 ] = "Hello from module!\n";
0
Работал с этой платой только 1 версии, бралась как ядро для аналога цифрового осциллографа, те задача снимать с платы обработки быстрый цифровой поток (10М) обрабатывать итд… точно так же сталкнулся с ограничениями GPIO через sysfs на 100Кгц… и в общем то забросил, вариант доступа к GPIO через /dev/mem увеличивает скорость до 2 мгц но это предел…
единственный выход это интерфейс видеокамеры, но разобраться с ним у меня не хватило знаний… возможно вы подскажите в какую сторону копать…
0
  • avatar
  • vadis
  • 03 августа 2014, 23:26
Смотря какой входной сигнал.
Если параллельный то варианты:
использовать младшие разряды какого либо порта и как в этом модуле: читать, накладывать маску и отдавать. Но скорее всего стоит использовать DMA. А еще как минимум в А20 есть специальный потоковый порт: страница 784 в мануале
Если какой-то последовательный интерфейс — тот уже сложнее. Кстати я на неделе попробую померить максимальную частоту, генерируемую из модуля ядра.
0
речь идет о стыковке с ПЛИС то есть интерфейс можно подобрать, но предполагалось использовать паралельный 8бит + управление на частоте 10 мгц…
К сожалению использование интерфейсов выходящих за рамки стандартных(существующих) драйверов для линукс(куби) для меня недоступно, я пока все это только изучаю, поэтому и отрагировал на публикацию.
Присоединяюсь к e_mc2 цикл статей по этим вопросам нужно приветствовать…
0
пояснить пожалуйста строчку
udelay(1); //задержка для формирования высокого уровня тактов частотой 500 КГц
каким образом образовалась эта мистическая цифра 500кгц?
0
в смысле в модуле гарантируется атомарность, нет скейлинга частоты, нет планировщика, под нагрузкой других процессов не может переключить контекст?
0
Мифическая цифра 500КГц образовалась из ожидаемой максимальной тактовой частоты АЦП
Все это конечно не гарантируется, разве что изменение частоты должно компенсироваться(не проверял), т.к. есть как минимум прерывания. Но в обычном МК прерывания тоже есть, но с этим все мирятся.
0
Попробовал я с Пидорой, ужасно не понравилось
Консольные приложения писать не интересно, без GUI и библиотек от того же Qt, очень трудоемко и тупо.
Если писать в Qt ужасные пляски с кроскомпиляцией…
Вот поговаривают, что скоро Intel выпустит одноплатные ПК на x86 архитектуре, вот это будет интересно )
+1
Так вроде есть QT для кубика linux-sunxi.org/Qt5_For_Mali_Binaries или это не оно?
0
ну и как в этом деле отлаживать и искать баги? )
Ибо не во всех случаях это возможно сделать на ПК.
Даже не касаясь ного-дрыга
0
Мое мнение, что все эти «одноплатные копутеры» — исключительно способ обогатится на волне интереса любителей.
По железу там цены явно сильно завышены, а инструментов как таковых нет, gcc и блокнот ) Уж тогда интереснее платы от Android…
И совсем не понятно для чего они вообще нужны, любители тратят по 3-6 тысяч на плату+корпус, настраивают сервер и т.д. чтобы включать лампочку )
А для профессионального применения — слишком дорого, ненадежно и слабо
0
А что, нельзя запустить отладчик прямо на распи? Или дебаг-сервер для GDB.
0
  • avatar
  • Vga
  • 02 сентября 2014, 09:39
можно, можно даже дождаться когда он что-то отобразит )
Но это не для слабонервных…
0
Сейчас делаем систему на одноплатнике. Не Кубебоард но по мощьность вполне сравним. Тоже АРМ. К нему подсоеденено АЦП через USB. И пара канов на борту. Плюс радиомодуль по ком порту ну и ногодрыг по мелочи. Отлажимаюсь через GDB. Вообще никаких проблем. Все быстро без задержек.
Макет этой системы, а вернее сказать идею проверял на Малинке. И тоже никаких нареканий к отладчику.
0
  • avatar
  • kos
  • 02 сентября 2014, 16:03
Ну и кроме того на многих однаплатниках собраны всякие там плееры. Это тоже ниша. Возможно кто-то хочет запилить плеер для себя так как хочется ему. Вполне себе ниша. Или веб-сервачек чисто для своей системы. Кторый как-то куда-то собирает данные и отображает их. Просто следя за системой из сотни другой датчиков. Всяко дешевле чем комп и по деньгам и по питанию. Тоже ниша. Приминений много.
0
  • avatar
  • kos
  • 02 сентября 2014, 16:08
я новичек совсем в этих делах. Так понимаю что проблема с отладкой большая будет пока не будет нормальной ide с эмулятором или отладчиком. Ну на этих штуках делаются планшеты как-то для них пишется софт и отлаживается все же.А как мк с полноценной графикой мне кажется вполне интересный вариант. Управление умным домом например или мониторинг производственных процессов.
0
А почему нет IDE? Она есть. Eclipce. Я рботаю именно на нем. Отлаживаюсь на целевой плате через сеть. Настроив и подняв DGB.
Я тоже новичок кстати. :-) И мне весьма интерестны Ваши эксперементы с драйверами. Так что слежу очень внимательно за вами! :-) Сам до драверов не дошел. Пока только прогу написал которая собирает обрабатывает и немного управляет. Все было отлажено (и отлаживается) через связку Eclipce+DGB
0
  • avatar
  • kos
  • 02 сентября 2014, 16:14
подскажите подробней где можно почитать как эклипс с кубиком подружить. Что таке DGB это аппаратная штука или программная?
и задружит ли такая связка для графических приложений? Мой опыт совсем скромен — ардуино и авр. Ну и 5 лет вуза по специвльности эвм правда 15 лет назад. Сейчас второе дыхание открылось.
0
GDB это, по видимому. Он же позволяет удаленную отладку.
0
  • avatar
  • Vga
  • 02 сентября 2014, 17:06
Спасибо. Посоветуйте статейку по настройке связки для кросс-компиляции и отладки. ECLIPSE+GDB + платка. Не знакю с какой стороны подойти к этому.
0
Вот ссылочка
takeworld.blogspot.ru/2011/09/arm-linux-eclipse.html
Это все праграмное. Идея в том что Эклипсу вообщем-то все равно на чем Вы будите работать. А в Линуксе все уже украдено до нас. И есть такая классная вещь как DGB -server. Вы его натравливаете на свое приложение а на него Эклипс. И все это прекрасно работает.
Я думаю что и для графических приложений это будет работать не менее классно.
0
  • avatar
  • kos
  • 02 сентября 2014, 17:19
спасибо. Попробуем. я понял что eclipse универсальная ide под разные языки программирования. Думаю что эти платы перспективное направление. Кубик скоро новую плату на А80 выпустит. По ардуино информации и библиотек под разные девайсы море а вот по линукс платам все только зарождается
0
На самом деле всяких разных драйверов море. И если Вы можете найти драйвер в исходниках то скомпилировав его под свой Линукс(версию я имею ввиду) вы получите рабочий драйвер с вероятностью 90%.
0
  • avatar
  • kos
  • 02 сентября 2014, 17:30
А если есть общие вопросы то просто ищите инфу по ним в инете. Даже если решение под ПК скорее всего оно прокатит и на платке. Это я чисто из своего не большого опыта говорю.
0
  • avatar
  • kos
  • 02 сентября 2014, 17:32
В догонку…
Будте готовы к тому что в вашей версии Эклипса не окажется того что описано в этой статье. Когда я ствил Эклипс на еще один комп помимо своего я сталкнулся с этим. Решается это установкой именно CDT версии Eclipce. Ну или пляской с бубном и становкой всяких разных плагинов под него. Я пошел по первому ломовому пути. :-)
0
  • avatar
  • kos
  • 02 сентября 2014, 17:24
УПС! Вы правы! Конечно DGB.
-1
  • avatar
  • kos
  • 02 сентября 2014, 17:15
можно конечно такое и на pc сделать делая контролер связи с нужными интерфейсами но как-то оно привлекательнее в одной платке. на ютубе видел демо проекты на qt для кубика
0
Эклипс под виндоус будет жить или все же под линуксом стоит его ставить?
0
Эклипс и под виндой должен жить. Но честно говоря я пошел по тому пути что программы для Линукс надо писать под Линукс. :-) Хотя возможно это некоторое заблуждение или миф. :-) Кроме того многие просто ставят виртуалку с Линуксом. Но мой комп как-то не очень хорошо на нее реагирует. Все слишком медленно.
0
  • avatar
  • kos
  • 03 сентября 2014, 17:18
Почитал немного в инете и понял что все же лучше под инукс ставить эклипс. Насчет линукса я в процессе создания бутлоадера пользовался флешкой Linux Live. Впечатления очень положительные думаю так и поступлю с эклипсом. Немного только разобраться надо с режимом persistence что бы данные сохранялись. Из описания немного не понял тулчейн под арм где брать то: «Так же нам понадобится toolchain под ARM. С собрал сам, с помощью OpenEmbedded, но пойдет любой, например CodeSourcery».Вы сами его собирали в том же эклипсе в тулчейне под intel?
0
нет тулчейн скачал. Откуда то отсюда… launchpad.net/gcc-linaro/4.8/?utm_source=linaro&utm_medium=slider&utm_content=button&utm_campaign=toolchain_slider_link
Хотя можно было б и с CodeSourcery ARM GNU/Linux tool chain но с последним КОНКРЕТНО на моей плате почему-то были проблемы. Моя плата это не Кубик. Это плата от startetkit.ru sk-imx6s. Кстати на Малинке со вторым компилятором все работало нормально. Так что может и я где-то чего-то не доразобрался.
0
  • avatar
  • kos
  • 03 сентября 2014, 19:56
И это должен быть не «bare-metal» компилятор. Там где есть приставка Linux. Первый это для голого железа без операционки
0
  • avatar
  • kos
  • 03 сентября 2014, 19:59
Компилятор поставил вот этот gcc-arm-linux-gnueabi успешно прикрутил его к Eclips. Примерчики компилятся и успешно запускаются на кубике. Теперь по дебагеру. Поставил на хост-машину gdb-multiarch а вот как поставить сервер на archlinux не получается. или просто ставить gdb а сервер там внутри получается где-то в пакете gdb?
0
У вас дебагер должен быть с тем же префиксом в Eclips. А сервер насколько я понимаю ставится на арч в составе копилятора. Т.е Вы можете поставить компилятор и мейк на свой кубик и все это должно запустится. У меня на Малинке именно так было. Да и на другой платке тоже.
0
  • avatar
  • kos
  • 15 сентября 2014, 17:07
Добрый вечер! Приобрёл Cubieboard2 + DVK522 + 7" LCD. Поставил Андроид тач работает. Сменил на Debian, пересобрал ядро 3.4.106 при попытке включения тач скрина получаю:
===========================ft5x_ts_init=====================
[    9.070172] ctp_fetch_sysconfig_para.
.............................
[    9.206676] ctp_wakeup.
[    9.252049] register_chrdev.
[    9.258002] incomplete xfer (0x20)
[    9.260332] incomplete xfer (0x20)
[    9.262698] incomplete xfer (0x20)
[    9.265171] ft5x_ts_init completed. // добавил сам, чтобы понять в каком месте возникают ошибки.
, т.е. ошибки возникают в участке кода:
ret= register_chrdev(I2C_MAJOR,CHARDEV_NAME,&aw_i2c_ts_fops );
	if(ret) {
		pr_info(KERN_ERR "%s:register chrdev failed\n",__FILE__);
		return ret;
	}

	i2c_dev_class = class_create(THIS_MODULE,"aw_i2c_dev");
	if (IS_ERR(i2c_dev_class)) {
		ret = PTR_ERR(i2c_dev_class);
		class_destroy(i2c_dev_class);
	}

	ret = i2c_add_driver(&ft5x_ts_driver);

Не подскажите почему?
0
Проблема решена. При сборке ядра отключить # CONFIG_I2C_COMPAT is not set
0
Подскажите а какими инструментами пользовались для отладки-компиляции? Хочется связку eclipse под виндоус и отлаживать по сети. Вот не могу найти тулчейн для этого дела. Для кубика под винду. Как быть?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.