Однажды, вдруг совершенно неожиданно и без объявления войны, появилась идея. И требовалось для этого написать и запрограммировать кристалл STM32.
А собственно в чем проблемам? stm32vldiscovery лежала на полке и дожидалась своего часа, программирование знаю и частенько пишу “на заказ”. С железом дружу хорошо.
Первым делом возник вопрос “на чем писать”? Сред программирования много, но язык только “Си”. Без вариантов. Ассемблер не рассматриваю в принципе. Светодиодом помигать можно, но что-то сложнее требует огромных трудозатрат.
Но я не знаю Си! Вообще. Всю жизнь писал только на Pascal/Delphi. Учить язык? Вы пробовали учить язык, когда вам более 40 лет возраста? Когда работа, семья и минимум свободного времени. Когда ум уже не так остр, как в молодости. Да и затевать все это ради одного проект смысла не более, чем учиться на права и покупать машину ради поездки в булочную в соседнем доме.
Выходом послужил найденный “mikroPascal PRO for ARM” от MikroElektronika. Если честно, я уже работал с “mikroPascal PRO for PIC” на пике популярности PIC чипов. Впечатления остались не очень хорошие. Компилятор “со странностями”, оболочка тоже не отличалась стабильностью и дружественным интерфейсом.
Тем более интересно было посмотреть, что изменилось за эти годы и в какую сторону.
И так, что мы имеем на руках:
Задача: освоить программирование микроконтроллера без единой строчки Си кода.
Итак приступим…
Создание проекта несложно. File -> New — > New Project.
Указать тип микроконтроллера. Спросит, какие стандартные библиотеки используем ( по умолчанию — все). Оставляем. “Лишние” библиотеки при компиляции будут выкинуты автоматом.
Не забываем настроить тактиирование. Если есть сомнения — воспользуетесь одним из стандартных “Scheme”. Это набор настроек для параметров тактирования.
Первым делом поиграемся светодиодами. Давайте просто попросим их гореть. Напоминаю, светодиоды сидят на портах D12, D13, D14 и D15.
Стоп! Сейчас у огромного количества народа, имеющего опыт работы с данными микроконтроллерами, возникнет вопрос: а где включение тактирования порта?!
Очень просто: при иниациализавции порта на ввод или вывод тактирование включается автоматически. Не верите — идем View->Statistics->Functions Tree (моя любимая!).

Если есть сомнения в том, что компилятор делает “автоматом” — идем и смотрим. Смотреть на горящий светодиод конечно приятно. Но скучно. Давайте попросим его помигать
На плате есть кнопка. Давайте и её запустим в работу. При нажатии на кнопку светодиоды загораются. А при отпускании — гаснут (правда неожиданно! — шутка). Кнопка у нас на A0. Для чтения используем функцию Button. Она может подавлять дребезг контактов и учитывает логику кнопки ( НЗ или НО). В принципе можно легко читать состояние бита порта «напрямую», но так читабельнее.
Прямой опрос кнопки в цикле? Не всегда есть возможность постоянно проверять кнопку. Часто кристалл занят более важными делами, чем опрос кнопок. И мы можем просто “упустить” момент нажатия. Правильнее будет при нажатии кнопки генерировать прерывание, где и обрабатываем событие.
Что у нас простаивает в кристалле? Таймер? Давайте с режимом ШИМ поиграемся. Что там по выводам? 4-й таймер выходит на вывод, что подсоединен к светодиоду. Так что мы ждем?
Таймер можно использовать не только для ШИМ. Руки просто чешутся использовать таймер для выполнения периодических действий. Давайте например помигаем таймером. Таймер будет дергать прерывание, а сам прерывание — управлять светодиодом.
Для этого нужно долго и упорно считать коэффициенты для записи в регистры таймера. А если нет — воспользоваться бесплатной программой “Timer Calculator” с сайта производителя.
Вбиваем исходные данные и получаем готовый код таймера. Как-то так:
UART? Тоже легко. Подключаем переходник UART-USB. Давайте для примера выводить значение температуры процессора каждую секунду.
Запускаем… Температура корпуса -27С. Это при том, что в комнате 23С. В принципе ничего странного. Сам производитель не рекомендует использовать данный датчик для измерения абсолютных температур, а использовать только для измерения роста/уменьшения температуры. Выходное напряжение датчика температуры сдвинуто от чипа к чипу, и сдвиг может достигать 45 градусов.
Стоп! Мы забыли сделать инициализацию GPIO? А тут еще одна “фирменная” фишка: при инициализации модуля, работающего “на выводы”, инициализация выводов происходит автоматически. Сомнения? Идем View->Statistics->Functions Tree (моя любимая!).

Как видим выводы автоматом переведены в альтернативный режим и для них включено тактирование.
И напоследок — доберемся до USB. Самое простое. Сделаем HID устройство, которое просто возвращает принятое сообщение.
Или аналогичное действие с использованием Interrupt:
Для нормальной работы этой программы (и любой программы, в которой используется стандартная библиотека USB) необходимо сгенерировать файл USBdsc.mpas. В нем прописана параметры USB для данного устройства. Сразу скажу, с меню “Tools” присутствует утилита “HID Terminel”. Эта утилита позволяет сформировать правильный файл, достаточный для запуска примеров и простых приложений. А если что посложнее — смотрим описание шины USB. И правим файл. Благо он щедро снабжен комментариями.

При помощи этой утилиты проверяем работу примера:

Конечно сейчас будут куча ворчания насчет “неэффективности” кода. Но посмотрите — достаточно “тяжелый” пример с USB HID занимает 1.7 % флеша и 1.2% опреативной памяти.
И небольшое отступление.
Вся эта “волшебная” машинерия работает только при правильной настройке системы тактирования. Если абсолютно правильный код начинает “чудить”, внешние интерфейсы начинают “пропадать” а подключение USB интерфейса порождает сообщения “Ошибка запроса идентификатора устройства” — откройте настройки проекта и еще раз проверьте тактирование.

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