Как стать автором
Обновить

Комментарии 36

Плюс поставил, хоть и стена текста. Одно только покоробило: PE_RST_N и PERST# — выделенное сразу дает понять, что активный уровень этого сигнала — низкий, и это не недостаток качества документации, а недостаток знаний принятых в индустрии обозначений.
Конечно, стена, я же предупреждал о потоке сознания.
А насчет обозначения — я прекрасно знаю принятые методики, но в том то и дело, что они принятые, а не стандартизованые и обязательные и всегда помимо имени с префиксом N на конце прилагалась таблица истинности.
А особую пикантность придает то, что по мнению автором документации, эти два названия обозначают одну и ту же ножку, ну вот тут с Вами на соглашусь, что так принято — иногда опускать подчеркивания в наименовании, а иногда их писать, и в одном месте обозначать низкий активный уровень буквой N, а в другом — символом #.
Автор схемы, скорее всего, под PE_RST_N имел в виду вывод микроконтролера, т.к. именно так он назван в даташите на него. PERST# — имя сигнала (или сети) PCIe reset, к которому нога PE_RST_N подключена. Две разные сущности — два разных имени, ничего необычного.
я в SPL от STM'a (стиль: заполнил структуру и вызвал функцию)
делал существенно проще:

задача: сделать загрузчик 8..16к по GPRS из готовых наработок GPRS которые после компиляции занимают почти 30к.

проблема: SPL занимает чуть больше 50% кода.

причина проблемы: в SPL используются функции и они работают со всеми видами переферии, т.е. внутри куча switch-case для всех случаев жизни которые в моём проэкте не используются

решение: все используемые функции у SPL копируем в инклудники и подписываем к ним static inline, и добавив к имени функций суффикс _inline, благо они используются почти все только один раз. И чуток дорабатываю свой код добавив _inline суффикс

результат: компилятор собирает проект аж целых 10 секунд, но зато использует только актуальные куски кода и не шараборится с структурами.

Итог: код стал 17к, после я ещё помелочи не куроча логики работы убрал лишнее и свистелки и перделки типа подробных логов с выводом таймингов (а так же всякие анархизмы типа impure_data and ..._ptr) и код стал 11к. При этом вся логика не изменила своего вида за исключением суффикса SPL функций.

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

но оно того стоит?
по-моему на собственно саму логику проекта уходит 90% всех сил
и нужно быть просто внимательным и аккуратным.
Судьба SPL весьма туманна после появления HAL. Генератор кода STM32CubeMX тоже весьма приятная штучка, заметно облегчает порог входа. А сгенерированный код можно потом допиливать путем удаления лишнего.
Это микроконтроллеры — а значит кристаллы не изменятся и никто не заставляет переходить на хал.
Вот новые типа М7 и выше, да проблема.
Возможно в HAL тоже можно применить ваш способ облегчения кода (если без HAL не обойтись). Там, правда, универсализация кода пошла еще дальше, в сравнении с SPL.
не думаю что можно.
SPL хорош тем что он на входе имел структуру и немного параметров, а на выходе изменял состояние переферии и почти не содержал глобальных переменных, глобально статических и тд, а так же не занимался обработкой прерываний.
SPL по сути набор макросов близкий к идеальной функции без сайд-эффектов. Некий перекидыватель данных стркутур в регистры переферии.
Одна функция — одно действие над одним блоком, нет такого чтоб можно было активировать одновременно UART and DMA.

HAL же пошёл дальше — он работает более высокоуровнево: в нём есть например функция которая настраивает и уарт и дма, их связывает и в связке посылает данные обрабатывая прерываний. Я бы не рискнул ТАКОЕ инлайнить — явно там есть и обработчик прерывания, и внутренние состояния и циклы ожидания и прочие «прелести».

В добавок у HAL есть большие проблемы с поддержкой errata от той же СТМ — он её игнорирует в многих вопросах, при этом все действия скрыты в нём и вмешаться в них сложно не залазя в код хала. Что очень жирный минус.

Вот например я сейчас мучаюсь с USB CDC. Добился скорости выше 400кбайт/сек для приёма и передачи, но не одновременно. При одновременной работе всё рушится — просто железяка перестаёт разом давать прерывания и на приём и на передачу войдя в аппаратный дедлок. А править кубовый усб это АД из сплошных нарушений всех правил данной статьи (чего стоит многократно вложенные указатели на фукнции которые возвращают структуры с указателями на функции). А причина всех бед наверное в этом пункте ерраты: http://i.prntscr.com/cac94bb65ca642caab59656105118e82.png. Так же у хала прочие пункты ерраты не учтены, например проблемы с каналом А0 ацп в сотой серии.
А править кубовый усб это АД из сплошных нарушений...

— это точно, пробовал завести USB HID на CubeMX — заработать вроде заработал, но с глюками. А код такой, что лучше и не видеть. В итоге взял кусок из прежних наработок.
К генератору кода у меня сложное отношение — вроде улучшает удобство, но при этом нужно с проектом тащить еще и некий файл с описанием параметров генерации; формат его неизвестен (недокументирован, реверснуть можно, но неохота); будет ли подерживаться в дальнейшем, непонятно. Так что не считаю это достаточно хорошей идеей.
Ну есть и определенные преимущества. Очень здорово, на мой взгляд, сделана в CubeMX страница Clock Configuration.
Еще можно легко протестировать различные варианты конфигурации периферии, даже если нет желания использовать сгенерированный код (и вправду тяжеловесный «макаронный») в конечной реализации.
В определенный момент проект можно оторвать от CubeMX (правда, вернуться уже не получится) и уйти в свободное плавание.
Вот это то и пугает, что вернуться нельзя, а если мы хотим внести изменения в конфигурацию — тогда и получается, что Cube — хорошее подспорье в начале разработки, но неважный инструмент в дальнейшем, все равно придется разбираться в коде, хотя идея была неплоха.
Если бы еще сделали кодогенератор, работающий из комментов, что то вроде Doxy, но я такого пока не видел.
Ну так есть же такое — Wizard в Кейле. Как раз в комментах и сидит, вот так примерно:
//*** <<< Use Configuration Wizard in Context Menu >>> ***


/*
// <h> USB Configuration
//   <o0> USB Power
//        <i> Default Power Setting
//        <0=> Bus-powered
//        <1=> Self-powered
//   <o1> Max Number of Interfaces <1-256>
//   <o2> Max Number of Endpoints  <1-16>
//      <i> Number of Bidirectional Endpoints
//   <o3> Max Endpoint 0 Packet Size
//        <8=> 8 Bytes <16=> 16 Bytes <32=> 32 Bytes <64=> 64 Bytes
//   <o4> Bytes in output/input report <1-64>
//   <h> Double Buffer Setup
//      <i>Double Buffer not yet supported
//<i>     <i> Enable Double Buffer Mode for selected Bulk Endpoints
//<i>     <o5.1>  Endpoint 1
//<i>     <o5.2>  Endpoint 2
//<i>     <o5.3>  Endpoint 3
//<i>     <o5.4>  Endpoint 4
................
«в С мы можем возвращать любой тип, за исключением массива»
я бы переформулировал, указатель на массив мы возвратить можем

char * returnArray()
{
static char a[3] = { 0, 0, 200 };
return a;
}

int main(int argc, char ** argv)
{
return returnArray()[2];
}
Мы не можем вернуть указатель на массив, а лишь указатель на элемент массива.
И меня больше всего удивляет, что структуру, содержащую массив, вернуть можно, причем именно структуру, а не указатель на нее (хотя, конечно, можно и указатель), а конкретно одинокий массив — нельзя. Странно это, господа.

Меня одно интересует, вы это всё с телефона написали? Такое упорство заслуживает одобрения уже само по себе )

Ну, конечно, не с телефона, а с планшета, у меня Perfeo 9716 (их больше не делают), но в основном да — по пути на работу и обратно. Кстати, увлекательнейшее занятие, время поездки пролетает просто незаметно, даже лучше, чем когда что-либо читаешь. На ПК делал только окончательное оформление.
Очень хорошо написано и интересно читать. Спасибо. Единственное, что меня напрягло: если Вы считаете, что программист должен отслеживать структуру модулей в продукте, то почему бы ему не перестать забывать сбросить поля структуры настройки перед конфигурированием?

PS. Видел интересную идею вроде задания
#define UARTConfigDefault {1,2,3}
и последующей инициализации структуры вроде
UARTConfigT UARTConfig = UARTConfigDefault;
Я писал о необходимости проектировать структуру модуля для программиста библиотек, который должен иметь более высокую квалификацию, чем ее пользователи. А вот как раз пользователь должен знать о внутренних заморочках библиотеки как можно меньше, и ему разрешается делать ошибки, связанные с забывчивостью, которые мы и пытаемся пресечь в корне. Так что я вполне осознанно применяю разные требования к разным уровням программирования встроенных систем.
А решение, которое Вы привели, интересное, но опять таки пользователь должен не забыть такую строку написать, и, что немаловажно, оно сработает только для первой инициализации.
Да, вы правы. Совсем забыл, что вопрос рассматривается с точки зрения создателя библиотеки. Спасибо.
Я (как, вероятно, каждый второй) для STM писал свою обертку над UART'ом и я для инициализации использовал вот такую сигнатуру:
void init( Pins pins, uint32_t baudrate, GPIO_TypeDef * rs485Port = 0, uint16_t rs485Pin = 0 );


При этом Pins — это строгий enum (аналог enum class средствами С++03):
    STRONG_ENUM( Pins, UART1_PA9_PA10,  UART1_PB6_PB7,
                       UART2_PA2_PA3,   UART2_PD5_PD6,
                       UART3_PB10_PB11, UART3_PD8_PD9, UART3_PC10_PC11,
                       UART4_PC10_PC11,  
                       UART5_PC12_PD2 );


Получается довольно удобно. Последние два опциональных параметра задают порт и ногу для переключения преобразователя UART-RS485 (который приходится использовать довольно часто).

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

Единственный минус — соответствие моего объекта uart'a и значения Pins проверяется только во время выполнения, но как это красиво проверять при компиляции я не придумал.
Ваш вариант выходит за путь самурая, о его недостатке я чуть сказал в тексте, но самое главное — посыл поста был в том, как ЗАСТАВИТЬ пользователя вызвать функцию инициализации, а тут мы его всего лишь уговариваем.
В таком случае, я, видимо, не до конца понял ваше решение. Как вы можете заставить пользователя вызвать вашу функцию? Это ведь не конструктор.
Если Вы не вызовете функцию инициализации, то не получите указателя. который дальше пропихивается через функции изменения в функцию использования.
Ну ведь никто не запретит пользователю просто создать указатель такого типа? Ну, если пользователь совсем дуб.
А если не совсем, то он и так понимает, что UART инициализировать нужно, т.е. и особого принуждения не требуется.
В том то и дело, что, например, я не совсем дуб, но проинициализировать начальные значения структуры иногда забываю.
Ну а что касается создания указателя, то да, помешать невозможно, но тут уж ничего не поделать.
Угу. хотелось бы писать как-то так:
UARTCreate()->setParity(XXX)->setBandwidth(XXX)->init();
Если Вы можете позволить себе С++, то, конечно, так можно и, наверное, даже нужно, единственный вопрос, как гарантировать последний вызов?
Гарантия первого у Вас есть. Но гарантия последнего вызова и у меня делается нестандартными средствами, что не есть хорошо.
Вариант без статической структуры, о которой не надо знать пользователю.

Либа:
typedef struct UARTConfigT
{
UARTSpeedT speed;
UARTStopT stop;
UARTParityT parity;
} UARTConfigT;

int UARTConfigUse(UARTConfigT config)
{
return DoSomething();
}

// инициализация со значениями по умолчанию
#define UARTConfig(...) UARTConfigUse((UARTConfigT){.speed = UARTSpeed9600, .stop = UARTStop1, .parity = UARTParityNone, __VA_ARGS__})

Инициализация с нужными параметрами:
UARTConfig(.speed = UARTSpeed115200, .parity = UARTParityEven);
А так можно?
Вариант интересный, сейчас попробую.
А ведь действительно можно!
Отличное решение, работает, начиная с С99, но я думаю, что ради такой красоты можно продлить путь самурая чуть дальше.
Немного пугает, что мы сказали пользователю именя полей, но, я думаю, оно того стоило, зато как компактно и понятно.
Спасибо большое за подсказку, настоятельно рекомендую такой вариант.
Единственное, чем придется заплатить, так это небольшим количеством памяти программ для хранения константных значений, но, думаю, оно будет меньше, чем затраты на вызов сеттеров.
Вот так, во взаимодействии профессионалов (Вы позволите так Вас назвать) и создаются шедевры.
К сожалению, я изучал язык С намного раньше 99 года и таким он у меня и остался, что меня нисколько не оправдывает.
Мне как раз нравится из-за названий, по-моему, очень читабельно, особенно если параметры не такие очевидные, как UARTSpeed115200. И узнал я о таком способе весьма недавно из книги «21st Century C» (Ben Klemens).
UARTConfigSpeed(UARTSpeed4800,UARTConfigParity(UARTParityEven,UARTConfigInit()));
А если что-то сложнее? (Библиотеки инициализации должны быть в одном стиле)
Я обычно шучу: первый оператор в выражении снимает с предохранителя, второй подает патрон в патронник, третий направляет в ногу, четвертый…
По-моему это очевидный минус такого подхода. И не знаю что лучше/хуже так или обьявление структуры с возможностью забыть инициализацию.

P.S. Неужели структура после Вашей строки остается в памяти?
Да, а куда ей деться то, но, к сожалению (или к счастью) доступ к ней вне данного оператора невозможен средствами языка. А так да, до окончания работы функции она лежит на стеке.
Я думал раз не используется (после последней вызываемой функции) то уничтожиться… как если бы завернуть все это в блок или в функцию. Интуитивно считал так, хотя очевидно что это не идентичные вещи, гуглю… и не могу найти ответы. По крайней мере это было бы разумно.
Ну по коду ассемблера видно, что стек продвигается только перед выходом из функции, так что пока другая вызванная из этой функция не вернет не-примитивный результат, последний результат лежит на стеке.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации