Как уже понятно из названия статьи, сегодня речь пойдет об организации обмена данными между микроконтроллером 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.
Только одно не понятно, зачем в функции отправки такой приём с таймаутом. когда ты ведущий в SPI у тебя флаг сто процентов взлетит, как только пройдут все тики синхронизации. Если такое использовать для 1-wire , или i2c, то понятно , что может из-за чего-нибудь ответ вовремя не прийти или вообще не прийти. Не понимаю зачем это в SPI? (я не супер эксперт)
На ожидании флага RXNE вполне может заснуть программа, если линию, например, оборвало и никаких данных не пришло.
Если линию MISO оборвёт, просто придут нули или какая нибудь случайная последовательность. Контроллер же ничего не ждёт , он просто опрашивает порт в соответствии со своими импульсами синхронизации, ему абсолютно всё равно ,что на входе. Но он всё равно поднимет флаг после того как отправит все импульсы синхронизации.
Уважаемый автор, подскажите пожалуйста поподробнее как проверить значение регистра who_i_am (последний рисунок)
Прошу прощения. Разобрался. Тогда другой вопрос. Вы не разбирались с регистром OUT_Temp? В нём находится непонятное значение, далёкое от действительности. Его необходимо калибровать?
Только увидел первый комментарий, прошу прощения, что не ответил (
OUT_Temp пробовал считывать - там число в районе 0х14-0х15 было всегда, но оно действительно уменьшалось с уменьшением температуры и наоборот увеличивалось с увеличением.
Уменьшается с увеличением температуры! Т.е температура=х минус значение в регистре. х-находить экспериментально..Информации мало.
Да, действительно, с охлаждением растет
Несколько раз наклоняю плату на один и тот же угол: в xPosition накапливается ошибка...
Ну это просто пример как получить данные и что с ними делать
Хм, хотя у меня даже с таким кодом как в примере ошибка не накапливается..
А вот в этом куске все верно?
// 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);
Если дергать вручную, то почему нога на вывод оформлена?
елки, поняла. Слишком учиталась даташитом на гироскоп и забыла, кто в доме хозяин 🙂
=)))
не могу понять, почему читаем регистр WHO_AM_I, у которого адрес 0x0F, командой receiveData[0] = readData(0x8F);
Почему 0x8F, а не 0x0F ?
Ох, там же адреса-то всего шесть бит, а остальное - управляющие биты.. Дошло только после чтения второй части по гироскопу
Здравствуйте! можно спрошу? почему не работате след. код?
#define IRQIN GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);
while (IRQIN!=1){}; говорит хочу скобку ")", хотя все скобки на месте...
а такой код:
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8)!=1){}; работает. why?? почему??
Примерную температуру гироскопа в градусах Цельсия можно посчитать по формуле:
t = 45 – OUT_Temp
потому что после применения define строчка выглядит следующим образом
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);!=1){};
В #define IRQIN GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);
Надо писать IRQIN() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);//Скобки забыл после IRQIN
while (IRQIN() !=1);
Мучился 3 дня с IAR'ом и stm32F3cube, пытаясь сделать то же самое - документацию по stm32F3cube фиг найдешь нормальную..., в самой библиотеке ошибки.. короче ужас. Отныне только SPL. Спасибо за урок;)
Да, меня тоже куб пока не впечатлил )
Пробую делать вычитку регистра WHO_AM_I_A, в мелкосхеме LSM303C, ф-ции практически одинаковые, но вычитывается только DUMMY_BYTE, который выдаётся вторым, для получения тактирования. Уже не знаю где копать, возможно есть какие-то подводные камни?
Всем здравствуйте. Имею такой вопрос. Настроил SPI между двумя stm32. Master — f4, slave — f100. Вопрос следующий. Отсылаю значение одной 16 битовой переменной. При приеме данных слейвом в первый раз младший бит всегда теряется. Отправляю 4 (b100), получаю 2 (b10). При приеме данных мастером все наоборот, появляется лишний бит. Отправил 2 (b10), получаю 4 (b100). При повторном приеме, передаче, приходят правильные данные и больше такое не возникает. В чем причина? Подскажите, заранее спасибо
там 4 режима работы: по возрастающему/ниспадающему фронту с низкого/высокого уровня
Такой таймаут как минимум помогает избавится от проблемы зависания отладчика в строке ожидания флага.
while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)
Сначала думал что это баг IARа, так как зависание происходила только при просмотре регистров SPI. Но создав проет в Keil ситуация была та же самая.
Такой таймаут как минимум помогает избавится от проблемы зависания отладчика в строке ожидания флага.
while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)== RESET)
Сначала думал что это баг IARа, так как зависание происходила только при просмотре регистров SPI. Но создав проет в Keil ситуация была та же самая.