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

SergDoc
oleg70:

Вот что у меня осталось от DiscoveryF3

Олег, попробуйте на всякий случай повесить резистор помиж кондёрами на кварц (между лапами кварца) килоОм 100-150…

Razek

копать от сюда и до заката =))
Приводим значение адреса в указатель на адрес памяти и копируем с этого адреса.

rual
oleg70:

Стандартные функции смотрел,

Олег, вот стандартная инициализация ДУС через стандартные драйверы дисковери (ранее я прилагал архив), см. внимательно, там же есть функции чтения и записи ИММЕНО по Ф3дисковери.

void DUS_Config(void)
{
 L3GD20_InitTypeDef L3GD20_InitStructure;
 L3GD20_FilterConfigTypeDef L3GD20_FilterStructure;
 /* Configure Mems L3GD20 */
 L3GD20_InitStructure.Power_Mode = L3GD20_MODE_ACTIVE;
 L3GD20_InitStructure.Output_DataRate = L3GD20_OUTPUT_DATARATE_3;
 L3GD20_InitStructure.Axes_Enable = L3GD20_AXES_ENABLE;
 L3GD20_InitStructure.Band_Width = L3GD20_BANDWIDTH_4;
 L3GD20_InitStructure.BlockData_Update = L3GD20_BlockDataUpdate_Continous;
 L3GD20_InitStructure.Endianness = L3GD20_BLE_LSB;
 L3GD20_InitStructure.Full_Scale = L3GD20_FULLSCALE_2000;
 L3GD20_Init(&L3GD20_InitStructure);

 L3GD20_FilterStructure.HighPassFilter_Mode_Selection =L3GD20_HPM_NORMAL_MODE_RES;
 L3GD20_FilterStructure.HighPassFilter_CutOff_Frequency = L3GD20_HPFCF_3;
 L3GD20_FilterConfig(&L3GD20_FilterStructure) ;
 L3GD20_FilterCmd(L3GD20_HIGHPASSFILTER_ENABLE);
 L3GD20_INT2InterruptCmd(L3GD20_INT2INTERRUPT_ENABLE);
}
SergDoc:

кто в переменные глядеть должен, в 16-ти битную ну никак не влезет

пишем исключительно полусловами (16 бит), вот моя перекладка демки

/*------------------- ýìóëÿòîð ôëýø ------------------------*/
FLASH_Status FLASH_Write16(uint32_t addr, uint16_t* buf, uint32_t size)
{
 FLASH_Status FLASHStatus = FLASH_COMPLETE;
 uint32_t inx = 0;
 uint32_t EraseCounter = 0x00, Address = 0x00;
  uint32_t NbrOfPage = 0x00;

 /* ïðîâåðèì âûõîä çà ïðåäåëû ðàçðåøåííîé îáëàñòè */
 if (addr+size*2 > FLASH_USER_END_ADDR) return FLASH_ERROR_PROGRAM;

  /* Ðàçáëîêèðîâêà äîñòóïà ê çàïèñè ôëåø */
  FLASH_Unlock();

  /* Î÷èñòèì ôëàãè */
  FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);
  /* Îïðåäåëèì êîëè÷åñòâî ñòèðàåìûõ ñòðàíèö */
  NbrOfPage = 1 + size / FLASH_PAGE_SIZE;
  /* Ñòèðàåì ñòðàíèöû */
  for(EraseCounter = 0; (EraseCounter < NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)
  {
  FLASHStatus = FLASH_ErasePage(FLASH_USER_START_ADDR + (FLASH_PAGE_SIZE * EraseCounter));
    if (FLASHStatus != FLASH_COMPLETE)  return FLASHStatus;
  }

  /* Ïðîãðàììèðóåì ôëåø */

  Address = FLASH_USER_START_ADDR;
  while (inx < size) {
  FLASHStatus = FLASH_ProgramHalfWord(Address, buf[inx]);

    if (FLASHStatus == FLASH_COMPLETE){
      Address += 2;
   inx++;
  }
    else return FLASHStatus;
  }
  /* Çàáëîêèðóåì çàïèñü âî ôëåø */
  FLASH_Lock();

 return FLASHStatus;
}

Вот стандартная функция записи полуслова

/**
  * @brief  Programs a half word at a specified address.
  * @note   To correctly run this function, the FLASH_Unlock() function
  *         must be called before.
  *         Call the FLASH_Lock() to disable the flash memory access
  *         (recommended to protect the FLASH memory against possible unwanted operation)
  * @param  Address: specifies the address to be programmed.
  * @param  Data: specifies the data to be programmed.
  * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
  *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  */
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
  FLASH_Status status = FLASH_COMPLETE;
  /* Check the parameters */
  assert_param(IS_FLASH_PROGRAM_ADDRESS(Address));
  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);

  if(status == FLASH_COMPLETE)
  {
    /* If the previous operation is completed, proceed to program the new data */
    FLASH->CR |= FLASH_CR_PG;

    *(__IO uint16_t*)Address = Data;
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);

    /* Disable the PG Bit */
    FLASH->CR &= ~FLASH_CR_PG;
  } 

У меня всё отлично заработало, писал в старший сектор, чтоб линкёр в него не лез уменьшил верхнюю границу флеши в настройке проекта. Почитаю про ф4, напишу отличия…

SergDoc

урааааа у меня зажегся первый светодиод(проверочный) !!! 😃

rual:

пишем исключительно полусловами (16 бит), вот моя перекладка демки

я имел ввиду адрес сектора отрезанного под еепромку 😃
ну с одной мёртвой точки сдвинулся, пойду дальше копать…

rual
SergDoc:

урааааа у меня зажегся первый светодиод(проверочный) !!!

Поздравляю! С флешей разобрался? Вообщем от Ф3 практически не отличается, кроме возможности установить ширину данных, и ещё не увидел в описании процедуры программирования снятие бита программирования
PG bit in the FLASH_CR register, надо смотреть стандартные библиотеки

SergDoc:

я имел ввиду адрес сектора отрезанного под еепромку

ну тут без вариантов uint32_t

SergDoc

так у меня проблема - проц запускается на частоте - 53.76 Мгц - чё я где утварил?
Виноват, сам скопировал от Discovery system_stm32f4xx.c и забыл про это…

SergDoc
rual:

и ещё не увидел в описании процедуры программирования снятие бита программирования
PG bit in the FLASH_CR register,

тут?
stm32f4xx_flash.c



if(status == FLASH_COMPLETE)
  {
    /* if the previous operation is completed, proceed to program the new data */
    FLASH->CR &= CR_PSIZE_MASK;
    FLASH->CR |= FLASH_PSIZE_BYTE;
    FLASH->CR |= FLASH_CR_PG;

    *(__IO uint8_t*)Address = Data;

    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation();

    /* if the program operation is completed, disable the PG Bit */
    FLASH->CR &= (~FLASH_CR_PG);
  }
rual:

У меня всё отлично заработало, писал в старший сектор, чтоб линкёр в него не лез уменьшил верхнюю границу флеши в настройке проекта.

посмотри st-link видит эту область, а то когда я по незнанке сносил нулевой сектор, то видел что он стирается и в него пишется, а так нет, а отпаивать, что-бы выдрать ввесь дамп, ну не хочется - при запаяном программаторе через uart не достучишься… ага вспомнил, если бы с виртуальником были бы траблы, он бы мне сообщил - светодиод бы моргал часто-часто, да и дальше бы не пошел…
сейчас дошел до автодетекта (его выкинуть надо), а так как нечего детектировать то висим перемигиваемся светодиодиками - почему и заметил, что как-то не так моргают, полез частоту проверять…

rual
SergDoc:

тут? stm32f4xx_flash.c

Да, ту всё нормально, а в доке на Ф3 нет ничего про снятие бита программирования.

SergDoc:

посмотри st-link видит эту область,

СТ-ЛИНК видит-читает, но непосредственно править в отладчике не даёт, только через программирование.

SergDoc

да-да у меня тоже всё прекрасно записано - отладчиком сейчас заглянул в седьмой сектор 0x08060000 - и до победы, занято только примерно пол килобайта, ещё 127,5 - свободны 😃


#include "board.h"
#include "mw.h"
#include <string.h>

#ifndef FLASH_PAGE_COUNT
#define FLASH_PAGE_COUNT 3
#endif
#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */


#define FLASH_PAGE_SIZE                 ((uint32_t)0x20000)
#define FLASH_WRITE_ADDR              ADDR_FLASH_SECTOR_7  //(0x08000000 + (uint32_t)FLASH_PAGE_SIZE*10 * (FLASH_PAGE_COUNT - 1))       // use the last KB for storage

config_t cfg;
const char rcChannelLetters[] = "AERT1234";

static uint8_t EEPROM_CONF_VERSION = 40;
static uint32_t enabledSensors = 0;
static void resetConf(void);

void parseRcChannels(const char *input)
{
    const char *c, *s;

    for (c = input; *c; c++) {
        s = strchr(rcChannelLetters, *c);
        if (s)
            cfg.rcmap[s - rcChannelLetters] = c - input;
    }
}

static uint8_t validEEPROM(void)
{
    const config_t *temp = (const config_t *)FLASH_WRITE_ADDR;//;ADDR_FLASH_SECTOR_7
    const uint8_t *p;
    uint8_t chk = 0;

    // check version number
    if (EEPROM_CONF_VERSION != temp->version)
        return 0;

    // check size and magic numbers
    if (temp->size != sizeof(config_t) || temp->magic_be != 0xBE || temp->magic_ef != 0xEF)
        return 0;

    // verify integrity of temporary copy
    for (p = (const uint8_t *)temp; p < ((const uint8_t *)temp + sizeof(config_t)); p++)
        chk ^= *p;

    // checksum failed
    if (chk != 0)
        return 0;

    // looks good, let's roll!
    return 1;
}

void readEEPROM(void)
{
    uint8_t i;

    // Read flash
    memcpy(&cfg, (char *)FLASH_WRITE_ADDR, sizeof(config_t));//ADDR_FLASH_SECTOR_7

    for (i = 0; i < 6; i++)
        lookupPitchRollRC[i] = (2500 + cfg.rcExpo8 * (i * i - 25)) * i * (int32_t) cfg.rcRate8 / 2500;

    for (i = 0; i < 11; i++) {
        int16_t tmp = 10 * i - cfg.thrMid8;
        uint8_t y = 1;
        if (tmp > 0)
            y = 100 - cfg.thrMid8;
        if (tmp < 0)
            y = cfg.thrMid8;
        lookupThrottleRC[i] = 10 * cfg.thrMid8 + tmp * (100 - cfg.thrExpo8 + (int32_t) cfg.thrExpo8 * (tmp * tmp) / (y * y)) / 10;      // [0;1000]
        lookupThrottleRC[i] = cfg.minthrottle + (int32_t) (cfg.maxthrottle - cfg.minthrottle) * lookupThrottleRC[i] / 1000;     // [0;1000] -> [MINTHROTTLE;MAXTHROTTLE]
    }

    cfg.tri_yaw_middle = constrain(cfg.tri_yaw_middle, cfg.tri_yaw_min, cfg.tri_yaw_max);       //REAR
}

void writeParams(uint8_t b)
{
    FLASH_Status status;
    uint32_t i;
    uint8_t chk = 0;
    const uint8_t *p;

    cfg.version = EEPROM_CONF_VERSION;
    cfg.size = sizeof(config_t);
    cfg.magic_be = 0xBE;
    cfg.magic_ef = 0xEF;
    cfg.chk = 0;
    // recalculate checksum before writing
    for (p = (const uint8_t *)&cfg; p < ((const uint8_t *)&cfg + sizeof(config_t)); p++)
        chk ^= *p;
    cfg.chk = chk;

    // write it
    FLASH_Unlock();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);

    if (FLASH_EraseSector(FLASH_Sector_7, VoltageRange_3) == FLASH_COMPLETE) {
        for (i = 0; i < sizeof(config_t); i += 4) {
            status = FLASH_ProgramWord(FLASH_WRITE_ADDR + i, *(uint32_t *) ((char *) &cfg + i));//ADDR_FLASH_SECTOR_7
            if (status != FLASH_COMPLETE)
                break;          // TODO: fail
        }
    }
    FLASH_Lock();

    readEEPROM();
    if (b)
        blinkLED(15, 20, 1);
}

void checkFirstTime(bool reset)
{
    // check the EEPROM integrity before resetting values
    if (!validEEPROM() || reset)
        resetConf();
}

// Default settings
static void resetConf(void)
{
    int i;
    const int8_t default_align[3][3] = { /* GYRO */ { 0, 0, 0 }, /* ACC */ { 0, 0, 0 }, /* MAG */ { -2, -3, 1 } };

    memset(&cfg, 0, sizeof(config_t));

    cfg.version = EEPROM_CONF_VERSION;
    cfg.mixerConfiguration = MULTITYPE_QUADX;
    featureClearAll();
    setFeature(FEATURE_VBAT);

    // cfg.looptime = 0;
    cfg.P8[ROLL] = 40;
    cfg.I8[ROLL] = 30;
    cfg.D8[ROLL] = 23;
    cfg.P8[PITCH] = 40;
    cfg.I8[PITCH] = 30;
    cfg.D8[PITCH] = 23;
    cfg.P8[YAW] = 55;
    cfg.I8[YAW] = 45;
    // cfg.D8[YAW] = 0;
    cfg.P8[PIDALT] = 11;
    cfg.I8[PIDALT] = 15;
    cfg.D8[PIDALT] = 7;
    cfg.P8[PIDPOS] = 11; // POSHOLD_P * 100;
    // cfg.I8[PIDPOS] = 0; // POSHOLD_I * 100;
    // cfg.D8[PIDPOS] = 0;
    cfg.P8[PIDPOSR] = 20; // POSHOLD_RATE_P * 10;
    cfg.I8[PIDPOSR] = 8; // POSHOLD_RATE_I * 100;
    cfg.D8[PIDPOSR] = 45; // POSHOLD_RATE_D * 1000;
    cfg.P8[PIDNAVR] = 14; // NAV_P * 10;
    cfg.I8[PIDNAVR] = 20; // NAV_I * 100;
    cfg.D8[PIDNAVR] = 80; // NAV_D * 1000;
    cfg.P8[PIDLEVEL] = 70;
    cfg.I8[PIDLEVEL] = 10;
    cfg.D8[PIDLEVEL] = 20;
    cfg.P8[PIDMAG] = 40;
    // cfg.P8[PIDVEL] = 0;
    // cfg.I8[PIDVEL] = 0;
    // cfg.D8[PIDVEL] = 0;
    cfg.rcRate8 = 99;
    cfg.rcExpo8 = 20;
    // cfg.rollPitchRate = 0;
    // cfg.yawRate = 0;
    // cfg.dynThrPID = 0;
    cfg.thrMid8 = 50;
    // cfg.thrExpo8 = 0;
    // for (i = 0; i < CHECKBOXITEMS; i++)
    //     cfg.activate[i] = 0;
    // cfg.angleTrim[0] = 0;
    // cfg.angleTrim[1] = 0;
    // cfg.accZero[0] = 0;
    // cfg.accZero[1] = 0;
    // cfg.accZero[2] = 0;
    // cfg.mag_declination = 0;    // For example, -6deg 37min, = -637 Japan, format is [sign]dddmm (degreesminutes) default is zero.
    memcpy(&cfg.align, default_align, sizeof(cfg.align));
    cfg.acc_hardware = ACC_DEFAULT;     // default/autodetect
    cfg.acc_lpf_factor = 4;
    cfg.acc_lpf_for_velocity = 10;
    cfg.accz_deadband = 50;
    cfg.gyro_cmpf_factor = 400; // default MWC
    cfg.gyro_lpf = 42;
    cfg.mpu6050_scale = 1; // fuck invensense
    cfg.baro_tab_size = 21;
    cfg.baro_noise_lpf = 0.6f;
    cfg.baro_cf = 0.985f;
    cfg.moron_threshold = 32;
    cfg.gyro_smoothing_factor = 0x00141403;     // default factors of 20, 20, 3 for R/P/Y
    cfg.vbatscale = 110;
    cfg.vbatmaxcellvoltage = 43;
    cfg.vbatmincellvoltage = 33;
    // cfg.power_adc_channel = 0;

    // Radio
    parseRcChannels("AETR1234");
    // cfg.deadband = 0;
    // cfg.yawdeadband = 0;
    cfg.alt_hold_throttle_neutral = 20;
    // cfg.spektrum_hires = 0;
    cfg.midrc = 1500;
    cfg.mincheck = 1100;
    cfg.maxcheck = 1900;
    // cfg.retarded_arm = 0;       // disable arm/disarm on roll left/right

    // Failsafe Variables
    cfg.failsafe_delay = 10;            // 1sec
    cfg.failsafe_off_delay = 200;       // 20sec
    cfg.failsafe_throttle = 1200;       // decent default which should always be below hover throttle for people.

    // Motor/ESC/Servo
    cfg.minthrottle = 1150;
    cfg.maxthrottle = 1850;
    cfg.mincommand = 1000;
    cfg.motor_pwm_rate = 490;
    cfg.servo_pwm_rate = 50;

    // servos
    cfg.yaw_direction = 1;
    cfg.tri_yaw_middle = 1500;
    cfg.tri_yaw_min = 1020;
    cfg.tri_yaw_max = 2000;

    // fixed wing
    cfg.pitch_min = 1020;
    cfg.pitch_mid = 1500;
    cfg.pitch_max = 1980;
    cfg.yaw_min = 1020;
    cfg.yaw_mid = 1500;
    cfg.yaw_max = 1980;
    cfg.flaperons = 0;						// enable (1) / disable (0) flaperon function
    cfg.flap_aux = 0;						// which AUX channel use to toggle flaps
    cfg.vtail = 0;							// airplane tail configuration, X (elev + rudd) [0] or V tail [1]
    cfg.pitch_direction = 1;				// airplane pitch servo orientation
    cfg.pitch_direction_l = 1;              // (Flying-wing only) left servo - pitch orientation
    cfg.pitch_direction_r = -1;              // (Flying-wing only) right servo - pitch orientation (opposite sign to pitch_direction_l if servos are mounted mirrored)
    cfg.roll_direction_l = 1;               // left servo - roll orientation
    cfg.roll_direction_r = -1;               // right servo - roll orientation  (same sign as ROLL_DIRECTION_L, if servos are mounted in mirrored orientation)

    // flying wing
    cfg.wing_left_min = 1020;
    cfg.wing_left_mid = 1500;
    cfg.wing_left_max = 1980;
    cfg.wing_right_min = 1020;
    cfg.wing_right_mid = 1500;
    cfg.wing_right_max = 1980;
    cfg.pitch_direction_l = 1;
    cfg.pitch_direction_r = -1;
    cfg.roll_direction_l = 1;
    cfg.roll_direction_r = 1;

    // gimbal
    cfg.gimbal_pitch_gain = 10;
    cfg.gimbal_roll_gain = 10;
    cfg.gimbal_flags = GIMBAL_NORMAL;
    cfg.gimbal_pitch_min = 1020;
    cfg.gimbal_pitch_max = 2000;
    cfg.gimbal_pitch_mid = 1500;
    cfg.gimbal_roll_min = 1020;
    cfg.gimbal_roll_max = 2000;
    cfg.gimbal_roll_mid = 1500;

    // gps/nav stuff
    cfg.gps_type = GPS_NMEA;
    cfg.gps_baudrate = 115200;
    cfg.gps_wp_radius = 200;
    cfg.gps_lpf = 20;
    cfg.nav_slew_rate = 30;
    cfg.nav_controls_heading = 1;
    cfg.nav_speed_min = 100;
    cfg.nav_speed_max = 300;

    // serial (USART1) baudrate
    cfg.serial_baudrate = 115200;

    // custom mixer. clear by defaults.
    for (i = 0; i < MAX_MOTORS; i++)
        cfg.customMixer[i].throttle = 0.0f;
    writeParams(0);
}

bool getSensors(uint32_t mask)
{
    return enabledSensors & mask;
}

void setSensors(uint32_t mask)
{
    enabledSensors |= mask;
}

void clearSensors(uint32_t mask)
{
    enabledSensors &= ~(mask);
}

uint32_t sensorsMask(void)
{
    return enabledSensors;
}

bool getFeature(uint32_t mask)
{
    return cfg.enabledFeatures & mask;
}

void setFeature(uint32_t mask)
{
    cfg.enabledFeatures |= mask;
}

void featureClear(uint32_t mask)
{
    cfg.enabledFeatures &= ~(mask);
}

void featureClearAll()
{
    cfg.enabledFeatures = 0;
}

uint32_t featureMask(void)
{
    return cfg.enabledFeatures;
}
rual
SergDoc:

занято только примерно пол килобайта, ещё 127,5 - свободны

Жалко тереть такую страницу, лучше всёж попытаться уйти в мелкий сектор.

SergDoc

ну пока не напрягает, у меня всего кода на 68к… хотя можно смело спрыгнуть в первый сектор (не нулевой)…

rual

Сергей, у тебя к этому порту ГУЙ Вийский или что-то своё?

SergDoc

Вий но пока до него не добрался… в вингуи сделали сразу терминал, разберусь с датчиками буду пробовать цепляться…
тут обновил

SergDoc

Добрался до гиро-акселя, пока присматриваю откуда спилить, вопрос такого плана DMA на SPI1 (там мпу висит) использовать?

rual
SergDoc:

вопрос такого плана DMA на SPI1 (там мпу висит) использовать?

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

oleg70

Буду очень благодарен, если кто нибудь доходчиво пояснит ЧТО и КАК фильтрует HIGH PASS Filter в датчиках, его практическое применение, в переводе с даташита так и не понял…

SergDoc

получается как-то, не слишком хорошо, DMA2: Stream 0, Channel 0 - ADC1; DMA2: Stream 2, Channel 4 - USART1_RX; Stream 7, Channel 4 - USART1_TX;

ага разобрался сделать: DMA2: Stream 5, Channel 4 - USART1_RX; Stream 7, Channel 4 - USART1_TX; и Stream 2, Channel 3 - SPI1_RX; Stream 3 Channel 3 - SPI1_TX;

картинко

SergDoc
oleg70:

ЧТО и КАК фильтрует HIGH PASS Filter

высокочастотный фильтр, пропускающий частоты выше некоторой частоты среза и ослабляющий более низкие частоты…
ну типа решили мы полетать в дождь, а у нас две-три капли в секунду барабанят (ну в моём случае по салатнице) - аксель с ума сводят - можно обрезать данное мероприятие (к гироскопам не применимо)…
о только щас подумал, а нафига акселю вообще вибрации чувствовать - зарезать с двух сторон - пусть доплывает потиху?

SergDoc
SergDoc:

Stream 3 Channel 3 - SPI1_TX;

а это наверно и ненадо, зачем мне буфер на выход если я один раз при инициализации записываю данные?