GPIO Extender-F (Forth)
Программируемый мини-контроллер для ваших проектов на базе Mecrisp-Stellaris.

Компактный размер. 10 универсальных портов.
USB для управления и программирования без IDE.
Мини-контроллер с USB интерфейсом
Общее описание

Мини-контроллер на базе микроконтроллера STM32F042 представляет собой компактную программируемую платформу с интерфейсом USB и 10 доступными пинами для подключения различной периферии.
Устройство программируется на языке Forth (Mecrisp-Stellaris), что обеспечивает интерактивную разработку непосредственно через USB-соединение. Контроллер может использоваться для автоматизации, управления устройствами, сбора данных, образовательных целей и различных DIY-проектов.

Характеристики контроллера

  • Микроконтроллер: STM32F042 (ARM Cortex-M0)
  • Тактовая частота: 48 МГц
  • USB интерфейс: CDC (виртуальный COM-порт)
  • Доступные пины: 10 GPIO
  • Питание: через USB
  • Поддержка аналогового ввода (ADC) на 9 пинах
  • Поддержка PWM на четырех пинах
  • Возможность сохранения настроек во флэш-памяти
  • Сторожевой таймер (IWDG)
  • Системный таймер (SysTick)
Характеристики
1. Интерфейс подключения: USB
2. Рабочие интерфейсы: GPIO, ADC, PWM
3. Количество выводов: 25, управляемых - 10
4. Габариты: 54х15х10мм
5. Работает в Win7/Linux/macOS и других ОС
6. Встроенный язык программирования Forth (Mecrisp Stellaris) для самостоятельного программирования логики работы.
Микроконтроллер имеет 32 КБ flash-памяти. Наша прошивка занимает 25 КБ.
Пользователю остается ~7 КБ для реализации своей логики.
Что такое Mecrisp-Stellaris
Mecrisp-Stellaris — это высокоэффективная реализация языка программирования Forth для микроконтроллеров на базе ARM Cortex-M.
Mecrisp-Stellaris отличается компактностью, надёжностью и оптимизированной производительностью. Эта среда позволяет запускать интерактивный интерпретатор Forth непосредственно на микроконтроллере, что открывает возможности для быстрой разработки и отладки кода без необходимости в перепрошивке устройства.

Forth — это уникальный стековый язык программирования, созданный Чарльзом Муром в 1970-х годах. Он предлагает:
  • Интерактивное программирование с мгновенной обратной связью
  • Минимальное потребление ресурсов памяти и процессора
  • Возможность расширения системы "на лету"
  • Прямой доступ к аппаратному обеспечению
  • Высокую портативность кода между разными платформами
  • Модульный подход к написанию программ
Forth идеально подходит для встраиваемых систем, где критичны размер кода и скорость выполнения.
Руководство программиста
Начало работы

  1. Подключите контроллер к компьютеру по USB
  2. Используйте любой терминал для COM-порта (PuTTY, TeraTerm, Minicom и т.д.)
  3. После подключения вы увидите приглашение Forth ok.

Forth - это интерактивный стековый язык программирования:
  • Операнды помещаются в стек
  • Операции извлекают операнды из стека и помещают результат обратно
  • Слова (функции) разделяются пробелами
  • Определение новых слов начинается с : имя_слова и заканчивается ;
Пример базовых операций
2 3 + .           \ Сложение 2+3 и вывод результата
: double 2 * ;  \ Определение слова для умножения на 2
5 double .       \ Результат: 10
Компиляция кода и управление памятью
Mecrisp-Stellaris Forth позволяет компилировать код как в оперативную память (RAM), так и во flash-память. Это важная особенность, которая определяет, будет ли ваш код сохранен после перезагрузки устройства.
\ Переключение в режим компиляции в RAM (будет потеряно при перезагрузке)
compiletoram

\ Переключение в режим компиляции во Flash (сохранится после перезагрузки)
compiletoflash
Создание точек восстановления
Система поддерживает механизм создания точек восстановления (cornerstone), который позволяет откатить состояние программы до определенного момента:
\ Создание точки восстановления с именем "basic_image"
compiletoflash    \ Важно: cornerstone работает только во Flash
: basic_image
  begin here dup flash-pagesize 1- and while 0 h, repeat
  does> begin dup dup flash-pagesize 1- and while 2+ repeat
  cr eraseflashfrom ;
После создания точки восстановления вы можете в любой момент вернуться к этому состоянию, вызвав её имя:
basic_image   \ Откат всех изменений, сделанных после создания cornerstone
Полный сброс пользовательской программы
Для полного удаления всего пользовательского кода из флэш-памяти используйте:
eraseflash   \ Полная очистка пользовательской программы
Рекомендации по компиляции
  1. Используйте compiletoram при разработке и отладке кода. Это позволит быстро вносить изменения без износа flash-памяти.
  2. После отладки переключитесь на compiletoflash и скомпилируйте окончательную версию для сохранения программы.
  3. Создавайте точки восстановления (cornerstone) после важных этапов разработки, чтобы можно было вернуться к рабочей версии.
  4. Делайте резервные копии кода на компьютере для сохранения важных программ.
Пример рабочего процесса
\ Начало разработки
compiletoram    \ Компиляция в RAM для тестирования

\ Тестирование базовых функций
: test-led IO3 iox! ;

\ После отладки, сохранение во Flash
compiletoflash
: test-led IO3 iox! ;

\ Создание точки восстановления
: basic_image
  begin here dup flash-pagesize 1- and while 0 h, repeat
  does> begin dup dup flash-pagesize 1- and while 2+ repeat
  cr eraseflashfrom ;

\ Продолжение разработки...
: more-functions
  \ ... дополнительный код

\ В случае проблем возврат к базовой версии
basic_image
Работа с GPIO
Настройка режимов пинов
\ Настройка пина на вывод (push-pull)
OMODE-PP IO3 io-mode!

\ Настройка пина на ввод с подтяжкой вверх
IMODE-HIGH IO7 io-mode!

\ Настройка пина на ввод с подтяжкой вниз
IMODE-LOW IO2 io-mode!

\ Настройка пина для аналогового ввода
IMODE-ADC IO6 io-mode!
Управление выводами
\ Установка высокого уровня
1 IO3 io!

\ Установка низкого уровня
0 IO3 io!

\ Инверсия состояния пина
IO3 iox!

\ Чтение состояния пина (результат: 0 или -1)
IO7 io@ .
Работа с АЦП
Загрузим необходимые функции
\ Инициализация ADC
: adc-init ( -- )
  9 bit $40021018 bis!   \ Включаем тактирование ADC (RCC_APB2ENR)
  adc-calib
  1 ADC-CR !           \ Включаем ADC (устанавливаем ADEN)
  adc-once drop ;

\ Деинициализация ADC
: adc-deinit ( -- )
  1 bit ADC-CR bis!     \ Отключаем ADC
  9 bit $40021018 bic! ; \ Выключаем тактирование ADC

\ Калибровка ADC: устанавливаем бит ADCAL (бит 31) в ADC-CR и ждём его сброса
: adc-calib ( -- )
  31 bit ADC-CR bis!   \ Устанавливаем ADCAL
  begin 31 bit ADC-CR bit@ 0= until ;

\ Однократное измерение ADC
: adc-once ( -- u )
  2 bit ADC-CR bis!    \ Устанавливаем ADSTART (бит 2)
  begin 2 bit ADC-ISR bit@ until  \ Ожидаем установки флага EOC
  ADC-DR @ ;

\ Чтение значения ADC с выбранного канала.
\ Функция выполняет два измерения для обхода errata.
: adc ( pin -- u )
  io# bit ADC-CHSELR !  \ Выбираем канал ADC, используя вашу функцию io#
  adc-once drop adc-once ;
Инициализация АЦП
\ Инициализация АЦП
adc-init
Чтение аналогового значения
\ Настройка пина для АЦП
IMODE-ADC IO3 io-mode!

\ Чтение сырого значения АЦП (0-4095)
IO3 adc .

\ Чтение значения в милливольтах
IO3 adc-mv .

\ Чтение температуры кристалла в градусах Цельсия
adc-temp .
Пример измерения напряжения
: check-voltage
  IMODE-ADC IO1 io-mode!
  adc-init
  ." Напряжение на IO1: " IO1 adc-mv . ." мВ" cr
  adc-deinit ;
Загрузим необходимые функции
: timer-base ( n -- addr )
  drop $40000000 ; \ Используем TIM2 для упрощения

\ Бит и адрес включения TIM2
: timer-enabit ( n -- bit addr )
  drop 0 bit RCC RCC_APB1ENR_OFFSET + ;

\ Инициализация таймера
: timer-init ( u n -- )  \ u = combined prescaler and auto-reload value
  dup timer-enabit bis!  \ Clock enable
  timer-base >r
  dup 16 rshift          \ Extract upper 16 bits (PSC)
  TIM.PSC r@ + !         \ Write to PSC register
  $FFFF and              \ Extract lower 16 bits (ARR)
  TIM.ARR r@ + !         \ Write to ARR register
  8 bit TIM.DIER r@ + bis!  \ Update event enable
  %010 4 lshift TIM.CR2 r@ + !  \ MMS = update
  0 bit TIM.CR1 r@ + bis!  \ Enable timer
  r> drop ;

: timer-deinit ( n -- )  \ disable timer n
  timer-enabit bic! ;

\ The following pins are supported for PWM setup on STM32F042:
\   TIM2:   PA0  PA1  PA2  PA3

\ Только TIM2
: p2tim ( pin -- n ) drop 2 ;

\ Получаем номер канала по пину (PA0=0, PA1=1, PA2=2, PA3=3)
: p2cmp ( pin -- n ) $3 and ;

\ Настройка альтернативных функций для всех поддерживаемых пинов
: pin-af2 ( pin -- )
  dup PA0 = if
    GPIO-BASE GPIO.AFRL + dup @
    $FFFFFFF0 and 2 or swap !  \ AF2 для PA0
  else dup PA1 = if
    GPIO-BASE GPIO.AFRL + dup @
    $FFFFF0FF and 2 4 lshift or swap !  \ AF2 для PA1
  else dup PA2 = if
    GPIO-BASE GPIO.AFRL + dup @
    $FFFF0FFF and 2 8 lshift or swap !  \ AF2 для PA2
  else dup PA3 = if
    GPIO-BASE GPIO.AFRL + dup @
    $FFF0FFFF and 2 12 lshift or swap !  \ AF2 для PA3
  then then then then drop ;

: pwm-init ( hz pin -- )
  >r
  OMODE-AF-PP r@ io-mode!
  r@ pin-af2
  
  \ Корректный расчет PSC и ARR для заданной частоты
  48000000 swap /                   \ Делитель для достижения нужной частоты
  
  \ Разделим на PSC и ARR (оба 16-битные)
  dup 65536 > if                    \ Если делитель > 65536, нужен предделитель
    dup 65536 / 1+ dup ( div div/65536+1 div/65536+1 )
    >r ( div div/65536+1 ) ( R: div/65536+1 )
    * r> ( div*div/65536+1 div/65536+1 )  
    swap over / 1-     \ ARR = (делитель / PSC) - 1
    swap 1-            \ PSC = div/65536+1 - 1
  else                  \ Если делитель <= 65536
    1 swap 1-           \ PSC = 0, ARR = делитель - 1
  then
  
  \ Установка PSC и ARR
  swap 16 lshift or    \ Создаем комбинированное значение PSC:ARR
  r@ p2tim timer-init
  
  \ Настройка режима PWM mode 1
  r@ p2cmp dup 2 < if
    \ Каналы 1 и 2 (PA0, PA1) используют CCMR1
    2 * 8 * $68 swap lshift
    r@ p2tim timer-base TIM.CCMR1 + bis!
  else
    \ Каналы 3 и 4 (PA2, PA3) используют CCMR2
    2 - 2 * 8 * $68 swap lshift
    r@ p2tim timer-base TIM.CCMR2 + bis!
  then
  
  \ Включение выхода
  r@ p2cmp 4 * bit r> p2tim timer-base TIM.CCER + bis!
;

\ Установка значения PWM
: pwm ( u pin -- )
  >r
  r@ p2tim timer-base TIM.ARR + @  \ Получаем значение ARR
  1+                              \ ARR+1 = полный период
  swap 10000 */                   \ Масштабирование: u * (ARR+1) / 10000
  r@ p2cmp cells                  \ Смещение для нужного канала CCR
  r> p2tim timer-base $34 + +     \ Адрес регистра CCRx
  !                               \ Записываем значение
;

Настройка PWM
\ Инициализация PWM на пине IO3 с частотой 1000 Гц
1000 IO3 pwm-init

\ Установка заполнения 50% (5000 из 10000)
5000 IO3 pwm
Пример плавного изменения яркости светодиода
: led-fade
  1000 IO3 pwm-init  \ инициализация PWM
  10 0 do            \ 10 циклов
    i 1000 * IO3 pwm \ значение PWM от 0 до 9000
    500 ms-delay     \ пауза 500 мс
  loop
  0 IO3 pwm ;        \ выключаем в конце
Системный таймер
Использование системного таймера
\ Инициализация системного таймера
systick-init

\ Чтение текущего значения счетчика миллисекунд
ms_tick_cnt @ .
Пример использования для измерения времени
: measure-time
  systick-init
  ms_tick_cnt @
  \ выполняем измеряемую операцию
  1000 0 do loop
  ms_tick_cnt @ swap -
  ." Время выполнения: " . ." мс" ;
Сторожевой таймер
Настройка сторожевого таймера
\ Инициализация IWDG с таймаутом 1000 мс
1000 iwdg-init-timeout

\ Сброс сторожевого таймера (необходимо вызывать периодически)
iwdg-refresh
Пример использования сторожевого таймера
: safe-loop
  1000 iwdg-init-timeout
  begin
    \ Основной код
    ." Работаю... " cr
    500 ms-delay
    
    \ Сброс сторожевого таймера
    iwdg-refresh
  again ;
Работа с флэш-памятью
Для сохранения настроек мы предлагаем использовать последнюю страницу памяти: SETTINGS_PAGE = 0x8007c00. Не затрите ее кодом, если планируете использовать.

: settings-write ( value addr -- )
    SETTINGS_PAGE +     \ ( value abs_addr )
    2dup h@ = if       \ Check if value equals current value at abs_addr
        2drop          \ If equal, do nothing
    else
        $08000000 - hflash!  \ If different, write value
    then ;

: settings-read ( addr -- value )
    SETTINGS_PAGE + h@ ;  \ Read half-word from absolute address

\ Сохранение настроек во флеш
: settings-erase ( -- )
    SETTINGS_PAGE $08000000 - flashpageerase
;
Работа с флэш-памятью
\ Стирание страницы настроек
settings-erase

\ Запись значения в адрес
123 0 settings-write  \ Запись числа 123 по смещению 0 последнего блока 1 КБ

\ Чтение значения из адреса
0 settings-read .     \ Чтение значения по смещению 0
Пример сохранения конфигурации
\ Структура конфигурации
0 constant CFG_PWM_FREQ    \ смещение для частоты PWM
2 constant CFG_ADC_THRESH  \ смещение для порога ADC

: save-config ( pwm-freq adc-thresh -- )
  settings-erase
  CFG_ADC_THRESH settings-write
  CFG_PWM_FREQ settings-write ;

: load-config
  CFG_PWM_FREQ settings-read
  CFG_ADC_THRESH settings-read ;
Примеры программ
Мигание светодиодом
: blink
  10 0 do
    1 LED1 io!
    500 ms-delay
    0 LED1 io!
    500 ms-delay
  loop ;
Считывание состояния кнопки
: wait-button
  IMODE-HIGH IO7 io-mode!  \ Кнопка с подтяжкой вверх
  begin
    IO7 io@ 0=  \ Проверка на нажатие (0 = нажата)
  until
  ." Кнопка нажата!" ;
Управление сервоприводом
: servo-init
  50 IO3 pwm-init ;  \ 50 Гц для стандартных сервоприводов

: servo-set ( angle -- )  \ angle: 0-180
  \ Преобразуем угол в длительность импульса
  \ от 500 до 2500 мкс (50 до 250 из 10000)
  180 min 0 max  \ Ограничиваем значение от 0 до 180
  2000 * 10000 /  \ Масштабируем
  500 +  \ Добавляем смещение
  IO3 pwm ;

: servo-test
  servo-init
  0 servo-set 1000 ms-delay
  90 servo-set 1000 ms-delay
  180 servo-set 1000 ms-delay
  90 servo-set ;
Аналоговый порог с сигнализацией
: threshold-alert
  IMODE-ADC IO4 io-mode!  \ Аналоговый вход
  OMODE-PP IO7 io-mode!   \ Светодиод для сигнализации
  adc-init
  
  begin
    IO4 adc-mv  \ Измеряем значение в мВ
    dup . ." мВ "
    
    2000 >  \ Порог 2000 мВ
    if
      ." ТРЕВОГА!" cr
      1 IO7 io!  \ Включаем светодиод
    else
      cr
      0 IO7 io!  \ Выключаем светодиод
    then
    
    500 ms-delay
  key? until  \ Выход по нажатию клавиши
  
  adc-deinit ;
Пример использования нескольких PWM каналов
: multi-pwm-test
  \ Инициализация нескольких PWM каналов
  1000 IO3 pwm-init  \ IO3 = PA0 (канал 1)
  1000 IO7 pwm-init  \ IO7 = PA1 (канал 2)
  1000 IO2 pwm-init  \ IO2 = PA2 (канал 3)
  1000 IO6 pwm-init  \ IO6 = PA3 (канал 4)
  
  \ Установка разных уровней PWM
  2500 IO3 pwm
  5000 IO7 pwm
  7500 IO2 pwm
  9000 IO6 pwm
  
  2000 ms-delay
  
  \ Сброс всех PWM
  0 IO3 pwm
  0 IO7 pwm
  0 IO2 pwm
  0 IO6 pwm ;
Расширенные возможности
Идентификатор устройства
: show-device-info
  ." Chip ID: " chipid drop drop drop .
  ." Hardware ID: " hwid .
  ." Flash size: " flash-kb . ." KB" ;
Ограничения и особенности
  1. Питание: Контроллер питается через USB, при автономной работе необходимо обеспечить стабильное питание 3.3В.
  2. USB питание: При потере USB-соединения или переходе компьютера в режим сна, контроллер переходит в режим приостановки. Учитывайте это при проектировании автономных приложений. Запитывайте устройство от постоянных источников напряжения 5В.
  3. Защита от сбоев: Для критичных приложений используйте сторожевой таймер и механизмы сохранения данных во flash-память.
  4. Ограничения PWM: PWM доступен только на пинах IO3 (PA0), IO7 (PA1), IO2 (PA2), IO6 (PA3) через таймер TIM2.

Надеемся, что данное руководство поможет вам в работе с мини-контроллером. Удачной разработки!
Дополнительные ресурсы