Доброго времени суток!
Как и обещал я продолжу свой предыдущий пост более подробным описанием электронной начинки и ПО.
Лунь 1.0
Первый вариант конструкции был очень прост: stm32vl-discovery, пульт от древней радиоуправляемой машинки и 2 инвертора для двигателей. Если схематически то выглядело это как-то так:
В прошивке как и в компоновке тоже ничего сложного не было. Управлялось всё stm32vl-discovery, а код был написан за пару часов в CoIDE. Отдельно хочется выразить низкий поклон разработчикам этой среды программирования, если б не ее удобство то, кто знает, может я так и не перелез бы с AVR.
Реализация управления была простая но абсолютно неудобная. В СВП нужно было управлять тремя двигателями, а пульт давал возможность управлять только двумя. Пришлось как-то выкручиваться, кнопка “вперед” управляла тяговым винтом, кнопка “назад” останавливала судно, запускалось же судно нажатием кнопки на самой отладочной плате.
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_tim.h>
#include <misc.h>
void init_leds();
void init_motors();
void init_timer();
void init_button();
void Delay_sig();
int sec=0;
int sm1=1000;
int m1=750;
int m2=750;
int i,l;
uint8_t r1=0,r2=0,r3=0,r4=0;
#define first_motor GPIO_Pin_10
#define second_motor GPIO_Pin_12
#define servo_motor GPIO_Pin_11
#define blue_led GPIO_Pin_8
#define green_led GPIO_Pin_9
#define radio4 GPIO_Pin_8
#define radio3 GPIO_Pin_9
#define radio2 GPIO_Pin_10
#define radio1 GPIO_Pin_11
#define BUTTON GPIO_Pin_0
int main()
{
init_leds();
init_button();
init_motors();
init_timer();
SysTick_Config(SystemCoreClock /300);
do
{
r1 = GPIO_ReadInputDataBit(GPIOA,radio1);
r2 = GPIO_ReadInputDataBit(GPIOA,radio2);
r3 = GPIO_ReadInputDataBit(GPIOA,radio3);
r4 = GPIO_ReadInputDataBit(GPIOA,radio4);
}while (1);
}
void SysTick_Handler()
{
static uint8_t btn_old_state = 0;
uint8_t btn_state = GPIO_ReadInputDataBit(GPIOA, BUTTON);
if (btn_old_state == 0 && btn_state == 1)
{
if(m1<1000)m1=m1+50;
}
if(r1==1)
{
if(sm1<1600) sm1=sm1+10;
}
else
{
if(sm1>1000) sm1=sm1-10;
}
if(r3==1)
{
m2=900;
}
else
{
m2=750;
}
if(r2==1)
{
if(sm1>400) sm1=sm1-10;
}
else
{
if(sm1<1000) sm1=sm1+10;
}
if(r4==1)
{
m1=750;
}
btn_old_state = btn_state;
}
void init_leds()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Pin = blue_led|green_led;
GPIO_Init(GPIOC, &gpio);
GPIO_ResetBits(GPIOC, blue_led);
GPIO_ResetBits(GPIOC, green_led);
}
void init_motors()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Pin = first_motor|second_motor|servo_motor;
GPIO_Init(GPIOC, &gpio);
GPIO_ResetBits(GPIOC, first_motor);
GPIO_ResetBits(GPIOC, second_motor);
GPIO_ResetBits(GPIOC, servo_motor);
}
void init_button()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);
gpio.GPIO_Mode = GPIO_Mode_IPD;
gpio.GPIO_Pin = BUTTON|radio1|radio2|radio3|radio4|BT_en;
gpio.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &gpio);
}
void init_timer()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseInitTypeDef base_timer;
TIM_TimeBaseStructInit(&base_timer);
base_timer.TIM_Prescaler = 12000 — 1;
base_timer.TIM_Period = 20;
TIM_TimeBaseInit(TIM6, &base_timer);
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM6, ENABLE);
NVIC_EnableIRQ(TIM6_DAC_IRQn);
}
void Delay_sig()
{
int us=0;
for(us=0; us<5000; us++)
{
if(us==sm1){GPIO_ResetBits(GPIOC, servo_motor);}
if(us==m1) {GPIO_ResetBits(GPIOC, first_motor);}
if(us==m2){GPIO_ResetBits(GPIOC, second_motor);}
}
}
void TIM6_DAC_IRQHandler()
{
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
GPIO_SetBits(GPIOC, first_motor);
GPIO_SetBits(GPIOC, second_motor);
GPIO_SetBits(GPIOC, servo_motor);
Delay_sig();
}
}
Лунь 2.0
Наставив гору костылей как в программе так и в механической части нашего робота мы решили, что надо основательно всё переделывать.
Первым делом решили добавить стабильности и простоты управления. Для этого был куплен датчик MPU-6050 и модуль bluetooth HC-04.
C блютузом никаких проблем не возникло, а вот акселерометр почему-то работал неправильно. Сначала через 10 секунд работы он отключался, а потом и вовсе перестал отвечать.
Обдумав возможные варианты было решено заменить отладочную плату, а с ней и всю схему компоновки.
Получилось вот что:
Главным огорчением было узнать, что CoIDE не поддерживает программирование микроконтроллеров STM32F303. Не долго думая была установлена коммерческая сборка все того же eclipse — Atollic TrueSTUDIO. Lite версия была ограничена размером откомпилированной прошивки (не более 32КБ) нас это устроило и проект переехал в другую среду.
На сайте ST можно скачать исходники примеров для этой IDE, что тоже не мало нас обрадовало. Проблемы появились когда начали углубленно изучать код. Дело в том, что просто скопировать код из предыдущей прошивки нельзя, в STM32f3 в сравнении STM32f100 поменялись назначения многих регистров. Да и на тот момент мы нашли только примеры от производителя, было такое чувство, что никто вообще не занимался этой отладочной платой хотя она была уже год как в продаже. Проблем добавилось когда мы решили установить датчик влажности. Код для работы с датчиком я честно позаимствовал из статьи ”STM32 + DHT11”, но написанная до этого момента прошивка напрочь отказывалась работать с новым кодом, он просто не выполнялся. Никаких ошибок или предупреждений при компиляции не было но код упорно не хотел выполняться правильно, убив два дня и очень много нервных клеток выяснилось, что если отключить оптимизацию в настройках то все заработает. Но тут возникла проблема которую я совсем не ожидал, после отключения оптимизации размер прошивки стал превышать 32КБ, пришлось искать новую среду разработки. После некоторого времени поисков наткнулся на статью о настройке IDE на основе Eclipse. Осталось создать проект и опять переезжать.
Одновременно с покупкой блютуз модуля началась разработка приложения для андроид. До этого я ни разу не писал приложения для мобильных устройств поэтому начать было довольно таки сложно. Особенно меня выводило из себя непонимание как передвинутая в графическом редакторе кнопка вызывала ошибку и закрытие приложения.
Кроме програмных изменений много времени было выделено на переработку компоновки.
Фюзеляж, юбку и мачту с направляющим двигателем оставили из первой версии. Двигатель установили в вертикальное положение, а электронику сместили в одно место. Также для удобства подключения переферии развели коммутационную плату:
Приводить весь программный код в статье не вижу смысла так и думаю будет лучше если я уделю больше внимания моментам которые вызвали проблемы.
Какие задачи ставились перед программой:
0. Управлять сервоприводами и инверторами;
1. Считать данные из встроенный в отладочную плату датчиков;
2. Согласно показаниям корректировать курс;
3. Принимать и отправлять данные на телефон;
4. Останавливаться или объезжать препятствия на пути;
5. Искать подходящий маршрут движения;
6. Считывать данные из периферийных датчиков;
7. Не допускать слишком большого разряда батареи.
Пункт ноль был организован на ШИМ с тремя каналами. В итоге после выполнения кода должна была получаться вот такая картинка:
, где по вертикали одна клетка — 0.4 вольта, а по горизонтали одна клетка — 2.5 мс.
void TIM_Init()
{
uint16_t Channel1Pulse = 139, Channel2Pulse = 104, Channel3Pulse = 104;
TIM_Config();
/* TIM1 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
/* Time Base configuration */
TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) ((SystemCoreClock / 100000));
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = (1000);
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
/* Channel 1, 2,3 Configuration in PWM mode */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_Pulse = Channel1Pulse;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
TIM_OC2Init(TIM1, &TIM_OCInitStructure);
TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
/* TIM1 counter enable */
TIM_Cmd(TIM1, ENABLE);
/* TIM1 Main Output Enable */
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
Для изменения скорости надо было лишь написать:
TIM_OCInitStructure.TIM_Pulse = n;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
Первый пункт полностью решили примеры от производителя. Да я мог снять данные, но без обработки с ними сложно было что-то делать.
Графики отображают показания акселерометра без фильтров. Сначала идет крен, потом тангаж, дальше одновременно и крен, и тангаж.
Поэтому со вторым уже было сложнее, если исходные коды корректировки положения для одного датчика в сети еще присутствовали, то чтоб корректировка учитывала все три датчика пришлось поискать. После нескольких дней чтения википедии (AHRS, IMU, Кватернион, Углы Эйлера, Фильтр Калмана), гугления и просмотра исходников, чтения статей и форумов, мне во сне явился сам Рудольф Эмиль Калман и продиктовал ссылку на Британскую компанию которая занималась в общем-то тем же, что и я. Именно у них я и подглядел открытую библиотеку для обработки данных с датчиков. Там же у них лежит и небольшое описание и способ применения. Из него я взял формулы для вычисления из кватернионов крена, тангажа и рысканья.
float getPitch()
{
return atan2(2*(q2*q3 + q0*q1), q0*q0 — q1*q1 — q2*q2 + q3*q3);
}float getYaw()
{
return asin(-2*(q1*q3 — q0*q2));
}float getRoll()
{
return atan2(2*(q1*q2 + q0*q3), q0*q0 + q1*q1 — q2*q2 — q3*q3);
}
Когда СВП переходит в режим корректировки курса текущее положение принимается за точку отсчета:
nullkorr=getRoll()*DegToRadIMU;
Далее определяется таким же способом текущее положение и высчитывается угол между точкой отсчета и текущим положением.
float retangle(float a,float b)
{
a+=180;
b+=180;
int r1=0;
r1 = a-b;
r1=r1%360;
if(r1 < 0) r1 += 360;
if(r1 > 180) return -(360 — r1);
else return r1;
}
После вычисления угла данные передаются на сервопривод где от текущего угла поворота отнимается корректирующий угол.
С третьим пунктом никаких проблем не было, примеры работы с UART или с HC-04 не составляет труда.
С четвертым пунктом были некоторые неприятности, ультразвуковой датчик приехал одновременно с датчиком влажности и я маялся поиском новой среды разработки. Да и тестирование датчика разрушило мои розовые мечты о том что он бьет по прямой узким лучом и отражается обратно независимо от материала и угла поворота поверхности. Чтоб не грузить МК лишними задачами общение с датчиком организовано на прерываниях. Пример взят у этого хорошего человека в этом месте. Код пришлось немного переделать так как в STM32f3 немного переиначили таймеры.
/**
**===========================================================================
**
** Abstract: Ultrasonic sensor interrupt handler
**
**===========================================================================
*/
void EXTI3_IRQHandler(void)
{
// Если поймали нарастающий фронт
if (!catcher_status)
{
// Запускаем отсчёт длительности импульса
TIM6->CR1 |= TIM_CR1_CEN;
// Переключаемся на отлов спадающего фронта
catcher_status = 1;
EXTI->RTSR &= ~EXTI_RTSR_TR3;
EXTI->FTSR |= EXTI_FTSR_TR3;
}
// Если поймали спадающий фронт
else
{
TIM6->CR1 &= ~TIM_CR1_CEN; // Останавливаем таймер
if(TIM6->CNT>58)
{
duration = TIM6->CNT; // Считываем значение длительности в мкс
if(duration<5800 && korr<25 && korr>-25 && stepmode==5)
{
m1=0;
upper_ctrl(m1); //motor off
bbf=0;
stepmode=1;
}
smas[scnum]=TIM6->CNT;
}
TIM6->CNT = 0; // Обнуляем регистр-счётчик
// Переключаемся на отлов нарастающего фронта
catcher_status = 0;
EXTI->FTSR &= ~EXTI_FTSR_TR3;
EXTI->RTSR |= EXTI_RTSR_TR3;
// Запускаем таймер 6 на отсчёт 50 мс
TIM6->DIER |= TIM_DIER_UIE; // Разрешаем прерывание от таймера
TIM6->CR1 |= TIM_CR1_CEN; // Запускаем таймер
}
EXTI->PR |= 0x01; //Очищаем флаг
}
/**
**===========================================================================
**
** Abstract: Ultrasonic sensor interrupt handler
** Обработчик прерывания TIM7
** Вызывается после того, как таймер 7 отсчитал 10 мкс для сигнального импульса
**===========================================================================
*/
void TIM7_IRQHandler(void)
{
TIM7->SR &= ~TIM_SR_UIF; // Сбрасываем флаг UIF
GPIOD->ODR &= ~GPIO_Pin_2; // Останавливаем сигнальный импульс
TIM7->DIER &= ~TIM_DIER_UIE; // Запрещаем прерывание от таймера 7
}
/**
**===========================================================================
**
** Abstract: Ultrasonic sensor interrupt handler
** Обработчик прерывания TIM6_DAC
** Вызывается после того, как таймер 6 отсчитал 50 мкс для периода цикла
**===========================================================================
*/
void TIM6_DAC_IRQHandler(void)
{
TIM6->SR &= ~TIM_SR_UIF; // Сбрасываем флаг UIF
if(scaning==2)
{
if(smas[scnum]>800)
{
scnum++;
US_servo_ctrl(scnum);
if(scnum>=160)
{
scnum=0;
scaning=3;
US_servo_ctrl(80);
}
}
else
{
scd++;
if(scd>3)
{
scd=0;
smas[scnum]=23200;
scnum++;
US_servo_ctrl(scnum);
}
}
}
GPIOD->ODR |= GPIO_Pin_2; // Включаем сигнальный импульс
// Запускаем таймер 7 на отсчёт 10 мс
TIM7->DIER |= TIM_DIER_UIE; // Разрешаем прерывание от таймера 7
TIM7->CR1 |= TIM_CR1_CEN; // Запускаем таймер
}
void USsensor_init()
{
//========================================================================
// Настройка таймера 6
// Используется для 2-х целей:
// 1) Подсчёт длительности Echo импульса (150 мкс — 25 мс)
// 2) Подсчёт с прерыванием для отчёта периода цикла — времени,
// необходимого для затухания остаточных колебаний в линии Echo
//========================================================================
// Включаем тактирование таймера
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
// Выставляем предделитель на срабатывание раз в мкс
TIM6->PSC = 72 — 1;
// Граница срабатывания — 50 мс = 50 000 мкс
TIM6->ARR = 50000;
//Разрешение TIM6_IRQn прерывания — необходимо для отсчёта периода цикла
NVIC_SetPriority(TIM6_DAC_IRQn, 3);
NVIC_EnableIRQ(TIM6_DAC_IRQn);
//========================================================================
// Настройка таймера 7
//========================================================================
// Включаем тактирование таймера
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
// Выставляем предделитель на срабатывание раз в мкс
TIM7->PSC = 72 — 1;
// Граница срабатывания — 10 мкс
TIM7->ARR = 10;
//Разрешение TIM7_IRQn прерывания — необходимо для отсчёта сигнального импульса
NVIC_SetPriority(TIM7_IRQn, 2);
NVIC_EnableIRQ(TIM7_IRQn);
//========================================================================
EXTI_InitTypeDef EXTI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOD, &GPIO_InitStructure);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOD, EXTI_PinSource3);
/* Configure EXTI3 line */
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
В этом коде используется прерывание на PORTD.3, точнее оно должно быть на PORTD, я так и не разобрался почему прерывание срабатывает на всех портах на третьем пине. Использование прерывания взято из примера от производителя, была предпринята попытка писать напрямую в регистры настройки прерываний, результата “0” либо прерывания нет, либо оно есть на всех портах.
Если кто-то объяснит где ошибка буду очень благодарен.
Пятый пункт был быстро пройден, а вот настройка АЦП в шестом причинила некоторую головную боль.
Опять же примеры, никаких нареканий нет, пример работает отлично. Только в примере они рассматривали считывание из одного канала АЦП, а у меня их четыре. В даташите расписывается о каналах, регулярных группах, введенных группах. Из-за ограниченного времени был выбран старый “дедовский” метод:
-Выбор канала;
-Запуск одноразового считывания;
-Обработка результата;
-Повтор.
{
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
ADC1ConvertedValue =ADC_GetConversionValue(ADC1);
/* Compute the voltage */
ADC1ConvertedVoltage = (ADC1ConvertedValue *3300)/0xFFF;
adcp[iadc]= ADC1ConvertedVoltage;
iadc++;
ADCready=1;
}
void SetADCchannel()
{
/*Configure ADC channel */
numadc[0]=7; //BT
numadc[1]=16; //Termometer
numadc[2]=6; // Battery voltage
numadc[3]=3; // Gas sensor
kadc=4; // amount channels
}
void getADC(uint8_t channel)
{
if(ADCready==1)
{
ADCready=0;
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime);
ADC_StartConversion(ADC1);
}
}
uint8_t ADC_init()
{
uint16_t calibration_value = 0;
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef ADC_CommonInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure the ADC clock */
RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2);
/* Enable ADC1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE);
/* ADC Channel configuration */
/* GPIOC Periph clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
/* Configure ADC Channel7 as analog input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_StructInit(&ADC_InitStructure);
/* Calibration procedure */
ADC_VoltageRegulatorCmd(ADC1, ENABLE);
/* Insert delay equal to */
Delay(15);
ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) != RESET );
calibration_value = ADC_GetCalibrationValue(ADC1);
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_Clock = ADC_Clock_AsynClkMode;
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStructure.ADC_DMAMode = ADC_DMAMode_OneShot;
ADC_CommonInitStructure.ADC_TwoSamplingDelay = 1000;
ADC_CommonInit(ADC1, &ADC_CommonInitStructure);
ADC_InitStructure.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Disable;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0;
ADC_InitStructure.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_OverrunMode = ADC_OverrunMode_Disable;
ADC_InitStructure.ADC_AutoInjMode = ADC_AutoInjec_Disable;
ADC_InitStructure.ADC_NbrOfRegChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelSequencerLengthConfig(ADC1,1);
/* ADC1 regular channel7 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime);
ADC_TempSensorCmd(ADC1,ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* wait for ADRDY */
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY));
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
return 1;
}
/* и где-то в main разместить
if(iadc<kadc)
{
getADC(numadc[iadc]);
}
else
{
iadc=0;
}
*/
После того как разобрался с АЦП седьмой пункт проблем не вызвал.
Приложение для андроида особых трудностей и потерь со стороны нервных не вызвало (не считая вылетов от изменения местоположения кнопки) примеров для работы с блютуз в сети предостаточно, а немного измененный класс из этой статьи добавил возможность управлять скоростью и направлением судна с помощью передвижения пальца вверх/вниз и влево/вправо по экрану.
public class SomeView extends View {
Paint paint;
int[] X;
int[] Y;
final static int Radius=50;
int PointerCount;
public SomeView(Context context, AttributeSet attrs)
{
super(context, attrs);
paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(3);
PointerCount=0;
X=new int[10];//Это будут массивы координат(будем воспринимать до 10 пальцев)
Y=new int[10];
}
public boolean onTouchEvent(MotionEvent event)
{
StringBuilder result=new StringBuilder(300);
PointerCount=event.getPointerCount();
for(int i=0;i<PointerCount;i++)
{
int ptrId=event.getPointerId(i);
X[i]=(int) event.getX(i);// Запоминаем координаты
Y[i]=(int) event.getY(i);
MainActivity.flag=1;
MainActivity.setX(X[i], Y[i],ptrId);
}
return true;
}
protected void onDraw(Canvas canvas)
{
for(int i=0;i<PointerCount;i++)
{
if(Y[i]<(MainActivity.height/4))Y[i]=MainActivity.height/4;
if(Y[i]>(MainActivity.height*0.75)-75)Y[i]=(int) (MainActivity.height*0.75)-75;
canvas.drawCircle(X[i], Y[i], Radius, paint);
canvas.drawLine(MainActivity.width/2, (int) (MainActivity.height*0.75)-25, X[i], Y[i], paint);
canvas.drawLine(0, (int) (MainActivity.height/4)-50, MainActivity.width, (int) (MainActivity.height/4)-50, paint);
canvas.drawLine(0, (int) (MainActivity.height*0.75)-25, MainActivity.width, (int) (MainActivity.height*0.75)-25, paint);
}
invalidate();
}
}
А для вывода информации о датчиках был использован DialogFragment:
public class dialog extends DialogFragment implements OnClickListener {
final String LOG_TAG = «myLogs»;
android.widget.TextView datch;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().setTitle(«Показания датчиков»);
View v = inflater.inflate(R.layout.dialog, null);
v.findViewById(R.id.btnYes).setOnClickListener(this);
datch = (android.widget.TextView)v.findViewById(R.id.textView1);
datch.setText(MainActivity.mesdat);
return v;
}
public void onClick(View v) {
Log.d(LOG_TAG, «Dialog: » + ((Button) v).getText());
dismiss();
}
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Log.d(LOG_TAG, «Dialog: onDismiss»);
}
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
Log.d(LOG_TAG, «Dialog: onCancel»);
}
}
Послесловие
Я знаю, что процесс создания охватил лишь в общих чертах но если будут вопросы я обязательно постараюсь на них ответить. Программа управления далека от идеала, еще надо много чего учесть. Например, при резком повороте инерция судна заставляет его «вилять хвостом» еще метров семь. Но мы будем продолжать и совершенствовать нашу работу.
Автор: X4ZiM