Когда отладки попали к нам в руки, захотелось вместо «серьёзной» демонстрационной задачи в честь приближающегося Нового года сделать что-нибудь забавное и креативное. Мы поскребли по сусекам и нашли старенький проектик — тетрис на MEGA168 через терминалку и решили портировать его на новый камень и представить общественности. Практического смысла в этом никакого, что называется Just for fun. Кому интересны подробности, прошу под кат.
Кратко о новых микроконтроллерах
- SAM D09 — младший представитель семейства SAM D. Имеет 8К или 16К флеша и 4К SRAM. Варианты корпусов QFN-24 и SOIC-14. На борту DMA и Event system. 2 SERCOM — универсальных коммуникационных модулей, которые могут конфигурироваться как USART, SPI или I2C. 5-ти или 10-ти канальный 12-ти битный АЦП.
- SAM D10 — апгрейд D09 в части добавления дополнительных таймеров, аналогового компаратора, ЦАП и контроллера сенсорных кнопок, а так же дополнительного SERCOM для некоторых модификаций. Варианты корпусов QFN-24, SOIC-14, SOIC-20.
- SAM D11 — тот же D10, но с добавлением Full-Speed USB Device.
Внешний вид отладочной платы. Программатор на борту, подключение через разъем Micro USB.
Теперь про сам тетрис
Работа тетриса основана на нескольких базовых принципах:
- общение с терминалкой осуществляется по протоколу VT100,
- обновление картинки происходит по таймеру,
- любая фигура вписывается в квадрат определенных размеров (4 на 4 символа).
Тетрис использует три команды из протокола VT100: очистка экрана, перемещение курсора в начало и сделать курсор невидимым.
Для работы по этому протоколу можно использовать терминалку Tera term, например.
Для управления используются 5 клавиш-букв клавиатуры:
- n – начать новую игру,
- w или space – повернуть фигуру,
- s – уронить фигуру,
- d – переместить вправо,
- a – переместить влево.
switch (c)
{
case 'w':
case ' ':
//ROTATE
tetris_rotate();
break;
case 's':
//DOWN
tetris_gravity();
break;
case 'd':
//RIGHT
tetris_move_right();
break;
case 'a':
//LEFT
tetris_move_left();
break;
default: break;
}
if (c == 'n')
{
c=0;
//Seed random function so we do not get same start condition
//for each new game. In essence we will not start a new game
//exactly at the same time.
srand(tick);
//New Game
is_running = true;
terminal_cursor_off();
terminal_clear();
tetris_init();
tetris_new_block();
terminal_cursor_home();
tetris_print();
}
Скорость игры устанавливается таймером. Для более опытных игроков можно задать «тиканье» быстрее, тогда и фигуры будут падать быстрее.
Конечно же, подсчитываются очки: за каждую исчезнувшую строку добавляется 100 очков. За каждую следующую «исчезнувшую» одновременно с первой, добавляется в два раза больше очков, чем за предыдущую.
Портируем с mega на samd10
Из периферии контролера нам нужен SERCOM в режиме UART для непосредственной передачи фигурок и картинки, и таймер для отсчета времени обновления картинки.
Вместо милой сердцу любого программиста 8-битных контроллеров настройки UART битами в регистрах:
static void board_init(void)
{
/*Configure IO pins:
* - UART pins
* - SW pin
* - LED pin
*/
DDRD &= ~USART_RX_PIN_bm;
DDRD |= USART_TX_PIN_bm;
PORTD |= USART_TX_PIN_bm;
PORTB |= SW_PIN_bm;
DDRB &= ~SW_PIN_bm;
/*Disable all modules we will not use*/
PRR = (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM0) | (1 << PRSPI) | (1 << PRADC);
}
конфигурируем sercom для работы в режиме uart, не забывая разрешить прерывания и callback по приему символа.
static void configure_console(void)
{
struct usart_config usart_conf;
usart_get_config_defaults(&usart_conf);
usart_conf.mux_setting = CONF_STDIO_MUX_SETTING;
usart_conf.pinmux_pad0 = CONF_STDIO_PINMUX_PAD0;
usart_conf.pinmux_pad1 = CONF_STDIO_PINMUX_PAD1;
usart_conf.pinmux_pad2 = CONF_STDIO_PINMUX_PAD2;
usart_conf.pinmux_pad3 = CONF_STDIO_PINMUX_PAD3;
usart_conf.baudrate = CONF_STDIO_BAUDRATE;
stdio_serial_init(&cdc_uart_module, CONF_STDIO_USART_MODULE, &usart_conf);
}
enum status_code usart_enable_rx_interrupt( struct usart_module *const module, uint8_t *rx_data)
{
// Sanity check arguments
Assert(module);
Assert(rx_data);
// Issue internal asynchronous read
// Get a pointer to the hardware module instance
SercomUsart *const usart_hw = &(module->hw->USART);
module->rx_buffer_ptr = rx_data;
// Enable the RX Complete Interrupt
usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_RXC;
return STATUS_OK;
}
void configure_usart_callbacks(void)
{
usart_register_callback(&cdc_uart_module, USART_RX_callback, USART_CALLBACK_BUFFER_RECEIVED);
usart_enable_callback(&cdc_uart_module, USART_CALLBACK_BUFFER_RECEIVED);
}
В исходном коде для меги данные по uart принимались с помощью putc, для samd10 сделаем проще: пусть просто по прерыванию каждый полученный байт сваливается в определенную переменную. Это решение не претендует на правильность и безопасность, оно для простоты перехода и ускорения его.
Подробно про то, как победить порой слишком «умную» ASF для приема одного байта по прерываниям, мы писали в нашей статье на сайте we.easyelectronics.ru.
Перейдем к таймерам.
Код для меги:
void init_timer(void)
{
/*Start timer used to iterate game and seed random function*/
TIFR1 = 1 << OCF1A;
TIMSK1 = 1 << OCIE1A;
OCR1A = TIMER_TOP_VALUE;
TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);
}
ISR(TIMER1_COMPA_vect, ISR_BLOCK)
{
++tick;
iterate_game = true;
}
И соответствующий код для samd10
/** Configures TC function with the driver.
*/
static void configure_tc(void)
{
struct tc_config config_tc;
tc_get_config_defaults(&config_tc);
config_tc.counter_size = TC_COUNTER_SIZE_16BIT;
config_tc.wave_generation = TC_WAVE_GENERATION_MATCH_FREQ;
config_tc.counter_16_bit.compare_capture_channel[0] = 2000;
config_tc.clock_prescaler=TC_CLOCK_PRESCALER_DIV1024;
tc_init(&tc_instance, CONF_TC_INSTANCE, &config_tc);
tc_enable(&tc_instance);
}
/** Registers TC callback function with the driver.
*/
static void configure_tc_callbacks(void)
{
tc_register_callback(&tc_instance, tc_callback_to_counter, TC_CALLBACK_CC_CHANNEL0);
tc_enable_callback(&tc_instance, TC_CALLBACK_CC_CHANNEL0);
}
static void tc_callback_to_counter( struct tc_module *const module_inst)
{
++tick;
iterate_game = true;
}
Вот и все. Весь остальной код для обработки движения фигур и всей остальной логики остается таким же.
Полностью проект для samd 10 лежит на github.
Стоимость отладочной платы ATSAMD10-XMINI составляет 450 рублей.
Комментарии (5)
den1s1
26.12.2015 11:28что-то по ссылке кроме мимолетного упоминания, о самом тетрисе ни слова. только о настройке режима увода модема.
tnt23
27.12.2015 13:34Там даже куски кода есть. (Что такое увод модема, не понял я?)
Если же вы хотели получить разжёванную реализацию Тетриса, то это упражнение для студентов первого курса по специальности «Программирование на языке BASIC»marus-ka
28.12.2015 10:33В приведенной вами по ссылке статье нет ни одного куска кода для тетриса. Мы не хотели получить разжеванную реализацию тетриса. В статье мы показали, что очень легко перейти с 8-битной меги на 32-битный samd10 с cortex-m0+.
tnt23
28.12.2015 11:23Что статья о легкости перехода с 8-битной меги на 32-битный кортекс, я понял с первого раза, прочитав статью. Замечу, что статья по ссылке также не о том, как написать собственно тетрис, а об использовании последовательного порта и ESC-последовательностей.
tnt23
Старая добрая идея использовать ESC-последовательности для отрисовки сложных оконных интерфейсов, в свое время массово использовалась на BBS.
А вот пример реализации тетриса на GSM модеме :)
www.compel.ru/lib/ne/2007/19/9-primer-postroeniya-polzovatelskogo-interfeysa-v-prilozheniyah-open-at