STM32F3. SPI и гироскоп L3GD20.

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

И для начала посмотрим, что же из себя представляет этот датчик:
Гироскоп L3GD20
Ну, собственно, датчик создан на базе микросхемы от STMicroelectronics L3GD20 и измеряет скорость вращения вокруг трех осей (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 бит для передачи адреса регистра, в который будет производится запись/чтение. На этом первый отправляемый байт заканчивается..)

А после первого байта, идет либо байт данных на запись, либо прочитанные данные, и на этом, в общем-то все 😉

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

Очень хочется уже перейти к практическому примеру……так и поступим, пожалуй ) Давайте включим и настроим 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.

Понравилась статья? Поделись с друзьями!

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

    • На ожидании флага RXNE вполне может заснуть программа, если линию, например, оборвало и никаких данных не пришло.

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

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

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

    • Только увидел первый комментарий, прошу прощения, что не ответил (
      OUT_Temp пробовал считывать — там число в районе 0х14-0х15 было всегда, но оно действительно уменьшалось с уменьшением температуры и наоборот увеличивалось с увеличением.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *