{"id":771,"date":"2025-05-17T00:59:07","date_gmt":"2025-05-17T07:59:07","guid":{"rendered":"https:\/\/nuclearprojects.com\/blog\/?p=771"},"modified":"2025-05-19T22:46:29","modified_gmt":"2025-05-20T05:46:29","slug":"communicating-with-bmi088-using-i2c-and-spi","status":"publish","type":"post","link":"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/","title":{"rendered":"Communicating with BMI088 using I2C and SPI"},"content":{"rendered":"<p><strong>I<\/strong>n my continued work with inertial sensors, specifically IMU&#8217;s for use in autonomous navigation systems, the latest sensor I&#8217;ve been using is the <strong>BMI088<\/strong> from Bosch. It&#8217;s well regarded for it&#8217;s ability to handle vibrations and is supposed to be well suited for robotics and drones. Onboard is a 3-axis accelerometer and 3-axis gyro. While they are on the same chip, they exist as two separate sensors, so communication with each must be handled individually with some uniqueness between the two. The IMU can handle both I2C and SPI protocols. This page aims mostly to keep track of notes for myself, but hopefully helpful to others as well <em>(<span style=\"color: #003366;\">computer science + electrical engineering from the perspective of a mechanical engineer, oh joy!<\/span>)<\/em>.<\/p>\r\n<h1>I2C<\/h1>\r\n<p>The first BMI088 breakout board I tried was the Grove board from Seeed Studios. It is configured for I2C only (no SPI connections).<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/grove_bmi088\/\" rel=\"attachment wp-att-787\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-787 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/grove_bmi088-300x240.jpg\" alt=\"\" width=\"215\" height=\"172\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/grove_bmi088-300x240.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/grove_bmi088.jpg 679w\" sizes=\"auto, (max-width: 215px) 100vw, 215px\" \/><\/a><\/p>\r\n<p>There are some good libraries for this, including ones from <a href=\"https:\/\/github.com\/Seeed-Studio\/Grove_6Axis_Accelerometer_And_Gyroscope_BMI088\">Seeed<\/a> and <a href=\"https:\/\/github.com\/bolderflight\/bmi088-arduino\">Bolder Flight Systems<\/a>. These provide a good option to get up and running. However, wanting to develop a better understanding of this IMU and the communication protocols, I began to write my down drivers, starting development in Arduino, then porting to STM32 (which I&#8217;m also learning).<\/p>\r\n<p>Physical connections are straight forward: Power, GND, SDA and SCL.<\/p>\r\n<p>BMI088 spec sheet, diagram for I2C:<\/p>\r\n<p><!-- \/wp:post-content -->\r\n\r\n<!-- wp:image {\"id\":765,\"width\":\"585px\",\"height\":\"auto\",\"sizeSlug\":\"large\",\"linkDestination\":\"media\"} --><\/p>\r\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"542\" class=\"wp-image-765\" style=\"width: 585px; height: auto;\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3-1024x542.png\" alt=\"\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3-1024x542.png 1024w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3-300x159.png 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3-768x406.png 768w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-3.png 1051w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\r\n<p><!-- \/wp:image -->\r\n\r\n<!-- wp:paragraph --><\/p>\r\n<p>&nbsp;<\/p>\r\n<p>Communication works by the <strong>master<\/strong> (microcontroller) sending a message to the <strong>slave<\/strong> (BMI088) device and awaiting a reply. Each device connected to the I2C bus has it&#8217;s own address, which we must utilize in each communication. I think of the I2C bus (and similarly for SPI), as a neighborhood. The I2C bus (literal wires connecting to the SDA and SCL pins) is like the main street through the neighborhood. Each &#8216;house&#8217; has an address. In this case, the &#8216;houses&#8217; are the gyro and accelerometer sensors. Within each &#8216;house&#8217; are a bunch of &#8216;rooms&#8217;, or in terms of the sensor, <em>registers<\/em>, each having their own address. These addresses are generally represented in hexadecimal form (<em>0x00<\/em>, <em>0x69<\/em>, etc.).<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2c_bus.jpg\" target=\"_blank\" rel=\"attachment noopener wp-att-775\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-775\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2c_bus.jpg\" alt=\"\" width=\"435\" height=\"242\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2c_bus.jpg 1000w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2c_bus-300x167.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2c_bus-768x427.jpg 768w\" sizes=\"auto, (max-width: 435px) 100vw, 435px\" \/><\/a><\/p>\r\n<p>&nbsp;<\/p>\r\n<p>The <strong>registers<\/strong> are where all the sensor data and settings reside. As an example, if we wish to read the device ID from the gyro, we must send a message to the gyro that contains both the sensor address and the register address we&#8217;re interested in. These addresses are all outlined in the BMI088 data sheet. This is the register information for the gyro ID:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/gyro_register_id\/\" rel=\"attachment wp-att-778\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-778 \" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_register_id.jpg\" alt=\"\" width=\"460\" height=\"142\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_register_id.jpg 712w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_register_id-300x93.jpg 300w\" sizes=\"auto, (max-width: 460px) 100vw, 460px\" \/><\/a><\/p>\r\n<p>We can see the register is at <em>0x00<\/em>. The <strong>reset value<\/strong>, or the default value at power-on is <em>0x0F<\/em>. Each register will have it&#8217;s own default setting. Some are read-only (such as the ID value above), others can be configured, such as the <em>gyro range<\/em> shown here at register <em>0x0F<\/em>:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/gyro_range\/\" rel=\"attachment wp-att-779\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-779 \" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_range.jpg\" alt=\"\" width=\"478\" height=\"301\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_range.jpg 859w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_range-300x189.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/gyro_range-768x484.jpg 768w\" sizes=\"auto, (max-width: 478px) 100vw, 478px\" \/><\/a><\/p>\r\n<p>The reset value for the gyro range is <em>0x00<\/em>, which corresponds to a default setting of +\/-2000 degrees\/second.<\/p>\r\n<p>Let&#8217;s look at a couple example code snippets for working with both of the registers presented above, starting with reading the <em>chip ID<\/em> for the gyro. To refresh:<\/p>\r\n<p>Gyro address on I2C bus: <strong>0x69<\/strong><\/p>\r\n<p>GYRO_CHIP_ID register: <strong>0x00<\/strong><\/p>\r\n<div>\r\n<div><strong>Arduino Code: <\/strong>(using Wire.h library)<\/div>\r\n<pre>Wire.beginTransmission(0x69); <span style=\"color: #008000;\">\/\/ Start communication with device at address 0x69<\/span><br \/>Wire.write(0x00); <span style=\"color: #008000;\">\/\/ Send the register address we're interested in (0x00)<\/span><br \/>Wire.endTransmission(false); <span style=\"color: #008000;\">\/\/ use 'false' to retain control of bus<\/span><br \/><br \/>Wire.requestFrom(0x69, 1); <span style=\"color: #008000;\">\/\/ requesting 1 byte from device at address 0x69<\/span><br \/><strong><span style=\"color: #993366;\">uint8_t<\/span><\/strong> gyroID = Wire.read(); <span style=\"color: #008000;\">\/\/ read the byte (device ID)<\/span><\/pre>\r\n<div>\r\n<p>&nbsp;<\/p>\r\n<p>Now, lets see how to write a value to the gyro. Specifically, let&#8217;s change the GYRO_RANGE from 2000 deg\/s to 500 deg\/s by changing the default register value from <em>0x00<\/em> to <em>0x02<\/em>.<\/p>\r\n<p><strong>Arduino Code:<\/strong> (using Wire.h library)<\/p>\r\n<div>\r\n<pre>Wire.beginTransmission(0x69); <span style=\"color: #008000;\">\/\/ Start communication with device at address 0x69<\/span><br \/>Wire.write(0x0F); <span style=\"color: #008000;\">\/\/ Send the register address we're interested in (0x00)<\/span><br \/>Wire.write(0x02); <span style=\"color: #008000;\">\/\/ Write the value 0x02 to the above specified register<\/span><br \/>Wire.endTransmission();<\/pre>\r\n<\/div>\r\n<\/div>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<p>Taking this over to the STM32 environment (using STM32CubeIDE), to read the gyro rates for all 3 axis:<\/p>\r\n<div style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\">\r\n<div style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\">\r\n<pre style=\"margin: 0;\"><strong><span style=\"color: #993366;\">#define<\/span><\/strong> GYRO_ADDR 0x69<br \/><strong><span style=\"color: #993366;\">#define<\/span><\/strong> GYRO_RATE_X_LSB_ADDR 0x02<br \/><span style=\"color: #3f7f5f;\"><span style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\"><span style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\"><span style=\"color: #000000;\"><span style=\"color: #005032;\"><br \/><\/span><\/span><\/span><\/span><\/span><span style=\"color: #993366;\"><strong>uint8_t<\/strong><\/span> sensorBuffer[6];<span style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\"><span style=\"color: #000000;\"><br \/><\/span><\/span><span style=\"color: #3f7f5f;\"><br \/><span style=\"color: #008000;\">\/\/ Tell gyro we want gyro rate<\/span><\/span><br \/><strong><span style=\"color: #993366;\">uint8_t<\/span><\/strong><span style=\"color: #000000;\"> buf[1] = { GYRO_RATE_X_LSB_ADDR };<\/span><br \/><span style=\"color: #000000;\">HAL_I2C_Master_Transmit(&amp;hi2c1, GYRO_ADDR&lt;&lt;1, buf, 1, 200);<\/span><br \/><br \/><span style=\"color: #008000;\">\/\/ Request 6 bytes - the BMI088 will autoincrement the register address after each byte read<span style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\"><span style=\"background-color: #ffffff; font-family: Consolas; font-size: 10pt; white-space: pre;\"><br \/><\/span><\/span>\/\/ We must shift the sensor address over 1 bit (Arduino does this automatically)<\/span><br \/><span style=\"color: #000000;\">HAL_I2C_Master_Receive(&amp;hi2c1, GYRO_ADDR&lt;&lt;1, sensorBuffer, 6, 500); <span style=\"color: #008000;\">\/\/ 500=timeout(ms)<\/span><\/span><br \/><br \/><span style=\"color: #008000;\">\/\/ Process gyro data<\/span><br \/><span style=\"color: #7f0055; font-weight: bold;\">for<\/span><span style=\"color: #000000;\">(<\/span><span style=\"color: #7f0055; font-weight: bold;\">int<\/span><span style=\"color: #000000;\"> i=0; i&lt;3; i++){<\/span><br \/><strong><span style=\"color: #993366;\">  uint8_t<\/span><\/strong><span style=\"color: #000000;\"> byteLSB, byteMSB;<\/span><br \/><span style=\"color: #993366;\"><strong>  int16_t<\/strong><\/span><span style=\"color: #000000;\"> twoByte;<\/span><br \/><br \/><span style=\"color: #000000;\">  byteLSB = sensorBuffer[i*2]; <\/span><span style=\"color: #3f7f5f;\">\/\/ Pull bytes into their own variables so we can manipulate them<\/span><br \/><span style=\"color: #000000;\">  byteMSB = sensorBuffer[(i*2)+1];<\/span><br \/><br \/><span style=\"color: #000000;\">  twoByte = byteMSB &lt;&lt; 8 | byteLSB; <\/span><span style=\"color: #3f7f5f;\">\/\/ Combine both bytes<\/span><br \/><br \/><span style=\"color: #7f0055; font-weight: bold;\">  float<\/span><span style=\"color: #000000;\"> rate = twoByte \/ 16.384; <\/span><span style=\"color: #3f7f5f;\">\/\/ (32768 \/ 2000deg\/s = 16.384) --- 32768 is largest signed number in 16-bit variable<\/span><br \/><br \/><span style=\"color: #000000;\">  gyro[i] = rate;<\/span><br \/><span style=\"color: #000000;\"> }<\/span><\/pre>\r\n<\/div>\r\n<\/div>\r\n<p>With regards to bit shifting the address on the STM32, this is because addresses are 7 bits long. You send the address plus the read\/write bit &#8211; for a total of 8 bits. The Arduino Wire library does this automatically, but we have to do this manually on the STM32.<\/p>\r\n<p>This diagram may be helpful to visualizing the address frame (7-bit address + 1-bit read\/write bit), from <a href=\"https:\/\/www.ti.com\/lit\/an\/sbaa565\/sbaa565.pdf?ts=1730006991981&amp;ref_url=https%253A%252F%252Fwww.google.co.th%252F\">Texas instruments<\/a> <em>(page 8).<\/em> Note that the MSB is sent first.<\/p>\r\n<p>&nbsp;<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/i2cdataframe\/\" rel=\"attachment wp-att-809\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-809 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2cdataframe.jpg\" alt=\"\" width=\"621\" height=\"167\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2cdataframe.jpg 1041w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2cdataframe-300x81.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2cdataframe-1024x275.jpg 1024w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/i2cdataframe-768x207.jpg 768w\" sizes=\"auto, (max-width: 621px) 100vw, 621px\" \/><\/a><\/p>\r\n<p>&nbsp;<\/p>\r\n<div>\r\n<p><strong>Important Notes:<\/strong><\/p>\r\n<ul>\r\n<li>For I2C, the SDA and SCL connections <strong>require<\/strong> pull-up resistors. The Grove board has 4.7k resistors tied to VCC. This works fine for 100 kHz speeds, but I ran into issues when trying to run at the maximum 400 kHz. The fix was to add a couple 2.2k resistors to up the voltage. The only downside of this is technically a little bit more power is used.<\/li>\r\n<li>ACC_PWR_CTRL for accelerometer (address <em>0x7D<\/em>) starts in &#8220;suspend&#8221; mode. It <strong>must<\/strong> be changed to &#8220;normal&#8221; mode by writing <em>0x04<\/em> to the register. <strong>There must be a delay of at least 5ms<\/strong> after this change before further communication with the sensor (else you&#8217;ll just get 0 values back).<\/li>\r\n<li>Start reading gyro rate data at address 0x02 (RATE_X_LSB). The LSB and MSB come in pairs (16-bit 2&#8217;s complement). Once the LSB byte is read, the MSB byte is locked until read to keep both bytes in sync.<\/li>\r\n<li>When reading accel or gyro data, a <strong>burst-access<\/strong> mechanism will auto-increment the register address being read. So for the gyro, which contains rate information for each axis at addresses 0x02 to 0x07, you can send a &#8220;read&#8221; byte to register 0x02, then just keep reading bytes until all 6 have been read. Works for both I2C and SPI protocols.<\/li>\r\n<\/ul>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<p>&nbsp;<\/p>\r\n<h1>SPI<\/h1>\r\n<div>\r\n<p>Since the Grove board doesn&#8217;t support SPI, I sought out a BMI088 breakout board that did. Apparently there really aren&#8217;t many options. The only available board I could find was the BMI088 Shuttle board from Bosch, which was designed to work with their development system. However, looking at the schematic, the board looks pretty bare-bones, so as long as I can work with the form factor, I&#8217;ll be golden.<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/shuttle\/\" rel=\"attachment wp-att-796\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-796 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/shuttle.jpg\" alt=\"\" width=\"279\" height=\"244\" \/><\/a><\/p>\r\n<p>The pin spacing on the Shuttle is only 0.05&#8243; instead of the typical 0.1&#8243; found on breakout boards. I found\u00a0 some adaptor boards on <a href=\"https:\/\/www.amazon.com\/dp\/B097YTGFS8\">Amazon<\/a> that will fix the pitch spacing.<a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/adapterboard\/\" rel=\"attachment wp-att-797\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-797 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboard-300x114.jpg\" alt=\"\" width=\"300\" height=\"114\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboard-300x114.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboard.jpg 357w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\r\n<p>The finished board:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/adapterboardfinished\/\" rel=\"attachment wp-att-800\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-800 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboardfinished.jpg\" alt=\"\" width=\"429\" height=\"347\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboardfinished.jpg 672w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/adapterboardfinished-300x243.jpg 300w\" sizes=\"auto, (max-width: 429px) 100vw, 429px\" \/><\/a><\/p>\r\n<p><strong>BMI088 Shuttle Pins:<\/strong><\/p>\r\n<\/div>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>Pin connections from <em>board<\/em>, top view (orientation coincides with my above photo):<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:image {\"id\":763,\"width\":\"411px\",\"height\":\"auto\",\"sizeSlug\":\"full\",\"linkDestination\":\"media\"} -->\r\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"502\" height=\"201\" class=\"wp-image-763\" style=\"width: 411px; height: auto;\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-1.png\" alt=\"\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-1.png 502w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-1-300x120.png 300w\" sizes=\"auto, (max-width: 502px) 100vw, 502px\" \/><\/a><\/figure>\r\n<!-- \/wp:image -->\r\n\r\n<!-- wp:image {\"id\":764,\"width\":\"599px\",\"height\":\"auto\",\"sizeSlug\":\"full\",\"linkDestination\":\"media\"} -->\r\n<figure class=\"wp-block-image size-full is-resized\"><\/figure>\r\n<!-- \/wp:image -->\r\n\r\n<!-- wp:paragraph -->\r\n<p>&nbsp;<\/p>\r\n<p>BMI088 pin-out and hardware connections on Shuttle:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-2.png\"><br \/><img loading=\"lazy\" decoding=\"async\" width=\"793\" height=\"280\" class=\"wp-image-764 aligncenter\" style=\"width: 599px; height: auto;\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-2.png\" alt=\"\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-2.png 793w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-2-300x106.png 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/image-2-768x271.png 768w\" sizes=\"auto, (max-width: 793px) 100vw, 793px\" \/><\/a><\/p>\r\n<p>&nbsp;<\/p>\r\n<p>From BMI088 datasheet:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-with-i2c-and-spi\/spiconnections\/\" rel=\"attachment wp-att-801\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-801 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiconnections.jpg\" alt=\"\" width=\"503\" height=\"253\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiconnections.jpg 892w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiconnections-300x151.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiconnections-768x386.jpg 768w\" sizes=\"auto, (max-width: 503px) 100vw, 503px\" \/><\/a><\/p>\r\n<p>&nbsp;<\/p>\r\n<!-- \/wp:paragraph -->\r\n\r\n<!-- wp:paragraph -->\r\n<p><strong>SPI communication:<\/strong><\/p>\r\n<p>The general process of working with the BMI088 using SPI is very similar to I2C, though there are some notable differences, especially with the accelerometer.<\/p>\r\n<ul>\r\n<li><strong>Gotcha #1<\/strong>: During read operations with the accelerometer <em>(this doesn&#8217;t apply to gyro)<\/em>, after the register address is sent to the chip, it will first send a &#8220;dummy&#8221; byte back, which must be discarded. For example, if we wish to read the accelerometer&#8217;s CHIP_ID, once we send the register address <em>0x00<\/em>, we will expect to get back <em>two<\/em> bytes. The first byte is the dummy, which we will ignore, the 2nd byte is the ID value we want. For reading the ACC data for all 3 axis, we&#8217;ll actually need to read 7 bytes rather than just the 6 we did with I2C, with the very first byte being discarded.<\/li>\r\n<li><strong>Gotcha #2<\/strong>: The accelerometer, <em>by default<\/em>, begins in <strong>suspend<\/strong> power mode. you <em><strong>MUST<\/strong> <\/em>change the mode to <strong>normal<\/strong> to activate this sensor by writing <em>0x04<\/em> to register ACC_PWR_CTRL.<\/li>\r\n<li><strong>Gotcha #3<\/strong>: The accelerometer, <em>by default<\/em>, begins in <strong>I2C<\/strong> mode. To change it to <strong>SPI<\/strong>, the CSB1 pin looks for a rising edge (setting that pin HIGH). Once switched to SPI mode, it will stay there until power-on-reset.<\/li>\r\n<\/ul>\r\n<ul class=\"wp-block-list\">\r\n<li>Ensure the microcontroller can handle the speed that is set in code. Fastest SPI speed is 1\/2 of microcontroller clock speed. For the Arduino UNO, which has a 16 MHz clock, the fastest SPI speed is 8 MHz (8000000 Hz) or less.<\/li>\r\n<!-- \/wp:paragraph --><\/ul>\r\n<ul>\r\n<li>In general, most SPI devices send MSB first, so bit #7 gets sent first, then #6, etc.<\/li>\r\n<li>With SPI, if you send a byte you have to receive a byte, and if you want to receive a byte, you have to send a byte. That means one of the devices will be sending a &#8220;dummy&#8221; byte.<\/li>\r\n<\/ul>\r\n<div class=\"brz-root__container\">Let&#8217;s jump into an Arduino code sample. Of note, ensure that the CS lines for both gyro and accelerometer are declared as &#8220;OUTPUT&#8221;, even if you should be working with only one of those two sensors <em>(I found out the hard way and left one &#8216;floating&#8217;&#8230; eventually I discovered why things weren&#8217;t working right!)<\/em>.<\/div>\r\n<div>\u00a0<\/div>\r\n<div><strong>Code to get things setup:<\/strong><\/div>\r\n<div>\r\n<div>\r\n<pre><strong><span style=\"color: #993366;\">#include<\/span><\/strong> &lt;SPI.h&gt;<br \/><br \/><strong><span style=\"color: #993366;\">#define<\/span><\/strong> CS_ACCEL 8 \u00a0<span style=\"color: #008000;\">\/\/ Chip select for accelerometer<\/span><br \/><strong><span style=\"color: #993366;\">#define<\/span><\/strong> CS_GYRO 9 \u00a0 <span style=\"color: #008000;\">\/\/ Chip select for gyroscope<\/span><br \/><br \/><strong><span style=\"color: #993366;\">float<\/span><\/strong> gyro[3], acc[3];<br \/><br \/>void setup() {<br \/><br \/>\u00a0 \u00a0 Serial.begin(115200);<br \/><br \/>\u00a0 \u00a0 delay(1); <span style=\"color: #008000;\">\/\/ ensure sensor has had at least 1ms boot-up time<\/span><br \/><br \/>\u00a0 \u00a0 SPI.begin();<br \/><br \/>\u00a0 \u00a0 pinMode(CS_ACCEL, OUTPUT);<br \/>  \u00a0 pinMode(CS_GYRO, OUTPUT);<br \/><br \/><span style=\"color: #008000;\">    \/\/ For both CSB1 (ACCEL CS) and CSB2 (GYRO CS), setting pins HIGH deselects them<br \/><\/span>    <span style=\"color: #008000;\">\/\/ For CSB1 (accelerometer), setting CSB1 high changes ACCELEROMETER protocol from I2C to SPI<\/span><br \/>  \u00a0 digitalWrite(CS_ACCEL, HIGH);<br \/>  \u00a0 digitalWrite(CS_GYRO, HIGH);<br \/>}<\/pre>\r\n<\/div>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<p>To read a register, I have this simple function, which I&#8217;ll break-down:<\/p>\r\n<div>\r\n<pre><strong><span style=\"color: #993366;\">uint8_t<\/span><\/strong> <strong>gyroReadRegister<\/strong>(uint8_t csPin, uint8_t reg) {<br \/>  \u00a0 digitalWrite(csPin, LOW); \/\/ Set gyro CS pin to LOW to select device<br \/>  \u00a0 SPI.transfer(reg | 0x80); \u00a0\/\/ Set read bit to 1 (0x80 = 0b10000000)<br \/>  \u00a0 <strong><span style=\"color: #993366;\">uint8_t<\/span><\/strong> value = SPI.transfer(0x00); \u00a0\/\/ Read<br \/>  \u00a0 digitalWrite(csPin, HIGH);<br \/>  \u00a0 return value;<br \/>}<\/pre>\r\n<\/div>\r\n<p>Using SPI, we have to first select the device we wish to communicate with on the bus. We do this by setting the CS (chip select) pin to LOW. Then, we send the register address. Note, at least for the gyro, this is a 16-bit protocol. We send either the register address + data byte, or register address + dummy byte. Because the address is only 7 bits, we need to add 1 more bit that tells the device if this is a READ or WRITE operation. A read operation sets this bit to 1, while a write operation uses 0. Since the MSB bit is sent first, we set the furthest left bit (bit #7) to 1. The <a href=\"https:\/\/eu.mouser.com\/datasheet\/2\/389\/lis3dsh-954955.pdf\">diagram<\/a> below gives a visual illustration of this. Note this is opposite of how I2C addresses are formatted (which place the R\/W bit on the far right at the LSB).<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/spiprotocol\/\" rel=\"attachment wp-att-817\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-full wp-image-817\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiprotocol.jpg\" alt=\"\" width=\"939\" height=\"348\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiprotocol.jpg 939w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiprotocol-300x111.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/spiprotocol-768x285.jpg 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/a><\/p>\r\n<p>Just like with I2C, we can then read the return byte. To do so, we end up sending a dummy byte for each read we want to do (<em>0x00<\/em> is used here). Sending this dummy byte also completes the 16-bit protocol requirement (see datasheet for details). Once we&#8217;ve read our byte, we deselect the device by returning the CS pin to HIGH.<\/p>\r\n<p>If were were <strong>writing<\/strong> a value to a register instead, the code would look like this:<\/p>\r\n<div>\r\n<pre><span style=\"color: #008000;\">\/\/ Write single value to sensor register (works for both gyro and accelerometer)<\/span><br \/><br \/>void writeRegister(uint8_t csPin, uint8_t reg, uint8_t value) {<br \/>  \u00a0 digitalWrite(csPin, LOW); \/\/ Select sensor<br \/>  \u00a0 SPI.transfer(reg); \u00a0\/\/ Send register address (write bit, MSB, should be 0 already)<br \/>  \u00a0 SPI.transfer(value); \/\/ Send single value<br \/>  \u00a0 digitalWrite(csPin, HIGH); \/\/ De-select sensor<br \/>}<\/pre>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<p>Moving back over to the STM32. I&#8217;m specifically using the <strong>STM32H723 Nucleo<\/strong> board for my current work. Configuring the SPI is fairly easy, though there are a lot of places to go wrong. I&#8217;ve setup SPI1. My settings to work with the BMI088 are shown below:<\/p>\r\n\n\t\t<style>\n\t\t\t#gallery-1 {\n\t\t\t\tmargin: auto;\n\t\t\t}\n\t\t\t#gallery-1 .gallery-item {\n\t\t\t\tfloat: left;\n\t\t\t\tmargin-top: 10px;\n\t\t\t\ttext-align: center;\n\t\t\t\twidth: 33%;\n\t\t\t}\n\t\t\t#gallery-1 img {\n\t\t\t\tborder: 2px solid #cfcfcf;\n\t\t\t}\n\t\t\t#gallery-1 .gallery-caption {\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\t\t\t\/* see gallery_shortcode() in wp-includes\/media.php *\/\n\t\t<\/style>\n\t\t<div id='gallery-1' class='gallery galleryid-771 gallery-columns-3 gallery-size-thumbnail'><dl class='gallery-item'>\n\t\t\t<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/spiconfig\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/SPIConfig-150x150.jpg\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt><\/dl><dl class='gallery-item'>\n\t\t\t<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/spiconfig2\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/SPIConfig2-150x150.jpg\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt><\/dl><dl class='gallery-item'>\n\t\t\t<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/pinconfig1\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/pinconfig1-150x150.jpg\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt><\/dl><br style=\"clear: both\" \/><dl class='gallery-item'>\n\t\t\t<dt class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/pinconfig2\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/pinconfig2-150x150.jpg\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" \/><\/a>\n\t\t\t<\/dt><\/dl>\n\t\t\t<br style='clear: both' \/>\n\t\t<\/div>\n\r\n<p>Things to note:<\/p>\r\n<ul>\r\n<li>The IDE will setup the SCK, MISO and MOSI pins. However, the CS pins must be manually configured by choosing a couple available pins to making them <strong>GPIO_Output<\/strong>. We&#8217;ll then manually control these in code to select the desired sensor.<\/li>\r\n<\/ul>\r\n<p>Here&#8217;s a learning experience I had. I ran into an issue with one of my chosen CS pins, PB2, which I&#8217;m using for the accelerometer. Confident I had my wiring correct, and having already gotten SPI working on the STM32 with the gyro, I spent far too long trying to get SPI working with the accelerometer. Eventually I discovered there are two PB2 pins on the board and they are not connected:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/pb2\/\" rel=\"attachment wp-att-825\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-825 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2.jpg\" alt=\"\" width=\"520\" height=\"402\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2.jpg 726w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2-300x232.jpg 300w\" sizes=\"auto, (max-width: 520px) 100vw, 520px\" \/><\/a><\/p>\r\n<p>I had my CS pin connected to PB2(D72), which wasn&#8217;t working. The other PB2 pin is D27. Once I swapped my wire over to the other PB2 pin, things suddenly worked as expected. But why? Diving back into the datasheet for the Nucleo board I found answers.<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/pb2_2\/\" rel=\"attachment wp-att-826\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-826 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2_2.jpg\" alt=\"\" width=\"485\" height=\"181\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2_2.jpg 766w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/PB2_2-300x112.jpg 300w\" sizes=\"auto, (max-width: 485px) 100vw, 485px\" \/><\/a><\/p>\r\n<p>Per the datasheet, PB2 is connected to QSPI_CLK by default, which corresponds to the PB2(D27) pin, and not the PB2(D72) pin. Mystery solved!<\/p>\r\n<p>Here&#8217;s a couple code samples to read and write from the gyro:<\/p>\r\n<div style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\">\r\n<div style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\">\r\n<pre style=\"margin: 0;\"><span style=\"color: #3f7f5f;\">\/\/ SPI - Gyro Read<\/span><br \/><span style=\"color: #7f0055; font-weight: bold;\">void<\/span> <span style=\"color: #000000; font-weight: bold;\">gyroRead<\/span><span style=\"color: #000000;\">(<\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\"> sensorRegister, <\/span><span style=\"color: #005032;\">uint16_t<\/span><span style=\"color: #000000;\"> numBytes, <\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\">* readBuffer)<\/span><br \/><span style=\"color: #000000;\">{<\/span><br \/><span style=\"color: #000000;\"> sensorRegister |= 0x80; <\/span><span style=\"color: #3f7f5f;\">\/\/ read operation, MSB must be set to 1<\/span><br \/><br \/><span style=\"color: #000000;\"> HAL_GPIO_WritePin(GPIOB, CS_GYRO_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_RESET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line LOW to select<\/span><br \/><span style=\"color: #000000;\"> HAL_SPI_Transmit(&amp;hspi1, &amp;sensorRegister, 1, 100); <\/span><span style=\"color: #3f7f5f;\">\/\/ send register address we wish to read from<\/span><br \/><span style=\"color: #000000;\"> HAL_SPI_Receive(&amp;hspi1, readBuffer, numBytes, 100);<\/span><br \/><span style=\"color: #000000;\"> HAL_GPIO_WritePin(GPIOB, CS_GYRO_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_SET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line HIGH to deselect<\/span><br \/><span style=\"color: #000000;\">}<\/span><\/pre>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<\/div>\r\n<div style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\">\r\n<div style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\">\r\n<pre style=\"margin: 0;\"><span style=\"color: #3f7f5f;\">\/\/ SPI - Gyro Write<\/span><br \/><span style=\"color: #7f0055; font-weight: bold;\">void<\/span> <span style=\"color: #000000; font-weight: bold;\">gyroWrite<\/span><span style=\"color: #000000;\">(<\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\"> sensorRegister, <\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\"> sensorMsg)<\/span><br \/><span style=\"color: #000000;\">{<\/span><br \/><span style=\"color: #005032;\"> uint8_t<\/span><span style=\"color: #000000;\"> TX_Buffer [] = { sensorRegister, sensorMsg } ; <\/span><span style=\"color: #3f7f5f;\">\/\/ DATA to send<\/span><br \/><br \/><span style=\"color: #000000;\"> HAL_GPIO_WritePin(GPIOB, CS_GYRO_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_RESET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line LOW to select<\/span><br \/><span style=\"color: #000000;\"> HAL_SPI_Transmit(&amp;hspi1, TX_Buffer, 2, 100); <\/span><span style=\"color: #3f7f5f;\">\/\/ tell how many bytes, wait time<\/span><br \/><span style=\"color: #000000;\"> HAL_GPIO_WritePin(GPIOB, CS_GYRO_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_SET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line HIGH to deselect<\/span><br \/><span style=\"color: #000000;\">}<\/span><\/pre>\r\n<\/div>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<p>Lets wrap this up with one more example reading from the accelerometer since it&#8217;s slightly different with needing to discard the first byte:<\/p>\r\n<div style=\"background-color: #ffffff; padding: 0px 0px 0px 2px;\">\r\n<div style=\"color: #000000; background-color: #ffffff; font-family: 'Consolas'; font-size: 10pt; white-space: pre;\">\r\n<pre style=\"margin: 0;\"><span style=\"color: #3f7f5f;\">\/\/ SPI - Accelerometer Read<\/span><br \/><span style=\"color: #7f0055; font-weight: bold;\">void<\/span> <span style=\"color: #000000; font-weight: bold;\">accelRead<\/span><span style=\"color: #000000;\">(<\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\"> sensorRegister, <\/span><span style=\"color: #005032;\">uint16_t<\/span><span style=\"color: #000000;\"> numBytes, <\/span><span style=\"color: #005032;\">uint8_t<\/span><span style=\"color: #000000;\">* readBuffer)<\/span><br \/><span style=\"color: #000000;\">{<\/span><br \/><span style=\"color: #000000;\"> sensorRegister |= 0x80; <\/span><span style=\"color: #3f7f5f;\">\/\/ read operation, MSB must be set to 1<\/span><br \/><span style=\"color: #005032;\"> uint8_t<\/span><span style=\"color: #000000;\"> discardByte;<br \/><\/span><br \/><span style=\"color: #000000;\"> HAL_GPIO_WritePin(GPIOB, CS_ACCEL_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_RESET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line LOW to select<\/span><br \/><span style=\"color: #000000;\"> <br \/> HAL_SPI_Transmit(&amp;hspi1, &amp;sensorRegister, 1, 100); <\/span><span style=\"color: #3f7f5f;\">\/\/ send register address we wish to read from<\/span><br \/><span style=\"color: #000000;\"> HAL_SPI_Receive(&amp;hspi1, &amp;discardByte, 1, 100); <\/span><span style=\"color: #3f7f5f;\">\/\/ first byte received is to be discarded (per datasheet)<\/span><br \/><span style=\"color: #000000;\"> HAL_SPI_Receive(&amp;hspi1, readBuffer, numBytes, 100);<\/span><br \/><span style=\"color: #000000;\"> <br \/> HAL_GPIO_WritePin(GPIOB, CS_ACCEL_Pin, <\/span><span style=\"color: #0000c0; font-style: italic;\">GPIO_PIN_SET<\/span><span style=\"color: #000000;\">); <\/span><span style=\"color: #3f7f5f;\">\/\/ set CS line HIGH to deselect<\/span><br \/><span style=\"color: #000000;\">}<\/span><\/pre>\r\n<\/div>\r\n<\/div>\r\n<p>&nbsp;<\/p>\r\n<h3>Bit Masking<\/h3>\r\n<p>Reading from some registers on the BMI088 (and many other devices for that matter) require some bitwise operations due to the formatting of the register data. As an example, the BMI088 accelerometer has a register, <em>0x02<\/em>, that returns error codes. There are two different errors grouped into one returned byte:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/acc_err\/\" rel=\"attachment wp-att-832\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-832 alignnone\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/acc_err.jpg\" alt=\"\" width=\"560\" height=\"257\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/acc_err.jpg 1054w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/acc_err-300x138.jpg 300w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/acc_err-1024x470.jpg 1024w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/acc_err-768x353.jpg 768w\" sizes=\"auto, (max-width: 560px) 100vw, 560px\" \/><\/a><\/p>\r\n<p>Bits 4:2 contain any &#8220;persistent errors&#8221;, and bit 0 is a flag indicating a &#8220;fatal error&#8221;. To pull these out of the returned byte, bit masking can be used. Using the above example, in order to retrieve the &#8220;error_code&#8221; for the accelerometer, we need to pull out bits 4:2 using the following bit mask:<\/p>\r\n<p><a href=\"https:\/\/nuclearprojects.com\/blog\/communicating-with-bmi088-using-i2c-and-spi\/bitwise1\/\" rel=\"attachment wp-att-833\"><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-833 aligncenter\" src=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/bitwise1.jpg\" alt=\"\" width=\"275\" height=\"109\" srcset=\"https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/bitwise1.jpg 313w, https:\/\/nuclearprojects.com\/blog\/wp-content\/uploads\/2025\/05\/bitwise1-300x119.jpg 300w\" sizes=\"auto, (max-width: 275px) 100vw, 275px\" \/><\/a><\/p>\r\n<p>Using the bitwise &#8216;&amp;&#8217; (AND) operator, we can extract just the 3 bits we care about. This result then needs to be shifted two(2) bits to the right. This might look like:<\/p>\r\n<pre><strong><span style=\"color: #993366;\">uint8_t<\/span><\/strong> error_code = (readByte &amp; 0x1C) &gt;&gt; 2;<\/pre>\r\n<p>&nbsp;<\/p>","protected":false},"excerpt":{"rendered":"<p>In my continued work with inertial sensors, specifically IMU&#8217;s for use in autonomous navigation systems, the latest sensor I&#8217;ve been using is the BMI088 from Bosch. It&#8217;s well regarded for it&#8217;s ability to handle vibrations and is supposed to be well suited for robotics and drones. Onboard is a 3-axis accelerometer and 3-axis gyro. While [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":800,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[36,34,38,35,40,39,37],"class_list":["post-771","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-everything-else","tag-arduino","tag-bmi088","tag-bosch","tag-c","tag-i2c","tag-spi","tag-stm32"],"_links":{"self":[{"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/posts\/771","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/comments?post=771"}],"version-history":[{"count":40,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/posts\/771\/revisions"}],"predecessor-version":[{"id":834,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/posts\/771\/revisions\/834"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/media\/800"}],"wp:attachment":[{"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/media?parent=771"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/categories?post=771"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nuclearprojects.com\/blog\/wp-json\/wp\/v2\/tags?post=771"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}