есть ли способ дождаться чего-то, не прерывая выполнение другого кода?
я должен написать функцию, которая, вызвав ее только один раз, должна:
включить выходной пин
контакт остается высоким в течение 200 мс
в конце таймера штифт должен снова стать низким.
контакт остается низким в течение 200 мс
по истечении таймера функция может быть вызвана снова.
для включения и выключения выходного контакта я уже написал и протестировал функции:
outOn(pin_id);
outOff(pin_id);
теперь я пытаюсь написать функцию, которая выполняет вышеупомянутые действия, и вот что у меня получилось:
void outOnT02(enum e_outs ou){
outOn(ou);
gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2;
if(gu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer) {
outOff(ou);
}
}
функция названаoutOnT02
потому что:
- это выход;
- после его вызова булавка стала высокой;
- T02, потому что контакт остается высоким в течение 0,2 секунды.
outOn(ou);
заставляет булавку идти высоко,
outOff(ou);
заставляет штифт идти низко,
gu_RegTim.BTime[BTIM_FUNCT].Timer = O_SEC01*2;
запускает таймер на 200 мс,
и верно, когда таймер истек.
это работает, но, как вы понимаете, в противном случае я должен поставить его в циклgu_RegTim.BTime[BTIM_FUNCT].b.Stato == O_EndTimer
никогда не будет правдой, и поэтому булавка останется высокой навсегда.
вот где я застрял. я не могу использоватьSLEEP(200);
потому что я не могу прервать выполнение кода.
язык — C, ide — MPLAB X IDE v6.00, компилятор — XC8 v2.31, а процессор — PIC16F15355.
1 ответ
Этот пост немного устарел, но на него стоит ответить, поскольку это и хороший вопрос, и распространенная проблема. Теперь эта проблема очень распространена в мире встраиваемых систем, когда мы разрабатываем приложения, которые должны работать только на одном процессоре. Следовательно, в рабочем процессе нет реального параллелизма. Кроме того, поскольку приложение не будет работать поверх какой-либо ОС, не будет планировщика, таймеров, потоков и т. д. Особенно в микроконтроллерах небольшого масштаба нет возможности запуска многих настоящих ОСРВ.
Но это не должно быть препятствием для разработки приложений, выполняющих задачи одновременно. Мы можем разработать приложение, используя некоторые приемы, чтобы оно выполняло задачи одновременно и вело себя как небольшая ОС. Выполнение одновременно означает, что никакая задача не блокирует ЦП, используя проверки ожидания занятости или что-то подобное, но мы блокируем задачу, которая должна ожидать возникновения некоторого события.
Когда мы блокируем задачу, определенные данные и следующая точка выполнения этой задачи должны быть сохранены, чтобы она могла продолжиться с того места, где должна быть, при следующем выполнении. Знание того, что нам нужно сохранить, помогает нам создавать структуры, похожие на потоки, которые выполняются до тех пор, пока им не придется ждать какого-либо события (например, временной задержки). Когда он должен ждать (это означает, что он будет заблокирован), его следующее состояние должно быть сохранено, и он выходит, чтобы передать управление ЦП, чтобы он выполнял другие задачи.
Когда нам нужно иметь дело с периодическими задачами, как в вопросе, это относительно проще реализовать, не блокируя выполнение ЦП, и тем временем обрабатывать другие задачи. Кроме того, для задач этого типа не требуется использование прерываний, если только задачи не являются чрезвычайно чувствительными ко времени.
Ну, хватит с сюжетной частью, давайте приступим к делу. Я буду основывать примеры на проблеме перепрошивки вывода OP. Однако те же методы можно применять и для других ситуаций, таких как события ввода-вывода, аппаратные события и т. д.
Давайте кратко суммируем требования: у нас есть задача, которая выполняется атомарно. То есть, когда он вызывается, он должен выполняться до завершения, чтобы его можно было вызвать снова (это то, что я понимаю из требования ОП):
- Включает выходной контакт на 200 мс
- Затем отключает контакт на 200 мс.
- После выключения и по истечении 200 мс его можно запустить снова.
Примечание. Некоторые функции в этом примере не реализованы, поскольку они могут зависеть от приложения или микроконтроллера.
Задачно-подобные функции
Предположим, мы хотим запланировать следующие две задачи, каждая из которых отслеживает точки продолжения выполнения.
Статические переменные объявлены в каждой функции, чтобы они помнили, с чего продолжать, всякий раз, когда они вызываются. Содержимое переменной не будет уничтожено компилятором при возврате функции, поскольку мы объявляем ее статической.
Обратите внимание, что в
/*
* This task-like function performs what the OP wants to achieve
*/
void outputTask(unsigned char periodic) {
static unsigned char cp = 0; // Continuation Point holder
static unsigned char currentMillis;
/*
* Check whether it is a periodic call or a new output signal call.
* If it is a periodic call and signalling has been initialized,
* proceed for time keeping.
* If it is a new signalling call and the task hasn't completed yet,
* simply ignore and return.
*/
if(!periodic && cp != 0) {
return;
}
switch(cp) {
case 0:
outOn(pin_id); // Turn on the output
cp = 1; // Next execution point
currentMillis = 200; // Load the 200ms counter for time keeping
break;
case 1:
currentMillis--;
if(currentMillis == 0) {
// 200ms time for output high has elapsed, proceed to next step
outOff(pin_id); // Turn off the output
currentMillis = 200; // Reload the counter value
cp = 2; // Proceed to the next step
}
break;
case 2:
currentMillis--;
if(currentMillis == 0) {
// 200ms time for output low has elapsed, proceed to next step
cp = 0; // Last step is done, reset the state for new calls
}
break;
default:
// For anything else, reset the task state to the initials
cp = 0 // Reset the task state to zero so that it accepts new calls
}
}
/*
* Let's say this task will wait for a button press event and will
* trigger the outputTask upon the event occurs
*/
void outputTriggerTask() {
static unsigned char cp = 0;
static unsigned char currentMillis;
switch(cp) {
case 0:
if(isButtonPressed()) { // Platform specific function
// A button press has been detected, debounce first
currentMillis = 50;
cp = 1; // Next step, check for the elapsed time
}
else {
break;
}
case 1:
currentMillis--;
if(currentMillis == 0) {
// Check whether the button press is consistent
if(isButtonPressed()) {
// Yes still consistent, handle the button press by triggering the output task
outputTask(0); // Not a periodic call
cp = 2; // Next step is to check whether button is released
}
else {
cp = 0; // Reset the task state
}
}
break;
case 2:
if(isButtonReleased()) { // Platform specific function
currentMillis = 50; // Reload the time counter
cp = 3;
}
else {
break;
}
case 3:
currentMillis--;
if(currentMillis == 0) {
// Check whether the button release is consistent
if(isButtonReleased()) {
// Yes still consistent, handle the button release if needed
cp = 0; // Reset the task to its initial state
}
}
break;
default:
cp = 0; // Reset to initials
}
}
Подходы к планированию
Следующие подходы предназначены для небольших встраиваемых систем, отличных от RTOS. Они подходят для широкого спектра 8-битных микроконтроллеров.
Подход 1 — создание временной базы на основе задержки для планирования задач
Планирование с использованием задержки блокировки ЦП подходит для хобби и образовательных целей, но не подходит для реальных проектов. В этом примере используется специфичная для платформы
void main(void) {
systemInit(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// The first thing we do is to create a 1ms timebase using delay.
// This is the heartbeat for the application
delay_ms(1000); // Platform specific function
// 1ms has elapsed check the tasks
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}
Подход 2 — создание временной базы на основе аппаратного таймера
void main(void) {
systemInit(); // Platform specific function
// Setup a hardware timer for 1ms overflow without interrupt
initTimerForOneMs(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Wait for the timer to overflow
while(!isTimerOverflow()) // Platform specific function
;
// Timer has overflowed, reload and check tasks
reloadTimer(); // Platform specific function
// 1ms has elapsed check the tasks
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}
Подход 3. Перевод процессора в спящий режим на временную развертку 1 мс
void main(void) {
systemInit(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Put the Processor to sleep along with a watchdog timer to wake it up
clearWatchdogTimer(); // Platform specific function
sleep(); // Platform specific function
// CPU slept for 1ms and woke up, handle the periodic tasks
outputTriggerTask(); // Check whether any button press event has occured
clearWatchdogTimer(); // Platform specific function
outputTask(1); // It is a periodic call for the output task
clearWatchdogTimer(); // Platform specific function
// Maybe more tasks go here...
}
}
И последнее, но не менее важное: подход к проверке времени
В этом подходе задачи будут сохранять время, проверяя, лучше сказать, сравнивая прошедшее время с желаемым временем, чтобы отложить задачи, не блокируя ЦП. Для этого нам нужно будет использовать бесплатный таймер. Это будет похоже на
Переписывание задач для подхода с проверкой времени
/*
* This task-like function performs what the OP wants to achieve
*/
void outputTask(unsigned char periodic) {
static unsigned char cp = 0; // Continuation Point holder
static unsigned short currentMillis; // 16 bit millisecond holder
/*
* Check whether it is a periodic call or a new output signal call.
* If it is a periodic call and signalling has been initialized,
* proceed for time keeping.
* If it is a new signalling call and the task hasn't completed yet,
* simply ignore and return.
*/
if(!periodic && cp != 0) {
return;
}
switch(cp) {
case 0:
outOn(pin_id); // Turn on the output
cp = 1; // Next execution point
currentMillis = getCurrentMillis(); // Platform specific function
break;
case 1:
if(getCurrentMillis() - currentMillis >= 200) {
// 200ms time for output high has elapsed, proceed to next step
outOff(pin_id); // Turn off the output
currentMillis = getCurrentMillis(); // Reload the counter value
cp = 2; // Proceed to the next step
}
break;
case 2:
if(getCurrentMillis() - currentMillis >= 200) {
// 200ms time for output low has elapsed, proceed to next step
cp = 0; // Last step is done, reset the state for new calls
}
break;
default:
// For anything else, reset the task state to the initials
cp = 0 // Reset the task state to zero so that it accepts new calls
}
}
/*
* Let's say this task will wait for a button press event and will
* trigger the outputTask upon the event occurs
*/
void outputTriggerTask() {
static unsigned char cp = 0;
static unsigned short currentMillis;
switch(cp) {
case 0:
if(isButtonPressed()) { // Platform specific function
// A button press has been detected, debounce first
currentMillis = getCurrentMillis(); // Platform specific function
cp = 1; // Next step, check for the elapsed time
}
else {
break;
}
case 1:
if(getCurrentMillis() - currentMillis >= 50) {
// Check whether the button press is consistent
if(isButtonPressed()) {
// Yes still consistent, handle the button press by triggering the output task
outputTask(0); // Not a periodic call
cp = 2; // Next step is to check whether button is released
}
else {
cp = 0; // Reset the task state
}
}
break;
case 2:
if(isButtonReleased()) { // Platform specific function
currentMillis = getCurrentMillis();
cp = 3;
}
else {
break;
}
case 3:
if(getCurrentMillis() - currentMillis >= 50) {
// Check whether the button release is consistent
if(isButtonReleased()) {
// Yes still consistent, handle the button release if needed
cp = 0; // Reset the task to its initial state
}
}
break;
default:
cp = 0; // Reset to initials
}
}
Планировщик для подхода к проверке времени
void main(void) {
systemInit(); // Platform specific function
initMillisTimerWithInterrupt(); // Platform specific function
// maybe some more init functions go here
// Application's infinite scheduler loop
while(1) {
// Now that we use a free running millis timer no need to block the CPU to create a timebase
// Just call tasks sequentially. Each task will know what to do individually
outputTriggerTask(); // Check whether any button press event has occured
outputTask(1); // It is a periodic call for the output task
// Maybe more tasks go here...
}
}