Эксперимент 50. Класс секундомера, логика работы

В прошлом эксперименте мы нарисовали интерфейс секундомера. Вывели симпатичную картинку и нули вместо часов, минут и секунд. Теперь нужно «оживить» этот прототип, заставить его считать время.

Первый вопрос, который возникает — как отсчитывать время? Как вариант, знакомая нам функция time.sleep(1), которая дает задержку на 1 секунду. Алгоритм работы получается простой. Завели переменную с секундами, которая в начале равна нулю. Отображаем ее значение на дисплее и делаем паузу на секунду. Потом увеличиваем значение переменной на 1 и отображаем новое значение. И так дальше.

Приведенный выше алгоритм простой, но не правильный. Дело в том, что на отображение данных на дисплее уходит время помимо паузы на 1 секунду и такой секундомер не будет считать правильно. Для данной задачи есть более верное решение.

Системная библиотека time предлагает функцию ticks_ms(), которая возвращает количество миллисекунд с начала запуска микроконтроллера. Это значение обновляется аппаратно по прерыванию от таймера, который настроен точно и не зависит от действий программы. Поэтому лучше ориентироваться именно на это значение.

При запуске таймера мы будем запоминать текущее значение ticks_ms() и следить за его изменением.

Схема эксперимента

Рисунок 1. Монтажная схема эксперимента для дисплея с 8 выводами

Рисунок 2. Монтажная схема эксперимента для дисплея с 11 выводами

Класс Stopwatch

  1. class Stopwatch:
  2. def __init__(self):
  3. self.hours = 0 # Значение часов
  4. self.mins = 0 # Значение минут
  5. self.secs = 0 # Значение секунд
  6. self.start_time = 0 # Время запуска
  7. self.state = 0 # Состояние. 0 - секундомер не запущен, 1 - запущен
  8. self.backcolor = tft.rgbcolor(255,251,240) # Цвет фона
  9. self.fontcolor = tft.rgbcolor(39,40,51) # Цвет шрифта
  10. self.draw_init() # Вызов функции отображения начального значения- нулей
  11.  
  12. # Функция вывода начального значения секундомера- нулей
  13. def draw_init(self):
  14. self.draw_value(98, 132, 2, 0) # Выводим на дисплей 0 секунд
  15. self.draw_value(55, 125, 3, 0) # Выводим на дисплей 0 минут
  16. self.draw_value(5, 125, 3, 0) # Выводим на дисплей 0 часов
  17.  
  18. # Выводим разделитель часов и минут
  19. tft.text(41, 125, ":", font.terminalfont, self.fontcolor, 3)
  20. # Выводим разделитель минут и секунд
  21. tft.text(90, 130, ":", font.terminalfont, self.fontcolor, 2)
  22.  
  23.  
  24. # Основная функция секундомера, отсчет времени
  25. def tick(self):
  26. if self.state: # отсчет производим только, если секундомер запущен
  27. # Находим разницу в миллисекундах между текущим временем и временем запуска
  28. diff = time.ticks_diff(time.ticks_ms(), self.start_time)
  29. hours = diff // (1000 * 60 * 60) # Деление без остатка. Находим сколько целых часов прошло
  30. diff = diff % (1000 * 60 * 60) # Находим остаток после деления на часы и сохраняем в diff
  31. mins = diff // (1000 * 60) # Деление без остатка. Находим сколько целых минут прошло
  32. diff = diff % (1000 * 60) # Находим остаток после деления на минуты и сохраняем в diff
  33. secs = diff // (1000) # Деление без остатка. Находим сколько целых секунд прошло
  34.  
  35. # Если сохраненное ранее значение секунд отличается от вычисленных- обновляем и выводим на дисплей
  36. if secs != self.secs:
  37. self.secs = secs
  38. self.draw_value(98, 132, 2, secs)
  39.  
  40. # Если сохраненное ранее значение минут отличается от вычисленных- обновляем и выводим на дисплей
  41. if mins != self.mins:
  42. self.mins = mins
  43. self.draw_value(55, 125, 3, mins)
  44.  
  45. # Если сохраненное ранее значение часов отличается от вычисленных- обновляем и выводим на дисплей
  46. if hours != self.hours:
  47. self.hours = hours
  48. self.draw_value(5, 125, 3, hours)
  49.  
  50.  
  51. # Функция запуска и остановки секундомера
  52. def start_stop(self):
  53. if self.state: # Если секундомер запущен- останавливаем
  54. self.state = 0
  55. else: # Иначе- запускаем
  56. self.state = 1
  57. self.start_time = time.ticks_ms() # Сохраняем время запуска секундомера
  58.  
  59.  
  60. # Функция отображения на дисплее значений
  61. def draw_value(self, x, y, size, value):
  62. # Перед выводом на дисплей новых данных закрашиваем старые прямоугольником с цветом фона
  63. tft.rect(x+2, y, 6 * size * 2, 8 * size -2, self.backcolor)
  64.  
  65. # Если значение для вывода менее 10, то добавляем ведущий ноль, чтобы всегда было 2 знака
  66. # конвертируем значение из числа в строку
  67. if value < 10:
  68. value = '0' + str(value)
  69. else:
  70. value = str(value)
  71.  
  72. # Выводим значение в виде строки на дисплей в нужном месте нужным цветом и размером
  73. 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

Сначала мы вычисляем целое количество часов, а потом мы вычисляем по сути сколько осталось после выделения целого количества часов. После этого там останется значение меньше одного часа. Его мы уже будем делить на минуты. А после вычитания минут будем определять сколько осталось секунд.

Теперь сделаем программу с использованием данного класса

Программный код эксперимента

Exp50.py
  1. from machine import Pin, SPI
  2. from tft import TFT_GREEN
  3. import font
  4. import time
  5. _init()
  6. machine.freq(160000000)
  7.  
  8.  
  9. dc = Pin(4, Pin.OUT)
  10. cs = Pin(2, Pin.OUT)
  11. rst = Pin(5, Pin.OUT)
  12.  
  13. spi = SPI(1, baudrate=40000000, polarity=0, phase=0)
  14. tft = TFT_GREEN(128, 160, spi, dc, cs, rst, rotate=0)
  15.  
  16. tft.initr(tft.BGR) # tft.initr(tft.RGB) #Если вместо синего цвета отображается красный, а вместо красного синий
  17. tft.clear(tft.rgbcolor(255,251,240))
  18. tft.draw_bmp(0,0,'time.bmp')
  19.  
  20.  
  21. class Stopwatch:
  22. def __init__(self):
  23. self.hours = 0
  24. self.mins = 0
  25. self.secs = 0
  26. self.start_time = 0
  27. self.state = 0
  28. self.backcolor = tft.rgbcolor(255,251,240)
  29. self.fontcolor = tft.rgbcolor(39,40,51)
  30. self.draw_init()
  31.  
  32.  
  33. def draw_init(self):
  34. self.draw_value(98, 132, 2, 0)
  35. self.draw_value(55, 125, 3, 0)
  36. self.draw_value(5, 125, 3, 0)
  37.  
  38. tft.text(41, 125, ":", font.terminalfont, self.fontcolor, 3)
  39. tft.text(90, 130, ":", font.terminalfont, self.fontcolor, 2)
  40.  
  41.  
  42. def tick(self):
  43. if self.state:
  44. diff = time.ticks_diff(time.ticks_ms(), self.start_time)
  45. hours = diff // (1000 * 60 * 60)
  46. diff = diff % (1000 * 60 * 60)
  47. mins = diff // (1000 * 60)
  48. diff = diff % (1000 * 60)
  49. secs = diff // (1000)
  50.  
  51. if secs != self.secs:
  52. self.secs = secs
  53. self.draw_value(98, 132, 2, secs)
  54.  
  55. if mins != self.mins:
  56. self.mins = mins
  57. self.draw_value(55, 125, 3, mins)
  58.  
  59. if hours != self.hours:
  60. self.hours = hours
  61. self.draw_value(5, 125, 3, hours)
  62.  
  63.  
  64. def start_stop(self):
  65. if self.state:
  66. self.state = 0
  67. else:
  68. self.state = 1
  69. self.start_time = time.ticks_ms()
  70.  
  71.  
  72. def draw_value(self, x, y, size, value):
  73. tft.rect(x+2, y, 6 * size * 2, 8 * size -2, self.backcolor)
  74.  
  75. if value < 10:
  76. value = '0' + str(value)
  77. else:
  78. value = str(value)
  79.  
  80. tft.text(x, y, value, font.terminalfont, self.fontcolor, size)
  81.  
  82.  
  83. class Button:
  84. def __init__(self, p, pressSate):
  85. self.pin = Pin(p, Pin.IN)
  86. self.pressSate = pressSate
  87. self.oldState = not pressSate
  88.  
  89. def onPress(self):
  90. state = self.pin.value()
  91. if state != self.oldState:
  92. self.oldState = state
  93. if state == self.pressSate:
  94. return True
  95. return False
  96.  
  97.  
  98. stopwatch = Stopwatch()
  99. stopwatch.start_stop()
  100.  
  101. while True:
  102. stopwatch.tick()

Код эксперимента должен быть понятен. Сначала как обычно подключаем библиотеки, настраиваем SPI и дисплей. Потом код класса секундомера и в конце мы создаем объект секундомера

  1. stopwatch = Stopwatch()

Запускаем его

  1. stopwatch.start_stop()

И в бесконечном цикле вызываем его метод tick(). В нем он занимается отслеживаем времени и отображением изменений на дисплее

  1. while True:
  2. stopwatch.tick()

Теперь наш секундомер умеет считать время. Но еще не слушается управления. В следующем эксперименте мы добавим управление с помощью кнопки и получим полноценный секундомер.