В прошлом эксперименте мы нарисовали интерфейс секундомера. Вывели симпатичную картинку и нули вместо часов, минут и секунд. Теперь нужно «оживить» этот прототип, заставить его считать время.
Первый вопрос, который возникает — как отсчитывать время? Как вариант, знакомая нам функция time.sleep(1), которая дает задержку на 1 секунду. Алгоритм работы получается простой. Завели переменную с секундами, которая в начале равна нулю. Отображаем ее значение на дисплее и делаем паузу на секунду. Потом увеличиваем значение переменной на 1 и отображаем новое значение. И так дальше.
Приведенный выше алгоритм простой, но не правильный. Дело в том, что на отображение данных на дисплее уходит время помимо паузы на 1 секунду и такой секундомер не будет считать правильно. Для данной задачи есть более верное решение.
Системная библиотека time
предлагает функцию ticks_ms()
, которая возвращает количество миллисекунд с начала запуска микроконтроллера. Это значение обновляется аппаратно по прерыванию от таймера, который настроен точно и не зависит от действий программы. Поэтому лучше ориентироваться именно на это значение.
При запуске таймера мы будем запоминать текущее значение ticks_ms()
и следить за его изменением.
Рисунок 1. Монтажная схема эксперимента для дисплея с 8 выводами
Рисунок 2. Монтажная схема эксперимента для дисплея с 11 выводами
class Stopwatch: def __init__(self): self.hours = 0 # Значение часов self.mins = 0 # Значение минут self.secs = 0 # Значение секунд self.start_time = 0 # Время запуска self.state = 0 # Состояние. 0 - секундомер не запущен, 1 - запущен self.backcolor = tft.rgbcolor(255,251,240) # Цвет фона self.fontcolor = tft.rgbcolor(39,40,51) # Цвет шрифта self.draw_init() # Вызов функции отображения начального значения- нулей # Функция вывода начального значения секундомера- нулей def draw_init(self): self.draw_value(98, 132, 2, 0) # Выводим на дисплей 0 секунд self.draw_value(55, 125, 3, 0) # Выводим на дисплей 0 минут self.draw_value(5, 125, 3, 0) # Выводим на дисплей 0 часов # Выводим разделитель часов и минут tft.text(41, 125, ":", font.terminalfont, self.fontcolor, 3) # Выводим разделитель минут и секунд tft.text(90, 130, ":", font.terminalfont, self.fontcolor, 2) # Основная функция секундомера, отсчет времени def tick(self): if self.state: # отсчет производим только, если секундомер запущен # Находим разницу в миллисекундах между текущим временем и временем запуска diff = time.ticks_diff(time.ticks_ms(), self.start_time) hours = diff // (1000 * 60 * 60) # Деление без остатка. Находим сколько целых часов прошло diff = diff % (1000 * 60 * 60) # Находим остаток после деления на часы и сохраняем в diff mins = diff // (1000 * 60) # Деление без остатка. Находим сколько целых минут прошло diff = diff % (1000 * 60) # Находим остаток после деления на минуты и сохраняем в diff secs = diff // (1000) # Деление без остатка. Находим сколько целых секунд прошло # Если сохраненное ранее значение секунд отличается от вычисленных- обновляем и выводим на дисплей if secs != self.secs: self.secs = secs self.draw_value(98, 132, 2, secs) # Если сохраненное ранее значение минут отличается от вычисленных- обновляем и выводим на дисплей if mins != self.mins: self.mins = mins self.draw_value(55, 125, 3, mins) # Если сохраненное ранее значение часов отличается от вычисленных- обновляем и выводим на дисплей if hours != self.hours: self.hours = hours self.draw_value(5, 125, 3, hours) # Функция запуска и остановки секундомера def start_stop(self): if self.state: # Если секундомер запущен- останавливаем self.state = 0 else: # Иначе- запускаем self.state = 1 self.start_time = time.ticks_ms() # Сохраняем время запуска секундомера # Функция отображения на дисплее значений def draw_value(self, x, y, size, value): # Перед выводом на дисплей новых данных закрашиваем старые прямоугольником с цветом фона tft.rect(x+2, y, 6 * size * 2, 8 * size -2, self.backcolor) # Если значение для вывода менее 10, то добавляем ведущий ноль, чтобы всегда было 2 знака # конвертируем значение из числа в строку if value < 10: value = '0' + str(value) else: value = str(value) # Выводим значение в виде строки на дисплей в нужном месте нужным цветом и размером tft.text(x, y, value, font.terminalfont, self.fontcolor, size)
Класс содержит конструктор init
и методы:
draw_init
— отображает начальное значение таймера, нули, как в прошлом эксперименте.start_stop
— запускает и останавливает секундомер. draw_value
— выводит значение часов минут или секунд. В качестве параметров принимает координаты, размер шрифта и значение.tick
— системная функция секундомера. Она определяет сколько часов, минут и секунд прошло с момента его запуска. Эту функцию необходимо вызывать постоянно.Код класса снабжен комментариями, подробно описывающими почти каждую строку. Кроме этого дадим несколько пояснений. Когда мы говорим «Сохраняем время запуска секундомера», то имеется в виду, что мы сохраняем значение количества миллисекунд с момента запуска микроконтроллера. И все упоминания времени следует читать в этом ключе.
Функция diff = time.ticks_diff(time.ticks_ms(), self.start_time)
нужна для нахождения разницы между количеством миллисекунд, прошедших со старта микроконтроллера при запуске секундомера и сейчас. Несмотря на то, что это есть обычное число, которое можно было бы вычесть обычным знаком минус, лучше это делать с помощью данной функции. Дело в том, что это значение может переполниться и обнулиться и начать считать «по второму кругу». Тогда простое вычитание даст ошибку. Только функция ticks_diff
гарантирует правильное значение.
Заострим внимание на вычислении количества часов:
hours = diff // (1000 * 60 * 60) diff = diff % (1000 * 60 * 60)
Оператор //
— это оператор деления без остатка. Например 5 // 2 = 2. А остаток 0.5 был отброшен.
А оператор %
— напротив возвращает только остаток от деления. 5 % 2 = 0.5
Сначала мы вычисляем целое количество часов, а потом мы вычисляем по сути сколько осталось после выделения целого количества часов. После этого там останется значение меньше одного часа. Его мы уже будем делить на минуты. А после вычитания минут будем определять сколько осталось секунд.
Теперь сделаем программу с использованием данного класса
from machine import Pin, SPI from tft import TFT_GREEN import font import time _init() machine.freq(160000000) dc = Pin(4, Pin.OUT) cs = Pin(2, Pin.OUT) rst = Pin(5, Pin.OUT) spi = SPI(1, baudrate=40000000, polarity=0, phase=0) tft = TFT_GREEN(128, 160, spi, dc, cs, rst, rotate=0) tft.initr(tft.BGR) # tft.initr(tft.RGB) #Если вместо синего цвета отображается красный, а вместо красного синий tft.clear(tft.rgbcolor(255,251,240)) tft.draw_bmp(0,0,'time.bmp') class Stopwatch: def __init__(self): self.hours = 0 self.mins = 0 self.secs = 0 self.start_time = 0 self.state = 0 self.backcolor = tft.rgbcolor(255,251,240) self.fontcolor = tft.rgbcolor(39,40,51) self.draw_init() def draw_init(self): self.draw_value(98, 132, 2, 0) self.draw_value(55, 125, 3, 0) self.draw_value(5, 125, 3, 0) tft.text(41, 125, ":", font.terminalfont, self.fontcolor, 3) tft.text(90, 130, ":", font.terminalfont, self.fontcolor, 2) def tick(self): if self.state: diff = time.ticks_diff(time.ticks_ms(), self.start_time) hours = diff // (1000 * 60 * 60) diff = diff % (1000 * 60 * 60) mins = diff // (1000 * 60) diff = diff % (1000 * 60) secs = diff // (1000) if secs != self.secs: self.secs = secs self.draw_value(98, 132, 2, secs) if mins != self.mins: self.mins = mins self.draw_value(55, 125, 3, mins) if hours != self.hours: self.hours = hours self.draw_value(5, 125, 3, hours) def start_stop(self): if self.state: self.state = 0 else: self.state = 1 self.start_time = time.ticks_ms() def draw_value(self, x, y, size, value): tft.rect(x+2, y, 6 * size * 2, 8 * size -2, self.backcolor) if value < 10: value = '0' + str(value) else: value = str(value) tft.text(x, y, value, font.terminalfont, self.fontcolor, size) class Button: def __init__(self, p, pressSate): self.pin = Pin(p, Pin.IN) self.pressSate = pressSate self.oldState = not pressSate def onPress(self): state = self.pin.value() if state != self.oldState: self.oldState = state if state == self.pressSate: return True return False stopwatch = Stopwatch() stopwatch.start_stop() while True: stopwatch.tick()
Код эксперимента должен быть понятен. Сначала как обычно подключаем библиотеки, настраиваем SPI и дисплей. Потом код класса секундомера и в конце мы создаем объект секундомера
stopwatch = Stopwatch()
Запускаем его
stopwatch.start_stop()
И в бесконечном цикле вызываем его метод tick()
. В нем он занимается отслеживаем времени и отображением изменений на дисплее
while True: stopwatch.tick()
Теперь наш секундомер умеет считать время. Но еще не слушается управления. В следующем эксперименте мы добавим управление с помощью кнопки и получим полноценный секундомер.