
该项目涉及软件平台的设计,为开发无刷电机的电机控制器提供了良好的基础。 ![]() 介绍 在我第一次使用Ada编程语言后不久,我就了解了Make WithAda 2017比赛。而且,就像它看起来一样,它结果是一种很好的方式来深入了解我刚开始学习的语言 ada-motorcontrol项目涉及用Ada编写的BLDC电机控制器软件平台的设计。这些类型的应用程序通常需要快速运行,核心控制软件通常与微控制器外围设备紧密连接。从嵌入式系统的角度来看,以C作为参考语言,最初的担忧是Ada中的实现是否真的能够满足这些要求。 事实证明,相反,Ada非常有能力考虑这两个要求。特别是,使用Ada_Drivers_Library访问STM32上的外设实际上比使用C编写的HAL更容易使用硬件相关操作。 在整个项目中,我发现了许多Ada功能的用途。例如,表示子句功能使得从接收(以及汇总传输)串行字节流中提取数据变得简单。此外,基于合同的编程和面向对象的概念(如摘要和泛型)提供了设计简洁易用的界面和组织良好的项目的方法。 该项目的目标之一是提供一个软件平台,以帮助开发各种电机控制应用程序,核心功能不依赖于某些特定的硬件。目前它只支持自定义逆变器板,因为不幸的是我发现Ada_Drivers_Library中提供的HAL不够全面,无法支持所有使用的外围功能。但是软件的组织是为了保持驱动程序相关代码的分离。为了进行测试,我欢迎为其他逆变器板添加支持的贡献。一个好的开始将是流行的VESC板。 Ada电机控制器项目日志: 动机 电驱动技术(电池,电动机和电力电子)的最新进展导致每个成本和功率密度的输出功率越来越高。这反过来又增加了现有电机控制应用的性能,但也使一些新的 - 其中许多是diyers和制造商中的热门项目,例如电动自行车,电动滑板,悬浮板,赛格威等。 在业余爱好层面,与这些相关的安全问题大多被忽略。然而,类似应用程序的专业开发通常需要满足某些领域特定标准,例如开发过程,编码约定和验证方法。例如,电动汽车的电机控制器需要根据ISO 26262进行开发,如果使用C语言,则使用MISRA-C,它定义了一套旨在防止C语言不安全使用的编程指南。 由于Ada编程语言专为安全关键应用而设计,因此它可以是C的良好替代品,用于实现例如电动车辆应用中使用的安全电机控制器。有关MISRA-C和Ada / SPARK的比较,请参阅此报告。虽然Ada是实现功能安全的替代方案,但在原型制作过程中,错误会导致硬件损坏(烧毁电机或电力电子设备)并不罕见。去过也做过!更严格的Ada编译器可以防止此类事故。 此外,虽然Ada不是一种特别“新”的语言,但它包含了现代语言所期望的更多功能,而不是由C提供的功能。例如,使用指定范围定义的类型,允许在编译期间进行值范围检查,以及内置的多任务处理功能。Ada还非常支持模块化,例如允许轻松集成新的控制接口- 这可能是将控制器用于新应用程序时最可能需要的更改。 该项目应包括并履行: 用于控制无刷直流电机的核心软件,主要针对业余爱好者和制造商。 支持传感器和无传感器操作。 开源软件(和硬件)。 在sta32f4xx的Ravenscar运行时之上的Ada中实现。 移植到另一个微控制器应该不会太困难。 特别是,对于那些想要了解电机控制细节或者扩展其他功能的人: 提供基本,清晰和可读的实现。 简短但有用的文档。 有意义且灵活的日志记录。 易于添加新的控制接口(例如CAN,ADC,蓝牙等)。 硬件 将用于此项目的电路板是我之前设计的定制电路板,旨在获得电机控制方面的实际操作知识。它是完全开源的,所有项目文件都可以在GitHub 上找到。 微控制器STM32F446,ARMCortex-M4,180 MHz,FPU 功率MOSFET 60 V. 内联相电流检测 PWM / PPM控制输入 位置传感器输入作为任一大厅或正交编码 电机和电路板温度传感器(NTC) UART / ADC / DAC / SPI / I2C / CAN的扩展接头 它可以按照电动滑板或电动自行车所需的顺序处理功率范围,具体取决于所使用的电池电压和冷却。 还有其他类似规格的逆变器板。一个非常受欢迎的是本杰明韦德的VESC 。将该项目移植到该板上也可能并不困难。 ![]() 粗糙的项目计划 我认为写下几件需要做的子弹是合适的。该名单可能会增长...... 在自定义板上为stm32f446目标创建Ravenscar运行时的端口 将stm32f446添加为Ada驱动程序库中的设备 获取某种hello world应用程序运行以显示这些东西是有效的 调查并试验有关开销的中断处理 为所有使用的mcu外设创建初始化代码 绘制整体软件架构并定义接口 履行 文档... 支持STM32F446 将用于该项目的微处理器是STM32F446。在当前版本的Ada驱动程序库和可用的Ravenscar嵌入式运行时中,没有明确支持此设备。幸运的是,它与stm32f4系列中的其他处理器非常相似,因此一旦我理解了存储库的结构,添加对stm32f446的支持并不是很困难。我分叉了这些并将它们作为子模块添加到这个项目的repo中。 与发现板使用的Ravenscar运行时相比,外部振荡器频率,可用中断向量和存储器大小存在差异。否则它们基本相同。 创建新驱动程序和运行时变体所需的重要工具是svd2ada。它基于svd文件(基本上是xml)在ada中生成设备规范和正文文件,该文件描述了存在哪些外设,寄存器的外观,地址,现有中断以及类似内容。它易于使用,但在生成驱动程序和运行时文件时应该如何设置标志/开关有点混乱。经过一些追踪和错误,我认为我做对了。我创建了一个Makefile,用于使用正确的开关生成所有这些文件。 我无法直接从ST找到stm32f446的svd文件,但在互联网上找到了一个。虽然不是很完美。一些使用生成的数据类型的源代码似乎对这些类型的结构做出了假设。根据svd文件的外观,svd2ada可能会或可能不会以预期的方式生成它们。svd文件中还有其他丢失和不正确的数据,因此我必须手动更正这些。可能还有其他问题我还没有找到...... 它还活着! 我做了一个非常简单的应用程序,包括一个周期性延迟的任务,并在每次任务恢复时切换板上的两个LED。LED会在预期的时间内切换,因此振荡器似乎正确初始化。 接下来我需要将不同的mcu引脚映射到相应的硬件功能,并尝试正确初始化所需的外设。 控制算法及其外围设备的使用 有几种控制无刷电机的方法,每种方法都有一个特定的用例。作为第一种方法,我将实现感知FOC,其中用户请求当前值(或扭矩值)。 为简化起见,此方法可分为以下步骤,每个PWM周期重复一次(通常约为20 kHz): 采样相电流 将值转换为转子固定参考系 根据请求的电流,计算一组新的相电压 转换回定子的参考系 计算PWM占空比以创建计算的相电压 幸运的是,stm32f446的外设具有许多功能,使这更容易实现。例如,可以直接从驱动PWM的定时器触发ADC。这样,采样将自动与PWM周期同步。因此,当ADC触发相应的转换完成中断时,可立即开始上述步骤1。实际上,许多现有实现在ISR内完全执行所有步骤1到6。这样做的原因只是为了减少任何不必要的开销,因为执行的计算有点冗长。请求的电流通过全局变量传递给ISR。 我想以传统的方式做到这一点,即在ISR中花费尽可能少的时间并触发单独的任务来执行所有计算。采样的电流值和请求的电流应通过受保护的对象传递。所有这些当然会产生更多的开销。也许太多了?需要调查。 ![]() PWM和ADC启动并运行 我花了一些时间使用Ada驱动程序库配置PWM和ADC外设。总而言之,它进展顺利,但我不得不对驱动程序进行一些较小的更改,以便能够配置我想要的方式。 PWM是互补输出,中心对齐频率为20 kHz PWM通道1至3产生相电压 PWM通道4用于触发ADC,这样就可以设置采样应在PWM周期内的位置 默认情况下,采样发生在正波形的中间(V7) 三个ADC配置为三重多模式,这意味着它们是同步的,以便同时对每个采样相位数进行采样。 相电流和电压a,b,c被映射到由PWM通道4触发的注入转换 电路板温度和总线电压映射到由14 kHz定时器触发的常规转换 转换完成后,定期转换将自动使用DMA移动到易失性阵列 注入的转换完成后,ADC会产生中断 驱动程序总是假设PWM输出映射到某个GPIO,因此为了配置触发通道,我必须向驱动程序添加一个新程序。此外,没有为我的配置正确设置的ADC的扫描模式,以及注入的序列顺序的配置是完全错误的。我将发送一个pull请求以将这些更改与master分支合并。 中断开销/延迟 如前面的帖子所述,用于中断处理的结构是在中断上下文中花费最少的时间并发出等待任务的信号以执行计算,该计算在完全启用中断的软件优先级执行。另一种方法是将所有代码放在中断上下文中。 此Ada Gem 及其后续部分描述了执行此类任务同步的两种不同方法。两者都使用受保护的过程作为中断处理程序,但以不同的方式发出等待任务的信号。第一个使用入口屏障,第二个使用Suspension Object。使用入口屏障的习语具有以下优点:它可以将数据作为信令的集成部分传递,而Suspension Object的行为更像是二进制信号量。 对于ADC转换完成中断,我测试了两种方法。用作ISR的受保护过程读取由六个uint16组成的转换值。对于入口障碍方法,这些方法使用out-parameter传递给任务。使用第二种方法时,任务需要使用受保护对象中的单独函数来收集样本数据。 在此上下文中的开销我定义为ADC生成中断的时间,以及事件触发任务开始运行的时间。这首先包括一个isr-wrapper,它是运行时的一部分,然后调用已安装的受保护过程,第二个是受保护过程的执行时间,它读取采样数据,最后是发送给等待任务的信号。 我通过在保护过程开始时直接将引脚设置为高电平来测量开销的近似值,然后在信号发出后直接唤醒时由等待任务直接降低。对于Suspension Object情况,在读取数据函数调用之后将引脚设置为低,即对于将采样数据复制到任务的两种情况。代码是使用-O3标志编译的。 第一个成语导致~8.4 us的开销,第二个~10 us。这应该与PWM的周期进行比较,在20 kHz时为50 us。显然,开销是不可忽略的,所以我可能会考虑使用更常用的方法来实现在中断环境中使用当前控制算法的电机控制应用。但是,在算法的执行时间已知之前,将假定进入障碍方法...... 注意:“开销”可能是错误的术语,因为我不知道在测量的时间内cpu是否真的很忙。否则我想它应该被称为延迟… ![]() 紫色:中心对齐的PWM在50%的负载下,ADC触发在正波形的中心。黄色:如上所述的Pin状态。高意味着开销/延迟时间。 参考框架 FOC算法的主要优点是实际控制在固定到转子的参考系中执行。这样,假定稳态操作,在定子的参考系中看到的正弦三相电流将表示为两个DC值。使用的变换(Clarke和Park)要求转子和定子之间的角度是已知的。作为第一步,我正在使用正交编码器,因为由于stm32的硬件支持,它提供了非常精确的测量和非常低的开销。 已经定义了三种类型,每种类型代表一个特定的参考框架:Abc,Alfa_Beta和Dq。使用上面的变换可以简单地写: declare Iabc :Abc; -- Measured current (stator ref) Idq ![]() Vdq ![]() Vabc :Abc; -- Calculated output voltage(stator ref) Angle : constant Float := ...; begin Idq := Iabc.Clarke.Park(Angle); -- Do the control... Vabc := Vdq.Park_Inv(Angle).Clarke_Inv; end; 请注意,Park和Park_Inv都使用相同的角度。确切地说,它们都使用Sin(角度)和Cos(角度)。现在,首先,我通过让每个变换在本地计算Sin和Cos来简单地实现这些。当然,这对于这个特定的应用来说是浪费。相反,我定义了一个角度对象,在创建时也计算角度的Sin和Cos,并添加了变换版本以使用这些“预先计算”的值。 declare -- Same... Angle : constant Angle_Obj := Compose (Angle_Rad); -- Calculates Sin and Cos begin Idq := Iabc.Clarke.Park(Angle); -- Do the control... Vabc := Vdq.Park_Inv(Angle).Clarke_Inv; end; 这有点缩短了执行时间(不像我想象的那么多),因为三角函数是重要部分。使用基于查找表的版本而不是Ada.Numerics提供的版本可能会更快...... 它旋转! 现在控制器的主要结构已经到位。当按下板上的按钮时,通过迫使转子达到已知角度,传感器与转子对齐。目前,要求的q电流由电位计设定。 截至目前,它肯定没有正确调整,但它至少表明通用算法正在按预期工作。 为了使这个项目更容易开发,无论是对我自己还是其他任何用户,我都需要添加一些日志记录和调优功能。这应该允许用户在控制器运行时更改和/或记录应用程序中的变量(例如控制参数)。我之前已经编写了一个用于执行此操作(通过串行)的工具,但之后在C中编写。在Ada中重写它会很有趣。 基于合同的编程 到目前为止,我还没有使用过这个功能。但是在为日志记录功能编写代码时,我很适合它。 我正在使用Consistent Overhead Byte Stuffing(COBS)来编码通过uart发送的数据。无论数据包内容如何,这种编码都可以实现明确的数据包成帧,从而使接收应用程序很容易从错误的数据包中恢复。数据包由分隔符分隔(在这种情况下为0),这使得接收解析器的同步变得容易。编码确保编码数据包本身不包含分隔符值。 COBS的一个很好的特性是,假设原始数据长度小于254,那么由于编码引起的开销总是恰好是一个字节。我当然可以简单地将这个事实写成对编码/解码函数的注释,允许用户做出这个假设以简化他们的代码。更好的方法是将此条件写为合同。 Data_Length_Max: constant Buffer_Index := 253; function COBS_Encode (Input : access Data) return Data with Pre => Input'Length <= Data_Length_Max, Post => (if Input'Length > 0 then COBS_Encode'Result'Length = Input'Length + 1 else Input'Length = COBS_Encode'Result'Length); function COBS_Decode (Encoded_Data : access Data) return Data with Pre => Encoded_Data'Length <= Data_Length_Max+ 1, Post => (if Encoded_Data'Length > 0 then COBS_Decode'Result'Length = Encoded_Data'Length - 1 else Encoded_Data'Length = COBS_Decode'Result'Length); 记录和调整 我只是让日志和调优功能正常工作。这是一个使用我之前的项目Calmeas 使用的协议的Ada实现。它使用户能够实时记录和更改应用程序中变量的值。在开发调试器在目标运行时没有读写内存功能的系统时,这非常有用。 数据通过uart发送和接收,由COBS编码。uart和cobs包的接口实现了抽象流类型,这意味着将uart更改为其他媒体非常简单,例如,如果需要,可以跳过cobs。 例子 用户可以简单地执行以下操作以获取变量V_Bus_Log可记录和/或可调: V_Bus_Log : aliased Voltage_V; ... Calmeas.Add(Symbol => V_Bus_Log'Access, Name => "V_Bus", Description => "Bus Voltage [V]"); 它适用于大小为8,16和32位的(un)有符号整数,以及浮点数。 添加几个变量后,将目标连接到gui: ![]() 例如,这可用于调整当前控制器的收益: ![]() 正如预期的那样,当增益增加时,实际电流更接近参考 截至目前,调整不是以“安全”的方式进行的。添加符号的写入由名为Logger的单独任务完成,只需对添加的符号的地址进行未经检查的写入,一次一个字节。同时,应用程序正在从具有更高prio的另一个任务中读取符号的值。最好的方法是通过受保护的类型传递值,但由于调优主要用于调试目的,我将在以后以正确的方式进行... 请注意,主机GUI不是用Ada(但是Python)编写的,并且本身不是该项目的一部分。 架构概述 这是一个显示软件概述的图: ![]() 摘要 该项目涉及软件平台的设计,为开发无刷电机的电机控制器提供了良好的基础。它包括一个基本但清晰可读的传感器场控制算法实现。其中包括一个日志记录功能,可以简化开发并允许用户可视化正在发生的事情。该项目表明,Ada可以成功用于需要快速执行的裸机项目。 由于Ada的许多不错的功能,与许多其他C实现相比,设计更容易理解,在最坏的情况下,一切都在一个ISR中完成。增加的设计可读性和Ada的严格性使得最终的软件更安全,并简化了进一步的协作开发和重用。 已完成的一些重点: 使用STM32F446将Ravenscar配置文件移植到定制板上 添加对STa32F446的支持到Ada_Drivers_Library项目 向Ada_Drivers_Library添加一些功能,以便充分利用所有外围功能 修复Ada_Drivers_Library中与更高级的ADC使用相关的错误 写入HAL-isch软件包,以便轻松移植到除STM32之外的其他设备 编写通信包和定义的接口,以便更容易添加控制输入。 编写一个日志包,允许开发人员实时调试,记录和调整应用程序。 使用感应磁场定向控制实现基本控制器 使用生成的html版本记录良好的规范 未来的计划: 添加霍尔传感器支持和6步块换向 添加无传感器操作 添加CAN支持(pcb目前没有收发器) SPARK证明 写一些显示如何使用接口的其他示例。 将软件移植到流行的VESC板上。 相关文件 ADA-motorcontrol---
![]() |
好资料,谢谢分享