Долго, долго я не мог добраться до этого интерфейса. Вот не нравился он мне и все. Встречающиеся на каждом шагу 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 везде одна и та же ошибка.