Эксперимент 54. Управление кладовщиком

Научим кладовщика двигаться при нажатии на кнопки «вверх», «вниз», «влево» и «вправо».

Подключим кнопки к микроконтроллеру. Три кнопки подтянуты к земле, а одна к питанию. Это сделано специально, чтобы обеспечить условия для правильной загрузки микроконтроллера. Микроконтроллер при включении проверяет состояние некоторых выводов и на основании этого принимает решение о режиме работы — выполнение программы или переход в режим перепрошивки. Для нормального режима работы необходимо, чтобы уровень сигнала на выводе 15 был низким, а на 2 и 0 был высоким. Чтобы соблюсти эти условия и пришлось подключить кнопки по-разному.

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

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

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

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

Exp54.ino
  1. #include <SPI.h>
  2. #include <Adafruit_ST7735.h>
  3. #include "LittleFS_ImageReader.h"
  4.  
  5. #define PIN_CS 2
  6. #define PIN_DC 4
  7. #define PIN_RST 5
  8.  
  9. Adafruit_ST7735 tft = Adafruit_ST7735(PIN_CS, PIN_DC, PIN_RST);
  10. LittleFS_ImageReader reader;
  11.  
  12. bool Map[10][8] = {
  13. {1,1,0,1,1,1,0,1},
  14. {0,1,1,1,1,1,1,0},
  15. {1,1,0,0,0,1,1,1},
  16. {0,1,0,1,0,1,0,1},
  17. {0,1,0,0,0,1,0,1},
  18. {1,1,1,1,0,0,0,1},
  19. {1,0,0,0,0,0,0,1},
  20. {1,0,0,0,1,0,0,1},
  21. {1,0,0,0,1,1,1,1},
  22. {1,1,1,1,1,0,0,0}
  23. };
  24.  
  25. struct Pos {
  26. int x = 0;
  27. int y = 0;
  28.  
  29. bool operator == (const Pos &pos) const {
  30. return x == pos.x && y == pos.y;
  31. }
  32. };
  33.  
  34. class Box {
  35. private:
  36. Adafruit_ST7735 *tft_ptr;
  37. LittleFS_ImageReader *reader_ptr;
  38. Pos pos;
  39. String picture = "/box.bmp";
  40. String picture_on_gate = "/boxngate.bmp";
  41. bool on_gate = false;
  42.  
  43. public:
  44. Box(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) {
  45. tft_ptr = _tft_ptr;
  46. reader_ptr = _reader_ptr;
  47. pos.x = x;
  48. pos.y = y;
  49. }
  50.  
  51. void draw() {
  52. if (on_gate) reader_ptr->drawBMP(picture_on_gate, *tft_ptr, pos.x * 16, pos.y * 16);
  53. else reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16);
  54. }
  55.  
  56. void setOnGate(bool state) {
  57. on_gate = state;
  58. }
  59.  
  60. bool getOnGate() const {
  61. return on_gate;
  62. }
  63.  
  64. Pos getPos() const {
  65. return pos;
  66. }
  67.  
  68. void setPos(Pos _pos) {
  69. pos.x = _pos.x;
  70. pos.y = _pos.y;
  71. draw();
  72. }
  73. };
  74.  
  75. class Gate {
  76. private:
  77. Adafruit_ST7735 *tft_ptr;
  78. LittleFS_ImageReader *reader_ptr;
  79. Pos pos;
  80. String picture = "/gate.bmp";
  81.  
  82. public:
  83. Gate(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) {
  84. tft_ptr = _tft_ptr;
  85. reader_ptr = _reader_ptr;
  86. pos.x = x;
  87. pos.y = y;
  88. }
  89.  
  90. void draw() const {
  91. reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16);
  92. }
  93.  
  94. Pos getPos() const {
  95. return pos;
  96. }
  97. };
  98.  
  99. class Man {
  100. private:
  101. Adafruit_ST7735 *tft_ptr;
  102. LittleFS_ImageReader *reader_ptr;
  103. Pos pos;
  104. String picture = "/man.bmp";
  105.  
  106. public:
  107. Man(Adafruit_ST7735 *_tft_ptr, LittleFS_ImageReader *_reader_ptr, int x, int y) {
  108. Serial.println("Man constructor");
  109. tft_ptr = _tft_ptr;
  110. reader_ptr = _reader_ptr;
  111. pos.x = x;
  112. pos.y = y;
  113. }
  114.  
  115. void draw() const {
  116. reader_ptr->drawBMP(picture, *tft_ptr, pos.x * 16, pos.y * 16);
  117. }
  118.  
  119. Pos getPos() const {
  120. return pos;
  121. }
  122.  
  123. void setPos(Pos _pos) {
  124. tft_ptr->fillRect(pos.x * 16, pos.y * 16, 16, 16, ST77XX_BLACK);
  125. pos.x = _pos.x;
  126. pos.y = _pos.y;
  127. draw();
  128. }
  129. };
  130.  
  131. class Button {
  132. private:
  133. int pin;
  134. bool pressState;
  135. bool oldState;
  136.  
  137. public:
  138. Button(int _pin, bool _pressState) {
  139. pin = _pin;
  140. setPinMode();
  141. pressState = _pressState;
  142. oldState = not _pressState;
  143. }
  144.  
  145. bool onPress() {
  146. bool state = digitalRead(pin);
  147. if (state != oldState){
  148. oldState = state;
  149. if (state == pressState) return true;
  150. }
  151. return false;
  152. }
  153.  
  154. void setPinMode() const {
  155. pinMode(pin, INPUT);
  156. }
  157. };
  158.  
  159. const int boxes_number = 3;
  160. Box boxes[boxes_number] = {
  161. {&tft, &reader, 3, 4},
  162. {&tft, &reader, 4, 6},
  163. {&tft, &reader, 2, 7},
  164. };
  165.  
  166. const int gates_number = 3;
  167. Gate gates[gates_number] = {
  168. {&tft, &reader, 6, 3},
  169. {&tft, &reader, 6, 4},
  170. {&tft, &reader, 6, 5},
  171. };
  172.  
  173. Man man(&tft, &reader, 5, 6);
  174.  
  175. Button btn_up(16, HIGH);
  176. Button btn_down(15, HIGH);
  177. Button btn_left(12, HIGH);
  178. Button btn_right(0, LOW);
  179.  
  180. void setup() {
  181. Serial.begin(9600);
  182. Serial.println();
  183. Serial.println("Setup");
  184. LittleFS.begin();
  185. tft.initR(INITR_BLACKTAB);
  186. tft.setRotation(2);
  187. tft.fillScreen(ST77XX_BLACK);
  188.  
  189. for (int y = 0; y < 10; y++) {
  190. for (int x = 0; x < 10; x++) {
  191. if (Map[y][x]) reader.drawBMP("/brick.bmp", tft, x * 16, y * 16);
  192. }
  193. }
  194.  
  195. for (int i = 0; i < boxes_number; i++) {
  196. boxes[i].draw();
  197. }
  198.  
  199. for (int i = 0; i < gates_number; i++) {
  200. gates[i].draw();
  201. }
  202.  
  203. man.draw();
  204.  
  205. btn_left.setPinMode();
  206. }
  207.  
  208. void loop() {
  209. Pos man_pos = man.getPos();
  210.  
  211. if (btn_up.onPress()) man.setPos({man_pos.x, man_pos.y - 1});
  212. if (btn_down.onPress()) man.setPos({man_pos.x, man_pos.y + 1});
  213. if (btn_left.onPress()) man.setPos({man_pos.x - 1, man_pos.y});
  214. if (btn_right.onPress()) man.setPos({man_pos.x + 1, man_pos.y});
  215. }

В код прошлого эксперимента мы добавили класс кнопки, который уже использовали ранее, например в проекте секундомера. Объявили 4 кнопки, на выводах 16, 15, 12 и 0:

  1. Button btn_up(16, HIGH);
  2. Button btn_down(15, HIGH);
  3. Button btn_left(12, HIGH);
  4. Button btn_right(0, LOW);

В класс Button мы добавили метод setPinMode() и вызываем его для кнопки на 12 пине, хотя он уже вызывался в конструкторе. Это необходимо, так как метод дисплея initR() вызывает переопределение режима пина 12.

  1. btn_left.setPinMode();

В бесконечном цикле постоянно мониторим события нажатия на кнопки:

  1. Pos man_pos = man.getPos();
  2.  
  3. if (btn_up.onPress()) man.setPos({man_pos.x, man_pos.y - 1});
  4. if (btn_down.onPress()) man.setPos({man_pos.x, man_pos.y + 1});
  5. if (btn_left.onPress()) man.setPos({man_pos.x - 1, man_pos.y});
  6. if (btn_right.onPress()) man.setPos({man_pos.x + 1, man_pos.y});

При нажатии на кнопку «вверх» уменьшаем координату Y и задаем новую координату кладовщику. Аналогичные действия производим при нажатии на другие кнопки. Теперь наш кладовщик может двигаться по экрану. Но сейчас нет никаких ограничений — он свободно проходит сквозь стены и ящики, никак на них не влияя. Теперь самое время реализовать логику игры, описать условия и взаимодействия. Этим займемся в следующем эксперименте.