前言
在公司工作期间,笔者参与了一个项目,使用SC7A20传感器检测产品的运动状态。在搜索相关驱动时,发现大部分驱动是用C++编写的,或者需要付费使用。秉承开源精神,本文将分享关于传感器驱动及物品状态检测算法的思路和实现方法。
本文采用ESP32-S3作为主控,开发环境为ESP-IDF,因此文中所提程序均基于ESP-IDF编写的C语言代码。在项目中,我们使用了i2c_bus头文件进行I2C通信。为了方便接口的替换,笔者对I2C的初始化、读写操作进行了封装,提供了自定义函数接口。各位在适配时,只需修改这三个函数的内部实现即可。
下面这个是SC7A20的数据手册:SC7A20TR_SILAN(士兰微)_SC7A20TR中文资料_PDF手册_价格-立创商城
寄存器介绍
从数据手册寄存器可以看到:
加速度是16位数据,并且有3轴,所以共有六个寄存器来输出加速度的信息。寄存器地址是从0x28到0x2D。因此读取的时候可以使用地址递增的方式来依次读取各个轴加速度的值(当然笔者没用这种方法, 是老老实实地将每个寄存器地址打出来了,毕竟工程代码只有你一个人看得懂是没有用的。)
当然,一个传感器如果想要使用它,那就必须要进行初始化。而初始化肯定就是进行寄存器的写操作了。通过翻阅数据手册可以看出,控制寄存器有CTRL_REG1-6。以下是各个寄存器的主要功能
1. CTRL_REG1 (控制寄存器1)
- 作用:用于控制设备的基本操作模式。
- 功能:
- 配置加速度计的工作模式,如正常模式、低功耗模式等。
- 设置加速度计的输出数据率(ODR),决定数据更新频率。
- 配置传感器的自检功能。
- 启用/禁用传感器的计算功能。
2. CTRL_REG2 (控制寄存器2)
- 作用:用于配置滤波器和自检功能。
- 功能:
- 配置高通滤波器的截止频率。
- 启用/禁用传感器的自检模式。
- 配置输出数据的格式(如高/低字节顺序)。
3. CTRL_REG3 (控制寄存器3)
- 作用:用于控制中断配置和其他特殊功能。
- 功能:
- 配置中断源,如运动检测、中断输出使能等。
- 配置中断的行为,如中断挂起、边缘触发等。
- 设置输出数据的中断配置。
4. CTRL_REG4 (控制寄存器4)
- 作用:用于设置加速度计的量程和精度。
- 功能:
- 配置加速度计的量程(例如 ±2g、±4g、±8g 等)。
- 配置输出数据的分辨率和精度。
- 调整输出数据的标度因子。
5. CTRL_REG5 (控制寄存器5)
- 作用:用于控制低功耗模式和优化传感器的电源管理。
- 功能:
- 设置传感器的低功耗模式。
- 配置高功耗和低功耗模式之间的切换。
- 进一步优化传感器的功耗。
6.CTRL_REG6 (控制寄存器6)
- 作用:用于配置一些特殊的功能,例如数据锁存。
- 功能:
- 配置加速度计的数据锁存模式。
- 启用/禁用特定的功能模块,如传感器自检。
- 配置电源管理功能以提高功耗效率
因为工作需要,笔者只研究了 CTRL_REG1 和CTRL_REG4 。因为笔者发现只需要对这两个寄存器进行初始化,即可完成对SC7A20加速度值的读取
CTRL_REG1:
从寄存器的表中不难看出,高四位要是不开低功耗模式,除了输入0000和1000其他应该可以随便输入(这个笔者没有进行研究,当时随便挑了个0101就输入了)。低四位要是不开启低功耗模式,输入0111就是使能x、y、z三轴加速度的输出。综上所述,笔者在这个寄存器写入的是0101 0111,也就是0x57。
CTRL_REG4:
这个寄存器是为了设置量程用的,其实改变B4和B5位即可,其余位输入0。所以0x00就是+-2G的量程,0x10就是+-4G的量程,以此类推。(个人在开发中有个小建议,因为加速度读取出来的原始数据要进行换算成真实的加速度,换算的时候需要用到这个加速度的量程,所以建议这个使用宏定义来进行声明。)
代码展示
SC7A20.c
#include "peripheral_sc7a20.h"
// I2C bus handle
static i2c_bus_handle_t i2c_handle;
/**
* @brief Write data to a specified register of SC7A20
*
* @param reg_addr Register address
* @param data Data to write
* @return esp_err_t Operation result
*/
static esp_err_t SC7A20_write_reg(uint8_t reg_addr, uint8_t data)
{
uint8_t addr = reg_addr;
return i2c_bus_write_bytes(i2c_handle, SC7A20_I2C_ADDR << 1, &addr, 1, &data, sizeof(data));
}
/**
* @brief Read data from a specified register of SC7A20
*
* @param reg_addr Register address
* @param data Pointer to store the read data
* @return esp_err_t Operation result
*/
static esp_err_t SC7A20_read_reg(uint8_t reg_addr, uint8_t *data)
{
uint8_t addr = reg_addr;
esp_err_t ret = i2c_bus_read_bytes(i2c_handle, SC7A20_I2C_ADDR << 1, &addr, 1, data, sizeof(*data));
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read register 0x%02X: %d", reg_addr, ret);
}
return ret;
}
/**
* @brief Initialize I2C bus for SC7A20
*
*/
void SC7A20_i2c_init() {
// Configure I2C bus parameters
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
.scl_io_num = GPIO_NUM_42,
.sda_io_num = GPIO_NUM_45,
};
// Create I2C bus
i2c_handle = i2c_bus_create(I2C_NUM_0, &conf);
if (i2c_handle == NULL) {
ESP_LOGE("SC7A20", "Failed to create I2C bus");
return 0;
}
}
/**
* @brief Initialize the SC7A20 sensor
*
* @return int Returns 1 on success, 0 on failure
*/
int SC7A20_begin(uint8_t scale) {
SC7A20_i2c_init();
// Read WHO_AM_I register to verify device connection
uint8_t who_am_i;
esp_err_t ret = SC7A20_read_reg(SC7A20_REG_WHO_AM_I, &who_am_i);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read WHO_AM_I register");
return 0;
}
if (who_am_i == 0x11) {
// If WHO_AM_I value is correct, initialize sensor configuration
SC7A20_initSensor(scale);
return 1;
} else {
ESP_LOGE("SC7A20", "WHO_AM_I value is incorrect: 0x%02X", who_am_i);
return 0;
}
}
/**
* @brief Initialize sensor configuration
*/
void SC7A20_initSensor(uint8_t scale) {
SC7A20_setDataRate(0x57); // Set data rate
SC7A20_setFullScale(scale); // Set full-scale range
}
/**
* @brief Read acceleration data from X, Y, Z axes
*
* @param x Pointer to store X-axis acceleration value
* @param y Pointer to store Y-axis acceleration value
* @param z Pointer to store Z-axis acceleration value
*/
void SC7A20_readAcceleration(int16_t *x, int16_t *y, int16_t *z, uint8_t scale) {
uint8_t xl, xh, yl, yh, zl, zh;
esp_err_t ret;
// Read X-axis low byte and high byte
ret = SC7A20_read_reg(SC7A20_REG_OUT_X_L, &xl);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read X_L register");
return;
}
ret = SC7A20_read_reg(SC7A20_REG_OUT_X_H, &xh);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read X_H register");
return;
}
*x = ((int16_t)xh << 8) | xl;
// Read Y-axis low byte and high byte
ret = SC7A20_read_reg(SC7A20_REG_OUT_Y_L, &yl);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read Y_L register");
return;
}
ret = SC7A20_read_reg(SC7A20_REG_OUT_Y_H, &yh);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read Y_H register");
return;
}
*y = ((int16_t)yh << 8) | yl;
// Read Z-axis low byte and high byte
ret = SC7A20_read_reg(SC7A20_REG_OUT_Z_L, &zl);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read Z_L register");
return;
}
ret = SC7A20_read_reg(SC7A20_REG_OUT_Z_H, &zh);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read Z_H register");
return;
}
*z = ((int16_t)zh << 8) | zl;
// Convert raw values to real acceleration based on scale
float scale_factor = 0.0f;
// Set scale factor based on the selected scale
switch (scale) {
case FULL_SCALE_2G:
scale_factor = 1.0f / 1000.0f; // 2g -> 1000 LSB/g
break;
case FULL_SCALE_4G:
scale_factor = 1.0f / 2000.0f; // 4g -> 2000 LSB/g
break;
case FULL_SCALE_8G:
scale_factor = 1.0f / 4000.0f; // 8g -> 4000 LSB/g
break;
case FULL_SCALE_16G:
scale_factor = 1.0f / 8000.0f; // 16g -> 8000 LSB/g
break;
default:
ESP_LOGE("SC7A20", "Invalid scale value");
return;
}
// Convert the raw data to acceleration in g
*x = *x * scale_factor;
*y = *y * scale_factor;
*z = *z * scale_factor;
}
/**
* @brief Set the data rate of the sensor
*
* @param rate Data rate value
*/
void SC7A20_setDataRate(uint8_t rate) {
uint8_t reg;
esp_err_t ret = SC7A20_read_reg(SC7A20_REG_CTRL_REG1, ®);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read CTRL_REG1 register");
return;
}
reg &= 0x0F; // Clear old data rate setting
reg |= (rate & 0xF0); // Set new data rate
SC7A20_write_reg(SC7A20_REG_CTRL_REG1, reg);
}
/**
* @brief Set the full-scale range of the sensor
*
* @param scale Full-scale range value
*/
void SC7A20_setFullScale(uint8_t scale) {
uint8_t reg;
esp_err_t ret = SC7A20_read_reg(SC7A20_REG_CTRL_REG4, ®);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read CTRL_REG4 register");
return;
}
reg &= 0xCF; // Clear old full-scale setting
reg |= (scale & 0x30); // Set new full-scale
SC7A20_write_reg(SC7A20_REG_CTRL_REG4, reg);
}
/**
* @brief Enable or disable the high-pass filter
*
* @param enable Whether to enable the high-pass filter
*/
void SC7A20_enableHighPassFilter(bool enable) {
uint8_t reg;
esp_err_t ret = SC7A20_read_reg(SC7A20_REG_CTRL_REG2, ®);
if (ret != ESP_OK) {
ESP_LOGE("SC7A20", "Failed to read CTRL_REG2 register");
return;
}
if (enable) {
reg |= 0x10; // Enable high-pass filter
} else {
reg &= ~0x10; // Disable high-pass filter
}
SC7A20_write_reg(SC7A20_REG_CTRL_REG2, reg);
}
/**
* @brief Set the interrupt threshold
*
* @param threshold Interrupt threshold value
*/
void SC7A20_setInterruptThreshold(uint8_t threshold) {
SC7A20_write_reg(SC7A20_REG_INT1_THS, threshold);
}
/**
* @brief Set the interrupt duration
*
* @param duration Interrupt duration value
*/
void SC7A20_setInterruptDuration(uint8_t duration) {
SC7A20_write_reg(SC7A20_REG_INT1_DURATION, duration);
}
/**
* @brief Configure FIFO functionality
*
* @param mode FIFO mode
* @param threshold FIFO threshold
*/
void SC7A20_configureFIFO(uint8_t mode, uint8_t threshold) {
SC7A20_write_reg(SC7A20_REG_FIFO_CTRL, (mode << 6) | (threshold & 0x1F));
}
SC7A20.h
#ifndef SC7A20_H
#define SC7A20_H
#include <string.h>
#include <stdio.h>
#include "i2c_bus.h"
#include "esp_log.h"
// Define I2C port and device address
#define SC7A20_I2C_PORT I2C_NUM_0 ///< I2C port number used for communication with the sensor
#define SC7A20_I2C_ADDR 0x19 ///< I2C address of the SC7A20 sensor
// Data rate settings
#define FULL_SCALE_2G 0x00
#define FULL_SCALE_4G 0x10
#define FULL_SCALE_8G 0x20
#define FULL_SCALE_16G 0x30
// Register addresses
#define SC7A20_REG_WHO_AM_I 0x0F ///< WHO_AM_I register address, used to verify device identity
#define SC7A20_REG_CTRL_REG1 0x20 ///< Control register 1 address, configures data rate and power mode
#define SC7A20_REG_CTRL_REG2 0x21 ///< Control register 2 address, configures high-pass filter settings
#define SC7A20_REG_CTRL_REG3 0x22 ///< Control register 3 address, configures interrupt settings
#define SC7A20_REG_CTRL_REG4 0x23 ///< Control register 4 address, configures full-scale range and self-test
#define SC7A20_REG_CTRL_REG5 0x24 ///< Control register 5 address, configures FIFO settings
#define SC7A20_REG_OUT_X_L 0x28 ///< Output register address for X-axis low byte
#define SC7A20_REG_OUT_X_H 0x29 ///< Output register address for X-axis hight byte
#define SC7A20_REG_OUT_Y_L 0x2A ///< Output register address for Y-axis low byte
#define SC7A20_REG_OUT_Y_H 0x2B ///< Output register address for Y-axis hight byte
#define SC7A20_REG_OUT_Z_L 0x2C ///< Output register address for Z-axis low byte
#define SC7A20_REG_OUT_Z_H 0x2D ///< Output register address for Z-axis hight byte
#define SC7A20_REG_INT1_CFG 0x30 ///< Interrupt configuration register address
#define SC7A20_REG_INT1_SRC 0x31 ///< Interrupt source register address
#define SC7A20_REG_INT1_THS 0x32 ///< Interrupt threshold register address
#define SC7A20_REG_INT1_DURATION 0x33 ///< Interrupt duration register address
#define SC7A20_REG_FIFO_CTRL 0x2E ///< FIFO control register address
#define SC7A20_REG_FIFO_SRC 0x2F ///< FIFO source register address
/**
* @brief Structure representing the SC7A20 sensor
*/
typedef struct {
uint8_t address; ///< I2C address of the sensor
} SC7A20;
/**
* @brief Initialize the I2C bus
*
* @return esp_err_t Operation result
*/
void SC7A20_i2c_init(void);
/**
* @brief Write data to a specified register of SC7A20
*
* @param reg_addr Register address
* @param data Data to write
* @return esp_err_t Operation result
*/
int SC7A20_begin(uint8_t scale);
/**
* @brief Initialize sensor configuration
*/
void SC7A20_initSensor(uint8_t scale);
/**
* @brief Read acceleration data from X, Y, Z axes
*
* @param x Pointer to store X-axis acceleration value
* @param y Pointer to store Y-axis acceleration value
* @param z Pointer to store Z-axis acceleration value
*/
void SC7A20_readAcceleration(int16_t *x, int16_t *y, int16_t *z, uint8_t scale);
/**
* @brief Set the data rate of the sensor
*
* @param rate Data rate value
*/
void SC7A20_setDataRate(uint8_t rate);
/**
* @brief Set the full-scale range of the sensor
*
* @param scale Full-scale range value
*/
void SC7A20_setFullScale(uint8_t scale);
/**
* @brief Enable or disable the high-pass filter
*
* @param enable Whether to enable the high-pass filter
*/
void SC7A20_enableHighPassFilter(bool enable);
/**
* @brief Set the interrupt threshold
*
* @param threshold Interrupt threshold value
*/
void SC7A20_setInterruptThreshold(uint8_t threshold);
/**
* @brief Set the interrupt duration
*
* @param duration Interrupt duration value
*/
void SC7A20_setInterruptDuration(uint8_t duration);
/**
* @brief Configure FIFO functionality
*
* @param mode FIFO mode
* @param threshold FIFO threshold
*/
void SC7A20_configureFIFO(uint8_t mode, uint8_t threshold);
#endif // SC7A20_H
笔者在开发中所用到的函数实际上只有SC7A20初始化和读取加速度数据的函数,其他的函数(如使能中断之类的)笔者是参照其他开源大佬写的,实际上并未使用,也不敢保证效果(不过应该是可行的)。如果有小伙伴使用了欢迎对笔者进行指点,也欢迎大家讨论使用中遇到的情况。