Мы закончили большой теоретический блок, показывающий, как можно строить ПЛИС-подсистему для комплекса Redd; как организовывать связь между ПЛИС и центральным процессором комплекса; как легко сохранять скоростные потоки данных в ОЗУ, имеющем прямую связь с ПЛИС, для последующей их неспешной перекачки к центральному процессору (или наоборот, помещать данные в это ОЗУ для последующей быстрой выдачи в канал). Мы рассмотрели методики трассировки работы процессора Nios II. Мы умеем оптимизировать быстродействие процессорной системы на базе Nios II, чтобы работа шла максимально эффективно. В общем, мы изучили весь необходимый минимум теории, и пора бы перейти к практике, спроектировав не очень сложное, но практически полезное устройство… Но имеется одно НО.
Самое удивительное — это то, что как только я решил сделать лирическое отступление на прочие темы, доброе начальство бросило меня в тяжкий бой на проект, где работа идёт с языком VHDL и ПЛИС Xilinx. Во-первых, именно поэтому давно я не брал в руки перо вообще, а во-вторых, понятно, что подготовка практических статей требует большого количества экспериментов. Заниматься же одновременно VHDL/Verilog и Xilinx/Altera несколько сложно. Так что перерыв в рассказах о ПЛИС сделать всё равно бы пришлось.
Итак. В первой статье цикла мы уже рассматривали структурную схему комплекса Redd. Давайте сделаем это ещё раз.
В сегодняшней статье специалисты по ОС Linux вряд ли найдут много ценной информации, но поверхностно пробежаться по картинкам всё-таки стоит. Те же, кто как и я, привык к работе из ОС Windows, найдут перечень готовых методик, позволяющих работать с комплексом. В общем, эта статья приведёт навыки тех и других групп читателей к общему знаменателю.
На структурной схеме мы видим контроллер FT4232, реализующий 4 последовательных порта (UART):
Но если рассуждать чуть более глобально, то у комплекса Redd не четыре, а шесть последовательных портов. Просто упомянутые четыре имеют уровни КМОП, а ещё два — припаяны на материнской плате, ведь в основе комплекса заложен обыкновенный PC.
а где искать линии c уровнями КМОП? В целом — на общем разъёме. Его цоколёвка приведена на схеме электрической принципиальной. Есть там, среди прочего, и контакты, соответствующие UART.
Как его использовать — зависит от задачи. Можно для подключения каждого устройства изготавливать жгут. Такой подход пригодится, если кто-то использует комплекс Redd для тестирования периодически изготавливаемых устройств одного и того же вида. Но основное назначение комплекса — всё-таки отладка разрабатываемого оборудования. И в этом случае проще подключаться к нему по временной схеме. Такая временная схеме видна на заставках ко всем статьям: прямо в разъём вставлены Aruino-проводочки. Само собой, отсчитывать контакты — то ещё удовольствие, а если они случайно вылетят — восстанавливать коммутацию настолько сложно, что проще подключить всё заново с нуля; поэтому для облегчения жизни имеется переходная плата, к которой можно подключаться хоть при помощи двухрядных разъёмов, хоть теми же Arduino-проводочками.
Последовательный порт — это устоявшийся, хорошо стандартизированный элемент, поэтому работа с ним идёт не через какие-то специфичные библиотеки FTDI, а через стандартные средства. Давайте рассмотрим, как эти средства выглядят в ОС Linux.
Из ряда статей и форумов в сети следует, что имена портов, обеспечиваемых переходниками USB-Serial, имеют формат /dev/ttyUSB0, /dev/ttyUSB1 и так далее. В ОС Linux все устройства можно просмотреть, используя те же команды, что и для просмотра обычных каталогов (собственно, устройства – это те же файлы). Давайте просмотрим, какие же имена есть в нашей системе. Подаём команду:
ls /dev/
Интересующие нас имена я выделил красной рамкой. Что-то много их. Какой порт чему соответствует? Кто хорошо ориентируется в Linux, знает тысячи заклинаний на все случаи жизни. Но тем, кто работал ещё с Windows 3.1 (ну и параллельно с тогда ещё вполне бодренькой старушкой RT-11), это запомнить всё равно сложно, с возрастом новое запоминается тяжелее. Поэтому проще каждый раз всё находить, пользуясь простыми путями. И вход на этот простой путь я выделил зелёной рамкой. Условный подкаталог serial. Сейчас мы смотрим пространство имён /dev/. А посмотрим пространство /dev/
С одной стороны, всё прекрасно. Теперь мы знаем, каким именам в пространстве /dev/ttyUSBX соответствует какое устройство. В частности, порты, организованные мостом FT4232 (Quad), имеют имена от ttyUSB3 до ttyUSB6. Но с другой, при рассмотрении этого участка я понял, что в Париже в палате мер и весов обязательно должна иметься комната, в которой размещён эталон бардака… Потому что как-то надо уметь измерять его величину. Ну, допустим, отсутствие портов /dev/ttyUSB0 и /dev/ttyUSB1 легко можно объяснить. Но как объяснить, что «родные» порты на базе отродясь установленного моста FTDI нумеруются с тройки, а вставленный под конкретный проект сторонний контроллер Prolific занял порт с номером 2? Как можно в такой обстановке работать? Завтра кто-то воткнёт в комплекс ещё какой-то контроллер (благо комплекс допускает одновременную работу разных групп разработчиков с разным оборудованием), и порты снова съедут. Какие же порты прописывать нам в конфигурационный файл для рабочего приложения?
Оказывается, всё не так плохо. Во-первых, жёлтое имя /dev/ttyUSB3 и голубое имя /dev/serial/by-id/usb-FTDI_Quad_RS232-HS-if00-port0 – это два псевдонима одного и того же устройства. И второй вариант тоже можно подавать как имя порта, а он уже более постоянный, чем первый. Правда, и в этом случае всё несколько нехорошо. В комплекс могут воткнуть внешний контроллер на базе FT4232, и уже надо будет разбираться с их нумерацией. И вот тут нам на помощь приходит «во-вторых». А именно ещё один альтернативный вариант именования. Мы помним, что каталог /dev/serial содержал не только подкаталог /by-id, но и подкаталог /by-path. Проверяем его содержимое (оно размещено в нижней части следующего рисунка, под красной чертой).
При разработке стоит воспользоваться замечательным руководством Serial Programming Guide for POSIX Operating Systems (первая попавшаяся прямая ссылка https://www.cmrr.umn.edu/~strupp/serial.html, но сколько она проживёт – не знает никто). Особенно важно, что там рассказывается, как работать с полным набором сигналов, ведь порты в комплексе реализованы полноценные. Правда, сегодня мы будем использовать только линии Tx и Rx.
#include
#include /* UNIX standard function definitions */
#include /* File control definitions */
#include /* Error number definitions */
#include /* POSIX terminal control definitions */
int OpenUART(const char* portName, speed_t baudRate)
{
// Открыли порт
int fd = open(portName, O_RDWR | O_NOCTTY | O_NDELAY);
// А он не открылся
if (fd == -1)
{
return fd;
}
// Переключаем ввод на неблокирующий
fcntl(fd, F_SETFL, FNDELAY);
// Задаём скорость порта
termios options;
tcgetattr(fd, &options);
// Говорят, что можно использовать не только типовые
// константы, но и значения. Заодно проверим...
cfsetspeed(&options, baudRate);
// Попутно настроим прочие параметры...
// 1 стоп бит, нет контроля чётности, 8 бит в байте
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag |= (CLOCAL | CREAD);
// Всё, установили...
tcsetattr(fd, TCSANOW, &options);
return fd;
}
int main()
{
printf("hello from ReddUARTTest!\n");
int fd1 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.3-port0", 9600);
int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 9600);
if ((fd1 != -1) && (fd2 != -1))
{
static const unsigned char dataForSend[] = {0xff,0xfe,0xfd,0xfb};
// Посылаем данные в один порт
write(fd1, dataForSend, sizeof(dataForSend));
unsigned char dataForReceive[128];
ssize_t cnt = 0;
// Будем подсчитывать число попыток чтения, чтобы убедиться,
// что мы прочли не за одну блокирующую попытку
int readSteps = 0;
// Мы должны принять хотя бы столько, сколько передали
while (cnt < (ssize_t)sizeof(dataForSend))
{
readSteps += 1;
ssize_t rd = read(fd2, dataForReceive + cnt, sizeof(dataForReceive) - cnt);
// Не считалось - уснули, чтобы не грузить процессор
if (rd <= 0)
{
usleep(1000);
}
else
// Иначе - учли считанное
{
cnt += rd;
}
}
// Отобразили результат
printf("%d read operations\n", readSteps);
printf("Read Data: ");
for (unsigned int i = 0; i < cnt; i++)
{
printf("%X ", dataForReceive[i]);
}
printf("\n");
}
else
{
printf("Error with any port open!\n");
}
// Закрываем порты
if (fd1 != -1)
{
close(fd1);
}
if (fd2 != -1)
{
close(fd2);
}
return 0;
}
Запускаем – получаем предсказуемый результат:
hello from ReddUARTTest!
14 read operations
Read Data: FF FE FD FB
Видно, что 4 байта принялись за 14 попыток, то есть, чтение было не блокирующим. Иногда система возвращала состояние «нет новых данных», и программа уходила в сон на одну миллисекунду.
В общем, всё хорошо, но без осциллографа я не могу быть уверен, что два порта на базе одной микросхемы действительно устанавливают скорость. Я уже нажигался на том, что скорость была одинаковая (на то он и один контроллер), но не та, которую я заказал. Давайте хоть как-то проверим, что она хотя бы управляется. Для этого я задам скорость принимающего порта вдвое больше, чем у передающего. А зная физику процесса передачи данных, можно предсказать, как эти данные исказятся при приёме. Давайте рассмотрим передачу байта 0xff в графическом виде. S – стартовый бит (там всегда ноль) бит, P – стоповый бит (там всегда единица), 0-7 – биты данных (для константы 0xFF – все единицы).
Построим такой же график для проверки, что будет соответствовать переданному значению 0xFE:
Ожидать следует значения «1111 1000» или 0xF8. Ну давайте ещё для закрепления материала проверим, что ждать при переданном значении 0xFD:
Получаем значение 0xE6. Ну, и для переданного значения 0xFB получаем принимаемое 0x9E (можете построить график и убедиться в этом сами). Замечательно! Меняем в тестовом приложении одну единственную строку, заменив скорость 9600 на 19200:
int fd2 = OpenUART("/dev/serial/by-path/pci-0000:00:15.0-usb-0:6.5:1.2-port0", 19200);
hello from ReddUARTTest!
9 read operations
Read Data: FE F8 E6 9E
К слову, я не зря провёл эту проверку. Сначала я использовал другие функции установки скорости (пару cfsetispeed/cfsetospeed), и они не работали! Благодаря данному тесту, проблема была своевременно выявлена и устранена. При работе с аппаратурой никогда нельзя доверять интуиции. Всё следует проверять!
Вообще, силовые линии 220 вольт не относятся к теме статьи (мосты FTDI), но зато относятся к теме данного раздела (последовательные порты). Давайте вскользь рассмотрим их.
Команда | Назначение |
---|---|
'A' | Включить первую розетку |
'a' | Выключить первую розетку |
'B' | Включить вторую розетку |
'b' | Выключить вторую розетку |
'C' | Включить третью розетку (если имеется) |
'c' | Выключить третью розетку (если имеется) |
'?' | Вернуть состояние розеток |
Команда ‘?’ (знак вопроса) — единственная, которая возвращает отклик. В ответ на неё всегда приходит 3 байта, каждый из которых соответствует состоянию одной из розеток. Собственно, состояния соответствуют командам. Например, ‘abc’ — все три розетки сейчас выключены, ‘Abc’ — первая включена, вторая и третья выключены и т. п.
Для экспериментов с этой подсистемой предлагаю не писать специальную программу (она ничем не отличается от приведённой ранее, только данные, посылаемые в порты, будут другими), а воспользоваться средствами ОС и поиграть с розетками интерактивно.
После массы экспериментов с отслеживанием порта через команду cat и посылкой команд в параллельном окне с помощью программы echo, я понял, что почему-то в паре ssh-терминалов на базе putty я не могу добиться результатов (даже играя с теми портами, с которыми только что прекрасно экспериментировал своей программой). Поэтому пришлось установить стандартную программу minicom. Напомню команду установки:
sudo apt-get minicom
Дальше запускаем её командой:
minicom –D /dev/ttyACM0
Имя порта короткое, так как при ручных опытах его ввести проще всего. При программной работе, как всегда, лучше использовать имя, привязанное к иерархии аппаратуры. Ещё раз обращаю внимание, что никакие другие параметры порта я не настраиваю потому, что он — виртуальный. Он заработает при любых настройках.
Правила работы SPI при этом не нарушаются, с точки зрения этой шины, всё работает корректно. Просто имейте в виду, что привычные на контроллерах нестандартные решения здесь не сработают. Правда, у комплекса имеется масса свободных разъёмов USB. Все нестандартные блоки могут быть разработаны отдельно и подключены к ним.
#include "../ftd2xx/ftd2xx.h"
#include "../LibFT4222/inc/LibFT4222.h"
void SpiTest (int pos)
{
FT_HANDLE ftHandle = NULL;
FT_STATUS ftStatus;
FT4222_STATUS ft4222Status;
// Открываем устройство
ftStatus = FT_Open(pos, &ftHandle);
if (FT_OK != ftStatus)
{
// open failed
printf ("error: Cannot Open FTDI Device\n");
return;
}
ft4222Status = FT4222_SPIMaster_Init(ftHandle, SPI_IO_SINGLE, CLK_DIV_4, CLK_IDLE_LOW, CLK_LEADING, 0x01);
if (FT4222_OK != ft4222Status)
{
printf ("error: Cannot switch to SPI Master Mode\n");
// spi master init failed
return;
}
uint8 wrBuf [] = {0x9f,0xff,0xff,0xff,0xff,0xff,0xff};
uint8 rdBuf [sizeof (wrBuf)];
uint16 dwRead;
ft4222Status = FT4222_SPIMaster_SingleReadWrite (ftHandle,rdBuf,wrBuf,sizeof (wrBuf),&dwRead,TRUE);
if (FT4222_OK != ft4222Status)
{
printf ("error: Error on ReadWrite\n");
} else
{
printf ("received: ");
for (int i=0;i<6;i++)
{
printf ("0x%X ",rdBuf[i]);
}
printf ("\n");
}
FT4222_UnInitialize(ftHandle);
FT_Close(ftHandle);
}