STM32F3DISCOVERY, акселерометры, шаговые двигатели и немного магии

в 9:24, , рубрики: discovery, diy или сделай сам, stm32, usb, акселерометр, двигатели, Песочница, Программинг микроконтроллеров, метки: , , , , ,

Добрый день, уважаемый Хабровчанин. Хочу рассказать тебе о своей работе, которой обычно занимаются студенты последних курсов технических ВУЗов (да-да, именно то нехорошее слово на букву «Д»).

Целью работы была разработка системы очувствления и управления мобильным роботом. За сими громкими словами стоит не очень большая, но для меня интересная задача.

Ближе к сути. Имеем микропроцессор, пачку датчиков, шаговый движок и необходимо, чтобы микропроцессор считывал данные с датчиков (акселерометры и гироскопы), отсылал данную информацию на ПК, принимал с компьютера команду управления движком, вращал движок.

Закупка:

Свой выбор остановил на следующих комплектующих:

• STM32F3DISCOVERY, так как имеет на борту уже установленный акселерометр и гироскоп. Да и под STM32 имеется уже много готовых примеров, что должно было облегчить задачу (отчасти облегчило).
• Цифровые акселерометры LIS331DH, 3ех осевые, высокоточные (от 2g до 8g). Вообще почти вся серия LIS* очень хороша и подходит под требования.
• Шаговый движок FL42STH25-0404A, ну тут что на любимой кафедре завалялось, то и пошло в дело.

Интересный момент, что в процессе работы искал статьи и информацию именно по STM32F3, и удивился, что ее не так много, как ожидалось (к примеру, по STM32F4 в разы больше примеров и информации). Да вы скажите, что там почти никакой разницы, и будете отчасти правы, но работа с периферией у них оказывается в некоторых местах разная. Поэтому я и решил внести свои 5 копеек по работе с этим микропроцессором.

Потихонечку разбираемся:

Достаем STM32F3DISCOVERY из коробочки, подключаем к ПК и запускаем. Демопрограмма показывает, что при отклонениях лампочки мигают, то есть датчики работают. Кричим «Ура!» и лезем в код разбираться и собственно реализовывать необходимое.
image

А необходимого много, но сначала решил остановиться на том, чтобы достучаться до внешних датчиков (не бортовых). Распаяли акселя, подключаем. У акселей есть 2 интерфейса для подключения: SPI и I2C. Решил остановиться на SPI, т.к. с ним уже приходилось иметь дело на ATTINY2313 (реализовывал его программно) и думал, что уж с аппаратным SPI вообще проблем не должно быть.

Хотел как проще, оказалось как всегда

STM32F3DISCOVERY, акселерометры, шаговые двигатели и немного магии
Подключение: MISO – MISO, MOSI – MOSI, SCK – SCK, CS можно вешать на любую ногу, так как будем дергать его программно.
Сначала нам надо проинициализировать SPI. В данном примере работа идет с SPI2, так как через первый SPI работает встроенный гироскоп (или аксель, точно не помню):

void SPI2_Configuration(void)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_5);        // SCK
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_5);        // MISO
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_5);        // MOSI
    
    SPI_InitTypeDef SPI_InitStructure;
    SPI_StructInit(&SPI_InitStructure);
   
    SPI_I2S_DeInit(SPI2);
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_InitStructure.SPI_CPHA              = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_CPOL              = SPI_CPOL_Low;
    SPI_InitStructure.SPI_DataSize          = SPI_DataSize_16b;
    SPI_InitStructure.SPI_Direction         = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_FirstBit          = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_Mode              = SPI_Mode_Master;
    SPI_InitStructure.SPI_NSS               = SPI_NSS_Soft;   
    SPI_InitStructure.SPI_CRCPolynomial     = 7;
    SPI_Init(SPI2, &SPI_InitStructure);

    /* Configure the Priority Group to 1 bit */                
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    /* Configure the SPI interrupt priority */
    NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* Initialize the FIFO threshold */
    SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);
    SPI_Cmd(SPI2, ENABLE);
}

Пытаемся прочитать данные с регистра WHO_AM_I:
getValue = getRegisterValue(&AXELx, 0x0F);
где

char getRegisterValue(AXEL_TypeDef* AXELx, char address)
{
    AXELx->CS_Port->BRR = AXELx->CS_Pin;
    SPI_I2S_SendData16(SPI2, 0x8000|(address<<8));
    while(!SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE));
    AXELx->CS_Port->BSRR = AXELx->CS_Pin;
    return SPI_I2S_ReceiveData16(SPI2);
}

Тут необходимо отметить важный нюанс, что надо вовремя дергать CS акселерометра, к которому обращаемся, так как прижимание CS к земле инициализирует начало передачи данных (именно из-за этого момента у меня возникли жестокие затыки и проблемы, плюс не все акселя удачно запаялись и часть оказалась нерабочими, что застопорило мою работу примерно недели на две. О_о ). Потом отправляем адрес регистра, с которым будем работать (читать/писать), вторым байтом читаем или пишем.

Ну а писать будем так:

void setRegisterValue(AXEL_TypeDef* AXELx, char address, char data)
{
    AXELx->CS_Port->BRR = AXELx->CS_Pin;
    SPI_I2S_SendData16(SPI2,((short)address<<8)|(short)data);
    while(!SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE));
    AXELx->CS_Port->BSRR = AXELx->CS_Pin;
    SPI_I2S_ReceiveData16(SPI2);
}

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

void Axel_Init(AXEL_TypeDef* AXELx)
{
    GPIO_InitTypeDef GPIO_InitStructure;
        
    /* Enable Axel CS Pin */
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin = AXELx->CS_Pin;
    GPIO_Init(AXELx->CS_Port, &GPIO_InitStructure);
  
      setRegisterValue(AXELx, 0x20, 0x27);
}

С датчиками закончили, ура! Теперь давайте перейдем к управлению шаговыми двигателями.

Тише едешь – дальше будешь

STM32F3DISCOVERY, акселерометры, шаговые двигатели и немного магии
Для управления ШД использовался драйвер VNH3SP30. Правда он позволяет управлять только одной из двух обмоток шагового двигателя, поэтому нам понадобится 2 таких платки.

Таким образом, для управления одной обмоткой нам понадобится 3 выхода с микроконтроллера (один несущей частоты и 2 направления), на весь двигатель – 6.

Дефайним порты для удобства:

#define A_PULSE_PORT     GPIOB
#define A_PULSE_PIN      GPIO_Pin_2
#define A_DIR1_PORT      GPIOB
#define A_DIR1_PIN       GPIO_Pin_0
#define A_DIR2_PORT      GPIOE
#define A_DIR2_PIN       GPIO_Pin_8

#define B_PULSE_PORT     GPIOE
#define B_PULSE_PIN      GPIO_Pin_12
#define B_DIR1_PORT      GPIOE
#define B_DIR1_PIN       GPIO_Pin_10
#define B_DIR2_PORT      GPIOE
#define B_DIR2_PIN       GPIO_Pin_14

И инициализируем их:

void StepMotorSetup()
{
  
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB |
                        RCC_AHBPeriph_GPIOE, ENABLE);
  
  /*
    Инициализация выводов МК для управления ШД
  */
  GPIO_InitTypeDef GPIO_InitStructure;

  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  
  GPIO_InitStructure.GPIO_Pin = A_PULSE_PIN;
  GPIO_Init(A_PULSE_PORT, &GPIO_InitStructure);  

  GPIO_InitStructure.GPIO_Pin = B_PULSE_PIN;
  GPIO_Init(B_PULSE_PORT, &GPIO_InitStructure);  
  
  GPIO_InitStructure.GPIO_Pin = A_DIR1_PIN;
  GPIO_Init(A_DIR1_PORT, &GPIO_InitStructure);  
  
  GPIO_InitStructure.GPIO_Pin = B_DIR1_PIN;
  GPIO_Init(B_DIR1_PORT, &GPIO_InitStructure);  
  
  GPIO_InitStructure.GPIO_Pin = A_DIR2_PIN;
  GPIO_Init(A_DIR2_PORT, &GPIO_InitStructure);  
  
  GPIO_InitStructure.GPIO_Pin = B_DIR2_PIN;
  GPIO_Init(B_DIR2_PORT, &GPIO_InitStructure); 
}

Для того чтобы сделать 1 шаг двигателем необходимо в нужном порядке включать обмотки двигателя, то есть подавать управляющие сигналы на драйвера

Маска управляющих сигналов следующая:

u8 WireConFullStep[4][4] = {{1, 0, 0, 0},   
                            {0, 0, 1, 0},
                            {0, 1, 0, 0},    
                            {0, 0, 0, 1}};

А теперь делаем шаг в нужном направлении. Направление в данном случае определяется направлением обхода по маске управляющих сигналов:

void DoStep(int index)
{ 
  GPIO_ResetBits(A_DIR1_PORT, A_DIR1_PIN);
  GPIO_ResetBits(A_DIR2_PORT, A_DIR2_PIN);
  GPIO_ResetBits(B_DIR1_PORT, B_DIR1_PIN);
  GPIO_ResetBits(B_DIR2_PORT, B_DIR2_PIN);
  GPIO_ResetBits(A_PULSE_PORT, A_PULSE_PIN);  
  GPIO_ResetBits(B_PULSE_PORT, B_PULSE_PIN);
  
  if(WireConFullStep[index][0] == 1)
  {
    GPIO_SetBits(A_DIR1_PORT, A_DIR1_PIN);
    GPIO_SetBits(A_PULSE_PORT, A_PULSE_PIN); 
  }

  if(WireConFullStep[index][1] == 1)
  {
    GPIO_SetBits(A_DIR2_PORT, A_DIR2_PIN);
    GPIO_SetBits(A_PULSE_PORT, A_PULSE_PIN); 
  }
  
  if(WireConFullStep[index][2] == 1)
  {
    GPIO_SetBits(B_DIR1_PORT, B_DIR1_PIN);
    GPIO_SetBits(B_PULSE_PORT, B_PULSE_PIN); 
  }
 
  if(WireConFullStep[index][3] == 1)
  {
    GPIO_SetBits(B_DIR2_PORT, B_DIR2_PIN);
    GPIO_SetBits(B_PULSE_PORT, B_PULSE_PIN); 
  }
}

Определим еще для удобства шаг против часовой и шаг по часовой стрелке:

void CWStep()
{
  DoStep(CurIndex);
  CurIndex+=1;
  if(CurIndex > 3)
    CurIndex = 0;
}

void CCWStep()
{
  DoStep(CurIndex);
  CurIndex-=1;
  if(CurIndex < 0)
    CurIndex = 3;
}

А теперь напишем функцию, с помощью которой будем вращать двигатель на нужное количество шагов в нужном направлении:

void Steps(u8 dir, s16 n)
{
  s16 i = 0;
  
  for(i = 0; i < n; i++)
  {
    if(dir)
    {
      CWStep();
      udelay(15000);
    }    
    else
    {
      CCWStep();
      udelay(15000);
    }
  }
}

В данной функции вставлены временные задержки, чтобы шаговый двигатель успевал сделать шаг, прежде чем нами будет послана команда следующего шага.

Власти в наших руках становится все больше и больше и мы переходим к следующему этапу – отправка данных на ПК и управление ШД с ПК.

Общение по USB

Для работы с USB использовал один из примеров работы с USB, а именно VirtualComport_Loopback (искать на просторах интернета в комплекте STM32 USB-FS-Device development kit). В данном демо подключённый stm32 к ПК определялся как виртуальный ком-порт, и отправлял в обратную все получаемые данные. Ну что же, это нам отлично подходит! Берем данный пример, разрываем петлю обмена и вуаля – пользуемся.

Единственная проблема, которая возникла – приложение на .Net не хотело подключаться к виртуальному ком-порту, если микропроцессор постоянно опрашивал датчик и слал данные на ПК (интересно, что сторонняя программа Hercules, которой я пользовался для отладки отлично открывала порт). Поэтому я решил добавить ожидание нажатия User Button, после которого уже начинался постоянный опрос датчиков и обмен информацией с ПК.

Собственно получился примерно следующий код:
Инициализация USB:

  Set_System();
  Set_USBClock();
  USB_Interrupts_Config();
  USB_Init();

Ждем пока не нажмем User Button:

  while (1)
  {
    /* Data exhange via USB */
    if (bDeviceState == CONFIGURED && UserButtonPressed != 0)
    {
    …
    }
  }

Обработчик на нажатие UserButton:

__IO uint32_t i =0;
extern __IO uint32_t UserButtonPressed;
void EXTI0_IRQHandler(void)
{ 
  if ((EXTI_GetITStatus(USER_BUTTON_EXTI_LINE) == SET)&&(STM_EVAL_PBGetState(BUTTON_USER) != RESET))
  {
    /* Delay */
    for(i=0; i<0x7FFFF; i++);
    
    /* Wait for SEL button to be pressed  */
    while(STM_EVAL_PBGetState(BUTTON_USER) != RESET); 
    /* Delay */
    for(i=0; i<0x7FFFF; i++);
    UserButtonPressed++;
    
    if (UserButtonPressed > 0x2)
    {
      UserButtonPressed = 0x0;
    }
    
    /* Clear the EXTI line pending bit */
    EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE);
  }
}

Заключение

В данной статье я опустил многие моменты по распиновке и подключению устройств друг к другу, схемы плат и некоторые другие детали (работа с АЦП) и постарался сделать акцент на работу с периферией. К сожалению, собранный рабочий макет был сдан в ВУЗ (будем надеяться, что последующие поколения заинтересуются данной работой и продолжат ее), в результате чего я не могу продемонстрировать его работу, но у меня сохранилось несколько фото. Вот к примеру фото, когда мы проводили эксперимент по определению амплитуды ускорения при перемещении физической модели транспортного средства по синусоидальной поверхности с разной жесткостью пневмоподвески.
STM32F3DISCOVERY, акселерометры, шаговые двигатели и немного магии

Также приложу проект для IAR под STM32F3. Там присутствует много «дурного» кода, так как писалось в большей части по принципу «лишь бы заработало, да поскорее». За любые комментарии по коду, и не только, буду благодарен.

Автор: Bratan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js