嵌入式技术论坛
直播中

王斌

8年用户 1395经验值
私信 关注
[经验]

基于RoboMasterC型开发板的I2C读取磁力计数据实验

  I2C简介
  I2C 是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。
  两线制代表 I2C 只需两根信号线,一根数据线 SDA,另一根是时钟线 SCL。这个也是I2C的优势所在,虽然传输速率较慢,但是占用引脚数量少,在引脚资源紧张的芯片上就特别好用。
  I2C 总线允许挂载多个主设备,但总线时钟同一时刻只能由一个主设备产生,并且要求每个连接到总线上的器件都有唯一的 I2C 地址,从设备可以被主设备寻址。那么我们想要在同一个I2C总线上使用多个主设备。
2.jpg
  这里善于思考的同学可能就会想到如果我们想要在同一个I2C总线上并联两个一样的设备ID冲突怎么办呢?
  之后带大家看传感器的Datasheet的时候,就会发现传感器上面有一个ADDR引脚,根据它引脚电平高低会切换ID号,这样就可以避免ID冲突了。
  I2C通信具有几类信号
  开始信号S:当SCL处于高电平时,SDA从高电平拉低至低电平,代表数据传输的开始
  结束信号P:当SCL处于高电平时,SDA从低电平拉高至高电平,代表数据传输结束
  数据信号:数据信号每次都传输8位数据,每一位数据都在一个时钟周期内传递,当SCL处于高电平时,SDA数据线上的电平需要稳定,当SCL处于低电平的时候,SDA数据线上的电平才允许改变。
  应答信号ACK/NACK:应答信号是主机发送8bit数据,从机对主机发送低电平,表示已经接收数据。
2.jpg
  整个I2C通信过程理解成收发快递的过程,设备I2C地址理解成学校快递柜的地址,读写位代表寄出和签收快递,寄存器地址则是快递柜上的箱号,而数据便是需要寄出或者签收的快递。整个过程便是如同到学校的快递柜(从机 I2C 地址),对第几号柜箱(寄存器地址), 进行寄出或者签收快递(数据)的过程。
  IST8310简介
  IST8310 是一款由 ISentek 公司推出的 3 轴磁场传感器,尺寸为 3.03.01.0mm,支持快速 I2C 通信,可达 400kHz,14 位磁场数据,测量范围可达1600uT(x,y-axis)和 2500uT(z-axis), 最高 200Hz 输出频率。使用IST8310磁力计可以检测地磁场强度,用于计算磁场角度。
  下图为IST8310的引脚功能表
2.jpg
  此外我们可以整理等下我们会用到的GPIO引脚
2.jpg
  以及我们可以看一下从机地址的设置,在这个芯片之后从机地址的设置是通过CAD0和CAD1两个引脚设置的,这样可以实现设置四个不同的地址。
2.jpg
  下面是开发板的原理图,我们可以看到下面CAD0和CAD1引脚都浮空了,根据上图可以得知,这个IST8310的从机地址为0x0E与原理图中标注的一致。
2.jpg
  CubeMX配置
  首先我们开始看到我们的原理图上。
2.jpg
3.jpg
  根据上面两个图我们可以发现这个IST8310是挂载在I2C总线上面的。
  STM32上I2C3_SCL在PA8,I2C_SDA在PC9,DRDY数据准备引脚在PG3,复位RSTN引脚在PG6,接着在CubeMX进行相应的配置。
2.jpg
3.jpg
  之后我们可以看到我们在CubeMX中配置的东西实际上都在board文件之中,其中board.c文件里面为我们的时钟树配置,stm32f4xx_hal_msp.c里的就是我们上面设置的那些引脚配置
2.jpg
  这里就是刚刚I2C3配置的函数,为啥突然开始讲这个主要也是实习时发现大部分情况下是没有STM32可以用的,也就不能用CubeMX那么轻松的配置了,pintopin替换直接把国产芯片当作STM32使用总感觉会出问题,因此还是学习自己配置的方法。
  else if(hi2c-》Instance==I2C3)
  {
  /* USER CODE BEGIN I2C3_MspInit 0 */
  /* USER CODE END I2C3_MspInit 0 */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  /**I2C3 GPIO Configuration
  PC9 ------》 I2C3_SDA
  PA8 ------》 I2C3_SCL
  */
  GPIO_InitStruct.Pin = GPIO_PIN_9;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
  GPIO_InitStruct.Pin = GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF4_I2C3;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  /* Peripheral clock enable */
  __HAL_RCC_I2C3_CLK_ENABLE();
  /* USER CODE BEGIN I2C3_MspInit 1 */
  /* USER CODE END I2C3_MspInit 1 */
  }
  代码编写
  首先在RT-Thread Settings组件中打开I2C设备驱动程序
2.jpg
  menuconfig BSP_USING_I2C3
  bool “Enable I2C3 BUS (software simulation)”
  default n
  select RT_USING_I2C
  select RT_USING_I2C_BITOPS
  select RT_USING_PIN
  if BSP_USING_I2C3
  comment “Notice: PA8 --》 8; PC9 --》 41”
  config BSP_I2C3_SCL_PIN
  int “i2c1 scl pin number”
  default 8
  config BSP_I2C3_SDA_PIN
  int “I2C1 sda pin number”
  default 41
  endif
2.jpg
  配置完后我们来看一下几个重要的实现函数
  i2c写入寄存器的函数
  rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number)
  {
  rt_uint8_t buf[2];
  buf[0] = write_addr;
  buf[1] = data;
  rt_size_t result;
  result = rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, buf, 2);
  rt_thread_mdelay(10);
  if (result == 2)
  {
  rt_kprintf(“IST8310 write failed,ERR is:%drn”, result);
  return -RT_ERROR;
  }
  }
  关于读取,写入这里引用上面推荐视频里的一幅图
2.jpg
  这里我们函数的实现也是按照这个原理,rt_i2c_master_send首先将设备的地址IST8310_ADDR,加上读写位RT_I2C_WR发送
  之后我们定义的buf缓冲区中装着的就是我们要发送的寄存器地址write_addr,和要写入的数据data。
  需要注意的是rt_i2c_master_send返回的是发送的消息的个数,且不包含一开始发送的设备地址IST8310_ADDR的。
  下面的读取函数也是同理的,先发送想要读取的read_addr,然后利用rt_i2c_master_recv函数进行读取。
  rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf)
  {
  //通知要读哪个设备的哪个内存地址的内容,(告知是需要读read_addr)
  rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, &read_addr, 1);
  //读取到的内容存入buf
  rt_i2c_master_recv(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_RD, buf, len); //地址读数据
  }
  这样看完大家应该对于i2c的通信方式更加熟悉了。
  下面介绍的是读取磁力计值函数ist8310_read_mag
  void ist8310_read_mag(float mag[3])
  {
  uint8_t buf[6];
  int16_t temp_ist8310_data = 0;
  //read the “DATAXL” register (0x03)
  ist8310_iic_read(0x03, 6, buf);
  temp_ist8310_data = (int16_t) ((buf[1] 《《 8) | buf[0]);
  mag[0] = MAG_SEN * temp_ist8310_data;
  temp_ist8310_data = (int16_t) ((buf[3] 《《 8) | buf[2]);
  mag[1] = MAG_SEN * temp_ist8310_data;
  temp_ist8310_data = (int16_t) ((buf[5] 《《 8) | buf[4]);
  mag[2] = MAG_SEN * temp_ist8310_data;
  }
2.jpg
  我们这里对照表格就可以看到数据寄存器是从0x03开始到0x08结束的,我们使用ist8310_iic_read实际上虽然我们一开始只说明了读取0x03的值,但是根据消息长度的大小,它继续读取下面寄存器的值的。
  最后给大家详细一下初始化函数ist8310_init
  rt_uint8_t ist8310_init(const char*name)
  {
  rt_uint8_t temp[2] = { 0, 0 };
  ist8310_i2c_bus = (struct rt_i2c_bus_device *) rt_device_find(name);
  rt_uint8_t res[2] = { 0, 0 };
  rt_uint8_t writeNum = 0;
  if (ist8310_i2c_bus == RT_NULL)
  {
  rt_kprintf(“can‘t find %s device!n”, name);
  }
  else
  {
  ist8310_RST_L();
  rt_thread_mdelay(sleepTime);
  ist8310_RST_H();
  rt_thread_mdelay(sleepTime);
  ist8310_IIC_read_single_reg(IST8310_WHO_AM_I, 1, res);
  if (res[0] != IST8310_WHO_AM_I_VALUE)
  {
  initialized = RT_TRUE;
  return IST8310_NO_SENSOR;
  }
  for (writeNum = 0; writeNum 《 IST8310_WRITE_REG_NUM; writeNum++)
  //开启中断,并且设置低电平,平均采样两次,200Hz输出频率
  {
  ist8310_IIC_write_single_reg(ist8310_write_reg_data_error[writeNum][0],
  ist8310_write_reg_data_error[writeNum][1]);
  ist8310_delay_us(wait_time);
  ist8310_IIC_read_single_reg(ist8310_write_reg_data_error[writeNum][0], 1, res);
  ist8310_delay_us(wait_time);
  if (res[0] != ist8310_write_reg_data_error[writeNum][1])
  {
  return ist8310_write_reg_data_error[writeNum][2];
  }
  }
  initialized = RT_TRUE;
  return IST8310_NO_ERROR;
  }
  }
  第一个特殊点是读取WHO AM I寄存器,这里是为了确认读取的是ist8310传感器,避免ID冲突或者没有传感器导致后续的通信错误。这里WHO AM I里的值默认是10我们读取到10则说明没有问题。
2.jpg
  第二个特殊点就是for循环里面的那些操作,ist8310_write_reg_data_error这个二维数组里面的内容大家可以翻一下最后完整的ist8310.h代码
  这里做的事情实际上是对配置寄存器进行写入配置,对应的寄存器即意义如下。大家根据下面的表格就可以计算出相应要写入的值了。
2.jpg
3.jpg
4.jpg
5.jpg
  ist8310.c完整代码
  //
  // Created by Goldengrandpa on 2022/11/4.
  //
  #include “ist8310.h”
  rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number)
  {
  rt_uint8_t buf[2];
  buf[0] = write_addr;
  buf[1] = data;
  rt_size_t result;
  result = rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, buf, 2);
  rt_thread_mdelay(10);
  if (result == 2)
  {
  rt_kprintf(“IST8310 write failed,ERR is:%drn”, result);
  return -RT_ERROR;
  }
  }
  void ist8310_delay_us(uint16_t us)
  {
  uint32_t ticks = 0;
  uint32_t told = 0, tnow = 0, tcnt = 0;
  uint32_t reload = 0;
  reload = SysTick-》LOAD;
  ticks = us * 72;
  told = SysTick-》VAL;
  while (1)
  {
  tnow = SysTick-》VAL;
  if (tnow != told)
  {
  if (tnow 《 told)
  {
  tcnt += told - tnow;
  }
  else
  {
  tcnt += reload - tnow + told;
  }
  told = tnow;
  if (tcnt 》= ticks)
  {
  break;
  }
  }
  }
  }
  rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf)
  {
  //通知要读哪个设备的哪个内存地址的内容,(告知是需要读read_addr)
  rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_WR, &read_addr, 1);
  //读取到的内容存入buf
  rt_i2c_master_recv(ist8310_i2c_bus, IST8310_ADDR, RT_I2C_RD, buf, len); //地址读数据
  }
  rt_err_t ist8310_IIC_write_single_reg(rt_uint8_t reg, rt_uint8_t data)
  {
  rt_uint8_t buf[2];
  buf[0] = reg;
  buf[1] = data;
  if (rt_i2c_master_send(ist8310_i2c_bus, IST8310_ADDR, 0, buf, 2) == 2)
  {
  return RT_EOK;
  }
  else
  {
  return -RT_ERROR;
  }
  }
  uint8_t ist8310_IIC_read_single_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf)
  {
  struct rt_i2c_msg msgs[2];
  msgs[0].addr = IST8310_ADDR; /* 从机地址 */
  msgs[0].flags = RT_I2C_WR; /* 写标志 */
  msgs[0].buf = ® /* 从机寄存器地址 */
  msgs[0].len = 1; /* 发送数据字节数 */
  msgs[1].addr = IST8310_ADDR; /* 从机地址 */
  msgs[1].flags = RT_I2C_RD; /* 读标志 */
  msgs[1].buf = buf; /* 读取数据指针 */
  msgs[1].len = len; /* 读取数据字节数 */
  if (rt_i2c_transfer(ist8310_i2c_bus, msgs, 2) == 2)
  {
  return RT_EOK;
  }
  else
  {
  return -RT_ERROR;
  }
  }
  void ist8310_RST_H(void)
  {
  rt_pin_write(IST8310_RSTN_PIN_NUM, 1);
  }
  void ist8310_RST_L(void)
  {
  rt_pin_write(IST8310_RSTN_PIN_NUM, 0);
  }
  void ist8310_read_mag(float mag[3])
  {
  uint8_t buf[6];
  int16_t temp_ist8310_data = 0;
  //read the “DATAXL” register (0x03)
  ist8310_iic_read(0x03, 6, buf);
  temp_ist8310_data = (int16_t) ((buf[1] 《《 8) | buf[0]);
  mag[0] = MAG_SEN * temp_ist8310_data;
  temp_ist8310_data = (int16_t) ((buf[3] 《《 8) | buf[2]);
  mag[1] = MAG_SEN * temp_ist8310_data;
  temp_ist8310_data = (int16_t) ((buf[5] 《《 8) | buf[4]);
  mag[2] = MAG_SEN * temp_ist8310_data;
  }
  rt_uint8_t ist8310_init(const char*name)
  {
  rt_uint8_t temp[2] = { 0, 0 };
  ist8310_i2c_bus = (struct rt_i2c_bus_device *) rt_device_find(name);
  rt_uint8_t res[2] = { 0, 0 };
  rt_uint8_t writeNum = 0;
  if (ist8310_i2c_bus == RT_NULL)
  {
  rt_kprintf(“can’t find %s device!n”, name);
  }
  else
  {
  ist8310_RST_L();
  rt_thread_mdelay(sleepTime);
  ist8310_RST_H();
  rt_thread_mdelay(sleepTime);
  ist8310_IIC_read_single_reg(IST8310_WHO_AM_I, 1, res);
  if (res[0] != IST8310_WHO_AM_I_VALUE)
  {
  initialized = RT_TRUE;
  return IST8310_NO_SENSOR;
  }
  for (writeNum = 0; writeNum 《 IST8310_WRITE_REG_NUM; writeNum++)
  {
  ist8310_IIC_write_single_reg(ist8310_write_reg_data_error[writeNum][0],
  ist8310_write_reg_data_error[writeNum][1]);
  ist8310_delay_us(wait_time);
  ist8310_IIC_read_single_reg(ist8310_write_reg_data_error[writeNum][0], 1, res);
  ist8310_delay_us(wait_time);
  if (res[0] != ist8310_write_reg_data_error[writeNum][1])
  {
  return ist8310_write_reg_data_error[writeNum][2];
  }
  }
  initialized = RT_TRUE;
  return IST8310_NO_ERROR;
  }
  }
  static void i2c_ist8310_sample(int argc, char *argv[])
  {
  rt_uint8_t buf;
  rt_uint8_t result=0;
  float msg[3] = { 0, 0, 0 };
  char name[RT_NAME_MAX];
  if (argc == 2)
  {
  rt_strncpy(name, argv[1], RT_NAME_MAX);
  }
  else
  {
  rt_strncpy(name, IST8310_I2C_BUS_NAME, RT_NAME_MAX);
  }
  if (!initialized)
  {
  /* 传感器初始化 */
  result=ist8310_init(name);
  }
  if (initialized)
  {
  ist8310_read_mag(msg);
  rt_kprintf(“read ist8310 sensor x:%f y:%f z:%fn”, msg[0], msg[1], msg[2]);
  }
  else
  {
  rt_kprintf(“%dn”,result);
  rt_kprintf(“initialize sensor failed!n”);
  }
  }
  /* 导出到 msh 命令列表中 */
  MSH_CMD_EXPORT(i2c_ist8310_sample, ist8310_sample);
  ist8310.h
  //
  // Created by Goldengrandpa on 2022/11/4.
  //
  #ifndef RTTHREAD_IST8310_H
  #define RTTHREAD_IST8310_H
  #include 《rtthread.h》
  #include 《rtdevice.h》
  #include “board.h”
  #define IST8310_I2C_BUS_NAME “i2c3” /* 传感器连接的I2C总线设备名称 */
  #define IST8310_ADDR 0X0E /* 从机地址 */
  #define IST8310_CALIBRATION_CMD 0xE1 /* 校准命令 */
  #define IST8310_NORMAL_CMD 0xA8 /* 一般命令 */
  #define IST8310_GET_DATA 0xAC /* 获取数据命令 */
  #define IST8310_WRITE_REG_NUM 4
  #define IST8310_WHO_AM_I 0x00 //ist8310 “who am I ”
  #define IST8310_WHO_AM_I_VALUE 0x10 //device ID
  #define IST8310_DATA_READY_BIT 2
  #define IST8310_NO_ERROR 0x00
  #define IST8310_NO_SENSOR 0x40
  static const uint8_t wait_time = 150;
  static const uint8_t sleepTime = 50;
  #define IST8310_RSTN_PIN_NUM GET_PIN(G,6)
  #define MAG_SEN 0.3f //raw int16 data change to uT unit. 原始整型数据变成 单位ut
  typedef struct ist8310_real_data_t
  {
  uint8_t status;
  float mag[3];
  } ist8310_real_data_t;
  //the first column:the registers of IST8310. 第一列:IST8310的寄存器
  //the second column: the value to be writed to the registers.第二列:需要写入的寄存器值
  //the third column: return error value.第三列:返回的错误码
  static const rt_uint8_t ist8310_write_reg_data_error[IST8310_WRITE_REG_NUM][3] ={
  {0x0B, 0x08, 0x01}, //enalbe interrupt and low pin polarity.开启中断,并且设置低电平
  {0x41, 0x09, 0x02}, //average 2 times.平均采样两次
  {0x42, 0xC0, 0x03}, //must be 0xC0. 必须是0xC0
  {0x0A, 0x0B, 0x04}}; //200Hz output rate.200Hz输出频率
  static struct rt_i2c_bus_device *ist8310_i2c_bus = RT_NULL; /* I2C总线设备句柄 */
  static rt_bool_t initialized = RT_FALSE; /* 传感器初始化状态 */
  rt_err_t ist8310_iic_write(rt_uint8_t write_addr, rt_uint8_t data, rt_uint32_t number);
  rt_err_t ist8310_iic_read(rt_uint8_t read_addr, rt_uint32_t len, rt_uint8_t *buf);
  void ist8310_read_mag(float mag[3]);
  static void read_mag(struct rt_i2c_bus_device *bus,float *cur_mag);/*读取磁场*/
  void ist8310_RST_H(void);/*设置RSTN引脚为1*/
  void ist8310_RST_L(void);/*设置RSTN引脚为0*/
  void ist8310_delay_us(rt_uint16_t us);
  rt_err_t ist8310_IIC_write_single_reg(rt_uint8_t reg, rt_uint8_t data);
  rt_uint8_t ist8310_IIC_read_single_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf);
  static void i2c_ist8310_sample(int argc, char *argv[]);
  #endif //RTTHREAD_IST8310_H
  运行结果:
2.jpg



原作者:goldengrandpa

更多回帖

发帖
×
20
完善资料,
赚取积分