​ 简介:这个文档下包含了航博远达六轴陀螺仪项目和安卓TV鼠标项目的相关算法和问题

相关知识

寄存器是什么 ?

寄存器是CPU用来暂存指令、数据和地址的电脑存储器。它位于CPU内部,存储速度想对硬盘、内存和缓存来说几乎是最快的。

​ 其速度快的原因有三:1.由CPU中的逻辑单元直接存取。2.位于CPU中,与逻辑单元的走线短。3.寄存器自身的结构优势

屏幕坐标系

​ 屏幕坐标系是用于描述屏幕上像素位置的二维坐标系统,是计算机图形学、用户界面设计(GUI)和交互式系统中的基础概念。以下是其核心定义、特点及应用的详细介绍:

一、基本定义与核心要素

  1. 原点(Origin)
    • 默认位置:绝大多数屏幕坐标系的原点位于屏幕的 左上角(即屏幕的最左上方顶点)。
    • 数学表示:原点坐标为 (0, 0),表示屏幕的起始位置。
  2. 坐标轴方向
    • X 轴:水平方向,从原点向右延伸,数值随水平位置增加而增大。
    • Y 轴:垂直方向,从原点向下延伸,数值随垂直位置增加而增大(与数学笛卡尔坐标系的 Y 轴方向相反,后者 Y 轴向上)。
  3. 单位
    • 像素(Pixel) 为基本单位,坐标值为非负整数(如 (100, 50) 表示水平方向第 100 像素、垂直方向第 50 像素的位置)。

二、坐标范围与分辨率

  • 范围:若屏幕分辨率为宽度×高度(如1920×1080),则:

    • X 轴范围:0 ≤ X < 宽度(如 0~1919)
    • Y 轴范围:0 ≤ Y < 高度(如 0~1079)
  • 坐标表示:屏幕上任意一点的位置由 (X, Y) 唯一确定,例如屏幕中心坐标为 (宽度/2, 高度/2)

六轴陀螺仪算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#define DT 0.01f        // 采样时间
#define PI 3.14f //Π

/**********************************************************
低通滤波器
作用:减少高频噪声的影响,使数据更平滑。
**********************************************************/
// 添加低通滤波结构体
typedef struct {
float alpha; // 滤波系数 (0.1-0.3)
int16_t last_x;
int16_t last_y;
} LowPassFilter;

// 初始化滤波器
LowPassFilter gyro_filter = {.alpha = 0.1, .last_x = 0, .last_y = 0};

// 低通滤波函数

static void apply_lowpass(LowPassFilter* filter, int* x, int* y) {
filter->last_x = filter->alpha * (*x) + (1 - filter->alpha) * filter->last_x;
filter->last_y = filter->alpha * (*y) + (1 - filter->alpha) * filter->last_y;
*x = filter->last_x; // 注意这里需要类型转换
*y = filter->last_y;
}


#define BASE_SENSITIVITY 0.0215f// 0.0215f
#define SENSITIVITY_CHANGE_THRESHOLD 55 // 灵敏度切换阈值

// 动态灵敏度计算
static float dynamic_sensitivity(int16_t delta) {
float abs_delta = fabs(delta);
if (abs_delta > SENSITIVITY_CHANGE_THRESHOLD) {
return BASE_SENSITIVITY * 0.4f;
}
return BASE_SENSITIVITY * (1.0f - 0.6f * (abs_delta / SENSITIVITY_CHANGE_THRESHOLD));
}


/**********************************************************
辅助函数
**********************************************************/

// 辅助函数:限制值范围
static inline int32_t constrain(int32_t value, int32_t min_val, int32_t max_val) {
if (value < min_val) return min_val;
if (value > max_val) return max_val;
return value;
}
// 静态全局状态
static float residual_x = 0, residual_y = 0;


#define RIGHT_COMPENSATION 1.018f // 根据实测调整此值


// 在文件头部添加时间相关宏定义
#define TIME_BASE (0x003fffff) // 24bit count shift 2 bit as 1us/bit
#define TIME_DELTA(x,y) ((x) >= (y) ? (x) - (y) : (TIME_BASE - (y) + (x)))
uint32_t CURRENT_TIME; // 假设存在外部时间计数器

void app_gyro_mouse_report(uint8_t is_ok_press) {
#if 1
// 时间处理部分
static uint32_t last_time = 0;
uint32_t now = CURRENT_TIME;
uint32_t delta_time = TIME_DELTA(now, last_time);
float dt = delta_time * 1e-6f; // 转换为秒(假设每个计数=1us)
last_time = now;

// 初始化时跳过无效时间差
if(dt <= 0 || dt > 0.1f) { // 有效时间差范围1us~100ms
dt = 0.01f; // 默认10ms
}

// 读取原始陀螺仪数据(交换X/Y轴字段)
// 注意:根据您的结构体定义,物理X轴对应 gyro_y,物理Y轴对应 gyro_x
int16_t raw_gyro_x = (int16_t)motion_packet.gyro_z; // 物理X轴
int16_t raw_gyro_y = (int16_t)motion_packet.gyro_x; // 物理Y轴

int16_t raw_gyro_roll = (int16_t)motion_packet.gyro_y / 10; // 获取横滚角的角速度

// 计算横滚角和获取角度
float acc_roll = -atan2f((int16_t)motion_packet.acc_x, (int16_t)motion_packet.acc_z) * (180.0f / PI);
motion_packet.theta_y = acc_roll; // 移除多余的*100
float theta = -(motion_packet.theta_y * PI / 180.0f); // 正确转换为弧度

// 坐标系转换,乘上Y轴的旋转矩阵
float gx_ground = cosf(theta) * raw_gyro_y - sinf(theta) * raw_gyro_x;
float gz_ground = sinf(theta) * raw_gyro_y + cosf(theta) * raw_gyro_x;
raw_gyro_x = -(int16_t)gz_ground;
raw_gyro_y = -(int16_t)gx_ground;


// 2. 零点校准(需根据静止状态校准 zero_x/zero_y)
static int16_t zero_x = 0, zero_y = 0;
int delta_x = raw_gyro_x - zero_x;
int delta_y = raw_gyro_y - zero_y;


// 应用低通滤波
apply_lowpass(&gyro_filter, &delta_x, &delta_y);

// +++ 新增动态死区控制 +++

// 定义死区参数
#define BASE_DEADZONE 16//15// 27//25// 35//45//50//40 // 基础死区阈值
#define DYNAMIC_FACTOR 0.4f//0.5f// 0.5f // 动态调整系数
static float avg_delta_x = 0, avg_delta_y = 0;
const float alpha = 0.035f;//0.03f;//0.04f;//0.1f;// 0.1f; // 平滑系数

// 计算动态平均(指数移动平均)
avg_delta_x = alpha * fabsf(delta_x) + (1 - alpha) * avg_delta_x;
avg_delta_y = alpha * fabsf(delta_y) + (1 - alpha) * avg_delta_y;

// 计算动态死区阈值(基础值 + 动态分量)
int deadzone_x = BASE_DEADZONE + (int)(avg_delta_x * DYNAMIC_FACTOR);
int deadzone_y = BASE_DEADZONE + (int)(avg_delta_y * DYNAMIC_FACTOR);

// 应用死区处理
if(abs(delta_x) < deadzone_x) delta_x = 0;
if(abs(delta_y) < deadzone_y) delta_y = 0;

// 对角速度积分,以移动的角度控制鼠标
float angle_delta_x = delta_x * dt; // 度/秒 × 秒 = 度
float angle_delta_y = delta_y * dt;

// 动态灵敏度(示例参数,需实际调试)
float sensitivity = BASE_SENSITIVITY;
float angle_speed = sqrtf(angle_delta_x*angle_delta_x + angle_delta_y*angle_delta_y)/dt;
// if(angle_speed < 5.0f) { // 慢速移动减低灵敏度,保持定位准确性
// sensitivity *= 0.013f;
// }

// 计算像素移动量(角度变化 × 灵敏度系数)
float pixel_move_x = angle_delta_x * sensitivity * 100.0f;
float pixel_move_y = angle_delta_y * sensitivity * 100.0f;

// 运动累积和余数处理
static float residual_x = 0, residual_y = 0;
pixel_move_x += residual_x;
pixel_move_y += residual_y;

int32_t speed_x = (int32_t)pixel_move_x;
int32_t speed_y = (int32_t)pixel_move_y;

residual_x = pixel_move_x - speed_x;
residual_y = pixel_move_y - speed_y;

// 限制输出范围
speed_x = constrain(speed_x, -127, 127);
speed_y = constrain(speed_y, -127, 127);

// 发送鼠标报告
hidKbdSendMouseReport((uint8_t)(speed_x & 0xFF),
(uint8_t)(speed_y & 0xFF),
is_ok_press);
#endif
}

#endif


三轴加速计算法

​ 基于SC7122由加速度控制的鼠标控制算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define DT 0.01f        // 采样时间
#define PI 3.14f //Π

#define BASE_SENSITIVITY 0.005f // 基础灵敏度系数(需实测调整)
#define ACC_DEADZONE 60 // 加速度死区阈值

// 在文件头部添加时间相关宏定义
#define TIME_BASE (0x003fffff) // 24bit count shift 2 bit as 1us/bit
#define TIME_DELTA(x,y) ((x) >= (y) ? (x) - (y) : (TIME_BASE - (y) + (x)))
uint32_t CURRENT_TIME; // 假设存在外部时间计数器

// /***************************************加速度计控制鼠标模块**************************************/
// 高通滤波器结构
typedef struct {
float alpha; // 低通滤波系数(0.05-0.2)
float low_x; // X轴低频分量
float low_y; // Y轴低频分量
} HighPassFilter;

HighPassFilter acc_filter = {.alpha = 0.1, .low_x = 0, .low_y = 0};

// 辅助函数:限制值范围
static inline int32_t constrain(int32_t value, int32_t min_val, int32_t max_val) {
if (value < min_val) return min_val;
if (value > max_val) return max_val;
return value;
}

// 高通滤波处理
static void apply_highpass(HighPassFilter* filter, int16_t acc_x, int16_t acc_y, int* high_x, int* high_y) {
// 更新低频分量
filter->low_x = filter->alpha * acc_x + (1 - filter->alpha) * filter->low_x;
filter->low_y = filter->alpha * acc_y + (1 - filter->alpha) * filter->low_y;

// 计算高频分量
*high_x = acc_x - filter->low_x;
*high_y = acc_y - filter->low_y;
}

void app_gyro_mouse_report(uint8_t is_ok_press) {
static uint32_t last_time = 0;
static float residual_x = 0, residual_y = 0;

// 获取时间差
uint32_t now = CURRENT_TIME;
uint32_t delta_time = TIME_DELTA(now, last_time);
float dt = delta_time * 1e-6f; // 转换为秒
last_time = now;

// 无效时间处理
if(dt <= 0 || dt > 0.1f) dt = 0.01f;

// 读取原始加速度数据
int16_t raw_acc_x = (int16_t)motion_packet.acc_x;
int16_t raw_acc_y = (int16_t)motion_packet.acc_y;


// 高通滤波去除重力分量
int high_x, high_y;
apply_highpass(&acc_filter, raw_acc_x, raw_acc_y, &high_x, &high_y);

// 死区处理
int delta_x = abs(high_x) > ACC_DEADZONE ? high_x : 0;
int delta_y = abs(high_y) > ACC_DEADZONE ? high_y : 0;

// 动态灵敏度调整(根据加速度幅度)
float speed_scale = fminf(fmaxf(sqrtf(delta_x*delta_x + delta_y*delta_y)/500.0f, 0.5f), 2.0f);
float sensitivity = BASE_SENSITIVITY * speed_scale;

// 计算像素位移量
float pixel_move_x = delta_x * sensitivity * dt * 1000; // 乘以1000增强响应
float pixel_move_y = delta_y * sensitivity * dt * 1000;

// 累积余数处理
pixel_move_x += residual_x;
pixel_move_y += residual_y;

int32_t speed_x = (int32_t)pixel_move_x;
int32_t speed_y = (int32_t)pixel_move_y;

residual_x = pixel_move_x - speed_x;
residual_y = pixel_move_y - speed_y;

// 限制输出范围
speed_x = constrain(speed_x, -127, 127);
speed_y = constrain(speed_y, -127, 127);

// 发送鼠标报告(Y轴方向取反符合屏幕坐标系)
hidKbdSendMouseReport((uint8_t)speed_x, (uint8_t)(-speed_y), is_ok_press);
}

三轴加速度计驱动移植

驱动移植与数据获取

​ 在之前的尝试中,驱动i2c失败,log信息如下:

​ 查看原理图和芯片手册,得知:当SDO(Single Drive Option,单驱动器选项)接为高电平时,7位I2C从机地址时0x19。而在SC7122中SDO是接地,地址为0x18,所以要进行修改。


​ 另外,由于SC7A20T的驱动比较久远和简陋,没有7122中SL_SC7I22_RawData_Read()这个直接能读取加速度计数据的函数,参考原来SC7122中的函数并查询芯片手册得知:读取三轴数据的寄存器地址为0x28~0x2D

​ 修改后的SL_SC7I22_RawData_Read()函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @brief 读取三轴加速度计原始数据(寄存器地址0x28~0x2D,逐字节读取)
* @param acc_data_buf 存储加速度数据的缓冲区,格式:[X轴, Y轴, Z轴]
*/
void SL_Accelerometer_RawData_Read(signed short *acc_data_buf) {
unsigned char raw_data[6]; // 存储6字节原始数据(0x28~0x2D)
unsigned char drdy_status = 0x00;
unsigned short drdy_cnt = 0;

// 等待数据就绪(假设状态寄存器地址为0x0B,标志位为0x01)
while ((drdy_status & 0x01) != 0x01) {
drdy_status = 0x00;
sl_delay(1); // 延时1ms(需根据实际平台调整)
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x0B, 1, &drdy_status);
drdy_cnt++;
if (drdy_cnt > 30000) break; // 超时退出
}

// 逐字节读取加速度计三轴数据(非连续读取)
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x28, 1, &raw_data[0]); // X高字节
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x29, 1, &raw_data[1]); // X低字节
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x2A, 1, &raw_data[2]); // Y高字节
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x2B, 1, &raw_data[3]); // Y低字节
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x2C, 1, &raw_data[4]); // Z高字节
SL_Accelerometer_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x2D, 1, &raw_data[5]); // Z低字节

// 组合为16位有符号数据(大端序:高字节在前)
acc_data_buf[0] = (signed short)(((unsigned char)raw_data[0] << 8) | (unsigned char)raw_data[1]); // X轴
acc_data_buf[1] = (signed short)(((unsigned char)raw_data[2] << 8) | (unsigned char)raw_data[3]); // Y轴
acc_data_buf[2] = (signed short)(((unsigned char)raw_data[4] << 8) | (unsigned char)raw_data[5]); // Z轴

#if SL_Sensor_Algo_Release_Enable == 0x00
// 调试输出(需确保LOG函数已定义)
LOG("RawData: AX=%d, AY=%d, AZ=%d\n", acc_data_buf[0], acc_data_buf[1], acc_data_buf[2]);
#endif
}

​ 经过上述修改后,没有i2c err的错误信息,但初始化函数返回值依然是-2(IIC 通信问题):

​ 接下来应该继续排查地址、时钟频率和寄存器以及加速度计芯片是否虚焊

​ 查询芯片手册,尝试根据SC7A20T手册中的芯片id地址读取芯片id的值,(WHO_AM_I寄存器),可以正常读取出来:

说明0x19的从机地址配置正确,芯片地址和芯片地址的值正确。证明总线物理层没有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**********sc7a20**********/

#define SL_SC7A20_CHIP_ID_ADDR (unsigned char)0X0F
#define SL_SC7A20_CHIP_ID_VALUE (unsigned char)0X11

int read_reg_value(void)
{
unsigned char i;
unsigned char ret = SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_CHIP_ID_ADDR, 1, &i);
if (ret == 0) { // 通信成功
LOG("chip id is 0x%02X\r\n", i); // 正确输出芯片ID(如0x11)
} else { // 通信失败
LOG("read error: %d\r\n", ret);
}
return ret;

}

​ 在尝试使用CTRL_REG1再次验证通信读取功能时,读出来的值是0x47,即0100 0111,不是默认的0000 0111

​ 查询寄存器配置表:0100表示电源模式和低功耗模式(50HZ),0111表示使用正常模式,XYZ轴均使能;

​ 问题是,为什么能通过IIC读取寄存器的值却初始化失败了,需要分析一下初始化函数返回-2的原因:

SL_SC7A20_Driver_Init 函数的主要作用是对 SL_SC7A20 设备进行初始化配置。它根据传入的参数选择使用 SPI 或 IIC 接口进行通信,向设备的寄存器写入初始化数据,然后读取这些寄存器以验证写入操作是否成功,最后读取设备的芯片 ID 并返回其值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//find device and init 
signed char SL_SC7A20_Driver_Init(unsigned char Sl_spi_iic_init,unsigned char Sl_pull_up_mode)
{

unsigned char i=0;

if(Sl_spi_iic_init==0)
SL_SPI_IIC_INTERFACE = 0;//spi
else
SL_SPI_IIC_INTERFACE = 1;//iic

if(SL_SPI_IIC_INTERFACE==1)
{
SL_SC7A20_INIT_REG1[4]=(SL_SC7A20_INIT_REG1[4]&0xF3)|Sl_pull_up_mode;
for(i=0;i<SL_SC7A20_INIT_REG1_NUM;i++)
{
SL_SC7A20_I2c_Spi_Write(SL_SPI_IIC_INTERFACE, SL_SC7A20_INIT_REG1[3*i], SL_SC7A20_INIT_REG1[3*i+1]);
}
}
for(i=0;i<SL_SC7A20_INIT_REG2_NUM;i++)
{
//将SL_SC7A20_INIT_REG2寄存器数组中预定义的值写入寄存器中
SL_SC7A20_I2c_Spi_Write(SL_SPI_IIC_INTERFACE, SL_SC7A20_INIT_REG2[3*i], SL_SC7A20_INIT_REG2[3*i+1]);
}
for(i=0;i<SL_SC7A20_INIT_REG2_NUM;i++)
{
//将寄存器中的值读取到SL_SC7A20_INIT_REG2寄存器数组中
SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_INIT_REG2[3*i],1, &SL_SC7A20_INIT_REG2[3*i+2]);
}
for(i=1;i<SL_SC7A20_INIT_REG2_NUM;i++)
{
//循环从 i=1 开始,即跳过第一个寄存器(i=0),仅验证 i=1、2、3、4 的寄存器,即只验证0x20,0x23,0x24.0x2E这四个寄存器
if(SL_SC7A20_INIT_REG2[3*i+1]!=SL_SC7A20_INIT_REG2[3*i+2]) break;
}
if(i != SL_SC7A20_INIT_REG2_NUM)
{
if(SL_SPI_IIC_INTERFACE==0)
return -1;//reg write and read error by SPI
else
LOG("init fail \r\n");

return -2;//reg write and read error by IIC
}
SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_CHIP_ID_ADDR,1, &i);
return i;
// if(i==SL_SC7A20_CHIP_ID_VALUE) return 1;
// else return 0;
}

​ 所以可能的原因是即使能正确的读出一些寄存器,但特定寄存器的写入值与回读值不一致,所以返回值是-2,根据前面由CTRL_REG1读出的配置信息,也可能是时钟频率配置不对导致时序不匹配。明天需要对这部分进行检查。

​ 以下定义了两个初始化寄存器的数组,三个字节一组为一个寄存器,前两个值分别表示寄存器的地址和值,第三个字节都是0x00,根据SL_SC7A20_Driver_Init函数,这个是用来存放前面写入的值,用于之后进行比对验证读写是否正常作为初始化是否成功的标志。第一个初始化寄存器数组有3个寄存器,第二个数组有5个寄存器。查询SC7A20T手册中的共52个寄存器的表,定义的这8个寄存器地址均存在且可读可写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define SL_SC7A20_INIT_REG1_NUM 3
static unsigned char SL_SC7A20_INIT_REG1[9]=
{
0x1E,0x05,0x00, //NVM_WR寄存器,手册中没有配置信息,查询资料可能是对非易失性存储器的写入使能操作
0x57,0x00,0x00, //DIG_CTRL寄存器,仅通过I2C通讯方式对SDO和I2C内部上拉电阻的进行开启和关闭
0x1E,0x00,0x00, //NVM_WR寄存器,可能是清除 WREN 防止意外写入
};
#define SL_SC7A20_INIT_REG2_NUM 5
static unsigned char SL_SC7A20_INIT_REG2[15]=
{
0x2E,0x00,0x00, //FIFO_CTRL_REG
0x20,0x47,0x00, //CTRL_REG1寄存器,写入0100 0111,表示使用正常模式50Hz,XYZ均使能,可能需要修改
0x23,0x98,0x00, //CTRL_REG4
0x24,0xC0,0x00, //CTRL_REG5
0x2E,0x4F,0x00, //FIFO_CTRL_REG
};

​ 结合SL_SC7A20_Driver_Init对其的使用进一步分析,并尝试打印寄存器的回读值。但是直接在SL_SC7A20_Driver_Init中打印却栈溢出了,所以沿用之前写的read_reg_value直接读取指定寄存器的值。在SL_SC7A20_Driver_Init中,返回-2的直接原因是i不等于第二个初始化寄存器数组中的寄存器数量。在上一个for循环中,当写入的值和读取出的值不一样时,就会break导致i的值不对。通过read_reg_value函数读出的寄存器的值如下:

1
2
3
4
5
6
7
8
 * static unsigned char SL_SC7A20_INIT_REG2[15]=
{
0x2E,0x00,0x00, //skip
0x20,0x47,0x00, //right value: 0x47 Default:0x07 ->write sucess
0x23,0x98,0x00, //right value: 0x98 Default:0x00 ->write sucess
0x24,0xC0,0x00, //error value: 0x40 Default:0x00 ->write fail (1100 0000 -> 0100 0000) break
0x2E,0x4F,0x00, //right value: 0x4F Default:0x00 ->write sucess
};

​ 0x24这个寄存器(CTRL_REG5)的值(BOOT)写入错误:

​ 先不管这个问题,尝试直接对acc寄存器的值读取,成功,与加速度有关的值在不断变化:

​ 接下来直接验证前面写的SL_Accelerometer_RawData_Read是否有用,并没有读出数据,原因应该是在新数据的判断上,把这部份注释掉后,成功读取了数据:

​ 但数据是否合理及可用要进一步验证,以及新数据判断部分,要根据芯片手册中以下这一句描述重新编写:

备注:查询新数据是否就位可采用如下条件进行判断:If((Read(0x27)&0x0F)==0x0F){break;}

​ 理解以下这一句的意思,0x27应该指的是状态寄存器(STATUS_REG),按位与0x0F如果还是0x0F,意味着XYZ轴的数据全部转换完成,参考7122判断新数据部分,编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
   unsigned char  raw_data[12];
unsigned char drdy_satus;
unsigned short drdy_cnt=0;

while((drdy_satus&0x0F)!=0x0F)
{
drdy_satus=0x00;
sl_delay(1);
SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, 0x27, 1, &drdy_satus);
drdy_cnt++;
if(drdy_cnt>30000) break;
}


​ 正常读出数据,数据可以使鼠标移动,但是呈现的是随机运动状态,此时的数据输出为(板子平放不移动):

​ 可能是SL_Accelerometer_RawData_Read部分的rawdata计算有问题,先看看之前正常使用的航博远达的加速度数据是怎样的:

​ 修改rawdata有效数据计算后:

​ 再尝试修改频率:

​ 数据变小了,但是在移动板子时,数据没有明显变化

​ 使用SL_SC7A20_Read_FIFO_Buf采集的数据,这些数据可以初步控制鼠标,需要优化:

数据优化的方向

​ 首先注意到CTRL_REG4这个寄存器,它配置了加速度计的量程,在鼠标控制方面应该会用到。另外,这个寄存器同时可以配置自测试模式,之后可能会和产测部分有关。

目前的配置:0x98(1001 1000),即量程为量程为+/-4G

修改后的配置:0x88(1000 1000)


​ 首先明确量程指的是该加速度计能够测量的加速度的范围,它对获取到的数据有多方面的影响。

量程及其含义

​ 加速度计是用于测量加速度的传感器,而 “G” 是加速度的单位,代表重力加速度,其近似值为 9.8m/s²。量程表示加速度计所能测量的加速度的最大值和最小值。

  • ±2G 量程:意味着该加速度计能够测量的加速度范围是从 -2G(约 -19.6m/s²)到 +2G(约 +19.6m/s²)。
  • ±4G 量程:可测量的加速度范围是 -4G(约 -39.2m/s²)到 +4G(约 +39.2m/s²)。
  • ±8G 量程:测量范围为 -8G(约 -78.4m/s²)到 +8G(约 +78.4m/s²)。
  • ±16G 量程:能够测量 -16G(约 -156.8m/s²)到 +16G(约 +156.8m/s²)的加速度。

量程对获取数据的影响

1. 分辨率

​ 分辨率是指加速度计能够分辨的最小加速度变化。量程和分辨率之间存在反比例关系,即量程越小,分辨率越高;量程越大,分辨率越低。

​ 例如,一个 16 位的加速度计,在 ±2G 量程下:
其可表示的数值范围是 -32768 到 +32767,总共 65536 个数值。那么每个数值对应的加速度变化(分辨率)为:2G×2/65536≈0.000061G

​ 而在±16G 量程下,同样是 16 位数据,每个数值对应的加速度变化为:16G×2/65536≈0.000488G

​ 可以看出,±2G 量程下的分辨率明显高于 ±16G 量程。因此,当你需要测量微小的加速度变化时,选择较小的量程可以获得更精确的数据。

因此,在同样的加速度情况下,选择更小的量程能够获得更大的输出数据

2. 数据饱和风险

如果实际的加速度超出了加速度计当前设置的量程,就会导致数据饱和。数据饱和意味着加速度计输出的数值达到了其所能表示的最大值或最小值,无法再准确反映实际的加速度大小。

例如,当加速度计设置为 ±2G 量程时,如果实际的加速度达到了 +3G,加速度计输出的数值将是其所能表示的最大值,而不是真实的 +3G。此时,获取到的数据是不准确的,可能会对后续的分析和应用产生严重影响。

因此,当你预计测量的加速度可能会较大时,应该选择较大的量程,以避免数据饱和。

3. 噪声影响

一般来说,较小的量程在测量时受到噪声的影响相对较小。因为在小量程下,加速度计对微小的加速度变化更敏感,而噪声通常是相对较小的干扰信号。在小量程下,噪声占测量值的比例相对较大量程会更小,从而使测量结果更加准确。

例如,假设存在一个固定大小的噪声信号,在 ±2G 量程下,这个噪声可能只占测量范围的很小一部分;而在 ±16G 量程下,相同的噪声可能会对测量结果产生更明显的影响。

实际应用中的选择

  • 测量微小加速度变化:如人体运动监测、地震监测等应用,通常需要测量微小的加速度变化,此时应选择较小的量程(如 ±2G),以获得更高的分辨率。
  • 测量较大加速度变化:如汽车碰撞测试、工业设备振动监测等应用,可能会遇到较大的加速度变化,这时需要选择较大的量程(如 ±16G),以避免数据饱和。

输出数据率及其含义

输出数据率(Output Data Rate, ODR)指的是加速度计传感器每秒输出数据的次数,单位为赫兹(Hz)。例如:

  • 1Hz表示传感器每秒输出 1 次数据(即每 1 秒更新一次测量结果)。
  • 400Hz表示传感器每秒输出 400 次数据(即每 2.5 毫秒更新一次测量结果)。

输出数据率对获取数据的影响

1. 时间分辨率与运动捕捉能力

  • 高数据率(如 400Hz)
    • 优势:能捕捉快速变化的运动(如振动、冲击、高频动作),数据点密集,时间分辨率高(相邻数据点间隔仅 2.5 毫秒)。
    • 适用场景:运动分析(如跑步步态检测)、振动监测、实时姿态跟踪等需要高频数据的场景。
    • 缺点:数据量大幅增加(每秒 400 组 X/Y/Z 数据),可能导致 MCU 处理压力增大、功耗上升(传感器高频工作更耗电),且需注意数据存储或传输的带宽限制。
  • 低数据率(如 1Hz)
    • 优势:数据量极少(每秒 1 组数据),功耗极低(传感器大部分时间处于休眠状态),适合电池供电的低功耗设备(如穿戴设备的静止监测)。
    • 适用场景:监测缓慢变化的运动(如静止姿态、长期倾角监测)、节能模式下的周期性数据采集。
    • 缺点:无法捕捉快速运动(可能丢失关键瞬态数据),时间分辨率差(相邻数据点间隔 1 秒)。

2. 抗混叠滤波与信号完整性

根据奈奎斯特采样定理,为了不失真地还原信号,数据率需至少为信号最高频率的 2 倍。例如:

  • 若要测量频率为 100Hz 的振动信号,数据率需≥200Hz(通常建议设置为 2.5~5 倍信号频率以留安全裕度)。
  • 若数据率过低(如 1Hz),高频信号会被 “混叠” 成低频信号,导致测量失真(例如快速振动被误判为缓慢变化)。

3. 功耗与系统资源

  • 高数据率:传感器内部电路(如 ADC、数字滤波器)需高频工作,功耗显著增加(可能是低数据率的数十倍)。
  • 低数据率:传感器可进入 “睡眠 - 唤醒” 模式,大幅降低功耗(适合物联网、可穿戴设备的长期续航)。

4. 数据处理与算法适配

  • 高数据率下,需考虑实时处理算法的效率(如卡尔曼滤波、FFT 频谱分析),避免数据堆积或处理延迟。
  • 低数据率下,数据点稀疏,可能需要通过插值或融合其他传感器数据(如陀螺仪)来提升信号连续性。

如何配置输出数据率?

通常通过设置传感器的寄存器来调整数据率(以常见的加速度计如 SC7A20 为例):

  1. 找到控制数据率的寄存器(如CTRL_REG1ODR_CTRL)。
  2. 写入对应的值选择数据率(例如,通过二进制位选择 1Hz、10Hz、100Hz、400Hz 等预设档位)。
  3. 部分传感器支持 “自由运行” 模式(固定数据率持续输出)或 “单次测量” 模式(按需唤醒采样)。

总结:如何选择数据率?

  • 高频运动 / 实时性要求高:选高数据率(如 200Hz~400Hz),牺牲功耗换取精度。
  • 低功耗 / 缓慢变化场景:选低数据率(如 1Hz~10Hz),优先节能和减少数据量。
  • 通用场景:平衡功耗和性能(如 50Hz~100Hz),适配大多数运动检测(如计步、姿态识别)。

根据应用需求(如是否需要捕捉快速动作、设备续航要求、MCU 处理能力),合理选择数据率可优化系统整体性能。

数据更新慢的问题

​ 已经尝试过更改输出数据率,但数据更新仍比较慢,所以可能的原因是在代码获取数据的处理上存在某种延迟。

​ 修改FIFO中的计算方式:

1
2
3
4
5
6
7
// x_buf[i] =(signed short int)(((unsigned char)sc7a20_data[2] * 256 ) + (unsigned char)sc7a20_data[1]);
// y_buf[i] =(signed short int)(((unsigned char)sc7a20_data[4] * 256 ) + (unsigned char)sc7a20_data[3]);
// z_buf[i] =(signed short int)(((unsigned char)sc7a20_data[6] * 256 ) + (unsigned char)sc7a20_data[5]);

x_buf[i] =(signed short int)(((unsigned char)sc7a20_data[2] << 8 ) + (unsigned char)sc7a20_data[1]);
y_buf[i] =(signed short int)(((unsigned char)sc7a20_data[4] << 8 ) + (unsigned char)sc7a20_data[3]);
z_buf[i] =(signed short int)(((unsigned char)sc7a20_data[6] << 8 ) + (unsigned char)sc7a20_data[5]);

​ 其他优化的地方:频率设置为400HZ,量程设置为2G

​ 将updata函数接入FIFO计算中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
unsigned char SL_SC7A20_Read_FIFO_Buf(signed short *x_buf,signed short *y_buf,signed short *z_buf)
{
unsigned char i=0;
unsigned char sc7a20_data[7];
signed short sl_sc7a20_data[3];
unsigned char SL_FIFO_ACCEL_NUM;

SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_FIFO_SRC_REG,1,&SL_FIFO_ACCEL_NUM);
SL_FIFO_ACCEL_NUM = SL_FIFO_ACCEL_NUM&0x1f;

for(i=0;i<SL_FIFO_ACCEL_NUM;i++)
{
if(SL_SPI_IIC_INTERFACE==0)
SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_SPI_OUT_X_L,7, &sc7a20_data[0]);
else
SL_SC7A20_I2c_Spi_Read(SL_SPI_IIC_INTERFACE, SL_SC7A20_IIC_OUT_X_L,6, &sc7a20_data[1]);
// x_buf[i] =(signed short int)(((unsigned char)sc7a20_data[2] * 256 ) + (unsigned char)sc7a20_data[1]);
// y_buf[i] =(signed short int)(((unsigned char)sc7a20_data[4] * 256 ) + (unsigned char)sc7a20_data[3]);
// z_buf[i] =(signed short int)(((unsigned char)sc7a20_data[6] * 256 ) + (unsigned char)sc7a20_data[5]);

x_buf[i] =(signed short int)(((unsigned char)sc7a20_data[2] << 8 ) + (unsigned char)sc7a20_data[1]);
y_buf[i] =(signed short int)(((unsigned char)sc7a20_data[4] << 8 ) + (unsigned char)sc7a20_data[3]);
z_buf[i] =(signed short int)(((unsigned char)sc7a20_data[6] << 8 ) + (unsigned char)sc7a20_data[5]);


if(sc7a20_data_flag ==1) z_buf[i]=z_buf[i]+sc7a20_data_off;
x_buf[i] =x_buf[i] >>6;//10bits
y_buf[i] =y_buf[i] >>6;//10bits
z_buf[i] =z_buf[i] >>6;//10bits

motion_update_acc(x_buf[i],y_buf[i],z_buf[i]);

}
SL_SC7A20_I2c_Spi_Write(SL_SPI_IIC_INTERFACE, SL_SC7A20_FIFO_CTRL_REG, 0X00);

SL_SC7A20_I2c_Spi_Write(SL_SPI_IIC_INTERFACE, SL_SC7A20_FIFO_CTRL_REG, 0X4F);

if(sc7a20_data_flag ==0)
{
for(i=0;i<SL_FIFO_ACCEL_NUM;i++)
{
sl_sc7a20_data[0]=x_buf[i];
sl_sc7a20_data[1]=y_buf[i];
sl_sc7a20_data[2]=z_buf[i];
SC7A20_DATA_EXE(&sl_sc7a20_data[0]);
}
}
return SL_FIFO_ACCEL_NUM;


}

算法和时间的调整

​ 在开启FIFO数据LOG的时候,控制的感觉比较好,但在关闭LOG调整参数时鼠标飞速移动,可能的原因时LOG占用了一定的时间,所以首先要调整的时定时器的间隔

​ 经过多种尝试后,得到初步可用版本,参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define BASE_SENSITIVITY 0.7f   // 基础灵敏度系数(需实测调整)
#define ACC_DEADZONE 5 // 加速度死区阈值
HighPassFilter acc_filter = {.alpha = 0.2, .low_x = 0, .low_y = 0}; //0.3-> 0.2

// 计算像素位移量 响应值1000 -> 500
float pixel_move_x = delta_x * sensitivity * dt * 500; // 乘以1000增强响应
float pixel_move_y = delta_y * sensitivity * dt * 500;

//寄存器
static unsigned char SL_SC7A20_INIT_REG2[15]=
{
0x2E,0x00,0x00,
0x20,0x77,0x00, //CTRL_REG1寄存器:修改0x47 -> 0x77 50Hz-> 400Hz
0x23,0x88,0x00, //CTRL_REG4寄存器:修改0x98 -> 0x88 量程4g -> 2g
0x24,0xC0,0x00,
0x2E,0x4F,0x00,
};

添加扩展按键问题

​ 由于时间问题,先完善其它重要的功能,首先是扩展按键的添加

​ 移植keyboard文件,测试产测,有四个按键按下不亮,在log中down键的键值发送成了mosue的键值,有的直接发送0x00 0x00

​ 查看原理图和板子丝印,这四个按键就是扩展的一列按键,说明添加扩展按键失败

​ 接下来要理解一下添加扩展按键的逻辑

​ 扩展按键的添加主要在keyboard.c和keyboard.h中修改

​ 最后经排查,是硬件元器件贴放导致,修好板子后代码正常可用,产测都能点亮

产测问题

​ 由于原来的7122六轴驱动中有自己检测函数,检测结果会更新到motion_ACC_selftest_state和motion_ACC_selftest_state中,产根据这些值判断是否发送键值。

​ 但7A20的驱动比较简陋,没有类似的自检测函数,所以需要重新编写了简易版本,根据能否正确读出加速度计中芯片地址的值进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//修改前的检测函数
bool hal_motion_ACC_GYR_state()
{
if(motion_ACC_selftest_state && motion_ACC_selftest_state)
{
return true;
}
else
{
return false;
}
}
//修改后的检测函数
bool hal_motion_ACC_GYR_state()
{
unsigned char i;
unsigned char ret = SL_SC7A20_I2c_Spi_Read(1, 0x0F, 1, &i); //通过iic读取芯片地址的值

if (ret == 0) { // 通信成功
if (i == 0x11)
{
LOG("chip id is 0x%02X test sucess\r\n", i); // 正确输出芯片ID(0x11)
return true;
}
else
{
LOG("read error: %d\r\n", ret);
return false;
}

} else { // 通信失败
LOG("read error: %d\r\n", ret);
return false;
}
}

语音问题

语音逻辑问题

​ 由于此代码是在航博远达的兼容国内外主机的代码上改的,所以语音逻辑需要做出修改

​ 先看一下原来航博远达语音逻辑部分的更改提交记录

​ 但走的是HID通道,看起来应该不影响谷歌的语音逻辑,尽管如此还是将其改回来

语音使用问题

​ 样机在对应盒子上无法使用语音,板子在样机上也无法使用语音

三轴加速度算法的优化

现有状况的分析

​ SC7A20T中手册中对三轴线性加速度方向的描述如下,结合加速度传感器芯片的焊接位置和方向来看,定位孔在板子反面朝上时是位于右上角的。当板子正面使用时,Z轴正方形朝下,X轴正方向朝前,Y轴正方向朝右。


​ 以下是基于SC7A20T做出参数调整的由加速度计算的鼠标控制算法

​ 改算法的主要逻辑是使用acc_y和acc_x的数据对鼠标进行上下左右的控制,并且使用高通滤波去除重力分量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#define BASE_SENSITIVITY 0.7f   // 基础灵敏度系数(需实测调整)
#define ACC_DEADZONE 5//5 // 加速度死区阈值

// 在文件头部添加时间相关宏定义
#define TIME_BASE (0x003fffff) // 24bit count shift 2 bit as 1us/bit
#define TIME_DELTA(x,y) ((x) >= (y) ? (x) - (y) : (TIME_BASE - (y) + (x)))
uint32_t CURRENT_TIME; // 假设存在外部时间计数器

// /***************************************加速度计控制鼠标模块**************************************/
// 高通滤波器结构
typedef struct {
float alpha; // 低通滤波系数(0.05-0.2)
float low_x; // X轴低频分量
float low_y; // Y轴低频分量
} HighPassFilter;

HighPassFilter acc_filter = {.alpha = 0.2, .low_x = 0, .low_y = 0}; //0.3

// 辅助函数:限制值范围
static inline int32_t constrain(int32_t value, int32_t min_val, int32_t max_val) {
if (value < min_val) return min_val;
if (value > max_val) return max_val;
return value;
}

// 高通滤波处理
static void apply_highpass(HighPassFilter* filter, int16_t acc_x, int16_t acc_y, int* high_x, int* high_y) {
// 更新低频分量
filter->low_x = filter->alpha * acc_x + (1 - filter->alpha) * filter->low_x;
filter->low_y = filter->alpha * acc_y + (1 - filter->alpha) * filter->low_y;

// 计算高频分量
*high_x = acc_x - filter->low_x;
*high_y = acc_y - filter->low_y;
}

void app_gyro_mouse_report(uint8_t is_ok_press) {
// LOG("lanuch acc mouse_report\r\n");
static uint32_t last_time = 0;
static float residual_x = 0, residual_y = 0;

// 获取时间差
uint32_t now = CURRENT_TIME;
uint32_t delta_time = TIME_DELTA(now, last_time);
float dt = delta_time * 1e-6f; // 转换为秒
last_time = now;

// 无效时间处理
if(dt <= 0 || dt > 0.1f) dt = 0.01f;

// 读取原始加速度数据
int16_t raw_acc_x = (int16_t)motion_packet.acc_y ;
int16_t raw_acc_y = (int16_t)motion_packet.acc_x ;


// 高通滤波去除重力分量
int high_x, high_y;
apply_highpass(&acc_filter, raw_acc_x, raw_acc_y, &high_x, &high_y);

// 死区处理
int delta_x = abs(high_x) > ACC_DEADZONE ? high_x : 0;
int delta_y = abs(high_y) > ACC_DEADZONE ? high_y : 0;

// 动态灵敏度调整(根据加速度幅度)
// float speed_scale = fminf(fmaxf(sqrtf(delta_x*delta_x + delta_y*delta_y)/10.0f, 0.5f), 2.0f);
float sensitivity = BASE_SENSITIVITY * 1;

// 计算像素位移量
float pixel_move_x = delta_x * sensitivity * dt * 500; // 乘以1000增强响应
float pixel_move_y = delta_y * sensitivity * dt * 500;

// 累积余数处理
pixel_move_x += residual_x;
pixel_move_y += residual_y;

int32_t speed_x = (int32_t)pixel_move_x;
int32_t speed_y = (int32_t)pixel_move_y;

residual_x = pixel_move_x - speed_x;
residual_y = pixel_move_y - speed_y;

// 限制输出范围
speed_x = constrain(speed_x, -127, 127);
speed_y = constrain(speed_y, -127, 127);

// 发送鼠标报告(Y轴方向取反符合屏幕坐标系)
hidKbdSendMouseReport((uint8_t)-speed_x, (uint8_t)(-speed_y), is_ok_press);
}

重力补偿

​ 传感器测量的重力加速度约为290

由于每个检测加速度的轴检测的加速度大小不一样,补偿重力十分困难,另外,即使只是用x和y轴进行控制,在水平甩动的时候在两个轴上都有分量,难以控制,故需要将加速度计传感器更换为角速度(陀螺仪)传感器,即L3G4200D。

陀螺仪芯片L3G4200D

规格书和驱动代码重点

芯片陀螺仪方向

驱动移植和数据读取

使能陀螺仪读取数据由寄存器1控制

电流功耗问题

在规格书的电气特性说明中,以下几点与测试相符:

1.工作电流:6mA

2.睡眠电流:1.5mA

3.关机电流:5uA

需要注意的是,从睡眠模式到工作模式和从关机模式到工作模式所需要的时间是不一样的。

从睡眠模式启动到工作模式要比从关机模式到工作模式更快。

六轴陀螺仪功耗优化

问题背景

​ 在固件版本HBYD_HB_019B_A_Mouse_K19S-2025032602-Q32-release中,客户提出遥控器在一周内使用就会没电,表示功耗过高,需要进行优化。

客诉遥控器及信息

功耗问题分析

​ 功耗过高本质是使用陀螺仪后没有关闭导致的