Создание собственной системы стабилизации

SergDoc

Блин тоска смертная, может начать рисовать что-нибудь, или купить что-нибудь ненужное, тита мелковия…

mahowik
rual:

Александр, мою сборку не пробовал? Интересно твоё сравнение с вием. Мне сравнить не с чем. Вообще аксель мотает жутко от вибраций. Хочется понять можно ли что то улучшить алгоритмом или нужен другой датчик.

в отпуск как маньяк прихватил с собой ф3 и ноут… т.к. в дистрибутивах был только coocox пробовал под него настроить твой проект… после некоторых мучений таки скомпилил, но hex получался нулевой… 😃 я java-ст и в сишных компиллерах/линкерах/билдерах как свиня в апельсинах 😃
под конец отпуска разжился платным инетом и таки скачал Keil, собрал твой проект и даж покрутил в вийном гуи )) кстати, почему ты выбрал вийный протокол, а не мавлинк тот же?!
по приезду работа… на этом все и закончилось…

по акселю могу посоветовать зажать его по самое не могу на 0.2-0.5 гц софтовым фильтром и соот-но дать доверия на макс. гире, т.е. так что бы только корректировать дрейф… нормально когда после дикой болтаники платы, горизонт полностью выходит в ноль через 2-4 сек…

если уж это не поможет, подруби mpu6050 и сравни… благо они сейчас на развес по 4-5$

mahowik

Еще вот вспомнил. Некоторые аксели плохенькие, с уменьшением частоты среза внутреннего НЧ фильтра банально уменьшают самплинг (имел дело когда то и намучался с ADXL345). В итоге получается к примеру на 10гц всего 20 самплов и при вибрациях, результат только хуже, т.к. выборки идут из всплесков шума. Потому надо поднимать самплинг хотя бы до 100гц (лучше 200гц) и забить на внутренний НЧ, точнее установить его так, что бы было честных 100 самплов. А далее уже читать эти raw самплы и давить софтовым НЧ фильтром. Как писал выше на ~0.2-0.5гц и отдавать в ИМУ…
Ну а в нормальных акселях, при уменьшении внутреннего НЧ фильтра, самплы не режутся и делается честное усреднение заданного кол-ва самплов на выбранный НЧ фильтр. В частности переход на bma020 тогда дал совсем другую картинку…
Также надо не забывать, что диапазон должен быть не менее 8g, дабы избежать переполнения на внутреннем АЦП акселя.
И последний печальный момент. У некоторых акселей, есть свои внутренние физические резонансы, которые ну никак не убрать. Тогда только менять на другой… mpu6050 ну просто песня в этом плане! 😃

SergDoc

да я уже описывал, мпу против лсм330 небо и земля, в лсм задушенном фильтрами acc_lpf_factor = 250, в мпу - это практически сырые данные… гиры работают примерно одинаково - не взлетал на мпу…

oleg70

Если кто нибудь имеет “инфу” по NMEA0183 поделитесь пожалуйста… Погуглив нашел массу ссылок и англ. и русскоязычных, но для построения алгоритма парсера в них не хватает важных тонкостей типа свойства конкретного поля изменять длину, или “вес” символа до и после точки…
Например в $GPVTG поле скорости км/ч имеет вид ,0.00, (?.??), это что до сотых долей что ли выдается?
Понял только что версий много (ну уж не одна), а где взять документацию? (не ужели опять за деньги, как где то встречал?)

SergDoc

Т.к. платы пока нет и программировать пока нечего (даже пытаться не хочется), начал рисовать реинкорнацию мелкоплаты. Из названия - размер 36X36, ну если удастся то ещё и меньше.
проц stm32F405(407)RGT6
гироаксель MPU6050 - из-за размера не смогу позволить SPI да и дешевле
барометр MS5611
компас HMC5883 подключен через MPU
вход один
выходов 8-10 (как выйдет)
2 UART
1 -2 SPI
АЦП
флешка (25P16)
Разъём сонара
возможны дополнительно свободные i2с и can
Пожелания имеются?

rual
mahowik:

почему ты выбрал вийный протокол, а не мавлинк тот же?!

мавлинк мне сильно сложным показался по началу, вий проще было портировать. Тем более ранее возможности мавлика были на пару голов выше моих потребностей. Теперь нахожу мавлинк весьма продуманным и удобным, кроме того есть полное описание. Но пока цейтнот из-за конкурса и дачных проблем. Как будет время буду портировать мавлика.

mahowik:

если уж это не поможет, подруби mpu6050 и сравни…

Так и сделаю, только вот нет у меня девплаты под МПУ, почта ходит хреново, а самому делать лень.

SergDoc:

Пожелания имеются?

Для мелких особых расширений не нужно, может только СПИ для сурагатного ОСД или под оптикфлоу.

oleg70:

Если кто нибудь имеет “инфу” по NMEA0183 поделитесь пожалуйста…

yug-gps.narod.ru/docs/000x/st007.htm все величины в десятичных дробях , кроме градусов долготы-широты

SergDoc
rual:

Для мелких особых расширений не нужно, может только СПИ для сурагатного ОСД или под оптикфлоу.

короче, пока получается всё наоборот, i2c оно же uart3 выведены в отдельный порт, датчики по SPI (маг через MPU6000, лапу прерывания с магнитометра наверно нет смысла выводить? всё равно всё в регистрах MPU будет) всё лишнее (eeprom flash) убрал нафиг, СПИ выведен но не для ОСД, а скорей для управления этой крохой (малина или ещё что), платка в принципе самодостаточная, но задумка есть использовать её как спинной мозг с мозжечком, а управление отдать чему-то другому.
8 выходов, 2 из которых могут быть i2c, а ещё 2 can, вход один, 2 входа АЦП, разъём под сонар, выход на пищалку, USB (наверно придётся ставить микро, мини полплаты занимает 😦 ), ну и вездессущий вездесущий uart1, контактные площадки под ST Link, всё больше ничего. рисую просто так, для занятся чем - нибудь, до изготовления когда дело дойдёт - незнаю, по готовности выкину всё в гит, может кому интересно будет…

rual
SergDoc:

задумка есть использовать её как спинной мозг с мозжечком

На Ф4 увесистый мозжечек получается))) Ф1 конечно скучно, но мож Ф3?

SergDoc

Ну во первых, платы хочу совместимые, что маленькая, что большая, во вторых - не оставляет надежда на полный фильтр Калмана, а там Ф4 придётся как нельзя лучше, ну и третье ф3 - не видел и не знаю, а с Ф4 более менее познакомился 😃 чесно - я так устал, у меня на протяжении всей этой истории косяк за косяком, как буд-то злой рок какой-то, всё чёрт знает как: управление отказывало, микросхемы ломал, жег, то вышлют то не вышлют, то вообще запрет на штатовские детали (полупроводники), как следствие доставать всё (все комплектующие) сложнее, да и штатных (нештатных) ситуаций сколько, батарейки дохнут, моторы отказывают. Тут наверно в пору бросит всё, но нет - доведу до конца, надо, да и упрямство сильнее 😃

SergDoc

для большей платы вот эту штуку взять или дорого? не 80 см маловать, но вместо сонара для автопосадки пойдёт?

DVE

И Наза и Х4 отлично садятся без каких-либо внешних датчиков, по изменению высоты с барометра наверно.

Имхо интереснее копать в сторону программной реализации.

SergDoc

Ну если медленно и аккуратно, то можно и по барометру, а если экстренно, то при быстром снижении надо притормозить перед касанием, а по барометру разброс показаний около земли будет около 30-40см - не хотелось бы впилить в планету со всей дури… ещё на мелкоплате экспериментировал с сонаром - катался на экране 10-15 см лапами от поверхности - прикольно катится как по льду - высоту не меняет 😃

rual
DVE:

И Наза и Х4 отлично садятся без каких-либо внешних датчиков, по изменению высоты с барометра наверно. Имхо интереснее копать в сторону программной реализации.

Если аксель и баро адекватны, то для самого процесса посадки достаточно управлять вертикальной скоростью. Сонар больше нужен просто для определения самого наличия поверхности под аппаратом, что важно для полной автоматизации полёта. Или для автопосадки самолётов, там мягкой посадки без него не реализовать.

SergDoc

Ну вот к примеру - пусть таже наза, как известно спускается она с трудом, имеем разбежку высот старта и посадки ну метров 15-20, ветер - что мы видим аппарат спустился до высоты точки старта - из-за ветра скорость спуска =0 - о да мы сели!!! и с 20 метров в асфальт, или что там под аппаратом…

DVE

Не, замеряется наверно именно скорость снижения, т.е. текущая дельта по барометру, а не разность давлений взлет/посадка.

rual
DVE:

Не, замеряется наверно именно скорость снижения, т.е. текущая дельта по барометру, а не разность давлений взлет/посадка.

Как там делается я не знаю, но при старте надо бы засекать высоту места, только вот теоретически за время полёта может измениться давление и дрейф ГПС и баро. Посему нужно снижаться до тех пор, пока интегральная составляющая вертикального ПИД не уйдёт существенно в минус, т.е. упёрлись в планету.

SergDoc

Я раньше видео выкладывал - не мог посадить из-за бокового ветра, газ практически в 0 а аппарат висит на одной высоте, пока крен против ветра не дал - тогда только ветром и прижало к земле…

oleg70

Есть у кого опыт работы с USART по передаче данных с чипа на чип ?
Похоже у меня скорости не совпадают, с контроллера полета на OSD еще более-менее, а с GPS на контроллер оч. плохо…
Делал то же самое (с тем же GPS) на AVRe, вроде стабильно было, скорость минимум -> 9600, провод 25 см. всего…
Видимо STM по капризней…

rual
oleg70:

Видимо STM по капризней…

Не правда ваша, нет разницы между меж процевым и обменом с внешним компом. СТМ отлично работает.

SergDoc

Сделайте прверку на окончание передачи, перед посылкой следующего пакета, ну и на оборот пакет принят - прерывание… писал же выше (правда уарт+дма) пока не сделал проверку на окончание передачи - висяк был, хотя в 103-м этого не требовалось…
вот драйвер рабочий под ф4 думаю и под ф3 пойдёт, 3 уарта:

#include "board.h"

/*
    DMA UART routines idea lifted from AutoQuad
    Copyright © 2011  Bill Nesbitt
*/
#define UART_BUFFER_SIZE    512

// Receive buffer, circular DMA
volatile uint8_t rxBuffer[UART_BUFFER_SIZE];
uint32_t rxDMAPos = 0;
volatile uint8_t txBuffer[UART_BUFFER_SIZE];
uint32_t txBufferTail = 0;
uint32_t txBufferHead = 0;

static void uartTxDMA(void)
{
    //while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) {
		//	};
		DMA2_Stream7->M0AR = (uint32_t)&txBuffer[txBufferTail];
    if (txBufferHead > txBufferTail) {
      //DMA_SetCurrDataCounter(DMA2_Stream7, txBufferHead - txBufferTail);
			DMA2_Stream7->NDTR = txBufferHead - txBufferTail;
        txBufferTail = txBufferHead;
    } else {
      //DMA_SetCurrDataCounter(DMA2_Stream7, UART_BUFFER_SIZE - txBufferTail);
			DMA2_Stream7->NDTR = UART_BUFFER_SIZE - txBufferTail;
        txBufferTail = 0;
    }

    DMA_Cmd(DMA2_Stream7, ENABLE);
}

void DMA2_Stream7_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA2_Stream7,DMA_IT_TCIF7))
  {
    /* Очищаем бит обработки прерывания */

    DMA_ClearITPendingBit(DMA2_Stream7,DMA_IT_TCIF7);
    DMA_Cmd(DMA2_Stream7, DISABLE);

    if (txBufferHead != txBufferTail)
			       uartTxDMA();
	}

}


void uartInit(uint32_t speed)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // USART1_TX    PA9
    // USART1_RX    PA10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

    // DMA TX Interrupt
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    USART_InitStructure.USART_BaudRate = speed;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);

    // Receive DMA into a circular buffer
    DMA_DeInit(DMA2_Stream5);
		while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) {
			};
		DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)rxBuffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_BufferSize = UART_BUFFER_SIZE;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
		DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_Init(DMA2_Stream5, &DMA_InitStructure);

    DMA_Cmd(DMA2_Stream5, ENABLE);
    USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
    rxDMAPos = DMA_GetCurrDataCounter(DMA2_Stream5);

    // Transmit DMA
    DMA_DeInit(DMA2_Stream7);
		while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) {
			};
		DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
		DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_Init(DMA2_Stream7, &DMA_InitStructure);
    DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);
    DMA2_Stream7->NDTR = 0;
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

    USART_Cmd(USART1, ENABLE);
}

uint16_t uartAvailable(void)
{
    return (DMA_GetCurrDataCounter(DMA2_Stream5) != rxDMAPos) ? true : false;
}

bool uartTransmitEmpty(void)
{
    return (txBufferTail == txBufferHead);
}

uint8_t uartRead(void)
{
    uint8_t ch;

    ch = rxBuffer[UART_BUFFER_SIZE - rxDMAPos];
    // go back around the buffer
    if (--rxDMAPos == 0)
        rxDMAPos = UART_BUFFER_SIZE;

    return ch;
}

uint8_t uartReadPoll(void)
{
    while (!uartAvailable()); // wait for some bytes
    return uartRead();
}

void uartWrite( uint8_t ch)
{
	  while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE) {
			};
    txBuffer[txBufferHead] = ch;

    txBufferHead = (txBufferHead + 1) % UART_BUFFER_SIZE;


    // if DMA wasn't enabled, fire it up
    if (!(DMA2_Stream7->CR & 1))
        uartTxDMA();
}

void uartPrint(char *str)
{
    while (*str)
        uartWrite(*(str++));
}


/* -------------------------- UART2  ----------------------------- */
uartReceiveCallbackPtr uart2Callback = NULL;
#define UART2_BUFFER_SIZE    128

volatile uint8_t tx2Buffer[UART2_BUFFER_SIZE];
uint32_t tx2BufferTail = 0;
uint32_t tx2BufferHead = 0;
bool uart2RxOnly = false;

static void uart2Open(uint32_t speed)
{
    USART_InitTypeDef USART_InitStructure;

    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = speed;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | (uart2RxOnly ? 0 : USART_Mode_Tx);
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2, ENABLE);


}

void uart2Init(uint32_t speed, uartReceiveCallbackPtr func, bool rxOnly)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    uart2RxOnly = rxOnly;

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    // USART2_TX    PD5
    // USART2_RX    PD6
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    if (!rxOnly)
        GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_USART2);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource6,GPIO_AF_USART2);

    uart2Open(speed);
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
    if (!rxOnly)
        USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
    USART_Cmd(USART2, ENABLE);
		uart2Callback = func;
}

void uart2ChangeBaud(uint32_t speed)
{
    uart2Open(speed);
}

void uart2Write(uint8_t ch)
{
    if (uart2RxOnly)
        return;

    tx2Buffer[tx2BufferHead] = ch;
    tx2BufferHead = (tx2BufferHead + 1) % UART2_BUFFER_SIZE;

    USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
}

bool uart2TransmitEmpty(void)
{
    return tx2BufferTail == tx2BufferHead;
}

void USART2_IRQHandler(void)
{
    uint16_t SR = USART2->SR;

    if (SR & USART_IT_RXNE) {
        if (uart2Callback)
            uart2Callback(USART_ReceiveData(USART2));
    }
    if (SR & USART_FLAG_TXE) {
        if (tx2BufferTail != tx2BufferHead) {
            USART2->DR = tx2Buffer[tx2BufferTail];
            tx2BufferTail = (tx2BufferTail + 1) % UART2_BUFFER_SIZE;
        } else {
            USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
        }
    }
}

/* -------------------------- UART3  ----------------------------- */
uartReceiveCallbackPtr uart3Callback = NULL;
#define UART3_BUFFER_SIZE    128

volatile uint8_t tx3Buffer[UART3_BUFFER_SIZE];
uint32_t tx3BufferTail = 0;
uint32_t tx3BufferHead = 0;
bool uart3RxOnly = false;

static void uart3Open(uint32_t speed)
{
    USART_InitTypeDef USART_InitStructure;

    USART_StructInit(&USART_InitStructure);
    USART_InitStructure.USART_BaudRate = speed;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | (uart3RxOnly ? 0 : USART_Mode_Tx);
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART3, &USART_InitStructure);
    USART_Cmd(USART3, ENABLE);
}

void uart3Init(uint32_t speed, uartReceiveCallbackPtr func, bool rxOnly)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);

    uart3RxOnly = rxOnly;

    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);



    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    if (!rxOnly)
        GPIO_Init(GPIOD, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_USART2);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource9,GPIO_AF_USART2);

    uart3Open(speed);
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
    if (!rxOnly)
        USART_ITConfig(USART3, USART_IT_TXE, ENABLE);
    USART_Cmd(USART3, ENABLE);
		uart3Callback = func;
}

void uart3ChangeBaud(uint32_t speed)
{
    uart3Open(speed);
}

void uart3Write(uint8_t ch)
{
    if (uart3RxOnly)
        return;

    tx3Buffer[tx3BufferHead] = ch;
    tx3BufferHead = (tx3BufferHead + 1) % UART3_BUFFER_SIZE;

    USART_ITConfig(USART3, USART_IT_TXE, ENABLE);
}

bool uart3TransmitEmpty(void)
{
    return tx3BufferTail == tx3BufferHead;
}

void USART3_IRQHandler(void)
{
    uint16_t SR = USART3->SR;

    if (SR & USART_IT_RXNE) {
        if (uart3Callback)
            uart3Callback(USART_ReceiveData(USART3));
    }
    if (SR & USART_FLAG_TXE) {
        if (tx3BufferTail != tx3BufferHead) {
            USART3->DR = tx3Buffer[tx3BufferTail];
            tx3BufferTail = (tx3BufferTail + 1) % UART3_BUFFER_SIZE;
        } else {
            USART_ITConfig(USART3, USART_IT_TXE, DISABLE);
        }
    }
}