[Из песочницы] Программирование ARM на Pascal
Однажды, вдруг совершенно неожиданно и без объявления войны, появилась идея. И требовалось для этого написать и запрограммировать кристалл STM32.
А собственно в чем проблемам? stm32vldiscovery лежала на полке и дожидалась своего часа, программирование знаю и частенько пишу «на заказ». С железом дружу хорошо.
Первым делом возник вопрос «на чем писать»? Сред программирования много, но язык только «Си». Без вариантов. Ассемблер не рассматриваю в принципе. Светодиодом помигать можно, но что-то сложнее требует огромных трудозатрат.
Но я не знаю Си! Вообще. Всю жизнь писал только на Pascal/Delphi. Учить язык? Вы пробовали учить язык когда вам более 40 лет возраста? Когда работа, семья и минимум свободного времени. Когда ум уже не так остр, как в молодости. Да и затевать все это ради одного проект смысла не более, чем учиться на права и покупать машину ради поездки в булочную в соседнем доме.
Выходом послужил найденный «mikroPascal PRO for ARM» от MikroElektronika. Если честно, я уже работал с «mikroPascal PRO for PIC» на пике популярности PIC чипов. Впечатления остались не очень хорошие. Компилятор «со странностями», оболочка тоже не отличалась стабильностью и дружественным интерфейсом.
Тем более интересно было посмотреть, что изменилось за эти годы и в какую сторону.
И так, что мы имеем на руках:
- Плату stm32f4discovery;
- mikroPascal PRO for ARM с лицензионном ключем (взято у товарища. потом придется вернуть). Без ключа — ограничение в 2 КВ на размер кода;
- Инженер, которого в ВУЗе учили исключительно Pascal.
Задача: освоить программирование микроконтроллера без единой строчки Си кода.
Итак приступим…
Создание проекта несложно. File → New — > New Project.
Указать тип микроконтроллера. Спросит, какие стандартные библиотеки используем (по умолчанию — все). Оставляем. «Лишние» библиотеки при компиляции будут выкинуты автоматом.
Не забываем настроить тактиирование. Если есть сомнения — воспользуетесь одним из стандартных «Scheme». Это набор настроек для параметров тактирования.
Первым делом поиграемся светодиодами. Давайте просто попросим их гореть. Напоминаю, светодиоды сидят на портах D12, D13, D14 и D15.
program MyProject1; begin // инициализируем порт на выход GPIO_Digital_Output(@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15); // зажигаем SetBit(GPIOD_ODR, 12); SetBit(GPIOD_ODR, 13); SetBit(GPIOD_ODR, 14); SetBit(GPIOD_ODR, 15); while true do nop; end.
Стоп! Сейчас у огромного количества народа, имеющего опыт работы с данными микроконтроллерами, возникнет вопрос:, а где включение тактирования порта?!
Очень просто: при иниациализавции порта на ввод или вывод тактирование включается автоматически. Не верите — идем View→Statistics→Functions Tree (моя любимая!).
Если есть сомнения в том, что компилятор делает «автоматом» — идем и смотрим. Смотреть на горящий светодиод конечно приятно. Но скучно. Давайте попросим его помигать
program MyProject1; begin // выставляем пины на выход и вход GPIO_Digital_Output(@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15); while true do begin if TestBit(GPIOD_ODR, 12) then ClearBit(GPIOD_ODR, 12) else SetBit(GPIOD_ODR, 12); Delay_1sec; end; end.
На плате есть кнопка. Давайте и её запустим в работу. При нажатии на кнопку светодиоды загораются. А при отпускании — гаснут (правда неожиданно! — шутка). Кнопка у нас на A0. Для чтения используем функцию Button. Она может подавлять дребезг контактов и учитывает логику кнопки (НЗ или НО). В принципе можно легко читать состояние бита порта «напрямую», но так читабельнее.
program MyProject1; begin // выставляем пины на выход и вход GPIO_Digital_Output(@GPIOD_BASE, _GPIO_PINMASK_12 or _GPIO_PINMASK_13 or _GPIO_PINMASK_14 or _GPIO_PINMASK_15); GPIO_Digital_Input(@GPIOA_BASE, _GPIO_PINMASK_0); while true do if Button(GPIOA_IDR, 0, 10, 1) then begin // зажигаем SetBit(GPIOD_ODR, 12); SetBit(GPIOD_ODR, 13); SetBit(GPIOD_ODR, 14); SetBit(GPIOD_ODR, 15); end else begin // гасим ClearBit(GPIOD_ODR, 12); ClearBit(GPIOD_ODR, 13); ClearBit(GPIOD_ODR, 14); ClearBit(GPIOD_ODR, 15); end; end.
Прямой опрос кнопки в цикле? Не всегда есть возможность постоянно проверять кнопку. Часто кристалл занят более важными делами, чем опрос кнопок. И мы можем просто «упустить» момент нажатия. Правильнее будет при нажатии кнопки генерировать прерывание, где и обрабатываем событие.
program MyProject1; procedure INT_EXTI0(); iv IVT_INT_EXTI0; ics ICS_AUTO; begin EXTI_PR:=$FFFF; // clear flag if TestBit(GPIOD_ODR, 12) then ClearBit(GPIOD_ODR, 12) else SetBit(GPIOD_ODR, 12); end; begin // настраиваем выводы GPIO_Digital_Output(@GPIOD_BASE, _GPIO_PINMASK_12); GPIO_Digital_Input(@GPIOA_BASE, _GPIO_PINMASK_0); EXTI_IMR := %1; Разрешаем прерывание от нужного входа EXTI_FTSR :=$FFFF; Прерывание по приходу 0 NVIC_IntEnable(IVT_INT_EXTI0); // Enable External interrupt EnableInterrupts(); while true do ; end.
Что у нас простаивает в кристалле? Таймер? Давайте с режимом ШИМ поиграемся. Что там по выводам? 4-й таймер выходит на вывод, что подсоединен к светодиоду. Так что мы ждем?
program MyProject1; var ratio, tmp: word; begin ratio := PWM_TIM4_Init(25000); tmp:=0; PWM_TIM4_Set_Duty(0, _PWM_NON_INVERTED, _PWM_CHANNEL1); PWM_TIM4_Start(_PWM_CHANNEL1, @_GPIO_MODULE_TIM4_CH1_PD12); while true do begin PWM_TIM4_Set_Duty(ratio-tmp, _PWM_INVERTED, _PWM_CHANNEL1); Inc(tmp); if tmp>ratio then tmp:=0; Delay_1ms; end; end.
Таймер можно использовать не только для ШИМ. Руки просто чешутся использовать таймер для выполнения периодических действий. Давайте например помигаем таймером. Таймер будет дергать прерывание, а сам прерывание — управлять светодиодом.
Для этого нужно долго и упорно считать коэффициенты для записи в регистры таймера. А если нет — воспользоваться бесплатной программой «Timer Calculator» с сайта производителя.
Вбиваем исходные данные и получаем готовый код таймера. Как-то так:
program ETXI; procedure Timer2_interrupt(); iv IVT_INT_TIM2; begin TIM2_SR.UIF := 0; if TestBit(GPIOD_ODR, 12) then ClearBit(GPIOD_ODR, 12) else SetBit(GPIOD_ODR, 12); end; procedure InitTimer2(); begin RCC_APB1ENR.TIM2EN := 1; TIM2_CR1.CEN := 0; TIM2_PSC := 2239; TIM2_ARR := 62499; NVIC_IntEnable(IVT_INT_TIM2); TIM2_DIER.UIE := 1; TIM2_CR1.CEN := 1; end; begin // выставляем пины на выход GPIO_Digital_Output(@GPIOD_BASE, _GPIO_PINMASK_12); // Enable digital output on PORTD // //Timer2 Prescaler :2239; Preload = 62499; Actual Interrupt Time = 1 InitTimer2(); while(TRUE) do nop; // Infinite loop end.
UART? Тоже легко. Подключаем переходник UART-USB. Давайте для примера выводить значение температуры процессора каждую секунду.
program MyProject1; var uart_tx : string[20]; tmp: integer; tmp1:real; begin UART2_Init(9600); // Настраиваем UART на скорость 9600 bps ADC1_Init(); Delay_ms(100); // Ожидаем стабилизации UART TSVREFE_bit:=1; UART2_Write_Text('Hello!'); UART2_Write(13); UART2_Write(10); while (TRUE) do begin tmp:= ADC1_Read(16); // Читаем данные температуры tmp1:=(3300*tmp)/4095; // Пересчитываем mV. Из расчета: 3.3 V = 4096 äèñêðåò tmp1:= ((tmp1-760)/2.5)+25; // Считаем в градусах. V25=0.76V S=2.5mV/C FloatToStr(tmp1, uart_tx); uart_tx:='T: '+uart_tx+ ' C'; UART2_Write_Text(uart_tx); UART2_Write(13); UART2_Write(10); Delay_ms(1000); end; end.
Запускаем… Температура корпуса -27С. Это при том, что в комнате 23С. В принципе ничего странного. Сам производитель не рекомендует использовать данный датчик для измерения абсолютных температур, а использовать только для измерения роста/уменьшения температуры. Выходное напряжение датчика температуры сдвинуто от чипа к чипу, и сдвиг может достигать 45 градусов.
Стоп! Мы забыли сделать инициализацию GPIO? А тут еще одна «фирменная» фишка: при инициализации модуля, работающего «на выводы», инициализация выводов происходит автоматически. Сомнения? Идем View→Statistics→Functions Tree (моя любимая!).
Как видим выводы автоматом переведены в альтернативный режим и для них включено тактирование.
И напоследок — доберемся до USB. Самое простое. Сделаем HID устройство, которое просто возвращает принятое сообщение.
program HID_Read_Write_Polling; var cnt, kk : byte; var readbuff : array[64] of byte; var writebuff : array[64] of byte; begin HID_Enable(@readbuff,@writebuff); while TRUE do begin USB_Polling_Proc(); // Call this routine periodically kk := HID_Read(); if (kk <> 0) then begin for cnt:=0 to 63 do writebuff[cnt]:=readbuff[cnt]; HID_Write(@writebuff,64); end ; end; end.
Или аналогичное действие с использованием Interrupt:
program HID_Read_Write; var cnt : byte; var readbuff : array[64] of byte; var writebuff : array[64] of byte; procedure USB1Interrupt(); iv IVT_INT_OTG_FS; begin USB_Interrupt_Proc(); end; begin HID_Enable(@readbuff,@writebuff); while TRUE do begin while(HID_Read() = 0) do ; for cnt:=0 to 63 do writebuff[cnt] := readbuff[cnt]; while(HID_Write(@writebuff,64) = 0) do ; end; end.
Для нормальной работы этой программы (и любой программы, в которой используется стандартная библиотека USB) необходимо сгенерировать файл USBdsc.mpas. В нем прописана параметры USB для данного устройства. Сразу скажу, с меню «Tools» присутствует утилита «HID Terminel». Эта утилита позволяет сформировать правильный файл, достаточный для запуска примеров и простых приложений. А если что посложнее — смотрим описание шины USB. И правим файл. Благо он щедро снабжен комментариями.
При помощи этой утилиты проверяем работу примера:
Конечно сейчас будут куча ворчания насчет «неэффективности» кода. Но посмотрите — достаточно «тяжелый» пример с USB HID занимает 1.7% флеша и 1.2% опреативной памяти.
И небольшое отступление.
Вся эта «волшебная» машинерия работает только при правильной настройке системы тактирования. Если абсолютно правильный код начинает «чудить», внешние интерфейсы начинают «пропадать», а подключение USB интерфейса порождает сообщения «Ошибка запроса идентификатора устройства» — откройте настройки проекта и еще раз проверьте тактирование.
Все это показывает, что знание Си не является обязательным условием для эффективного программирования микроконтроллера. А наличие стандартных библиотек значительно снижают порог вхождения для неподготовленных людей. При этом код получается достаточно компактный и читабельный.
Подробное описание среды программирования — будет написано отдельной статьей.
P.S.: На самом деле я знаю Си (С++). Свободно читаю и понимаю код. Но написание программ на данном языке является для меня «некомфортным». Мозги автоматом выдают выход в паскалеподобном коде.