Как стать автором
Поиск
Написать публикацию
Обновить
4
0

Пользователь

Отправить сообщение

Inline непосредственно для инлайнинга уже давно практически бесполезен, нужно использовать всякие attribute((always_inline)), но по сути все верно.

Зачем тут constexpr? В стандартных хедерах полно таких структур, для примера можно все переписать таким образом:


struct GPIO_TypeDef
{
    __IO uint32_t MODER;
    __IO uint32_t OTYPER;
    __IO uint32_t OSPEEDR;
.....
};

static constexpr uint32_t PERIPH_BASE = 0x40000000;
static constexpr uint32_t D3_AHB1PERIPH_BASE = PERIPH_BASE + 0x18020000;
static constexpr uint32_t GPIOD_BASE = D3_AHB1PERIPH_BASE + 0x0C00;
static const auto GPIOD = (GPIO_TypeDef*)GPIOD_BASE;

Если теперь написать GPIOD->ODR = 123, то даже для -O0 получим три инструкции, меньше просто некуда...

Можно подробнее? Помнится в gcc 6 или 7 я в либе портов передавал в качестве шаблонного параметра (int)GPIOA и т.д., а потом уже пришлось передавать GPIOA_BASE, который не volatile. Соответственно внутри класса сейчас так:


static auto _inline_ base() { return (GPIO_TypeDef*)Gpio; }
static void _inline_ write(bool data) { base()->BSRR = (0x10000 | data) << pin; }

А раньше вместо base() можно было использовать constexpr константу, но по сути изменения минимальны, никаких макросов и непонятных сообщений об ошибках, на генерации кода это также никак не отразилось. Больше ничего на эту тему и не вспомню, при этом C++20 у меня задействуется по-полной...

В дизассемблер лезть не придется, получим или массив во флеше или программа не скомпилируется(для чего она должна быть написана с ошибками), а все потому, что от C++ можно явно это потребовать. Не случайно же настолько впечатляющий результат получается даже при отключенной оптимизации, когда один sort() занял бы в рантайме 3КБ флеша.
Добавили для простого массива const и он пойдет по флеш, добавили для класса constexpr/consteval и компилятор будет обязан его разместить там же, но константным класс стал только после создания, за которое отвечает конструктор, а в нем можно делать практически что угодно и совершенно бесплатно. Многие пишущие на C++ удивятся просто потому, что не знают о таких возможностях языка, на ПК они, например, не особо востребованы. Что уж говорить про тех, кто на С++ не пишет...

Приведу достаточно продвинутый пример использования современного C++. Допустим есть класс для работы с LTDC и в функцию инициализации одной строкой передаются требуемые пины, пусть это будут 14 пинов для RGB444. Эта функция пробрасывает пины в более общую initRgb666(), а в качестве недостающих передаются PinDummy<>. Далее все пины передаются в функцию которая добавляет им правильные AF одновременно проверяя допустимость использования конкретных ног и если что выдается сообщение об ошибке с именами ошибочных пинов. И пины одним списком возвращаются обратно, уже с AF, затем этот список вместе с режимом, а можно использовать и список разных режимов, передается функции инициализации пинов из класса портов. Там к пинам подмешиваются режимы, удаляются PinDummy<> и вызывается конструктор очередного класса который сначала распаковывает все пины в обычный массив структур, затем сортирует его по трем параметрам при помощи std::ranges::sort() и далее идет код примерно на страницу который в цикле берет отсортированные данные и этого массива и после некоторых преобразований сохраняет их в другом массиве. Наконец есть еще небольшой класс который забирает массив у предыдущего и копирует, в такой-же массив, но меньшего размера. В самом конце вызывается функция, которая получает на вход массив с информацией о пинах/AF/режимах и выполняет их инициализацию. Итак, что же получится на выходе, если на входе было:


ltdc.initRgb444<PC6, PA4, PE15, PB1, PC0, PA11, PD3, PC7, PB11, PB10, PB9, PB8, PA3, PA10>();

Думаю даже многие из тех кто давно пишет на C++ удивились бы узнав, что получим мы следующее, даже при отключенной оптимизации:


ldr r0, [pc, #668]
bl 0x24000454

В R0 грузится адрес массива и этот массив расположен во флеше(!), а размер его 29 байт. Насколько это мало должно быть понятно из того, что 14 переданных пинов были с 5-ти портов с тремя разными AF. Фактически, поскольку в С++20 можно на этапе компиляции динамически выделять память, использовать виртуальные функции или std::vector, это все тоже могло там быть с нулевым оверхедом в рантайме. Использую С или ассм и близко ничего похожего не получишь, пора уже смириться с тем, что при правильном подходе С++ — это самый эффективный язык для эмбедда :)

Пины, даже если их треть от общего числа используются, с большой вероятностью будут со всех портов, к тому же по моему примеру видно, что используются два SPI и FMC, но и дисплеи и тач задействуют еще вспомогательные пины, которые также нужно учитывать. В итоге проще не заморачиваться и включить тактирование портов самому… Аналогично если мк усыпляли и периферию отключали, то затем помимо включения периферии придется для нее вызывать init(), который может включать ее сам… Кода генерится будет немного больше, но так проще.

Да, только теперь у пользователя есть список с номерами портов(возможно ошибочный) из которого непонятно какие это пины, потому ему придется добавить соответствующий комментарий(возможно ошибочный), а сами пины инитить в другом месте, а тут можно перепутать как сами пины, так и режимы с AF. Я пошел другим путем, у меня можно сделать так:


using spi2 = Spi2<PA12, PC2, PC3, PB4>;
//using lcd = LcdSpi<ST7735, LcdOrient::LandscapeRev, spi2, PD10, PD9>;

using fmc = FmcBank1<8, PD7, PE6>;
using lcd = LcdFmc<RM68140, LcdOrient::LandscapeRev, fmc, PB13, 1, 1, 1, 3, 3, 3>;

using lcdDataPins = PinList<PE10, PE9, PE8, PE7, PD1, PD0, PD15, PD14>;
//using lcd = Lcd<ILI9481, LcdOrient::LandscapeRev, PB13, lcdDataPins, PD7, PE6, PD5, PD4, 0, 4, 0>;

using spi4 = Spi4<PE4, PE12, PE5, PE14>;
Touch<spi4, PB12, lcd> touch;

Тут пара SPI, в шаблон передаются именно пины, причем для каждого мк только из списка допустимых, если, допустим, SCK нельзя повесить на данный пин, то будет ошибка компиляции говорящая о том какой именно пин ошибочный, при этом для всех пинов автоматически добавляется номер AF. Также здесь есть объявление для трех разных дисплеев подключенных по SPI/FMC или GPIO(к пинам FMC). Помимо пинов для дисплеев также передаются тайминги, а инитятся все три простым вызовом init(), возможно с указанием скорости для SPI, после чего все дисплеи работают одинаково. Пины обрабатываются списками, сразу задаются режимы для всех пинов с одного порта, потому эффективность достаточно высокая Тактирование периферия включает для себя лично, тактирование для "расшаренных" портов или DMA включая я вот таким образом:


Periph::enableClock(Ahb4Periph::Gpios);
Periph::enableClock(Ahb1Periph::Dma1 | Ahb1Periph::Dma2);

Теперь представим что вместо половины моих пинов будут номера портов, а вся инициализация находится где-то еще, ее нужно написать, не ошибиться с пинами/AF/режимами и все это ради экономии самого дешевого ресурса — флеша. Для примеру беру реальный проект, далеко не самый простой, с -O2 размер бинарника 11496 байт, для -Os уже всего 8780, а у мк 512КБ флеша :)

Берем какой-нибудь G071RB, там всего два SPI, но суммарно они ремапяпся на 39 пинов 4-х портов, так что isRemap достаточно только для морально устаревших F1.

Лично меня напрягает, что включаешь SPI2 и UASRT1, а включается также часть портов и DMA… А если DMA не используется? Или если у мк есть DMAMUX и можно для любой периферии выбрать любой канал любого DMA, то написать SPI<2> уже будет недостаточно. А ремап пинов? Есть мк у которых ноги SPI ремапятся на все порты, следовательно чтобы определить эти порты нужно в класс SPI передать его номер, 4 пина и канал DMA… Передав пины логично уже там их и инитить, а это до 5-ти регистров на порт(за исключением F1), при этом не самая эффективная инициализация значительно перекроет оверхед приходящийся на лишнее включение бита периферии, если его делать по отдельности.

Ясно, если добавляются не спектрумовские цвета, то это нужно видеть, но если брать популярные алгоритмы типа hqx, то там даже при большем увеличении и большем количестве цветов все равно картинка получается мутноватая.

Принцип какой? Если просто удваивать один из пикселей, то получается жуть, а что еще можно сделать при таком количестве цветов?

Не будет оно нормально выглядеть, даже если на ПК делать...

Самый высокий приоритет не гарантирует равномерность. Допустим сработало прерывание, мк лезет в таблицу векторов, я так понимаю она во флеше, т.е. добавляются такты ожидания, затем то же самое происходит при переходе к самому обработчику, но у мк есть ART и он может все нужные данные закешировать, т.е. тайминги могут отличаться на весьма значительное количество тактов. Далее, если прерывание сработало сразу вслед за предыдущим, то может не потребоваться сохранять на стеке регистры, они там уже есть — это минус 6 тактов. Помимо этого есть атомарные операции, например, невыровненный доступ или bit banding, если DMA пытается читать из памяти, то проц может занять шину сразу на несколько тактов и тоже будет небольшая задержка. В идеале видеобуфер должен быть в другой SRAM к которой проц во время вывода не обращается, а у F401 она только одна. То же самое с портами, опрос единственной кнопки будет временами тормозить вывод данных в порт и т.д… Тут нужен или QSPI, или сразу брать мк с LTDC, типа относительно дешевого H750, на нем можно выделить буфер для 800x600 и еще на спектрум-512 RAM останется.

У меня $19.23 с доставкой. И для большинства эту плату нужно купить конкретно под эмулятор, в то время как в случае STM32 на них собирают эмулятор потому что такая плата уже есть, или есть похожая и другой STM32 тоже может быть как-то использован в иных целях.

Официальная XT была с 8-ми битной шиной и работала на смешных 4.77 MHz, а официальная AT — это 6-8 MHz. Ориентировочно такой комп можно эмулировать без тормозов и программ рассчитанных под такое железо явно больше, чем для спектрума. Если для нормальной работы нужно 20-30 MHz, значит это уже софт предназначенный для 386 и выше, про него речь не идет. На FPGA конечно будет быстрее, но доступнее ли?

Отдельно H750 стоит 4-5$, в действительности это H753 с двумя метрами флеша, из которого проверено только 128КБ, учитывая что там еще и два банка рабочий метр можно получить практически гарантированно. В простейшем случае его можно хоть на переходник впаять и собрать все на макетке… Насчем скорости QSPI не скажу, память такая есть, но не тестил еще. Однако по докам даже если обращаться к одному байту, то нужно передать команду, адрес в quad режиме, для чтения еще добавляется 6 тактов ожидания и потом 2 такта на данные, итого 16-22 такта при частоте 144MHz, т.е. 2 метра в секунду — это мало даже в таком самом медленном случае(хотя можно 24 бит адреса побитно передавать). В любом случае 22 такта — это в десятки раз меньше, чем затратит на обращение к внешней памяти 8086.

8086 — медленный проц с многотактовыми инструкциями, думаю на STM32H7, который гонится почти до 700МHz, умеет выполнять по две инструкции за такт, имеет кеш и CCM для выполнения кода, а также метр RAM которая позволяет отказаться от медленной SDRAM, должно получиться явно больше чем 8086 на 3-5 мегагерц. Аналогично с Atari ST, должно тянуть без проблем, там обвязка простая, в отличии от амиги.

Что у этого хорошего эмулятора со звуком? На видео в первой же игре (Cauldron II) вместо музыки непонятный набор звуков напоминающий оригинал весьма отдаленно.

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

Я проверял на STM32G0, а к нему старых хедеров не может быть в принципе, но и для всех остальных серий стоит UL, кроме древних хедеров от SPL:


#define  FLASH_ACR_LATENCY                   ((uint8_t)0x03) 

Там еще и многие регистры объявлены 16-ти битными, но это было давно, сейчас скачал для проверки ST-й пак STM32CubeF4 и там все в точности как у меня в VisualGDB, никаких 8-ми битных констант в хедерах от производителя уже нет много лет.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность