Перед началом, хотелось бы сразу оговориться, что код написан не мной и взят отсюда. Данная программа написана в среде Arduino IDE и в связке с arduino pro mini и nrf24l01+ позволяет управлять игрушечными мультикоптерами (с радиочипами XN297, клонами nrf24l01) с любых аппаратур управления, на которых есть PPM выход. Всю информацию о поддерживаемых мультикоптерах можно найти по ссылке выше.
Я решил переписать данный код для управления коптером Eachine H8 mini с аппаратуры Radiolink AT9. За подробностями прошу под кат.
MSP430 был выбран потому, что у него напряжение питания 3.3 вольта, напряжение питания nrf24l01 также 3.3 вольта, да и MPS430 мне как-то больше нравятся. Внутри аппаратуры располагаются контакты 3V3, OUT, GND, к которым мы и будем подключаться.
Если нет желания разбирать аппаратуру, то можно подключится к тренерскому разъему, но напряжение на нем = напряжению аккумулятора, поэтому в схему добавляется стабилизатор напряжения.
Приступим к коду
Первым делом откроем проект в IDE Energia (клон Arduino IDE для MSP430 и других чипов от TI) и попытаемся его скомпилировать, но сразу же увидим ошибку компиляции. Данный проект использует дополнительные библиотеки и обращение к регистрам, и первым делом нужно начинать с них. Итак, приступим к детальному анализу.
Библиотеки
Править будем файл nRF24_multipro.ino и начнем с include’ов. В данном проекте используются библиотеки atomic и EEPROM.
atomic
Убираем строку
#include <util/atomic.h>
и в функции void update_ppm() убираем
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
Вместо фигурных скобок пишем
__disable_interrupt();
и
__enable_interrupt();
Функция ATOMIC_BLOCK() выключает прерывания, пока исполняется код в его «теле» и в зависимости от параметра, включает прерывания или восстанавливает значение флага прерывания в состояние, которое было до вызова функции ATOMIC_BLOCK().
EEPROM
В MSP430 нет EEPROM памяти, но есть FLASH память и для работы с ней есть библиотека MspFlash. Поэтому убираем строку
#include <EEPROM.h>
и добавляем библиотеку MspFlash (Sketch->Import Library…->MspFlash), добавляем в какой сегмент FLASH памяти мы будем записывать данные
#define flash SEGMENT_D
В функции void selectProtocol() меняем строки
else current_protocol = constrain(EEPROM.read(ee_PROTOCOL_ID),0,PROTO_END-1); // update eeprom EEPROM.update(ee_PROTOCOL_ID, current_protocol);
на
// update eeprom Flash.write(flash+ee_PROTOCOL_ID, & current_protocol,1);
Убираем полностью чтение идентификатора протокола (зачем, читаем ниже).
В фунции void set_txid(bool renew) нужно сделать немного больше, чем заменить две строки. В FLASH памяти мы можем только обнулять биты. Для выставления в битах FLASH памяти значения 1, нужно стереть весь сегмент (тогда в него запишутся значения 1). Данная функция (set_txid) вызывается раньше чем selectProtocol, поэтому стирать сегмент мы будем тут.
Было:
void set_txid(bool renew) { uint8_t i; for(i=0; i<4; i++) transmitterID[i] = EEPROM.read(ee_TXID0+i); if(renew || (transmitterID[0]==0xFF && transmitterID[1]==0x0FF)) { for(i=0; i<4; i++) { transmitterID[i] = random() & 0xFF; EEPROM.update(ee_TXID0+i, transmitterID[i]); } } }
Стало:
void set_txid(bool renew) { uint8_t i; unsigned char p; for(i=0; i<4; i++) { Flash.read(flash+ee_TXID0+i,&p,1); transmitterID[i] =p; } Flash.read(flash+ee_PROTOCOL_ID,&p,1); current_protocol = constrain(p,0,PROTO_END-1); Flash.erase(flash); if(renew || (transmitterID[0]==0xFF && transmitterID[1]==0x0FF)) { for(i=0; i<4; i++) { transmitterID[i] = random(0xff) & 0xFF; p = transmitterID[i]; Flash.write(flash+ee_TXID0+i, &p,1); } }else{ for(i=0; i<4; i++) { p = transmitterID[i]; Flash.write(flash+ee_TXID0+i, &p,1); } } }
Тут мы считываем значение идентификаторов передатчика и протокола, стираем сегмент, и если дана команда на обновление идентификатора передатчика, записываем новое значение, иначе записываем старое. Байт FLASH памяти с адресом flash+ee_PROTOCOL_ID остался чистым (0xFF), поэтому в функции selectProtocol() мы его и не читаем, а сразу записываем туда идентификатор протокола.
Регистры
Для начала разберемся с пинами. В данном проекте используется программная реализация SPI, при котором используются макросы для «дергания» ногами через регистры.
#define PPM_pin 2 // PPM in //SPI Comm.pins with nRF24L01 #define MOSI_pin 3 // MOSI - D3 #define SCK_pin 4 // SCK - D4 #define CE_pin 5 // CE - D5 #define MISO_pin A0 // MISO - A0 #define CS_pin A1 // CS - A1 #define ledPin 13 // LED - D13
на
#define PPM_pin P1_5 // PPM in //SPI Comm.pins with nRF24L01 #define MOSI_pin P2_0 // MOSI #define SCK_pin P2_1 // SCK #define CE_pin P2_2 // CE #define MISO_pin P2_3 // MISO #define CS_pin P2_4 // CS #define ledPin P1_4 // LED
а так же
#define MOSI_on PORTD |= _BV(3) // PD3 #define MOSI_off PORTD &= ~_BV(3)// PD3 #define SCK_on PORTD |= _BV(4) // PD4 #define SCK_off PORTD &= ~_BV(4) // PD4 #define CE_on PORTD |= _BV(5) // PD5 #define CE_off PORTD &= ~_BV(5) // PD5 #define CS_on PORTC |= _BV(1) // PC1 #define CS_off PORTC &= ~_BV(1) // PC1 // SPI input #define MISO_on (PINC & _BV(0)) // PC0
на
#define MOSI_on P2OUT |= _BV(0)// P2_0 #define MOSI_off P2OUT &= ~_BV(0)// P2_0 #define SCK_on P2OUT |= _BV(1)// P2_1 #define SCK_off P2OUT &= ~_BV(1)// P2_1 #define CE_on P2OUT |= _BV(2)// P2_2 #define CE_off P2OUT &= ~_BV(2)// P2_2 #define CS_on P2OUT |= _BV(4)// P2_4 #define CS_off P2OUT &= ~_BV(4) // P2_4 // SPI input #define MISO_on (P2IN & _BV(3)) // P2_3
В МК от ATMEL за состояние выхода отвечают регистры PORTx в MSP430 PxOUT. За состояние входа- регистры PINx и PxIN соответственно. Кстати, функции _BV(x) в IDE Energia нет, поэтому добавим ее сами:
#define _BV(val) 1<<val
В функции void setup() меняем значения пинов аналоговых входов на свободные с
randomSeed((analogRead(A4) & 0x1F) | (analogRead(A5) << 5));
например на
randomSeed((analogRead(A0) & 0x1F) | (analogRead(A1) << 5));
В MSP430 аналоговые входы A0, A1, A2, …, A7 соответствуют пинам P1_0, P1_1,P1_2, …, P1_7.
При подключении прерывания меняем
attachInterrupt(PPM_pin - 2, ISR_ppm, CHANGE);
на
attachInterrupt(PPM_pin , ISR_ppm, CHANGE);
В Arduino Uno, Nano, Mini, и др. на mega328 для подключения прерывания доступны только 2 пина (2, 3) и в функции attachInterrupt первым аргументом является номер прерывания, а не пин как у MSP430. Более подробно про attachInterrupt().
Таймер
Меняем в void setup()
TCCR1A = 0; //reset timer1 TCCR1B = 0; TCCR1B |= (1 << CS11); //set timer1 to increment every 1 us @ 8MHz, 0.5 us @16MHz
На
TACTL = TASSEL_2 + ID_3 + MC_2 + TACLR; //16000000 / 8
Таймер нужен для определения длительности импульсов в PPM. Устанавливаем его в режим прямого счета с частотой 2МГц (тактовая частота 16МГц и делитель на 8).
В функции void ISR_ppm() меняем
counterPPM = TCNT1; TCNT1 = 0;
на
counterPPM = TAR; TAR = 0;
Регистры TCNT1 и TAR счетчики таймера.
random()
Не знаю почему, но функция random() без аргументов в Energia не вызывается, но так как нам необходимо случайное однобайтное число, то заменяем в файлах nRF24_multipro.ino и Bayang.ino
random() & 0xFF;
на
random(0xFF);
и
random() % 0x42;
на
random(0x41);
???
Ну и напоследок в файле MJX.ino в функции void MJX_bind() добавляем скобочки к вызову функции mjx_init2;
Компилируем проект и получаем успешный вывод.
Исходный код проекта
Измененный код проекта
Для успешного переноса программы на другую платформу необходимо понять что, как и зачем выполняется в данном коде, разобраться в особенностях подключенных библиотек, назначении регистров и в ходе работы чаще заглядывать в лог компиляции, для поиска ошибок.
Ну а если делать совмсем по хорошему, то надо переносить проект в другую среду разработки, подключать аппаратный SPI и работать только с регистрами, но это совсем другая история.
ссылка на оригинал статьи https://geektimes.ru/post/273948/
Добавить комментарий