Содержание

Эксперимент 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 и методы:

Код класса снабжен комментариями, подробно описывающими почти каждую строку. Кроме этого дадим несколько пояснений. Когда мы говорим «Сохраняем время запуска секундомера», то имеется в виду, что мы сохраняем значение количества миллисекунд с момента запуска микроконтроллера. И все упоминания времени следует читать в этом ключе.

Функция 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()

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