--- title: "nRF24L01 + DualShock2 = радиоуправление" categories: ["mcu", "archive"] date: 2016-01-28T00:00:00+03:00 draft: false featured_image: miniature.jpg --- Я, наконец-то решился купить модули nRF24L01. Для знакомства с ними нужно придумать что из них сделать. И сделал радиоуправляемую машинку. Из всего, что есть. Наверное, все в детстве хотели радиоуправляемую машинку :) Выглядят модули вот так: ![Радиомодуль nRF24L01](nrf24l01.jpg "Радиомодуль nRF24L01") Описание: Дальность до 100 м. Скорость до 2 Мб Интерфейс SPI для управления Напряжение: 3-3.6В (рекомендуется 3,3) В Максимальная выходная мощность: +20 дБм Коэффициент усиления антенны (пиковая): 2dBi Работает радиомодуль на частоте 2.4ГГц (как эти ваши вайфаи и блютузы). В качестве пульта я решил использовать тот самый [белый геймпад](/dualshock-avr-usb) (он не обиделся). Нормальных библиотек особо не нашёл, почитал [мануал](http://store.curiousinventor.com/guides/ps2), посмотрел структуру пакета (рисунок 2), решил написать сам. Геймпад общается по протоколу SPI. ![Структура пакета геймпада](psx_packet.png "Структура пакета геймпада") Вроде бы ничего сложного, написал. Подключил экран и проверил. Работает: И всё работало замечательно пока не отключили свет. И так повезло, что после этого все исходники превратились в кровавое месиво. И тут я случайно нашёл другую [библиотеку](http://digitalcave.ca/resources/avr/psx.jsp) и взял ее. После обработки напильником библиотека стала иметь более-менее приличный вид: psx.h: ```c #ifndef PSX_lib_h #define PSX_lib_h #include #include #define PSX_DATA_PORT PORTD #define PSX_SCK_PORT PORTD #define PSX_DC_PORT PORTD #define PSX_CS_PORT PORTD #define PSX_DATA_PINREG PIND #define PSX_DATA_DDR DDRD #define PSX_SCK_DDR DDRD #define PSX_DC_DDR DDRD #define PSX_CS_DDR DDRD #define PSX_SCK_PIN 4 #define PSX_DC_PIN 7 #define PSX_CS_PIN 5 #define PSX_DATA_PIN 6 // задержка тактов SPI, us #define CTRL_CLK 10 // кнопкм #define PSB_SELECT 0x0001 #define PSB_L3 0x0002 #define PSB_R3 0x0004 #define PSB_START 0x0008 #define PSB_PAD_UP 0x0010 #define PSB_PAD_RIGHT 0x0020 #define PSB_PAD_DOWN 0x0040 #define PSB_PAD_LEFT 0x0080 #define PSB_L2 0x0100 #define PSB_R2 0x0200 #define PSB_L1 0x0400 #define PSB_R1 0x0800 #define PSB_TRIANGLE 0x1000 #define PSB_CIRCLE 0x2000 #define PSB_CROSS 0x4000 #define PSB_SQUARE 0x8000 // джойстики #define PSS_RX 5 #define PSS_RY 6 #define PSS_LX 7 #define PSS_LY 8 // инициализация геймпада void psx_init(bool analog); // получить состояние всех кнопок uint16_t psx_buttons(); // получить состояниие кнопки (PSB_x) uint8_t psx_button(uint16_t); // получить состояниие оси джойстика (PSS_x) uint8_t psx_stick(unsigned int); // обновить состояние кнопок void psx_read_gamepad(); #endif ``` psx.c: ```c #include "psx.h" #include // буфер static uint8_t _psx_data[21]; // отправить байт программным SPI и получить ответ uint8_t _psx_gamepad_shift(uint8_t transmit_byte) { uint8_t received_byte = 0; for(uint8_t i = 0; i < 8; i++) { PSX_SCK_PORT &= ~_BV(PSX_SCK_PIN); if (transmit_byte & (_BV(i))) { PSX_DC_PORT |= _BV(PSX_DC_PIN); } else { PSX_DC_PORT &= ~_BV(PSX_DC_PIN); } _delay_us(CTRL_CLK); PSX_SCK_PORT |= _BV(PSX_SCK_PIN); if(PSX_DATA_PINREG & _BV(PSX_DATA_PIN)) { received_byte |= _BV(i); } _delay_us(CTRL_CLK); } PSX_SCK_PORT |= _BV(PSX_SCK_PIN); return received_byte; } // отправить команду void _psx_send_command(uint8_t send_data[], uint8_t size){ PSX_CS_PORT &= ~(_BV(PSX_CS_PIN)); PSX_DC_PORT |= _BV(PSX_DC_PIN); PSX_SCK_PORT |= _BV(PSX_SCK_PIN); for (uint8_t i = 0; i < size; i++){ send_data[i] = _psx_gamepad_shift(send_data[i]); } PSX_CS_PORT |= _BV(PSX_CS_PIN); } // обновить состояние кнопок void psx_read_gamepad() { _psx_data[0] = 0x01; _psx_data[1] = 0x42; for (uint8_t i = 2; i < 21; i++){ _psx_data[i] = 0x00; } _psx_send_command(_psx_data, 21); } // инициализация геймпада void psx_init(bool analog){ PSX_SCK_DDR |= _BV(PSX_SCK_PIN); PSX_CS_DDR |= _BV(PSX_CS_PIN); PSX_DC_DDR |= _BV(PSX_DC_PIN); PSX_DATA_DDR &= ~(_BV(PSX_DATA_PIN)); PSX_DATA_PORT |= _BV(PSX_DATA_PIN); PSX_SCK_PORT |= _BV(PSX_SCK_PIN); PSX_DC_PORT |= _BV(PSX_DC_PIN); psx_read_gamepad(); if(!analog) return; // войти в режим конфигурации uint8_t enter_config_command[] = {0x01, 0x43, 0x00, 0x01, 0x00}; _psx_send_command(enter_config_command, 5); // заблокирвать геймпад в аналоговом режиме uint8_t lock_analog_mode_command[] = {0x01, 0x44, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00}; _psx_send_command(lock_analog_mode_command, 9); // выйти из режима конфигурации uint8_t exit_config_command[] = {0x01, 0x43, 0x00, 0x00, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A}; _psx_send_command(exit_config_command, 9); } // получить состояние всех кнопок uint16_t psx_buttons() { uint16_t buttons = *(uint16_t*)(_psx_data + 3); // получить 2 байта, содержащих позиции данных 3 и 4 return ~buttons; } // получить состояниие кнопки (PSB_x uint8_t psx_button(uint16_t button) { uint16_t buttons = psx_buttons(); return ((buttons & button) > 0); } // получить состояниие оси джойстика (PSS_x) uint8_t psx_stick(unsigned int stick) { return _psx_data[stick]; } ``` Код передатчика: host.c: ```c #define NRF24_IO_PORT PORTC #define NRF24_IO_PINREG PINC #define NRF24_IO_DDR DDRC #include #include #include "lib/nrf24.h" #include "lib/psx.h" // ATMega8 nrf24 // ________ _________ // | PC0 |----| CE | // | PC1 |----| CSN | // | PC2 |----| SCK | // | PC3 |----| MOSI | // | PC4 |----| MISO | // | | |_________| // | | // | | Геймпад // | | _________ // | PD4 |----| SCK | // | PD5 |----| CS | // | PD6 |----| DATA | // | PD7 |----| DC | // |________| |_________| uint8_t data_array[2]; uint8_t tx_address[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; uint8_t rx_address[5] = {0xD7, 0xD7, 0xD7, 0xD7, 0xD7}; int main() { nrf24_init(); nrf24_config(2, 2); // канал 2, размер пакета 2 nrf24_tx_address(tx_address); nrf24_rx_address(rx_address); psx_init(false); while (1) { psx_read_gamepad(); // чтение геймпада data_array[0] = 0xFE; // проверочное значение // вверх - левое шасси вперёд // вниз - левое шасси назад // треугольник - правое шасси вперёд // крест - правое шасси назад data_array[1] = (psx_button(PSB_PAD_UP) << 1) | (psx_button(PSB_PAD_DOWN) << 2) | (psx_button(PSB_TRIANGLE) << 3) | (psx_button(PSB_CROSS) << 4); // запихиваем кнопки nrf24_send(data_array); // отправляем данные while (!nrf24_isSending()); _delay_ms(20); } } ``` Код черепахи: turtle.c: ```c #include #include "lib/nrf24.h" #include #include // ATMega8 nrf24 // ________ _________ // | PC0 |----| CE | // | PC1 |----| CSN | // | PC2 |----| SCK | // | PC3 |----| MOSI | // | PC4 |----| MISO | // | | |_________| // | | // | | Шасси // | PD0 |-(2) (1)--[/\ /\]--(3) // | PD1 |-(1) |L| |R| // | PD2 |-(3) |L| |R| // | PD3 |-(4) (2)--[\/ \/]--(4) // |________| uint8_t data_array[2]; uint8_t tx_address[5] = {0xD7, 0xD7, 0xD7, 0xD7, 0xD7}; uint8_t rx_address[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; #define ENGINES_PORT PORTD #define ENGINES_DDR DDRD #define LEFT_CHASSIS_FWD_PIN 1 #define LEFT_CHASSIS_REV_PIN 0 #define RIGHT_CHASSIS_FWD_PIN 3 #define RIGHT_CHASSIS_REV_PIN 2 int main() { ENGINES_DDR |= 0b00001111; nrf24_init(); nrf24_config(2, 2); // канал 2, размер пакета 2 nrf24_tx_address(tx_address); nrf24_rx_address(rx_address); while (1) { if (nrf24_dataReady()) nrf24_getData(data_array); if (data_array[0] == 0xFE) { // проверка типа пакета bool left_fw = (data_array[1] & (1 << 1)) != 0; bool left_bck = (data_array[1] & (1 << 2)) != 0; bool right_fw = (data_array[1] & (1 << 3)) != 0; bool right_bck = (data_array[1] & (1 << 4)) != 0; ENGINES_PORT = (left_fw << LEFT_CHASSIS_FWD_PIN) | (right_fw << RIGHT_CHASSIS_FWD_PIN) | (left_bck << LEFT_CHASSIS_REV_PIN) | (right_bck << RIGHT_CHASSIS_REV_PIN); } _delay_ms(20); } } /* ------------------------------------------------------------------------- */ ``` Библиоткека для nRF24: nrf24.h: ```c /* * ---------------------------------------------------------------------------- * “THE COFFEEWARE LICENSE” (Revision 1): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a coffee in return. * ----------------------------------------------------------------------------- * This library is based on this library: * https://github.com/aaronds/arduino-nrf24l01 * Which is based on this library: * http://www.tinkerer.eu/AVRLib/nRF24L01 * ----------------------------------------------------------------------------- */ #ifndef NRF24 #define NRF24 #include #include #ifdef TURTLE #define NRF24_IO_PORT PORTB #define NRF24_IO_PINREG PINB #define NRF24_IO_DDR DDRB #else #define NRF24_IO_PORT PORTC #define NRF24_IO_PINREG PINC #define NRF24_IO_DDR DDRC #endif #define NRF24_CE_PIN 0 #define NRF24_CSN_PIN 1 #define NRF24_SCK_PIN 2 #define NRF24_MOSI_PIN 3 #define NRF24_MISO_PIN 4 /* IO Helpers */ #define nrf24_set_bit(reg,bit) reg |= (1< Pin HIGH * - state:0 => Pin LOW */ /* -------------------------------------------------------------------------- */ extern void nrf24_ce_digitalWrite(uint8_t state); /* -------------------------------------------------------------------------- */ /* nrf24 CE pin control function * - state:1 => Pin HIGH * - state:0 => Pin LOW */ /* -------------------------------------------------------------------------- */ extern void nrf24_csn_digitalWrite(uint8_t state); /* -------------------------------------------------------------------------- */ /* nrf24 SCK pin control function * - state:1 => Pin HIGH * - state:0 => Pin LOW */ /* -------------------------------------------------------------------------- */ extern void nrf24_sck_digitalWrite(uint8_t state); /* -------------------------------------------------------------------------- */ /* nrf24 MOSI pin control function * - state:1 => Pin HIGH * - state:0 => Pin LOW */ /* -------------------------------------------------------------------------- */ extern void nrf24_mosi_digitalWrite(uint8_t state); /* -------------------------------------------------------------------------- */ /* nrf24 MISO pin read function /* - returns: Non-zero if the pin is high */ /* -------------------------------------------------------------------------- */ extern uint8_t nrf24_miso_digitalRead(); #endif ``` nrf24.c: ```c /* * ---------------------------------------------------------------------------- * “THE COFFEEWARE LICENSE” (Revision 1): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a coffee in return. * ----------------------------------------------------------------------------- * This library is based on this library: * https://github.com/aaronds/arduino-nrf24l01 * Which is based on this library: * http://www.tinkerer.eu/AVRLib/nRF24L01 * ----------------------------------------------------------------------------- */ #include "nrf24.h" uint8_t payload_len; void nrf24_setupPins() { nrf24_set_bit(NRF24_IO_DDR, NRF24_CE_PIN); // CE output nrf24_set_bit(NRF24_IO_DDR, NRF24_CSN_PIN); // CSN output nrf24_set_bit(NRF24_IO_DDR, NRF24_SCK_PIN); // SCK output nrf24_set_bit(NRF24_IO_DDR, NRF24_MOSI_PIN); // MOSI output nrf24_clr_bit(NRF24_IO_DDR, NRF24_MISO_PIN); // MISO input } void nrf24_ce_digitalWrite(uint8_t state) { if (state) { nrf24_set_bit(NRF24_IO_PORT, NRF24_CE_PIN); } else { nrf24_clr_bit(NRF24_IO_PORT, NRF24_CE_PIN); } } void nrf24_csn_digitalWrite(uint8_t state) { if (state) { nrf24_set_bit(NRF24_IO_PORT, NRF24_CSN_PIN); } else { nrf24_clr_bit(NRF24_IO_PORT, NRF24_CSN_PIN); } } void nrf24_sck_digitalWrite(uint8_t state) { if (state) { nrf24_set_bit(NRF24_IO_PORT, NRF24_SCK_PIN); } else { nrf24_clr_bit(NRF24_IO_PORT, NRF24_SCK_PIN); } } void nrf24_mosi_digitalWrite(uint8_t state) { if (state) { nrf24_set_bit(NRF24_IO_PORT, NRF24_MOSI_PIN); } else { nrf24_clr_bit(NRF24_IO_PORT, NRF24_MOSI_PIN); } } uint8_t nrf24_miso_digitalRead() { return nrf24_check_bit(NRF24_IO_PINREG, NRF24_MISO_PIN); } /* init the hardware pins */ void nrf24_init() { nrf24_setupPins(); nrf24_ce_digitalWrite(LOW); nrf24_csn_digitalWrite(HIGH); } /* configure the module */ void nrf24_config(uint8_t channel, uint8_t pay_length) { /* Use static payload length ... */ payload_len = pay_length; // Set RF channel nrf24_configRegister(RF_CH, channel); // Set length of incoming payload nrf24_configRegister(RX_PW_P0, 0x00); // Auto-ACK pipe ... nrf24_configRegister(RX_PW_P1, payload_len); // Data payload pipe nrf24_configRegister(RX_PW_P2, 0x00); // Pipe not used nrf24_configRegister(RX_PW_P3, 0x00); // Pipe not used nrf24_configRegister(RX_PW_P4, 0x00); // Pipe not used nrf24_configRegister(RX_PW_P5, 0x00); // Pipe not used // 1 Mbps, TX gain: 0dbm nrf24_configRegister(RF_SETUP, (0 << RF_DR) | ((0x03) << RF_PWR)); // CRC enable, 1 byte CRC length nrf24_configRegister(CONFIG, nrf24_CONFIG); // Auto Acknowledgment nrf24_configRegister(EN_AA, (1 << ENAA_P0) | (1 << ENAA_P1) | (0 << ENAA_P2) | (0 << ENAA_P3) | (0 << ENAA_P4) | (0 << ENAA_P5)); // Enable RX addresses nrf24_configRegister(EN_RXADDR, (1 << ERX_P0) | (1 << ERX_P1) | (0 << ERX_P2) | (0 << ERX_P3) | (0 << ERX_P4) | (0 << ERX_P5)); // Auto retransmit delay: 1000 us and Up to 15 retransmit trials nrf24_configRegister(SETUP_RETR, (0x04 << ARD) | (0x0F << ARC)); // Dynamic length configurations: No dynamic length nrf24_configRegister(DYNPD, (0 << DPL_P0) | (0 << DPL_P1) | (0 << DPL_P2) | (0 << DPL_P3) | (0 << DPL_P4) | (0 << DPL_P5)); // Start listening nrf24_powerUpRx(); } /* Set the RX address */ void nrf24_rx_address(uint8_t *adr) { nrf24_ce_digitalWrite(LOW); nrf24_writeRegister(RX_ADDR_P1, adr, nrf24_ADDR_LEN); nrf24_ce_digitalWrite(HIGH); } /* Returns the payload length */ uint8_t nrf24_payload_length() { return payload_len; } /* Set the TX address */ void nrf24_tx_address(uint8_t *adr) { /* RX_ADDR_P0 must be set to the sending addr for auto ack to work. */ nrf24_writeRegister(RX_ADDR_P0, adr, nrf24_ADDR_LEN); nrf24_writeRegister(TX_ADDR, adr, nrf24_ADDR_LEN); } /* Checks if data is available for reading */ /* Returns 1 if data is ready ... */ uint8_t nrf24_dataReady() { // See note in getData() function - just checking RX_DR isn't good enough uint8_t status = nrf24_getStatus(); // We can short circuit on RX_DR, but if it's not set, we still need // to check the FIFO for any pending packets if (status & (1 << RX_DR)) { return 1; } return !nrf24_rxFifoEmpty();; } /* Checks if receive FIFO is empty or not */ uint8_t nrf24_rxFifoEmpty() { uint8_t fifoStatus; nrf24_readRegister(FIFO_STATUS, &fifoStatus, 1); return (fifoStatus & (1 << RX_EMPTY)); } /* Returns the length of data waiting in the RX fifo */ uint8_t nrf24_payloadLength() { uint8_t status; nrf24_csn_digitalWrite(LOW); spi_transfer(R_RX_PL_WID); status = spi_transfer(0x00); return status; } /* Reads payload bytes into data array */ void nrf24_getData(uint8_t *data) { /* Pull down chip select */ nrf24_csn_digitalWrite(LOW); /* Send cmd to read rx payload */ spi_transfer(R_RX_PAYLOAD); /* Read payload */ nrf24_transferSync(data, data, payload_len); /* Pull up chip select */ nrf24_csn_digitalWrite(HIGH); /* Reset status register */ nrf24_configRegister(STATUS, (1 << RX_DR)); } /* Returns the number of retransmissions occured for the last message */ uint8_t nrf24_retransmissionCount() { uint8_t rv; nrf24_readRegister(OBSERVE_TX, &rv, 1); rv = rv & 0x0F; return rv; } // Sends a data package to the default address. Be sure to send the correct // amount of bytes as configured as payload on the receiver. void nrf24_send(uint8_t *value) { /* Go to Standby-I first */ nrf24_ce_digitalWrite(LOW); /* Set to transmitter mode , Power up if needed */ nrf24_powerUpTx(); /* Do we really need to flush TX fifo each time ? */ #if 1 /* Pull down chip select */ nrf24_csn_digitalWrite(LOW); /* Write cmd to flush transmit FIFO */ spi_transfer(FLUSH_TX); /* Pull up chip select */ nrf24_csn_digitalWrite(HIGH); #endif /* Pull down chip select */ nrf24_csn_digitalWrite(LOW); /* Write cmd to write payload */ spi_transfer(W_TX_PAYLOAD); /* Write payload */ nrf24_transmitSync(value, payload_len); /* Pull up chip select */ nrf24_csn_digitalWrite(HIGH); /* Start the transmission */ nrf24_ce_digitalWrite(HIGH); } uint8_t nrf24_isSending() { uint8_t status; /* read the current status */ status = nrf24_getStatus(); /* if sending successful (TX_DS) or max retries exceded (MAX_RT). */ if ((status & ((1 << TX_DS) | (1 << MAX_RT)))) { return 0; /* false */ } return 1; /* true */ } uint8_t nrf24_getStatus() { uint8_t rv; nrf24_csn_digitalWrite(LOW); rv = spi_transfer(NOP); nrf24_csn_digitalWrite(HIGH); return rv; } uint8_t nrf24_lastMessageStatus() { uint8_t rv; rv = nrf24_getStatus(); /* Transmission went OK */ if ((rv & ((1 << TX_DS)))) { return NRF24_TRANSMISSON_OK; } /* Maximum retransmission count is reached */ /* Last message probably went missing ... */ else if ((rv & ((1 << MAX_RT)))) { return NRF24_MESSAGE_LOST; } /* Probably still sending ... */ else { return 0xFF; } } void nrf24_powerUpRx() { nrf24_csn_digitalWrite(LOW); spi_transfer(FLUSH_RX); nrf24_csn_digitalWrite(HIGH); nrf24_configRegister(STATUS, (1 << RX_DR) | (1 << TX_DS) | (1 << MAX_RT)); nrf24_ce_digitalWrite(LOW); nrf24_configRegister(CONFIG, nrf24_CONFIG | ((1 << PWR_UP) | (1 << PRIM_RX))); nrf24_ce_digitalWrite(HIGH); } void nrf24_powerUpTx() { nrf24_configRegister(STATUS, (1 << RX_DR) | (1 << TX_DS) | (1 << MAX_RT)); nrf24_configRegister(CONFIG, nrf24_CONFIG | ((1 << PWR_UP) | (0 << PRIM_RX))); } void nrf24_powerDown() { nrf24_ce_digitalWrite(LOW); nrf24_configRegister(CONFIG, nrf24_CONFIG); } /* software spi routine */ uint8_t spi_transfer(uint8_t tx) { uint8_t i = 0; uint8_t rx = 0; nrf24_sck_digitalWrite(LOW); for (i = 0; i < 8; i++) { if (tx & (1 << (7 - i))) { nrf24_mosi_digitalWrite(HIGH); } else { nrf24_mosi_digitalWrite(LOW); } nrf24_sck_digitalWrite(HIGH); rx = rx << 1; if (nrf24_miso_digitalRead()) { rx |= 0x01; } nrf24_sck_digitalWrite(LOW); } return rx; } /* send and receive multiple bytes over SPI */ void nrf24_transferSync(uint8_t *dataout, uint8_t *datain, uint8_t len) { uint8_t i; for (i = 0; i < len; i++) { datain[i] = spi_transfer(dataout[i]); } } /* send multiple bytes over SPI */ void nrf24_transmitSync(uint8_t *dataout, uint8_t len) { uint8_t i; for (i = 0; i < len; i++) { spi_transfer(dataout[i]); } } /* Clocks only one byte into the given nrf24 register */ void nrf24_configRegister(uint8_t reg, uint8_t value) { nrf24_csn_digitalWrite(LOW); spi_transfer(W_REGISTER | (REGISTER_MASK & reg)); spi_transfer(value); nrf24_csn_digitalWrite(HIGH); } /* Read single register from nrf24 */ void nrf24_readRegister(uint8_t reg, uint8_t *value, uint8_t len) { nrf24_csn_digitalWrite(LOW); spi_transfer(R_REGISTER | (REGISTER_MASK & reg)); nrf24_transferSync(value, value, len); nrf24_csn_digitalWrite(HIGH); } /* Write to a single register of nrf24 */ void nrf24_writeRegister(uint8_t reg, uint8_t *value, uint8_t len) { nrf24_csn_digitalWrite(LOW); spi_transfer(W_REGISTER | (REGISTER_MASK & reg)); nrf24_transmitSync(value, len); nrf24_csn_digitalWrite(HIGH); } ``` Первая проверка без заднего хода: {{< youtube d0INQcEuwmg >}} Проверка с задним ходом: {{< youtube LrE-XA4EGXk >}} Геймпад: [Мануал по структурам пакетов геймпада](http://store.curiousinventor.com/guides/ps2) [Оригинальная библиотека для геймпада](http://digitalcave.ca/resources/avr/psx.jsp) nrf24: [Оригинальная библиоткека nRF24 на GitHub](https://github.com/kehribar/nrf24L01_plus)