图32.3.1.1 QMA6100P实验程序流程图
32.3.2 QMA6100P函数解析
这一章节除了涉及到GPIO、IIC的API函数,便没有再涉及到其他API函数。因此,有关GPIO和IIC的API函数介绍,请读者回顾此前的第十章与第十九章的内容。接下来,笔者将直接介绍QMA6100P的驱动代码。
32.3.3 QMA6100P驱动解析
在IDF版22_qma6100p例程中,作者在22_qma6100p\components\BSP路径下新增了一个QMA6100P文件夹,分别用于存放qma6100p.c、qma6100p.h这两个文件。其中,qma6100p.h文件负责声明QMA6100P相关的函数和变量,而qma6100p.c文件则实现了QMA6100P的驱动代码。下面,我们将详细解析这两个文件的实现内容。
1,qma6100p.h文件
该文件下包含了对QMA6100P的命令配置以及寄存器地址的相关定义。
#define QMA6100P_ADDR 0x12 /* QMA6100P地址 */
/* QMA6100P命令 */
/* 获取ID,默认值为0x9x */
#define QMA6100P_REG_CHIP_ID 0x00
/* 数据寄存器,三轴数据,默认值为0x00 */
#define QMA6100P_REG_XOUTL 0x01
#define QMA6100P_REG_XOUTH 0x02
#define QMA6100P_REG_YOUTL 0x03
#define QMA6100P_REG_YOUTH 0x04
#define QMA6100P_REG_ZOUTL 0x05
#define QMA6100P_REG_ZOUTH 0x06
/* 带宽寄存器 */
#define QMA6100P_REG_BW_ODR 0x10
/* 电源管理寄存器 */
#define QMA6100P_REG_POWER_MANAGE 0x11
/* 加速度范围,设置加速度计的满刻度 */
#define QMA6100P_REG_RANGE 0x0f
/* 软件复位 */
#define QMA6100P_REG_RESET 0x36
#define QMA6100P_REG_ACC_VAL(lsb, msb) ((int16_t)(((uint16_t)msb << 8) |
((uint16_t)lsb & 0xFC)) >> 2)
typedef struct {
uint8_t data[2];
float acc_x;
float acc_y;
float acc_z;
float acc_g;
float pitch; /* 围绕X轴旋转,也叫做俯仰角 */
float roll; /* 围绕Z轴旋转,也叫翻滚角 */
}qma6100p_rawdata_t;
/* 设置量程寄存器 */
typedef enum
{
QMA6100P_BW_100 = 0,
QMA6100P_BW_200 = 1,
QMA6100P_BW_400 = 2,
QMA6100P_BW_800 = 3,
QMA6100P_BW_1600 = 4,
QMA6100P_BW_50 = 5,
QMA6100P_BW_25 = 6,
QMA6100P_BW_12_5 = 7,
QMA6100P_BW_OTHER = 8
}qma6100p_bw;
/* 设置加速度寄存器 */
typedef enum
{
QMA6100P_RANGE_2G = 0x01,
QMA6100P_RANGE_4G = 0x02,
QMA6100P_RANGE_8G = 0x04,
QMA6100P_RANGE_16G = 0x08,
QMA6100P_RANGE_32G = 0x0f
}qma6100p_range;
/* 设置复位寄存器 */
typedef enum
{
QMA6100P_RESET = 0xB6,
QMA6100P_RESET_END = 0x00,
}qma6100p_reset;
/* 设置中断 */
typedef enum
{
QMA6100P_MAP_INT1,
QMA6100P_MAP_INT2,
QMA6100P_MAP_INT_NONE
}qma6100p_int_map;
/* 设置管理寄存器 */
typedef enum
{
QMA6100P_ACTIVE_DIGITAL = 0x84,
QMA6100P_STANDBY = 0x00,
}qma6100p_power;
typedef enum
{
QMA6100P_MCLK_102_4K = 0x03,
QMA6100P_MCLK_51_2K = 0x04,
QMA6100P_MCLK_25_6K = 0x05,
QMA6100P_MCLK_12_8K = 0x06,
QMA6100P_MCLK_6_4K = 0x07,
QMA6100P_MCLK_RESERVED = 0xff
}qma6100p_mclk;
typedef enum
{
QMA6100P_SENSITITY_2G = 244,
QMA6100P_SENSITITY_4G = 488,
QMA6100P_SENSITITY_8G = 977,
QMA6100P_SENSITITY_16G = 1950,
QMA6100P_SENSITITY_32G = 3910
}qma6100p_sensitity;
2,qma6100p.c文件
/**
* @retval 无
*/
void qma6100p_init(i2c_obj_t self)
{
if (self.init_flag == ESP_FAIL)
{
iic_init(I2C_NUM_0); /* 初始化IIC */
}
qma6100p_i2c_master = self;
while (qma6100p_config()) /* 检测不到qma6100p */
{
ESP_LOGE("qma6100p", "qma6100p init fail!!!");
vTaskDelay(500);
}
}
/**
* @brief 初始化qma6100p
* @param 无
* @retval 0, 成功;
1, 失败;
*/
uint8_t qma6100p_config(void)
{
static uint8_t id_data[2];
/* 读取设备ID,正常是0x90 */
qma6100p_register_read(QMA6100P_REG_CHIP_ID, id_data, 1);
/* qma6100p的初始化序列,请看手册“6.3 Initial sequence”章节 */
qma6100p_register_write_byte(QMA6100P_REG_RESET, QMA6100P_RESET);
vTaskDelay(5);
qma6100p_register_write_byte(QMA6100P_REG_RESET, QMA6100P_RESET_END);
vTaskDelay(10);
/* 读取设备ID,正常是0x90 */
qma6100p_register_read(QMA6100P_REG_CHIP_ID, id_data, 1);
qma6100p_register_write_byte(0x11, 0x80);
qma6100p_register_write_byte(0x11, 0x84);
qma6100p_register_write_byte(0x4a, 0x20);
qma6100p_register_write_byte(0x56, 0x01);
qma6100p_register_write_byte(0x5f, 0x80);
vTaskDelay(1);
qma6100p_register_write_byte(0x5f, 0x00);
vTaskDelay(10);
qma6100p_register_write_byte(QMA6100P_REG_RANGE, QMA6100P_RANGE_8G);
qma6100p_register_write_byte(QMA6100P_REG_BW_ODR, QMA6100P_BW_100);
qma6100p_register_write_byte(QMA6100P_REG_POWER_MANAGE,
QMA6100P_MCLK_51_2K | 0x80);
qma6100p_register_write_byte(0x21, 0x03);/* default 0x1c, step latch mode */
qma6100p_step_int_config(QMA6100P_MAP_INT1, 1);
if (id_data[0] == 0x90)
{
ESP_LOGE("qma6100p", "qma6100p success!!!");
return 0; /* qma6100p正常 */
}
else
{
ESP_LOGE("qma6100p", "qma6100p fail!!!");
return 1; /* qma6100p失败 */
}
}
在qma6100_init()函数中,通过判断IIC初始化标志位,确认IIC是否已经初始化,如果没有则进行IIC初始化,已经初始化了则跳过。然后把IIC_SDA引脚和IIC_SCL引脚作为I2C_NUM_0的数据线和时钟线使用。然后调用了qma6100p_config函数,用于初始化和配置QMA6100P传感器模块。在qma6100p_config函数中,我们首先读取0x00寄存器来获取设备ID。然后,我们复位该设备并执行初始化序列(请参考规格书的6.3小节)。接下来,我们配置量程刻度、带宽、中断等参数。最后,我们检查读取的ID是否为0x90。如果是,则设备通信成功;否则,通信失败。
接下来我们来讲解一下对QMA6100P的IIC写时序函数,我们编写QMA6100P的IIC写时序函数,如下所示:
/**
* @brief 向qma6100p寄存器写数据
* @param reg_addr : 要写入的寄存器地址
* @param data : 要写入的数据
* @retval 错误值 :0成功,其他值:错误
*/
static esp_err_t qma6100p_register_write_byte(uint8_t reg, uint8_t data)
{
uint8_t memaddr_buf[1];
memaddr_buf[0] = reg;
i2c_buf_t bufs[2] = {
{.len = 1, .buf = memaddr_buf},
{.len = 1, .buf = &data},
};
i2c_transfer(&qma6100p_i2c_master, QMA6100P_ADDR, 2, bufs,I2C_FLAG_STOP);
return ESP_OK;
}
在上述源代码中,作者根据传入的IIC控制块,调用了IIC收发函数来发送QMA6100P的命令和数据。发送完成后,函数返回了ESP_OK状态。
接下来我们来讲解一下对QMA6100P的IIC读时序函数,我们编写QMA6100P的IIC读时序函数,如下所示:
/**
* @brief 读取qma6100p寄存器的数据
* @param reg_addr : 要读取的寄存器地址
* @param data : 读取的数据
* @param len : 数据大小
* @retval 错误值 :0成功,其他值:错误
*/
esp_err_t qma6100p_register_read(const uint8_t reg, uint8_t *data, const size_t len)
{
uint8_t memaddr_buf[1];
memaddr_buf[0] = reg;
i2c_buf_t bufs[2] = {
{.len = 1, .buf = memaddr_buf},
{.len = len, .buf = data},
};
i2c_transfer(&qma6100p_i2c_master,
QMA6100P_ADDR,
2,
bufs,
I2C_FLAG_WRITE | I2C_FLAG_READ | I2C_FLAG_STOP);
return ESP_OK;
}
同样地,QMA6100P的读时序也是利用IIC收发函数来实现的。写时序和读时序的唯一区别在于最后的flag标志位不同,从而导致发送流程有所不同。
下面是根据XYZ原始数据,使用特定的算法来计算pitch俯仰角和roll翻滚角,如下所示:
/**
* @brief 从QMA6100P寄存器中读取原始x,y,z轴数据
* @param data : 3轴数据存储数组
* @retval 无
*/
void qma6100p_read_raw_xyz(int16_t data[3])
{
uint8_t databuf[6] = {0};
int16_t raw_data[3];
qma6100p_read_reg(QMA6100P_XOUTL, databuf, 6);
raw_data[0] = (int16_t)(((databuf[1] << 8)) | (databuf[0]));
raw_data[1] = (int16_t)(((databuf[3] << 8)) | (databuf[2]));
raw_data[2] = (int16_t)(((databuf[5] << 8)) | (databuf[4]));
data[0] = raw_data[0] >> 2;
data[1] = raw_data[1] >> 2;
data[2] = raw_data[2] >> 2;
}
/**
* @brief 计算得到加速度计的x,y,z轴数据
* @param accdata : 3轴数据存储数组
* @retval 无
*/
void qma6100p_read_acc_xyz(float accdata[3])
{
int16_t rawdata[3];
qma6100p_read_raw_xyz(rawdata);
accdata[0] = (float)(rawdata[0] * M_G) / 1024;
accdata[1] = (float)(rawdata[1] * M_G) / 1024;
accdata[2] = (float)(rawdata[2] * M_G) / 1024;
}
上述源码中,作者先读取三轴的XYZ原始数据,然后经过特定的算法计算出pitch俯仰角和roll翻滚角。
32.3.4 CMakeLists.txt文件
打开本实验BSP下的CMakeLists.txt文件,其内容如下所示:
set(src_dirs
IIC
KEY
LCD
LED
QMA6100P
SPI
XL9555)
set(include_dirs
IIC
KEY
LCD
LED
QMA6100P
SPI
XL9555)
set(requires
driver
esp_adc)
idf_component_register(SRC_DIRS ${src_dirs}
INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
上述的红色QMA6100P驱动以及esp_adc依赖库需要由开发者自行添加,以确保QMA6100P驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了QMA6100P驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。
32.3.5 实验应用代码
打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。
i2c_obj_t i2c0_master;
/**
* @brief 显示原始数据
* @param x, y : 坐标
* @param title: 标题
* @param val : 值
* @retval 无
*/
void user_show_mag(uint16_t x, uint16_t y, char *title, float val)
{
char buf[20];
sprintf(buf,"%s%3.1f", title, val); /* 格式化输出 */
/* 清除上次数据(最多显示20个字符,20*8=160) */
lcd_fill(x + 30, y + 16, x + 160, y + 16, WHITE);
lcd_show_string(x, y, 160, 16, 16, buf, BLUE); /* 显示字符串 */
}
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
uint8_t t;
qma6100p_rawdata_t xyz_rawdata;
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES
|| ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
spi2_init(); /* 初始化SPI2 */
xl9555_init(i2c0_master); /* 初始化XL9555 */
lcd_init(); /* 初始化LCD */
qma6100p_init(i2c0_master); /* 初始化三轴加速度计 */
lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);
lcd_show_string(30, 70, 200, 16, 16, "QMA6100P TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, " ACC_X :", RED);
lcd_show_string(30, 130, 200, 16, 16, " ACC_Y :", RED);
lcd_show_string(30, 150, 200, 16, 16, " ACC_Z :", RED);
lcd_show_string(30, 170, 200, 16, 16, " Pitch :", RED);
lcd_show_string(30, 190, 200, 16, 16, " Roll :", RED);
while (1)
{
vTaskDelay(10);
t++;
if (t == 20) /* 0.2秒左右更新一次三轴原始值 */
{
qma6100p_read_rawdata(&xyz_rawdata);
user_show_mag(30, 110, "ACC_X :", xyz_rawdata.acc_x);
user_show_mag(30, 130, "ACC_Y :", xyz_rawdata.acc_y);
user_show_mag(30, 150, "ACC_Z :", xyz_rawdata.acc_z);
user_show_mag(30, 170, "Pitch :", xyz_rawdata.pitch);
user_show_mag(30, 190, "Roll :", xyz_rawdata.roll);
t = 0;
LED_TOGGLE();
}
}
}
从上述源码可知,我们首先初始化各个外设,如IIC、SPI、XL9555、QMA6100P和LCD等驱动,然后调用qma6100.qma6100p_read函数测量数据,最后调用qma6100p_acc_x等函数获取XYZG、pitch俯仰角和roll翻滚角数据,并在SPILCD上显示。。LED灯每隔200毫秒状态翻转,实现闪烁效果。
32.4 下载验证
程序下载到开发板后,LCD不断刷新三轴的原始数据、pitch俯仰角和roll翻滚角。当用户转动或翻转开发板时,pitch俯仰角和roll翻滚角会随之变化,如下图所示: