Как стать автором
Обновить

Программирование ARM на Pascal

Время на прочтение7 мин
Количество просмотров23K
Однажды, вдруг совершенно неожиданно и без объявления войны, появилась идея. И требовалось для этого написать и запрограммировать кристалл 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.: На самом деле я знаю Си (С++). Свободно читаю и понимаю код. Но написание программ на данном языке является для меня “некомфортным”. Мозги автоматом выдают выход в паскалеподобном коде.
Теги:
Хабы:
+13
Комментарии20

Публикации

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн