STM-32/ MPU6500.
Learning how devices sense movement.
- What I built
- An STM32 system that reads gyroscope and accelerometer data from an MPU-6500.
- My contribution
- Studied the documentation, connected the components, wrote the C program, and tested it with mentor support.
- Key challenge
- Finding and understanding the relevant datasheet settings, then getting the hardware setup to work.
- Result
- The STM32 reads the sensor data. The working demonstration and complete C source are included below.
I started this project in the summer of 2025, when I was 15 years old. I was interested in how robots and drones understand their position and direction in space. To learn more about this in practice, I decided to connect an STM32 microcontroller to an MPU-6500 sensor and write a program to read data from its gyroscope and accelerometer.
My mentor was Muslim. He provided me with the equipment and helped me learn about electronics. He explained how transistors, resistors, capacitors, and sensors work. His support was especially important when I faced difficulties connecting and setting up the equipment.
I spent a lot of time studying technical documentation, including datasheets and other materials about the STM32 and MPU-6500. There was much more information than I expected. I had to find the correct sections, understand the settings, and learn how to use this information in my project. I could not understand everything immediately, so I had to return to some sections several times.
I completed most of the work independently at home. I studied documentation, connected components, wrote code, and tested the results. I managed to solve some problems on my own. However, when I could not find a solution, I asked Muslim for help. He helped me understand the causes of the problems and suggested where I should look for answers.
I wrote the program myself in C. I had been learning programming since I was 14, and my previous knowledge of C++ helped me understand C more quickly and write the code for this project. When I faced unfamiliar parts of STM32 programming, Muslim explained them to me. My previous experience helped me understand his explanations and apply them in practice.
Because of schoolwork and other projects, I could not work on this project regularly. As a result, it took approximately six months to complete. Despite the difficulties and breaks, I managed to finish it successfully. The STM32 was finally able to read data from the MPU-6500.
Now I understand what the measurements along the X, Y, and Z axes represent and how data from the gyroscope and accelerometer can help track the movement and orientation of a device. This project helped me better understand one of the principles behind how robots, drones, and unmanned boats work.
Explore the C codePress play to watch with sound.
The C code.
The complete supplied source, formatted for reading.
#include "periphery.h"
void init_tim() {
//-------TIM4-DELAAY--------------
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
TIM4->PSC = 71;
TIM4->ARR = 50000;
TIM4->CR1 |= TIM_CR1_CEN;
}
void init_gpio() {
RCC->APB2ENR |= (RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN |
RCC_APB2ENR_AFIOEN); //--PORT-A:B:C:ALT-FUNC-CLOCK-ENABLE
//-----PORT-A-RESET--
GPIOA->CRL = 0UL;
GPIOA->CRH = 0UL;
//-----PORT-B-RESET--
GPIOB->CRL = 0UL;
GPIOB->CRH = 0UL;
//-----PORT-C-RESET--
GPIOC->CRL = 0UL;
GPIOC->CRH = 0UL;
//---------ON-BOARD-LED---------
GPIOC->CRH |= GPIO_CRH_MODE13_0;
//---------INT---------------
GPIOB->CRL |= GPIO_CRL_CNF0_1;
//---------USART3--------------
//-PB10
GPIOB->CRH |= GPIO_CRH_MODE10;
GPIOB->CRH |= GPIO_CRH_CNF10_1;
//-PB11
GPIOB->CRH |= GPIO_CRH_CNF11_1;
//---------I2C-----------------
GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_CNF6; // I2C1 SCL
GPIOB->CRL |= GPIO_CRL_MODE7 | GPIO_CRL_CNF7; // I2C1 SDA
GPIOB->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6);
GPIOB->CRL &= ~(GPIO_CRL_MODE7 | GPIO_CRL_CNF7);
GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_CNF6;
GPIOB->CRL |= GPIO_CRL_MODE7 | GPIO_CRL_CNF7;
}
void init_usart(void) {
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
USART3->CR1 = 0UL;
USART3->BRR = 0x0138; //--9600BD = 0x0EA6; --115200BD = 0x0138;
USART3->CR1 |= USART_CR1_RXNEIE;
USART3->CR1 |= USART_CR1_TE;
USART3->CR1 |= USART_CR1_RE;
USART3->CR1 |= USART_CR1_UE;
NVIC_EnableIRQ(USART3_IRQn);
}
void I2C1_Init(void) {
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
I2C1->OAR1 = 0x00004000U;
I2C1->OAR2 = 0x0U;
I2C1->CR2 = 36; // set FREQ = APB1 = 18MHz
I2C1->CCR = 180; // 100 KHz
I2C1->TRISE = 37;
I2C1->CR1 |= I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_PE;
}
//------------------------------------------------------------------
void I2C_Master_Transmit(uint16_t DevAddress, uint8_t *pData, uint16_t Size) {
while (I2C1->SR2 & I2C_SR2_BUSY)
;
I2C1->CR1 &= ~I2C_CR1_POS;
uint8_t *Buff = pData;
uint16_t Count = Size;
uint16_t rSize = Count;
I2C1->CR1 |= I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
;
I2C1->DR = ((uint8_t)DevAddress << 1);
while (!(I2C1->SR1 & I2C_SR1_ADDR))
;
(void)I2C1->SR1;
(void)I2C1->SR2;
while (rSize > 0U) {
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
I2C1->DR = *Buff;
Buff++;
Count--;
rSize--;
if ((I2C1->SR1 & I2C_SR1_BTF) && (rSize != 0U)) {
I2C1->DR = *Buff;
Buff++;
Count--;
rSize--;
}
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
}
I2C1->CR1 |= I2C_CR1_STOP;
}
//------------------------------------------------------------------
void I2C_Master_Receive(uint16_t DevAddress, uint8_t *pData, uint16_t Size) {
while (I2C1->SR2 & I2C_SR2_BUSY)
;
I2C1->CR1 &= ~I2C_CR1_POS;
uint8_t *Buff = pData;
uint16_t Count = Size;
uint16_t rSize = Count;
I2C1->CR1 |= I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
;
I2C1->DR = ((uint8_t)((DevAddress << 1) | I2C_OAR1_ADD0));
while (!(I2C1->SR1 & I2C_SR1_ADDR))
;
if (rSize == 0U) {
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 |= I2C_CR1_STOP;
} else if (rSize == 1U) {
I2C1->CR1 &= ~I2C_CR1_ACK;
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 |= I2C_CR1_STOP;
} else if (rSize == 2U) {
I2C1->CR1 |= I2C_CR1_POS;
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 &= ~I2C_CR1_ACK;
} else {
I2C1->CR1 |= I2C_CR1_ACK;
(void)I2C1->SR1;
(void)I2C1->SR2;
}
while (rSize > 0U) {
if (rSize <= 3U) {
if (rSize == 1U) {
while (!(I2C1->SR1 & I2C_SR1_RXNE))
;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
} else if (rSize == 2U) {
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 |= I2C_CR1_STOP;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
} else {
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 &= ~I2C_CR1_ACK;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 |= I2C_CR1_STOP;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
}
} else {
while (!(I2C1->SR1 & I2C_SR1_RXNE))
;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
if (I2C1->SR1 & I2C_SR1_BTF) {
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
}
}
}
}
//-----------------------------------------------------------------
void I2C_Mem_Write(uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData,
uint16_t Size) {
while (I2C1->SR2 & I2C_SR2_BUSY)
;
I2C1->CR1 &= ~I2C_CR1_POS;
uint8_t *Buff = pData;
uint16_t Count = Size;
uint16_t rSize = Count;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
;
I2C1->DR = ((uint8_t)DevAddress << 1);
while (!(I2C1->SR1 & I2C_SR1_ADDR))
;
(void)I2C1->SR1;
(void)I2C1->SR2;
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
if (MemAddSize == 1) {
I2C1->DR = ((uint8_t)MemAddress);
} else {
I2C1->DR = ((uint8_t)(((uint16_t)((MemAddress) & (uint16_t)0xFF00)) >> 8));
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
I2C1->DR = ((uint8_t)MemAddress);
}
while (rSize > 0U) {
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
I2C1->DR = *Buff;
Buff++;
Count--;
rSize--;
if ((I2C1->SR1 & I2C_SR1_BTF) && (rSize != 0U)) {
I2C1->DR = *Buff;
Buff++;
Count--;
rSize--;
}
}
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 |= I2C_CR1_STOP;
}
//-------------------------------------------------------------------------
void I2C_Mem_Read(uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData,
uint16_t Size) {
while (I2C1->SR2 & I2C_SR2_BUSY)
;
I2C1->CR1 &= ~I2C_CR1_POS;
uint8_t *Buff = pData;
uint16_t Count = Size;
uint16_t rSize = Count;
I2C1->CR1 |= I2C_CR1_ACK;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
;
I2C1->DR = ((uint8_t)DevAddress << 1);
while (!(I2C1->SR1 & I2C_SR1_ADDR))
;
(void)I2C1->SR1;
(void)I2C1->SR2;
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
if (MemAddSize == 1) {
I2C1->DR = ((uint8_t)MemAddress);
} else {
I2C1->DR = ((uint8_t)(((uint16_t)((MemAddress) & (uint16_t)0xFF00)) >> 8));
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
I2C1->DR = ((uint8_t)MemAddress);
}
while (!(I2C1->SR1 & I2C_SR1_TXE))
;
I2C1->CR1 |= I2C_CR1_START;
while (!(I2C1->SR1 & I2C_SR1_SB))
;
I2C1->DR = ((uint8_t)((DevAddress << 1) | I2C_OAR1_ADD0));
while (!(I2C1->SR1 & I2C_SR1_ADDR))
;
if (rSize == 0U) {
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 |= I2C_CR1_STOP;
} else if (rSize == 1U) {
I2C1->CR1 &= ~I2C_CR1_ACK;
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 |= I2C_CR1_STOP;
} else if (rSize == 2U) {
I2C1->CR1 |= I2C_CR1_POS;
(void)I2C1->SR1;
(void)I2C1->SR2;
I2C1->CR1 &= ~I2C_CR1_ACK;
} else {
I2C1->CR1 |= I2C_CR1_ACK;
(void)I2C1->SR1;
(void)I2C1->SR2;
}
while (rSize > 0U) {
if (rSize <= 3U) {
if (rSize == 1U) {
while (!(I2C1->SR1 & I2C_SR1_RXNE))
;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
} else if (rSize == 2U) {
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 |= I2C_CR1_STOP;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
} else {
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 &= ~I2C_CR1_ACK;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
while (!(I2C1->SR1 & I2C_SR1_BTF))
;
I2C1->CR1 |= I2C_CR1_STOP;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
}
} else {
while (!(I2C1->SR1 & I2C_SR1_RXNE))
;
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
if (I2C1->SR1 & I2C_SR1_BTF) {
*Buff = (uint8_t)I2C1->DR;
Buff++;
Count--;
rSize--;
}
}
}
}
void USART3_IRQHandler(void) {
if (USART3->SR & USART_SR_RXNE) {
USART3->SR &= ~USART_SR_RXNE;
usart3_rx_buf[usart3_rx_inc] = USART3->DR;
usart3_rx_inc++;
usart3_rx_timeout = 50;
}
}
void USART_TX(char *array) {
while (*array != 0) {
while (!(USART3->SR & USART_SR_TXE))
;
USART3->DR = *array++;
}
}
void sys_clocking(void) {
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY))
;
FLASH->ACR &= ~FLASH_ACR_PRFTBE;
FLASH->ACR |= FLASH_ACR_PRFTBE;
FLASH->ACR |= FLASH_ACR_LATENCY_2;
RCC->CFGR |= RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE2_DIV1 | RCC_CFGR_PPRE1_DIV2;
RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL);
RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL9;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY))
;
RCC->CFGR &= ~RCC_CFGR_SW_PLL;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while (!(RCC->CFGR & RCC_CFGR_SWS_PLL))
;
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000);
}
void SysTick_Handler(void) {
if (wto > 0) {
wto--;
}
if (usart3_rx_timeout > 0) {
usart3_rx_timeout--;
if (usart3_rx_timeout <= 1) {
usart3_rx_done = 1;
usart3_rx_timeout = 0;
}
}
if (!mpu_read_flag) {
if (mpu_flag_time < 100) {
mpu_flag_time++;
} else {
mpu_read_flag = 1;
mpu_flag_time = 0;
}
}
}
void init_all() {
sys_clocking();
init_gpio();
init_tim();
init_usart();
I2C1_Init();
}