Миландр представляет хорошую поддержку в SPL для микропроцессоров 1986ВЕ1Т и семейства 1986ВЕ9х. Но помимо этого производитель выпускает достаточно много других интересных микропроцессоров, поддержка которых пока реализована слабо. В этой статье мы рассмотрим, как создать проект для одного из таких МК - 1901ВЦ1Т.
Микропроцессор 1901ВЦ1Т состоит из двух ядер - RISC и DSP. Первое RISC ядро аналогично ядру семейства 1986ВЕ9х и разработано на базе ARM Cortex M3. Поэтому все, что мы напишем для 1986ВЕ9х, должно более-менее прижиться и в 1901ВЦ1Т. Работать с этим ядром мы будет в том же Keil.
DSP ядро является аналогом TMS320C546 и для его программирования необходима среда "Code Composer Studio" версий 2 или 3.3. Собранный с помощью данной среды hex файл можно загрузить через RISC ядро в DSP. Также, на сколько я знаю, среда разработки "CodeMaster-ARM" позволяет производить компиляцию и отладку для обоих ядер сразу. Это наверное очень удобно, но в моем случае пока недоступно. Поэтому мы пойдем по первому пути и попробуем когда-нибудь реализовать вариант с загрузкой DSP через RISC ядро. Сейчас же создадим первый проект, который будет собираться и загружаться в 1901ВЦ1Т.
Дополнение: Pack с поддержкой 1901ВЦ1Т теперь можно взять из этого проекта - ссылка. С ним проекты для 1901ВЦ1Т собираются стандартным образом, как и для прочих микроконтроллеров. Поэтому статья становится не вполне актуальна, но показывает способ собрать проект под микроконтроллер, для которого нет поддержки.
(Для сборки проекта необходимо установить Legacy support for ARM Cortex-M devices. Если этого не сделать, то файлы поддержки ядра, такие как core_cm3.h, core_cmFunc.h, … необходимо будет подключить вручную из пака для микроконтроллеров 1986ВЕ9х.)
Итак, нажимаем создать новый проект, выбираем папку и название проекта. Назовем его "HelloWorld_1901". После этого нам открывается окно с выбором микроконтроллера. Можно раскрыть дерево "Milandr" и убедиться, что наш процессор не поддерживается в PACK от производителя. Надеюсь вскоре это исправят.
Раскрываем дерево "ARM", выбираем "ARM Cortex M3" и там присутствует всего один шаблон "ARMCM3" - выбираем его, жмем "ОК".
Далее открывается окно "Manage Run-Time Environment", ничего выбирать тут не нужно, просто жмем "ОК".
Создался пустой проект, папку для кода "Source Group 1" мы лаконично переименуем в "user". Добавим файл "main.c":
В группе проекта появился файл "main.c". Давайте сразу реализуем в нем пустую функцию main.
Запускаем компиляцию и видим по ошибкам внизу, что нашему проекту много чего не хватает, и в первую очередь, таблицы векторов прерываний. Таблица векторов прерываний обычно находится в файле с расширением "*.s", ищем такой файл для нашего процессора в папке PACK от Миландр и находим его по такому пути:
C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Libraries\CMSIS\CM3\DeviceSupport\MDR1901VC1T\ inc\ MDR1901VC1T.h startup\arm\ startup_MDR1901VC1T.s system_MDR1901VC1T.c system_MDR1901VC1T.h
Здесь я сразу представил дерево путей и все файлы, которые нам потребуются для компиляции проекта. Эти файлы защищены от записи, а нам потребуется внести некоторые изменения. Кроме этого, эти файлы потенциально могут быть обновлены при апдейте Pack. Поэтому скопируем их в директорию нашего проекта, для этого заведем в ней отдельную папку - "lib". Затем снимем защиту от записи:
Подключаем файл с таблицей векторов прерываний - "startup_MDR1901VC1T.s":
Пытаемся скомпилировать проект, и возникает следующая ошибка:
По сообщению компилятора понятно, что объектному файлу скомпилированному из файла "startup_MDR1901VC1T.s" требуется внешняя (из другого объектного файла) функция SystemInit. Если поискать, то такая функция реализована в файле "system_MDR1901VC1T.c". Подключаем его к проекту аналогично тому, как подключали файл "startup_MDR1901VC1T.s". Пытаемся скомпилировать:
Опять ошибка, требуется подключить еще один файл - "MDR32F9Qx_config.h", он располагается в C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Config. Скопируем его так-же к себе в проект к остальным библиотечным файлам, в папку "lib" и подключим.
Теперь при попытке сборки, возникает аналогичная ошибка, но с указанием, что требуется файл "MDR32F9Qx_lib.h". Находим его в C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Libraries\MDR32F9Qx_StdPeriph_Driver\inc и подключаем. Запускаем сборку и видим, что проект успешно собрался.
Проект компилируется, теперь необходимо указать специфику микропроцессора в опциях проекта. В микропроцессоре 1901ВЦ1Т используется RISC ядро аналогичное 1986ВЕ92У, поэтому все настройки необходимо выставить как в статье - "Настройки проекта для 1986ВЕ9х", за исключением вкладки "Device".
Если сравнить спецификации на 1901ВЦ1Т и 1986ВЕ9х, то видно, что порты ввода вывода располагаются в одинаковых адресах и с одинаковыми смещениями (раздел "Описание регистров портов ввода-вывода"). Также наблюдаем полную аналогию в тактировании для CPU_CLK (раздел "Сигналы тактовой частоты" - схема тактирования и описание регистров). По этой причине наш пример "Hello World - светодиод" не требует никаких дополнительных доработок. Тактирование и переключение вывода порта будут работать и в данном микроконтроллере 1901ВЦ1Т. Копируем наш код из примера в текущий файл "main.c"
Для работы примера требуется два файла - MDR32F9Qx_port.c и MDR32F9Qx_rst_clk.c. Они должны быть скомпилированы средой Keil, поэтому подключаем их к нашему проекту. Файлы эти находятся по следующему пути:
C:\Keil_v5\ARM\PACK\Keil\MDR1986BExx\1.4\Libraries\MDR32F9Qx_StdPeriph_Driver\src
Окно проекта у нас теперь выглядит так, здесь подключены все необходимые файлы:
Для того чтобы компиляция прошла успешно, необходимо указать среде, где следует искать заголовочные файлы.
На данном этапе проект компилируется с многочисленными ошибками. Для их устранения требуется зайти в файл MDR32F9Qx_config.h и сделать макроопределение под наш микропроцессор.
#ifndef __MDR32F9Qx_CONFIG_H #define __MDR32F9Qx_CONFIG_H #define USE_MDR1901VC1T /* - Это макроопределение указывает используемый МК*/ .... .... #endif /* __MDR32F9Qx_CONFIG_H */
Теперь проект снова успешно собирается. Осталось проверить, какой вывод и от какого порта разведен на один из светодиодов на демоплате. На плате, рядом с микросхемой памяти DD1, виден целый ряд перемычек к каждому светодиоду. Если присмотреться, то на плате указана маркировка, какой порт используется на каждом светодиоде. Давайте выберем крайний светодиод, он подписан на плате РВ11. Модифицируем код "Hello World - светодиод" для мигания этим светодиодом.
Для того чтобы оставить рабочим старый вариант, вынесем все определения, зависящие от конкретного МК, в макроопределения. Итоговый код получился таким:
#include <MDR32F9Qx_port.h> #include <MDR32F9Qx_rst_clk.h> // Прототип функции задержки, реализованной ниже void Delay(int waitTicks); #define _test1901 // Выбираем секцию 1901ВЦ1Т для компиляции #ifdef _testBE9x // Определения для 1986ВЕ9х - неактивна при выбранном ключе _test1901 #define PORT_LedClock RST_CLK_PCLK_PORTC #define PORT_Led MDR_PORTC #define PORT_LedPin PORT_Pin_0 #define DELAY_Ticks 1000000 #endif #ifdef _test1901 // Определения для 1901ВЦ1Т - активна #define PORT_LedClock RST_CLK_PCLK_PORTB #define PORT_Led MDR_PORTB #define PORT_LedPin PORT_Pin_11 #define DELAY_Ticks 1000000 #endif // Точка входа, отсюда начинается исполнение программы int main() { // Заводим структуру конфигурации вывода(-ов) порта GPIO PORT_InitTypeDef GPIOInitStruct; // Включаем тактирование порта C RST_CLK_PCLKcmd (PORT_LedClock, ENABLE); // Инициализируем структуру конфигурации вывода(-ов) порта значениями по умолчанию PORT_StructInit(&GPIOInitStruct); // Изменяем значения по умолчанию на необходимые нам настройки GPIOInitStruct.PORT_Pin = PORT_LedPin; GPIOInitStruct.PORT_OE = PORT_OE_OUT; GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW; GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL; // Применяем заполненную нами структуру для PORTC. PORT_Init(PORT_Led, &GPIOInitStruct); // Запускаем бесконечный цикл обработки - Основной цикл while (1) { // Считываем состояние вода PC0 // Если на выводе логический "0", то выставляем вывод в логическую "1" if (PORT_ReadInputDataBit (PORT_Led, PORT_LedPin) == 0) { PORT_SetBits(PORT_Led, PORT_LedPin); // LED } // Задержка Delay(DELAY_Ticks); // Считываем состояние вода PC0 // Если на выводе = "1", то выставляем "0" if (PORT_ReadInputDataBit (PORT_Led, PORT_LedPin) == 1) { PORT_ResetBits(PORT_Led, PORT_LedPin); }; // Задержка Delay(DELAY_Ticks); } } // Простейшая функция задержки, позднее мы заменим ее на реализацию через таймер void Delay(int waitTicks) { int i; for (i = 0; i < waitTicks; i++) { __NOP(); } }
Далее все просто - компилируем, прошиваем программу и наблюдаем мигание на выбранном нами светодиоде. Значит наш проект собрался и запустился успешно. Подобным образом можно реализовывать управление прочей периферией RISC ядра 1901ВЦ1Т, схожей по реализации с 1986ВЕ9х.