Альтернативы потоков для встроенных систем
В настоящее время изучаю электротехнику. Из-за пандемии мои занятия были приостановлены, и я использую это время, чтобы узнать больше об электронике и программировании.
В настоящее время я пытаюсь использовать Pic16f628a
обычный цифровой дисплей для создания цифровых часов с некоторыми функциями. Дело в том, что мне пришлось получить доступ к меню, нажав кнопку во время выполнения, пока отображаются часы. Обычно я вызываю поток для отображения часов, и основной поток будет следить за вводом, но из-за простоты контроллера pic я не могу использовать ресурс.
Итак, мой код C (еще не реализованный специально для pic) выглядит примерно так:
void display_timer(){
static struct current_time timer;
static int is_time_set = 0;
set_current_time(&timer, &is_time_set);
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
sleep(1);
update_timer(&timer, &is_time_set);
}
}
int main ()
{
while (1){
display_menu();
}
}
Во время сна () контроллер должен иметь возможность отслеживать новые входные данные и действовать соответственно.
Я решил использовать конечный автомат для хранения нажатия кнопки, разделив функцию сна на 4 или 8 интервалов, примерно так:
while (is_time_set){
system("clear");
printf("########\n");
printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
printf("########\n");
for (int i = 0; i<8; i++){
if (state_machine_input == 1){state_machine_input = 0; break;}
sleep(1/8);
}
update_timer(&timer, &is_time_set);
Это можно сделать, но я был бы признателен, если бы мне не пришлось усложнять проект, например, добавляя еще один конечный автомат. Что я мог сделать в программном обеспечении для реализации этой функции?
Ответы
Потоки - это концепция более высокого уровня, чем программирование микроконтроллеров. Проще говоря, потоки реализованы как планировщик, который использует прерывания таймера, который, в свою очередь, сохраняет счетчик программы + указатель стека и т. Д. И устанавливает их в разные места. Таким образом, вполне возможно и легко реализовать аналогичную концепцию с использованием прерываний - с тем преимуществом, что вы получаете специализированные прерывания вместо универсальной многопоточности.
Это почти единственный разумный способ сделать это с ограниченным устаревшим биттером 8, таким как PIC, который чрезвычайно ограничен, когда дело доходит до использования стека. Забудьте об использовании библиотек потоков, даже написанных для микроконтроллеров. Это только добавит чрезмерной раздутости и сложности, ничего не получится. В общем, переносить концепции программирования ПК во встраиваемый мир - плохая идея.
Что вам нужно сделать, так это поместить сканирование кнопок в прерывание циклического таймера, которое выполняется один раз в 10 мс или около того. Изнутри прерывания вы опрашиваете кнопки и сравниваете прочитанную кнопку с предыдущей в целях устранения неполадок. Результат этого сохраняется в переменной, совместно используемой с основной программой, объявленной как volatile
состояние гонки и защищенной от нее. Поскольку вы пишете в переменную только изнутри прерываний, это может быть достаточной защитой, чтобы гарантировать, что чтение составляет 8 бит, но вы должны дизассемблировать, чтобы быть уверенным. Более подробная информация о том , что здесь: Использовании летучего в разработках встраиваемого C .
Используйте прерывания
Вы хотите запустить какой-то код при нажатии кнопки? Используйте прерывание при смене вывода
Вы хотите что-то делать с фиксированным интервалом? Используйте прерывание по таймеру
В некотором смысле аппаратное обеспечение микроконтроллера запускает «поток», который контролирует источники прерываний и запускает «обратный вызов» или процедуру прерывания для каждого события.
Основная программа автоматически приостанавливается при выполнении прерывания.
Распространенный способ обмена данными между прерываниями и основным кодом - через volatile
глобальные переменные и временное отключение прерываний при чтении данных из этих глобальных объектов, когда они превышают размер слова контроллера (почти всегда на 8-битном контроллере)
Я бы, наверное, предложил совместную библиотеку многозадачности. Раньше я использовал Protothreads:http://www.dunkels.com/adam/pt/
Любая приличная совместная библиотека многозадачности поможет абстрагироваться от неявного конечного автомата, необходимого для отслеживания вещей.
Удачи.
Когда дело доходит до встроенной системы, существуют разные подходы к многозадачности:
- Опрос или совместная многозадачность : все выполняется в одном бесконечном цикле, и задачи разработаны таким образом, чтобы занимать минимально возможное время и возвращаться к основному выполнению как можно быстрее, чтобы избежать задержек. Обратите внимание, что задачи, подходящие для этой архитектуры, могут быть не такими, как вы думаете с точки зрения концепции более высокого уровня, например, в вашем приложении может быть одна задача, а может быть
update_display
другая задача,check_button
и вы должны построить цикл, например:
while(1){
check_buttons();
update_display();
sleep(0.1); //seconds
}
Прерывания : все возможные входы, связанные с аппаратными прерываниями, и основное выполнение оставлено для вещей, которые не могут быть помещены в прерывание (может быть ничего, и в этом случае обычно микроконтроллер переводится в спящий режим для снижения энергопотребления. Подробнее о том, как это выполняется обычно в зависимости от конкретного используемого микроконтроллера и компилятора.
ОСРВ : в зависимости от мощности микроконтроллера можно запустить операционную систему реального времени (ОСРВ), которая может предоставлять API для создания задач или даже потоков. Это зависит от приложения и возможностей оборудования, и для учебных примеров не обязательно (или желательно, imo)
Учтите также, что еще одна важная часть в принятии решения об общей архитектуре приложения - это разделение задач и способы их взаимодействия. Одна из используемых парадигм - это State Machines (ссылка ведет на общую страницу википедии, которая может быть огромной; более простые ресурсы, относящиеся к встроенному программированию, можно найти в вашем учебнике или в Google).
В основном в 8-битных устройствах есть ограниченный исходный код. Я думаю, что более простое решение - лучшее решение для 8-битных PIC.
Вы можете создавать аппаратные таймеры для двух разных задач. Установите флаг и проверьте флаг в своем бесконечном цикле, выполните задачу и сбросьте флаг. Не используйте задержки. Этот метод гарантирует выполнение ваших задач в вашем бесконечном цикле, если ваш флаг поднят.
Но вы должны знать, что задачи не выполняются точно в то время, когда установлены флажки. Если было установлено два флага одновременно, вы не можете знать, какой из них выполняется первым. Потому что вы не знаете, где находится бесконечный цикл. Но в основном это нормально для приложений, не критичных по времени.