Pull to refresh

Comments 23

Если к программированию МК вот прям лежит душа, и вам это интересно и нравится - советую при выборе следующего места работы, очень тщательно подходить к этому вопросу. Embedded сфера к сожалению (по крайней мере в России) очень консервативна с плохой точки зрения - древние деды-руководители будут вам доказывать, что RTOS это фигня, надо делать "по старинке" через конечные автоматы, динамически выделять память в куче нельзя, и так далее. А еще часто будут пытаться доказывать, что нужен ассемблер, без него производительность будет "не та", хотя на вопросы "Где доказательства, давайте напишем тесты/посчитаем конкретно" - обычно "ну ты не понимаешь, я начальник, я так сказал" и так далее.

Без RTOS код превращается в не поддерживаемую лапшу из всего подряд, в основном - там будут попытки придумать подобие RTOS (Как же часто я видел эти потуги, и как же часто я с 0 переписывал все это).

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

Пока же, к сожалению, похоже что вы просто потратили 1.5 года довольно не эффективно.

К программированию МК именно что душа лежит, да. Говоря простыми словами: от оживающей железки на столе получаю больше удовольствия, нежели от буковок/циферок бегающих по экрану.

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

1.5 года потратил не очень эффективно, возможно. Всё познаётся в сравнении. Пока не с чем особо сравнивать, но сравнивая себя на старте и себя сейчас - разница существенная, значит не совсем уж не эффективно)

Да вот хз с РТОС. Не всякий набор задач требует параллельности. Черезмерное использование приводит к еще более дикой каше и дико дико дико усложняет код. Использование ртос правильное гораздо сложнее, чем кажется на первый взгляд - просто выкинь в отдельный таск и прокинь очереди.

Прокидывание очереди заставляет отвечать на множество вопросов, которые без нее бы не возникли. Какой установить размер? Что делать при ее переполнении? Как делать если очередь пустая?

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

Асинхронные баги гораздо сложнее поймать.

И все это ради чего? Всегда ли это требуется? Безусловно, иногда это чуть ли не единственный путь. Но часто я видел, как во всяких примерах множат сущности без какой либо нужды. Оно прекрасно работает, когда у тебя в приложении 2-3 таска, имеющих простейшую логику, но когда это становится чем-то несущим пользу и взаимодействующим с реальным миром - начинается жопа. А ну и да. ОЗУ она жрет дай боже, когда приложение начинает разрастаться.

Поэтому РТОС не серебряная пуля даже близко и я вполне могу понять людей, которые предпочтут иметь парочку КА, вместо лишнего таска и очередей. Все возможно, но то что вот без РТОС макароны-говнокод, а с РТОС все чудеса, не код, а произведение шекспира -- гадкая ложь, РТОС еще больше требований к правильной архитектуре ставит и там где без РТОС макароны, с ней еще большая жопа.

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

Множество сложностей и вопросов на начальных этапах изучения РТОС (Хотя все эти сложности можно избежать, если просто взять и предварительно прочитать специализированную литературу, например Mastering the FreeRTOS realtime kernel) потом с лихвой окупятся, поскольку вы станете проектировать и разрабатывать надежные, расширяемые, и поддерживаемые другими разработчиками впоследствии приложения.

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

Сможете ли вы написать такое используя ворох конечных автоматов и т.п. ? Возможно сможете, но пройдете путь неистовой боли (Не знаю лично ни одного коллегу, кто бы впоследствии не сказал, что это было не так) и по-своему опыту могу сказать, что вероятно осилите вы только часть про "надежность" если будет хорошо покрыто тестами и оттестировано.

Вот прямо сейчас я потрошу очередной проект, который был написан в обыденной уже форме - велосипедные очереди, попытки организовать этот хаос (Человек опять же видимо не понимал разницу между вытесняющей и кооперативной многозадачностью), и т.п. - так почему нельзя было взять РТОС с самого начала, если все равно потом самому же стало ясно, что так жить дальше нельзя ?

Так что после вас, с 99% все эти ваши труды будут выкинуты или переписаны кем-то другим, если не сделать изначально качественно.

Не так важно, используется RTOS или нет, но важно, как всё организуется. Из практики. Были несколько проектов, без оси. Всё крутилось в главном цикле, всё выполнялось последовательно. Работало. В новом поколении устройств внедрили РТОС, и проекты переписали. Каждый чих - отдельным потоком, и вроде всё красиво, в теории. А а по факту, работает криво-косо. Потоки вразнобой, рандомно задерживают друг друга из-за необходимости синхронизации. Эту самую синхронизацию местами забывают добавить. Чёткую временную диаграмму выдержать сложно. Период какой-то операции неправильный? Пытаются лечить задержками. Задержку у одного потока поменяли - временная диаграмма подвинулась, остальное уехало. Боль, вечные замечания от заказчика, вечная поддержка, доработка костылями. Проект переусложнённый, новички пугаются. Или, что ещё хуже, начинают считать, что это норма. Наконец это надоело уже, и переписали по-классике. Основной поток, в нём цикл, в нём последовательно выполняются функции-задачи. Отдельные потоки - только для действительно независимых, фоновых задач, не связанных напрямую с прикладным функционалом. Кучи потоков теперь нет, временная диаграмма стала чёткой и понятной, синхронизация не нужна, кода стало в два раза меньше, ресурсов процессора потребляется меньше, что и как делает проект стало наконец-то понятно человеку, впервые открывшему код. Ну и замечания, которые до этого годами копились и висели, вгоняя разработчиков в депрессию, вдруг тихо и мирно пропали.

Судя по вашему описанию, похоже что проблема вообще не лежит в плоскости "Мегацикл vs РТОС" - выглядит так, как будто бы программисты что внедряли новое поколение устройств, абсолютно не понимают, как применять и что такое RTOS, а так же зачем вообще они нужны. "Синхронизацию местами забывают добавить" - все мы люди конечно, но если такое происходит не у новичков, и неоднократно - я бы поставил ребром вопросы об их общей компетентности. Это действительно те же самые люди, что писали изначальные проекты ? И где условная гарантия, что такие люди не будут плодить баги и т.п. при работе в стиле "Мегацикла" ? Похоже, что решение об внедрении РТОС принималось либо руководством "Потому что круто" либо кем-то, кто опять же слабо понимал, что он делал, и зачем.

В конце, где вы описываете, как хорошо стало при возврате к исходному мегациклу - вы оперируете терминами, очень близкими по своей сути к тем, которые уже предоставляют РТОС. Ситуация напоминает аналогию к комп. играм, где прокачивается древо технологий - осталось собрать еще определенное кол-во элементов, и вам откроется следующий этап - как ни удивительно, этот самый РТОС.

А может быть так, что следствием того, что проект не получилось перенести нормально на РТОС потому, что на самом деле никто не понимает, как все работает, и пришлось просто делать по аналогии с тем, что уже было (Либо прямо в стиле ctrl+c/ctrl+v переносить) ?

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

Меня просто вот эта фраза зацепила

Embedded сфера к сожалению (по крайней мере в России) очень консервативна с плохой точки зрения - древние деды-руководители будут вам доказывать, что RTOS это фигня, надо делать "по старинке" через конечные автоматы, динамически выделять память в куче нельзя, и так далее.

Я считаю, что именно в embedded здоровый консерватизм бывает особенно полезен. Поэтому и привёл пример, как иногда бывает, когда "древних дедов-руководителей" нет, а решения принимают молодые и прогрессивные, несущие модные технологии, идеи и паттерны, но зачастую не понимающие, что и зачем они на самом деле делают. Ну действительно, зачем в условном устройстве с тремя кнопками и двумя светодиодами несколько десятков потоков? Да, как таковая РТОС тут действительно не при чём, но она дала возможности "прострелить себе ноги". В момент принятия решений не оказалось в наличии оппозиции в виде "древних дедов", и парни сделали, как их учили в их книжках/курсах - берём ОС, на каждую задачку по потоку, разруливаем всё семафорами, применяем модных паттернов побольше. И в итоге, очень простую вещь закодировали очень сложно, а потом сами мучались от этой сложности. Не оказалось того, кто сразу, посмотрев на всё это, сказал бы - но ребята, это же простая задача, давайте решим её просто.

И где условная гарантия, что такие люди не будут плодить баги и т.п. при работе в стиле "Мегацикла"

Люди всегда будут плодить баги, как без этого. Но будут плодить заметно меньше, потому что будут лучше представлять, что и в каком порядке в программе происходит. Просто взглянешь на главный цикл, и сразу видишь последовательность действий программы. Искать ошибки, если они возникнут, станет на порядок проще, хотя бы потому, что весь цикл можно по шагам пройти в отладчике, видя всю причинно-следственную цепочку. А если всё навёрнуто на потоках, когда энкодеры читаются в одном потоке, кнопки в другом, светодиоды мигают в третьем и четвёртом, и чёткой последовательности отработки всего этого нет, то попробуй разберись, почему нажатия иногда пропускаются, периоды мигания плавают, и вообще, почему иногда какая-то жесть творится (а, так это ещё несколько неочевидных потоков, которые запускаются из тех что уже запущены, и что-то ещё там своё непонятное делают, а у одного из них ещё и стек переполняется, затирая стек другому, поэтому в определённый момент при определённом порядке отработки потоков, который нарочно фиг повторишь, всё падает, и т.п).

Теперь вот еще и STM32 придется чем то импорто-замещать!

Ммландр - fabless компания. Если они смогут хоть один контроллер на Микрон передать - уже неплохо будет.

Gigadevice? На тот же F405, для которого сейчас пишу проект, есть его клон GD32f405

Они с РФ не работают насколько я слышал. Посмотрите Artery или Geehy

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

Я лично прошёл через то, что проходите вы и уверяю, что

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

абсолютная правда - в подобной среде развиваться в данном направлении крайне сложно.

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

Проработав в месте с подобной организацией работы ~1.8 года я был на 100% был уверен, что так быть не должно и что-то нужно изменить. Подтянул английский до уверенного Upper Intermediate, подтянул знания по чистому C, потренеровался со сборкой проектов через cmake (не то чтобы было обязательно, но лично мне дало очень много понимания), подтянул знания по RTOS-ам, закинул пару простых проектов в GitHub аккаунт и пошёл искать работу на LinkedIn, HH и т.д. Как итог стабильно имел 1-3 собеседования в неделю и уже после 5го имел несколько офферов от крупных компаний с выстроенными процессами менеджмента проектов, их развития и поддержки на позицию Junior Embedded Developer.

Самый мой любимый вопрос на каждом собеседовании был: "Почему вы решили сменить место работы?". На который я всегда отвечал "Soviet Union based management principles", а после недоумения интервьюера излагал примерно слово в слово первый абзац раздела "Трудности новичка" вашей статьи. На меня конечно смотрели с недоумением как такое в наше время возможно, но старались тактично уйти от дальшейшего обсуждения этого вопроса без комментирования моего ответа. Кстати, если интервьюеры, которые были из СНГ региона смотрели на меня, наверное, как я на вашу статью и говорили "Старайся и вали оттуда".

Как итог могу посоветовать вам выполнить те же действия, что я описал и не задерживаться на этом месте работы. Попадя в хорошую компанию вы поймёте, что 1.5 года занимались на работе ерундой вместо серьёзной Embedded разработки.

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

Не увидел где там у автора в тексте жалобы на "Soviet Union based management principles".
Контора из разряда обычных.
Все мной виденные западные мелкие фирмы именно так и работали.
С ТЗ никто не заморачивается. Один дивайс - один разработчик. Что разработчик хочет, то и использует. За такое место держаться надо.

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

Как минимум надо портировать несколько RTOS. Начнёте использовать чипы TI столкнётесь с TI-RTOS-MCU, начнёте беспроводные стеки от Silicon Labs столкнётесь с Micrium OS и uC/OS, у Cypress придётся изучать ThreadX и т.д.

RTX неплохая RTOS, её последняя версия гораздо лучше и продуманней чем FreeRTOS.
На FreeRTOS могут склонять перейти некоторые опенсорсные проекты из куба и прочих пакетов, которые её используют как backend. Нельзя поддаваться этим позывам.
На самом деле реально популярные и нужные наборы middleware (FATFs, CMSIS, lwIP, lvgl, wolfSSL ...) имеют всегда слой абстракции от RTOS и привязываться к какой-то конкретной нет необходимости. Если уж попалось что-то очень крупное на FreeRTOS и без слоя абстракции, то можно вместо неё подложить ось которая сама может эмулировать FreeRTOS, это например Azure RTOS. И тогда соедините мир промежуточного софта обоих осей.

Реально сила в диверсификации своих тулсов. Пишите свой код как угодно. Большую часть ваших проектов должен составлять чужой код.
А потому и код-ревью не имеет большого смысла. Все равно будет смесь стилей и архитектурных подходов.




Жалоб, в том-то и дело, нет) Напротив, я рад, что меня взяли без опыта с перспективой к росту и дали возможность мало того, что защитить диплом, так ещё и не спеша учиться на большом проекте. Было немного неудобно заваливать коллег глупыми вопросами о базовых вещах, про которые в университете не рассказывали, но только на этом и удалось подучиться.

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

Отличная идея, спасибо!

Мой опыт отдиличается от вашего. Я работаю в компании, которая занимается крупносерийным производством устройств (в год >100000 только тех, которыми занимается мой отдел, а всего их 9 серий и в каждой из них 6+ исполнений).

У устройства есть жизненый цикл - это понятное дело. У нас он составляет порядка 5 лет, с учётом разработки, производства, эксплуатации и поддержки.

В таких условия концепция "один разработчик - одно устройство" не жизнеспособна.

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

В команде с более опытными разработчиками я регулярно получаю рекомендации от них по тонкостям архитектуры и используемых методик написания firmware. Что по моему личному опыту ускоряет профессиональный рост на порядок.

Я работал с ТЗ с которым не заморачивались и с которым заморачивались очень сильно. Как итог лично мне показалось, что заморачивать стоит определённо по причинам:

  1. Не четко сформулированное ТЗ приводит к недопониманиям и переписыванию функционала. (Случаи "забыл сказать", "ну что ты сам не знаешь что ли?", "а давай добавим", "а я тут подумал и прочитал, короче надо переделать"). А это всё время разработки.

  2. Чётко сформулированное ТЗ позволяет не иметь к друг другу претензий по поводу реализации функционала.

К тому же всё очень сильно зависит от объема реализуемого функционала на устройсте. Я полностью согласен, что при его не большом объеме его может реализовать 1 человек без проблем, но если одно устройство сожержит GPS/GSM/FOTA/SD/Flash/VCP + CLI/Акселерометр/RS232/RS485/BLE + Конфигуратор (Desktop), то сколько его будет реализовывать один человек? Что делать, если человек заболел, умер, ушёл в отпуск на месяц? Получается, пока не нашли нового - устройство без поддержки. В такой крупный проект пока вкатится кто-то новый пройдёт 1-2 месяца спокойно, а это будет даже не 30% общего функционала. (по моему субъективному опыту - вместе с уходом человека умерли и все его разработки).

Также в компании много замечал, что даже для старших и опытных разработчиков работа превратилась в кружок рукоделия из-за того, что "сам себе хозяин", но это вопрос нравственности и самообладания.

Держаться за такую работу для себя смысла не увидел. Платят там не больше, работа в команде не менее интересная. Возможно это больше актуальной для Senior Dev, но начинающего человека посадить самостоятельно разрабатывать проект для меня не выглядит хорошей мыслью. Обмен опытом в команде эффективнее, чем подойти спросить человека не с твоего проекта, так как за частую решение и ответ зависит от use-case устройства.

По поводу code review не согласен. Коллеги во время code review зачастую могут подсказать решения, к которым ты мог сам так быстро не прийти, best practice случаи которые по началу очень не очевидны. Если кратко, то expirience-exchange это всегда эффективно (это не значит, что надо забыть про всё остальное). К тому же, если код понятен для тебя - это не значит, что он понятен остальным, поэтому code style тоже является его очень важной частью review.

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

но если одно устройство сожержит GPS/GSM/FOTA/SD/Flash/VCP + CLI/Акселерометр/RS232/RS485/BLE + Конфигуратор (Desktop), то сколько его будет реализовывать один человек?

Как раз такое устройство собираюсь скоро начать делать. Буду делать один.
Только еще добавлю Wi-Fi и LoRa. А RS232/RS485 делать не буду, поставлю два USB-C с host и device зарядником. И наверно дисплей поставлю.

Сделаю, выложу на GitHub.

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

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

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

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


Специфика компании есть специфика компании.

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

Согласен, что при нашем подходе, если уйдёт коллега, который поддерживает 2 самых прибыльных проекта компании и ещё несколько по мелочи - весь наш отдел захлебнётся. Но он старожил компании и не намеревается куда-то уходить, пока что его всё устраивает. (ключевое слово здесь "пока что", да).

Держаться за такую работу для себя смысла не увидел

Чтобы не держаться за такую работу надо хотя бы один проект запустить, чтобы на собеседовании было чем помахать. Так что пока что держусь и набираюсь опыта)

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

Вот именно из-за этого я и задумался о код-ревью) Заметил за собой, что на написание и переписывание разных тулзов для работы трачу слишком много времени. Да спустя много времени тулза получается универсальной, красивой и отшлифованной, но затраченное время всё равно слишком велико, а у кого-то она может быть давно уже написана, да ещё и время исполнения сильно меньше.

Тут вопрос что вы называете тулсом.
Кольцевые буферы? Стеки FIFO? Связные списки?
Поверьте, это такие мелочи, что потом будет смешно как вы переживали из-за их недостаточной универсальности .
Если нужны образцы высшего пилотажа ООП в embedded, то советовал бы посмотреть классы в проекте mbed, кстати основан на RTX.
Ну и думаете mbed вырвался благодаря этому в лидеры? Нет!
Универсальность не гарантирует юзабельности, и убивает производительность.
Вот только что в LL для USB от STM32H7 убрал универсальность обработчика прерываний основанную на косвенных вызовах, и заменил на прямое однократное обращение к регистрам. И увеличил быстродействие в 6-ть раз.
Но думаю, что первый же код-ревью в большой фирме, строго бы сказал так не делать. Потому что поломал LL, сильно затруднил его будущие апгрейды, рассинхронизировался с основной веткой и взвалил на себя ответственность за поддержку своей ветки LL.
Что самое страшное, я потом буду дублировать этот LL много раз и каждый раз с изменениями. Уже начал. Но не переживаю из-за этого. Последующая синхронизация сорсов на самом деле совсем нетрудный процесс и даже полезный для ретроспективного обзора. Подумайте о работе реверс-инженеров, которые за месяц восстанавливают и полностью осваивают не то чтобы чужие сорсы, а исходники голых бинарных дампов.

Я конечно не автор поста, но мне тоже интересно знать, куда двигаться. В нашей фирме за своё устройство отвечает один разработчик; много legacy кода, написанного на Ассемблере для PIC16 / x51 ядра фирмы Silabs. Про С±± пока нет и речи. Перевести бы изделия с зоопарка архитектур на GigaDevice.

Про ASM-Legasy могу только посочувствовать...

А на счёт борьбы с зоопарком: после того, как статья вышла, коллеги почитали её и комментарии - мы сели думать, как нам сорганизовать самих себя. Пока что есть две идеи: 1 - переход на Rust (неоднозначная и сложная) и 2 - использование общего шаблонного мастер-проекта и libopencm3. Со второй идеей интереснее, потому что она решает именно проблему зоопарка: библиотека работает на очень многих cortex-M разных производителей. Пока ещё на стадии оформления, со временем посмотрим, что из этого получится. Может есть что-то подобное libopencm3 для x51?

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

Сейчас меня заминусят, но многие программисты (я в том числе) достаточно сильные интроверты. Им хорошо в своём уголке, и никто не оспаривает их решений и не навязывает мнения, пока сами не спросят. С высоты "выстроенных процессов" это кажется тупиковой ветвью развития, но некоторым людям так гораздо проще работать, чем в большой команде "умников" выглядеть "глупцом" и сгорать со стыда от этого и выгореть ещё раньше в конечном итоге.

Sign up to leave a comment.

Articles