STM32 HAL. Часть 2 - Системное время (+ модуль преключателя).

Приветствую коллеги и просто любопытствующие. Я несколько лет занимаюсь разработкой ПО для встраиваемых систем. В основном для STM32 с использованием Standart Peripheral Library. Недавно попытался пересесть на их HAL под названием CUBE. Мягко говоря, разочаровался этим непродуманным продуктом и окончательно решил, что надо поделиться своим собственным HAL-ом, который накатывается поверх Standart Peripheral Library. В этой и, очень надеюсь, последующих статьях я выложу коды, опишу их и примеры их использования. Кому это интересно — прошу под кат.

Во второй части я расскажу об использовании системного времени в программе реализованного в модуле SysTick. А что бы статья не вышла слишком мелкой, добавлю к этому модуль управления переключателем использующим системное время и модуль GPIO описанный в первой части.

SysTick
Начнём. Для начала нужно скачать соответствующий модуль SysTick.
Затем необходимо определиться с приоритетом прерывания отсчитывающего системное время. Приоритет определяется в source-файле следующим дефайном

    // приоритет прерывания глобального таймера (0..15) 
    #define SYSTICK_PRIORITY    0x01


Далее подключаем модуль и инициализируем глобальный таймер функцией

    // инициализация глобального таймера
    SysTickInit(1000, 0);

Где первый операнд — частота таймера отсчитывающего системное время (думаю практически всегда это будет 1000 Гц, то есть системное время будет считать миллисекунды), второй — включение микросекундного таймера (0 — не включать, 1 — включить). Микросекундный таймер базируется на DWT и используется только для реализации микросекундных пауз. Данная функция возвращает результат 1 если данная частота возможна, иначе 0.

После инициализации глобального таймера, 32-битная переменная globalTimer определённая в source-файле начинает инкрементироваться в обработчике SysTick_Handler с частотой указанной при инициализации. Тем самым отсчитывая системное время.
Теперь посредством функции возвращающей эту переменную

    // Получение текущего системного времени
    uint32_t SysTickGet (void);

можно в любом модуле узнать «который час» в микроконтроллере.

Также для удобства были добавлены 2 банальные функции паузы

    // Пауза в отсчётах глобального таймера
    void SysTickDelay (uint16_t delay);
    
    // Микросекундная пауза
    void SysTickDelayuS (uint16_t delay);

Из комментариев думаю понятен их смысл и различие.

Последняя доступная пользователю функция это деинициализация глобального таймера.

 // Денициализация глобального таймера
    void SysTickDeinit();

Этой функцией мы отключаем срабатывание обработчика SysTick_Handler. К примеру я этой функцией пользуюсь перед такой критической секцией, как переход из бутлодера в основное приложение.

Ну с системным временем разобрались. Переходим к его использованию. И использовать мы его будем с помощью выше озвученного модуля управления переключателем.

Relay
Сразу предупреждаю: данный модуль предполагает использование себя для управления несложными устройствами подключенными к микроконтроллеру. Такими как светодиоды, пьезо-пищалки, реле и т.п. Не надо говорить что модуль плохой потому, что им нельзя реализовать много-килогерцовый ШИМ. Он не для этого.
И так, как же его использовать? Как обычно для начала этот модуль Relay нужно скачать.

Далее в source-файле добавляем пин, который будет использовать наш выключатель и определяем переключатель

extern sGpio gpioLed1;

    // светодиод 1
    sRelay led1 =
    {
        .gpio       = &gpioLed1,  
        .onLevel    = 1,
    };

Где первый параметр — указатель на используемый пин, второй — уровень при котором переключатель считается включенным (0 — земля, 1 — питание). Остальные параметры структуры не используются вне модуля, поэтому о них я не упоминаю. Кому интересно — всегда можно посмотреть в коде. Всё, переключатель создан.

Теперь остаётся его лишь инициализировать в абстракции уровнем выше объявив вместе с extern

    // инициализация светодиода
    RelayInit(&led1);

Инициализированный переключатель становится выключенным при любом значении параметра onLevel.
Всё, наш переключатель готов к работе. Для его управления доступны следующие функции

    // Включение на определённый промежуток времени
    void RelaySetTimeOn (uint32_t time, sRelay* relay);

    // Задание периодического включения переключателя
    void RelaySetPeriodOn (uint32_t time, uint32_t period, sRelay* relay);
        
    // Включение переключателя
    void RelayOn (sRelay* relay);

    // Выключение переключателя
    void RelayOff (sRelay* relay);

Все эти функции более полно описаны в source-файле. Там всё предельно понятно. И последний, но самый важный штрих — используем периодический вызов функции

    RelayPoll(&led1);

Благодаря этой функции наш переключатель будет осознавать себя во времени и делать что нужно и когда нужно.
По сути период вызова этой функции определит квант системного времени для данного переключателя.
Вот и всё. Благодаря этому модулю мы получаем неограниченное кол-во независимых переключателей которые работают параллельно друг другу и основной программе. К тому же есть ещё такая фишка: запускать периодическое включение можно сколько угодно раз, при этом счёт времени не начинается с начала. То есть не нужно привязываться к какому-либо событию, а можно привязаться к какому-либо состоянию. Это делает код наглядней и избавляет от лишних переменных.

Пример
В завершение статьи коротко опишу, что делает пример идущий вместе с модулем (main.c в папке SysTick).
Его функционал заключается в следующем: изначально LED1 зажигается на 5 секунд, затем гаснет навсегда. LED2 зажигается на 200 мс каждые 500 мс. LED3 зажигается на 400 мс каждые 2 секунды. LED4 зажигается на 50 мс каждые 100 мс и отключается через 30 секунд работы.

В GPIO.c примера создаются 4 пина

 // пин лампочки 1
    sGpio gpioLed1 = {B, 7, HIGH, OUT_PP};

    // пин лампочки 2
    sGpio gpioLed2 = {B, 6, HIGH, OUT_PP};
    
    // пин лампочки 3
    sGpio gpioLed3 = {B, 5, HIGH, OUT_PP};
    
    // пин лампочки 4
    sGpio gpioLed4 = {D, 2, HIGH, OUT_PP};


а в Relay.c — 4 переключателя управляющие светодиодами через эти пины подключеные к аноду светодиодов (поэтому onLevel = 1).

 extern sGpio    gpioLed1,
                 gpioLed2,
                 gpioLed3,
                 gpioLed4;
        
    // светодиод 1
    sRelay led1 =
    {
        .gpio       = &gpioLed1,  
        .onLevel    = 1,
    };

    // светодиод 2
    sRelay led2 =
    {
        .gpio       = &gpioLed2, 
        .onLevel    = 1,
    };
    
    // светодиод 3
    sRelay led3 =
    {
        .gpio       = &gpioLed3,  
        .onLevel    = 1,
    };
    
    // светодиод 4
    sRelay led4 =
    {
        .gpio       = &gpioLed4,  
        .onLevel    = 1,
    };


И код самого мэйна

#include "SysTick\SysTick.h"
#include "Relay\Relay.h"

//==============================================================================
//                          Глобальные переменные
//==============================================================================

    // внешне определенные переменные
    extern uint32_t globalTimer;
    extern sRelay   led1, 
                    led2,
                    led3,
                    led4;
    
    // таймер обработки светодиодов
    uint32_t ledsPollTimer;
    
//==============================================================================
//                         Основная программа
//==============================================================================

int main (void)
{   
    // инициализация глобального таймера
    SysTickInit(1000, 0);
    
    // инициализация светодиодов
    RelayInit(&led1);
    RelayInit(&led2);   
    RelayInit(&led3);
    RelayInit(&led4);
    
    // установка режима работы для светодиодов
    RelaySetTimeOn(5000, &led1);
    RelaySetPeriodOn(200, 500, &led2);
    RelaySetPeriodOn(400, 2000, &led3);
    RelaySetPeriodOn(50, 100, &led4);
    
    while(1) 
    {
        // периодическая обработка светодиодов
        if(globalTimer - ledsPollTimer >= 5)
        {
            RelayPoll(&led1);
            RelayPoll(&led2);
            RelayPoll(&led3);
            RelayPoll(&led4);
        }        
        
        // по истечении 30 секунд выключение светодиода 4
        if(globalTimer >= 30000)
            RelayOff(&led4);
    }
}

В котором сначала инициализируется глобальный таймер считающий миллисекунды.
Затем инициализируются переключатели.
Потом устанавливаются режимы работы светодиодов.
И запускается суперцикл в котором программа периодически поглядывает как там светодиоды.
А через 30 секунд светодиод 4 потухает «навсегда».

На этом всё. Прошу не судить строго. Изложил как смог. Основной материал собственно в самом коде, а не здесь. Приветствуются вопросы, предложения и прочие комментарии.
П.С. Также выкладываю проект SysTick project под ИАР с примером.

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

RSS свернуть / развернуть
            RelayPoll(&led1);
            RelayPoll(&led2);
            RelayPoll(&led3);
            RelayPoll(&led4);
Почему бы не вести список реле и не опрашивать все релейки одним вызовом? Если не хочется вести список в библиотеке (скажем, из-за нежелания юзать там динамическую память) — можно передавать все релейки в один вызов массивом или через variadic функцию.

Алсо, или в примере ошибка — ledsPollTimer никогда не обновляется и не инициализируется — или он неявно обновляется где-то в другом месте.
0
  • avatar
  • Vga
  • 13 мая 2016, 18:39
По поводу таймера вы абсолютно правы. Прошляпил. Исправлю.
А по поводу опроса релеек. Вы правильно предположили — использовать динамическое выделение нет желания. От variadic мы вряд ли выйграем по времени. Остаётся массив. Тут 2 варианта: либо сразу создавать массив релеек, либо иметь массив указателей на релейки. Первый вариант мне не нравится тем, что исчезают названия конкретных релеек. То есть доступ просто по ничего не значащему индексу. Можно конечно задефайнить эти индексы с названиями в хидере модуля, либо где выше уровнем. Но это уже снова новые данные. А я стремлюсь к лаконичности и удобству. И во-вторых, глупый редактор ИАРа не осознаёт дефайны и не выдаёт их как варианты автодополнения кода. И последний вариант — использовать массив указателей на релейки. Тут мы либо передаём в функцию Poll вторым операндом кол-во релеек, либо последний элемент массива должен быть нулевым, либо сам модуль должен знать сколько у нас релеек крутится. Если честно ни один из этих трёх вариантов мне не пришёлся по душе. Да и релеек этих у меня в устройствах больше 5 не встречалось. И я их обрабатывал далеко не каждые 5 мс. скорее 50. Потому решил реализовать именно так. Но ваше предложение очень даже уместно и имеет полное право на жизнь.
0
В вариадик мы выиграем по отсутствию копипаста. По времени — это зависит от того, сколько там на вызов расходы и на обход арглиста.
Массив конечно указателей. Можно еще релейки в linked list собирать. В этом случае, кстати, если мейнтейнить линки в библиотеке, можно вести автоматический учет релеек без использования динамической памяти и не хардкодя их число.
0
Ни когда не понимал таких костылей…

П.С. Не удержался ;)
+5
  • avatar
  • ZiB
  • 13 мая 2016, 19:07
Честно говоря, фраза «накатывается поверх SPL» меня убила. Особенно в сочетании с «разочаровался этим непродуманным продуктом». Напрашивается вывод, что автор считает SPL продуманым…
0
Эм, я, например, тоже считаю SPL вполне себе продуманным. Не абсолютно во всех местах, но всё же, использовать вполне можно.
0
SPL это не HAL и даже не библиотека, поскольку отсутствует, собственно, абстрагирование. Более того — он добавляет совершенно бесполезный код и требует тщательно следить за инициализацией стректур, иначе можно огрести тредно уловимые ошибки. Тут не то что продумыванием не пахнет, это скорее похоже на издевательство. Если интересно посмотреть на продуманную либу — посмотрите github.com/andysworkshop/stm32plus.
0
Насчет «не библиотека» — это ты уже загнул.
0
Любая библиотека нужна с одной единственной целью — повысить уровень абстракции. Тут ничего подобного нет и близко.
0
Согласно определению, библиотека — сборник подпрограмм или аналогичных единиц кода, используемых для разработки ПО. Ему SPL вполне отвечает. Можно, конечно, считать, что не повышающая уровень абстракции библиотека бесполезна, но библиотекой она от того быть не перестает.
0
По этому определению сборник примеров из учебника тоже библиотека. И, уж если пытаться дать точное определение того, чем на самом деле является SPL, то это типичный адаптер, который меняет интерфейс не меняя функциональности. Такие адаптеры, как правило, нужны, что бы состыковать два куска кода писаные разными людьми (группами) независимо друг от друга. Учитывая, что код написанный с использованием прямой манипуляции регистрами зачастую проще, чем громоздкий код на SPL, смысл такого адаптера от меня ускользает.
Вобщем, можно сколько угодно играться в определения, ценность SPL от этого не повысится. Что же касается абстракции, то это ключевой параметр любой библиотеки или фреймворка. Они для этого создаются. Это как, скажем, микросхемы, которые, по сути, тоже еще один уровень абстракции — вместо отдельных транзисторов разработчик оперирует целыми функциональными блоками. С этой точки зрения SPL мне напоминает переходник из SO-8 в DB-9. Наверное в каких-то экзотических случаях такое и может понадобиться, но ставить его везде, где используется SO-8 смысла накакого.
0
По этому определению сборник примеров из учебника тоже библиотека.
Нууу… Обычно библиотекой понимается то, что можно влинковать. Хотя в те времена, когда слово появилось — возможно библиотека вообще в тетрадке велась.
это типичный адаптер, который меняет интерфейс не меняя функциональности.
Это да. Как и то, что мало что она меняет. Хотя, ИМХО, инит на ней все же чуть наглядней прямой работы с регистрами.
0
Хорошо повышает уровень абстракций libopencm3 github.com/libopencm3/libopencm3 — вплоть до того, что почти HAL, т.е. не то что бы совсем можно один код компилировать и под STM32 и под Stellaris или как там в очередной раз TI свои армы переименовал, но близко к тому.
0
Читаем SysTick.c
1) Сначала
#define SYSTICK_PRIORITY    0x01
Потом
NVIC_SetPriority(SysTick_IRQn, 0x1);
То есть плевать мы хотели.
2)
if(SysTick_Config(SystemCoreClock / frequence))    
        while (1);
Тупо зависаем, навечно.
3)
uint32_t globalTimer;
доступен в main.c не только на чтение, но и на запись. То есть возможны различные случайные вмешательства со стороны пользователя библиотеки. Хотите свой счётчик тиков — спрячьте его за методом чтения. Почему я, используя библиотеку должен писать вот такие вещи
extern uint32_t globalTimer;
?
4) Вот это непростительно:
while(globalTimer - timer <= delay);
Когда ваш globalTimer переходит через «переполнение» во время выполнения вот этого, выход из этого цикла никогда не наступит. Разберём для случая, когда globalTimer, timer и delay имеют тип uint8_t, чтобы не ворочать большими числами, пусть globalTimer = 254, timer = 250, а delay = 6. Чтобы наступил выход из цикла globalTimer = timer+delay+1 = 257, а этого никогда не будет.
Я дальше код не читал.
0
Кстати, про FreeRTOS слышали? Он решает эти и другие задачи.
0
1) Да, здесь вы правы. Копипаста, она такая. Сейчас поправлю.
2) Если у вас не запустилось системное время, при том что оно вам нужно раз вы его запускаете. То думаю не стоит программе продолжать работать.
3) Можно сделать как вы предлагаете. Можно всё позащищать от дураков. Везде только геты и сеты. Ни одной глобальной переменной. Только дурак может и вовсе начать менять память по адресу. Библиотека расчитана на адекватного человека, который понимает что делает в коде.
4) Непростительно не знать банальной 16-ричной арифметики. Никогда не говорите никогда )))
0
2) С натяжкой согласен, но могли бы и значение возвращать. Та функция, которую вы используете так и делает.
3) часто бывает, что случайно, вместо "==" по невнимательности пишут "=". Статический анализ кода выдаст предупреждение, но можно пропустить.
4) Поясните этот момент подробно. При чём тут 16ричная арифметика?
0
2) Хорошо. Я могу возвращать значение. тогда просто этот бесконечный while переедет в мэйн. Что нам это даст?
3) Думаю в этом я с вами соглашусь и сделаю гет. Просто я ни когда не использую с этой переменной ==, использую только <= или >=.
4) поясняю:
254 — 250 = 4
255 — 250 = 5
0 — 250 = 6
1 — 250 = 7 и т.д.
0
2) В main уже пользователь должен будет обрабатывать проблему. Как — его задача.
4) Вроде согласен.
0
3) Думаю в этом я с вами соглашусь и сделаю гет. Просто я ни когда не использую с этой переменной ==, использую только <= или >=.
Если пишешь библиотеку не только для себя — приходится учитывать, что другие могут сделать иначе. К тому же, другие не будут знать, как библиотека работает — это тоже приходится учитывать.
P.S.
Непростительно не знать банальной 16-ричной арифметики
Это не 16-ричная арифметика, а модульная.
+1
Только дурак может и вовсе начать менять память по адресу. Библиотека расчитана на адекватного человека, который понимает что делает в коде.
20% мер решают 80% проблем. Так что традиционное разграничение доступа сделать следует. Просто потому, что если торчит наружу переменная, то не скурив весь код и не узнаешь, что запись туда все сломает. Особенно если по соседству висит переменная, в которую юзеру положено писать. Инлайновый геттер отдновременно спрячет переменную и не добавит оверхеда.
+1
А уж что будет с этим циклом, если переменная globalTimer не будет volatile…
+2
С каким циклом?
0
while(globalTimer — timer <= delay);
0
Ну поведайте что же с ним будет. Мне очень интересно.
0
Ээээ. Вы никогда не видели, как такой цикл (когда переменная не volatile) компилятор легко оптимизирует в if + вечный цикл? И имеет право, гад!
Это же один из самых частых вопросов от начинающих программировать на C На микроконтроллерах.
+1
цикл то не вечный. Так что не имеет.
0
Вечный, вечный. Без volatile компилятор имеет право смотреть на этот код в вакууме. В этом коде нет ни одного изменения участвующих переменных. Всё, оптимизация — проверить условие один раз, если не выполнилось — зациклится, потому что ничего в этом цикле не может повлиять на условие. Это правильная и годная оптимизация.

Это такая основа оптимизирующих компиляторов и семантики языка C, что я даже не знаю, вы правда этого никогда не встречали?
+1
«проверить условие один раз, если не выполнилось» — если ВЫПОЛНИЛОСЬ, конечно.
0
Честное благородное. Ни разу мне компилятор такой свиньи не подкладывал. Спасибо за наводку. Завтра поэкспериментирую с этим.
0
В общем, всё, что может поменяться в обработчике прерывания (или другом потоке, если у нас многопоточная среда, что, в общем, одно и то же) надо объявлять volatile, и тогда компилятор будет обязан делать честное обращение к памяти всегда без оптимизаций и каких-либо предположений.
+1
Вот простейший пример. Вот код


#include <stdint.h>

extern uint32_t globalClock;

void delay(uint32_t delay) {
	uint32_t waitStart = globalClock;
	while (globalClock - waitStart <= delay)
		;
}


Компилируем официальным свежим GCC 5.3.0 для arm-embedded с -O3. Получаем вот такой ассемблер:


	.cpu arm7tdmi
	.fpu softvfp
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.arm
	.syntax divided
	.file	"opt.c"
	.text
	.align	2
	.global	delay
	.type	delay, %function
delay:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
.L2:
	b	.L2
	.size	delay, .-delay
	.ident	"GCC: (GNU Tools for ARM Embedded Processors) 5.3.1 20160307 (release) [ARM/embedded-5-branch revision 234589]"


Так как арифметика беззнаковая, то даже первичная проверка убрана, потому что компилятор видит, что результат вычитания всегда 0, а ноль всегда меньше или равен любому беззнаковому числу.

Привет!

Добавление volatile к extern меняет картину на адекватную:


	.cpu arm7tdmi
	.fpu softvfp
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.arm
	.syntax divided
	.file	"opt.c"
	.text
	.align	2
	.global	delay
	.type	delay, %function
delay:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	ldr	r2, .L5
	ldr	r1, [r2]
.L2:
	ldr	r3, [r2]
	rsb	r3, r1, r3
	cmp	r3, r0
	bls	.L2
	bx	lr
.L6:
	.align	2
.L5:
	.word	globalClock
	.size	delay, .-delay
	.ident	"GCC: (GNU Tools for ARM Embedded Processors) 5.3.1 20160307 (release) [ARM/embedded-5-branch revision 234589]"
+1
Вот такой код (который мне нравится больше), кстати, оставляет первичную проверку, тут у компилятора не хватает уже мозгов отказаться от неё, но всё равно потом — вечный цикл (без volatile):


#include <stdint.h>

extern uint32_t globalClock;

void delay(uint32_t delay) {
	uint32_t waitEnd = globalClock + delay;
	while (globalClock <= waitEnd)
		;
}



	.cpu arm7tdmi
	.fpu softvfp
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.arm
	.syntax divided
	.file	"opt.c"
	.text
	.align	2
	.global	delay
	.type	delay, %function
delay:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	ldr	r3, .L6
	ldr	r3, [r3]
	cmn	r3, r0
	bxcs	lr
.L4:
	b	.L4
.L7:
	.align	2
.L6:
	.word	globalClock
	.size	delay, .-delay
	.ident	"GCC: (GNU Tools for ARM Embedded Processors) 5.3.1 20160307 (release) [ARM/embedded-5-branch revision 234589]"


А вот это же с volatile, короче вашего варианта с volatile, так как из цикла ушло вычитание


	.cpu arm7tdmi
	.fpu softvfp
	.eabi_attribute 20, 1
	.eabi_attribute 21, 1
	.eabi_attribute 23, 3
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.arm
	.syntax divided
	.file	"opt.c"
	.text
	.align	2
	.global	delay
	.type	delay, %function
delay:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	ldr	r2, .L5
	ldr	r3, [r2]
	add	r0, r3, r0
.L2:
	ldr	r3, [r2]
	cmp	r0, r3
	bcs	.L2
	bx	lr
.L6:
	.align	2
.L5:
	.word	globalClock
	.size	delay, .-delay
	.ident	"GCC: (GNU Tools for ARM Embedded Processors) 5.3.1 20160307 (release) [ARM/embedded-5-branch revision 234589]"


Хотя важно ли это при цикле задержки — я не уверен. Я бы вообще сюда WFI воткнул, на самом деле. Раньше чем случится прерывание (таймера) мы в любом случае не выйдем из цикла, так что электричество жечь? Можно и поспать.
+1
.fpu softvfp
Эта директива заставила задуматься — а как плавучка на таких армах реализуется? Как на AVR — непосредственной подстановкой вызовов soft-float функций, или действительно софтовым VFP, вешающимся на соответствующее исключение?
0
Ну так эксперимент же легко ставится.

	stmfd	sp!, {r4, lr}
	bl	__aeabi_fadd
	ldmfd	sp!, {r4, lr}
	bx	lr

Это сложение по-дефолту, флоаты. Судя по префиксу AEABI это не фантазия gcc, а стандарт.


	vadd.f32	s0, s0, s1
	bx	lr


Это cortex-m4, vfpv5-d16, hard-float. Т.е. это M4E. Или он с неоном? Ну, в общем, надо смотреть что точно соответствует M4E.
0
M4 это -mfpu=fpv4-sp-d16, M7 — fpv5-sp-d16
0
Ха-ха. Вот тут-то с плюсом я и облажался. Тут если текущий таймер + заджержка переполнятся, то нифига работать не будет — сразу текущий таймер окажется больше конца ожидания и всё.
0
Чтобы наступил выход из цикла globalTimer = timer+delay+1 = 257, а этого никогда не будет.
250+6+1=2, все нормально работать будет.
0
Вернее, 250+6+1=1.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.