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

Комментарии (20)


  1. mmMike
    10.08.2015 11:55
    +7

    Уж извините. Сочетание «С железом дружу хорошо.» и «Но я не знаю Си! Вообще. Всю жизнь писал только на Pascal/Delphi.»
    Сразу вызывает когнитивный диссонанс.

    Программирование микроконтроллеров и «С/С++» — «синонимы» уже давно.

    Статья заинтересовала только как «удаление гланд через анальное отверстие».


    1. MacIn
      10.08.2015 13:12

      С каких пор «дружу с железом» — всенепременно программирование микроконтроллеров?


      1. mmMike
        10.08.2015 13:35
        +1

        В статье ни слова о другом контексте жаргонизма «железо» (аналоговые/цифровые схемы и пр.).
        Только контекст микроконтроллеров.


        1. MacIn
          10.08.2015 13:38

          Учитывая, что человек говорит о МК как об «одном проекте», это что-то иное. Вот, мой пример: увлекался радиолюбительством, собирал множество аналоговых устройств. Скоро собираюсь собрать и наладить радиолюбительский Радио86-РК. Для этого тоже надо «дружить с железом». но там Си и близко нет — голый Intel 8080 ассемблер.


          1. mmMike
            10.08.2015 13:55
            +1

            > Скоро собираюсь собрать и наладить радиолюбительский Радио86-РК.

            В 89 году, использовал кросс С (MS DOS) для сборки программ на Z80.
            Кажется там был вариант и для 8080 набора команд.
            При наличие доступных корпусов Z80, использовать более дорогие (на тот момент) 8080 необходимости не было.

            Но… простите, в 2015 году собирать что то на 8080. Хотя и писать на ассемблере для него как раз в русле темы «собрать и наладить радиолюбительский Радио86-РК».
            Можно еще и ламповый телевизор спаять по схеме из журнала Радио…

            Не осуждаю и не критикую, просто не понимаю.


            1. MacIn
              10.08.2015 14:26

              В 89 году, использовал кросс С (MS DOS) для сборки программ на Z80.
              Кажется там был вариант и для 8080 набора команд.

              Да, у меня есть этот компилятор. Но это «кросс», это «не честно». А целиком в 32 Кб ОЗУ он не поместится.

              При наличие доступных корпусов Z80, использовать более дорогие (на тот момент) 8080 необходимости не было

              Зато 580ВМ80А — под рукой.

              Не осуждаю и не критикую, просто не понимаю.

              Люблю ретро. Это просто разминка, потом буду собирать следующий ПК собственной конструкции, для т.н. demo.
              Это просто был пример «железа».


    1. Sykoku
      11.08.2015 11:00

      «Удаление гланд» — это из личного опыта?
      Не обижайтесь, но есть вещи, где С (в любой его реинкарнации) вызывает, как уже говорил кто-то ниже, бешеную потерю времени на отладку. У Вас оно есть — я рад. Высказывание Шерлока Холмса помните? Ему было все равно, Земля вращается вокруг Солнца или наоборот. Для его работы это знание бесполезно. На его чердаке не свалена куча бесполезного хлама (пару версий компиляторов или библиотек, зачастую не работающие при смене версии языка или окружения), а есть только набор всего необходимого, но в безукоризненном порядке.

      И насчет слабоумия.
      Паскаль — это ящик с инструментами, где все на своих местах.
      Си — коробка с размотанными шпульками с нитками. Если потянешь не за ту (а зачастую код пишут не для себя и не ты его потом сопровождаешь -или Вы всю жизнь проведете на одном месте?) — получишь кашу на те самые полдня работы. В лучшем случае.

      И не все железо — это С. Есть вещи, где либо ассемблер (я их знаю 8), либо еще попроще (переключатели).


      1. mmMike
        11.08.2015 11:38
        +1

        Если бы я не сопровождал и не разрабатывал практически каждый день на работе (а данное время):
        1. C (ПО платежных терминалов, которое лень/нет смысла на C++ переводить)
        2. C++ (сервисы Windows + сервисы Unix + FM модуль SafeNet HSM + АРМ curses Unix)
        3. PL-SQL функционал для Unix сервисов и Web.
        4. python для скриптов
        5. Java (Swing приложения + Web (SOAP) сервисы + Web gwt + JavaCard)

        и не работал раньше с lisp (Autocad), fortran, perl, v.basic и Pascal (Delphi) довольно долго, то может и захотел бы продолжить этот спор.

        А так — какая мне разница. Пишите на чем хотите. Меня забавляют выступления апологетов конкретных языков программирования.

        Лично мне все равно какой язык использовать. Принципы везде одинаковы. Программирование это не знание конкретного языка…

        А данном же контексте — тратить время на поиск экзотичного компилятора вместо того что бы потратить час на то что бы глянуть на синтаксис С!
        Все же С++ не так уж от Object Pascal отличается, а C вообще прост.
        Для ARM (STM32) вообще C++ в принципе не использую. Не так уж там много ОЗУ, что бы на динамическую память тратить.

        Область Pascal==Delphi ограничена исключительно тем, на что мертвый Borland обращал внимание. Отсюда и порядок. Как на кладбище!

        P.S. насчет слабоумия — это вы мой пост с каким то другим перепутали…


        1. Sykoku
          11.08.2015 12:01

          Вы меня то же с кем-то перепутали. Я не заставляю писать на чем-то одном.
          Мое «детство» в программировании началось с блок-схем. Потом ушло в Бейсик, в классе 8-м (BBС-ий больше всего нравился, хотя выучил штук 6 диалектов — благо литература была, хоть и при СССР). Да и матчасть (ДВК-1/2 и Искра-1030 позже) не особо позволяла. А вот на низком уровне писать приходилось чаще — ЕС/СМ. Потом в институте пригодилось — старый плоттер подключили к 286-й — и чертежи на диплом не надо рисовать.
          Потом были PDP и НР, micro VAX (создание терминала).
          Довелось работать и на аналоговых машинах (вот вам настоящая ОС РВ).
          Кассовые аппараты, терминалы — только ASM. Иначе потом в коде получаются пустоты и многочисленные лишние телодвижения на сотню-другую тактов (в лучшем случае), а в некоторых сферах это не применимо.
          Писал на С, Prolog'e, Яве, Графорте. Баловался Модулой и Адой.
          И поэтому я не ратую ни за один из языков. И тех, что описал, и тех, на которых сейчас пишу/сопровождаю. У каждого — своя ниша.


  1. dkukushkin
    10.08.2015 12:11
    +2

    Но я не знаю Си! Вообще. Всю жизнь писал только на Pascal/Delphi. Учить язык? Вы пробовали учить язык когда вам более 40 лет возраста?

    Ни прибедняйтесь! После Pascal на C вы перейдете без проблем и в 60. Ведь речь не о Lisp и не о Haskell.

    Причем это сделать полезно — меньше риск развития слабоумия в старости.


  1. segrus
    10.08.2015 12:35
    +5

    Что вы накинулись на человека со своим «Си»? Он просто дал иной способ написания кода. И сделал это блестяще!
    Автору спасибо за интересную статью и ждём продолжения!


    1. MacIn
      10.08.2015 13:25
      +3

      Поддерживаю. Перед автором стоит инженерная задача, для решения которой у него есть ресурсы, в том числе время. При наличии знакомого инструмента, который позволит решить ту же задачу, тратить время на изучение неизвестного для разовой работы просто потому что так принято — нерационально.


  1. Keroro
    10.08.2015 13:14

    Помнится, был еще Бейсик, от этой же конторы…


    1. segrus
      10.08.2015 13:27

      Он и щас есть (в моём случае для AVR). С удовольствием пишу в BASCOM и не нарадуюсь, что он ещё развивается.
      Хотя знаю и «Cи». Но часто, например, проще, быстрее и приятнее написать 2 строчки в Basic, чем кучу непонятного кода в «Си», а потом ловить пол дня ошибки.


  1. safari2012
    10.08.2015 13:44
    +1

    Что-то код в листингах плохо скопировался/вставился — всё в одну строку.
    Неужели нельзя было проверить/поправить перед постингом статьи?


    1. DanilinS
      10.08.2015 23:28

      Вы не поверите — тщательно «вылизал» и проверил. Но это моя первая статья. Пока статью не одобрят — я её не вижу. При редактировании — все красиво. А при публикации совершенно неожиданно вылезла эта проблема.

      Спасибо за сообщение — подправил страницу для правильного отображения кода.


      1. safari2012
        11.08.2015 14:36

        Так гораздо лучше! ))


  1. potan
    10.08.2015 18:05
    -4

    Не нравится Си — есть Rust.
    Все равно что-то придется учить — Паскаль безнадежно устарел.


  1. segrus
    11.08.2015 07:25

    Ещё раз спасибо за статью! Открыл для себя MikroBasic ARM. Улётная вещь! Прощайте сотни строк ST-шного пестрящего кода! Теперь я могу думать над идеей устройства, а не ломать голову «Си» и сотней глючных h-файлов. Таким и должен быть язык программирования.


    1. jok40
      12.08.2015 21:25

      Сотней глючных h-файлов? Надеюсь, это была шутка.