Алгоритм организации задержки на Systick

Алгоритм борьбы с дребезгом

При нажатии на кнопку в реальных условиях контакт происходит не мгновенно и происходит несколько замыканий-размыканий в результате которых несколько раз за единицы миллисекунд сменяются значения нуля и единицы на выходе кнопки. Из-за этого явления задача обнаружения нажатия кнопки становится не тривиальной.

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

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

uint8_t button_state=1;//Кнопка отжата.

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

#define LED_INV() togglebit(GPIOD, GPIO_pin_14);

#define BUTTON_READ() read_bit(GPIOA,GPIO_pin_0);

Теперь напишем саму реализацию:

if (BUTTON_READ() == 0)//Кнопка нажата

{

if (button_state == 1)

{

LED_INV();

button_state = 0;

}

}

else

{

if (button_state == 0)

{

button_state = 1;

}

}

 

В этой программе мы проверяем нажата ли кнопка. Если нажата (на ножке установлен 0), то мы проверяем переменную, в которую записываем состояние кнопки. Если состояние реальной кнопки а нашей переменной отличаются, то делается вывод, что это момент изменения состояния кнопки и светодиод меняет своё состояние. Сама же переменная принимает значение реальной кнопки.

Но эта программа никак не учитывает дребезг. Допишем переменную, которая будет уменьшаться до 0, если кнопка отжата и увеличиваться до заданного порога, если кнопка нажата.

uint8_t button_count=0;

if (BUTTON_READ() == 0)//Кнопка нажата

{

if (button_count < 10) //счёт до порога

{

button_count++;

} else if (button_state == 1) //Срабатывает если кнопка была отжата

{

LED_INV();

button_state = 0;

}

}

else // кнопка отжата

{

if (button_count > 0) //счёт до 0

{

button_count--;

}

else if (button_state == 0) //Срабатывает если кнопка была нажата

{

button_state = 1;

}

}

 

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

 

Алгоритм организации задержки на Systick

Systick – это системный таймер, периодически выдающий прерывания через заданные промежутки времени. Это свойство таймера позволяет создавать задержки не загружая АЛУ. Рассмотрим для начала реализацию функции задержки на systick.

 

 

uint16_t delay_count=0;

void Systick_Handler(void)

{

if (delay_count > 0)

{

delay_count--;

}

}

 

void delayMS(uint16_t delay_data)

{

delay_count = delay_data;

while(delay_count){};

}

 

Аналогично можно реализовать задержку без функции delayMS. Для этого по окончанию отсчёта надо устанавливать флаг окончания ожидания. Причём устанавливать флаг в единицу надо когда уменьшаемая в Systick_Handler переменная равна 1, а не 0. Т.к., если устанавливать её при переменной равной нулю, флаг будет устанавливаться бесконечно. В итоге функция обработки прерывания выглядит примерно так:

uint16_t delay_count=0;

uint8_t delay_flag=0;

void Systick_Handler(void)

{

if (delay_count > 1)

{

delay_count--;

} if (delay_count == 1)

{

delay_flag = 1;

delay_count--;

}

}

 

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

В функции main тогда мигание светодиодом будет выглядеть так:

 

int main(void)

{

delay_count =501;

 

while(1)

{

if (delay_flag == 1)

{

delay_flag = 0;

LED_INV();

delay_count =501;

}

}

}

 

Как видно в программе проверяется delay_flag. Если флаг равен 1, то выполняется следующие действия:

1) Сбрасывается флаг.

2) Инвертируется состояние светодиода.

3) Устанавливается новый отрезок времени записью значения отличного от 0 в переменную delay_count. Стоит заметить, что следует задавать значение на 1 большее, т.е. флаг устанавливается на 1, а не на 0.

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

Алгоритм TimeOut.

В USART бывают случаи когда приходит пакет не целиком. В это случае его необходимо сбросить и ожидать прихода следующего. Это делается по алгоритму, аналогичному тому, что описан ранее. По приёму байта данных устанавилвается переменная в какое-то значение, большее 1. Если приходят новые байты данных, то величина этой переменной обновляется. Обновление необходимо производить, если не предпологается приход всего пакета за заданный отрезок времени. Когда принят последний байт пакета данных, переменная программно сбрасывается в ноль и сброса пакета по Timeout (превышению времени ожидания) не происходит, т.к. флаг устанавливается при значении равном 1.

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

Например:

 

uint8_t USART_data[256];

uint8_t USART_data_write=0;

uint8_t USART_data_read=0;

uint8_t USART_timeout_count=0;

uint8_t USART_timeout_flag=0;

 

//---------------------------------

void USART_Handler(void)

{

USART_data[USART_data_write] = USART_Read_Data(USART3);

USART_data_write++;

if (USART_data_write == 20)

{

USART_timeout_count = 0;//выключили

} else {

USART_timeout_count = 200;//200 мс

}

 

}

 

//---------------------------------

void Systick_Handler(void)

{

if (USART_timeout_count > 1)

{

USART_timeout_count --;

} else if (USART_timeout_count == 1)

{

USART_timeout_count --;

USART_timeout_flag = 1;

}

}

 

//---------------------------------

int main(void)

{

while(1)

{

if (USART_timeout_flag == 1)

{

USART_data_write = 0;

}

}

}

 

Из этой программы видно, что в функции USART_Handler принимаются данные. При этом каждый раз обновляется счётчик для времени ожидания. При принятии же 20-го байта (Предполагается, что в пакете 20 байт), счётчик сбрасывается в 0.

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

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