Программирование устройства. Проектирование конечного автомата

Электроника

Давайте рассмотрим процесс программирования нашего устройства на основе конечного автомата. На этом занятии мы пройдем все этапы программирования микроконтроллера
Глоссарий
Для успешного освоения материала рекомендуем вам изучить следующие понятия:
Внешние события
взаимодействие с пользователем или с другими устройствами
Внутренние события
обычные изменения переменных в самом микроконтроллере
Микроконтроллер
микросхема, предназначенная для управления электронными устройствами. Типичный микроконтроллер сочетает на одном кристалле функции процессора и периферийных устройств, содержит ОЗУ и (или) ПЗУ. По сути, это однокристальный компьютер, способный выполнять относительно простые задачи. Отличается от микропроцессора интегрированными в микросхему устройствами ввода-вывода, таймерами и другими периферийными устройствами
Видеолекция
Конспект
Давайте рассмотрим программирование нашего устройства на основе конечного автомата. Мы пройдем все этапы программирования микроконтроллера

Этапы
  1. Определение состояний
  2. Определение событий
  3. Переходы между событиями
  4. Действия по переходам из состояния в состояние
  5. Формирование шаблона программы на основе автомата
Конечный автомат
Логику функционирования нашего устройства удобно представить в виде конечного автомата. Он в один момент времени находится в одном состоянии.
Важно
Количество состояний может быть огромным, но при этом их конечное число
Переходы между состояниями осуществляются через события. При этом для нашего устройства события могут быть внутренними и внешними.
Важно
Внешними событиями является взаимодействие с пользователем или с другими устройствами, а внутренними — обычное изменение переменных в самом микроконтроллере
Нашей целью является определение событий и состояний, а также переходов между событиями и действия при переходе из одного состояния в другое
Определение событий для часов
  1. Кнопка энкодера нажата (обозначим p)
  2. Кнопка энкодера отжата (пока не будем использовать)
  3. Энкодер повернут по часовой стрелке (I)
  4. Энкодер повернут против часовой стрелки (D)
  5. Сработал будильник (A)
  6. Таймаут сигнализации (ТА)
  7. Сон по неактивности (SE)
  8. Активность по сенсору (AS)
  9. Нет событий (N)
Окошки
Взаимодействие пользователя с часами осуществляется с помощью окошек. Для каждого окошка свой список состояний. Каждое из них обозначим S с номером.
  • Первое окошко — отображение циферблата
  • Второе окошко — настройка времени
  • Третье окошко — установка будильника
  • Четвертое окошко — срабатывание будильника
  • Пятое окошко — данные от аккумулятора и сенсора положения
Представление в виде графа
  • Состояние S1 переходит в состояние S2 при событии P (кнопка нажата)
  • Из S2 переходим в S3 при событии I. Т. е. при повороте энкодера по часовой на 1 время меняется на 1
  • Если сработало событие D, то состояние S2 перейдет уже в S4, т. е. часы уменьшились на 1
  • Если произошло событие P, т. е. энкодер нажат, то переходим в S5
  • Если из S1 произошло событие SL, то происходит переход в S14, т. е. «спящее состояние», но если возникает любая активность пользователя (AS), то мы возвращаемся в S1

И так далее. Все состояния соединяются стрелками с условием (событием) или без. Нажимая энкодер мы по кольцу пройдем все экраны нашего устройства.
Важно
Практически для всех состояний есть возвратные стрелочки. То есть состояние возвращается само в себя (по сути не меняется), либо по тику программы, либо в бесконечном цикле
Представление конечного автомата
1. Бесконечный цикл While, в котором будут вызываться диспетчер событий, изменение состояний и действия по состоянию

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

3. По выбранному событию происходит смена состояния

4. На основе переходов решаем, какое действие вызвать

5. Далее возвращаемся по циклу и повторяем все пункты
Написание шаблона алгоритма в программе
1. Открываем проект, сформированный для тестирования модулей устройства

2. Описываем события. Указываем название каждого события и создаем переменную для текущего события

3. Описываем все используемые состояния и создаем переменную для текущего события
char str7[20]={0};
char str8[20]={0};
char str9[20]={0};

char ss[32];

enum SIGNAL
{
	encoder_push,
	encoder_push_int,
	encoder_poll,
	encoder_inc,
	encoder_dec,
	start_alarm,
	timeout_alarm,
	no_active_to_sleep,
	sensor_to_active,
	nosignal
};

enum SIGNAL signal=nosignal;

enum STATE
{
	AnalogCurrentTime,
	SetTimeHours,
	SetTimeHoursInc,
	SetTimeHoursDec,
	SetTimeMinutes,
	SetTimeMinutesInc,
	SetTimeMinutesDec,
	SetAlarmHours,
	SetAlarmHoursInc,
	SetAlarmHoursDec,
	SetAlarmMinutes,
	SetAlarmMinutesInc,
	SetAlarmMinutesDec,
	Sleep,
	IMU,
	Alarm,
	Wake,
	NoState
};

volatile enum STATE state=NoState;
4. В цикле While на основе предложенного алгоритма записываем код
while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

enum STATE state_old;
	  signal= getsignal();
	  state_old = state;
	  state= change_state(&signal, state);
	  DO_work(state, state_old);

  }
  /* USER CODE END 3 */
}
  • getsignal получает на вход флаг и формирует сигнал, например, «энкодер нажат»
enum SIGNAL getsignal(void)
{


	if(btn_try_push==1 || btn_try_push_int==1)
	{
		btn_try_push=0;
		btn_try_push_int=0;
		signal = encoder_push;
		return signal;
	}

	if(try_sensor_to_active==1)
	{
		try_sensor_to_active=0;
		signal = sensor_to_active;
		return signal;
	}

	if(enc_try_inc==1)
	{
		enc_try_inc=0;
		signal = encoder_inc;
		return signal;
	}

	if(enc_try_dec==1)
	{
		enc_try_dec=0;
		signal = encoder_dec;
		return signal;
	}

	if(signal_timeout_sleep==1)
	{
		signal_timeout_sleep=0; //priority
		return signal=no_active_to_sleep;
	}

	if(signal_alarm==1)
	{
		signal_alarm=0; //priority
		return signal=start_alarm;
	}

	if(signal_stop_alarm==1)
	{
		signal_stop_alarm=0; //priority
		return signal=timeout_alarm;
	}

	return signal;
}
Важно
Нажатие кнопки на энкодере имеет максимальный приоритет, отключение будильника — минимальный
  • change_state получает на вход текущее состояние и текущий сигнал, а на выходе формирует новое состояние. На основе состояния вызывает нужный метод
enum STATE change_state(enum SIGNAL *signal, enum STATE state)
{

	switch(state)
	{//by state
		case AnalogCurrentTime:
			state=change_state_from_AnalogCurrenTime(signal, state);
			break;
		case SetTimeHours:
			state=change_state_from_SetTimeHours(signal, state);
			break;
		case SetTimeMinutes:
			state=change_state_from_SetTimeMinutes(signal, state);
			break;
		case SetAlarmHours:
			state=change_state_from_SetAlarmHours(signal, state);
			break;
		case SetAlarmMinutes:
			state=change_state_from_SetAlarmMinutes(signal, state);
			break;
		case IMU:
			state=change_state_from_IMU(signal, state);
			break;
		case Alarm:
			state=change_state_from_Alarm(signal, state);
			break;
		case Sleep:
			state=change_state_from_Sleep(signal, state);
			break;
		case SetTimeHoursInc:
			state=change_state_from_SetTimeHoursInc(signal, state);
			break;
		case SetTimeHoursDec:
			state=change_state_from_SetTimeHoursDec(signal, state);
			break;
		case SetTimeMinutesInc:
			state=change_state_from_SetTimeMinutesInc(signal, state);
			break;
		case SetTimeMinutesDec:
			state=change_state_from_SetTimeMinutesDec(signal, state);
			break;
		case SetAlarmHoursInc:
			state=change_state_from_SetAlarmHoursInc(signal, state);
			break;
		case SetAlarmHoursDec:
			state=change_state_from_SetAlarmHoursDec(signal, state);
			break;
		case SetAlarmMinutesInc:
			state=change_state_from_SetAlarmMinutesInc(signal, state);
			break;
		case SetAlarmMinutesDec:
			state=change_state_from_SetAlarmMinutesDec(signal, state);
			break;
		case NoState:
			state=change_state_from_NoState(signal, state);
			break;
		case Wake:
			state=change_state_from_Wake(signal, state);
			break;
		default:
			state=change_state_from_NoState(signal, state);
			break;
	}
	return state;
}
  • DO_work рассматривает новое и предыдущее состояния и вызывает метод действий
void DO_work(enum STATE state, enum STATE state_old)
{
	switch(state)
		{//by state
			case AnalogCurrentTime:
				draw_analog_watch(state, state_old);
				break;
			case SetTimeHours:
				draw_settime_hours(state, state_old);
				break;
			case SetTimeMinutes:
				draw_settime_minutes(state, state_old);
				break;
			case SetAlarmHours:
				draw_setalarm_hours(state, state_old);
				break;
			case SetAlarmMinutes:
				draw_setalarm_minutes(state, state_old);
				break;
			case IMU:
				draw_IMU(state, state_old);
				break;
			case Alarm:
				draw_alarm(state, state_old);
				break;
			case Sleep:
				to_sleep(state, state_old);
				break;
			case Wake:
				to_wake(state, state_old);
				break;
			case SetTimeHoursInc:
				inc_draw_settime_hours(state, state_old);
				break;
			case SetTimeHoursDec:
				dec_draw_settime_hours(state, state_old);
				break;
			case SetTimeMinutesInc:
				inc_draw_settime_minutes(state, state_old);
				break;
			case SetTimeMinutesDec:
				dec_draw_settime_minutes(state, state_old);
				break;
			case SetAlarmHoursInc:
				inc_draw_setalarm_hours(state, state_old);
				break;
			case SetAlarmHoursDec:
				dec_draw_setalarm_hours(state, state_old);
				break;
			case SetAlarmMinutesInc:
				inc_draw_setalarm_minutes(state, state_old);
				break;
			case SetAlarmMinutesDec:
				dec_draw_setalarm_minutes(state, state_old);
				break;
			default:

				break;
		}

}
Мы сделали шаблон, которым можно будет пользоваться для многих устройств, а также рассмотрели проектирование конечного автомата. Давайте перейдем к заданиям.
Интерактивное задание
Тест
Для закрепления полученных знаний пройдите тест
Стартуем!
Что является внутренним событием?
Дальше
Проверить
Узнать результат
Как обозначается поворот энкодера по часовой стрелке?
Дальше
Проверить
Узнать результат
Что делает DO_work ?
Дальше
Проверить
Узнать результат
К сожалению, вы ответили неправильно
Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз
Неплохо!
Но можно лучше. Прочитайте лекцию и посмотрите видео еще раз
Пройти еще раз
Отлично!
Вы отлично справились. Теперь можете ознакомиться с другими компетенциями
Пройти еще раз