具有 ASEK DLL 谐波线性化功能的先进编程算法

下载 PDF 版

作者:K. Robert Bate,
Allegro MicroSystems, LLC

介绍

无论是工业自动化和机器人技术,还是电动助力转向和电机位置传感,许多应用都需要监测旋转轴(以轴上或离轴排列形式)的角度。

在设计中使用磁体时,磁场输入在整个旋转范围很可能不均匀,它存在固有误差。这些磁场输入误差会导致系统内的测量误差。线性化能减少这些输入误差。

A1332 和A1335 可采用的谐波线性化能以最多 15 种修正谐波的形式应用线性化,利用快速傅立叶变换 (FFT) 可确定它们的相位和振幅,根据磁体在角度传感器 IC 周围的一次旋转产生的数据可完成 FFT。通过使用 Allegro 提供的软件计算系数,并对片内 EEPROM 进行编程,可使用这种技术。本应用说明介绍了,当 Allegro 提供的软件不够灵活或要使用定制软件时,客户能使用的功能和处理流程。

编程要求

所有软件都是在使用 .NET 4.0 的 Microsoft Visual Studio 2010 环境下开发的。请为您要使用的器件下载命令库 (C#/.NET),并添加到其所含的 3 个 DLL 的项目参考中。

收集数据

首先,关闭所有后线性化算法处理;这包括零点偏移、后线性化旋转 (RO)、短行程反转 (IV) 和旋转晶片位元 (RD)。预线性化调节可保持开启,如 ORATE 设置、IIR 过滤器 (FI) 和预线性化旋转 (LR)。

沿角度增加的位置移动编码器。如果角度传感器 IC 的输出未相应增大,可设置 LR 位元以反转角度传感器 IC 输出的反向,或在校准时沿反方向转动编码器,在此情况下,可能需要设置后线性化旋转位元 (RO)。参阅《A1332/ A1335 编程参考》了解更多详情。

最佳收集方法是按间距相等的步数旋转目标,这样产生的数据点数量就是 2 的幂数。通常,32 或 64 个间距均匀的数据点就足够了。如果不能实现,可收集数据点,然后必须按下节介绍的方法预处理数据。

另一种收集所需数据点的方法是多次旋转目标,然后按预定义的间隔收集数据。当收集到足够的数据点覆盖目标的整个旋转范围时,接下来必须按下节介绍的方法预处理数据。

预处理数据

如果收集的数据点数量不是 2 的幂数,或者收集的数据点间距不等,必须调整数据点数组的长度并/或使它们间距相等。要对数据执行此操作,可调用 ResizePointArray 例程。

参数 x 是编码器数值的数组,参数 y 是在该编码器数值中收集的器件读数。参数 newSize 是重新调整的数组大小。如果参数 x 设置为空,则假设已按从 0 开始至 360 结束的相等间距收集数值 y。如果参数 x 不是空,则需要在调整数组大小前,为输入数组排序。

double[] ResizePointArray(double[] x, double[] y, int newSize)

此例程会在输入数组上执行三次样条插值,以采用所需的数据点数量,生成间距相等的数组。

初始处理

数据收集完毕,并形成长度为 2 的幂数的数组后,就可以计算谐波系数了。要计算谐波系数,可调用 CalculateHarmonicLinearCoefficients 例程。

HarmonicCoefficients[] CalculateHarmonicLinearCoefficients (double[] points, out bool pointError)

其输入是已收集的角度数组。此例程会执行 FFT,并会返回系数数组和一个警告标记。当一个或多个输入角比例程计算的角度大 20 度时,需要设置点误差警告标记。

以一个包含 8 个输入项的数组为例,例程计算的角度应为 [0, 45, 90, 135, 180, 225, 270, 315]。如果输入数组是 [0, 45, 90, 135, 180, 204, 270, 315],则例程会设置 pointError,因为第 6 个输入项的误差超过 20 度。

选择谐波

当所有谐波系数已计算完毕后,必须选择所需的谐波。通常,计算例程生成的谐波数量会超过器件能支持的谐波数量,所以,必须选择一些算法以选择相关的谐波。

使用谐波的数量还取决于所用的器件种类和功能。A1332 的谐波最大数量是 15,但如果使用最大值,一些可编程的功能会使用默认值,如短行程设置和特定的 I2C 与 SPI 设置。不使用默认值时,这些可编程功能的谐波最大数量是 9。A1335 的谐波最大数量是 11,但要达到此数量,一些可编程功能会使用默认值,如短行程设置。不使用默认值时,这些可编程功能的谐波最大数量是 8。

最简单的算法是按照所需的谐波数量选择第一个谐波。这种方法很简单,它选择的谐波不会对输出产生显著的影响。

Allegro A1335 的示例编程器目前使用的算法是选择振幅大于 0.3 的谐波。需要注意的是,当前软件的一个限制是在所选谐波之间只能跳过 4 个谐波。如果跳过的谐波超过 4 个,还需要选择最后一个选定谐波和所需谐波之间的所有谐波。

器件编程

谐波选择完毕后,可调用例程 GenerateHarmonicLinearizationDeviceValues 生成要写入器件的数值。

HarmonicDeviceValues[] GenerateHarmonicLinearizationDeviceValues (HarmonicCoefficients[] coefficients)

谐波系数传递到此例程中,它会返回器件编程所需的一组数值。此例程抛出的唯一异常是在所选系数之间跳过 4 个以上谐波系数的情形。

要对器件进行谐波线性化编程,必须设置 HL 标记,必须将 HAR_MAX 字段设置为要使用的系数数量,同时必须编写 HARMONIC_PHASE_n、ADV_n 和 HARMONIC_AMPLITUDE_n 字段。

代码实例

using System;
using Allegro.ASEK;

namespace HarmonicLinearizationExample
{
    public class HarmonicLinearizationExample
    {
        public HarmonicLinearizationExample()
        {
        }

        public void ProgramHarmonicLinearization(string filePath, ASEK asekProgrammer)
        {
            try
            {
                HarmonicCoefficients[] hc;
                bool pointError = false;
                double[] points = null;
                string fieldBuffer = File.ReadAllText(filePath);
                string line;
                List<double> encoderReadings = new List<double>();
                List<double> deviceReadings = new List<double>();

                // 1.1 收集数据
                // 从文本文件中读取角度。忽略空白行或以 a # 打头的行。
                if (!string.IsNullOrEmpty(fieldBuffer))
                {
                    using (StringReader sr = new StringReader(fieldBuffer))
                    {
                        while ((line = sr.ReadLine()) != null)
                        {
                            line = line.Trim();
                            if (string.IsNullOrEmpty(line) || line.StartsWith("#"))
                            {
                                continue;
                            }

                            // 每行可采用 2 种格式中的 1 种。
                            // 首行包含编码器角度,其次是逗号隔开的器件角度 (22.125,23.543)
                            // 或只有器件角度 (23.543)
                            // 如果角度的间距不等,则两个数值都需要。
                            string[] values = line.Split(‘,’);

                            if (values.Length > 1)
                            {
                                double encoder = Convert.ToDouble(values[0]);
                                while (encoder >= 360.0)
                                {
                                    encoder -= 360.0;
                                }
                                while (encoder < 0.0)
                                {
                                    encoder += 360.0;
                                }
                                encoderReadings.Add(encoder);
                                deviceReadings.Add(Convert.ToDouble(values[1]));
                            }
                            else
                            {
                                deviceReadings.Add(Convert.ToDouble(values[0]));
                            }
                        }
                    }

                    // 1.2 预处理数据
                  if (!powerOfTwo(deviceReadings.Count()))
                    {
                        // 如果数据点数量差一个,只删除最后一个,
                        if (powerOfTwo(deviceReadings.Count() - 1))
                        {
                            deviceReadings.RemoveAt(deviceReadings.Count() - 1);
                            points = deviceReadings.ToArray();
                        }
                        else
                        {
                            // 否则应计算所需的样本数量。
                            // 如果样本数量小于 64,
                            // 可四舍五入至最接近 2 的幂数的数值,否则应向下取整数。
                            int desiredSamples = 8;
                            while (desiredSamples < deviceReadings.Count())
                            {
                                desiredSamples *= 2;
                            }

                           if (deviceReadings.Count() > 64)
                            {
                                desiredSamples /= 2;
                            }

                            // 如果没有编码器读数,可假设器件读数的间距相等。
                           如果 (encoderReadings.Count() != deviceReadings.Count())
                            {
                                // 将角度列表转换为数组,然后调整其长度。
                                points = ((IHarmonicLinearization)asekProgrammer).ResizePointArray(null, deviceReadings.ToArray(), desiredSamples);
                            }
                            else
                            {
                                // 将角度列表转换为数组,然后调整其长度。
                                points = ((IHarmonicLinearization)asekProgrammer).ResizePointArray(encoderReadings.ToArray(), deviceReadings.ToArray(), desiredSamples);
                            }
                        }
                    }
                    else
                    {
                        // 将角度列表转换为数组
                        points = deviceReadings.ToArray();
                    }

                    // 1.3 初始处理
                    // 通过数据点数组计算系数。
                    hc = ((IHarmonicLinearization)asekProgrammer).CalculateHarmonicLinearCoefficients(points, out pointError);

                    // 当一个或多个入射角比例程的计算结果大 20 度时,就会出现数据点误差。
                    // 以包含 8 个数值的数组为例 [0, 45, 90, 135, 180, 204, 270, 315],计算例程会为
                    // 第 6 个输入项发放警告标记,因为它应该接近 225。
                    if (pointError)
                    {
                        MessageBox.Show("其中一个入射角比预期值大 20 度。");
                    }

                    // 1.4 选择谐波
                    // 一组谐波系数计算完毕后,需要选择系数。计算例程
                    //返回的系数数量通常会超过器件能支持的系数数量,因此需要
                    // 需要使用一些限制系数数量的方法。可以先选出前 8 个,也可使用其他方法。
                    int numberOfHarmonicComponents = hc.Length;
                    int numberOfSelectedHarmonicComponents = 0;
                    int lastHarmonicComponentSelected = 0;
                    int maxHarmonicComponentsSelected = 8; // 在影响器件的其他功能前,可使用谐波的最大数量

                    // 就此例而言,选择前 8 个振幅超过 0.3 的谐波。
                    for (int index = 0; index < numberOfHarmonicComponents; ++index)
                    {
                        if ((hc[index].amplitude > 0.3) && (numberOfSelectedHarmonicComponents < maxHarmonicComponentsSelected))
                        {
                            // 如果要选择的谐波和选择的最后一个谐波
                            // 之间的谐波数量大于 4,还需要
                            // 选择它们之间的一些谐波。
                            int skip = index - lastHarmonicComponentSelected;
                            if (skip > 4)
                            {
                                // 确保要选择的谐波数量
                                // 不超过所需的谐波数量。
                                int numberNeeded = skip / 4;
                                if ((numberNeeded + numberOfSelectedHarmonicComponents) <= maxHarmonicComponentsSelected)
                                {
                                    for (int jndex = 1; jndex <= numberNeeded; ++jndex)
                                    {
                                        hc[jndex].select = true;
                                        ++numberOfSelectedHarmonicComponents;
                                    }
                                    hc[index].select = true;
                                    ++numberOfSelectedHarmonicComponents;
                                }
                                else
                                {
                                    // 代码在选择所需的谐波时,超过
                                    // 所选系数的最大数量,因此它会停止选择。
                                    break;
                                }
                            }
                            else
                            {
                                hc[index].select = true;
                                ++numberOfSelectedHarmonicComponents;
                            }
                            lastHarmonicComponentSelected = index;
                        }
                    }

                    // 如果未选择谐波,可选择前 8 个。
                    if (numberOfSelectedHarmonicComponents == 0)
                    {
                        for (int i = 0; (i < numberOfHarmonicComponents) && (numberOfSelectedHarmonicComponents < 8); ++i)
                        {
                            hc[i].select = true;
                            ++numberOfSelectedHarmonicComponents;
                        }
                    }

                    // 1.5 器件编程
                    // 生成需要写入 eeprom 的数值。
                    HarmonicDeviceValues[] eepromValues = ((IHarmonicLinearization)asekProgrammer).GenerateHarmonicLinearizationDeviceValues(hc);

                    // 确保器件通电
                    asekProgrammer.SetVcc(5.0);

                    // 确保写入器件的 eeprom,这需要使 SRAM 具备可写性,并关停处理器
                    ((ISRAMWriteAccessMode)asekProgrammer).SetSRAMWriteAccessMode();
                    ((IProcessorMode)asekProgrammer).SetProcessorIdle();

                    // 在 eeprom 中开启谐波线性化
                    ((IRegisterAccess)asekProgrammer).WritePartialRegister(MemoryAccessType.extended, 0x306, 1, 15, 15); // HL = 1

                    // 设置要使用的谐波数量
                    ((IRegisterAccess)asekProgrammer).WritePartialRegister(MemoryAccessType.extended, 0x309, eepromValues.Length, 19, 16); // HAR_MAX = 谐波数量

                    // For the harmonics
                    for (uint index = 0; index < eepromValues.Length; ++index)
                    {
                        uint registerValue = (uint)(((eepromValues[index].phase << 12) & 0x0FFF000) +
                                                    ((eepromValues[index].advance << 10) & 0x0C00) +
                                                    (eepromValues[index].amplitude & 0x03FF));
                        ((IRegisterAccess)asekProgrammer).WriteRegister(MemoryAccessType.extended, 0x30C + index, registerValue); // HARMONIC_PHASE, ADV and HARMONIC_AMPLITUDE
                    }

                    // 关闭电源,然后再接通,确保器件使用新的线性化数值。
                    asekProgrammer.SetVccOff();
                    asekProgrammer.SetVcc(5.0);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private bool powerOfTwo(int value)
        {
            int log2npoints = 0;
            int j = value;

            while ((j > 0) && ((j & 1) == 0))      // 计算输入值的对数底数 2
            {
                log2npoints++;
                j >>= 1;
            }

            if ((value < 2) || (value != (1 << log2npoints)))
            {
                return false;
            }

            return true;
        }
    }
}

 

角度输入文件的格式

此文件包含一个角度值列表。如果有两个数值被逗号隔开,则第一个数值是编码器角度,第二个数值是器件角度。行可以是空白的,如果它们以 # 打头,则可将其视为注释。

角度输入文件实例:

329.59
354.81
6.832
13.566
17.592
20.228
22.638
24.638
25.956
27.454
28.77
30.054
30.966

包含两列时:

0,123
22.5,145.5
45,168
67.5,190.5
90,213
112.5,235.5
135,258
157.5,280.5
180,303
202.5,325.5
225,348
247.5,10.5
270,33
292.5,55.5
315,78
337.5,100.5