MinimumLaw / StarLine_TestTask_2021

Test task for StarLine

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Тестовое задание на соискание должности разработчика встраиваемых систем в StarLine

Формулировка заказчиком

Реализовать программный модуль управления кольцевым буфером на 100 байт для STM32F412:

  • извлечение байта
  • добавление байта (при переполнении новый байт перетирает старый по кругу)
  • если буфер непустой, его байты отправляются по UART

Модуль должен содержать:

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

К модулю нужно составить достаточную документацию, по которой другой программист сможет встроить его в свой код.

Результат прислать в виде:

  • исходные коды модуля + документация
  • проекта Keil, демонстрирующий работу модуля, который компилируется и может быть прошит в микроконтроллер для проверки. Демо-проект должен генерировать случайные массивы байт через случайные промежутки времени и добавлять их в очередь на отправку в UART.

Анализ

про ТЗ

Немного забавляет предположение о том, что у меня есть отладка с STM32F412. Действительно, весь дом забит всякими востребованными и не очень железками. На самом деле ровно наоборот. Железо на работе. Дома отдых и аквариумы. Однако же, оказалось что дома есть BluPill c STM32F103. Это, безусловно, не 412-ый, но хоть что-то. Однако у него нет встроенного генератора случайных чисел.

Опять же - не то чтоб сложная проблема, но... Опять же в рамках выполнения тестового задания заморачиваться еще и генератором как-то не хочется. Потому обзавелся валяющейся с давних пор на работе STM32F4-Discovery c STM32F407-ым. Уже ближе.

Кольцевой буфер на 100 байт. Да не вопрос. Но не могу не заметить - на 128 было бы сильно оптимальнее в плане производительности (а равно любого размера, кратного 2 в степени).

Обработчик прерывания софтовой реализации UART. Формулировка классная. Реально нравится.

А случайные массивы байт через случайные промежутки времени - если б это было не тестовое задание, то и за написание бы не сел. Крайне мало конкретики.

Документация, необходимая и достаточная. Тоже звучит неплохо.

По предоставлению результата - данный репозитарий + данный файл. Помним о том, что согласован IAR вместо Keil (хрен редьки не слаще - особо роли не играет). Ну не работал я с ним. Вкусовщина. Помимо этого готов предоставить F4-Discovery и возможность поиграться параметрами в main.h в части размера буфера, размера блока случайных данных и времени случайных задержек.

про реализацию

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

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

Потому первая часть это реализация mutex'ов. К счастью, на ARM есть инструкции обеспечивающие эксклюзивный доступ к ресурсам, а CMSIS делает из доступными практически из любого компилятора. Таким образом, минимально необходимый Mutex элементарно пишется на LDREX/STREX/CLREX.

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

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

Код

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

Для чекого отделения того, что сделал STM32CubeMX от того, что написано мной просто перечисляю свое

Mutex'ы

Файлы:

  • Core/Src/mutex.c
  • Core/Inc/mutex.h

mutex_t new; - создать новый

mutex_unlock(&new); - разблокировать (или инициализировать разблокированным) не возвращает ничего

mutex_try_lock(&new); - попытаться захватить может вернуть:

  • SUCCESS (E_MTX_SUCCESS) если удалось
  • E_MTX_ALREADY_LOCKED если уже захвачен другой программой
  • E_MTX_TRY_AGAIN - если в процессе захвата поменялось значение.

FixMe: про mutex'ы в процессе документирования

mutex_unlock(NULL); или mutex_try_lock(NULL) приведет к разыменовыванию NULL, а не должен. Нет предела совершенству. Главное знать когда остановиться. Проверки дописываются очень легко и быстро, а у меня и так deadline прострочен

Кольцевой буфер

Файлы:

  • Core/Src/rbuff_s.c
  • Core/Inc/rbuff_s.h

прототипы и реализация простого кольцевого буфера (произвольного размера)

rbuff_s_init(void *mem, uint32_t sz); - создать кольцевой буфер из области памяти по указателю mem размером size. Размер области хранения будет sz - sizeof(rbuff_s_ctrl). Данную формулу вполне можно использовать для выделения буфера в HEAP. Возвращает:

  • SUCCESS (E_RBS_SUCCESS) если все хорошо и сделано (hint: rb->ctrl.max_size будет содержать размер области хранения)
  • E_RBS_PARAM ошибка во входных данных (указатель недействительный, слишком мал объем выделенного блока)

в случае удачного завершения желательно сделать так rbuff_s *rb = (rbuff_s *)mem; для более удобного доступа к буферу

rbuff_s_put(rb, data) - поместить элемент data в буфер (тип элемента data - синоним uint8_t) Возвращает:

  • SUCCESS (E_RBS_SUCCESS) если все хорошо
  • E_RBS_BUSY в данный момент буфер занят, повторите попытку позже (конкурентный доступ)

rbuff_s_gett(rb, &data) - возвращает элемент data из буфера (тип элемента data - синоним uint8_t) Возвращает:

  • SUCCESS (E_RBS_SUCCESS) если все хорошо
  • E_RBS_BUSY в данный момент буфер занят, повторите попытку позже (конкурентный доступ)
  • E_RBS_EMPTY не вернули, нет ничего - буфер пуст

rbuff_s_used(rb, &used) - возвращает количество данных в буфере в used (синоним uint32_t) Возвращает:

  • SUCCESS (E_RBS_SUCCESS) если все хорошо
  • E_RBS_BUSY в данный момент буфер занят, повторите попытку позже (конкурентный доступ)

Не уверен, что понадобится, но все же пусть будет

Soft-UART

Файлы:

  • Core/Src/uart_s.c
  • Core/Inc/uart_s.h

прототипы и реализация soft-uart'а

На данный момент выдает единственную внешнюю переменную s_uart* типа s_uart_t.

Код допускает расширение и, ценой небольших доработок, выдать несколько таких устройств. Для простоты тут минимум.

s_uart->init(s_uart, speed, bits, parity, stop); где:

  • speed - скорость в бод
  • bits - количество бит (от 5 до 32)
  • par - символ четности ('N','n',' ' - нет, 'E','e' и остальные не перечисленные символы- до четности , 'O','o' - до нечетности, 'M','m','+' - принудительно ставить, 'C','c','-' - принудительно не ставить)
  • stop - количество стоп битов (значение больше 2 эквивалентно 2).

FixMe: по функции инициализации

bits не проверяется на больше 32, а стоило бы

Возвращает:

  • SUCCESS (E_SUART_SUCCESS) - все хорошо
  • E_SUART_PARAM - неверные параметры на входе

s_uart->send(s_uart, data, &on_send_done); - отправить data (uint32_t) через устройство s_uart, и по готовности запустить функцию void on_send_done(void) Возвращает:

  • SUCCESS (E_SUART_SUCCESS) все хорошо
  • E_SUART_PARAM неверные параметры на входе
  • E_SUART_BUSY устройство занято (уже передает данные, OVERRUN не поддерживается)

FixMe: в целом по реализации soft-uart

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

main() и генератор случайных данных

Переменные:

  • static mutex_t s_uart_used;
  • static char rb_memory_block[RINGBUFF_S_MEM_SIZE];
  • static rbuff_s *rb = (rbuff_s *)rb_memory_block;
  • static volatile uint32_t random_bytes_remain;

s_uart_used должен быть (и будет) захвачен на время передачи данных по soft-uart

rb_memory_block и *rb - память под и удобный указатель для доступа к кольцевому буферу

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

Функции:

  • void my_bug(void); вызывается из тех мест, куда попасть не должны никогда. Некий HoneyPot простой.
  • static void sw_init(void); инициализирует софтовые структуры (железо уже проинициализировано HALом)
  • static void s_uart_send_next(void); - самозамкнутая функция, которая читает следующий байт из буфера и осуществляет его передачу по UART. По необходимости освобождает mutex занятости UART (и в этот момент переключает зеленую лампочку на F4-Discovery для удобства отладки)
  • void rng_ready(RNG_HandleTypeDef *hrng, uint32_t random32bit); обработчик готовности генератора случайных чисел. Функция самозамкнутая, сгенерированные числа помещаются в буфер по мере генерации.
  • static void producer_task(void); - функция, вызываема из главного цикла. Реализует случайную задержку (до заданного значения милисекунд), вычисляет нужный для данного цикла случайный размер данных, ожидает готовности блока этих самых случайных данных и, при необходимости, иницирует старт передачи.

До главного цикла необходимо вызвать sw_init(), в главном цикле poducer_task();

main.h и настроечные параметры

#define RINGBUFF_S_MEM_SIZE             (100+16) /* data + sizeof(control_block) */

Задает размер суммарного блока памяти под кольцевой буфер (данные + управление)

#define MAX_TEMP_ARRAY_SIZE             (250)    /* more, than buffer size */

Задает максимальный размер случайных данных, помещаемых в буфер (естественно, если перекрывает размер буфера, то с хорошей вероятностью данные будут перетираться)

#define MAX_DELAY_MS                    (25)

Задает максимально возможную задержку в миллисекундах (не очень точно) между помещением следующего блока случайных данных в буфер.

FixMe по main()

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

Хронометраж по выполнению

Расклад примерно следующий:

  • mutex, rbuff_s, s_uart - базовый код, без пробы на железе 4 часа (туда и обратно в электричке до дачи)
  • Базовая проверка на F103 и начальное документирование - 1 час
  • Проверка на F4-Discovery s_uart (с осцилографом и вылавливанием блох с четностью) - 1,5 часа
  • Дописание producer_task и прочего кода в main, проверка всего проект в сборе - 2,5 часа
  • Документирование - 1,5 часа

Итого потрачено 10,5 часов. Internet использовался для поиска быстрого алгоритма подсчета единиц (четность).

Общее впечатление: разномастный код получился. Впрочем, из трех китов каждый последующий сложнее предыдущего. Закономерно, что mutex'ы просты как мычание, rbuff_s и producer посложнее, с s_uart хочется сделать максимально похожим на драйверную реализацию во взрослой оси. Но все это требует времени, а решение надо сейчас.

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

License - BSD

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

About

Test task for StarLine


Languages

Language:C++ 56.6%Language:C 43.0%Language:Assembly 0.4%