Pull to refresh
4
0
Send message

Разве Segger виноват в том, что программист задает нулевой размер кучи а потом создает экземпляр класса при помощи new, причем там, где это совершенно не нужно? Классический ленивый Singleton может так выглядеть:


class Singleton
{
public:
    static Singleton& instance()
    {
        static Singleton inst;
        return inst;
    }
    ...
};

Практически у всех вижу подобный Toggle(), но все же лучше читать ODR и писать в BSRR, как для Set() и Reset(), это медленнее, но безопасно. Еще хуже когда, как у автора статьи, старое значение бита читают из IDR. Например, пин в режиме OpenDrain без подтяжки, тогда из IDR можно прочесть что угодно, а если подтяжка есть, то правильное значение будет читаться до достижения определенных скоростей и два Toggle() подряд для мк работающего на 100+ MHz не гарантирует возврата к первоначальному состоянию.

Понятно, так действительно работает, но вообще странно, что нужно дополнительно указывать, что хочешь использовать основную стандартную библиотеку, ведь STLport теперь легаси. А RTT у меня класс в одном файле и копируется он в новый проект вместе с десятками других подобных файлов, одним каталогом, так что сишная либа RTT в С++ проекте еще и со своим отдельным форматированием - это сомнительное преимущество. И лично меня раздражают стартапы на ассме, зачем тащить целых три ассм файла в проекты на ARM, когда на C/C++ можно сделать то же самое, но гораздо проще...

На wiki написано, что C++ library добавила хедеры для exception, new, typeinfo, плюс обертки типа cmath для math.h. Я даже не поленился и установил SES, скачал ARM libcxx library и действительно хедеры для exception, new и typeinfo видит, а array, functional, type_traits и другие уже нет. И это типа прогресс, потому что STLport который использовался до этого был еще хуже. Ну и сама IDE похоже не особо поменялась, даже чтобы переименовать main.c в main.cpp мне пришлось удалять его из проекта, переименовывать вручную и добавлять заново...

Не знаю как сейчас, но последний раз я ковырялся в SES 2 года назад и тогда там даже банального std::array и трейтов из С++11 не было, более того разрабы на форуме писали, что все эти stl отнюдь не приоритетное направление... В то же время до конца года должен выйти gcc 11 для ARM, а бетка gcc 10 с неплохой поддержкой С++20 еще в начале лета прошлого года была.

Таблицы синусов генерятся элементарно, любого размера и точности, примерно как и круг в данной статье рисуется, т.е. будет класс с constexpr конструктором внутри которого массив и из конструктора вызываются constexpr методы заполняющие этот массив, который в итоге оказывается во флеше. Генерация битмапов из jpeg - это все же не совсем то, тут нужны достаточно сложные утилиты которые обычно уже существуют и писать их аналоги на С++ не имеет смысла, хотя легко можно представить, допустим, шрифт в виде массива в неком промежуточном формате, при этом можно указать компилятору, что хочу этот шрифт увеличенный вдвое, повернутый на 90гр., с обрезанными пустыми полями, кодировка чтоб была КОИ-8 и вот строка с текстом, найди в ней все уникальные символы и добавляй только их. Не так и сложно такое написать, явно проще компилятора. Но таблица синусов более показательный пример, потому что обычно генерятся разного рода таблицы и тогда проще написать класс на С++ который будет частью проекта, чем написать его на другом языке и таскать вместе с проектом.

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

У меня подход с генерацией или конвертацией имеющихся данных на этапе компиляции используется во множестве мест, от форматирования текста до компиляции программ на ассме для модулей PIO входящих в состав Raspberry Pi Pico. Более удобная генерация при помощи внешних программ - это про какие-то исключительные случаи, лично я избавился от всех своих скриптов на C# которыми что-то генерил раньше.

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

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

Выбирая одну дверь из трех вероятность угадать дверь с призом равна 1/3. Если выбрана дверь с призом, то после смены получим дверь без приза. Если в двух оставшихся случаях выбрана одна из дверей без приза, то она поменяется на дверь с призом, что дает уже вероятность равную 2/3.

Никто в здравом уме не станет для региона памяти в котором расположены регистры периферии менять тип памяти с Device на Normal и т.д., иначе вообще ничего работать не будет, потому рассуждать о подобным гипотетических ситуациях не имеет смысла.

И я говорил не про один и тот же пин порта, а про разные пины одного порта, один можно тоглить в главном цикле, а второй в прерывании и будет глючить. Даже HAL_GPIO_TogglePin() раньше работал чисто с ODR, а теперь тоже переделали через BSRR.

А реализация от чего зависит? Мы же с регистрами работаем, тип памяти у нас Device и никакого кеширования, строгий порядок записи и чтения гарантируется уже только этим. Есть же периферия которая требует установки одного бита, а следом другого в том же регистре. Не вместе и не в обратном порядке и все это работает без никаких барьеров. А чтение из IDR вообще зависит от многих факторов, если есть быстрый мк, но медленные порты или задан не самый подходящий режим, типа OpenDrain с подтяжкой, то после записи в ODR придется вставлять задержку просто потому, что уровень сигнала на выходе нарастает слишком медленно и одного DSB может не хватить. Toggle() довольно часто пытаются так делать, читают IDR, инвертируют биты и пишут в ODR... Твой invert(), кстати, тоже проблемный, т.к. не атомарный, второй invert() над тем же портом в прерывании может все поломать.

Volatile дает гарантии строгого порядка для volatile объектов, т.е. если дважды что-то записать в BSRR, то генерируемые инструкции будут идти в том же порядке, хотя между ними компилятор может вставить обращение к не volatile объектам. А если у нас есть две инструкции записи в порт, то никакого реордеринга не будет, кортексы так просто не умеют, уж точно не для портов висящих на одной шине, потому DSB тут не нужна, она только тормозов добавит, а у F1 и так порты медленные.

I2C работает в режиме Stop даже на простых сериях типа F0/L0/G0. Не работает в Standby, но L0 в Stop потребляет меньше 1uA. У современного 8-ми ногого G0 целых 9 таймеров, у трех из них 4-х канальный ШИМ, один таймер может работать на удвоенной частоте(128 Mhz) и гонится вместе с мк более чем в 2 раза, сложно представить ситуацию когда бы для STM32 пришлось делать ШИМ ногодрыгом… Но при желании можно, причем на некоторых сериях быстрее даже в тактах, не считая намного большие частоты, а на любых кроме M0+ можно выводить в порты при помощи DMA, хоть 16 бит сразу и также начиная от 4-х тактов. И для WS2812 подходит, хотя обычно используют DMA + ШИМ, при этом мк 98% времени будет простаивать, а типичный подход для AVR — это блокирующая передача при помощи SPI. Опять же когда много RAM, то и подключать SPI SRAM нет никакого смысла, я разве что 8МБ PSRAM подключал к QUADSPI, причем в новых сериях все разруливается автоматически и к внешней RAM можно обращаться как ко встроенной. На ассме тоже пишу в самом крайнем случае, начиная со стартапа все на C++ и тут AVR также подкачал, потому как многие хедеры для современного C++ в GCC просто отсутствуют. В итоге практически единственное преимущество AVR — это простота :)

Тонкая Errata у древних AVR, возьми какую-нибудь новую тиньку, типа tiny212 и там для последней ревизии чипов в Errata 21 пункт. Для сравнения у таких же новых STM32G0 — 33, у STM32H750 — 45. А если у этого H750 не использовать Ethernet/CEC/SDMMC/FDCAN/QUADSPI, которых у AVR все равно нет, то остается 23...

У Blue Pill одно ядро работающее на вдвое меньшей частоте и у нее в 13 раз меньше RAM. Само ядро то мощнее, но в Pico добавили аппаратное деление, семафоры и т.д., плюс PIO работает параллельно процу, там десяток команд, по сути пишутся микропрограммы на ассме.

Передаю в качестве шаблонного параметра GPIOx_BASE, внутри класса получаю его как Gpio, далее использую base() и при -O0 никакого вызова функции не будет, потому что за inline(с подчерками) скрывается always_inline O2. Более того, можно взять не пин, а значительно более сложный список пинов, тогда write() будет выглядеть так:


static void _inline_ write(uint32_t value)
{
    ports_.foreach([=](auto port) __inline__ { GpioT<unbox<port>::gpio, 
       getPinsMask<unbox<port>::gpio>(pins_)>::write(readWrite<PinShift::Write>(value, 
       getIndexedPins<unbox<port>::gpio>(pins_))); });
}

Тут видно 7 вызовов функций, base() — 8-я, она вызывается в самом конце, а readWrite() — это достаточно тяжелая функция вызывающая еще ряд других. Смотрим что будет для простого списка пинов с -O0:


PinList<PC4, PC3, PC2> pins;
pins.write(5);

И получим 8 инcтрукций, никаких вызовов функций здесь нет:


movs r3, #5 
str.w r3, [r7, #404]
ldr.w r3, [r7, #404]
lsls r3, r3, #2 
and.w r3, r3, #28 
orr.w r3, r3, #1835008
ldr r2, [pc, #164]
str r3, [r2, #24]

А вот так всего 3 инструкции:


pins.write<5>();

// ldr r3, [pc, #168]
// ldr r2, [pc, #184]
// str r2, [r3, #24]

Первый вариант с оптимизацией — две инструкции :)

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

Есть у меня класс таймеров, один на все серии STM32, если я, допустим, хочу разрешить какое-то прерывание, то делаю так:


tim.enableInterrupts<TimInt::Break>();

При этом на стадии компиляции проверяется есть ли у данного таймера этот Break и проверяется именно при помощи концептов, потому что если использовать static_assert(), то получим ошибку внутри enableInterrupts() и не понятно что именно является ее источником, а с концептом будет ошибка в месте вызова.

Information

Rating
Does not participate
Registered
Activity