{"id":20316082,"url":"https://github.com/lupyuen/rust-i2c-nuttx","last_synced_at":"2025-04-11T17:40:50.875Z","repository":{"id":87767172,"uuid":"468969275","full_name":"lupyuen/rust-i2c-nuttx","owner":"lupyuen","description":"Rust talks I2C to Bosch BME280 Sensor on Apache NuttX RTOS","archived":false,"fork":false,"pushed_at":"2022-03-21T09:14:20.000Z","size":301,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T13:39:47.197Z","etag":null,"topics":["bl602","bme280","i2c","nuttx","pinecone","rust","rust-embedded"],"latest_commit_sha":null,"homepage":"https://lupyuen.github.io/articles/rusti2c","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lupyuen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["lupyuen"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["paypal.me/lupyuen"]}},"created_at":"2022-03-12T03:33:40.000Z","updated_at":"2024-12-12T13:02:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"cbde23d4-50af-4f3e-9c9c-c4019f738d95","html_url":"https://github.com/lupyuen/rust-i2c-nuttx","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Frust-i2c-nuttx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Frust-i2c-nuttx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Frust-i2c-nuttx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lupyuen%2Frust-i2c-nuttx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lupyuen","download_url":"https://codeload.github.com/lupyuen/rust-i2c-nuttx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248451351,"owners_count":21105855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bl602","bme280","i2c","nuttx","pinecone","rust","rust-embedded"],"created_at":"2024-11-14T18:24:14.379Z","updated_at":"2025-04-11T17:40:50.866Z","avatar_url":"https://github.com/lupyuen.png","language":"Rust","funding_links":["https://github.com/sponsors/lupyuen","paypal.me/lupyuen"],"categories":[],"sub_categories":[],"readme":"# Rust talks I2C to Bosch BME280 Sensor on Apache NuttX RTOS\n\n(Tested on Pine64 PineCone BL602)\n\nRead the articles...\n\n-   [\"Rust talks I2C on Apache NuttX RTOS\"](https://lupyuen.github.io/articles/rusti2c)\n\n-   [\"Rust on Apache NuttX OS\"](https://lupyuen.github.io/articles/rust2)\n\nThis repo depends on...\n\n-   [lupyuen/rust-nuttx](https://github.com/lupyuen/rust-nuttx)\n\n# Install App\n\nTo add this repo to your NuttX project...\n\n```bash\n## TODO: Change this to the path of our \"incubator-nuttx-apps\" folder\npushd nuttx/apps/examples\ngit submodule add https://github.com/lupyuen/rust-i2c-nuttx rust_i2c\npopd\n```\n\nThen update the NuttX Build Config...\n\n```bash\n## TODO: Change this to the path of our \"incubator-nuttx\" folder\ncd nuttx/nuttx\n\n## Preserve the Build Config\ncp .config ../config\n\n## Erase the Build Config\nmake distclean\n\n## For BL602: Configure the build for BL602\n./tools/configure.sh bl602evb:nsh\n\n## For ESP32: Configure the build for ESP32.\n## TODO: Change \"esp32-devkitc\" to our ESP32 board.\n./tools/configure.sh esp32-devkitc:nsh\n\n## Restore the Build Config\ncp ../config .config\n\n## Edit the Build Config\nmake menuconfig \n```\n\nIn menuconfig, enable the Rust I2C App under \"Application Configuration\" → \"Examples\".\n\nTo build the NuttX + Rust project...\n\n```bash\ncd nuttx/apps/examples/rust_i2c\n./run.sh\n```\n\nIn NuttX Shell, enter this to run the app...\n\n```bash\nrust_i2c\n```\n\nHere's the output...\n\n```text\nNuttShell (NSH) NuttX-10.2.0-RC0\nnsh\u003e rust_i2c\nHello from Rust!\ntest_i2c\ntest_i2c: Register 0xd0 is 0x60\n\ntest_hal_read\ntest_hal_read: Register 0xd0 is 0x60\n\ntest_hal_write\ntest_hal_write: Write 0xA0 to register\ntest_hal_write: Register value is 0xa0\ntest_hal_write: Write 0x00 to register\ntest_hal_write: Register value is 0x00\n\nread_bme280\nRelative Humidity = 89.284164%\nTemperature = 29.942907 deg C\nPressure = 100483.04 pascals\nDone!\nnsh\u003e\n```\n\n# Rust Embedded HAL for NuttX\n\nThe Rust Embedded HAL for NuttX has been published at crates.io...\n\nhttps://crates.io/crates/nuttx-embedded-hal\n\nTo use it in your project, add this to your [Cargo.toml](rust/Cargo.toml):\n\n```text\n# External Rust libraries used by this module.  See crates.io.\n[dependencies]\nnuttx-embedded-hal = \"1.0.6\"  # Rust Embedded HAL for NuttX: https://crates.io/crates/nuttx-embedded-hal\n```\n\n[(Always use the latest version of __nuttx-embedded-hal__)](https://crates.io/crates/nuttx-embedded-hal)\n\n# From C to Rust\n\nThis is how we read an I2C Register in C...\n\n```c\nstatic int bme280_reg_read(const struct device *priv,\n    uint8_t start, uint8_t *buf, int size)\n{\n  DEBUGASSERT(priv != NULL);\n  DEBUGASSERT(buf != NULL);\n  struct i2c_msg_s msg[2];\n  int ret;\n\n  msg[0].frequency = priv-\u003efreq;\n  msg[0].addr      = priv-\u003eaddr;\n\n#ifdef CONFIG_BL602_I2C0\n  //  For BL602: Register ID must be passed as I2C Sub Address\n  msg[0].flags     = I2C_M_NOSTOP;\n#else\n  //  Otherwise pass Register ID as I2C Data\n  msg[0].flags     = 0;\n#endif  //  CONFIG_BL602_I2C0\n\n  msg[0].buffer    = \u0026start;\n  msg[0].length    = 1;\n\n  msg[1].frequency = priv-\u003efreq;\n  msg[1].addr      = priv-\u003eaddr;\n  msg[1].flags     = I2C_M_READ;\n  msg[1].buffer    = buf;\n  msg[1].length    = size;\n\n  ret = I2C_TRANSFER(priv-\u003ei2c, msg, 2);\n```\n\n[(Source)](https://github.com/lupyuen/bme280-nuttx/blob/main/driver.c#L155-L183)\n\nHow do we call __I2C_TRANSFER__ from a NuttX App? Thanks to the I2C Demo App we have the answer...\n\n```c\nint i2ctool_get(FAR struct i2ctool_s *i2ctool, int fd, uint8_t regaddr,\n                FAR uint16_t *result)\n{\n  struct i2c_msg_s msg[2];\n  ...\n  int ret = i2cdev_transfer(fd, msg, 2);\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/rusti2c/system/i2c/i2c_get.c#L158-L206)\n\n__i2cdev_transfer__ is defined as...\n\n```c\nint i2cdev_transfer(int fd, FAR struct i2c_msg_s *msgv, int msgc)\n{\n  struct i2c_transfer_s xfer;\n\n  /* Set up the IOCTL argument */\n\n  xfer.msgv = msgv;\n  xfer.msgc = msgc;\n\n  /* Perform the IOCTL */\n\n  return ioctl(fd, I2CIOC_TRANSFER, (unsigned long)((uintptr_t)\u0026xfer));\n}\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx-apps/blob/rusti2c/system/i2c/i2c_devif.c#L117-L129)\n\nLet's port this to Rust.\n\n# C Types and Constants\n\nEarlier we've seen __i2c_msg_s__ and __i2c_transfer_s__. They are defined as...\n\n```c\nstruct i2c_msg_s\n{\n  uint32_t frequency;         /* I2C frequency */\n  uint16_t addr;              /* Slave address (7- or 10-bit) */\n  uint16_t flags;             /* See I2C_M_* definitions */\n  FAR uint8_t *buffer;        /* Buffer to be transferred */\n  ssize_t length;             /* Length of the buffer in bytes */\n};\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L208-L215)\n\n```c\nstruct i2c_transfer_s\n{\n  FAR struct i2c_msg_s *msgv; /* Array of I2C messages for the transfer */\n  size_t msgc;                /* Number of messages in the array. */\n};\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L231-L235)\n\n__I2CIOC_TRANSFER__ is defined as...\n\n```c\n#define I2CIOC_TRANSFER      _I2CIOC(0x0001)\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L105-L129)\n\n___I2CIOC__ is defined as...\n\n```c\n#define _I2CIOC(nr)       _IOC(_I2CBASE,nr)\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L467-L468)\n\n___IOC__ and ___I2CBASE__ are defined as...\n\n```c\n#define _IOC(type,nr)   ((type)|(nr))\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L107)\n\n```c\n#define _I2CBASE        (0x2100) /* I2C driver commands */\n```\n\n[(Source)](https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L73)\n\nWe'll port these C Types and Constants to Rust as well.\n\n# Read I2C Register in Rust\n\nHere's how we read an I2C Register in Rust, ported from the above C code...\n\n```rust\n/// Test the I2C Port by reading an I2C Register\npub fn test_i2c() {\n    println!(\"test_i2c\");\n\n    //  Open I2C Port\n    let i2c = unsafe { \n        open(b\"/dev/i2c0\\0\".as_ptr(), O_RDWR) \n    };\n    assert!(i2c \u003e 0);\n\n    //  Read one I2C Register, starting at Device ID\n    let mut start = [BME280_REG_ID ; 1];\n    let mut buf   = [0u8 ; 1];\n\n    //  Compose I2C Transfer\n    let msg: [i2c_msg_s ; 2] = [\n        //  First I2C Message: Send Register ID\n        i2c_msg_s {\n            frequency: BME280_FREQ,   //  I2C Frequency\n            addr:      BME280_ADDR,   //  I2C Address\n            buffer:    start.as_mut_ptr(),      //  Buffer to be sent\n            length:    start.len() as ssize_t,  //  Length of the buffer in bytes\n\n            //  For BL602: Register ID must be passed as I2C Sub Address\n            #[cfg(target_arch = \"riscv32\")]  //  If architecture is RISC-V 32-bit...\n            flags:     I2C_M_NOSTOP,  //  I2C Flags: Send I2C Sub Address\n            \n            //  Otherwise pass Register ID as I2C Data\n            #[cfg(not(target_arch = \"riscv32\"))]  //  If architecture is not RISC-V 32-bit...\n            flags:     0,  //  I2C Flags: None\n\n            //  TODO: Check for BL602 specifically, not just RISC-V 32-bit\n        },\n        //  Second I2C Message: Receive Register Value\n        i2c_msg_s {\n            frequency: BME280_FREQ,  //  I2C Frequency\n            addr:      BME280_ADDR,  //  I2C Address\n            buffer:    buf.as_mut_ptr(),      //  Buffer to be received\n            length:    buf.len() as ssize_t,  //  Length of the buffer in bytes\n            flags:     I2C_M_READ,   //  I2C Flags: Read from I2C Device\n        },\n    ];\n\n    //  Compose ioctl Argument\n    let xfer = i2c_transfer_s {\n        msgv: msg.as_ptr(),         //  Array of I2C messages for the transfer\n        msgc: msg.len() as size_t,  //  Number of messages in the array\n    };\n\n    //  Execute I2C Transfer\n    let ret = unsafe { \n        ioctl(\n            i2c,\n            I2CIOC_TRANSFER,\n            \u0026xfer\n        )\n    };\n    assert!(ret \u003e= 0);\n\n    //  Show the received Register Value\n    println!(\n        \"test_i2c: Register 0x{:02x} is 0x{:02x}\",\n        BME280_REG_ID,  //  Register ID (0xD0)\n        buf[0]          //  Register Value (0x60)\n    );\n\n    //  Register Value must be BME280 Device ID (0x60)\n    assert!(buf[0] == BME280_CHIP_ID);\n     \n    //  Close the I2C Port\n    unsafe { close(i2c); }\n\n    //  Sleep 5 seconds\n    unsafe { sleep(5); }\n}\n```\n\n[(Source)](rust/src/test.rs)\n\nThe NuttX Types are ported from C to Rust like so...\n\n```rust\n/// I2C Message Struct: I2C transaction segment beginning with a START. A number of these can\n/// be transferred together to form an arbitrary sequence of write/read\n/// transfer to an I2C device.\n/// TODO: Import with bindgen from https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L208-L215\n#[repr(C)]\npub struct i2c_msg_s {\n    /// I2C Frequency\n    pub frequency: u32,\n    /// I2C Address\n    pub addr: u16,\n    /// I2C Flags (I2C_M_*)\n    pub flags: u16,\n    /// Buffer to be transferred\n    pub buffer: *mut u8,\n    /// Length of the buffer in bytes\n    pub length: ssize_t,\n}\n\n/// I2C Transfer Struct: This structure is used to communicate with the I2C character driver in\n/// order to perform IOCTL transfers.\n/// TODO: Import with bindgen from https://github.com/lupyuen/incubator-nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L231-L235\n#[repr(C)]\npub struct i2c_transfer_s {\n    /// Array of I2C messages for the transfer\n    pub msgv: *const i2c_msg_s,\n    /// Number of messages in the array\n    pub msgc: size_t,\n}\n```\n\n[(Source)](https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/lib.rs#L125-L153)\n\n# Test I2C Port\n\nTo build the NuttX + Rust project...\n\n```bash\ncd nuttx/apps/examples/rust_i2c\n./run.sh\n```\n\nIn NuttX Shell, enter this to run our Rust app...\n\n```bash\nrust_i2c\n```\n\nOur Rust app reads BME280 Register 0xD0 (Device ID), which should contain 0x60...\n\n```text\nNuttShell (NSH) NuttX-10.2.0-RC0\nnsh\u003e rust_i2c\nHello from Rust!\ntest_i2c\ni2cdrvr_ioctl: cmd=2101 arg=4201c378\nbl602_i2c_transfer: subflag=1, subaddr=0xd0, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x60\nbl602_i2c_transfer: i2c transfer success\ntest_i2c: Register 0xd0 is 0x60\nDone!\nnsh\u003e\n```\n\nYep our Rust app reads the BME280 I2C Register correctly!\n\n# Rust Embedded HAL\n\nRust Embedded HAL defines a standard API for I2C Operations. Let's wrap the NuttX I2C ioctl() Commands and expose as Rust Embedded HAL interfaces...\n\n```rust\n/// NuttX Implementation of I2C Read\nimpl i2c::Read for I2c {\n    ...\n    /// TODO: Read I2C data\n    fn read(\u0026mut self, addr: u8, buf: \u0026mut [u8]) -\u003e Result\u003c(), Self::Error\u003e { ... }\n}\n\n/// NuttX Implementation of I2C Write\nimpl i2c::Write for I2c {\n    ...\n    /// TODO: Write I2C data\n    fn write(\u0026mut self, addr: u8, buf: \u0026[u8]) -\u003e Result\u003c(), Self::Error\u003e { ... }\n}\n\n/// NuttX Implementation of I2C WriteRead\nimpl i2c::WriteRead for I2c {\n    ...\n    /// TODO: Write and read I2C data\n    fn write_read(\u0026mut self, addr: u8, wbuf: \u0026[u8], rbuf: \u0026mut [u8]) -\u003e Result\u003c(), Self::Error\u003e { ... }\n}\n```\n\n[(Source)](https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L20-L160)\n\n# Read I2C Register\n\nHere's how we implement the Rust Embedded HAL to read an I2C Register...\n\n```rust\n/// NuttX Implementation of I2C WriteRead\nimpl i2c::WriteRead for I2c {\n    /// Error Type\n    type Error = i32;\n\n    /// Write `wbuf` to I2C Port and read `rbuf` from I2C Port.\n    /// We assume this is a Read I2C Register operation, with Register ID at `wbuf[0]`.\n    /// TODO: Handle other kinds of I2C operations\n    fn write_read(\u0026mut self, addr: u8, wbuf: \u0026[u8], rbuf: \u0026mut [u8]) -\u003e Result\u003c(), Self::Error\u003e {\n        //  We assume this is a Read I2C Register operation, with Register ID at wbuf[0]\n        assert_eq!(wbuf.len(), 1);\n        let reg_id = wbuf[0];\n\n        //  Read I2C Registers, starting at Register ID\n        let mut start = [reg_id ; 1];\n\n        //  Compose I2C Transfer\n        let msg = [\n            //  First I2C Message: Send Register ID\n            i2c_msg_s {\n                frequency: self.frequency,  //  I2C Frequency\n                addr:      addr as u16,     //  I2C Address\n                buffer:    start.as_mut_ptr(),      //  Buffer to be sent\n                length:    start.len() as ssize_t,  //  Number of bytes to send\n\n                //  For BL602: Register ID must be passed as I2C Sub Address\n                #[cfg(target_arch = \"riscv32\")]  //  If architecture is RISC-V 32-bit...\n                flags:     crate::I2C_M_NOSTOP,  //  I2C Flags: Send I2C Sub Address\n                \n                //  Otherwise pass Register ID as I2C Data\n                #[cfg(not(target_arch = \"riscv32\"))]  //  If architecture is not RISC-V 32-bit...\n                flags:     0,  //  I2C Flags: None\n\n                //  TODO: Check for BL602 specifically (by target_abi?), not just RISC-V 32-bit\n            },\n            //  Second I2C Message: Receive Register Values\n            i2c_msg_s {\n                frequency: self.frequency,  //  I2C Frequency\n                addr:      addr as u16,     //  I2C Address\n                buffer:    rbuf.as_mut_ptr(),      //  Buffer to be received\n                length:    rbuf.len() as ssize_t,  //  Number of bytes to receive\n                flags:     I2C_M_READ,  //  I2C Flags: Read I2C Data\n            },\n        ];\n\n        //  Compose ioctl Argument\n        let xfer = i2c_transfer_s {\n            msgv: msg.as_ptr(),         //  Array of I2C messages for the transfer\n            msgc: msg.len() as size_t,  //  Number of messages in the array\n        };\n\n        //  Execute I2C Transfer\n        let ret = unsafe { \n            ioctl(\n                self.fd,\n                I2CIOC_TRANSFER,\n                \u0026xfer\n            )\n        };\n        assert!(ret \u003e= 0);   \n        Ok(())\n    }\n}\n```\n\n[(Source)](https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L98-L160)\n\nTo read an I2C Register, we call the Rust Embedded HAL like so...\n\n```rust\n/// Test the I2C HAL by reading an I2C Register\npub fn test_hal_read() {\n\n    //  Open I2C Port\n    let mut i2c = nuttx_embedded_hal::I2c::new(\n        \"/dev/i2c0\",  //  I2C Port\n        BME280_FREQ,  //  I2C Frequency\n    ).expect(\"open failed\");\n\n    //  Buffer for received I2C data\n    let mut buf = [0 ; 1];\n\n    //  Read one I2C Register, starting at Device ID\n    i2c.write_read(\n        BME280_ADDR as u8,  //  I2C Address\n        \u0026[BME280_REG_ID],   //  Register ID (0x60)\n        \u0026mut buf            //  Buffer to be received\n    ).expect(\"read register failed\");\n\n    //  Show the received Register Value\n    println!(\n        \"test_hal_read: Register 0x{:02x} is 0x{:02x}\",\n        BME280_REG_ID,  //  Register ID (0xD0)\n        buf[0]          //  Register Value (0x60)\n    );\n\n    //  Register Value must be BME280 Device ID (0x60)\n    assert_eq!(buf[0], BME280_CHIP_ID);\n}\n```\n\n[(Source)](rust/src/test.rs)\n\n# Test I2C HAL\n\nRust Embedded HAL works OK for reading an I2C Register!\n\n```text\nNuttShell NSH NuttX-10.2.0-RC0\nnsh\u003e rust_i2c\nHello from Rust!\n...\ntest_hal_read\ni2cdrvr_ioctl: cmd=2101 arg=4201c360\nbl602_i2c_transfer: subflag=1, subaddr=0xd0, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x60\nbl602_i2c_transfer: i2c transfer success\ntest_hal_read: Register 0xd0 is 0x60\nDone!\nnsh\u003e\n```\n\n# Write I2C Register\n\nThis code calls the Rust Embedded HAL to write the value 0xA0 to the I2C Register 0xF5....\n\n```rust\n/// Test the I2C HAL by writing an I2C Register\npub fn test_hal_write() {\n\n    //  Open I2C Port\n    let mut i2c = nuttx_embedded_hal::I2c::new(\n        \"/dev/i2c0\",  //  I2C Port\n        BME280_FREQ,  //  I2C Frequency\n    ).expect(\"open failed\");\n\n    //  Write 0xA0 to register 0xF5\n    i2c.write(\n        BME280_ADDR as u8,          //  I2C Address\n        \u0026[BME280_REG_CONFIG, 0xA0]  //  Register ID and value\n    ).expect(\"write register failed\");\n```\n\n[(Source)](rust/src/test.rs)\n\nBut the Logic Analyser shows that BL602 is writing to I2C the value 0x00 instead of 0xA0...\n\n```text\nSetup Write to [0xEE] + ACK\n0xF5 + ACK\n0x00 + ACK\n```\n\n![BL602 is writing to I2C the value 0x00 instead of 0xA0](https://lupyuen.github.io/images/rusti2c-logic1.png)\n\nLet's fix this. Here's the log for the I2C write...\n\n```text\nnsh\u003e rust_i2c\nHello from Rust!\ntest_hal_write\ni2cdrvr_ioctl: cmd=2101 arg=4201c370\nbl602_i2c_transfer: subflag=1, subaddr=0xf5, sublen=1\nbl602_i2c_send_data: count=1, temp=0xa0\nbl602_i2c_transfer: i2c transfer success\ntest_hal_write: Write 0xA0 to register\n```\n\n# Fix I2C Write\n\nBL602 has a peculiar I2C Port that uses I2C Sub Addresses ... Let's make it work with Rust Embedded HAL\n\n[\"Quirks in BL602 I2C Driver\"](https://lupyuen.github.io/articles/bme280#appendix-quirks-in-bl602-nuttx-i2c-driver)\n\nWe tried all sequences of I2C Read / Write / Sub Address. Only this strange sequence works for writing to I2C Registers...\n\n1.  Write I2C Register ID and I2C Data together as I2C Sub Address\n\n1.  Followed by Read I2C Data\n\n```rust\n/// NuttX Implementation of I2C Write\nimpl i2c::Write for I2c {\n    /// Error Type\n    type Error = i32;\n\n    /// Write `buf` to I2C Port.\n    /// We assume this is a Write I2C Register operation, with Register ID at `buf[0]`.\n    /// TODO: Handle other kinds of I2C operations\n    fn write(\u0026mut self, addr: u8, buf: \u0026[u8]) -\u003e Result\u003c(), Self::Error\u003e {\n        //  Copy to local buffer because we need a mutable reference\n        let mut buf2 = [0 ; 64];\n        assert!(buf.len() \u003c= buf2.len());\n        buf2[..buf.len()].copy_from_slice(buf);\n\n        //  Buffer for received I2C data\n        let mut rbuf = [0 ; 1];\n\n        //  Compose I2C Transfer\n        let msg = [\n            //  First I2C Message: Send Register ID and I2C Data as I2C Sub Address\n            i2c_msg_s {\n                frequency: self.frequency,  //  I2C Frequency\n                addr:      addr as u16,     //  I2C Address\n                buffer:    buf2.as_mut_ptr(),     //  Buffer to be sent\n                length:    buf.len() as ssize_t,  //  Number of bytes to send\n\n                //  For BL602: Register ID must be passed as I2C Sub Address\n                #[cfg(target_arch = \"riscv32\")]  //  If architecture is RISC-V 32-bit...\n                flags:     crate::I2C_M_NOSTOP,  //  I2C Flags: Send I2C Sub Address\n                \n                //  Otherwise pass Register ID as I2C Data\n                #[cfg(not(target_arch = \"riscv32\"))]  //  If architecture is not RISC-V 32-bit...\n                flags:     0,  //  I2C Flags: None\n\n                //  TODO: Check for BL602 specifically (by target_abi?), not just RISC-V 32-bit\n            },\n            //  Second I2C Message: Read I2C Data, because this forces BL602 to send the first message correctly\n            i2c_msg_s {\n                frequency: self.frequency,  //  I2C Frequency\n                addr:      addr as u16,     //  I2C Address\n                buffer:    rbuf.as_mut_ptr(),      //  Buffer to be received\n                length:    rbuf.len() as ssize_t,  //  Number of bytes to receive\n                flags:     I2C_M_READ,  //  I2C Flags: Read I2C Data\n            },\n        ];\n        \n        //  Compose ioctl Argument to write I2C Registers\n        let xfer = i2c_transfer_s {\n            msgv: msg.as_ptr(),         //  Array of I2C messages for the transfer\n            msgc: msg.len() as size_t,  //  Number of messages in the array\n        };\n\n        //  Execute I2C Transfer to write I2C Registers\n        let ret = unsafe { \n            ioctl(\n                self.fd,          //  I2C Port\n                I2CIOC_TRANSFER,  //  I2C Transfer\n                \u0026xfer             //  I2C Messages for the transfer\n            )\n        };\n        assert!(ret \u003e= 0);   \n        Ok(())\n    }\n}\n```\n\n[(Source)](https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L33-L96)\n\nAfter fixing, the Logic Analyser shows that BL602 writes correctly to the I2C Register! (With a harmless I2C Read at the end)\n\n```text\nSetup Write to [0xEE] + ACK\n0xF5 + ACK\n0xA0 + ACK\nSetup Read to [0xEF] + ACK\n0xA0 + NAK\n```\n\n![BL602 writes correctly to the I2C Register! With a harmless I2C Read at the end](https://lupyuen.github.io/images/rusti2c-logic3a.png)\n\nHere's the log...\n\n```text\nnsh\u003e rust_i2c\nHello from Rust!\ntest_hal_write\ni2cdrvr_ioctl: cmd=2101 arg=4201c358\nbl602_i2c_transfer: subflag=1, subaddr=0xa0f5, sublen=2\nbl602_i2c_recvdata: count=1, temp=0xa0\nbl602_i2c_transfer: i2c transfer success\ntest_hal_write: Write 0xA0 to register\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c370\nbl602_i2c_transfer: subflag=1, subaddr=0xf5, sublen=1\nbl602_i2c_recvdata: count=1, temp=0xa0\nbl602_i2c_transfer: i2c transfer success\ntest_hal_write: Register value is 0xa0\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c358\nbl602_i2c_transfer: subflag=1, subaddr=0xf5, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x0\nbl602_i2c_transfer: i2c transfer success\ntest_hal_write: Write 0x00 to register\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c370\nbl602_i2c_transfer: subflag=1, subaddr=0xf5, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x0\nbl602_i2c_transfer: i2c transfer success\ntest_hal_write: Register value is 0x00\nDone!\nnsh\u003e\n```\n\n_What if we write to the I2C Register without reading?_\n\nThe I2C Address is sent incorrectly (`0x02`) and the I2C Write gets truncated...\n\n```text\nSetup Write to [0x02] + NAK\n```\n\n![Write to I2C Register without reading](https://lupyuen.github.io/images/rusti2c-noread.png)\n\n_What if we send the Register ID and Register Value as I2C Data (flags = 0) instead of I2C Sub Address?_\n\nThe Register ID and value are sent incorrectly as `0x00 0x00`...\n\n```text\nSetup Write to [0xEE] + ACK\n0x00 + ACK\n0x00 + ACK\n(...600 microseconds later...)\nSetup Read to [0xEF] + ACK\n0x00 + NAK\n```\n\n![Send the Register ID and Register Value as I2C Data instead of I2C Sub Address](https://lupyuen.github.io/images/rusti2c-nosubaddr.png)\n\n# Rust Embedded Driver for BME280\n\nNow that our Rust Embedded HAL is working on NuttX, let's test the Rust Embedded Driver for BME280...\n\nhttps://crates.io/crates/bme280\n\nWe add the BME280 Driver to [Cargo.toml](rust/Cargo.toml)...\n\n```text\n# External Rust libraries used by this module.  See crates.io.\n[dependencies]\nbme280 = \"0.2.1\"  # BME280 Driver: https://crates.io/crates/bme280\n```\n\nHere's how we read the Temperature, Humidity and Pressure from the BME280 Driver...\n\n```rust\n/// Read Temperature, Pressure and Humidity from BME280 Sensor over I2C\npub fn read_bme280() {\n    println!(\"read_bme280\");\n\n    //  Open I2C Port\n    let i2c = nuttx_embedded_hal::I2c::new(\n        \"/dev/i2c0\",  //  I2C Port\n        400000,       //  I2C Frequency: 400 kHz\n    ).expect(\"open failed\");\n    \n    //  Init the BME280 Driver\n    let mut bme280 = bme280::BME280::new(\n        i2c,   //  I2C Port\n        0x77,  //  I2C Address of BME280\n        nuttx_embedded_hal::Delay  //  Delay Interface\n    );\n\n    //  Init the BME280 Senor\n    bme280.init()\n        .expect(\"init failed\");\n\n    //  Measure Temperature, Pressure and Humidity\n    let measurements = bme280.measure()\n        .expect(\"measure failed\");\n\n    //  Print the measurements\n    println!(\"Relative Humidity = {}%\", measurements.humidity);\n    println!(\"Temperature = {} deg C\",  measurements.temperature);\n    println!(\"Pressure = {} pascals\",   measurements.pressure);\n}\n```\n\n[(Source)](rust/src/bme280.rs)\n\n# Test Rust Driver for BME280\n\nRust Driver for BME280 works OK on NuttX!\n\n```text\nnsh\u003e rust_i2c\nHello from Rust!\nread_bme280\ni2cdrvr_ioctl: cmd=2101 arg=4201c340\nbl602_i2c_transfer: subflag=1, subaddr=0xd0, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x60\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c328\nbl602_i2c_transfer: subflag=1, subaddr=0xb6e0, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x0\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c350\nbl602_i2c_transfer: subflag=1, subaddr=0x88, sublen=1\nbl602_i2c_recvdata: count=26, temp=0x65e66e97\nbl602_i2c_recvdata: count=22, temp=0x8f990032\nbl602_i2c_recvdata: count=18, temp=0xbd0d581\nbl602_i2c_recvdata: count=14, temp=0xffdb1e71\nbl602_i2c_recvdata: count=10, temp=0x26acfff9\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c350\nbl602_i2c_transfer: subflag=1, subaddr=0xe1, sublen=1\nbl602_i2c_recvdata: count=7, temp=0x14000165\nbl602_i2c_recvdata: count=3, temp=0x141e000b\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c340\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0000\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c338\nbl602_i2c_transfer: subflag=1, subaddr=0x1f2, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x141e0001\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c340\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0000\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c338\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x141e0000\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c340\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0000\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c338\nbl602_i2c_transfer: subflag=1, subaddr=0x54f4, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x141e0054\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c340\nbl602_i2c_transfer: subflag=1, subaddr=0xf5, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0000\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c338\nbl602_i2c_transfer: subflag=1, subaddr=0x10f5, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x141e0010\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c370\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0054\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c370\nbl602_i2c_transfer: subflag=1, subaddr=0xf4, sublen=1\nbl602_i2c_recvdata: count=1, temp=0x141e0054\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c368\nbl602_i2c_transfer: subflag=1, subaddr=0x55f4, sublen=2\nbl602_i2c_recvdata: count=1, temp=0x141e0055\nbl602_i2c_transfer: i2c transfer success\n\ni2cdrvr_ioctl: cmd=2101 arg=4201c380\nbl602_i2c_transfer: subflag=1, subaddr=0xf7, sublen=1\nbl602_i2c_recvdata: count=8, temp=0x86f0b752\nbl602_i2c_recvdata: count=4, temp=0x7b8f806b\nbl602_i2c_transfer: i2c transfer success\n\nRelative Humidity = 87.667625%\nTemperature = 30.358515 deg C\nPressure = 100967.46 pascals\nDone!\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Frust-i2c-nuttx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupyuen%2Frust-i2c-nuttx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupyuen%2Frust-i2c-nuttx/lists"}