Долго, долго я не мог добраться до этого интерфейса. Вот не нравился он мне и все. Встречающиеся на каждом шагу SPI и USART покрывают почти все нужны, а вот датчики экономят провода, вот и понадобился i2c.
Честно сказать, при знакомстве подумалось, что это какой-то 1-wire. Ну очень похож.
Перед собственно программированием, давайте посмотрим, что это вообще такое.
I2C (еще его часто зовут IIC) — один из самых популярных интерфейсов у датчиков. Тут сказывается малое количество проводников! Для передачи данных используются две линии. SDA — линия передачи данных. SCL — линия тактирования. Оба провода должны быть подтянуты к питанию 10К резисторами. На шине может сидеть до 127 устройств. Подключаются они параллельно (если сказать умными словами «по схеме монтажного И»).
Цикл передачи данных включает в себя посылку старт сигнала, данных и стоп сигнала. Тут все очень похоже на USART. Если мастеру нужно послать посылку, он придавливает линию SDA к 0 при активном (единичном) состоянии линии SCL. Это служит для устройств на шине индикатором начала передачи. После этого мастер генерирует тактирование и передает побитно данные. Заканчивается все STOP последовательностью. Когда при высоком уровне на SCL линия SDA переходит с низкого на высокий уровень.
Есть еще куча примочек. Но не будем в них углубляться.
Теперь посмотрим как обобщатся с датчиком HMC5883L. Это цифровой компас.
Нам нужно включить в нем циклический режим измерения и читать данные из трех регистров (для трех осей). Регистры эти разбиты на пары (старшая часть и младшая), поэтому нужно будет еще их «склеивать».
Как водится в мире embedded программирования i2c нужно включить и настроить.
void HMC5883L_I2C_Init(void) { // Включаем тактирование нужных модулей RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // настройка I2C i2c.I2C_ClockSpeed = 100000; i2c.I2C_Mode = I2C_Mode_I2C; i2c.I2C_DutyCycle = I2C_DutyCycle_2; // адрес отфанарный i2c.I2C_OwnAddress1 = 0x15; i2c.I2C_Ack = I2C_Ack_Disable; i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(HMC5883L_I2C, &i2c); // I2C использует две ноги микроконтроллера, их тоже нужно настроить gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode = GPIO_Mode_AF; gpio.GPIO_Speed = GPIO_Speed_50MHz; gpio.GPIO_OType = GPIO_OType_OD; gpio.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &gpio); GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1); // стартуем модуль I2C I2C_Cmd(HMC5883L_I2C, ENABLE); }
Супер, модуль теперь работает. Дело за малым научится писать и читать.
void HMC5883L_I2C_ByteWrite(u8 slaveAddr, u8* pBuffer, u8 WriteAddr) { while(I2C_GetFlagStatus(HMC5883L_I2C, I2C_FLAG_BUSY)); /* шлем старт */ I2C_GenerateSTART(HMC5883L_I2C, ENABLE); /* ждем подтверждение */ while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT)); /* шлем HMC5883 адрес */ I2C_Send7bitAddress(HMC5883L_I2C, slaveAddr, I2C_Direction_Transmitter); /* ждем подтверждение */ while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /* шлем HMC5883L адрес регистра */ I2C_SendData(HMC5883L_I2C, WriteAddr); /* ждем окончания передачи */ while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* шлем данные */ I2C_SendData(HMC5883L_I2C, *pBuffer); /* ждем */ while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* шлем стоп */ I2C_GenerateSTOP(HMC5883L_I2C, ENABLE); } void HMC5883L_I2C_BufferRead(u8 slaveAddr, u8* pBuffer, u8 ReadAddr, u16 NumByteToRead) { /* ждем пока шина не освободится */ while (I2C_GetFlagStatus(HMC5883L_I2C, I2C_FLAG_BUSY)); /* шлем старт */ I2C_GenerateSTART(HMC5883L_I2C, ENABLE); while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT)); /* шлем адрес */ I2C_Send7bitAddress(HMC5883L_I2C, slaveAddr, I2C_Direction_Transmitter); while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /* Для очистки EV6 запускаем модуль снова */ I2C_Cmd(HMC5883L_I2C, ENABLE); /* шлем HMC5883L адрес чтения */ I2C_SendData(HMC5883L_I2C, ReadAddr); while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* шлем старт второй раз */ I2C_GenerateSTART(HMC5883L_I2C, ENABLE); while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT)); /* шлем HMC5883L адрес чтения */ I2C_Send7bitAddress(HMC5883L_I2C, slaveAddr, I2C_Direction_Receiver); while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); /* Пока есть что читать */ while (NumByteToRead) { if (NumByteToRead == 1) { I2C_AcknowledgeConfig(HMC5883L_I2C, DISABLE); I2C_GenerateSTOP(HMC5883L_I2C, ENABLE); } if (I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED)) { *pBuffer = I2C_ReceiveData(HMC5883L_I2C); pBuffer++; NumByteToRead--; } } I2C_AcknowledgeConfig(HMC5883L_I2C, ENABLE); }
Учитывая все вышесказанное, настройка датчика будет выглядеть вот так:
void HMC5883L_Init() { HMC5883L_I2C_Init(); u8 buf = 0; // 10k continious mode HMC5883L_I2C_ByteWrite(HMC5883L_DEFAULT_ADDRESS, &buf, HMC5883L_MODE_REG); HMC5883L_I2C_ByteWrite(HMC5883L_DEFAULT_ADDRESS, &buf, HMC5883L_CONF_REGB); }
А функции чтения значений из регистров данных вот так:
u16 HMC5883L_ReadX() { u8 msb,lsb; HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &msb, HMC5883L_DATA_OUT_X_MSB_REG, 1); HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &lsb, HMC5883L_DATA_OUT_X_LSB_REG, 1); return (msb << 8) | lsb; } u16 HMC5883L_ReadY() { u8 msb,lsb; HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &msb, HMC5883L_DATA_OUT_Y_MSB_REG, 1); HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &lsb, HMC5883L_DATA_OUT_Y_LSB_REG, 1); return (msb << 8) | lsb; } u16 HMC5883L_ReadZ() { u8 msb,lsb; HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &msb, HMC5883L_DATA_OUT_Z_MSB_REG, 1); HMC5883L_I2C_BufferRead(HMC5883L_DEFAULT_ADDRESS, &lsb, HMC5883L_DATA_OUT_Z_LSB_REG, 1); return (msb << 8) | lsb; }
Но это еще не все! Нужно ведь посчитать угол.
dataX = HMC5883L_ReadX(); dataY = HMC5883L_ReadY(); dataZ = HMC5883L_ReadZ(); x = dataX * 0.92; y = dataY * 0.92; z = dataZ * 0.92; // считаем угол angle = atan2(y, x); if (angle 2*PI) angle -= 2*PI; deg = (u16)(angle * (180 / PI)) ;
Учтите, что значения знаковые. Т.е. переменные dataX,Y,Z должны быть со знаком. Иначе Вы сможете измерить угол только от 0 до 45 градусов. Остальное будет мало сходится с реальностью.
Вот проект для IAR: https://www.dropbox.com/s/tnweie92fkripzv/stm32_HMC5883L.rar
Подписывайтесь, ставьте лайк! ну или как там говорят…
Это все замечательно, вот только портянки while'ов не радуют. Что, если МК помимо работы с датчиками, должен другую работу выполнять? Частота проца 168 МГц, а i2c даже если 400 кГц, то проц тупо простаивать кучу времени будет. Лучше на прерываниях делать. В stm вход в прерывание всего 12 тактов и событий i2c наверняка кучу можно использовать. Точно не знаю, но вряд ли тут ситуация хуже, чем в AVR.
Предположим, в системе с десяток датчиков, процу, помимо их опрашивания, необходимо выполнять другую работу. С такой реализацией он будет кучу времени простаивать. Ведь скорость шины i2c очень и очень невысока. Вывод: никаких while'ов, только прерывания. Конечно, надо реализовать логику общения с датчиками с учетом занятой шины, ошибок приема-передачи и др. В представленном виде реализация годится лишь "на попробовать".
именно на попровобавть и есть)
посмотреть кто такой i2c
Для этого в HAL есть обмен через прерывания или через DMA. Это пример только для проверки работоспособности.
I2C_Direction_Transmitter используется только при передаче, не не при приеме. Просмотрел разные ресурсы на тему I2C везде одна и та же ошибка.