Pull to refresh

Comments 24

Программирование МК — это не та область, где существуют «правильные» и «неправильные» решения так же явно, как они существуют на обычных компьютерах.

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

Вы скажете, что для этих целей можно реализовать отдельную функцию ожидания завершения работы интерфейса, а код вывода следует оставить как есть. И будете правы. Во всем, кроме одного маленького момента. Иногда на этих крохах можно сэкономить критически необходимые такты быстродействия или байтики ПЗУ. Когда на счету каждый такт или каждый байт — то не до жиру в плане красоты программ и их абстрактной надежности. А единственное, что играет роль — это характеристики программы и ее фактическая надежность в тех условиях, в которых она будет работать, без учета фантазий, а с учетом только реальных обстоятельств.
Да, вы правы, единственное возможное объяснение такой реализации — необходимость удостовериться, что передача завершена и это единственное оправдание. Но в данном случае этот аспект не использовался и, если это действительно необходимо, следовало бы в комментариях отметить, почему применяется неудачное решение.
А общий смысл поста был именно в том, что есть более удачное по всем параметрам решение и следует привыкать использовать именно его, хотя истина всегда конкретна.
встроенном программировании

Наверное, всё-таки, программирование встроенно в человека, а тут речь о железках.
Народ подскажет верный термин, пока что получается «программирование встроенного» или «программирование встраиваемых систем». Можно ещё выделить или заключить в скобки. что бы было понятно, что это термин или придумать какой-нибудь жаргонизм, типа встрой-программинг.
Ну я сделал такую кальку с сочетания «embedded programming», иногда еще употребляют «fimware» в смысле «прошивка», наверное такой жаргонизм был бы правильнее.

Объясните пожалуйста, что даёт const в передаваемом функции параметре? Почему использование static в конкретном примере — правильное применение?
Может я конечно неправильно понял суть проблемы, но нельзя ли тут попользовать прерывания, если не хотим в while ожидать флага? Кстати boolean в чистом си нет, или это от wiring сюда попало?

const говорит компилятору, что попытка изменить значение параметра будет ошибкой и должна пресекаться. В данном случае, когда мы передаем по значению, это неважно, но нужно приобретать полезный навык и все параметры, не подразумевающие возврат, отмечать как константные, так что это решение правильное, но в данном примере и необязательное.
static вообще полезен, чтобы не загромождать глобальное пространство имен и не вызвать по ошибке функцию, которую вызывать не собирались — это в общем. Но многие компиляторы (например IAR) inline без static просто не разрешат, чтобы не было соблазна вызвать его из другой единицы компиляции.
И Вы правы, прерывание будет правильным, речь шла исключительно о разных вариантах ожидания флага.
boolean этот мой enum (False=0,True=1), я о нем в предыдущих постах говорил, вот и использую.

Я в программировании недавно, но мне кажется, что именно здесь const не удобен, и возможно даже помешает. Возможно ведь, что мне потребуется передать не какой-нибудь константный объект, а например полученный из датчика массив байтов или еще что-то подобное. Обычно такие функции, лично я использую, чтобы потом их попользовать уже например в более серьезной функции передачи того же массива. Тут кстати и static уже не грех применить, типа такой инкапсуляции чтоли) Вобщем думаю еслиб указатель передавался, то const бы мог уже на этапе компиляции, поругать, если мы захотим его изменить. А так один фиг передача по значению, как вы правильно заметили.
Я пишу в AtmelStudio чаще всего, там inline без static работает(точнее компилятор не ругается), но честно говоря не проверял, реально ли inline там работает, или все же происходит именно вызов функции.

Вы не совсем правы, const никак не может помешать, если мы не собираемся модифицировать передаваемые параметры, поэтому применяем его где только можно.

да, проверил никак не влияет… я думал если параметр функции помечен как const, то и получать функция должна const. Над матчасть повторять периодически)))

Собственно, вариант 1 гарантирует что с объектом можно работать сразу после выхода из процедуры. Иногда эта функция важна — передать байт, выключить модуль передачи и уснуть до следующего приёма.
Вставлять проверки и до и после тоже не вариант, Часто нужно сделать что-то и не ждать когда оно выполнится — заняться другой работой, в т.ч. подготовкой следующей порции данных пока передаётся очередная.
Учитывая что в контроллерах достаточно строгий контроль когда и что происходит, применяются оба варианта не по принципу правильно/неправильно а исходя из необходимого поведения в конкретном месте. Часто бывает такое что применяют и первый и второй варианты, просто надо знать что нужен строгий контроль над тем что и когда происходит и может произойти с объектами в программе.
Подскажите, пожалуйста, а зачем в первой реализации nop?
А я и сам не знаю, в фирменных примерах его нет, может быть, автор боялся что флаг готовности после инициализации не упадет мгновенно и может произойти вторая подряд запись. Где то в памяти сидит, что у кого-то когда-то подобные устройства были, но конкретно вспомнить не могу.
Я люблю когда мои программы простые, поэтому я хочу чтобы мой код выглядел примерно так:
chipSelect();
writeSPI(0x0a);
chipUnselect();

однако, если использовать второй вариант, мне придется явно добавлять ожидание, в лучшем случае так:
chipSelect();
writeSPI(0x0a);
waitReadySpi();
chipUnselect();
Все верно, но это только в том случае, когда Вы делаете ровно одну запись, во всех остальных случаях я рассмотрел преимущества второго способа.
Что значит ровно одну? Даже одна работать не будет:
chipSelect();
writeSPI(0x0a);
writeSPI(0x0b);
writeSPI(0x0c);
chipUnselect();

При использовании второй реализации, 0x0c в устройство записан не будет — кого винить?
Вестимо функцию chipUnselect() которая не убедилась что все данные записаны прежде чем убрать разрешающий сигнал.
Не все программят на Сях. Даже и МК. Для охвата аудитории полезно было бы алгоритм описывать не на СИ-коде.
Да в общем то код настолько прост, что транслируется в асм практически один в один, хотя, может Вы и правы.
Я, конечно, чувствую себя недоумком, но всё таки скажу: есть большущее подмножество промышленных MEK-контроллеров, типа Овен, которые программятся на пятёрке МЭК-языков (три из которых — визуальные), пользователи которых могут не знать ни АСМ-а ни Сей, а пользоваться, например, Питоном, как я. Прочли такие пользователи статью и ничего не поняли — китайская грамота. Возможно, есть и другие не Си-шные контроллеры.

То, что программируется на МЭКоских языках обычно называется PLC/ПЛК (программируемый логический контроллер). Это не микроконтроллеры, про которые речь в статье. Внутри PLC содержит тот же микроконтроллер, на котором крутится программа, почти всегда написанная на C или C++. Это просто совершенно разные уровни.


Если вы сталкиваетесь с микроконтроллерами, то стоит не просто знать синтаксис Си, а понимать его. Также как уметь читать datasheet'ы, всякие временные диаграммы, простые логические схемы (используются, например, при описании мультиплексируемых входов и выходов микроконтроллера) и т. п.


В случае PLC у вас не возникнет проблемы контроля за состоянием бита занятости в контрольном регистре какого-нибудь SPI перед или после записи байта в data register. Вы просто запишете значение в глобальную переменную или подадите на вход специального атома/блока.


Си — lingua franca для системщины и прочего низкоуровнего. Его не избежать, если хочется с пониманием делать что-то на микроконтоллерах. Всякие заходы python, js, lua на контроллерах забавны, но абстракции там текут очень сильно, так что рано или поздно может понадобится заглянуть в потроха интерпретатора, а там опять же Си.

Спасибо, я бы не ответил лучше.
На самом деле, во всей статье упущена одна важная вещь: spi это дуплексный интерфейс — взамен отправленного байта вы получаете байт на чтение. Отсюда вопрос: когда читать данные если использовать второй вариант?

При использовании busy-wait'а, как в статье, я обычно пишу функцию, которая пишет указанный байт, а считанный возвращает и две обёртки для записи (которая игнорирует считанный байт) и для чтения (которая пишет dummy байт). В рамках базовой функции делается синхронизация на возможность записи в DR в начале, и на возможность чтения перед возвратом результата. дерганье CS/nCS происходит в более высокоуровневых функциях. Описываемые функции являются по возможности static inline.

Если Вас в данный момент интересует только запись (а часто так оно и есть), то обычно перед инициализацией операции чтения либо нулируется указатель чтения (если это возможно), либо вычитывается буфер в мусорку.
Sign up to leave a comment.

Articles