Перед началом, хотелось бы сразу оговориться, что код написан не мной и взят отсюда. Данная программа написана в среде 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 и работать только с регистрами, но это совсем другая история.
Автор: winKING