На рисунке приведена структурная схема современного, одного из самых навороченных (я подозреваю) 32-битного ARM процессора или микроконтроллера-microcontroller, в документации используются оба термина: high-performance Flash microcontroller (MCU) based on the 32-bit ARM Cortex-M7 RISC (х.хх CoreMark/MHz) processor.
Мне кажется, если еще разрисовать некоторые прямоугольники из этой схемы, то картинка по масштабу вполне сможет сравниться со структурной схемой какого-нибудь космического корабля.
Все это богатство убирается в микросхеме, которая по объему заметно меньше спичечного коробка. Вы легко можете найти достаточно подробное техническое описание (datasheet) узлов, систем, настроек, спецификаций по этой-такой схеме. Давайте попробуем коротко пройтись по одному из таких описанию, ссылки в конце статьи.
Оглавление из datasheet
Я решил вывести вам оглавление только основных разделов в виде картинки что бы вы могли оценить и представить объем знаний заключенных в одном камушке с несколькими десятками (обычно, в нашем случае до 144 ног) выводов
Тут у меня получилось уместить на странице 58 разделов, но это не совсем полный список, плюс некоторые разделы не нумеруются, хотя некоторые, скажем десять из 60 или 15 из 65 почти не имеют содержания. При таком объеме информации вряд ли кто-то возьмется утверждать, что он все это досконально знает. Я думаю даже те, кто занимается разработкой таких микросхем, владеет каким-то базовым набором знаний, которые позволяют разрабатывать отдельные подсистемы или взаимодействующие -связанные подсистемы внутри огромной системы. Нельзя все это одновременно держать в голове, такое описание обычно используется как справочник, когда возникает необходимость разобраться с каким-то чужим аппаратно-зависимым решением или спроектировать что-то специфическое свое.
Но в общем где-то половину из этого списка приходится так или иначе штудировать, когда ты занимаешься разработкой софта внутри (поверх) этой системы, хотя это все еще не полная документация, еще надо учитывать конкретную схемотехнику обвязки, в которой будет работать процессор(микросхема), какое-то общее описание для ARM-архитектуры, например «ARM Cortex-M7 Devices Generic User Guide» (у меня оно от 2015 года, может кто-то даст ссылку на последнюю версию).
Глядя на приведенную схему, можно увидеть вполне отчетливую аналогию с полноценным Персональным Компьютером, а точнее с материнской платой вместе со всем тем, что на ней находится. На материнской плате обычно присутствует какая-то PCI шина, которая соединяет центральный процессор системы с набором периферийных устройств. Приведем для примера несколько самых общеизвестных периферийных устройств:
USB-контроллер, аналог здесь: 37. USB High-Speed Interface (USBHS)).
Сетевая карта, аналог здесь: 38. Ethernet MAC (GMAC)).
Последовательный порт/COM-порт/RS232, аналог здесь: 46. Universal Asynchronous Receiver Transmitter (UART).
Изначально я хотел написать статью про DMA, но быстро понял, что такая статья требует большого объема вводных описаний-определений, поэтому я решил написать эту статью как предварительную с обзором одной реальной системы, для которой у меня есть работающее решение. Полное понимание работы DMA невозможно без общего понимания работы процессора, интерфейсов к памяти, типов (микросхем) памяти, типов шин, типов сигналов управления, прерываний, … полный список вы узнаете когда сделаете-сконфигурируете свою первую транзакцию по DMA, и… И сможете убедиться что эта транзакция прошла успешно, что в памяти назначения появились данные полученные из памяти источника, которых там не было до выполнения транзакции. Это не на столько сложно (там нет никакой высшей математики), насколько муторное и угнетающе нудное занятие следить за соответствием значений, разбросанных по разным битам в десятках или даже сотнях байт конфигурации. Как ни странно, существуют люди, которые находят удовлетворение в своей способности не только правильно расставлять эти биты, но и находить ошибки в таких расстановках и, таким образом, управлять работой машины по пересылке байт. Если вы понимаете, о чем я говорю, скорее всего у вас тоже все получится.
Наверно не надо объяснять, что на любой материнской плате отдельная шина данных связывает слоты для микросхем памяти с центральным процессором. Так вот на нашей схеме такая шина тоже есть и не одна, а две, так как АРМ процессоры работают с двумя типами памяти: память данных и память программ. Самые любопытные могут даже почитать, что такое много-портовая SRAM память. Так вот в нашей микросхеме тоже есть такого типа оперативная память как для слотов на материнской плате. На нашей схеме SRAM это, как бы, внешняя память процессора и ITCM и DTCM шины к ней, только все это вместе находится внутри одной микросхемы.
Собственно, теперь можно понять разницу между терминами «процессор» и «микроконтроллер». Под процессором понимается часть системы, собственно вычислительное ядро (в нашем случае типа АРМ) без памяти и без периферии, а все вместе, то есть АРМ-процессор +Память +Периферия +Шины +Мосты +Ноги-микросхемы — буржуи именуют микроконтроллером. То есть микроконтроллер внутри себя содержит процессор. Поскольку существуют микроконтроллеры или просто контроллеры, которые НЕ имеют в своем составе процессоров, я предпочитаю все-таки рассматриваемую микросхему (тип микросхем) называть процессором, чтобы сделать акцент на том, что эта микросхема с процессором внутри.
На самом деле и для нашего микроконтроллерного процессора можно распаять на выделенный набор ног шину данных и подключить к этим ногам действительно внешнюю микросхему памяти, например некоторую микросхему, например, SDRAM памяти. Если вы возьмете какую-то отладочную плату для нашего процессора, то такая память там может быть распаяна, по крайней мере у меня когда-то была отладочная плата с такой внешней памятью.
С точки зрения работы процессора эта память не отличается от встроенной (впаянной) внутри микросхемы SRAM памяти, кроме того, что операции чтения и записи данных (байтов, слов) из этой памяти будут длиться заметно дольше до 5 раз дольше!
Чтобы продемонстрировать что эта задержка значит на практике, достаточно рассмотреть простейший пример С-кода:
int A = 15;
int B;
...
B = A;
...
Чтобы выполнить присваивание процессор должен прочитать значение 15 из ячейки памяти по адресу, который для нас именуется как А, по сути А это буквенное представление некоторого адреса в памяти и, значит, в некоторой микросхеме памяти. То есть адрес задает еще и физический носитель данных, к которому обращается процессор выполняя ассемблерные инструкции.
Если адрес относится к внутренней быстрой памяти загрузка данных произойдет, скажем за 2 такта процессора, а если этот адрес принадлежит внешней SDRAM памяти таже самая загрузка данных произойдет за 10 тактов процессора. Если мы хотим проанализировать длительность операции присваивания, нам придется анализировать еще и физическую принадлежность адреса переменной B.
Но латентность обращений к памяти может значительно компенсироваться наличием в процессоре встроенной кэш-памяти, как известно.
Чтобы использовать внешнюю SDRAM память надо:
сконфигурировать периферийный модуль 28. SDRAM Controller (SDRAMC).
Надо сконфигурировать соответствующие выводы процессора из таблицы раздела «5. Package and Pinout» как линии интерфейса к микросхеме памяти.
Надо прописать блок настроек в периферийный модуль 31. Power Management Controller (PMC).
Прописать в настройках линковщика проекта на C /C++/ assembler диапазоны доступных областей памяти в memory protection unit (MPU), описание которого можно найти в общей документации по АРМ-архитектуре. Refer to the ARM Cortex-M7 Technical Reference Manual (ARM DDI 0489) available on www.arm.com/.
Наверняка я что-то здесь забыл, поскольку пару лет уже не занимался по такому справочнику.
Слава богу производитель (или какие-то аффилированные с производителем разработчики софта) предоставляет набор примеров, в котором вся процедура расписана в виде С-кода, и нам обычно надо только поправить некоторые настройки-константы в соответствии с параметрами-конфигурацией нашего железа. Собственно без такого более-менее законченного примера пытаться разобраться с тем как работает некоторая система в процессоре и как ее использовать в каких-то своих целях практически не имеет смысла, если это не какой-то простенький периферийный юнит вроде SPI или UART, хотя и современные версии UART-ов разворачивают такие структуры конфигурации для управления таким количеством аппаратных функций и возможностей, что без некоторого предварительного опыта работы с менее навороченными версиями задача их правильной настройки тоже попадает в разряд «не для слабонервных», даже если ваше окружение разработчика (IDE) предоставляет некоторый визуальный интерфейс конфигурации периферии. Кстати, хочу заметить, что для старших версий процессоров-микроконтроллеров я не видел таких визуальных интерфейсов. Мне кажется, что создание визуальных интерфейсов для конфигурации периферии на некотором уровне сложности этой самой периферии становится практически невозможной (не оправданно затратной) задачей, и все что могут предложить разработчики таких сложных микросхем, это набор примеров С-кода для демонстрации возможных применений внутренних модулей микроконтроллера.
Есть, например вот такой современный код демонстрации работы с внешней памятью (один из многих):
https://github.com/atmelcorp/atmel-software-package/tree/master/examples/sdmmc_sdcard
Дополнительной проблемой таких примеров является то, что они написаны сразу для десятка аппаратных платформ-конфигураций.
Память устройств ввода/вывода, регистры управления периферией
Выше мы рассмотрели физические модули оперативной памяти, это физические единицы, которые предназначены только для хранения данных, которые процессор использует в своих вычислениях, но любое устройство из состава микроконтроллера, например контроллер SPI шины представляется для процессора как модуль памяти, как диапазон адресов из которых можно прочитать данные и в которые можно записать данные (байты, биты, n-байтные слова). Процессор в общем то не умеет ничего, кроме как писать и читать данные из памяти.
Есть два основных способа организации памяти которые отображаются в то, какие инструкции ассемблера доступны процессору для исполнения. Память может быть сплошной или линейной и тип адресуемого устройства определяется только принадлежностью к определенному диапазону адресов (просто диапазон чисел) ИЛИ
Может существовать два (или более) типа адресов. То есть тип адресуемого устройства будет зависеть не только от численного значения адреса, но и от инструкции ассемблера с помощью которой происходит адресация, по-другому можно сказать, что выбор инструкции ассемблера компилятором зависит от типа устройства, к которому в коде задано обращение, это две стороны одной медали.
В общем принцип работы любого устройства для приема данных (для измерения в том числе) сводится к тому что устройство предоставляет адрес памяти по которому можно узнать готовы ли принятые устройством данные и соответственно можно ли их читать и использовать, при этом сама операция чтения этих готовых данных из устройства переводит устройство в режим ожидания новой порции данных и сбрасывает признак готовности данных. Это допустимый режим работы процессора с устройством он называется «режим постоянного опроса» - polling. Есть другой режим работы устройств, режим работы через прерывание interrupt, когда устройство генерирует сигнал который останавливает (прерывает) текущую работу процессора и заставляет процессор перейти по адресу в памяти программ по которому располагается функция которая обработает полученные устройством данные и сделает их доступными основной программе процессора, которая в свою очередь должна получить возможность их обработать.
Мало кто знает, что те же сигналы прерывания могут использоваться для организации работы DMA. Дело в том, что сигналы прерываний — это действительно сигналы, они могут воздействовать на устройство, до которого они доведены и для которого они разрешены (настроены на управление некоторой функцией). Наш АРМ-процессор это просто одно из устройств в составе микроконтроллера, так же, как и DMA контроллер. Прерывания либо нарушают на время последовательное исполнение кода процессором, либо когда они направлены в DMA контроллер, запускают соответствующие транзакции копирования данных между разными регионами памяти. Есть и другие функции, которые могут запускаться по прерываниям, но они очень специфичные и изучаются в рамках описания отдельных устройств периферии.
Я настоятельно рекомендую перед тем, как пытаться разбираться с принципами построения-использования DMA, изучить сначала систему работы и использование прерываний.
DMA контроллер
Теперь когда мы познакомились с примером подключаемой памяти, вспомнили про прерывания, про шины данных, мы можем подступиться к тому, что представляет собой описание контроллера DMA в нашем АРМ-микроконтроллере.
Обратите внимание на рисунке приведены только начало и конец подпункта 35.9, на самом деле он состоит из 31 подпункта.
Далее вы можете найти вот такую схему реализации контроллера DMA:
Слово интерфейс-interface здесь обозначает интерфейс к шине данных, как мы можем видеть DMA контроллер это все в основном о шинах и о сигналах прерываний.
В самом простом случае DMA контроллер реализует функцию, которая может быть выражена в псевдо коде примерно так:
for (int i=0; i<Conf.Count;i++)
{
Wait(Conf.EnabledInteruptSignal);
*Conf.DstPtr = *Conf.SrcPtr;
Conf.DstPtr += Conf.IncrementDst;
Conf.SrcPtr += Conf.IncrementSrc;
}
где Cong - 'это структура конфигурации канала DMA.
Использование Операционной Системы
С точки зрения Embedded разработки, о которой только и может идти речь, когда мы говорим о микроконтроллерах необходимость использования операционной системы и даже само определение операционной системы вызывает много вопросов. Классическая ОС позволяет вам выбрать файл и запустить его на исполнение из консольного окна, удаленного или не очень. Если исходить только из этого примитивного набора функций, который определяет программу, которая уже зашита и крутится внутри микроконтроллера, как Операционную Систему, мы получим огромное разнообразие вариантов реализации такой функциональности, начиная с того, где будет расположена файловая система, из которой мы сможем запускать на исполнение файлы и заканчивая тем, через какой интерфейс мы будем отображать окно консоли. Если у вас есть хоть какая-то возможность подключиться к вашему устройству с ПК и получить кокой-то вывод на консоль из вашего устройства – это тоже можно назвать Операционной Системой, это ОС которая обеспечивает операцию логирование на удаленный терминал. Без наличия такого софта, который (обязательно в исходниках хотя иначе и не бывает, по крайней мере у меня по другому не случалось), такого софта который:
демонстрирует вам работоспособность аппаратной платформы и всей инфраструктуры необходимой для компиляции прошивки,
для записи прошивки в память микроконтроллера для исполнения,
для подключения к вашему работающему устройству и для индикации результатов этой работы хотя бы в текстовом режиме.
Без наличия такой инфраструктуры мы не сможем работать даже с простейшим 8-ми битным микроконтроллером.
Будь моя воля я бы запретил называть Операционной Системой любую программу, а тем более библиотеку которая не позволяет загружать и запускать на исполнение внешнюю программу и потом останавливать эту загруженную программу, чтобы загрузить и запустить другую программу.
Я думаю, Операционной Системой надо называть программу, которая позволяет выполнять внутри себя другую программу (хотя бы одну) и управлять этим исполнением. По крайней мере такое определение через соотношение программ снимает большинство вопросов по Операционным Системам, как мне кажется.
Библиотеки, которые компилируются вместе с целевым алгоритмом и обеспечивают в разных вариациях аналоги POSIX и C++ объектов синхронизации, потоков, очередей остаются не более чем библиотеками.
Но даже 20 лет назад я наблюдал успешные попытки воткнуть Linux на тогда еще 16-битный, но вполне взрослый процессор. К сожалению, эта титаническая работа по натягиванию Linux, например, на ваш микроконтроллер практически никак не помогает вам решить вашу целевую Embedded задачу, если, конечно, это не прошивка для какого-нибудь смартфона более-менее стандартной функциональности. Вам все также досконально придется разбираться с описанием аппаратной части периферии, задействованной в вашей задаче, но при этом вам придется еще учитывать ограничения по реализации драйверов в рамках вашей успешно портированной на вашу Embedded платформу Операционной Системы. Единственная польза, которую мне удавалось извлечь из таких портированных систем это подсмотреть некоторые решения по использованию аппаратных возможностей платформы, чтобы применить их в своем “Bare Metal” решении, ну и просто, с точки зрения диагностики, всегда полезно иметь альтернативную прошивку, пусть очень ограниченной или даже альтернативной функциональности для своей Embedded системы.
На этом пока все. Если у меня будет время и вдохновение я постараюсь расписать и разрисовать практическую задачу с обоснованным применением DMA. То есть такую задачу, которая не решается без использования DMA. У меня есть высокоскоростной преобразователь интерфейса из последовательной шины в Ethernet на базе LWIP библиотеки.
Ссылки
https://github.com/atmelcorp/atmel-software-package/tree/master/lib/lwip
https://www.tme.eu/Document/b85c146da65ff583bac4b0dfc098ee7b/ATSAME70-DTE.pdf