Raspberry Pi:Примеры/Библиотека WiringPi/Игра жизни
Содержание | Введение | Продукты | Операционная система | Настройка | Основы Linux | Аппаратные средства | Неисправности | Типовые проблемы | Часто возникающие вопросы | Библиотеки | Примеры |
Игра жизни[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 ;
}