Top.Mail.Ru

STM32 и гироскоп L3GD20. Часть 1. Настройка и обмен данными.

Как уже понятно из названия статьи, сегодня речь пойдет об организации обмена данными между микроконтроллером STM32F3 и гироскопом L3GD20. Тут даже добавить нечего )

Микросхема L3GD20 от STMicroelectronics измеряет скорость вращения вокруг трех осей (x, y, z), и, соответственно, является 3-осевым гироскопом. Возможны два варианта общения с гироскопом - по шине I2C или SPI. Я буду использовать плату STM32F3Discovery, на которой уже распаяны контроллер и гироскоп, причем реализовано подключение по SPI, поэтому этот интерфейс и буду использовать. Вот кстати схема включения:

STM32F3Discovery и L3GD20

Формат посылки по SPI имеет следующий вид:

Формат данных

На протяжении всего обмена данными Chip Select должен быть в нуле, что, собственно, и видно из рисунка ) С тактированием тут все, я думаю, понятно, так что переходим сразу к формату посылки.

Первый бит отправляемого байта - бит R/W - если он равен 1, значит мы собираемся читать данные из L3GD20. Если нам нужно записать данные, то этот бит надо выставить в 0.

Следом идет бит MS - он нужен для выполнения ряда последовательных команд записи или чтения. Если выставить его в 1, то можно отправлять гироскопу байты данных подряд, а он сам будет инкрементировать адрес записываемого регистра. То есть, например, подаем команду на запись в регистр с адресом 0x01 с выставленным битом MS и отправляем подряд 5 байт данных. Тогда эти данные будут записаны в регистры с адресами 0x01, 0x02, 0x03, 0x04, 0x05. Полезная штука.

Движемся дальше... После бита MS следуют 6 бит для передачи адреса регистра, в который будет производится запись/чтение. На этом первый отправляемый байт заканчивается... А после первого байта, идет либо байт данных на запись, либо прочитанные данные, и на этом, в общем-то все.

Обязательно стоит почитать, ну или хотя бы полистать, документацию на этот датчик, поскольку он содержит в себе довольно-таки много всяких крутых штук. Тут и прерывания на разные события, и различные настройки фильтров. С этим всем мы будем разбираться при написании программы по мере необходимости.

Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также небольшая подборка на тему датчиков:

Самое время перейти к практическому примеру... Давайте включим и настроим 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) и смотрим, что из этого вышло:

Данные от гироскопа L3GD20

Все верно, получили значение 0xD4, которое, как сказано в документации, и должно храниться в этом регистре. Таким образом, все работает правильно, и на этом мы сегодня заканчиваем. Но скоро обязательно вернемся к опытам с гироскопом и платой STM32F3Discovery.

Подписаться
Уведомить о
guest

27 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Егор
Егор
10 лет назад

Только одно не понятно, зачем в функции отправки такой приём с таймаутом. когда ты ведущий в SPI у тебя флаг сто процентов взлетит, как только пройдут все тики синхронизации. Если такое использовать для 1-wire , или i2c, то понятно , что может из-за чего-нибудь ответ вовремя не прийти или вообще не прийти. Не понимаю зачем это в SPI? (я не супер эксперт)

Егор
Егор
Ответ на комментарий  Aveal
10 лет назад

Если линию MISO оборвёт, просто придут нули или какая нибудь случайная последовательность. Контроллер же ничего не ждёт , он просто опрашивает порт в соответствии со своими импульсами синхронизации, ему абсолютно всё равно ,что на входе. Но он всё равно поднимет флаг после того как отправит все импульсы синхронизации.

Андрей
Андрей
10 лет назад

Уважаемый автор, подскажите пожалуйста поподробнее как проверить значение регистра who_i_am (последний рисунок)

Андрей
Андрей
10 лет назад

Прошу прощения. Разобрался. Тогда другой вопрос. Вы не разбирались с регистром OUT_Temp? В нём находится непонятное значение, далёкое от действительности. Его необходимо калибровать?

Андрей
Андрей
10 лет назад

Уменьшается с увеличением температуры! Т.е температура=х минус значение в регистре. х-находить экспериментально..Информации мало.

Андрей
Андрей
10 лет назад

Несколько раз наклоняю плату на один и тот же угол: в xPosition накапливается ошибка...

Марина
Марина
10 лет назад

А вот в этом куске все верно?
// 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);

Если дергать вручную, то почему нога на вывод оформлена?

Марина
Марина
10 лет назад

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

Марина
Марина
10 лет назад

не могу понять, почему читаем регистр WHO_AM_I, у которого адрес 0x0F, командой receiveData[0] = readData(0x8F);
Почему 0x8F, а не 0x0F ?

Марина
Марина
10 лет назад

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

Антон Бауер
Антон Бауер
10 лет назад

Здравствуйте! можно спрошу? почему не работате след. код?
#define IRQIN GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);
while (IRQIN!=1){}; говорит хочу скобку ")", хотя все скобки на месте...
а такой код:
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8)!=1){}; работает. why?? почему??

Илья Петров
Илья Петров
10 лет назад

Примерную температуру гироскопа в градусах Цельсия можно посчитать по формуле:
t = 45 – OUT_Temp

Гость
Гость
9 лет назад

потому что после применения define строчка выглядит следующим образом
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);!=1){};

Альфис
Альфис
9 лет назад

В #define IRQIN GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);
Надо писать IRQIN() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8);//Скобки забыл после IRQIN
while (IRQIN() !=1);

nn_
nn_
9 лет назад

Мучился 3 дня с IAR'ом и stm32F3cube, пытаясь сделать то же самое - документацию по stm32F3cube фиг найдешь нормальную..., в самой библиотеке ошибки.. короче ужас. Отныне только SPL. Спасибо за урок;)

Владимир
Владимир
8 лет назад

Пробую делать вычитку регистра WHO_AM_I_A, в мелкосхеме LSM303C, ф-ции практически одинаковые, но вычитывается только DUMMY_BYTE, который выдаётся вторым, для получения тактирования. Уже не знаю где копать, возможно есть какие-то подводные камни?

Дмитрий
Дмитрий
8 лет назад

Всем здравствуйте. Имею такой вопрос. Настроил SPI между двумя stm32. Master — f4, slave — f100. Вопрос следующий. Отсылаю значение одной 16 битовой переменной. При приеме данных слейвом в первый раз младший бит всегда теряется. Отправляю 4 (b100), получаю 2 (b10). При приеме данных мастером все наоборот, появляется лишний бит. Отправил 2 (b10), получаю 4 (b100). При повторном приеме, передаче, приходят правильные данные и больше такое не возникает. В чем причина? Подскажите, заранее спасибо

Сергей
Сергей
Ответ на комментарий  Дмитрий
2 лет назад

там 4 режима работы: по возрастающему/ниспадающему фронту с низкого/высокого уровня

Алексей
Алексей
8 лет назад

Такой таймаут как минимум помогает избавится от проблемы зависания отладчика в строке ожидания флага.

while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)

Сначала думал что это баг IARа, так как зависание происходила только при просмотре регистров SPI. Но создав проет в Keil ситуация была та же самая.

Алексей
Алексей
8 лет назад

Такой таймаут как минимум помогает избавится от проблемы зависания отладчика в строке ожидания флага.

while ((SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)== RESET)

Сначала думал что это баг IARа, так как зависание происходила только при просмотре регистров SPI. Но создав проет в Keil ситуация была та же самая.

27
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x