Пишемо завантажувач ПЛІС в LabVIEW. Частина 2

Завантаження конфігурації в ПЛІС через USB або розбираємо FTDI MPSSE
Пишемо завантажувач ПЛІС в LabVIEW. Частина 1

У першій статті ми обкатали алгоритм завантаження на старому доброму Сі, у другій статті розібралися, як в LabVIEW можна організувати програму і реалізувати простий інтерфейс користувача. В цей раз ми познайомимося з новими прийомами роботи в LabVIEW, розберемо особливості обробки помилок і завершимо проект: реалізуємо протокол завантаження файлу конфігурації в ПЛІС.

Обробка помилок

 

Відкриваємо сишный исходник, аналізуємо функцію MPSSE_open. Незважаючи на алгоритмічну простоту (функції викликаються одна за одною) потрібно імпортувати досить багато елементів API D2XX: FT_OpenEx, FT_ResetDevice, FT_Purge, FT_SetUSBParameters, FT_SetChars, FT_SetTimeouts, FT_SetLatencyTimer, FT_SetFlowControl, FT_SetBitMode. Як було показано в попередній статті, імпорт функцій здійснюється за допомогою вузла Call library Function. Цей вузол має виділені термінали для контролю помилок. В LabVIEW є одне просте правило: все ВП повинні відслідковувати помилки і повідомляти про помилки, повертаються терміналами помилок. Більшість вбудованих ВП неухильно дотримується. Сподіваюся, всім зрозуміло, наскільки важливо контролювати і обробляти помилки особливо на етапі налагодження, однак є ще одна причина, чому це так важливо, неочевидна “класичним” програмістам. В LabVIEW немає суворої послідовності виконання приладів на блок-діаграмі: прилад виконується, коли на його входах будуть готові дані. Якщо з виходу одного ВП дані передаються на вхід іншого СП, то зрозуміло, що на початку відпрацює перший ВП, тільки після нього другий. А як бути, якщо немає передачі даних, а СХП виконують незалежні дії? Звичайно можна скористатися громіздкою “Flat Sequence Structure”, але набагато зручніше з’єднати прилади між собою потоком помилок.

 

При імпорті функцій D2XX ми стикаємося з двома типами помилок. Перший — це помилка безпосередньо імпорту — повертає сам блок Call library Function. Другий — помилка самої бібліотеки, повертається майже кожною функцією через FT_STATUS. Всі можливі значення описані у вигляді enum’а в заголовочном файлі ftd2xx.h. Хоча досить знати, що значення FT_OK — відсутність помилки, а всі інші значення — коди помилок, хотілося б відстежити не тільки сам факт помилки, але і яка сталася помилка і де саме вона відбулася.

 

В LabVIEW дані про помилку поширюються через кластери error. Це такий спеціальний виділений тип даних, в LabVIEW є безліч ВП і функцій для роботи з ним. Кластер помилок складається з трьох елементів: логічна змінна відображає статус, знакове ціле число — код помилки, рядок — джерело помилки. Статус показує, відбулася помилка, код помилки визначає її тип і використовується спеціальними ВП для формування звіту. Рядок дає більш розгорнуте уявлення про те, де саме сталася помилка. В LabVIEW прийнято, якщо статус дорівнює TRUE, то це помилка, якщо статус дорівнює FALSE, але код не дорівнює нулю і рядок опису не порожня, то це попередження, якщо ж статус FALSE, код дорівнює нулю і рядок порожній — ні помилки.

LabVIEW містить внутрішню базу даних, в якій код кожної помилки пов’язаний з її описом. Для кожного типу помилок виділений спеціальний діапазон значень кодів. Наприклад, для помилок пов’язаних з роботою мережі виділено декілька діапазонів: від -2147467263 до -1967390460, від 61 до 65, від 116 до 118 та 122, 1101, 1114, 1115, 1132 до 1134, від 1139 до 1143 і від 1178 до 1185. Для помилок, які визначаються користувачем зарезервовано два діапазону від -8999 до -8000 і від 5000 до 9999. З цих діапазонів ми можемо вибрати значення для кодів помилок бібліотеки D2XX.

 

Створимо ВП, що приймає на вхід статус функції D2XX і конвертирующий цей статус у кластер помилки LabVIEW. Більшість функцій і ВП в LabVIEW, отримавши на вхід Error In статус TRUE, не виконують свій код, а передають інформацію про помилку на термінал Error Out. Це дозволяє ефективно передати інформацію про джерелі через весь ланцюжок до обробника помилок, виключивши виконання коду в аварійному режимі. Бажано, щоб і наші ВП вели себе аналогічно.

 

Оформимо список статусів D2XX у вигляді enum і винесемо його в окремий тип (у попередній статті ми так вчинили з типами FTDI).

enum FT_Status

Новий ВП зберігаємо під ім’ям FT_error.vi. На передню панель додаємо два кластера Error In і Error Out, знайти їх можна в панелі “Array, Matrix & Cluster”. Приєднуємо їх до терміналів на панелі сполук у нижньому лівому і правому нижньому кутах відповідно, як вже говорилося в минулій статті, це прийняте в LabVIEW розташування терміналів потоку помилок. На блок-діаграму додаємо структуру Case, на вхід Case selector подаємо кластер Error In, після чого структура Case змінює колір і ділитися дві поддиаграммы: “No Error” — зелений колір, і “Error” — червоний колір. Всередині випадку Error передаємо кластер помилок від терміналу селектора безпосередньо до вихідного тунелю на правій границі. А в зеленому випадку додаємо ще один Case, він в залежності від статусу буде визначати, чи потрібно створювати помилку (статус не дорівнює FT_OK), або залишити все як є: пропустити вхідний кластер помилки на вихід без зміни.

Читайте також  Трагікомедія в NaN актах: як розробники зробили гру на JS і випустили її в Steam

 

Для того, щоб технічно перетворити код помилки в кластер, можна використовувати ВП Error Cluster From Error Code VI. Цей SubVI в опис помилки додає ланцюжок виклику, завдяки чому ми зможемо визначити не тільки що сталося, але ще і де це сталося.

 

Щоб виділити текст, відповідний вхідному статусу (FT_Status), використовуємо блок властивостей: вибираємо “RingText.Text”. Текст помилки передаємо на вхід error message ВП Error Cluster From Error Code VI.
Не забуваємо намалювати “розмовляючу” іконку.

FT_error.vi


Передня (фронт) панель подприбора

 


Блок-діаграма. На вході помилка

 


Блок-діаграма. На вході немає помилки і статус дорівнює FT_OK

 


Блок-діаграма. На вході немає помилки, але статус відмінний від FT_OK

Для випробування FT_error можна створити порожній ВП, додати туди створений ВП і подивитися, як буде змінюватися значення при запуску, якщо подавати різні статуси.

Тест FT_error.vi


Передня (фронт) панель приладу

 


Блок-діаграма

 

Тепер, після будь-якого виклику функцій API D2XX, ми будемо використовувати SubVI FT_error.vi. А кластер помилок буде проходити через все ВП по всій ієрархії виклику.

 

В СП верхнього рівня ми повинні визначитися, що робити з виявленою помилкою: можна вивести повідомлення у діалоговому вікні, записати його у файл звіту, проігнорувати або просто “тихо” завершити додаток. Діалогове вікно — найпростіший і найпопулярніший спосіб звітування про помилки. А ще він зручний для початківця програміста, так як робити нічого не треба. У кожному ВП за замовчуванням активований режим автоматичної обробки помилок (Enable automatic error handling, знаходиться в категорії Execution меню ВП Properties). Працює він так: якщо в якомусь сайті вихідний термінал Error Out нікуди не підключений, і в цьому вузлі відбувається помилка, то LabVIEW призупиняє виконання програми та видає діалогове вікно. Якщо термінал Error Out вузла з’єднаний, то потік помилки поширюється, як запрограмовано, і ніяких додаткових дій не відбувається. Однак вікно повідомлення можна викликати програмно, для цього потрібно скористатися ВП General Error Handler і Simple Error Handler (знаходяться на панелі “Dialog&User Interface”). При цьому інформацію про помилку ми можемо використовувати для завершення програми. На блок-діаграмі це виглядає приблизно так:

 


Картинка кликабельная

 

Коли станеться помилка, програма буде припинена, з’явиться вікно із звітом, після закриття вікна програма коректно завершитися.

Вікно звіту

 

Відкрити і закрити FTDI

 

Отже, повертаємося до функції MPSSE_open. Створюємо новий VI. Першим ділом, додаємо термінали для потоку помилок. Додаємо структуру вибору і на селектор подаємо вхід Error In. У зеленому кейсі робимо імпорт функцій у порядку та з параметрами як в Сишном прототипі. Всі вузли Call Library Function Node з’єднуємо в ланцюжок потоком помилок. В червоному кейсі через тунель з’єднуємо Error In з вихідним терміналом помилки.

 


Картинка кликабельная

 


ВП MPSSE_open.vi

 

На вхід SubVI подається рядок з описом FTDI (Description), на виході — Handle і ініціалізований чіп FTDI в режимі MPSSE.

 

Створимо ВП, завершує роботу з FTDI і можна вже перевірити працездатність на залозі.

FT_Close.vi


Блок-діаграма

 


Передня панель

 

У попередній статті для налагодження інтерфейсу ми зробили ВП заглушку SP_FT_MPSSE_FPGA.vi, зараз настав час наповнити його. Додаємо на його блок-діаграму MPSSE_open.vi і FT_Close.vi. На даному етапі досить складно оцінити, чи вірно ініціалізація пройшла, проте ненульове значення Handle на виході MPSSE_open.vi і відсутність помилки нам вже багато про що скаже.

 


Блок-діаграма SP_FT_MPSSE_FPGA.vi

 

Для того, щоб подивитися значення Handle можна скористатися вікном “Probe Watch Window”. Це зручний інструмент налагодження, який дозволяє вивести значення даних на будь-якому (майже кожному) проводі в процесі виконання приладу. Для того щоб встановити пробу на лінію, потрібно в контекстному меню цій самій лінії вибрати пункт “Probe”. Відкриється вікно “Probe Watch Window”, а на лінії з’явиться цифра з номером проби. На малюнку вище це “3”.

Probe Watch Window


На лінії Handle значення 698389336

 

Відмінно! Запускаємо СП верхнього рівня, підключаємо до комп’ютера налагоджувальну плату. У списку “Виберіть пристрій” з’являється опис підключеної мікросхеми FTDI, натискаємо кнопку “Програмувати” і… нічого не відбувається. Тільки у вікні “Probe Watch” з’явилося значення Handle. І це добре.

 

Відключаємо плату, список пристроїв очищається. Натискаємо “Програмувати”. Ось тут-то вискакує вікно звіту про помилку.

Вікно звіту

 

Після натискання кнопки “Continue”, ВП завершує свою роботу.

 

Варто заборонити натискати кнопку, якщо не знайдено жодного пристрою. Модифікуємо кейс “Timeout” обробника подій. Нагадаю, два рази в секунду скануються підключені до ПК чіпи FTDI, якщо такі виявлені і можуть бути використані для програмування ПЛІС, то через властивість Strings[] їх дескриптори додаються в Devices list. Створюємо для “Програмувати” властивість Disabled, і, якщо придатних пристроїв не виявлено, то відключаємо і затемнюємо кнопку.

Case Timeout


Картинка кликабельна

Читайте також  Деякі завдання шкільної математики

 

Освоюємо GPIO

 

Після того, як MPSSE активований, робота з ним здійснюється через так звані “op-code”, а з функцій API D2XX використовується тільки FT_Write, FT_Read і FT_Queue (щоб дізнатися статус буфера приймача). За наторенной доріжці створюємо відповідні VI: FT_Write.vi, FT_Read.vi, FT_Queue.vi.

Трохи рутини


FT_Write.vi

 


Блок-діаграма. FT_Write.vi

 


FT_Read.vi

 


Блок-діаграма. FT_Read.vi

 


FT_Queue.vi

 


Блок-діаграма. FT_Queue.vi

 

Тепер з цих трьох цеглинок викладаємо ВП для читання паралельного порту і запису в нього. Значення зручно представити у вигляді масиву булевих змінних.

MPSSE_Set_LByte.vi і MPSSE_Get_LByte.vi


MPSSE_Set_LByte.vi

 


Блок-діаграма. MPSSE_Set_LByte.vi

 


MPSSE_Get_LByte.vi

 


Блок-діаграма. MPSSE_Get_LByte.vi

 

Каюся, мені було ліниво створювати іменований список для всіх op-code, тому залишив їх у вигляді Magic Numbers.

 

Як говорилося в першій статті протокол завантаження ПЛІС “Passive Serial” є ніщо інше як SPI з додатковою маніпуляцією прапорами. Всього використовується п’ять ніжок: лінії DCLK, DATA[0], nCONFIG повинні бути сконфігуровані як виходу, лінії nSTATUS, CONF_DONE — як входи.

Терморегулятори схеми у вигляді таблиці

FPGA pin Pin Name Pin MPSSE Direction default

DCLK BDBUS0 38 TCK/SK Out
DATA[0] BDBUS1 39 TDI/DO Out 1
nCONFIG BDBUS2 40 TDO/DI Out 1
nSTATUS BDBUS3 41 TMS/CS In 1
CONF_DONE BDBUS4 43 GPIOL0 In 1

 

Нам знадобиться ВП, який зможе змінювати значення обраної на ніжці не зачіпаючи всі інші. Насамперед створюємо Enum з порядковими номерами ніжок в порту, зберігаємо у вигляді “Strict Type Def” в файл SP_LBYTE_BITS.ctl. Створюємо новий ВП, додаємо звичні термінали потоку помилок. Зчитуємо поточне значення паралельного порту з допомогою MPSSE_Get_LByte.vi, з допомогою функції Replace Array Subset модифікуємо потрібний біт і записуємо значення назад в порт (MPSSE_Set_LByte.vi).

SP_Set_Flag.vi


SP_Set_Flag.vi

 


Блок-діаграма. SP_Set_Flag.vi

 


Enum SP_LBYTE_BITS.ctl

 

Для початку конфігурації контролер MPSSE повинен генерувати перехід з низького рівня до високого на лінії nCONFIG. Як тільки ПЛІС буде готова до прийому даних, вона сформує високий рівень на лінії nSTATUS. На даному етапі у нас все готово для експерименту в залозі. На блок-діаграму SP_FT_MPSSE_FPGA.v додаємо управління лінією nCONFIG — після ініціалізації MPSSE подаємо низький рівень, а потім високий. Після кожної операції (для налагодження) зчитуємо стан ніжок порту.

SP_FT_MPSSE_FPGA.vi


Під час запуску

 


Блок-діаграма

 

В цілому, під час запуску VI видно, що ПЛІС реагує на перехід на лінії nCONFIG — на ніжці nSTATUS встановлюється нуль, а потім одиниця. Але не буде зайвим проконтролювати це з допомогою осцилографа. Годиться майже будь-двоканальний осцилограф з можливістю запуску тригера (режим, що чекає). Канал А (синій трек) я ставлю в контрольну точку ланцюга nCONFIG, канал B (червоний трек) — ланцюг nSTATUS. Тригер налаштований на спадаючий фронт каналу A.

 


Картинка кликабельна. З подробицями!

 

Робота з файлом

 

ПЛІС готова прийняти файл конфігурації. А чи ми готові передати файл в ПЛІС?

 

LabVIEW містить великий набір інструментів для роботи з файлами. Не скажу, що функціоналу вистачає на абсолютно весь спектр завдань, однак базові операції типу читання і запис виконуються легко і приємно. Основний набір VI для роботи з файлами можна знайти в палітрі “File I/O”. Для розв’язуваної задачі потрібно відкрити файл конфігурації, оцінити його розмір (нам потрібно знати, скільки байт відправляти ПЛІС), прочитати його і закрити. Все просто і один за одним. Використовуємо ВП Open/Create/Replace File, Get File Size, Read from Binary File, Close File, об’єднуємо їх ланцюжком потоку помилок і refnum — число, типу файлового дескриптора, створюється при відкритті файлу і повинно бути передано на вхід іншим ВП, що працюють із цим файлом.

 

Поки нам нікуди утилізувати лічені дані, але якщо дуже хочеться перевірити працездатність ланцюжка, то можна створити індикатор типу String і трошки налаштувати його. У контекстному меню активуємо опцію “Hex Display”, включаємо вертикальний скроллбар (Visible Items -> Vertical Scrollbar) і після запуску спостерігаємо вміст бінарного файлу конфігурації.

SP_FT_MPSSE_FPGA.vi


Передня панель. Дивимося на вміст файлу

 


Блок-діаграма. Каринка кликабельная

 

На блок-діаграмі ВП утворилося дві незалежні паралельні лінії коду, тому для них використовуються роздільні ланцюжка помилок. Для того, щоб звести паралельні потоки в один термінал Error Out, використовується функція Merge Errors. Ця функція переглядає помилки на вході зверху вниз (так, там може більше двох вхідних терміналів, розтягується мишкою) і повертає першу, яку знайде. Якщо помилок немає, то повертає перше-ліпше попередження. Якщо і попереджень не виявлено, то на виході помилка відсутня. Важливо відзначити, що порядок підключення входів Merge Errors визначає пріоритет помилок, і якщо помилка виникне відразу в двох ланцюжках, то нижня помилка буде проігнорована. До цього потрібно ставитися уважно.

 

Якщо ми спробуємо в СП верхнього рівня натиснути кнопку “Програмувати” не вибравши файл, то на вхід SP_FT_MPSSE_FPGA.vi надійде порожній шлях, що викличе помилку “Error 1430. LabVIEW: (Hex 0x596) The path is empty or relative. You must use an absolute path.” Як каже мій друг дитинства: “Дурниці, справа-то житейська!”. І ця помилка — зовсім не помилка, а так, неуважність користувача. Зупиняти програму і лаятися на нього вікном з червоним хрестиком ми не будемо, просто вилучимо помилку з цим кодом з потоку і в діалоговому вікні порекомендуємо користувачеві визначитися з файлом. Для фільтрації помилки використовуємо ВП “Clear Errors” на панелі “Dialog&User Interface”. Для виводу повідомлення — “One Button Dialog”.

Читайте також  Просунуте використання Гіта або як вийти на пенсію на півроку раніше?

 

Блок-діаграма


Картинка кликабельна

 

Завантаження конфігурації

 

Для послідовної передачі даних процесору MPSSE потрібно послати op-code 0x18, аргументами команди буде довжина переданої послідовності (два байти, починаючи з молодшого), і сама послідовність даних. Довжина кодується за вирахуванням одиниці. Відправку блоку даних оформимо у вигляді ВП MPSSE_send.

MPSSE_Send.vi


MPSSE_Send.vi

 


Блок-діаграма

 

Розмір вхідного буфера (Array Size) перетворюємо до двухбайтовому типу U16, віднімаємо одиницю, міняємо місцями молодший і старший байт (Swap Bytes) — відправляти довжину треба починаючи з молодшого, і перетворюємо двобайтове число однобайтний масив (Type Cast).

 

Функція Type Cast заслуговує окремої уваги. Це такий універсальний перетворювач типів, кмітливість якого іноді сильно дивує. Якщо коротко, то:

 


Наочно для програміста

 

Однак це не просто приведення даних до іншого типу, це ще й евристична інтерпретація. Ця функція дозволяє виконувати перетворення між несумісними типами даних, при цьому функція не гребує вирівнюванням вхідних даних і навіть видалення “зайвих” частин. Якщо запитаний тип даних вимагає пам’яті більше, ніж у вхідних даних, то функція виділить відсутній обсяг. Для початківця розробника LabVIEW Type Cast може стати паличкою-виручалочкою, але з дорослішанням, краще від такого перетворювача відмовитися — дуже багато приховано від очей і може стати джерелом непередбачених помилок. Краще використовувати більш явні методи перетворення, наприклад, Coerce To Type.

 

При ініціалізації процесора MPSSE, ми поставили максимально допустимий розмір буфера для передачі даних в 65536 байт, отже файл конфігурації ми повинні розділити на фрагменти, розмір яких не перевищує зазначений розмір. Скористаємося функцією Array Subset, ця функція виділяє з масиву подмассив починаючи з елемента index і довжиною length. Розбивати будемо в циклі While, кожну ітерацію індекс будемо збільшувати на 65536, між ітераціями значення передамо через сдвиговый регістр. Як тільки не вдасться від основного масиву відщипнути 65536 байт, беремо все, що залишилося, відправляємо і зупиняємо цикл.

 

Згідно з протоколом завантаження, після того, як усі дані були передані, потрібно подати ще два тактових імпульсу, щоб почалася ініціалізація ПЛІС. Для цього після циклу виконуємо відправку ще одного “порожнього” байта.

SP_FT_MPSSE_FPGA.vi


Картинка кликабельна

 

Для того, щоб зрозуміти успіх прошивки, вважаємо прапори, і, якщо CONF_DONE встановлений в одиницю, рапортуємо СП верхнього рівня, що все ОК.

 

Програма завершена. Залишилося переконатися, що ПЛІС успішно прошивається, а плата щасливо блимає светодиодиками.

 

Про іменування ВП

 

Ні для кого не секрет, що в класичному програмуванні всім користувальницьким об’єктів і функцій потрібно давати осмислені імена, то ж можна сказати і про LabVIEW, особливо, якщо в ролі об’єктів виступає SubVI. Я звик імена файлів ВП давати на основі їх місця в ієрархії розроблюваного ПЗ. У поточному додатку можна виділити чотири рівня абстракції:

 

  • Найнижчий рівень — це ВП, що виконують безпосередню взаємодію з FTDI, в більшості своїй вони зводяться до виклику відповідної функції API D2XX. У своєму проекті імена ВП цього рівня я починав з префікса “FT”, наприклад FT_Close.vi або FT_Read.vi.
  • Другий рівень — це взаємодія з процесором MPSSE. Імена ВП цього рівня починаються з префікса “MPSSE”. Приклад: MPSSE_open.vi, MPSSE_Set_LByte.vi, MPSSE_Get_LByte.vi.
  • Третій рівень — це реалізація протоколу “Passive Serial” поверх MPSSE. Всі файли мають префікс “ЅР”. Наприклад, SP_FT_MPSSE_FPGA.vi (страшне ім’я, що складається з абревіатури) і SP_LBYTE_BITS.ctl.
  • Рівень програми. СП верхнього рівня. Ім’я може бути довільним, человекоориентированным.

 

Якщо проект досить великий (десятки ВП), то для кожного рівня файли краще зберігати в окремих директоріях з відповідною назвою. У нашому проекті все ВП розмістилися в одній папці subVI.

 

Висновок

 

Може здатися, що опис процесу надмірно докладно, але дуже вже не хотілося створити чергове посібник з малювання сови.

Малюємо сову

 

Я не намагався зробити довідник або підручник з мови, я хотів показати процес створення програми на LabVIEW, показати як будується логіка розробки в цьому середовищі. Людина зрозуміє, чи потрібно це йому взагалі, початківець розробник безсумнівно почерпне для себе багато нового, професіонал поблажливо посміхнеться і згадає себе в молодості (або кине в мене помідором). А мені буде куди направити моїх студентів і дипломників.

 

Матеріали по темі

 

  1. Блюм П. LabVIEW: стиль програмування. Пер. з англ. під ред. Міхєєва П.– М.:
    ДМК Прес, 2008 – 400 с.: іл.
  2. labview_mpsse. Репозиторій з проектом.
  3. Навчальний стенд для ЦГЗ. Залізо для досвіду
  4. Application Software Development D2XX programmer’s Guide. Керівництво по API D2XX.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *