You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by GitBox <gi...@apache.org> on 2018/07/11 00:03:03 UTC

[GitHub] microbuilder opened a new issue #1257: I2C device simulation

microbuilder opened a new issue #1257: I2C device simulation
URL: https://github.com/apache/mynewt-core/issues/1257
 
 
   ## Introduction
   
   As discussed in #1251, my goal is to enable simulation of I2C devices using the `@apache-mynewt-core/hw/bsp/native` target in Mynewt to be able to test difficult to reproduce edge cases with sensor data in as natural a manner as possible (transparently switching between real HW and HW simulation).
   
   I'll put a PR together for this shortly, but require PRs #1251 and #1237 to be merged first since the simulation code builds on top of both of these, but is somewhat independent of the valid HW-only implementation already provided.
   
   ## Proposal
   
   In my opinion, the most reliable method of simulating an I2C device in my opinion is to implement the simulator at the lowest level, meaning that we capture and handle the `hal_i2c` init/read/write requests for any address we register in the native hal_i2c module.
   
   I would propose an additional `hal_i2c_sim.h` header that is only include when **`ARCH_sim`** is defined, with the following features:
   
   ### `hal_i2c_sim.h` (hw/hal)
   ```
   /*
    * Licensed to the Apache Software Foundation (ASF) under one
    * or more contributor license agreements.  See the NOTICE file
    * distributed with this work for additional information
    * regarding copyright ownership.  The ASF licenses this file
    * to you under the Apache License, Version 2.0 (the
    * "License"); you may not use this file except in compliance
    * with the License.  You may obtain a copy of the License at
    *
    *  http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing,
    * software distributed under the License is distributed on an
    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    * KIND, either express or implied.  See the License for the
    * specific language governing permissions and limitations
    * under the License.
    */
   
   
   /**
    * @addtogroup HAL
    * @{
    *   @defgroup HALI2cSim HAL I2c sim
    *   @{
    */
   
   #ifndef H_HAL_I2C_SIM_
   #define H_HAL_I2C_SIM_
   
   #include <inttypes.h>
   #include <hal/hal_i2c.h>
   
   #ifdef __cplusplus
   extern "C" {
   #endif
   
   /**
    * Initialize a new i2c device with the I2C number.
    *
    * @param i2c_num The number of the I2C device being initialized
    * @param cfg The hardware specific configuration structure to configure
    *            the I2C with.  This includes things like pin configuration.
    *
    * @return 0 on success, and non-zero error code on failure
    */
   typedef int (*hal_i2c_sim_init_t)(uint8_t i2c_num, void *cfg);
   
   /**
    * Sends a start condition and writes <len> bytes of data on the i2c bus.
    * This API does NOT issue a stop condition unless `last_op` is set to `1`.
    * You must stop the bus after successful or unsuccessful write attempts.
    * This API is blocking until an error or NaK occurs. Timeout is platform
    * dependent.
    *
    * @param i2c_num The number of the I2C device being written to
    * @param pdata The data to write to the I2C bus
    * @param timeout How long to wait for transaction to complete in ticks
    * @param last_op Master should send a STOP at the end to signify end of
    *        transaction.
    *
    * @return 0 on success, and non-zero error code on failure
    */
   typedef int (*hal_i2c_sim_master_write_t)(uint8_t i2c_num,
                struct hal_i2c_master_data *pdata, uint32_t timeout,
                uint8_t last_op);
   
   /**
   * Sends a start condition and reads <len> bytes of data on the i2c bus.
   * This API does NOT issue a stop condition unless `last_op` is set to `1`.
   * You must stop the bus after successful or unsuccessful write attempts.
   * This API is blocking until an error or NaK occurs. Timeout is platform
   * dependent.
   *
   * @param i2c_num The number of the I2C device being written to
   * @param pdata The location to place read data
   * @param timeout How long to wait for transaction to complete in ticks
   * @param last_op Master should send a STOP at the end to signify end of
   *        transaction.
   *
   * @return 0 on success, and non-zero error code on failure
   */
   typedef int (*hal_i2c_sim_master_read_t)(uint8_t i2c_num,
                struct hal_i2c_master_data *pdata,
                uint32_t timeout, uint8_t last_op);
   
   struct hal_i2c_sim_driver {
       hal_i2c_sim_init_t sd_init;
       hal_i2c_sim_master_write_t sd_write;
       hal_i2c_sim_master_read_t sd_read;
   
       /* 7-bit I2C device address */
       uint8_t addr;
   
       /* Reserved for future use */
       uint8_t rsvd[3];
   
       /* The next simulated sensor in the global sim driver list. */
       SLIST_ENTRY(hal_i2c_sim_driver) s_next;
   };
   
   /**
   * Register a driver simulator
   *
   * @param drv The simulated driver to register
   *
   * @return 0 on success, non-zero on failure.
   */
   int hal_i2c_sim_register(struct hal_i2c_sim_driver *drv);
   
   #ifdef __cplusplus
   }
   #endif
   
   #endif /* H_HAL_I2C_SIM_ */
   
   /**
    *   @} HALI2cSim
    * @} HAL
    */
   ```
   
   This allows simulated drivers to be **registered** via a call to `hal_i2c_sim_register`, and the simulated driver implementation can be placed in the same package as the HW driver implementation for easy maintenance.
   
   Inside the native mode hal_i2c implementation we have the following code, which implements a simple linked list of registered sensors, handling the **`hal_i2c_probe`**  call internally and forwarding all init/read/write requests when an address match is found:
   
   ### `hal_i2c.c` (mcu/native)
   ```
   /*
    * Licensed to the Apache Software Foundation (ASF) under one
    * or more contributor license agreements.  See the NOTICE file
    * distributed with this work for additional information
    * regarding copyright ownership.  The ASF licenses this file
    * to you under the Apache License, Version 2.0 (the
    * "License"); you may not use this file except in compliance
    * with the License.  You may obtain a copy of the License at
    *
    *  http://www.apache.org/licenses/LICENSE-2.0
    *
    * Unless required by applicable law or agreed to in writing,
    * software distributed under the License is distributed on an
    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    * KIND, either express or implied.  See the License for the
    * specific language governing permissions and limitations
    * under the License.
    */
   
   #include <stdio.h>
   #include "os/mynewt.h"
   #include "hal/hal_i2c.h"
   #include "hal/hal_i2c_sim.h"
   
   struct {
       struct os_mutex mgr_lock;
       SLIST_HEAD(, hal_i2c_sim_driver) mgr_sim_list;
   } hal_i2c_sim_mgr;
   
   int
   hal_i2c_init(uint8_t i2c_num, void *cfg)
   {
       os_mutex_init(&hal_i2c_sim_mgr.mgr_lock);
   
       return 0;
   }
   
   int
   hal_i2c_master_write(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
                        uint32_t timeout, uint8_t last_op)
   {
     struct hal_i2c_sim_driver *cursor;
   
     cursor = NULL;
     SLIST_FOREACH(cursor, &hal_i2c_sim_mgr.mgr_sim_list, s_next) {
         if (cursor->addr == pdata->address) {
             /* Forward the read request to the sim driver */
             return cursor->sd_write(i2c_num, pdata, timeout, last_op);
         }
     }
   
   #if 0
       printf("hal_i2c wrote %d byte(s) on device 0x%02X (I2C_%d):",
              pdata->len, pdata->address, i2c_num);
       for (uint16_t i=0; i<pdata->len; i++) {
           printf(" %02X", pdata->buffer[i]);
       }
       printf("\n");
       fflush(stdout);
   #endif
   
       return 0;
   }
   
   int
   hal_i2c_master_read(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
                       uint32_t timeout, uint8_t last_op)
   {
       struct hal_i2c_sim_driver *cursor;
   
       cursor = NULL;
       SLIST_FOREACH(cursor, &hal_i2c_sim_mgr.mgr_sim_list, s_next) {
           if (cursor->addr == pdata->address) {
               /* Forward the read request to the sim driver */
               return cursor->sd_read(i2c_num, pdata, timeout, last_op);
           }
       }
   
   #if 0
       printf("hal_i2c read  %d byte(s) on device 0x%02X (I2C_%d):",
              pdata->len, pdata->address, i2c_num);
       for (uint16_t i=0; i<pdata->len; i++) {
           printf(" %02X", pdata->buffer[i]);
       }
       printf("\n");
       fflush(stdout);
   #endif
   
       return 0;
   }
   
   int
   hal_i2c_master_probe(uint8_t i2c_num, uint8_t address,
                        uint32_t timeout)
   {
       struct hal_i2c_sim_driver *cursor;
   
       cursor = NULL;
       SLIST_FOREACH(cursor, &hal_i2c_sim_mgr.mgr_sim_list, s_next) {
           if (cursor->addr == address) {
               return 0;
           }
       }
   
       return -1;
   }
   
   /**
    * Lock sim manager to access the list of simulated drivers
    */
   int
   hal_i2c_sim_mgr_lock(void)
   {
       int rc;
   
       rc = os_mutex_pend(&hal_i2c_sim_mgr.mgr_lock, OS_TIMEOUT_NEVER);
       if (rc == 0 || rc == OS_NOT_STARTED) {
           return (0);
       }
       return (rc);
   }
   
   /**
    * Unlock sim manager once the list of simulated drivers has been accessed
    */
   void
   hal_i2c_sim_mgr_unlock(void)
   {
       (void) os_mutex_release(&hal_i2c_sim_mgr.mgr_lock);
   }
   
   /**
    * Insert a simulated driver into the list
    */
   static void
   hal_i2c_sim_mgr_insert(struct hal_i2c_sim_driver *driver)
   {
       struct hal_i2c_sim_driver *cursor, *prev;
   
       prev = cursor = NULL;
       SLIST_FOREACH(cursor, &hal_i2c_sim_mgr.mgr_sim_list, s_next) {
           prev = cursor;
       }
   
       if (prev == NULL) {
           SLIST_INSERT_HEAD(&hal_i2c_sim_mgr.mgr_sim_list, driver, s_next);
       } else {
           SLIST_INSERT_AFTER(prev, driver, s_next);
       }
   }
   
   int
   hal_i2c_sim_register(struct hal_i2c_sim_driver *drv)
   {
       int rc;
   
       rc = hal_i2c_sim_mgr_lock();
       if (rc != 0) {
           goto err;
       }
   
       printf("Registering I2C sim driver for 0x%02X\n", drv->addr);
       fflush(stdout);
       hal_i2c_sim_mgr_insert(drv);
   
       hal_i2c_sim_mgr_unlock();
   
       return (0);
   err:
       return (rc);
   }
   ```
   
   ## Sample Implementation
   
   A sample implementation (currently a basic proof of concept!) can be seen below, using the TSL2591 package contained in PR #1237:
   
   ### `tsl2591_sim.c`
   
   ```
   /**************************************************************************/
   /*!
       @file     tsl2591_sim.c
       @author   Kevin Townsend
       @section LICENSE
       Software License Agreement (BSD License)
       Copyright (c) 2018, Kevin Townsend
       All rights reserved.
       Redistribution and use in source and binary forms, with or without
       modification, are permitted provided that the following conditions are met:
       1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
       2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
       3. Neither the name of the copyright holders nor the
       names of its contributors may be used to endorse or promote products
       derived from this software without specific prior written permission.
       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
       EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
       DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
   /**************************************************************************/
   
   #include <string.h>
   #include <errno.h>
   #include "os/mynewt.h"
   #include "tsl2591/tsl2591.h"
   #include "tsl2591_priv.h"
   #include "hal/hal_i2c_sim.h"
   
   int
   tsl2591_sensor_sim_init(uint8_t i2c_num, void *cfg)
   {
       printf("TSL2591 init on I2C_%d\n", i2c_num);
       fflush(stdout);
   
       return 0;
   }
   
   int
   tsl2591_sensor_sim_write(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
                            uint32_t timeout, uint8_t last_op)
   {
       printf("TSL2591 wrote %d byte(s):", pdata->len);
       for (uint16_t i=0; i<pdata->len; i++) {
           printf(" 0x%02X", pdata->buffer[i]);
       }
       printf("\n");
       fflush(stdout);
   
       return 0;
   }
   
   int
   tsl2591_sensor_sim_read(uint8_t i2c_num, struct hal_i2c_master_data *pdata,
                               uint32_t timeout, uint8_t last_op)
   {
       printf("TSL2591  read %d byte(s):", pdata->len);
       for (uint16_t i=0; i<pdata->len; i++) {
           printf(" 0x%02X", pdata->buffer[i]);
       }
       printf("\n");
       fflush(stdout);
   
       return 0;
   }
   
   static const struct hal_i2c_sim_driver g_tsl2591_sensor_sim_driver = {
       .sd_init = tsl2591_sensor_sim_init,
       .sd_write = tsl2591_sensor_sim_write,
       .sd_read = tsl2591_sensor_sim_read,
       .addr = MYNEWT_VAL(TSL2591_SHELL_ITF_ADDR)
   };
   
   int
   tsl2591_sim_init(void)
   {
       printf("Registering TSL2591 sim driver\n");
       fflush(stdout);
   
       /* Register this sim driver with the hal_i2c simulator */
       hal_i2c_sim_register((struct hal_i2c_sim_driver *)
                            &g_tsl2591_sensor_sim_driver);
   
       return 0;
   }
   ```
   
   ## Sample Output
   
   This following output is seen when the above code is run using `bsp=@apache-mynewt-core/hw/bsp/native`
   
   ```
   uart0 at /dev/ttys002
   Registering TSL2591 sim driver
   Registering I2C sim driver for 0x29
   TSL2591 wrote 2 byte(s): 0xA0 0x03
   TSL2591 wrote 1 byte(s): 0xA1
   TSL2591  read 1 byte(s): 0x00
   TSL2591 wrote 2 byte(s): 0xA1 0x00
   TSL2591 wrote 1 byte(s): 0xA1
   TSL2591  read 1 byte(s): 0x00
   TSL2591 wrote 2 byte(s): 0xA1 0x00
   ```
   
   ### Summary
   
   While this is a work in progress, or perhaps more a proof of concept, the current code is valuable to model HW that is unavailable, only available in limited quantities, or where difficult to reproduce setups and scenarios are involved (repeatable light patterns or level, etc.).
   
   Further discussion on the best way to model I2C devices is of course welcome, and the main purpose in raising an issue here to begin with!

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services