SPL: общение с МЭМС датчиком MPU9250 по интерфейсу I2C

     Краткое описание: В этом примере будет установлена связь с микро электро механическим датчиком MPU9250. Датчик подключен к микроконтроллеру по интерфейсу i2c. Реализуем стандартную задачу перед началом работы с датчиками этого типа, прочитав значение из регистра WHO_AM_I.

     Задача: Требуется написать с использованием библиотеки StdPeriph код. Программа должна производить настойку аппаратного i2c. Используя стандартные функции библиотеки StdPeriph для работы с i2c прочитать содержимое регистра WHO_AM_I. Успешное чтение данных подтвердить включением индикационного светодиода.  

#include "stm32f30x.h"
#include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_i2c.h"
#include "stm32f30x_tim.h"
#include "stm32f30x_syscfg.h"

#define ADR_MPU9250_W 0xD0 //(hex 0xD0 = bin 1101000 (адрес MPU9250)) + (bin 0 (write / бит записи))
#define ADR_MPU9250_R 0xD1 //(hex 0xD1 = bin 1101000 (адрес MPU9250)) + (bin 1 (read / бит чтения))
#define ADR_WHO_AM_I 0x75 //Регистр для проверки связи с микросхемой MPU9250 (содержит значение 0x71)

#define OWN_ADR 0x15 //Собственный адрес устройства I2C1
#define TIMING_I2C1 200000 //Частота тактового сигнала I2C1
#define PRESCALER_TIM2 720 //Пределитель чистоты для таймера 2
#define PERIOD_TIM2 100 //Период таймера 10 миллисекунд (1 единица значения равна 0,01 миллисекунде (10 микросекунд))

GPIO_InitTypeDef gpio; //Объявляем переменную gpio типа GPIO_InitTypeDef
I2C_InitTypeDef i2c; //Объявляем переменную I2C1 типа I2C_InitTypeDef
TIM_TimeBaseInitTypeDef timer; //Объявляем переменную timer типа TIM_TimeBaseInitTypeDef

int WHO_AM_I = 0; //Переменная которая будет хранить значение полученной по I2C1 из регистра ADR_WHO_AM_I

void initAll() //Функция инициализации
{
    RCC_DeInit(); //Сбрасываем настройки тактирования
    RCC_HSICmd(DISABLE); //Отключаем внутренний источник тактирования
    RCC_HSEConfig(RCC_HSE_ON); //Включаем внешний источник тактирования
    RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE); //Подаем тактовый сигнал на системную шину

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); //Тактирование I2C1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Тактирование таймера 2

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); //Тактирование порта C
    gpio.GPIO_Mode = GPIO_Mode_OUT; //Выбираем режим работы пинов
    gpio.GPIO_Pin = GPIO_Pin_6; //Выбираем нужны пин
    GPIO_Init(GPIOC, &gpio); //Инициализируем структуру с настройками

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); //Тактирование порта B
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_4); //Выбираем альтернативную функцию для работы с ножкой
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_4); //Выбираем альтернативную функцию для работы с ножкой

    //Настройки ножек I2C1
    gpio.GPIO_Mode = GPIO_Mode_AF; //Выбираем режим работы пинов
    gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; //Выбираем нужные пины
    gpio.GPIO_Speed = GPIO_Speed_50MHz; //Выбираем максимальную скорость
    gpio.GPIO_OType = GPIO_OType_OD; //Выход с открытым стоком
    gpio.GPIO_PuPd = GPIO_PuPd_UP; //Конфигурируем подтяжку
    GPIO_Init(GPIOB, &gpio); //Инициализируем структуру с настройками

    //Настройки модуля I2C1
    i2c.I2C_Timing = TIMING_I2C1; //Частота тактового сигнала i2c1
    i2c.I2C_Mode = I2C_Mode_I2C; //Решим работы
    i2c.I2C_OwnAddress1 = OWN_ADR; //Собственный адрес устройства
    i2c.I2C_Ack = I2C_Ack_Enable; //Включение/Отключения бита подтверждения ACK
    i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //Выбор формата адреса: 7 бит / 10 бит
    I2C_Init(I2C1, &i2c); //Инициализация настроек
    I2C_Cmd(I2C1, ENABLE); //Включение модуля i2c1

    TIM_TimeBaseStructInit(&timer); //Инициализация структуры
    timer.TIM_Prescaler = PRESCALER_TIM2; //Выбор пределителя
    timer.TIM_Period = PERIOD_TIM2; //Период досчитав до которого произойдет прерывание
    TIM_TimeBaseInit(TIM2, &timer); //Инициализация выбранных значений

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //Настраивает таймер на генерирования прерывания по переполнению
    NVIC_EnableIRQ(TIM2_IRQn); //Разрешаем соответствующие прерывания
    TIM_Cmd(TIM2, ENABLE); //Запускаем таймер
}

int main()
{
    initAll(); //Вызов функции инициализации i2c1
    __enable_irq(); //Разрешаются прерывания с линии IRQ на контроллере " disable_irq и enable_irq управляeт битом в регистре PRIMASK (запрет/разрешение)"

    while (1) {
    }
}

void TIM2_IRQHandler() //Функция прерывания по переполнению таймера 2
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //Функция очищает бит прерывания

    /*!!!!!!!!!!!!!Для работы с I2C1!!!!!!!!!!!!!*/
    //**************************************************************************************
    /*!!!!!!!!!!!!!Читаем данные из регистра ADR_WHO_AM_I = hex 0x75!!!!!!!!!!!!!*/
    //**************************************************************************************

    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) {}; //Ждем пока освободится шина I2C1

    /* Формируем первую посылку с адресом ведомого устройства и битом записи (ADR_MPU9250_W = hex 0xD0) / Выбираем режим работы  модуля / генерируем старт записи*/                                                                                   

    I2C_TransferHandling(I2C1, ADR_MPU9250_W, 1, I2C_AutoEnd_Mode, I2C_Generate_Start_Write);
    while (I2C_GetFlagStatus(I2C1, I2C_ISR_TXE) == RESET) {}; //Ждем, установки флага TXE

    I2C_SendData(I2C1, ADR_WHO_AM_I); //Отправляем адрес регистра из которого хотим прочитать данные (ADR_WHO_AM_I = hex 0x75)
    while (I2C_GetFlagStatus(I2C1, I2C_ISR_TXE) == RESET) {}; //Ждем, установки флага TXE

    /* Формируем посылку с адресом ведомого устройства и битом чтения (ADR_MPU9250_R = hex 0xD1) / Выбираем режим работы модуля / генерируем старт чтения*/
                                                                                                      
    I2C_TransferHandling(I2C1, ADR_MPU9250_R, 1, I2C_AutoEnd_Mode, I2C_Generate_Start_Read);
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET) {}; //Ждем, установки флага RXEN

    WHO_AM_I = I2C_ReceiveData(I2C1); //Запись данных в переменную WHO_AM_I
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) != RESET) {}; //Ждем, установки флага RXEN

    /*!!!!!!!!!!!!!По окончанию работы с I2С вызываем следующие функции!!!!!!!!!!!!!*/

    while (I2C_GetFlagStatus(I2C1, I2C_ISR_STOPF) == RESET); //Ждем установки флага STOPF

    I2C_ClearFlag(I2C1, I2C_ICR_STOPCF); //Очистить флаг STOPF

    if (WHO_AM_I == 0x71) {
        GPIO_SetBits(GPIOC, GPIO_Pin_6); //Зажигаем индикационный светодиод если прочитано значение 0x71
    }
}

     Скомпилируйте программу нажав кнопку Build, прошейте микроконтроллер, нажмите RESET. При запуске программы, произойдет чтение регистра WHO_AM_I, если полученное значение соответствует значению из документации производителя на датчик, то загорится индикационный светодиод. 

     ВНИМАНИЕ: Приведенный листинг кода не является единственной и оптимальной реализацией поставленной задачи. Пример опубликован с демонстрационной целью. Также автор во избежание переписывания чужих статей, пропускает теоретические основы необходимые для понимания примера, связывая это с тем что всю необходимую информацию можно найти в интернете.

Комментариев (0)

Написать комментарий

Имя *
E-mail
Введите комментарий *
Капча
33 + ? = 41