Raspberry Pi:Примеры/Библиотека WiringPi/Игра жизни

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.



Игра жизни[1]

Я искал интересные идеи для тестирования WiringPi, и презентация Эми Мафер (Amy Mather) на конференции Manchester Raspberry Jamboree подарила мне отличную идею – реализовать на Pi «Игру жизни» Джона Конуэя при помощи чипов с GPIO-расширителей и светодиодных матриц 8x8. Для своей «Игры жизни» Эми использовала Arduino и светодиодную матрицу, но я реши подойти к вопросу при помощи двух GPIO-расширителей MXP23017.

Вот так выглядит моя сборка:

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

«Железо»

В моей сборке задействована одна из плат-расширителей, сделанных Джоном Джейсом. Когда-то я уже использовал одну из его плат, но для этого теста мне понадобилась другая – у нее 2 GPIO-чипа MCP23017, работающих через шину I2C, что в общей сумме дает 32 дополнительных GPIO-бита.

Библиотека WiringPi видит их как дополнительные «контакты», поэтому никакой возни с конфигурационными регистрами и прочим не требуется.

Светодиодный дисплей 8х8 – двухцветный (зеленый и красный), поэтому для его корректной работы нам понадобится 24 GPIO-бита. Как правило, я улучшаю «железо» за счет небольшого усложнения кода, поэтому на общих выводах, идущих от этих двухцветных светодиодов, у меня стоит только 8 резисторов – это значит, что одновременно я могу зажечь либо красный, либо зеленый светодиод. Теоретически, я могу оптимизировать код таким образом, чтобы одновременно зажигать все 8 светодиодов, но делать этого не буду :)

Одной из обнаруженный мною проблем стала скорость (точнее, ее отсутствие!) шины I2C. По умолчанию она составляет 100 Кбит/сек, но мне удалось увеличить ее до 750 Кбит/сек. Однако если я поднимал ее выше этой отметки, шина I2C становилась ненадежной – сигнал не мог вернуться обратно к 3,3 вольтам. Дело, по всей видимости, в плате (MCP23017 справляется и с 1,7 Мбит/сек), поэтому, готовясь к следующему проекту, имейте в виду, что шина I2C может быть слабовата...

Короткое видео, показывающее мою сборку в действии:https://www.youtube.com/watch?v=g5cSvVGCX_A

Вы, вероятно, заметили, что светодиод иногда мигает красным – я немного изменил код, чтобы показать умирающие клетки (для одного поколения). Ну а еще, чтобы показать, что на этом дисплее есть и красные светодиоды.

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

Если взглянуть на программу ниже, то можно увидеть, что код для выходных контактов на GPIO-расширителе ограничивается лишь функциями digitalWrite(). Все заботы по интерпретации регистров, битов и прочего у MCP23017 библиотека WiringPi берет на себя, оставляя вам лишь работу над интерфейсами.

/*
 * life.c:
 ***********************************************************************
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>

#include <wiringPi.h>
#include <mcp23017.h>

#define ROW_OFFSET      108
#define COL_RED         116
#define COL_GREEN       124

unsigned char matrix [8][8] ;

PI_THREAD (matrixUpdater)
{
  int row, col ;
  unsigned char data ;

  piHiPri (50) ;

  for (;;)
  {
    for (row = 0 ; row < 8 ; ++row)
    {
      digitalWrite (ROW_OFFSET + row, 1) ; 
      for (col = 0 ; col < 8 ; ++col)
      {
        data = matrix [col][row] ;
        /**/ if (data == 0)
          continue ;
        else if (data == 1)     // зеленый
        {
          digitalWrite (COL_GREEN + col, 0) ; 
          delayMicroseconds (500) ;
          digitalWrite (COL_GREEN + col, 1) ;
        }
        else                    // красный
        {
          digitalWrite (COL_RED + col, 0) ; 
          delayMicroseconds (500) ;
          digitalWrite (COL_RED + col, 1) ;
        }
      }
      digitalWrite (ROW_OFFSET + row, 0) ;
    }
  }

  return NULL ;
}

void setupMatrix (void)
{
  int row, col ;

  // Нам нужно инициализировать WiringPi, чтобы заработала 
  // функция delay(), но для того, чтобы использовать I2C,
  // root-пользователем быть необязательно, поэтому...
  wiringPiSetupSys () ;

  // добавляем два GPIO-расширителя 23017 на контакты 100 и 116:
  mcp23017Setup (100, 0x20) ;
  mcp23017Setup (116, 0x21) ;

  // настраиваем контакты, поскольку они нам понадобятся; 
  // второй порт второго чипа отвечает за ряды:
  for (row = 8 ; row < 16 ; ++row)
  {
    pinMode (100 + row, OUTPUT) ;
    digitalWrite (100 + row, 0) ;
  }

// первый порт второго чипа подключен к зеленым, 
// а затем к красным светодиодам:

  for (col = 16 ; col < 32 ; ++col)
  {
    pinMode (100 + col, OUTPUT) ;
    digitalWrite (100 + col, 1) ;
  }
}

/*
 * Функция torus()
 * Делаем координаты, обертывающие мир в фигуру «тор».
 ********************************************************************* */

void torus (int *x, int *y)
{
  if (*x < 0) *x = *x + 8 ;
  if (*x > 7) *x = *x - 8 ;
  if (*y < 0) *y = *y + 8 ;
  if (*y > 7) *y = *y - 8 ;
}

/*
 * Функция neighbours()
 * Считаем соседей; этот алгоритм считает, что мир – тороидальный.
 ********************************************************************
 */

int neighbours (int x, int y)
{
  int sx, sy ;
  int x1, y1 ;
  int count = 0 ;

  for (sx = x - 1 ; sx < x + 2 ; ++sx)
  {
    for (sy = y - 1 ; sy < y + 2 ; ++sy)
    {
      if ((sx == x) && (sy == y))       // не считаем себя!
        continue ;

      x1 = sx ; y1 = sy ;
      torus (&x1, &y1) ;
      if (matrix [x1][y1] == 1)
        ++count ;
    }
  }
  return count ;
}

/*
 * Функция updateLife()
 * Берем матрицу и создаем новое поколение.
 *********************************************************************
 */

void updateLife (void)
{
  int n ;
  int x, y ;
  char newLife [8][8] ;

  for (x = 0 ; x < 8 ; ++x)
  {
    for (y = 0 ; y < 8 ; ++y)
    {
      n = neighbours (x, y) ;

      /**/ if ((n == 0) || (n == 1))    // смерть из-за изоляции
        if (matrix [x][y] == 1)         // здесь была жизнь
          newLife [x][y] = 2 ;
        else
          newLife [x][y] = 0 ;
      else if (n == 2)                  // 2 соседа - стабильность
        newLife [x][y] = matrix [x][y] ;
      else if (n == 3)                  // 3 соседа – новая жизнь 
                                        // (или та же старая)
        newLife [x][y] = 1 ;
      else                              // 4 или больше – 
                                        // смерть от перенаселения 
        newLife [x][y] = 0 ;
    }
  }

  // копируем новую жизнь на матрицу:
  memcpy (matrix, newLife, sizeof (newLife)) ;
}

/*
 *********************************************************************
 * Существа
 *********************************************************************
 */

#undef  Test
#define Glider
#undef  Rpent
#undef  Toad

#ifdef  Glider
char initial [64] =
 "  *     "
 "   *    "
 " ***    "
 "        "
 "        "
 "        "
 "        "
 "        " ;
#endif

#ifdef  Test
char initial [64] =
 "**      "
 "        "
 "        "
 "  **    "
 "  **    "
 "        "
 "        "
 "        " ;
#endif

#ifdef  Rpent
char initial [64] =
 "        "
 "  **    "
 " **     "
 "  *     "
 "        "
 "        "
 "        "
 "        " ;
#endif

#ifdef  Toad
char initial [64] =
 "        "
 " ***    "
 "***     "
 "        "
 "     ***"
 "    *** "
 "        "
 "        " ;
#endif

int main (int argc, char *argv [])
{
  int x, y ;

  setupMatrix () ;
  piThreadCreate (matrixUpdater) ;

// копируем на матрицу исходные значения:

  for (x = 0 ; x < 8 ; ++x)
    for (y = 0 ; y < 8 ; ++y)
      if (initial [x + 8 * y] == '*')
        matrix [x][y] = 1 ;
      else
        matrix [x][y] = 0 ;

  delay (2000) ;

  for (;;)
  {
    updateLife () ;
    delay (100) ;
  }

  return 0 ;
}

См.также

Внешние ссылки