Добрый день, уважаемый Хабровчанин. Хочу рассказать тебе о своей работе, которой обычно занимаются студенты последних курсов технических ВУЗов (да-да, именно то нехорошее слово на букву «Д»).
Целью работы была разработка системы очувствления и управления мобильным роботом. За сими громкими словами стоит не очень большая, но для меня интересная задача.
Ближе к сути. Имеем микропроцессор, пачку датчиков, шаговый движок и необходимо, чтобы микропроцессор считывал данные с датчиков (акселерометры и гироскопы), отсылал данную информацию на ПК, принимал с компьютера команду управления движком, вращал движок.
Свой выбор остановил на следующих комплектующих:
• STM32F3DISCOVERY, так как имеет на борту уже установленный акселерометр и гироскоп. Да и под STM32 имеется уже много готовых примеров, что должно было облегчить задачу (отчасти облегчило).
• Цифровые акселерометры LIS331DH, 3ех осевые, высокоточные (от 2g до 8g). Вообще почти вся серия LIS* очень хороша и подходит под требования.
• Шаговый движок FL42STH25-0404A, ну тут что на любимой кафедре завалялось, то и пошло в дело.
Интересный момент, что в процессе работы искал статьи и информацию именно по STM32F3, и удивился, что ее не так много, как ожидалось (к примеру, по STM32F4 в разы больше примеров и информации). Да вы скажите, что там почти никакой разницы, и будете отчасти правы, но работа с периферией у них оказывается в некоторых местах разная. Поэтому я и решил внести свои 5 копеек по работе с этим микропроцессором.
Достаем STM32F3DISCOVERY из коробочки, подключаем к ПК и запускаем. Демопрограмма показывает, что при отклонениях лампочки мигают, то есть датчики работают. Кричим «Ура!» и лезем в код разбираться и собственно реализовывать необходимое.

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

Подключение: MISO – MISO, MOSI – MOSI, SCK – SCK, CS можно вешать на любую ногу, так как будем дергать его программно.
Сначала нам надо проинициализировать SPI. В данном примере работа идет с SPI2, так как через первый SPI работает встроенный гироскоп (или аксель, точно не помню):
Пытаемся прочитать данные с регистра WHO_AM_I:
где
Тут необходимо отметить важный нюанс, что надо вовремя дергать CS акселерометра, к которому обращаемся, так как прижимание CS к земле инициализирует начало передачи данных (именно из-за этого момента у меня возникли жестокие затыки и проблемы, плюс не все акселя удачно запаялись и часть оказалась нерабочими, что застопорило мою работу примерно недели на две. О_о ). Потом отправляем адрес регистра, с которым будем работать (читать/писать), вторым байтом читаем или пишем.
Ну а писать будем так:
Для корректной работы датчики тоже надо проинициализировать, а именно указать, что будем читать по всем трем осям и указать рабочую частоту (значение управляющего слова и его формирование смотрим в даташите на аксель).
С датчиками закончили, ура! Теперь давайте перейдем к управлению шаговыми двигателями.

Для управления ШД использовался драйвер VNH3SP30. Правда он позволяет управлять только одной из двух обмоток шагового двигателя, поэтому нам понадобится 2 таких платки.
Таким образом, для управления одной обмоткой нам понадобится 3 выхода с микроконтроллера (один несущей частоты и 2 направления), на весь двигатель – 6.
Дефайним порты для удобства:
И инициализируем их:
Для того чтобы сделать 1 шаг двигателем необходимо в нужном порядке включать обмотки двигателя, то есть подавать управляющие сигналы на драйвера
Маска управляющих сигналов следующая:
А теперь делаем шаг в нужном направлении. Направление в данном случае определяется направлением обхода по маске управляющих сигналов:
Определим еще для удобства шаг против часовой и шаг по часовой стрелке:
А теперь напишем функцию, с помощью которой будем вращать двигатель на нужное количество шагов в нужном направлении:
В данной функции вставлены временные задержки, чтобы шаговый двигатель успевал сделать шаг, прежде чем нами будет послана команда следующего шага.
Власти в наших руках становится все больше и больше и мы переходим к следующему этапу – отправка данных на ПК и управление ШД с ПК.
Для работы с USB использовал один из примеров работы с USB, а именно VirtualComport_Loopback (искать на просторах интернета в комплекте STM32 USB-FS-Device development kit). В данном демо подключённый stm32 к ПК определялся как виртуальный ком-порт, и отправлял в обратную все получаемые данные. Ну что же, это нам отлично подходит! Берем данный пример, разрываем петлю обмена и вуаля – пользуемся.
Единственная проблема, которая возникла – приложение на .Net не хотело подключаться к виртуальному ком-порту, если микропроцессор постоянно опрашивал датчик и слал данные на ПК (интересно, что сторонняя программа Hercules, которой я пользовался для отладки отлично открывала порт). Поэтому я решил добавить ожидание нажатия User Button, после которого уже начинался постоянный опрос датчиков и обмен информацией с ПК.
Собственно получился примерно следующий код:
Инициализация USB:
Ждем пока не нажмем User Button:
Обработчик на нажатие UserButton:
В данной статье я опустил многие моменты по распиновке и подключению устройств друг к другу, схемы плат и некоторые другие детали (работа с АЦП) и постарался сделать акцент на работу с периферией. К сожалению, собранный рабочий макет был сдан в ВУЗ (будем надеяться, что последующие поколения заинтересуются данной работой и продолжат ее), в результате чего я не могу продемонстрировать его работу, но у меня сохранилось несколько фото. Вот к примеру фото, когда мы проводили эксперимент по определению амплитуды ускорения при перемещении физической модели транспортного средства по синусоидальной поверхности с разной жесткостью пневмоподвески.

Также приложу проект для IAR под STM32F3. Там присутствует много «дурного» кода, так как писалось в большей части по принципу «лишь бы заработало, да поскорее». За любые комментарии по коду, и не только, буду благодарен.
dl.dropboxusercontent.com/u/61862295/VirtualComport_Loopback.rar
Хочется выразить благодарность ВУЗу, который научил меня многому за эти долгие 6 лет и своему дипломному руководителю, который был в меру отзывчив и всегда помогал мне с «научной» деятельностью.

1. STM32F3 Discovery kit firmware package, including 28 examples and preconfigured projects for 4 different IDEs www.st.com/web/en/catalog/tools/PF258154
2. Даташиты по всем перечисленным устройствам.
Целью работы была разработка системы очувствления и управления мобильным роботом. За сими громкими словами стоит не очень большая, но для меня интересная задача.
Ближе к сути. Имеем микропроцессор, пачку датчиков, шаговый движок и необходимо, чтобы микропроцессор считывал данные с датчиков (акселерометры и гироскопы), отсылал данную информацию на ПК, принимал с компьютера команду управления движком, вращал движок.
Закупка:
Свой выбор остановил на следующих комплектующих:
• STM32F3DISCOVERY, так как имеет на борту уже установленный акселерометр и гироскоп. Да и под STM32 имеется уже много готовых примеров, что должно было облегчить задачу (отчасти облегчило).
• Цифровые акселерометры LIS331DH, 3ех осевые, высокоточные (от 2g до 8g). Вообще почти вся серия LIS* очень хороша и подходит под требования.
• Шаговый движок FL42STH25-0404A, ну тут что на любимой кафедре завалялось, то и пошло в дело.
Интересный момент, что в процессе работы искал статьи и информацию именно по STM32F3, и удивился, что ее не так много, как ожидалось (к примеру, по STM32F4 в разы больше примеров и информации). Да вы скажите, что там почти никакой разницы, и будете отчасти правы, но работа с периферией у них оказывается в некоторых местах разная. Поэтому я и решил внести свои 5 копеек по работе с этим микропроцессором.
Потихонечку р��збираемся:
Достаем STM32F3DISCOVERY из коробочки, подключаем к ПК и запускаем. Демопрограмма показывает, что при отклонениях лампочки мигают, то есть датчики работают. Кричим «Ура!» и лезем в код разбираться и собственно реализовывать необходимое.

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

Подключение: 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); }
С датчиками закончили, ура! Теперь давайте перейдем к управлению шаговыми двигателями.
Тише едешь – дальше будешь

Для управления ШД использовался драйвер 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); } }
Заключение
В данной статье я опустил многие моменты по распиновке и подключению устройств друг к другу, схемы плат и некоторые другие детали (работа с АЦП) и постарался сделать акцент на работу с периферией. К сожалению, собранный рабочий макет был сдан в ВУЗ (будем надеяться, что последующие поколения заинтересуются данной работой и продолжат ее), в результате чего я не могу продемонстрировать его работу, но у меня сохранилось несколько фото. Вот к примеру фото, когда мы проводили эксперимент по определению амплитуды ускорения при перемещении физической модели транспортного средства по синусоидальной поверхности с разной жесткостью пневмоподвески.

Также приложу проект для IAR под STM32F3. Там присутствует много «дурного» кода, так как писалось в большей части по принципу «лишь бы заработало, да поскорее». За любые комментарии по коду, и не только, буду благодарен.
dl.dropboxusercontent.com/u/61862295/VirtualComport_Loopback.rar
Хочется выразить благодарность ВУЗу, который научил меня многому за эти долгие 6 лет и своему дипломному руководителю, который был в меру отзывчив и всегда помогал мне с «научной» деятельностью.

Используемые источники:
1. STM32F3 Discovery kit firmware package, including 28 examples and preconfigured projects for 4 different IDEs www.st.com/web/en/catalog/tools/PF258154
2. Даташиты по всем перечисленным устройствам.
