一、任务目标
测试利用Arduino Nano通过SPI读取MA600A数据
二、硬件部分
这个是第一次打的直接50mm的
新打的直径34mm的小板也到了,焊接测试正常
三、软件部分
写了两个程序,全部借助AI,这个是第一次打板的程序
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// MA600A引脚定义
#define MA600A_CS_PIN 10 // 片选引脚
// MA600A寄存器地址
#define ANGLE_REGISTER 0x0000 // 角度寄存器地址
// OLED显示屏配置
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // OLED重置引脚,-1表示不使用
#define SCREEN_ADDRESS 0x3C // I2C地址,通常是0x3C或0x3D
// 创建OLED显示对象
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// 全局变量
uint16_t angleRaw = 0;
float angleRadians = 0.0; // 以弧度为单位
float angleDegrees = 0.0; // 以度为单位
float filteredAngleRad = 0.0; // 滤波后的弧度
float filteredAngleDeg = 0.0; // 滤波后的度
const float filterFactor = 0.1; // 滤波系数,0.0-1.0,值越小滤波效果越强
unsigned long lastUpdateTime = 0;
const unsigned long updateInterval = 50; // 更新间隔(ms)
uint16_t zeroValue = 0; // 零位值
void setup() {
// 初始化串口通信
Serial.begin(115200);
// 初始化OLED显示屏
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F(“SSD1306 allocation failed”));
for(;;); // 停在这里
}
display.display();
delay(500); // 初始显示延迟
// 初始化SPI
SPI.begin();
// 配置SPI参数 - 降低速度确保稳定
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); // 2MHz
// 配置片选引脚
pinMode(MA600A_CS_PIN, OUTPUT);
digitalWrite(MA600A_CS_PIN, HIGH); // 默认高电平(不选中)
// 延时让传感器稳定
delay(100);
// 显示启动信息
displayStartupScreen();
// 校准零位
calibrateZeroPosition();
Serial.println(“MA600A + OLED 显示系统已启动 - 弧度制版本”);
}
void loop() {
// 以固定间隔更新
if(millis() - lastUpdateTime >= updateInterval) {
lastUpdateTime = millis();
// 读取MA600A角度数据
angleRaw = readMA600ARegister(ANGLE_REGISTER);
// 计算角度 - 使用弧度制
angleRadians = calculateAngleRadiansFromRaw(angleRaw);
angleDegrees = angleRadians * 180.0 / PI;
// 应用360°环绕滤波(弧度制)
filteredAngleRad = circularFilterRad(filteredAngleRad, angleRadians, filterFactor);
filteredAngleDeg = filteredAngleRad * 180.0 / PI;
// 打印到串口(调试用)
Serial.print(“原始值: 0x”);
Serial.print(angleRaw, HEX);
Serial.print(" | 零位: 0x");
Serial.print(zeroValue, HEX);
Serial.print(" | 角度: ");
Serial.print(angleDegrees, 2);
Serial.print(“° (”);
Serial.print(angleRadians, 3);
Serial.print("rad) | 滤波后: ");
Serial.print(filteredAngleDeg, 2);
Serial.print(“° (”);
Serial.print(filteredAngleRad, 3);
Serial.println(“rad)”);
// 更新OLED显示
updateDisplay();
}
}
/**
* 360°环绕滤波函数(弧度制)
*/
float circularFilterRad(float current, float target, float factor) {
// 计算最短路径差值
float diff = target - current;
// 处理2π弧度环绕
if(diff > PI) {
diff -= 2*PI;
} else if(diff < -PI) {
diff += 2*PI;
}
// 应用滤波
float newAngle = current + diff * factor;
// 保持在0-2π范围内
if(newAngle < 0) newAngle += 2*PI;
if(newAngle >= 2*PI) newAngle -= 2*PI;
return newAngle;
}
/**
* 从原始值计算弧度值
*/
float calculateAngleRadiansFromRaw(uint16_t rawValue) {
// MA600A是16位分辨率,所以分母是65536
float radians = (rawValue * 2 * PI) / 65536.0;
// 确保在0-2π范围内
if(radians < 0) radians += 2*PI;
if(radians >= 2*PI) radians -= 2*PI;
return radians;
}
/**
* 读取MA600A寄存器值 - 优化版
*/
uint16_t readMA600ARegister(uint16_t regAddress) {
uint16_t result = 0;
// 拉低片选信号
digitalWrite(MA600A_CS_PIN, LOW);
delayMicroseconds(2); // 增加CS拉低延迟
// 发送寄存器地址(高8位 + 低8位)
uint8_t highAddr = (regAddress >> 8) & 0xFF;
uint8_t lowAddr = regAddress & 0xFF;
SPI.transfer(highAddr);
SPI.transfer(lowAddr);
// 等待t_sclk_nd(数据准备时间)
delayMicroseconds(100); // 增加到100us确保数据就绪
// 读取数据(高8位 + 低8位)
uint8_t highByte = SPI.transfer(0x00);
uint8_t lowByte = SPI.transfer(0x00);
// 合并为16位数据
result = (highByte << 8) | lowByte;
// 拉高片选信号
digitalWrite(MA600A_CS_PIN, HIGH);
delayMicroseconds(2); // 增加CS拉高延迟
return result;
}
/**
* 校准零位
*/
void calibrateZeroPosition() {
Serial.println(“开始校准零位…”);
// 读取10次取平均值,提高精度
uint32_t sum = 0;
const int samples = 10;
for(int i = 0; i < samples; i++) {
sum += readMA600ARegister(ANGLE_REGISTER);
delay(10);
}
zeroValue = sum / samples;
Serial.print(“零位校准值: 0x”);
Serial.println(zeroValue, HEX);
// 初始化滤波值
filteredAngleRad = calculateAngleRadiansFromRaw(zeroValue);
filteredAngleDeg = filteredAngleRad * 180.0 / PI;
// 显示校准结果
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(“ZERO CALIBRATED”);
display.setCursor(0, 20);
display.print(“Value: 0x”);
display.print(zeroValue, HEX);
display.setCursor(0, 40);
display.print(“Ready in 2s…”);
display.display();
delay(2000);
}
/**
* 显示启动屏幕
*/
void displayStartupScreen() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(“MA600A + OLED”);
display.setTextSize(2);
display.setCursor(10, 20);
display.print(“System”);
display.setTextSize(1);
display.setCursor(20, 45);
display.print(“Starting…”);
display.display();
delay(1500);
}
void updateDisplay() {
display.clearDisplay();
// 标题
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(“MA600A Angle Sensor”);
// 分隔线
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
// 显示当前角度(弧度制,保留3位小数)- 移动半个字符宽度
display.setTextSize(1);
display.setCursor(3, 20); // 从X=3开始,相当于移动半个字符宽度
display.print("Angle: ");
char buffer[10];
dtostrf(filteredAngleRad, 5, 3, buffer);
display.print(buffer);
display.print(“rad”);
// 显示度数 - 也移动半个字符宽度
display.setCursor(3, 28); // 从X=3开始
display.print(" ");
dtostrf(filteredAngleDeg, 5, 3, buffer);
display.print(buffer);
display.print(“deg”);
// 显示原始值 - 也移动半个字符宽度
display.setTextSize(1);
display.setCursor(3, 43); // 从X=3开始
display.print(“Raw: 0x”);
display.print(angleRaw, HEX);
// Zero值移到最后一行 - 也移动半个字符宽度
display.setCursor(3, 58); // 从X=3开始
display.print(“Zero: 0x”);
display.print(zeroValue, HEX);
display.display();
}
这个是第二次打板的程序库文件
/**
* @file ma600a_spi.h
* @brief MA600A高精度磁性角度传感器SPI驱动库
* @author Auto-generated from MA600A datasheet
* @date 2025-06-05
*/
#ifndef MA600A_SPI_H
#define MA600A_SPI_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern “C” {
#endif
// 错误代码枚举
typedef enum {
MA600A_SUCCESS = 0, MA600A_ERROR_INVALID_PARAM, MA600A_ERROR_COMM} ma600a_error_t;
// 多圈/速度模式选择
typedef enum {
MA600A_MTSP_MULTITURN = 0, // 多圈计数模式 MA600A_MTSP_SPEED = 1 // 速度模式} ma600a_mtsp_mode_t;
// MA600A设备结构体
typedef struct {
void (\*spi_transfer)(uint8_t \*tx_data, uint8_t \*rx_data, uint16_t length); void (\*chip_select)(bool state); void (\*delay_us)(uint32_t us); uint8_t device_count; // 菊花链中的设备数量} ma600a_t;
// 函数声明
ma600a_error_t ma600a_init(ma600a_t *device,
void (\*spi_transfer)(uint8_t\*, uint8_t\*, uint16_t), void (\*chip_select)(bool), void (\*delay_us)(uint32_t), uint8_t device_count);ma600a_error_t ma600a_read_angle(ma600a_t *device, float *angle_deg);
ma600a_error_t ma600a_read_multiturn_speed(ma600a_t *device,
ma600a_mtsp_mode_t mtsp_mode, float \*angle_deg, int32_t \*value);ma600a_error_t ma600a_read_register(ma600a_t *device, uint8_t reg_addr, uint8_t *reg_value);
ma600a_error_t ma600a_write_register(ma600a_t *device, uint8_t reg_addr, uint8_t reg_value);
#ifdef __cplusplus
}
#endif
#endif // MA600A_SPI_H
这是c文件
/**
* @file ma600a_spi.c
* @brief MA600A SPI驱动库实现
*/
#include “ma600a_spi.h”
#include <string.h>
// SPI命令定义
#define MA600A_CMD_READ_ANGLE 0x0000
#define MA600A_CMD_READ_REGISTER 0x4B00 // 01001011xxxx
#define MA600A_CMD_WRITE_REGISTER 0x5752 // 0101011100101010
#define MA600A_CMD_STORE_REG_BLOCK 0x4B0B // 0100101100001011
#define MA600A_CMD_RESTORE_ALL_BLOCKS 0x4C00 // 01001100xxxx
#define MA600A_CMD_CLEAR_ERROR_FLAGS 0x4F00 // 0100111100000000
// SPI时序参数(单位:ns)
#define MA600A_TIDLE_COMMAND 120
#define MA600A_TSTORE_REG_BLOCK 600000 // 600ms
#define MA600A_TRESTORE_ALL_REG_BLOCKS 240 // 240us
#define MA600A_TCSL 20
#define MA600A_TSCLK 40
#define MA600A_TCSH 20
#define MA600A_TCIPO 15
// 内部函数声明
static void ma600a_spi_select(ma600a_t *device, bool state);
static void ma600a_spi_transfer_16bit(ma600a_t *device, uint16_t tx_data, uint16_t *rx_data);
static ma600a_error_t ma600a_wait_nvm_ready(ma600a_t *device);
/**
* @brief 初始化MA600A设备
*/
ma600a_error_t ma600a_init(ma600a_t *device,
void (\*spi_transfer)(uint8_t\*, uint8_t\*, uint16_t), void (\*chip_select)(bool), void (\*delay_us)(uint32_t), uint8_t device_count) { if (!device || !spi_transfer || !chip_select || !delay_us) { return MA600A_ERROR_INVALID_PARAM; } device->spi_transfer = spi_transfer; device->chip_select = chip_select; device->delay_us = delay_us; device->device_count = device_count > 0 ? device_count : 1; // 初始化时清除错误标志 return ma600a_clear_error_flags(device);}
/**
* @brief 读取角度值
*/
ma600a_error_t ma600a_read_angle(ma600a_t *device, float *angle_deg) {
uint16_t rx_data; ma600a_spi_select(device, true); ma600a_spi_transfer_16bit(device, MA600A_CMD_READ_ANGLE, &rx_data); ma600a_spi_select(device, false); // 计算角度:Angle(deg) = Angle(dec) / 2^16 \* 360 \*angle_deg = (float)rx_data \* 360.0f / 65536.0f; return MA600A_SUCCESS;}
/**
* @brief 读取多圈计数或速度值
*/
ma600a_error_t ma600a_read_multiturn_speed(ma600a_t *device,
ma600a_mtsp_mode_t mtsp_mode, float \*angle_deg, int32_t \*value) { uint16_t rx_data\[2\]; // 读取32位数据(16位角度 + 16位多圈/速度) ma600a_spi_select(device, true); // 第一个16位:角度 ma600a_spi_transfer_16bit(device, MA600A_CMD_READ_ANGLE, &rx_data\[0\]); // 第二个16位:多圈计数或速度(使用16位传输确保字节序正确) ma600a_spi_transfer_16bit(device, 0x0000, &rx_data\[1\]); ma600a_spi_select(device, false); // 计算角度 \*angle_deg = (float)rx_data\[0\] \* 360.0f / 65536.0f; // 速度值:16位有符号扩展为32位 \*value = (int32_t)((int16_t)rx_data\[1\]); return MA600A_SUCCESS;}
/**
* @brief 读取寄存器值
*/
ma600a_error_t ma600a_read_register(ma600a_t *device,
ma600a_register_t reg_addr, uint8_t \*reg_value) { uint16_t tx_cmd, rx_data\[2\]; if (reg_addr > MA600A_REG_MAX) { return MA600A_ERROR_INVALID_PARAM; } // 构建读寄存器命令:01001011 + 寄存器地址 tx_cmd = 0x4B00 | (reg_addr & 0xFF); ma600a_spi_select(device, true); // 第一帧:发送读命令 ma600a_spi_transfer_16bit(device, tx_cmd, &rx_data\[0\]); // 等待空闲时间 device->delay_us((MA600A_TIDLE_COMMAND + 999) / 1000); // 第二帧:获取寄存器值 ma600a_spi_transfer_16bit(device, 0x0000, &rx_data\[1\]); ma600a_spi_select(device, false); // 寄存器值在第二个16位数据的低8位 \*reg_value = (uint8_t)(rx_data\[1\] & 0xFF); return MA600A_SUCCESS;}
/**
* @brief 写入寄存器值
*/
ma600a_error_t ma600a_write_register(ma600a_t *device,
ma600a_register_t reg_addr, uint8_t reg_value) { uint16_t tx_data\[2\], rx_data\[3\]; if (reg_addr > MA600A_REG_MAX) { return MA600A_ERROR_INVALID_PARAM; } ma600a_spi_select(device, true); // 第一帧:发送写命令 ma600a_spi_transfer_16bit(device, MA600A_CMD_WRITE_REGISTER, &rx_data\[0\]); // 等待空闲时间 device->delay_us((MA600A_TIDLE_COMMAND + 999) / 1000); // 第二帧:发送寄存器地址和值 tx_data\[0\] = ((uint16_t)reg_addr << 8) | reg_value; ma600a_spi_transfer_16bit(device, tx_data\[0\], &rx_data\[1\]); // 等待空闲时间 device->delay_us((MA600A_TIDLE_COMMAND + 999) / 1000); // 第三帧:验证写入 ma600a_spi_transfer_16bit(device, 0x0000, &rx_data\[2\]); ma600a_spi_select(device, false); return MA600A_SUCCESS;}
/**
* @brief 存储单个寄存器块到NVM
*/
ma600a_error_t ma600a_store_register_block(ma600a_t *device, uint8_t block_num) {
uint16_t tx_cmd, rx_data\[2\]; if (block_num > 1) { return MA600A_ERROR_INVALID_PARAM; } // 检查NVM是否空闲 if (ma600a_wait_nvm_ready(device) != MA600A_SUCCESS) { return MA600A_ERROR_NVM_BUSY; } // 构建存储命令:0100101100001011 (block 0) 或 0100101100011011 (block 1) tx_cmd = 0x4B0B | (block_num << 8); ma600a_spi_select(device, true); // 第一帧:发送存储命令 ma600a_spi_transfer_16bit(device, tx_cmd, &rx_data\[0\]); // 第二帧:无操作 uint16_t dummy = 0; device->spi_transfer((uint8_t\*)&dummy, (uint8_t\*)&rx_data\[1\], 2); ma600a_spi_select(device, false); // 等待存储完成 device->delay_us(MA600A_TSTORE_REG_BLOCK / 1000); return MA600A_SUCCESS;}
/**
* @brief 从NVM恢复所有寄存器块
*/
ma600a_error_t ma600a_restore_all_blocks(ma600a_t *device) {
uint16_t rx_data; // 检查NVM是否空闲 if (ma600a_wait_nvm_ready(device) != MA600A_SUCCESS) { return MA600A_ERROR_NVM_BUSY; } ma600a_spi_select(device, true); // 发送恢复命令 ma600a_spi_transfer_16bit(device, MA600A_CMD_RESTORE_ALL_BLOCKS, &rx_data); ma600a_spi_select(device, false); // 等待恢复完成 device->delay_us(MA600A_TRESTORE_ALL_REG_BLOCKS); return MA600A_SUCCESS;}
/**
* @brief 清除错误标志
*/
ma600a_error_t ma600a_clear_error_flags(ma600a_t *device) {
uint16_t rx_data; ma600a_spi_select(device, true); // 发送清除错误命令 ma600a_spi_transfer_16bit(device, MA600A_CMD_CLEAR_ERROR_FLAGS, &rx_data); ma600a_spi_select(device, false); return MA600A_SUCCESS;}
/**
* @brief 读取菊花链中所有设备的角度
*/
ma600a_error_t ma600a_daisy_chain_read_angles(ma600a_t *device, float *angles_deg) {
if (device->device_count <= 1) { return ma600a_read_angle(device, &angles_deg\[0\]); } uint16_t rx_data\[16\]; // 最大支持16个设备 uint16_t tx_data = MA600A_CMD_READ_ANGLE; if (device->device_count > 16) { return MA600A_ERROR_INVALID_PARAM; } ma600a_spi_select(device, true); // 传输所有设备的数据 for (uint8_t i = 0; i < device->device_count; i++) { ma600a_spi_transfer_16bit(device, tx_data, &rx_data\[i\]); } ma600a_spi_select(device, false); // 转换角度值(注意:菊花链中最后一个设备的数据最先返回) for (uint8_t i = 0; i < device->device_count; i++) { uint8_t idx = device->device_count - 1 - i; angles_deg\[i\] = (float)rx_data\[idx\] \* 360.0f / 65536.0f; } return MA600A_SUCCESS;}
/**
* @brief 内部:片选控制
*/
static void ma600a_spi_select(ma600a_t *device, bool state) {
device->chip_select(state); if (state) { // 等待片选建立时间 device->delay_us((MA600A_TCSL + 999) / 1000); } else { // 等待片选保持时间 device->delay_us((MA600A_TCSH + 999) / 1000); }}
/**
* @brief 内部:16位SPI传输
*/
static void ma600a_spi_transfer_16bit(ma600a_t *device, uint16_t tx_data, uint16_t *rx_data) {
uint8_t tx_bytes\[2\] = {(uint8_t)(tx_data >> 8), (uint8_t)tx_data}; uint8_t rx_bytes\[2\]; device->spi_transfer(tx_bytes, rx_bytes, 2); \*rx_data = ((uint16_t)rx_bytes\[0\] << 8) | rx_bytes\[1\]; // 等待时钟周期 device->delay_us((MA600A_TSCLK + 999) / 1000);}
/**
* @brief 内部:等待NVM就绪
*/
static ma600a_error_t ma600a_wait_nvm_ready(ma600a_t *device) {
uint8_t if_reg; uint32_t timeout = 100000; // 100ms超时 while (timeout--) { if (ma600a_read_register(device, MA600A_REG_IF, &if_reg) != MA600A_SUCCESS) { continue; } // 检查NVM忙标志(假设第7位是NVM忙标志,具体需要参考寄存器定义) if (!(if_reg & 0x80)) { return MA600A_SUCCESS; } device->delay_us(100); // 100us延时 } return MA600A_ERROR_TIMEOUT;}
四、展示照片
大板测试

小板测试

五、总结
手艺太差,焊的比较差劲,Arduino Nano 相对简单,把资料丢给AI,就把库文件,和程序编写好了,另外这个Arduino Nano 移植到STM32 还在研究中
六、下一步计划
整体测试驱动部分


