把工业级产品常见的"通信健康监测+故障自愈"思路和"多硬件SPI资源合理分配"这两个通常只在更复杂产品里才会考虑的工程实践,一起落地在了一个教学级小项目上,同时对开源的ADXL362 Arduino驱动库做了针对性二次开发,让它支持挂载到任意一路硬件SPI总线
目录
一、系统接线部分
1.1 硬件清单
1.2 接线方案表
1.3 连接示意图
1.4 具体接线图
二、安装与使用部分
三、代码讲解部分
3.1 双硬件SPI总线分配与实例化
3.2 加速度计校准与倾斜映射
3.3 非阻塞轮询与去抖动状态机
3.4 迷宫碰撞检测与食物刷新
3.5 显示管理器与HUD刷新策略
四、项目结果演示
4.1 操作流程
4.2 演示视频
五、技术讲解部分
5.1 ADXL362的架构与通信方式
5.2 倾斜检测的物理原理
5.3 双SPI总线的时序隔离
六、常见问题解答(FAQ)
Q1:ADXL362能不能用I2C接?
Q2:ADXL362跟TFT共用SPI不行吗?
项目概述
本项目零知派增强板(主控STM32F407VET6)实现,利用零知派增强板多SPI外设优势,用双硬件SPI总线彻底隔离显示刷新与传感器采样。ADXL362以12位分辨率采集三轴加速度数据,通过硬件SPI1读取。将X/Y轴加速度值经死区阈值映射为小球在迷宫中的移动步长,倾斜越大,小球移动越快。实时轮询检测ADXL362的SPI通信是否正常,断线自动跳失败页、右上角显示红色状态点,恢复后自动软复位+重新校准+显示绿色状态点
项目难点
问题描述:在不依赖任何额外硬件的前提下,让系统知道传感器什么时候掉线、什么时候又恢复了,并且自己完成恢复动作
解决方案:设计独立的SensorHealth健康检测模块,连续N次一致才切换状态的去抖动逻辑,检测到恢复后自动执行一次软复位+重新进入测量模式+重新校准的自愈动作
一、系统接线部分
1.1 硬件清单
| 组件 | 型号/规格 | 数量 |
|---|---|---|
| 主控板 | 零知派标准板(STM32F103RBT6) | 1 |
| 加速度传感器 | ADXL362 三轴加速度计(SPI 接口) | 1 |
| 显示屏 | ST7789 240×240 SPI TFT | 1 |
| EEPROM | AT24C02C(I2C 接口,256 字节) | 板载 |
| 连接线 | 杜邦线/排线 | 若干 |
1.2 接线方案表
这张表严格按config.h里MODE_DUAL_HW分支的宏定义整理,TFT和ADXL362分别独立占用两条不同的硬件SPI总线
①ST7789 TFT屏(SPI2)
| ST7789引脚 | 连接到 | 对应config.h宏 | 说明 |
|---|---|---|---|
| VCC | 3.3V | — | 不要接5V |
| GND | GND | — | 与主控共地 |
| SCL(SCK) | D52 | 板载硬件SPI2 | 软件模拟时钟 |
| SDA(MOSI) | D51 | 板载硬件SPI2 | 软件模拟数据线 |
| RES(RST) | D4 | TFT_RST | 复位 |
| DC | D2 | TFT_DC | 数据/命令选择 |
| CS | D53 | TFT_CS | 片选 |
② ADXL362加速度计(SPI1)
| ADXL362引脚 | 连接到 | 对应config.h宏 | 说明 |
|---|---|---|---|
| VDD | 3.3V | — | ADXL362是3.3V器件,不能上5V |
| GND | GND | — | 与主控共地 |
| SCLK | A5 | ACCEL_SPI1_SCLK | STM32F103标准SPI1时钟脚 |
| SDA | A7 | ACCEL_SPI1_MOSI | 主控→传感器方向 |
| SDO | A6 | ACCEL_SPI1_MISO | 传感器→主控方向 |
| CS | A4 | ACCEL_CS | 由代码手动控制片选,不用SPI外设自带的NSS功能 |
1.3 连接示意图
ADXL362的VDD必须接3.3V,不能接5V。ADXL362的工作电压范围是1.8V~3.3V
1.4 具体接线图
TFT和ADXL362务必接到两组独立的信号线,需要考虑片选时序
二、安装与使用部分
2.1 开源平台-输入"ADXL362加速度传感器"并搜索-代码下载自动打开
2.2 连接-验证-上传
2.3 调试-串口监视器
三、代码讲解部分
整个项目采用模块化设计,共4个自定义模块:ADXL362(改进版驱动)、SensorHealth(健康检测)、MazeGame(游戏逻辑)、DisplayManager(显示管理),加上主程序。下面重点讲解五个最重要的代码部分
3.1 双硬件SPI总线分配与实例化
/* ============================ SPI 驱动模式选择 ============================ * MODE_SOFT_HW : TFT走软件SPI,ADXL362走硬件SPI(全局SPI对象) —— 零知派标准板 * MODE_DUAL_HW : TFT走板载默认硬件SPI,ADXL362单独走硬件SPI1(A4~A7) —— 增强板 * ========================================================================= */ #define MODE_SOFT_HW 0 #define MODE_DUAL_HW 1 #define ACTIVE_SPI_MODE MODE_DUAL_HW // <-- 零知派增强板(STM32F407VET6)用这个模式 /* ============================ ST7789 显示屏引脚 ============================ */ #define TFT_RST 4 // 复位 #define TFT_DC 2 // 数据/命令 #if (ACTIVE_SPI_MODE == MODE_SOFT_HW) #define TFT_CS 6 #define TFT_MOSI 8 #define TFT_SCLK 7 #else // MODE_DUAL_HW: TFT使用板载默认硬件SPI(实测SPI2,CS/SCK/MOSI/MISO=D53/52/51/50) #define TFT_CS 53 #endif /* ============================ ADXL362 引脚 / SPI总线 ============================ */ #if (ACTIVE_SPI_MODE == MODE_SOFT_HW) #define ACCEL_CS A4 #else #define ACCEL_CS A4 // SPI1软件片选,手动控制 #define ACCEL_SPI1_MOSI A7 #define ACCEL_SPI1_MISO A6 #define ACCEL_SPI1_SCLK A5 #endif- &SPI:Adafruit_ST7789的硬件SPI构造函数接受一个SPIClass*指针,传入全局默认的SPI对象,让TFT走板载默认那条硬件总线;
- spi1:另外单独实例化的一个SPIClass对象,默认构造后再用
setMOSI/setMISO/setSCLK把它的三根信号线重新指定到A7/A6/A5 - xl.begin(ACCEL_CS, &spi1):把这个专属的spi1对象地址传给ADXL362驱动,让它内部所有的transfer()调用都走这条总线,而不是默认的全局SPI
3.2 加速度计校准与倾斜映射
X轴倾斜θ角时,X轴读数约为g×sin(θ)。对于小角度倾斜,sin(θ)≈θ(弧度),所以加速度值与倾角近似线性
/* --------------------------------------------------------------------------- * 现场校准:静置状态下采样求平均值,作为零位偏移 * ------------------------------------------------------------------------- */ void calibrateAccelerometer() { long sx = 0, sy = 0, sz = 0; const int samples = 20; int16_t x, y, z, t; for (int i = 0; i < samples; i++) { xl.readXYZTData(x, y, z, t); sx += x; sy += y; sz += z; delay(10); } calibX = sx / samples; calibY = sy / samples; calibZ = sz / samples; Serial.print(F("[CALIB] X:")); Serial.print(calibX); Serial.print(F(" Y:")); Serial.print(calibY); Serial.print(F(" Z:")); Serial.println(calibZ); } void enterFailPage() { uiState = UI_FAIL_PAGE; pageEnterMs = millis(); disp.drawFailPage(); } bool MazeGame::update(int16_t accelX, int16_t accelY) { int moveX = 0, moveY = 0; if (abs(accelX) > BALL_MOVE_DEADZONE) moveX = map(accelX, -200, 200, -BALL_MOVE_MAXSTEP, BALL_MOVE_MAXSTEP); if (abs(accelY) > BALL_MOVE_DEADZONE) moveY = map(accelY, -200, 200, -BALL_MOVE_MAXSTEP, BALL_MOVE_MAXSTEP); int oldX = _ballX, oldY = _ballY; int newX = constrain(_ballX + moveX, BALL_RADIUS, SCREEN_WIDTH - BALL_RADIUS); int newY = constrain(_ballY - moveY, BALL_RADIUS, SCREEN_HEIGHT - BALL_RADIUS); if (checkCollision(newX, newY)) { return false; // 撞墙,保持原位 } _ballX = newX; _ballY = newY; clearBallAt(oldX, oldY); drawBall(); int dx = _ballX - _targetX, dy = _ballY - _targetY; if (dx * dx + dy * dy < (BALL_RADIUS * 2)) { _score++; generateNewTarget(); // 食物随机刷新到迷宫内任意一个可通行格子 drawTarget(); return true; } return false; }| 参数 | 值 | 含义 |
|---|---|---|
samples | 20 | 校准采样次数,取平均值作为零位偏移 |
BALL_MOVE_DEADZONE | 50 | 死区阈值,加速度绝对值小于此值时小球不移动 |
BALL_MOVE_MAXSTEP | 5 | 单次最大移动步长(像素) |
-200/200 | 映射输入范围 | 加速度值的有效范围 |
不校准的话水平放置时X/Y轴可能读到±30的偏移值,导致小球“自己往一边跑”。校准后,水平放置时X/Y轴读数接近零
3.3 非阻塞轮询与去抖动状态机
consecutiveOk和_consecutiveFail是两个计数器,分别记录连续正常和连续失败的次数
- 只有当连续正常次数达到HEALTH_OK_DEBOUNCE(2次)时,才判定为恢复
- 连续失败达到HEALTH_FAIL_DEBOUNCE(2次)时,才判定为失败
HealthEvent SensorHealth::poll() { uint32_t now = millis(); if (now - _lastPollMs < HEALTH_POLL_INTERVAL_MS) { return HEALTH_EVENT_NONE; // 还没到轮询节拍,直接跳过(非阻塞) } _lastPollMs = now; // 读 DEVID_AD(0x00,应为0xAD) 和 PARTID(0x02,应为0xF2) 两个只读寄存器, // 判断SPI链路是否正常——即使传感器已经在测量模式下运行,这两个寄存器 // 的值也不会变,用它们做"心跳检测"不影响正常的姿态数据采集。 bool ok = (_accel != nullptr) && _accel->checkCommunication(); if (ok) { _consecutiveFail = 0; if (_consecutiveOk < 255) _consecutiveOk++; } else { _consecutiveOk = 0; if (_consecutiveFail < 255) _consecutiveFail++; } if (_state != COMM_OK && _consecutiveOk >= HEALTH_OK_DEBOUNCE) { bool wasFail = (_state == COMM_FAIL); _state = COMM_OK; if (wasFail) { return HEALTH_EVENT_RECOVERED; // 只有"曾经失败过"才算恢复事件 } } else if (_state != COMM_FAIL && _consecutiveFail >= HEALTH_FAIL_DEBOUNCE) { _state = COMM_FAIL; _failEventCount++; return HEALTH_EVENT_NEW_FAIL; } return HEALTH_EVENT_NONE; }用millis()时间戳判断代替delay(),是嵌入式非阻塞编程最基础也最重要的模式,健康检测不会拖慢游戏画面刷新
恢复自愈逻辑:
} else if (ev == HEALTH_EVENT_RECOVERED) { xl.SPIwriteOneRegister(0x1F, 0x52); // 软复位 delay(10); xl.beginMeasure(); // 重新进入测量模式 calibrateAccelerometer(); // 重新校准 record.calibX = calibX; ... eeprom.save(record); enterOkPage(); }关键的是软复位 + 重新进入测量模式:
- ADXL362断电重连后,POWER_CTL寄存器(0x2D)的测量模式位会被硬件复位清零
- 但SPI协议层面通信其实已经恢复了,如果不处理这一步,游戏会显示"通信正常"却读到无效数据
- 检测到RECOVERED事件的瞬间,自动补一次软复位+重新进入测量模式+重新校准,相当于让代码自己完成了原本需要人工按复位键才能做的事
3.4 迷宫碰撞检测与食物刷新
迷宫地图的位压缩存储
存储方式是byte maze[12][12],144字节,uint16_t按行压缩成位图,一行12格墙体信息压进16位整数的低12位(1=墙,0=通路),12行只需24字节
static const uint16_t MAZE_ROWS[MAZE_HEIGHT] = { 0xFFF, 0x811, 0xBD5, 0xA05, 0xAFD, 0xA81, 0xABD, 0x8A5, 0xBB5, 0xA05, 0x8F1, 0xFFF, }; bool MazeGame::isWall(uint8_t cellX, uint8_t cellY) { if (cellX >= MAZE_WIDTH || cellY >= MAZE_HEIGHT) return true; return (MAZE_ROWS[cellY] >> cellX) & 0x01; }取值(MAZE_ROWS[cellY] >> cellX) & 0x01是标准的"取第cellX位"位运算写法:右移cellX位把目标位移到最低位,再与0x01清除其余高位。cellX >= MAZE_WIDTH这行边界检查把越界统一当墙处理,相当于给迷宫套了层看不见的保护墙
判断所在格子是否为墙
小球被抽象成一个半径为
BALL_RADIUS(8px)的圆
bool MazeGame::checkCollision(int x, int y) { int top = y - BALL_RADIUS, bottom = y + BALL_RADIUS; int left = x - BALL_RADIUS, right = x + BALL_RADIUS; if (checkPointCollision(x, top)) return true; if (checkPointCollision(x, bottom)) return true; if (checkPointCollision(left, y)) return true; if (checkPointCollision(right, y)) return true; if (checkPointCollision(left, top)) return true; if (checkPointCollision(right, top)) return true; if (checkPointCollision(left, bottom)) return true; if (checkPointCollision(right, bottom)) return true; return false; }碰撞检测取圆的四条边中点和四个角点共8个采样点,每个点调用checkPointCollision()计算量是常量级(8次调用),比精确的圆-矩形相交检测简单得多,对于20px格子的迷宫来说精度完全够用
随机尝试法
这种方法的优势是不需要预先计算所有可通行格子的列表,节省RAM和代码复杂度
void MazeGame::generateNewTarget() { int nx = _targetX, ny = _targetY; for (uint8_t attempt = 0; attempt < 50; attempt++) { int cx = random(1, MAZE_WIDTH - 1); int cy = random(1, MAZE_HEIGHT - 1); if (!isWall((uint8_t)cx, (uint8_t)cy)) { nx = cx * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2; ny = cy * MAZE_CELL_SIZE + MAZE_CELL_SIZE / 2; break; } } clearBallAt(_targetX, _targetY); _targetX = nx; _targetY = ny; }在迷宫的内部区域(排除边界墙)随机取一个格子,如果不是墙就放食物;最多尝试50次
3.5 显示管理器与HUD刷新策略
将整行顶部(0~20px)刷黑,这样做的好处是HUD和状态点共享同一个黑色背景条,视觉上更统一
void DisplayManager::drawStatusDot(CommState state) { // 先刷黑底再画圆点,避免迷宫墙壁的蓝色从圆点周围透出来 int16_t bx = STATUS_DOT_X - STATUS_DOT_R - 2; int16_t by = STATUS_DOT_Y - STATUS_DOT_R - 2; int16_t bs = (STATUS_DOT_R + 2) * 2; _tft->fillRect(bx, by, bs, bs, COLOR_BG); uint16_t c = (state == COMM_OK) ? COLOR_DOT_OK : COLOR_DOT_FAIL; _tft->fillCircle(STATUS_DOT_X, STATUS_DOT_Y, STATUS_DOT_R, c); } void DisplayManager::drawScoreHUD(uint16_t score, uint16_t best) { // 整行铺满刷黑,不留空档给状态点——本项目里这个函数只在 // enterGameScreen()里被调用,后面一定紧跟一次drawStatusDot()。 _tft->fillRect(0, 0, SCREEN_WIDTH, 20, COLOR_BG); _tft->setCursor(6, 4); _tft->setTextColor(COLOR_TEXT); _tft->setTextSize(1); _tft->print("Score:"); _tft->print(score); _tft->print(" Best:"); _tft->print(best); }由于drawScoreHUD()之后一定紧跟drawStatusDot(),状态点会画在已经刷黑的区域上
系统流程图
①ADXL362 库文件多总线改造
原版 ADXL362 Arduino 库使用全局 SPI 对象,硬编码在 SPI.transfer() 调用中。本项目将 begin() 函数改造为接受第二个 SPIClass *spiPort 参数,并新增 _spi 成员变量
void ADXL362::begin(int16_t chipSelectPin, SPIClass *spiPort) { _csPin = chipSelectPin; _spi = (spiPort != nullptr) ? spiPort : &SPI; // 不传就退化成默认全局SPI,向下兼容 pinMode(_csPin, OUTPUT); digitalWrite(_csPin, HIGH); _spi->begin(); _spi->setDataMode(SPI_MODE0); // CPHA=CPOL=0,对应手册Mode 0 delay(1000); SPIwriteOneRegister(0x1F, 0x52); // 软复位:寄存器地址0x1F,写入0x52(ASCII 'R') delay(10); }所有寄存器读写函数内部原本直接写
SPI.transfer(...)的地方,全部同步改为_spi->transfer(...)。
- 当 ST7789 正在接收像素数据时,SPI2 的 MOSI 线被占满,但 SPI1 的 MOSI/MISO/SCK 完全独立,ADXL362 的采样不受任何影响
- 两条总线并行工作,游戏刷新和传感器采样真正做到了互不干扰
既支持了 SPI1 独立总线,又通过spiPort != nullptr的判断保持了向下兼容——如果不传第二个参数,自动退化为默认全局 SPI。
②测量模式切换机制与断电恢复
ADXL362 的测量模式切换遵循以下步骤:
软复位:写 0x1F 寄存器为 0x52,将所有寄存器恢复到默认值→ 配置参数:在待机模式下设置输出数据率、量程、噪声模式等 → 进入测量模式:读 0x2D 的当前值,将 bit1 置 1(temp | 0x02),写回 0x2D
在HEALTH_EVENT_RECOVERED分支中,自动执行三步自愈动作:
xl.SPIwriteOneRegister(0x1F, 0x52); // 软复位:寄存器全部恢复默认值 delay(10); xl.beginMeasure(); // 重新进入测量模式 calibrateAccelerometer(); // 重新校准零位偏移- 软复位确保芯片回到已知状态,清除可能存在的寄存器异常值
- beginMeasure() 重新将 POWER_CTL 的 MEASURE 位写入测量模式
- 重新校准是因为断电重连后芯片的零位偏移可能发生变化
这三步相当于让代码自动完成了原本需要人工按复位键才能做的事,实现了“通信中断→自动恢复→无需手动干预”的闭环。
四、项目结果演示
4.1 操作流程
①系统上电
屏幕显示开机Logo和固件版本号,随后进入ADXL362初始化,此时
spi1总线正在被配置到A5/A6/A7
屏幕下方显示"Calibrating..."时,请让板子保持水平静止,正在采集20个样本做零位校准
②进入游戏主界面
蓝色为墙体,白色为通路,红色圆点是小球,绿色圆点是目标(食物)
任意方向倾斜板子,小球朝对应方向滚动,撞墙不可穿越。小球接触绿色目标得1分,目标在迷宫内随机挑选新的可通行位置重新刷新
③右上角圆形状态点
绿色=ADXL362(挂载在独立SPI1总线上)通信正常,红色=通信异常
- 试着拔掉ADXL362在A4~A7这组独立总线上的任意一根信号线,观察屏幕先跳转红色警示页、3秒后自动回主界面显示红点
- 重新插回后几百毫秒内跳转"COMM RESTORED"提示页,随后自动回主界面变绿点
4.2 演示视频
零知派增强板双硬件SPI驱动ADXL362重力迷宫游戏
本视频展示了基于ADXL362三轴加速度计的体感迷宫游戏。玩家通过倾斜零知派增强板,控制红色小球在12×12迷宫中滚动,避开蓝色墙壁,撞击绿色目标点得分。视频包含硬件接线展示、上电初始化过程、游戏操作演示、以及通信失败/恢复的自愈过程。重点展示了双硬件SPI架构下,显示屏刷新与传感器采样互不干扰的流畅体验
五、技术讲解部分
5.1 ADXL362的架构与通信方式
ADXL362的核心是一个微机械加工的三轴加速度传感结构。当传感器受到加速度时,内部的可移动质量块发生偏转,差分电容的平衡被打破,产生与加速度成正比的电容变化。这个变化经过相敏解调后被转换为数字信号,最终以12位精度存储在XDATA/YDATA/ZDATA寄存器中
ADXL362的通信接口是4线SPI,工作模式为CPOL=0、CPHA=0,推荐的SPI时钟频率为1MHz~5MHz。SPI命令集只有三条:
| 命令字节 | 功能 | 说明 |
|---|---|---|
| 0x0A | 写寄存器 | 后跟地址字节和数据字节 |
| 0x0B | 读寄存器 | 后跟地址字节,然后读取数据 |
| 0x0D | 读FIFO | 无需地址字节,连续读取FIFO内容 |
SPI传输采用多字节结构:第一个字节是命令,第二个字节是寄存器地址,后续字节是数据。寄存器读写命令支持多字节(突发)传输,地址会自动递增
5.2 倾斜检测的物理原理
加速度传感器的基本物理模型是:加速度a = 重力分量 + 运动加速度。当传感器静止时,运动加速度为零,此时读到的就是重力在各轴上的投影
设传感器坐标系为X-Y-Z,重力向量为g(指向地心,大小为1g)。
- 当传感器倾斜时,重力在X轴和Y轴上的投影分别为:X轴读数 ≈ g×sin(θ_x),Y轴读数 ≈ g×sin(θ_y)。
- 对于小角度倾斜(θ<30°),sin(θ)≈θ,所以加速度值与倾角近似线性,这也是用
map()函数做线性映射的理论基础
5.3 双SPI总线的时序隔离
STM32F407VET6有两个独立的SPI外设:SPI1挂载在APB2总线上,SPI2挂载在APB1总线上
本项目中的分配方案:
Adafruit_ST7789 tft(&SPI, TFT_CS, TFT_DC, TFT_RST); // TFT 走默认 SPI(SPI2) SPIClass spi1; // 独立创建 SPI1 对象 spi1.setMOSI(ACCEL_SPI1_MOSI); // SPI1_MOSI → A7 spi1.setMISO(ACCEL_SPI1_MISO); // SPI1_MISO → A6 spi1.setSCLK(ACCEL_SPI1_SCLK); // SPI1_SCLK → A5 xl.begin(ACCEL_CS, &spi1); // 将 SPI1 对象传给 ADXL362- TFT:传入全局 &SPI 对象,走板载默认硬件 SPI(STM32F407VET6 上实测为 SPI2,对应 D53/52/51/50)
- ADXL362:创建独立的 spi1 对象,通过 setMOSI/setMISO/setSCLK 将三根信号线映射到 A7/A6/A5,再将该对象的地址传给 xl.begin()。ADXL362 库内部所有 _spi->transfer() 调用都会走这条总线
SPI通信是全双工同步串行:主设备产生时钟(SCK),数据在MOSI和MISO上同时传输。当ST7789正在接收像素数据时,SPI2的MOSI线被占满,但SPI1的MOSI/MISO/SCK完全独立,ADXL362的采样不受任何影响
SPI 命令集
ADXL362 的 SPI 端口采用多字节结构,第一个字节是命令字节,命令集只有三条:
| 命令字节 | 功能 | 命令结构 |
|---|---|---|
| 0x0A | 写寄存器 | <CS down> <0x0A> <地址> <数据> <...> <CS up> |
| 0x0B | 读寄存器 | <CS down> <0x0B> <地址> <数据> <...> <CS up> |
| 0x0D | 读 FIFO | <CS down> <0x0D> <数据> <数据>... <CS up> |
多字节(突发)传输:读写寄存器命令支持多字节突发访问,地址会自动递增。地址递增在无效寄存器地址 63(0x3F)处停止,避免地址回绕
寄存器映射
ADXL362 的寄存器映射覆盖了从设备 ID 到加速度数据、FIFO 控制、运动检测配置等完整功能。本项目重点涉及以下几个关键寄存器。设备标识寄存器(用于通信自检) :
| 寄存器名 | 地址 | 固定值 | 说明 |
|---|---|---|---|
| DEVID_AD | 0x00 | 0xAD | Analog Devices 设备 ID |
| DEVID_MST | 0x01 | 0x1D | 模拟器件 MEMS ID |
| PARTID | 0x02 | 0xF2 | 器件型号 ID(362 的八进制表示) |
项目 SensorHealth::poll() 中调用的 _accel->checkCommunication() 就是通过读取并比对 DEVID_AD 和 PARTID 来判断 SPI 链路是否正常
POWER_CTL 寄存器(地址 0x2D):
POWER_CTL 寄存器是 ADXL362 最核心的控制寄存器
复位默认值为 0x00(待机模式),位定义如下:
| 位 | 名称 | 设置值 | 描述 |
|---|---|---|---|
| 7 | Reserved | — | 保留 |
| 6 | EXT_CLK | 0/1 | 外部时钟使能,由 INT1 引脚输入 |
| [5:4] | LOW_NOISE | 00/01/10 | 00=普通模式 01=低噪声 10=超低噪声 |
| 3 | WAKEUP | 0/1 | 唤醒模式使能 |
| 2 | AUTOSLEEP | 0/1 | 自动休眠使能(需配合活动/非活动检测) |
| [1:0] | MEASURE | 00/10 | 00=待机 10=测量模式 |
MEASURE 位的操作要点:将 POWER_CTL 的 bit[1:0] 写入10即可进入测量模式。对 POWER_CTL 之前的所有寄存器(0x00~0x2C)的修改应在待机模式下进行,如果在测量模式下修改,可能只在部分测量周期内生效
六、常见问题解答(FAQ)
Q1:ADXL362能不能用I2C接?
A:不能。手册明确只支持4线SPI,引脚定义里也没有SDA/SCL,这是芯片本身的硬件限制,如果一定要I2C接口的加速度计,需要换成ADXL345/ADXL343等支持I2C/SPI双模的型号
Q2:ADXL362跟TFT共用SPI不行吗?
A:共用一条总线也能工作(只是每次收发前要额外切换片选和SPI参数),但增强板既然有多余的硬件SPI资源,独立分配一条给ADXL362可以避免任何总线仲裁开销,代码逻辑也更清晰,如果换到只有一路好用硬件SPI的板子,本项目也保留了
MODE_SOFT_HW模式(TFT走软件SPI)作为兼容方案
项目资源整合
ADXL362 库文件: ADXL362 (Rev.G)
ADXL362 数据手册: annem/ADXL362