【2026 机器人模块大赛】3 +基于MPS的智能家居六能交互机器人+MA600A测试

一、任务目标

测试利用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;

}

四、展示照片

大板测试

2026-06-13T02_37_18.765Z-193617

小板测试

2026-06-13T02_32_43.964Z-985602

五、总结

手艺太差,焊的比较差劲,Arduino Nano 相对简单,把资料丢给AI,就把库文件,和程序编写好了,另外这个Arduino Nano 移植到STM32 还在研究中

六、下一步计划

整体测试驱动部分