Как уже понятно из названия статьи, сегодня речь пойдет об организации обмена данными между микроконтроллером STM32F3 и гироскопом L3GD20. Тут даже добавить нечего )
Микросхема L3GD20 от STMicroelectronics измеряет скорость вращения вокруг трех осей (x, y, z), и, соответственно, является 3-осевым гироскопом. Возможны два варианта общения с гироскопом - по шине I2C или SPI. Я буду использовать плату STM32F3Discovery, на которой уже распаяны контроллер и гироскоп, причем реализовано подключение по SPI, поэтому этот интерфейс и буду использовать. Вот кстати схема включения:
Формат посылки по SPI имеет следующий вид:
На протяжении всего обмена данными Chip Select должен быть в нуле, что, собственно, и видно из рисунка ) С тактированием тут все, я думаю, понятно, так что переходим сразу к формату посылки.
Первый бит отправляемого байта - бит R/W - если он равен 1, значит мы собираемся читать данные из L3GD20. Если нам нужно записать данные, то этот бит надо выставить в 0.
Следом идет бит MS - он нужен для выполнения ряда последовательных команд записи или чтения. Если выставить его в 1, то можно отправлять гироскопу байты данных подряд, а он сам будет инкрементировать адрес записываемого регистра. То есть, например, подаем команду на запись в регистр с адресом 0x01 с выставленным битом MS и отправляем подряд 5 байт данных. Тогда эти данные будут записаны в регистры с адресами 0x01, 0x02, 0x03, 0x04, 0x05. Полезная штука.
Движемся дальше... После бита MS следуют 6 бит для передачи адреса регистра, в который будет производится запись/чтение. На этом первый отправляемый байт заканчивается... А после первого байта, идет либо байт данных на запись, либо прочитанные данные, и на этом, в общем-то все.
Обязательно стоит почитать, ну или хотя бы полистать, документацию на этот датчик, поскольку он содержит в себе довольно-таки много всяких крутых штук. Тут и прерывания на разные события, и различные настройки фильтров. С этим всем мы будем разбираться при написании программы по мере необходимости.
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также небольшая подборка на тему датчиков:
- ПИД-регулятор. Пример ПИД-регулятора температуры на STM32.
- Настройка ПИД-регулятора. Метод Циглера-Никольса.
- Подключение датчика температуры DS18B20 к STM32. Модуль KY-001.
- Подключение магнитного энкодера AS5048 к микроконтроллеру.
Самое время перейти к практическому примеру... Давайте включим и настроим SPI для контроллера STM32F3, запишем что-нибудь в регистры L3GD20 и прочитаем какой-нибудь регистр, чтобы убедиться, что все работает правильно. Вот список всех регистров:
Для того, чтобы запустить микросхему L3GD20, необходимо записать в регистр CTRL_REG1 значение 0x0F. Так и сделаем. А после этого прочитаем данные из регистра под названием WHO_AM_I - если все пройдет успешно, то получим значение 0xD4. Приступаем!
Создаем проект, как в предыдущей статье (тут) и подключаем все необходимые файлы из SPL и CMSIS:
/***************************************************************************************/ #include "stm32f30x_gpio.h" #include "stm32f30x_rcc.h" #include "stm32f30x_spi.h" #include "stm32f30x.h" /***************************************************************************************/
Тут даже нечего комментировать, идем дальше:
/***************************************************************************************/ #define DUMMY 0x00 #define TIMEOUT_TIME 0x1000 SPI_InitTypeDef spi; GPIO_InitTypeDef gpio; uint8_t sendData; uint8_t receiveData[8]; uint16_t timeout; uint8_t tempByte; /***************************************************************************************/
Объявили переменные и константы, которые нам понадобятся. Давайте переходить к инициализации необходимой периферии:
/***************************************************************************************/ void initAll() { // Включаем тактирование портов и SPI RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // За SPI в STM32F3 отвечают ножки PA5, PA6 и PA7, так что настраиваем их на использование // в режиме альтернативной функции GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_5); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_5); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_5); gpio.GPIO_Mode = GPIO_Mode_AF; gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_OType = GPIO_OType_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); // Chip Select создатели платы подключили к выводу PE3, будем дергать им вручную gpio.GPIO_Mode = GPIO_Mode_OUT; gpio.GPIO_Pin = GPIO_Pin_3; gpio.GPIO_OType = GPIO_OType_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE, &gpio); // Настраиваем и включаем SPI spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex; spi.SPI_DataSize = SPI_DataSize_8b; spi.SPI_CPOL = SPI_CPOL_High; spi.SPI_CPHA = SPI_CPHA_2Edge; spi.SPI_NSS = SPI_NSS_Soft; spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; spi.SPI_FirstBit = SPI_FirstBit_MSB; spi.SPI_CRCPolynomial = 7; spi.SPI_Mode = SPI_Mode_Master; SPI_Init(SPI1, &spi); SPI_Cmd(SPI1, ENABLE); SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF); SPI_DataSizeConfig(SPI1, ENABLE); } /***************************************************************************************/
Готово, с настройками закончили, теперь нам нужны функции чтения/записи:
/***************************************************************************************/ uint8_t sendByte(uint8_t byteToSend) { timeout = TIMEOUT_TIME; while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) & (timeout != 0)) { timeout--; } SPI_SendData8(SPI1, byteToSend); timeout = TIMEOUT_TIME; while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) & (timeout != 0)) { timeout--; } return (uint8_t)SPI_ReceiveData8(SPI1); } /***************************************************************************************/ void writeData(uint8_t address, uint8_t dataToWrite) { GPIO_ResetBits(GPIOE, GPIO_Pin_3); sendByte(address); sendByte(dataToWrite); GPIO_SetBits(GPIOE, GPIO_Pin_3); } /***************************************************************************************/ uint8_t readData(uint8_t address) { GPIO_ResetBits(GPIOE, GPIO_Pin_3); sendByte(address); tempByte = sendByte(DUMMY); GPIO_SetBits(GPIOE, GPIO_Pin_3); return tempByte; } /***************************************************************************************/
Итак, первая по списку, но не по важности - функция sendByte() - она нам пригодится как в функции чтения, так и в функции записи. Тут стоит обратить внимание на один момент, а именно наличие таймаута при проверке флагов SPI. Зачем это нужно? А все просто, если по какой-то причине произошла потеря каких-нибудь данных и флаг не взлетел, то без таймаута программа намертво зависнет в цикле while(), а это, естественно, не очень хорошо, а точнее очень плохо.
Функция для записи данных - writeData() - там все понятно - Chip Select в ноль, пишем адрес регистра, пишем данные и Chip Select вверх.
Чтение не намного сложнее - но есть один немаловажный момент. Мало просто отправить команду на чтение, для того, чтобы получить данные, необходимо, чтобы на шине было тактирование. Поэтому для приема данных надо отправить на шину так называемый dummy байт - то есть, например 0x00. Тогда мы без проблем сможем принять нужные нам значения.
Вроде бы все, теперь дело за функцией main():
/***************************************************************************************/ int main() { initAll(); writeData(0x20, 0x0F); while(1) { receiveData[0] = readData(0x8F); } } /***************************************************************************************/
Читаем данные из регистра WHO_AM_I (адрес 0x0F) и смотрим, что из этого вышло:
Все верно, получили значение 0xD4, которое, как сказано в документации, и должно храниться в этом регистре. Таким образом, все работает правильно, и на этом мы сегодня заканчиваем. Но скоро обязательно вернемся к опытам с гироскопом и платой STM32F3Discovery.