Это старая версия документа!


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

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

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

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

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

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

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

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

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

Класс Stopwatch

  1. class Stopwatch {
  2. private:
  3. unsigned int hours = 0; // Значение часов
  4. unsigned int mins = 0; // Значение минут
  5. unsigned int secs = 0; // Значение секунд
  6. unsigned long start_time = 0; // Время запуска
  7. bool state = 0; // Состояние: 0 - секундомер не запущен, 1 - запущен
  8. unsigned int backcolor = tft.color565(255, 251, 240); // Цвет фона
  9. unsigned int fontcolor = tft.color565(39, 40, 51); // Цвет шрифта
  10.  
  11. public:
  12. // Метод инициализации вместо конструктора, которым мы не можем воспользоваться,
  13. // т.к. нам нужен уже инициализированный объект дисплея
  14. void init() {
  15. tft.fillScreen(backcolor); // Закрашиваем дисплей цветом фона
  16. reader.drawBMP("/time.bmp", tft, 0, 0); // Выводим изображение секундомера
  17. drawInit(); // Вызов функции отображения начального значения- нулей
  18. }
  19.  
  20. // Метод вывода начального значения секундомера — нулей
  21. void drawInit() {
  22. drawValue(8, 125, 3, 0); // Выводим на дисплей 0 часов
  23. drawValue(55, 125, 3, 0); // Выводим на дисплей 0 минут
  24. drawValue(98, 132, 2, 0); // Выводим на дисплей 0 секунд
  25.  
  26. tft.setTextColor(fontcolor); // Задаем цвет текста
  27.  
  28. tft.setCursor(41, 125); // Задаем координаты вывода
  29. tft.setTextSize(3); // Задаем размер текста
  30. tft.print(":"); // Выводим разделитель часов и минут
  31.  
  32. tft.setCursor(88, 132); // Задаем координаты вывода
  33. tft.setTextSize(2); // Задаем размер текста
  34. tft.print(":"); // Выводим разделитель минут и секунд
  35. }
  36.  
  37. // Метод отображения на дисплее значений
  38. void drawValue(int x, int y, int size, int value) {
  39. // Перед выводом на дисплей новых данных закрашиваем старые прямоугольником с цветом фона
  40. tft.fillRect(x, y, 6 * size * 2, 8 * size -2, backcolor);
  41.  
  42. tft.setCursor(x, y); // Задаем координаты вывода
  43. tft.setTextColor(fontcolor); // Задаем цвет текста
  44. tft.setTextSize(size); // Задаем размер текста
  45.  
  46. // Если значение для вывода менее 10, то добавляем ведущий ноль, чтобы всегда было 2 знака
  47. if (value < 10) tft.print(0);
  48. tft.print(value); // Выводим значение
  49. }
  50.  
  51. // Основной метод секундомера, отсчет времени
  52. void tick() {
  53. if (state) { // отсчет производим только, если секундомер запущен
  54. // Находим разницу в миллисекундах между текущим временем и временем запуска
  55. unsigned long diff = millis() - start_time;
  56. int hours_cur = diff / (1000 * 60 * 60); // Деление без остатка. Находим сколько целых часов прошло
  57. diff = diff % (1000 * 60 * 60); // Находим остаток после деления на часы и сохраняем в diff
  58. int mins_cur = diff / (1000 * 60); // Деление без остатка. Находим сколько целых минут прошло
  59. diff = diff % (1000 * 60); // Находим остаток после деления на минуты и сохраняем в diff
  60. int secs_cur = diff / (1000); // Деление без остатка. Находим сколько целых секунд прошло
  61.  
  62. // Если сохраненное ранее значение часов отличается от вычисленных — обновляем и выводим на дисплей
  63. if (hours_cur != hours) {
  64. hours = hours_cur;
  65. drawValue(8, 125, 3, hours);
  66. }
  67.  
  68. // Если сохраненное ранее значение минут отличается от вычисленных — обновляем и выводим на дисплей
  69. if (mins_cur != mins) {
  70. mins = mins_cur;
  71. drawValue(55, 125, 3, mins);
  72. }
  73.  
  74. // Если сохраненное ранее значение секунд отличается от вычисленных — обновляем и выводим на дисплей
  75. if (secs_cur != secs) {
  76. secs = secs_cur;
  77. drawValue(98, 132, 2, secs);
  78. }
  79. }
  80. }
  81.  
  82. // Метод запуска и остановки секундомера
  83. void startStop() {
  84. state = !state; // Инвертируем состояние секундомера
  85. if (state) start_time = millis(); // Если запущен сохраняем время запуска секундомера
  86. }
  87. };

Класс содержит конструктор методы:

  • draw_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()

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