在嵌入式开发领域,SPI+DMA的通讯方式一直是实现高效数据传输的黄金组合。最近我在APM32F425微控制器上实现了与MT6835编码器的通讯,整个过程踩了不少坑,也积累了一些实战经验。MT6835作为一款高精度磁性旋转编码器芯片,在工业控制、机器人关节定位等场景应用广泛,而APM32F425这颗国产MCU的性能表现也让我感到惊喜。
这个项目最核心的挑战在于:如何通过SPI协议以DMA方式稳定读取MT6835的绝对位置数据。传统轮询方式会占用大量CPU资源,而中断方式在高速通讯时又容易丢帧。经过反复测试,最终实现的方案在1MHz SPI时钟下能稳定工作,CPU占用率几乎为零。下面我就把整个实现过程拆解开来,包括硬件连接、SPI配置、DMA初始化和数据处理的完整细节。
APM32F425是极海半导体推出的一款Cortex-M4内核MCU,主频高达150MHz,内置256KB Flash和64KB SRAM。我选择它的原因主要有三点:首先,它具备4个SPI接口和2个DMA控制器,完全满足我们的需求;其次,它的外设寄存器布局与STM32高度兼容,开发工具链通用;最后,国产芯片的供货和价格优势在当前环境下显得尤为重要。
MT6835是MAVET公司的高分辨率磁性编码器,14位绝对位置输出,最高支持10MHz SPI时钟。它的独特之处在于采用非接触式磁感应技术,比传统光电编码器更耐恶劣环境。在选型时我特别注意了它的工作电压范围(3.3V-5V)与APM32F425的兼容性。
实际接线时需要特别注意以下细节:
电平匹配:虽然APM32F425是3.3V器件,但它的IO口兼容5V输入。MT6835我选择在3.3V下工作,这样直接连接即可,省去了电平转换电路。
引脚分配:
抗干扰措施:
特别注意:MT6835的DI线在只读模式下其实可以悬空,但建议保留连接以便未来扩展写功能。
APM32F425的SPI外设配置与STM32非常相似,但有些细节寄存器需要特别注意:
c复制void SPI1_Init(void)
{
SPI_Config_T spiConfig;
spiConfig.clockPolarity = SPI_CLOCK_POLARITY_HIGH; // MT6835要求CPOL=1
spiConfig.clockPhase = SPI_CLOCK_PHASE_SECOND_EDGE; // CPHA=1
spiConfig.dataSize = SPI_DATA_SIZE_16BIT; // 14位数据+2位状态
spiConfig.firstBit = SPI_FIRST_BIT_MSB;
spiConfig.mode = SPI_MODE_MASTER;
spiConfig.prescaler = SPI_CLOCK_PRESCALER_16; // 150MHz/16≈9.375MHz
spiConfig.nss = SPI_NSS_SOFT; // 软件控制CS
SPI_Init(SPI1, &spiConfig);
SPI_Enable(SPI1);
}
这里有几个关键点:
DMA配置是整个项目的核心难点,APM32F425的DMA与STM32有些细微差别:
c复制void DMA_Config(void)
{
DMA_Config_T dmaConfig;
dmaConfig.bufferSize = 1; // 每次传输1个16位数据
dmaConfig.direction = DMA_DIRECTION_PERIPHERAL_TO_MEMORY;
dmaConfig.memoryBaseAddr = (uint32_t)&encoderValue;
dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
dmaConfig.memoryInc = DMA_MEMORY_INC_DISABLE;
dmaConfig.peripheralBaseAddr = (uint32_t)&SPI1->DT;
dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
dmaConfig.priority = DMA_PRIORITY_HIGH;
dmaConfig.loopMode = DMA_LOOP_MODE_ENABLE; // 循环模式
DMA_Init(DMA1_Stream0, &dmaConfig);
DMA_Enable(DMA1_Stream0);
}
特别要注意的是:
整合SPI和DMA的完整工作流程如下:
初始化阶段:
SPI_EnableDMA(SPI1, SPI_DMA_REQUEST_RX)数据采集过程:
c复制void ReadEncoder(void)
{
GPIO_WriteBit(GPIOB, GPIO_PIN_0, 0); // CS拉低
while(DMA_GetFlagStatus(DMA1_FLAG_TC0) == RESET); // 等待传输完成
DMA_ClearFlag(DMA1_FLAG_TC0);
GPIO_WriteBit(GPIOB, GPIO_PIN_0, 1); // CS拉高
// 数据处理
actualValue = (encoderValue >> 2) & 0x3FFF; // 取14位有效数据
statusBits = encoderValue & 0x0003; // 状态位
}
初期测试时发现读取的数据总是错位2位,经过逻辑分析仪抓取波形发现:
CPHA=1,同时调整CS信号的触发时机在长时间运行测试中,偶尔会出现DMA传输卡死的情况:
Delay(10)在工业现场测试时发现偶发数据跳变:
c复制#define FILTER_DEPTH 5
uint16_t filterBuffer[FILTER_DEPTH];
uint16_t FilterValue(uint16_t rawValue)
{
static uint8_t index = 0;
filterBuffer[index++] = rawValue;
if(index >= FILTER_DEPTH) index = 0;
uint32_t sum = 0;
for(uint8_t i=0; i<FILTER_DEPTH; i++) {
sum += filterBuffer[i];
}
return (uint16_t)(sum / FILTER_DEPTH);
}
经过多次迭代优化,总结出以下提升系统性能的经验:
时钟配置优化:
中断优先级设置:
c复制NVIC_SetPriority(DMA1_Stream0_IRQn, 1); // DMA中断优先级高于SPI
NVIC_SetPriority(SPI1_IRQn, 2);
内存访问优化:
__attribute__((aligned(4)))确保内存对齐实时性保障措施:
在自动化控制平台上进行了为期两周的连续测试:
测试环境:
性能指标:
异常处理方案:
这套方案最终成功应用于某型工业机械臂的关节位置检测系统,连续运行三个月无异常。相比传统方案,系统响应时间缩短了40%,CPU负载降低到原来的1/5。