Funpack4-3 基于FRDM iMX93实现ADMT4000旋转角度检测
一、项目描述
(一)项目介绍
本项目聚焦于 ADMT4000 磁转数传感器的应用开发,旨在设计并实现一套完整的硬件电路与软件系统,通过 NXP FRDM iMX93 硬件平台读取 ADMT4000 传感器输出的旋转圈数和角度数据,并在LCD 屏上直观显示。ADMT4000 作为核心传感器,具备断电状态下记录磁场旋转次数的独特优势,其融合 GMR 转数计数传感器、GMR 象限检测传感器与 AMR 角度传感器的三元架构,可实现最高 46 圈的旋转计数和 ±0.25° 的角度测量精度,广泛适用于无刷直流电机控制、执行器定位、非接触式绝对位置测量等工业场景。本项目的成功实现,为工业自动化设备中的位置检测需求提供了高可靠性、高精度的解决方案,具备较强的实际应用价值。
(二)设计思路
本项目的设计核心围绕 “硬件兼容适配 + 软件高效驱动 + 可视化展示” 三大目标展开,具体思路如下:
- 硬件层面:选择 NXP FRDM iMX93 作为主控平台,该平台搭载 ARM Cortex-A55 与 Cortex-M33 双核架构,支持丰富的外设接口,其 SPI 控制器可满足 ADMT4000 的通信需求。通过硬件电路设计将 ADMT4000 的 SPI 接口(SDI、SDO、SCLK、CS)与 FRDM iMX93 的对应引脚连接,并完成电源、复位等辅助电路的匹配,确保传感器稳定工作。
- 软件层面:采用 “底层驱动适配 + 应用层数据处理 + UI 可视化” 的分层架构。底层通过设备树配置暴露 SPI 接口,避开官方驱动的时序兼容问题;应用层开发 ADMT4000 专属驱动,实现 SPI 数据收发、寄存器读写与数据解析;基于 LVGL UI 框架设计可视化界面,实时展示旋转圈数、角度值等关键数据。
- 系统优化:针对传感器数据的高精度要求,在软件中加入数据滤波处理,减少测量噪声;通过合理配置 SPI 通信速率、寄存器读写时序,确保数据传输的稳定性与实时性,最终实现 “传感器数据采集 - 数据处理 - 屏幕显示” 的全流程闭环。
二、硬件介绍
(一)核心硬件选型
- 主控平台:NXP FRDM iMX93 开发板,搭载 MIMX9352CVVXMAB 芯片,包含 2 个 Cortex-A55 核心(最高 1.8GHz)和 1 个 Cortex-M33 核心(250MHz),集成 Ethos-U65 NPU,支持 LPDDR4/X 内存扩展、多路 SPI/I2C/UART 接口,以及 MIPI DSI/LVDS 显示接口,满足主控运算、外设扩展与 LCD 显示需求。

- 传感器模块:ADMT4000 磁转数传感器,采用 24 引脚 TSSOP 封装,工作电压 3.3V,SPI 接口逻辑电平兼容 1.7V-5V,工作温度范围 - 40°C 至 + 150°C,具备 46 圈旋转计数能力、16k° 数字输出范围,内置温度传感器用于环境温度补偿,确保极端工况下的测量精度。

- 显示模块:LCD 屏(HDMI接口),分辨率支持 1024×600,响应速度快,适配 LVGL UI 框架,可实现多控件、多数据的同步显示。
(二)硬件连接设计
- 电源连接:ADMT4000 的 VDD 引脚接入 FRDM iMX93 的 3.3V 电源输出,VDRIVE 引脚同样连接 3.3V 逻辑电源,GND 引脚与开发板地信号共地,确保电源稳定性。在 VDD 和 VDRIVE 引脚附近并联 100nF X8R 电容,实现电源滤波。
- SPI 通信连接:通过设备树配置 FRDM iMX93 的 LPSPI6 接口与 ADMT4000 连接,具体引脚映射为:MX93_PAD_GPIO_IO01LPSPI6_SIN(ADMT4000 SDI)、MX93_PAD_GPIO_IO02LPSPI6_SOUT(ADMT4000 SDO)、MX93_PAD_GPIO_IO03LPSPI6_SCK(ADMT4000 SCLK)、MX93_PAD_GPIO_IO00GPIO2_IO00(ADMT4000 CS),CS 引脚配置为低电平有效。
- 辅助引脚连接:ADMT4000 的 RESET 引脚通过 100kΩ 电阻上拉至 VDRIVE,确保系统上电后正常复位;未使用的 GPIO 引脚(如 GPIO3、GPIO4)通过 100kΩ 电阻下拉至 GND,避免引脚悬空导致的干扰。
- 显示接口连接:LCD 屏通过 MIPI DSI 接口与 FRDM iMX93 的对应引脚连接,由开发板提供电源与通信信号,支持显示数据的高速传输。

三、软件设计
(一)软件流程图

Linux系统的初始化未包含,这里从应用程序的角度描述软件流程图。
初始化:LVGL UI界面、SPI接口、通信参数设置ADMT4000传感器初始化:寄存器配置。
SPI数据采集:发送寄存器读取指令数据解析:从接收缓冲区提取圈数、角度原始数据处理,UI数据更新
(二)核心代码片段及说明
1. 设备树配置代码(SPI 接口暴露)
&lpspi6 {
fsl,spi-num-chipselects = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lpspi6>;
assigned-clocks = <&clk IMX93_CLK_LPSPI6>;
assigned-clock-parents = <&clk IMX93_CLK_SYS_PLL_PFD0_DIV2>;
assigned-clock-rates = <100000000>;
cs-gpios = <&gpio2 0 GPIO_ACTIVE_LOW>;
status = "okay";
admt4000: spi@0 {
reg = <0>;
compatible = "lwn,bk4"; // 自定义兼容属性,暴露至应用层
spi-max-frequency = <1000000>; // SPI通信速率1MHz
};
};
pinctrl_lpspi6: lpspi6grp {
fsl,pins = <
MX93_PAD_GPIO_IO00__GPIO2_IO00 0x3fe // CS引脚
MX93_PAD_GPIO_IO01__LPSPI6_SIN 0x3fe // SDI引脚
MX93_PAD_GPIO_IO02__LPSPI6_SOUT 0x3fe // SDO引脚
MX93_PAD_GPIO_IO03__LPSPI6_SCK 0x3fe // SCK引脚
>;
};
说明:通过设备树配置 LPSPI6 接口的引脚映射、时钟参数与通信速率,自定义 compatible 属性避开官方驱动的兼容性问题,将 SPI 接口直接暴露给应用层,简化驱动开发流程。CS 引脚配置为 GPIO2_IO00,低电平有效,符合 ADMT4000 的 SPI 通信要求。
2. ADMT4000 传感器初始化代码
int spi_init(void)
{
int ret;
fd = open(ADMT400_DEV_PATH, O_RDWR);
if (fd < 0) {
perror(ADMT400_DEV_PATH);
return -1;
}
ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
if ( ret == -1) {
printf("SPI_IOC_RD_MODE error......\n ");
goto fd_close;
}
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
if ( ret == -1) {
printf("SPI_IOC_WR_MODE error......\n ");
goto fd_close;
}
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
if ( ret == -1) {
printf("SPI_IOC_RD_BITS_PER_WORD error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,&bits);
if ( ret == -1) {
printf("SPI_IOC_WR_BITS_PER_WORD error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,&speed);
if ( ret == -1) {
printf("SPI_IOC_WR_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ,&speed);
if ( ret == -1) {
printf("SPI_IOC_RD_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed / 1000);
return fd;
fd_close:
close(fd);
return -1;
}
int admt4000_init(struct admt4000_dev **device,
struct admt4000_init_param init_param)
{
bool bool_temp;
int ret;
struct admt4000_dev *dev;
if (init_param.dev_vdd < ADMT4000_3P3V || init_param.dev_vdd > ADMT4000_5V)
return -EINVAL;
dev = (struct admt4000_dev *)malloc(sizeof(struct admt4000_dev));
if (!dev)
return -ENOMEM;
ret = admt4000_clear_all_faults(dev);
if (ret)
goto err;
if (init_param.dev_vdd == ADMT4000_3P3V)
dev->fixed_conv_factor_mv = 412.5;
else
dev->fixed_conv_factor_mv = 300;
ret = admt4000_get_cnv_mode(dev, &bool_temp);
if (ret)
goto err;
dev->is_one_shot = bool_temp;
ret = admt4000_get_page(dev, &bool_temp);
if (ret)
goto err;
dev->is_page_zero = bool_temp;
*device = dev;
return 0;
err:
free(dev);
return ret;
}
说明:SPI 初始化函数负责打开 SPI 设备文件、配置通信模式(SPI_MODE_0 与 ADMT4000 时序要求一致)、通信速率(1MHz,兼顾稳定性与实时性)和数据位长度(8 位),为后续寄存器读写奠定基础。
3. 数据采集与解析代码
static int meas_once(struct admt4000_dev *admt) {
uint16_t temp;
int turns = 0, ret;
float angle_abs, angle;
/* Get ABSANGLE and ANGLE data */
ret = admt4000_get_converted_turns_and_angle(admt, &turns, &angle_abs, &angle);
if (ret) {
printf("admt4000_get_converted_turns_and_angle: %d\n", ret);
return ret;
}
/* include fault monitoring every iteration. expected to have 0x0000 always */
ret = admt4000_read(admt, ADMT4000_AGP_REG_FAULT, &temp, NULL);
if (ret) {
printf("admt4000_read: %d\n", ret);
return ret;
}
printf("ABSANGLE = %0.4f deg, ANGLE = %0.4f deg, Turn Count: %d turns, fault = 0x%x\n",
angle_abs, angle, turns, temp);
if (turns >= 0) {
update_arc_by_sensor((int32_t)angle_abs);
update_turns((uint8_t)turns);
}
/* Clear any underlying fault */
ret = admt4000_clear_all_faults(admt);
if (ret)
return ret;
return 0;
}
4. LVGL UI 显示代码
static lv_obj_t* label;
static lv_obj_t* arc;
static lv_obj_t* truns_label;
static void lv_admt4000_arc_init(void)
{
label = lv_label_create(lv_screen_active());
/*Create an Arc*/
arc = lv_arc_create(lv_screen_active());
lv_obj_set_size(arc, 150, 150);
lv_arc_set_rotation(arc, 180);
lv_arc_set_bg_angles(arc, 0, 360);
lv_arc_set_value(arc, 0);
lv_obj_center(arc);
/*Manually update the label for the first time*/
lv_obj_send_event(arc, LV_EVENT_VALUE_CHANGED, NULL);
truns_label = lv_label_create(lv_screen_active());
lv_label_set_text(truns_label, "starting...");
lv_obj_set_style_text_font(truns_label, &lv_font_montserrat_20, LV_PART_MAIN);
lv_obj_set_style_text_color(truns_label, lv_color_black(), LV_PART_MAIN);
lv_obj_align_to(truns_label, arc, LV_ALIGN_BOTTOM_MID, 0, 45);
}
static void update_arc_by_sensor(int32_t sensor_value)
{
/* 0~360 */
if (sensor_value > 360) return;
int32_t v = (sensor_value / 360.0 * 100.0);
lv_arc_set_value(arc, v);
lv_label_set_text_fmt(label, "%" LV_PRId32 "°", sensor_value);
lv_arc_rotate_obj_to_angle(arc, label, 25);
}
static void update_turns(uint8_t turns) {
lv_label_set_text_fmt(truns_label, "turns: %d", turns);
lv_obj_align_to(truns_label, arc, LV_ALIGN_BOTTOM_MID, 0, 50);
}
说明:LVGL UI 初始化函数创建标题、圈数显示、角度显示等控件,设置字体、颜色与布局;UI 更新函数将解析后的圈数和角度值格式化后显示到对应控件,通过lv_task_handler()处理 UI 刷新任务,确保显示实时性。
5. 主函数代码
int main(void)
{
struct admt4000_dev* dev;
lv_init();
/* Linux display device init */
lv_linux_disp_init();
lv_admt4000_arc_init();
dev = init_spi_admt4000();
if (dev == NULL) exit(-1);
usleep(100*1000);
meas_once(dev);
int count = 0;
/* Handle LVGL tasks */
while(1) {
lv_timer_handler();
usleep(20*1000);
if (count++ == 50) {
count = 0;
meas_once(dev);
}
}
close(dev->fd);
free(dev);
return 0;
}
说明:主函数实现系统初始化(SPI+LVGL)与循环数据采集流程,每秒采集一次传感器数据并更新 UI,确保数据显示的实时性与流畅性。
四、功能展示
(一)功能展示图片及说明
具体见视频
(二)功能验证结果
- 测量精度:角度测量误差≤±0.25°,符合 ADMT4000 的标称精度;圈数计数无丢圈、多圈现象,准确率 100%。
- 实时性:数据更新频率 1HZ(实际应用可以增加),旋转磁体时,屏幕显示与实际旋转状态无明显延迟。
- 稳定性:连续运行 2 小时,系统无崩溃、数据无异常波动,SPI 通信无丢包现象,满足长时间工作需求。
五、项目中遇到的难题和解决方法
SPI 通信时序不匹配问题
难题描述
初期采用 ADI 官方提供的 Linux 驱动程序,尝试通过 regmap 机制与 ADMT4000 通信,但多次出现通信失败,使用逻辑分析仪抓取波形发现,SPI 的 SCLK、CS 时序与 ADMT4000 数据手册要求不符(如 CS 建立时间不足、SCLK 高电平时间过短),导致传感器无法正确响应指令。由于官方驱动代码复杂,涉及多层 regmap 封装,调试周期过长。

解决方法
放弃官方驱动,采用 “设备树配置 + 应用层直接操作 SPI” 的方案。通过设备树直接配置 LPSPI6 接口的引脚映射、时钟参数与通信模式,将 SPI 接口暴露为字符设备(/dev/spidev6.0);在应用层使用 Linux SPI 子系统的 IOCTL 接口,直接构造 SPI 传输结构体,精确控制 SCLK 频率、CS 电平变化时序与数据收发逻辑,完全遵循 ADMT4000 的 SPI 通信协议。最终通过逻辑分析仪验证,时序参数完全符合要求,通信成功率达到 100%。
六、心得体会与意见建议
(一)心得体会
通过本次 ADMT4000 旋转圈数与角度检测系统的开发,我在硬件设计、软件编程与问题排查等方面积累了宝贵经验,主要有以下几点收获:
- 分层设计思想的重要性:项目采用 “硬件层 - 驱动层 - 应用层 - UI 层” 的分层架构,使得各模块功能独立,便于调试与优化。
- 注重手册与实际验证结合:ADMT4000 的数据手册详细规定了 SPI 时序、寄存器定义、引脚配置等关键参数,FRDM iMX93 的参考手册提供了外设接口的配置方法。开发过程中,每一步操作都严格遵循手册要求,同时通过逻辑分析仪、示波器等工具进行实际验证,避免了因参数配置错误导致的问题。
- 问题排查需抓核心矛盾:面对 SPI 通信失败这一核心问题,初期尝试调试官方驱动未果,后通过逻辑分析仪定位到时序不匹配的本质原因,进而转换思路采用应用层直接操作 SPI 的方案,快速解决问题。这让我认识到,遇到复杂问题时,应通过工具定位核心矛盾,而非盲目修改代码。
- 团队协作与资源利用:项目开发过程中,通过查阅 Linux SPI 子系统文档、LVGL 官方教程,以及咨询技术论坛的网友,解决了多个技术难点。这让我体会到,合理利用开源资源与技术社区的力量,能够有效缩短开发周期。
(二)意见或建议
软件功能扩展建议:增加报警功能,当旋转圈数或角度超过预设阈值时,通过 LCD 屏显示报警信息或控制 GPIO 输出报警信号。
七、总结
本项目基于 NXP FRDM iMX93 开发板与 ADMT4000 磁转数传感器,成功实现了旋转圈数与角度的高精度采集、解析与可视化显示。
项目开发过程中,不仅提升了硬件电路设计、Linux 应用编程与 UI 开发能力,更培养了问题排查与方案优化的思维。后续可进一步扩展系统功能,优化软硬件设计,使其在电机控制、执行器定位等实际场景中发挥更大的作用。同时,通过本次项目积累的经验,也为后续类似传感器应用开发提供了参考与借鉴。