你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

SuperIMU Circle - AdaIMU - 13x 6DoF IMU BMI088传感器

[复制链接]
点点&木木 发布时间:2019-3-12 23:33
本帖最后由 点点&木木 于 2019-4-12 23:57 编辑


硬件组件
STM32 Nucleo-F746ZG  ×    1      
博世BMI088   ×          13     
跳线(通用)     ×  1      

手动工具和制造机器
烙铁(通用)         
焊料空气流动站
信号分析仪


介绍
在这个项目中,我描述了我在Ada中的小型奥德赛,用于嵌入式ARM从头开始创建冗余IMU(使用多个6DoF陀螺仪/加速传感器)和软件环境来控制它。
我还将向您展示我用来达到目标​​的工具和方法(调试,分析器......)来估算一组6DoF IMU的音高和滚动:

file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png 1.jpg



局限性:
在本教程中,我没有描述控制火箭副翼或如何减少偏航旋转的PID过程,而只描述如何获取数据并估算滚转和俯仰。工作正在进行中,将在单独的教程中进行描述。

动机:
对于正在进行的业余火箭项目,我们需要一个能够容忍各种加速度(和陀螺仪)的IMU,而无需在飞行范围内重新配置。此外,我们希望有一个多IMU板用于可靠性和过滤目的。
出于这个原因,我设计了一个冗余的BMI088(13x IMU),试图在高振动环境中获得最佳结果,一个业余火箭和高精度无人机。
在我们的使用案例中,它将被放置在如下的火箭中,连接到托管微控制器的板,该微控制器能够控制和处理来自传感器的数据。

2.jpg


使用AdaCore框架和工具链将使我们能够从他们提供的强大编码环境中受​​益(在安全/可靠性导向的行业中得到高度认可,如航空航天和运输)。
我注意到在代码实现过程中,Ada代码,特别是在AdaCore GitHubrepo中编辑的代码很容易阅读。对于像我这样习惯于C /C ++和java环境的人来说,使用Ada并不是特别难。

语境:
SuperIMU已被开发用于CNRS PlaneteScience的业余火箭队。它将有助于在高速和变化的振动环境中计算精确俯仰/滚转,以在飞行期间使用副翼稳定火箭。

3.jpg
火箭的第一个目标

SuperIMU必须嵌入芯径小于90mm中。

4.jpg


第1步:系统描述
SuperIMU具有13倍冗余6DoF IMU,可以直径50mm。它将使用SPI接口与微控制器通信,以获取原始数据(加速度和陀螺仪数据)。

5.jpg
板载配置

我们使用简单的架构来可视化和测试SuperIMU。
微控制器使用SPI端口从SuperIMU过滤原始数据(加速度,角度速率),并估算角度。出于测试目的,它使用UART端口发送角度值。

6.jpg
测试configuartion

UART端口连接到计算机,以串行到USB模块可视化数据。
该项目的下一步是为SuperIMU创建一个特定的主机板。主机板将是圆形的,并嵌入STM32F746微控制器。
IMU按以下方式分布,呈星形:

7.jpg
角度分布遵循45°模式:

8.jpg

选择了先前的模式以最大化表面上IMU的存在。根据在德克萨斯大学(RedundantIMU Configurations:paper)进行的几何研究,其中测试了不同的几何配置:

传感器的3D空间中的几何分布比计划中的分布更好的结果(我们的情况)
分布对加速度没有影响(aDOP Accelaration Dilution of Precision)
分布对旋转精度有影响(wDOP角精度稀释)
我们添加的IMU越多,我们的精确度就越高
IMU的方向对结果没有显着影响

9.jpg

我们记住这个研究,为了达到像配置这样的立方体,我们可以将两个SuperIMU并行叠加。目前我们继续使用UnitCircle配置,尝试从中获取最大值。

10.jpg

灵敏度范围配置:火箭模式
在业余火箭中,在飞行的最初几秒内,加速度可以达到8到9克。我们在滑槽开口和落地时观察到了一阵骚动。
这是一个典型的飞行日志(加速度/速度)的业余火箭与三个阶段(推进,上升无推进,滑槽开放,潜水与滑槽,地面接触):
11.jpg

在短途飞行期间,典型的加速曲线如下:
发射<7秒:加速度> 6g
在降落伞开启前<11s,无推进上升:<3g
用降落伞潜水<50s:<3
12.jpg

在飞行期间,感应范围需要容忍低和高,以无缝地进行精确控制。IMU的范围将在发布前配置。目前我将尝试四种不同的配置,当然这些配置不是确定的,它们需要通过实验进行调整:
13.jpg

IMU分布,范围和名称
在飞行期间,将根据配置的范围忽略传感器原始值:
14.jpg

过滤过程
在开始使用它们的俯仰/滚转估计之前,根据它们的范围过滤来自IMU的采集数据(加速度,角速度)。
范围过滤器:
过滤器获取原始数据,并根据每个IMU的配置范围,仅输出低于阈值的值:配置范围的95%。
范围滤波器还根据配置的范围提供每个测量数据的权重。权重用于取消高范围值的影响,它由平均过滤器使用。
权重具有以下值:0,1 / 2,1 / 4,1 / 8。
例如,如果IMU的范围是3G并且测量的加速度是2.9G,则范围滤波器输出来自该IMU的值为0的权重。
15.jpg

例如,如果测量的加速度<1G,则最准确的IMU是配置有3G范围的IMU。3G IMU的重量为1,6G IMU的重量为1/2,12G IMU的重量为1/4,24G IMU的重量为1/8。
权重根据下表计算:
16.jpg

例外,当测量数据> 24%> 95%时,它不会为零加权。它会避免产生价值。
范围滤波器的输出值是每个IMU的四倍(加速度,加速度,陀螺仪,陀螺仪等)。

平均过滤器:
该过滤器根据每个重量计算平均值。当权重为空时,不考虑测量值。
平均滤波器输出滤波数据,该数据是3轴的耦合(accel_filtered-xyz,gyro_filtered-xyz)。

滚动/间距估算器:
然后使用经过滤的数据来估计滚转角和俯仰角。使用众所周知的互补滤波器来估计飞行角度。
17.jpg

TimeSampling
目标st具有估计角度@ 1ms的采样率。这意味着我们需要测量所有13xIMU,在1ms的时间范围内进行滤波和补充估计。
第2步:使用硬件
IMU传感器BMI088
使用的传感器是BMI088,它专为无人机和机器人设计:
它可以承受24g的加速度。
SPI运行速度最高为10Mhz。
分辨率:0.09mg,0.004°/ s
低TCO为0.2 mg / K,低光谱噪声仅为230 pg / sqrt(Hz)最大值
输出数据速率:1.6kHz - 每个加速度为0.6ms - 陀螺仪最大为2000Hz - 每个0.5ms
Nucleo-F746ZG的描述:
核板最有趣的是它们嵌入了ST Link V2-1编程接口以允许调试(例如使用OpenOCD或St-Util)。
它运行@ 216Mhz,非常适合需要快速采集和计算以获得最佳采样时间的用例。
您只需使用micro usb电缆将电路板连接到计算机。对于我的情况,我使用Ubuntu 18.x,自动检测到电路板,无需安装任何驱动程序。

18.jpg

Nucleo主板的另一个有趣之处在于它们与ArduinoUno标头保持兼容。在下面以绿色(D0 ... D13)呈现。
19.jpg

资料来源:https://os.mbed.com/platforms/ST-Nucleo-F746ZG/

由于我们需要在读取BMI088传感器(13x IMU)时将延迟降至最大,因此我们使用SPI端口获取Accel和Gyro数据(在这种情况下,I2C不适用)。

对于代码,我们将使用STM32F746的SPI1:
SCK:PA_5
MISO:PA_6
MOSI:PA_7
Super IMU的描述:
SuperIMU基于BMI088,它使用SPI接口配置和获取IMU的数据。从数据表中,用户电路如下:
20.jpg

在SuperIMU的情况下,没有使用中断,它按顺序获取数据。ACCEL和GYRO的SDO在电路板中输出相同的输出。
我必须单独选择每个Gyro和Accel。
21.jpg

AdaIMU BMI088 Circle(SuperIMU)的原理图
SuperIMU的设计可以使用单个SPI获取数据(下一个版本将使用多个SPI),它具有13x BMI088:
22.jpg

这是它的样子。我需要焊接接头以连接杜邦线:
23.jpg
在后面有引脚的描述,SPI(SDO / SCK / SDI)以及用于选择陀螺仪和引脚的引脚:
24.jpg


25.jpg


26.jpg
完成焊接后的样子如下:
27.jpg
La pieuvre:
28.jpg


29.jpg
SuperIMU使用3v3电源和SPI端口1连接到Nucleo板。
30.jpg


GPIO引脚用于选择Gyro,13x IMU的Accel将按如下方式完成:
31.jpg


用于选择IMU的陀螺仪/加速度计的GPIO引脚的定义:
-- IMU select
  IMU1_SPI1_SELC    : GPIO_Point renames PC8;
  IMU1_SPI1_SELG    : GPIO_Point renames PC9;
  IMU2_SPI1_SELC    : GPIO_Point renames PC10;
  IMU2_SPI1_SELG    : GPIO_Point renames PC11;
  IMU3_SPI1_SELC    : GPIO_Point renames PC12;
  IMU3_SPI1_SELG    : GPIO_Point renames PD2;
  IMU4_SPI1_SELC    : GPIO_Point renames PG2;
  IMU4_SPI1_SELG    : GPIO_Point renames PG3;
  IMU5_SPI1_SELC    : GPIO_Point renames PD7;
  IMU5_SPI1_SELG    : GPIO_Point renames PD6;
  IMU6_SPI1_SELC    : GPIO_Point renames PD5;
  IMU6_SPI1_SELG    : GPIO_Point renames PD4;
  IMU7_SPI1_SELC    : GPIO_Point renames PD3;
  IMU7_SPI1_SELG    : GPIO_Point renames PE2;
  IMU8_SPI1_SELC    : GPIO_Point renames PE4;
  IMU8_SPI1_SELG    : GPIO_Point renames PE5;
  IMU9_SPI1_SELC    : GPIO_Point renames PE6;
  IMU9_SPI1_SELG    : GPIO_Point renames PE3;
  IMU10_SPI1_SELC   : GPIO_Point renames PF8;
  IMU10_SPI1_SELG   : GPIO_Point renames PF7;
  IMU11_SPI1_SELC   : GPIO_Point renames PF9;
  IMU11_SPI1_SELG   : GPIO_Point renames PG1;
  IMU12_SPI1_SELC   : GPIO_Point renames PA3;
  IMU12_SPI1_SELG   : GPIO_Point renames PC0;
  IMU13_SPI1_SELC   : GPIO_Point renames PC3;
  IMU13_SPI1_SELG   : GPIO_Point renames PF3;
  IMU_SPI1_SEL_Points : constant STM32.GPIO.GPIO_Points := (IMU1_SPI1_SELC,
                                                            IMU1_SPI1_SELG,
                                                            IMU2_SPI1_SELC,
                                                            IMU2_SPI1_SELG,
                                                            IMU3_SPI1_SELC,
                                                            IMU3_SPI1_SELG,
                                                            IMU4_SPI1_SELC,
                                                            IMU4_SPI1_SELG,
                                                            IMU5_SPI1_SELC,
                                                            IMU5_SPI1_SELG,
                                                            IMU6_SPI1_SELC,
                                                            IMU6_SPI1_SELG,
                                                            IMU7_SPI1_SELC,
                                                            IMU7_SPI1_SELG,
                                                            IMU8_SPI1_SELC,
                                                            IMU8_SPI1_SELG,
                                                            IMU9_SPI1_SELC,
                                                            IMU9_SPI1_SELG,
                                                            IMU10_SPI1_SELC,
                                                            IMU10_SPI1_SELG,
                                                            IMU11_SPI1_SELC,
                                                            IMU11_SPI1_SELG,
                                                            IMU12_SPI1_SELC,
                                                            IMU12_SPI1_SELG,
                                                            IMU13_SPI1_SELC,
                                                            IMU13_SPI1_SELG);
  IMU_SPI1_Accel_SEL_Points : array (1 .. 13) of access GPIO_Point'Class := (
                                                            IMU1_SPI1_SELC'Access,
                                                            IMU2_SPI1_SELC'Access,
                                                            IMU3_SPI1_SELC'Access,
                                                            IMU4_SPI1_SELC'Access,
                                                            IMU5_SPI1_SELC'Access,
                                                            IMU6_SPI1_SELC'Access,
                                                            IMU7_SPI1_SELC'Access,
                                                            IMU8_SPI1_SELC'Access,
                                                            IMU9_SPI1_SELC'Access,
                                                            IMU10_SPI1_SELC'Access,
                                                            IMU11_SPI1_SELC'Access,
                                                            IMU12_SPI1_SELC'Access,
                                                            IMU13_SPI1_SELC'Access
                                                           );
  IMU_SPI1_Gyro_SEL_Points : array (1 .. 13) of access GPIO_Point'Class := (
                                                            IMU1_SPI1_SELG'Access,
                                                            IMU2_SPI1_SELG'Access,
                                                            IMU3_SPI1_SELG'Access,
                                                            IMU4_SPI1_SELG'Access,
                                                            IMU5_SPI1_SELG'Access,
                                                            IMU6_SPI1_SELG'Access,
                                                            IMU7_SPI1_SELG'Access,
                                                            IMU8_SPI1_SELG'Access,
                                                            IMU9_SPI1_SELG'Access,
                                                            IMU10_SPI1_SELG'Access,
                                                            IMU11_SPI1_SELG'Access,
                                                            IMU12_SPI1_SELG'Access,
                                                            IMU13_SPI1_SELG'Access
                                                            );




第3步:软件设置/ IDE
使用Adacore GPS IDE
我从Adacore网站安装了GPS IDE:gnat-community-2018-20180528-x86_64-linux-bin,它有一个很酷的编辑器,包括debuging界面和Ada和SPARK的所有检查环境。
我从Adacore网站安装了Arm-ELF编译器:gnat-community-2018-20180524-arm-elf-linux64-bin,它为ARM(以及bb-runtimes)提供编译器和构建器工具链。

使用项目的代码
要使用该软件,您需要克隆此分支http://github.com/LaetitiaEl/Ada_Drivers_Library,此存储库基于Ada驱动程序库:AdaCore /Ada_Drivers_Library

使用./GNAT/2018/bin/gps 打开项目文件:Ada_Drivers_Library / examples /STM32F746_Nucleo / SuperIMU.gpr
你编译/上传,这就是全部!

该项目针对STM32F746,如果需要可以轻松移植到STM32F4(通过github或黑客联系我寻求帮助)。

第4步:首先进行眨眼测试!
在继续之前,我需要确保可以正确编程电路板。

我保留了现有文件:blinky_f7disco.gpr,并修改了以下路径。继续使用stm32f746_discovery_full.gpr没问题

with "../../boards/stm32f746_nucleo/stm32f746_discovery_full.gpr";
blinky_f7disco使用以下主条目:
Ada_Drivers_Library/examples/shared/hello_world_blinky/src/blinky.adb

blinky的内容如下:
with STM32.Board;   use STM32.Board;
with STM32.GPIO;    use STM32.GPIO;
with Ada.Real_Time; use Ada.Real_Time;
procedure Blinky is
  Period : constant Time_Span := Milliseconds (200);  -- arbitrary
  Next_Release : Time := Clock;
begin
  STM32.Board.Initialize_LEDs;
  loop
     Toggle (All_LEDs);
     Next_Release := Next_Release + Period;
     delay until Next_Release;
  end loop;
end Blinky;



似乎这个代码对所有主板都是通用的。使用GPS IDE我遵循All_LED的定义,它定义于:
Ada_Drivers_Library/boards/stm32_common/stm32f746disco/stm32-board.ads

值All_LEDs是索引为1的集合,指向PI1:
  Green_LED : User_LED renames PI1;
  LED1      : User_LED renames Green_LED;
  LCH_LED   : User_LED renames Green_LED;
  All_LEDs : GPIO_Points := (1 => Green_LED);


在Nucleo板上没有LED连接到PI1,找出我可以使用的输出,简单的方法是检查核板的原理图http://www.st.com/resource/en/sc ... leo_144pins_sch.zip

32.jpg

我把蓝色LED连接到PB7。然后代码更新为蓝色:Blue_LED : User_LED renames PB7;
  LED1      : User_LED renames Blue_LED;
  LCH_LED   : User_LED renames Blue_LED;
  All_LEDs : GPIO_Points := (1 => Blue_LED);




然后使用上传按钮上传代码(重要的是您需要配置上传/调试,在我的情况下,我使用OpenOCD,如下所述)
33.jpg
代码编译得很好,正确上传并且蓝色LED闪烁:
34.jpg

让我们开始认真的工作。为BMI088制作SPI驱动程序。

第5步:为BMI088制作驱动程序
很酷的是,Ada_Drivers_Library已经在“components / src / motion”目录中提供了一些SPI IMU示例。
我只需要使用l3gd20来启动bmi088的驱动程序以获得灵感。
35.jpg
要正确选择寄存器和协议特性,请使用以下资源: - BST-BMI088-DS001- officiel C驱动程序:有助于获得时间
有关信息,现有驱动程序的演示位于路径中:Ada_Drivers_Library/ arch / ARM / STM32 / driver_demos /
更新板定义:定义SPI1在STM32.Board包体中的文件stm32-board.ads中,我们添加:
  ---------------------------------
  --  SPI1 / IMU BMI088 example  --
  ---------------------------------
  -- SPI Port
  IMU_SPI : SPI_Port renames SPI_1;
  -- SPI IOs
  SPI1_SCK     : GPIO_Point renames PA5; -- D13 in connector CN10
  SPI1_MISO    : GPIO_Point renames PA6; -- D14 in connector CN10
  SPI1_MOSI    : GPIO_Point renames PA7; -- D15 in connector CN10
  -- IMU select
  IMU_SEL1    : GPIO_Point renames PG9; -- D0 in connector CN10
  -- To initialize the SPI1 port for the BMI088
  procedure Initialize_SPI1_For_BMI088;


STM32F7中使用8SPI的技巧
BIM088与8位通信非常重要。但是数据tx / rx DR寄存器是16位,默认情况下它发送16位。首先写入/读取:NOK
36.jpg

根据参考手册,诀窍是将寄存器DS(数据大小)配置为4位,并正确格式化DR寄存器中写入的数据。
37.jpg

这就是为什么我更新了SPI(stm32-spi.adb)的默认驱动程序,如下面的写/读8位模式。

procedure Configure (This : in out SPI_Port; Conf : SPI_Configuration) is
  begin
...
    This.Periph.CR1.DFF     := Conf.Data_Size = HAL.SPI.Data_Size_16b;
     This.Periph.CR2.DS      := STM32_SVD.SPI.Size_4Bit; -- add this line
     This.Periph.CR1.CPOL     := Conf.Clock_Polarity = High;
...
procedure Send_8bit_Mode
    (This     : in out SPI_Port;
     Outgoing : HAL.SPI.SPI_Data_8b)
  is
     Tx_Count : Natural := Outgoing'Length;
     Index    : Natural := Outgoing'First;
     Tmp : UInt16;
  begin
     if Current_Mode (This) = Slave or else Tx_Count = 1 then
        Tmp := UInt16 ((Outgoing (Index) and 16#0F#))*256 + UInt16((Outgoing (Index) and 16#F0#))/16;
        This.Periph.DR.DR := Tmp;
        Index := Index + 1;
        Tx_Count := Tx_Count - 1;
     end if;
...
procedure Receive_8bit_Mode
    (This     : in out SPI_Port;
     Incoming : out HAL.SPI.SPI_Data_8b)
  is
     Generate_Clock : constant Boolean := Current_Mode (This) = Master;
     Tmp : Uint16;
  begin
     for K of Incoming loop
        if Generate_Clock then
           This.Periph.DR.DR := 0;
        end if;
        while Rx_Is_Empty (This) loop
           null;
        end loop;
        Tmp := This.Periph.DR.DR;
        K := UInt8 (Tmp and 16#000F#)*16+ UInt8 ((Tmp and 16#0F00#)/256);
...





这样我获得了更好的时钟,BMI088寄存器中的读/写在8位模式下工作正常:
38.jpg

但还是不行:
因为我们需要在读取加速度计时(不是陀螺仪的情况)总是丢弃BMI088中的第一个接收字节。这就是为什么每次我进行读取时,我都必须先读取虚拟字节。通过此更新,我获得了更好的结果。

在BMI088.adb中:
  ----------
  -- Read --
  ----------
  procedure Accel_Read
    (This : Six_Axis_Imu;
     Addr : Register;
     Data : out UInt8)
  is
     Status : SPI_Status;
     Tmp_Data : SPI_Data_8b (1 .. 1);
  begin
     SPI_Mode (This, Enabled => True);  -- select the chip
     This.Port.Transmit (SPI_Data_8b'(1 => UInt8 (Addr) or ReadWrite_CMD),
                        Status);
     if Status /= Ok then
        raise Program_Error;
     end if;
     This.Port.Receive (Tmp_Data, Status); -- dummy constraint BMI088
     This.Port.Receive (Tmp_Data, Status);
     if Status /= Ok then
        raise Program_Error;
     end if;
     Data := Tmp_Data (Tmp_Data'First);
     SPI_Mode (This, Enabled => False); -- unselect the chip
  end Read;
  ----------------
  -- Read_UInt8s --
  ----------------
  procedure Accel_Read_UInt8s
    (This   : Six_Axis_Imu;
     Addr   : Register;
     Buffer : out SPI_Data_8b;
     Count  : Natural)
  is
     Index : Natural := Buffer'First;
     Status : SPI_Status;
     Tmp_Data : SPI_Data_8b (1 .. 1);
  begin
     SPI_Mode (This, Enabled => True);
     This.Port.Transmit
       (SPI_Data_8b'(1 => UInt8 (Addr) or ReadWrite_CMD),
        Status);
     if Status /= Ok then
        raise Program_Error;
     end if;
     This.Port.Receive (Tmp_Data, Status); -- dummy constraint BMI088
     for K in 1 .. Count loop
        This.Port.Receive (Tmp_Data, Status);
        if Status /= Ok then
           raise Program_Error;
        end if;
        Buffer (Index) := Tmp_Data (Tmp_Data'First);
        Index := Index + 1;
     end loop;
     SPI_Mode (This, Enabled => False);
  end Read_UInt8s;



最好从寄存器中读取加速数据。在下图中,我们仅表示SDO和时钟(SDI未表示,我只有2个探测......:'():
39.jpg
对于陀螺仪数据,虚拟字节没有问题。
这就是为什么必须要有Accel_Read和Gyro_Read不同的程序。
初始化BMI088的步骤
首先,您需要通过调用以下过程正确初始化SPI以与BMI088通信:

procedure Initialize_SPI1_For_BMI088 is
     Config : GPIO_Port_Configuration;
     Config_SPI : SPI_Configuration;
  begin
     -- Enable the clocks
     Enable_Clock (IMU_SPI);
     Enable_Clock (SPI1_Points);
     Enable_Clock (IMU_SEL1);
     -- Configure the SPI IOs
     Config := (Mode           => Mode_AF,
                   AF             => GPIO_AF_SPI1_5,
                   AF_Speed       => Speed_100MHz,
                   AF_Output_Type => Push_Pull,
                Resistors      => Floating);
     Configure_IO(SPI1_Points, Config);
     -- Configure the SPI select chip IOs
     Config := (Mode        => Mode_Out,
                   Speed       => Speed_25MHz,
                   Output_Type => Push_Pull,
                   Resistors   => Pull_Up);
     Configure_IO(IMU_SEL_Points, Config);
     -- Configure the SPI1 port
     Config_SPI :=
          (Direction           => D2Lines_FullDuplex,
           Mode                => Master,
           Data_Size           => HAL.SPI.Data_Size_8b,
           Clock_Polarity      => Low,
           Clock_Phase         => P1Edge,
           Slave_Management    => Software_Managed,
           Baud_Rate_Prescaler => BRP_32
           First_Bit           => MSB,
           CRC_Poly            => 7);
     Configure (IMU_SPI, Config_SPI);
     STM32.SPI.Enable (IMU_SPI);
  end Initialize_SPI1_For_BMI088;

然后按照以下步骤获取加速度计(寄存器和命令的值在数据表中定义,您可以在最后的驱动程序中找到它们):

---- check the device ID
  Bmi.Read(ACC_CHIP_ID_ADDR, Result);
  while Result /= ACC_CHIP_ID loop
     Bmi.Read(ACC_CHIP_ID_ADDR, Result);
     delay until Clock + Milliseconds (1);
  end loop;
---- reset the accel
  Bmi.Write(ACC_SOFT_RESET_ADDR,ACC_RESET_CMD);
  delay until Clock + Milliseconds (50);
  Turn_ON(IMU_SEL1);
  delay until Clock + Milliseconds (5);
  Turn_Off(IMU_SEL1);
  delay until Clock + Milliseconds (5);
---- enable the accel (rundandant to make sure it works)
  Bmi.Write(ACC_PWR_CNTRL_ADDR,ACC_ENABLE_CMD);
  delay until Clock + Milliseconds (50);  
  Bmi.Read(ACC_PWR_CNTRL_ADDR, Result);
  while Result /= ACC_ENABLE_CMD loop
     Bmi.Write(ACC_SOFT_RESET_ADDR,ACC_RESET_CMD);
     --delay until Clock + Milliseconds (1);
     Bmi.Write(ACC_PWR_CNTRL_ADDR,ACC_ENABLE_CMD);
     --delay until Clock + Milliseconds (50);
     Bmi.Read(ACC_PWR_CNTRL_ADDR, Result);
     delay until Clock + Microseconds (4);
  end loop;
---- enter active mode
  Bmi.Write(ACC_PWR_CONF_ADDR,ACC_ACTIVE_MODE_CMD);
  delay until Clock + Milliseconds (5);
  Bmi.Read(ACC_PWR_CONF_ADDR, Result);
  while Result /= ACC_ACTIVE_MODE_CMD loop
     Bmi.Write(ACC_PWR_CONF_ADDR,ACC_ACTIVE_MODE_CMD);
     delay until Clock + Milliseconds (5);
     Bmi.Read(ACC_PWR_CONF_ADDR, Result);
  end loop;
---- set range 24G
  Bmi.Write(ACC_RANGE_ADDR,RANGE_24G);
  delay until Clock + Milliseconds (1);
  Bmi.Read(ACC_RANGE_ADDR, Result);
  while Result /= RANGE_24G loop
     Bmi.Read(ACC_RANGE_ADDR, Result);
  end loop;
---- set default ODR
  Bmi.Write(ACC_ODR_ADDR,ODR_1600HZ_BW_280HZ);
  delay until Clock + Milliseconds (1);
  Bmi.Read(ACC_ODR_ADDR, Result);
  while Result /= ODR_1600HZ_BW_280HZ loop
     Bmi.Read(ACC_ODR_ADDR, Result);
  end loop;
---- check if there is an error
  Bmi.Read(ACC_ERR_CODE_ADDR, Result);
  while Result /= 16#0# loop
     Bmi.Read(ACC_ERR_CODE_ADDR, Result);
  end loop;
  ---- read the value
  loop
     Bmi.Read_UInt8s(ACC_ACCEL_DATA_ADDR,Received,6);
     delay until Clock + Microseconds (100);
  end loop;

按照上面的步骤,我使用来自BMI088的SPI获取值。

步骤6:过滤和估计描述
应用几何变换:
SuperIMU中IMU的配置不一样,它们被旋转(45°和90°):
40.jpg
只有加速度X和Y受到旋转的影响。陀螺仪没有受到影响(转速是相对的)。

根据每个IMU位置完成旋转变换。旋转按以下过程编码:

--------------------
  -- Accel rotation --
  --------------------
  Sqrt2 : constant Float := 1.41421356237;
  IMU_Alpha_Index : constant array (1 .. 3) of Integer := (1,3,7);
  IMU_Beta_Index  : constant array (1 .. 2) of Integer := (5,11);
  IMU_Gamma_Index : constant array (1 .. 2) of Integer := (4,9);
  IMU_Delta_Index : constant array (1 .. 2) of Integer := (2,13);     
  procedure ApplyIMURotation(i : Integer;xf : in out Float; yf : in out Float) is
  begin
     if i in IMU_Alpha_Index'range then
        null; -- we don't change
     elsif i in IMU_Beta_index'range then
        xf := -xf;
        yf := -yf;
     elsif i in IMU_Gamma_Index'range then
        xf := yf;
        yf := xf;
     elsif i in IMU_Delta_Index'range then
        xf := -yf;
        yf := -xf;
     elsif i = 8 then
        xf := Sqrt2*(-yf + xf);
        yf := Sqrt2*(yf + xf);
     elsif i = 6 then
        xf := Sqrt2*(-yf - xf);
        yf := Sqrt2*(-yf + xf);
     elsif i = 10 then
        xf := Sqrt2*(yf + xf);
        yf := Sqrt2*(yf - xf);
     elsif i = 12 then
        xf := Sqrt2*(yf - xf);
        yf := Sqrt2*(-yf - xf);
     end if;
  end;  



使用范围:
IMU的范围不同。每组IMU都有一个特定的范围:
41.jpg
IMU分布,范围和名称

配置中使用的范围使用以下代码计算:

--------------------------
  -- Get the range to use --
  --------------------------
  Range_3G_Index : constant array (1 .. 5) of Integer := (1,3,4,5,2);
  Range_6G_Index : constant array (1 .. 4) of Integer := (8,6,12,10);
  Range_12G_Index : constant array (1 .. 2) of Integer := (9,13);
  Range_24G_Index : constant array (1 .. 2) of Integer := (7,11);
  function GetAccelRangeConf(i : Integer) return Accel_Range is
  begin
     if i in Range_3G_Index'Range then
        return ACCEL_RANGE_3G;
     elsif i in Range_6G_Index'Range then
        return ACCEL_RANGE_6G;
     elsif i in Range_12G_Index'Range then
        return ACCEL_RANGE_12G;
     elsif i in Range_24G_Index'Range then
        return ACCEL_RANGE_24G;
     else
        return ACCEL_RANGE_24G;
     end if;
  end;
  Range_125DPS_Index : constant array (1 .. 5) of Integer := (1,3,4,5,2);
  Range_500DPS_Index : constant array (1 .. 4) of Integer := (8,6,12,10);
  Range_1000DPS_Index : constant array (1 .. 2) of Integer := (9,13);
  Range_2000DPS_Index : constant array (1 .. 2) of Integer := (7,11);
  function GetGyroRangeConf(i : Integer) return Gyro_Range is
  begin
     if i in Range_125DPS_Index'Range then
        return GYRO_RANGE_125DPS;
     elsif i in Range_500DPS_Index'Range then
        return GYRO_RANGE_500DPS;
     elsif i in Range_1000DPS_Index'Range then
        return GYRO_RANGE_1000DPS;
     elsif i in Range_2000DPS_Index'Range then
        return GYRO_RANGE_2000DPS;
     else
        return GYRO_RANGE_500DPS;
     end if;        
  end;


过滤代码:
根据每个IMU的配置范围对加速度和陀螺仪速率进行滤波。
权重使用以下代码计算:

-- IMUs filter, filters the Gyro/Accel according to the range
  function GetAccelWeight(rng : Accel_Range; val : Sensor_Accel_Data) return Float is
     weight : Float := 0.0;
     refaccel : Float := 3.0;
     refrange : Float := 3.0;
  begin
     if ((abs(val(X))<3.0*0.95*G and abs(val(Y))<3.0*0.95*G and abs(val(Z))<3.0*0.95*G)) then
        refaccel := 3.0;
     elsif ((abs(val(X))<6.0*0.95*G and abs(val(Y))<6.0*0.95*G and abs(val(Z))<6.0*0.95*G)) then
        refaccel := 6.0;
     elsif ((abs(val(X))<12.0*0.95*G and abs(val(Y))<12.0*0.95*G and abs(val(Z))<12.0*0.95*G)) then
        refaccel := 12.0;
     elsif ((abs(val(X))<24.0*0.95*G and abs(val(Y))<24.0*0.95*G and abs(val(Z))<24.0*0.95*G)) then
        refaccel := 24.0;
     end if;
     if rng = ACCEL_RANGE_3G then
        refrange := 3.0;
     elsif rng = ACCEL_RANGE_6G then
        refrange := 6.0;
     elsif rng = ACCEL_RANGE_12G then
        refrange := 12.0;
     elsif rng = ACCEL_RANGE_24G then
        refrange := 24.0;
     end if;
     weight := refaccel / refrange;
     if weight > 1.0 then
        return 0.0;
     end if;
     return refaccel/refrange;
  end GetAccelWeight;
function GetGyroWeight(rng : Gyro_Range; val : Sensor_Gyro_Data) return Float is
     weight : Float := 0.0;
     refgyr : Float := 125.0;
     refrange : Float := 125.0;
  begin
     if ((abs(val(X))<125.0*0.95 and abs(val(Y))<125.0*0.95 and abs(val(Z))<125.0*0.95)) then
        refgyr := 125.0;
     elsif ((abs(val(X))<250.0*0.95 and abs(val(Y))<250.0*0.95 and abs(val(Z))<250.0*0.95)) then
        refgyr := 250.0;
     elsif ((abs(val(X))<500.0*0.95 and abs(val(Y))<500.0*0.95 and abs(val(Z))<500.0*0.95)) then
        refgyr := 500.0;
     elsif ((abs(val(X))<1000.0*0.95 and abs(val(Y))<1000.0*0.95 and abs(val(Z))<1000.0*0.95)) then
        refgyr := 1000.0;
     elsif ((abs(val(X))<2000.0*0.95 and abs(val(Y))<2000.0*0.95 and abs(val(Z))<2000.0*0.95)) then
        refgyr := 2000.0;
     end if;
     if rng = GYRO_RANGE_125DPS then
        refrange := 125.0;
     elsif rng = GYRO_RANGE_250DPS then
        refrange := 250.0;
     elsif rng = GYRO_RANGE_500DPS then
        refrange := 500.0;
     elsif rng = GYRO_RANGE_250DPS then
        refrange := 1000.0;
     elsif rng = GYRO_RANGE_125DPS then
        refrange := 2000.0;
     end if;
     weight := refgyr / refrange;
     if weight > 1.0 then
        return 0.0;
     end if;
     return refgyr/refrange;
  end GetGyroWeight;


使用权重,加速度值是所有值的加权平均值:
procedure FilterSuperIMU(facc   : out Sensor_Accel_Data;
                           fgyr   : out Sensor_Gyro_Data) is
     sumacc  : Sensor_Accel_Data;  -- cumulated accel value
     sumgyr  : Sensor_Gyro_Data;  -- cumulated gyro value
     sumwacc : Float := 0.0;  -- cumulated accel weight
     sumwgyr : Float := 0.0;  -- cumulated accel weight
     macc    : Sensor_Accel_Data;  -- measured accel value
     mgyr    : Sensor_Gyro_Data;  -- measured accel value
     mwacc   : Float := 0.0;  -- measured accel weight
     mwgyr   : Float := 0.0;  -- measured accel weight
  begin
     sumacc := NullAccelRate;
     sumgyr := NullGyroRate;
     for i in Integer range 1 .. BMIs'Length loop         
        -- get accelerartion
        BMIs(i).ReadAccelRates(macc);
        -- get weight according to the range
        mwacc := GetAccelWeight(BMIs(i).GetCurrentAccelRange, macc);
        -- apply 45° and inversion tranformation (geometry of the IMU)
        ApplyIMURotation(i,macc(X),macc(Y));
        -- update the cumulated sum
        sumacc(X) := sumacc(X) + macc(X)*mwacc;
        sumacc(Y) := sumacc(Y) + macc(Y)*mwacc;
        sumacc(Z) := sumacc(Z) + macc(Z)*mwacc;
        sumwacc := sumwacc + mwacc;
        -- get the gyro rate
        -- we don't rotate the gyro rate, no need
        BMIs(i).ReadGyroRates(mgyr);
        -- compute the weight according to the range
        mwgyr := GetGyroWeight(BMIs(i).GetCurrentGyroRange, mgyr);
        -- update the cummulated weighted sum
        sumgyr(X) := sumgyr(X) + mgyr(X)*mwgyr;
        sumgyr(Y) := sumgyr(Y) + mgyr(Y)*mwgyr;
        sumgyr(Z) := sumgyr(Z) + mgyr(Z)*mwgyr;
        --
        sumwgyr := sumwgyr + mwgyr;
     end loop;
     facc(X) := sumacc(X) / sumwacc;
     facc(Y) := sumacc(Y) / sumwacc;
     facc(Z) := sumacc(Z) / sumwacc;
     fgyr(X) := sumgyr(X) / sumwgyr;
     fgyr(Y) := sumgyr(Y) / sumwgyr;
     fgyr(Z) := sumgyr(Z) / sumwgyr;
  end FilterSuperIMU;


估算代码:
我用一个简单的互补滤波器来估算音高和滚动。我们从IMU获得加速度和陀螺仪速率数据。
代码在这里:

procedure ComplementaryFilter(accelData : Sensor_Accel_Data; gyroData : Sensor_Gyro_Data) is
     pitchAccel : Float := 0.0; -- pitch computed from the accel
     rollAccel  : Float := 0.0; -- roll computed from the accel
  begin
     -- integrate the gyroscope data
     estimatedPitch := estimatedPitch + gyroData(X)*dt;
     estimatedRoll := estimatedRoll + gyroData(Y)*dt;
     -- compensate the drift using the acceleration
     pitchAccel := Ada.Numerics.Elementary_Functions.Arctan(accelData(Y),accelData(Z))*RadToDeg;
     estimatedPitch :=  estimatedPitch * 0.98 + pitchAccel * 0.02;
     rollAccel := Ada.Numerics.Elementary_Functions.Arctan(accelData(X),accelData(Z))*RadToDeg;
     estimatedRoll := estimatedRoll * 0.98 + rollAccel * 0.02;
     null;
  end ComplementaryFilter;

第7步:退役
使用OpenOCD
您也可以使用st-util,我已经在其他项目中完成了openocd的工作,这就是我使用它的原因。
重要(3小时获取时间评论)你需要在sudo模式下运行gnat gps,否则openocd无法访问usb端口。
安装OpenOCD
在ubuntu上使用OpenOCD的最佳方法是构建它。这样你就拥有了最新版本。我使用了gnu-mcu-eclipse/ openocd的最新版OpenOCD 。
要构建它很简单,只需确保安装了通用的C / C ++构建工具:
you need to install libusb dev v1.0 then# ./configure --enable-stlink # make # sudo make install  
用于OpenOCD的配置:
- 本地主机的端口为3333
- 我使用了来自openocd github 的stm32f 74discovery的配置文件。
42.jpg
用于OpenOCD的配置

使用带有Analyzer2goCYUSB3KIT-003SPI进行分配
Analyzer2Go只是一个用于小预算的神奇工具。结合电路板CYUSB3KIT-003(45 $),您可以在200MHz采样率下使用功能强大的16x通道分析仪!
对于我的情况,与SPI / UART的交换低于10Mhz,并且读取Nucleo板的MCO(在100MHz下的PLL),这就足够了。
SPI测试
SPI的速度配置为STM32F746的6MHz(波特率DIV 32)。
我可以使用来自BMI088实例的SPI使用以下过程正确获取IMU运动数据:

  procedure ReadAccelRates (This : in out Six_Axis_Imu;                            Result   : out Sensor_Accel_Data);
  procedure ReadGyroRates (This : in out Six_Axis_Imu;
                            Result   : out Sensor_Gyro_Data);


第8步:PLL时钟测试:


在核板中,外部高速时钟(HSE)不是来自25MHz晶体(如Adacore内置STM32F746Disco),而是来自ST-Link芯片@ 8MHz。
经过数小时的调试后,我最终修改了adl_config.ads和s-bbbopa.ads(作为workarround)文件以设置HSE时钟的正确值。我终于为Nucleo-F746创建了一个新的BSP,而不是因为这种差异而使用stm32f746disco。
现在时钟正常运行,测量显示时间:
在调试模式下读取加速值需要35us 。


43.jpg

在从IMU读取值结束时,我使用流动代码切换引脚PG1:

  --------------------
  -- ReadAccelRates --
  --------------------
  procedure ReadAccelRates (This : in out Six_Axis_Imu;
                            Result   : out Sensor_Data)
  is
     Received        : SPI_Data_8b (1 .. 6); -- 2 bytes per axis
     AccelRates : BMI088.IMU_Rates;-- todo create AccelRate type
  begin
     PG1.Set;
     This.AccelRead_UInt8s(ACC_ACCEL_DATA_ADDR,Received,6);
     AccelRates.X := As_IMU_Rates_Pointer (Received (1)'Address).all;
     AccelRates.Y := As_IMU_Rates_Pointer (Received (3)'Address).all;
     AccelRates.Z := As_IMU_Rates_Pointer (Received (5)'Address).all;
     Result(X) := Float (AccelRates.X) / 32768.0 * Accel_Range_MSS;
     Result(Y) := Float (AccelRates.Y) / 32768.0 * Accel_Range_MSS;
     Result(Z) := Float (AccelRates.Z) / 32768.0 * Accel_Range_MSS;
     PG1.Clear;
  end ReadAccelRates;

Reading在调试模式下,完整的陀螺仪值需要34us
44.jpg


   -------------------
  -- ReadGyroRates --
  -------------------
  procedure ReadGyroRates (This : in out Six_Axis_Imu;
                            Result   : out Sensor_Data)
  is
     Received        : SPI_Data_8b (1 .. 6); -- 2 bytes per axis
     GyroRates : BMI088.IMU_Rates;-- todo create AccelRate type
  begin
PG1.Set;
     This.AccelRead_UInt8s(ACC_ACCEL_DATA_ADDR,Received,6);
     GyroRates.X := As_IMU_Rates_Pointer (Received (1)'Address).all;
     GyroRates.Y := As_IMU_Rates_Pointer (Received (3)'Address).all;
     GyroRates.Z := As_IMU_Rates_Pointer (Received (5)'Address).all;
     Result(X) := Float (GyroRates.X) / 32768.0 * Gyro_Range_Rads;
     Result(Y) := Float (GyroRates.Y) / 32768.0 * Gyro_Range_Rads;
     Result(Z) := Float (GyroRates.Z) / 32768.0 * Gyro_Range_Rads;
PG1.Clear;
  end ReadGyroRates;

Of 对于真实用例,我需要将编译模式设置为生产以进行正确的优化。

步骤9:使用UART绘制数据
为了可视化输出数据,我想使用UART在超级惊人的酷系列绘图仪中可视化它:https://github.com/CieNTi/serial_port_plotter
45.png

现在的问题是将Float转换为String ..以使用UART将其发送到Serial_Port_Plotter工具。
为此,我在这里使用神奇的单词'Img in Ada 将Float转换为String。然后我逐字节地将字符串发送到UART。
如果要发送一个值:
- 发送“$”
- 发送浮点值
- 发送“;”
如果你想发送多个值:
- 发送“$”开始
- 发送浮点数值1
- 发送空格“”
...
- 发送浮点值i
- 发送空格“”
- 发送“;” 终止

为了能够向绘图仪发送加速度,我使用了以下代码:

  type FixedFloat is delta 0.00001 digits 18;---
     data_1B(0) := UInt8(36); -- '$'
     UART_OUT.Transmit(data_1B,status);
     for ch of FixedFloat(AccelRates(X))'Img loop
        c := Character(ch);
        data_1B(0) := Character'Pos(c);
        UART_OUT.Transmit(data_1B,status);
     end loop;
     data_1B(0) := UInt8(59); -- ';'
     UART_OUT.Transmit(data_1B,status);


结果非常令人满意,在下面的快照中我实时可视化Accelerometer(X)值:
46.jpg

创建驱动程序后,该数据通过UART端口正确发送:
47.jpg

UART制作驱动程序
不幸的是,在STM32F7器件中还没有UART驱动器(仅适用于STM32F4)。
要为UART创建驱动程序,我从STM32 F4 的stm32-usarts.ads/adb开始。我修改了文件以符合STM32 F7 的SVD 。我还要修改STM32-Device.ads/adb以包含STM32F7的UART / USART定义。
它适用于传输9或8位大小的字节。我没有测试它的DMA风格(因为从未做过

代码广告:
------------------------------------------------------------------------------
--                                                                          --
--                  Copyright (C) 2015-2016, AdaCore                        --
--                                                                          --
--  Redistribution and use in source and binary forms, with or without      --
--  modification, are permitted provided that the following conditions are  --
--  met:                                                                    --
--     1. Redistributions of source code must retain the above copyright    --
--        notice, this list of conditions and the following disclaimer.     --
--     2. Redistributions in binary form must reproduce the above copyright --
--        notice, this list of conditions and the following disclaimer in   --
--        the documentation and/or other materials provided with the        --
--        distribution.                                                     --
--     3. Neither the name of STMicroelectronics nor the names of its       --
--        contributors may be used to endorse or promote products derived   --
--        from this software without specific prior written permission.     --
--                                                                          --
--   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    --
--   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      --
--   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  --
--   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   --
--   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
--   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT       --
--   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  --
--   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  --
--   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT    --
--   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  --
--   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   --
--                                                                          --
--                                                                          --
--  This file is based on:                                                  --
--                                                                          --
--   @file    stm32f4xx_hal_usart.h                                         --
--   @author  MCD Application Team                                          --
--   @version V1.1.0                                                        --
--   @date    19-June-2014                                                  --
--   @brief   Header file of USARTS HAL module.                             --
--                                                                          --
--   COPYRIGHT(c) 2014 STMicroelectronics                                   --
------------------------------------------------------------------------------
--  This file provides register definitions for the STM32F4 (ARM Cortex M4F)
--  USART from ST Microelectronics.
--  Note that there are board implementation assumptions represented by the
--  private function APB_Clock.
with System;
with HAL.UART; use HAL.UART;
private with STM32_SVD.USART;
package STM32.USARTs is
  type Internal_USART is limited private;
  type USART (Periph : not null access Internal_USART) is
    limited new HAL.UART.UART_Port with private;
  procedure Enable (This : in out USART) with
    Post => Enabled (This),
    Inline;
  procedure Disable (This : in out USART) with
    Post => not Enabled (This),
    Inline;
  function Enabled (This : USART) return Boolean with Inline;
  procedure Receive (This : USART;  Data : out UInt9) with Inline;
  --  reads Device.DR into Data
  function Current_Input (This : USART) return UInt9 with Inline;
  --  returns Device.DR
  procedure Transmit (This : in out USART;  Data : UInt9) with Inline;
  function Tx_Ready (This : USART) return Boolean with Inline;
  function Rx_Ready (This : USART) return Boolean with Inline;
  type Stop_Bits is (Stopbits_1, Stopbits_2) with Size => 2;
  for Stop_Bits use (Stopbits_1 => 0, Stopbits_2 => 2#10#);
  procedure Set_Stop_Bits (This : in out USART;  To : Stop_Bits);
  type Word_Lengths is (Word_Length_8, Word_Length_9);
  procedure Set_Word_Length (This : in out USART;  To : Word_Lengths);
  type Parities is (No_Parity, Even_Parity, Odd_Parity);
  procedure Set_Parity (This : in out USART;  To : Parities);
  subtype Baud_Rates is UInt32;
  procedure Set_Baud_Rate (This : in out USART;  To : Baud_Rates);
  type Oversampling_Modes is (Oversampling_By_8, Oversampling_By_16);
  --  oversampling by 16 is the default
  procedure Set_Oversampling_Mode
    (This : in out USART;
     To   : Oversampling_Modes);
  type UART_Modes is (Rx_Mode, Tx_Mode, Tx_Rx_Mode);
  procedure Set_Mode (This : in out USART;  To : UART_Modes);
  type Flow_Control is
    (No_Flow_Control,
     RTS_Flow_Control,
     CTS_Flow_Control,
     RTS_CTS_Flow_Control);
  procedure Set_Flow_Control (This : in out USART;  To : Flow_Control);
  type USART_Interrupt is
    (Parity_Error,
     Transmit_Data_Register_Empty,
     Transmission_Complete,
     Received_Data_Not_Empty,
     Idle_Line_Detection,
     Line_Break_Detection,
     Clear_To_Send,
     Error);
  procedure Enable_Interrupts
    (This   : in out USART;
     Source : USART_Interrupt)
    with
      Post => Interrupt_Enabled (This, Source),
      Inline;
  procedure Disable_Interrupts
    (This   : in out USART;
     Source : USART_Interrupt)
    with
      Post => not Interrupt_Enabled (This, Source),
       Inline;
  function Interrupt_Enabled
    (This   : USART;
     Source : USART_Interrupt)
     return Boolean
    with Inline;
  type USART_Status_Flag is
    (Parity_Error_Indicated,
     Framing_Error_Indicated,
     USART_Noise_Error_Indicated,
     Overrun_Error_Indicated,
     Idle_Line_Detection_Indicated,
     Read_Data_Register_Not_Empty,
     Transmission_Complete_Indicated,
     Transmit_Data_Register_Empty,
     Line_Break_Detection_Indicated,
     Clear_To_Send_Indicated);
  function Status (This : USART; Flag : USART_Status_Flag) return Boolean
    with Inline;
  procedure Clear_Status (This : in out USART; Flag : USART_Status_Flag)
    with Inline;
  procedure Enable_DMA_Transmit_Requests (This : in out USART) with
    Inline,
    Post => DMA_Transmit_Requests_Enabled (This);
  procedure Disable_DMA_Transmit_Requests (This : in out USART) with
    Inline,
    Post => not DMA_Transmit_Requests_Enabled (This);
  function DMA_Transmit_Requests_Enabled  (This : USART) return Boolean with
    Inline;
  procedure Enable_DMA_Receive_Requests (This : in out USART) with
    Inline,
    Post => DMA_Receive_Requests_Enabled (This);
  procedure Disable_DMA_Receive_Requests (This : in out USART) with
    Inline,
    Post => not DMA_Receive_Requests_Enabled (This);
  function DMA_Receive_Requests_Enabled  (This : USART) return Boolean with
    Inline;
  procedure Pause_DMA_Transmission (This : in out USART)
    renames Disable_DMA_Transmit_Requests;
  procedure Resume_DMA_Transmission (This : in out USART) with
    Inline,
    Post => DMA_Transmit_Requests_Enabled (This) and
            Enabled (This);
  procedure Pause_DMA_Reception (This : in out USART)
    renames Disable_DMA_Receive_Requests;
  procedure Resume_DMA_Reception (This : in out USART) with
    Inline,
    Post => DMA_Receive_Requests_Enabled (This) and
            Enabled (This);
  function Data_Receive_Register_Address (This : USART) return System.Address with
    Inline;
  function Data_Transmit_Register_Address (This : USART) return System.Address with
    Inline;
  --  Returns the address of the USART Data Register. This is exported
  --  STRICTLY for the sake of clients driving a USART via DMA. All other
  --  clients of this package should use the procedural interfaces Transmit
  --  and Receive instead of directly accessing the Data Register!
  --  Seriously, don't use this function otherwise.
  -----------------------------
  -- HAL.UART implementation --
  -----------------------------
  overriding
  function Data_Size (This : USART) return HAL.UART.UART_Data_Size;
  overriding
  procedure Transmit
    (This    : in out USART;
     Data    : UART_Data_8b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
    with
      Pre'Class => Data_Size (This) = Data_Size_8b;
  overriding
  procedure Transmit
    (This    : in out USART;
     Data    : UART_Data_9b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
    with
      Pre'Class => Data_Size (This) = Data_Size_9b;
  overriding
    (This    : in out USART;
     Data    : out UART_Data_8b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
    with
      Pre'Class => Data_Size (This) = Data_Size_8b;
  overriding
  procedure Receive
    (This    : in out USART;
     Data    : out UART_Data_9b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
    with
      Pre'Class => Data_Size (This) = Data_Size_9b;
private
  function APB_Clock (This : USART) return UInt32 with Inline;
  --  Returns either APB1 or APB2 clock rate, in Hertz, depending on the
  --  USART. For the sake of not making this package board-specific, we assume
  --  that we are given a valid USART object at a valid address, AND that the
  --  USART devices really are configured such that only 1 and 6 are on APB2.
  --  Therefore, if a board has additional USARTs beyond USART6, eg USART8 on
  --  the F429I Discovery board, they better conform to that assumption.
  --  See Note # 2 in each of Tables 139-141 of the RM on pages 970 - 972.
  type Internal_USART is new STM32_SVD.USART.USART_Peripheral;
  type USART (Periph : not null access Internal_USART) is
    limited new HAL.UART.UART_Port with null record;
end STM32.USARTs;

代码adb:
------------------------------------------------------------------------------
--                                                                          --
--                  Copyright (C) 2015-2017, AdaCore                        --
--                                                                          --
--  Redistribution and use in source and binary forms, with or without      --
--  modification, are permitted provided that the following conditions are  --
--  met:                                                                    --
--     1. Redistributions of source code must retain the above copyright    --
--        notice, this list of conditions and the following disclaimer.     --
--     2. Redistributions in binary form must reproduce the above copyright --
--        notice, this list of conditions and the following disclaimer in   --
--        the documentation and/or other materials provided with the        --
--        distribution.                                                     --
--     3. Neither the name of STMicroelectronics nor the names of its       --
--        contributors may be used to endorse or promote products derived   --
--        from this software without specific prior written permission.     --
--                                                                          --
--   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS    --
--   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      --
--   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  --
--   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   --
--   HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
--   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT       --
--   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  --
--   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  --
--   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT    --
--   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  --
--   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.   --
--                                                                          --
--                                                                          --
--  This file is based on:                                                  --
--                                                                          --
--   @file    stm32f4xx_hal_usart.c                                         --
--   @author  MCD Application Team                                          --
--   @version V1.1.0                                                        --
--   @date    19-June-2014                                                  --
--   @brief   USARTS HAL module driver.                                     --
--                                                                          --
--   COPYRIGHT(c) 2014 STMicroelectronics                                   --
------------------------------------------------------------------------------
with System;          use System;
with STM32_SVD.USART; use STM32_SVD, STM32_SVD.USART;
with STM32.Device;    use STM32.Device;
package body STM32.USARTs is
  ---------------
  -- APB_Clock --
  ---------------
  function APB_Clock (This : USART) return UInt32 is
     Clocks : constant RCC_System_Clocks := System_Clock_Frequencies;
  begin
     if This.Periph.all'Address = USART1_Base
       or
        This.Periph.all'Address = USART6_Base
     then
        return Clocks.PCLK2;
     else
        return Clocks.PCLK1;
     end if;
  end APB_Clock;
  ------------
  -- Enable --
  ------------
  procedure Enable (This : in out USART) is
  begin
     This.Periph.CR1.UE := True;
  end Enable;
  -------------
  -- Disable --
  -------------
  procedure Disable (This : in out USART) is
  begin
     This.Periph.CR1.UE := False;
  end Disable;
  -------------
  -- Enabled --
  -------------
  function Enabled (This : USART) return Boolean is
    (This.Periph.CR1.UE);
  -------------------
  -- Set_Stop_Bits --
  -------------------
  procedure Set_Stop_Bits (This : in out USART; To : Stop_Bits)
  is
  begin
     This.Periph.CR2.STOP := Stop_Bits'Enum_Rep (To);
  end Set_Stop_Bits;
  ---------------------
  -- Set_Word_Length --
  ---------------------
  procedure Set_Word_Length
    (This : in out USART;
     To : Word_Lengths)
  is
  begin
     This.Periph.CR1.M0 := To = Word_Length_9;
  end Set_Word_Length;
  ----------------
  -- Set_Parity --
  ----------------
  procedure Set_Parity (This : in out USART; To : Parities) is
  begin
     case To is
        when No_Parity =>
           This.Periph.CR1.PCE := False;
           This.Periph.CR1.PS  := False;
        when Even_Parity =>
           This.Periph.CR1.PCE := True;
           This.Periph.CR1.PS  := False;
        when Odd_Parity =>
           This.Periph.CR1.PCE := True;
           This.Periph.CR1.PS  := True;
     end case;
  end Set_Parity;
  -------------------
  -- Set_Baud_Rate --
  -------------------
  procedure Set_Baud_Rate (This : in out USART; To : Baud_Rates)
  is
     Clock        : constant UInt32 := APB_Clock (This);
     Over_By_8    : constant Boolean := This.Periph.CR1.OVER8;
     Int_Scale    : constant UInt32 := (if Over_By_8 then 2 else 4);
     Int_Divider  : constant UInt32 := (25 * Clock) / (Int_Scale * To);
     Frac_Divider : constant UInt32 := Int_Divider rem 100;
  begin
     --  the integer part of the divi
     if Over_By_8 then
        This.Periph.BRR.DIV_Fraction :=
          BRR_DIV_Fraction_Field (((Frac_Divider * 8) + 50) / 100 mod 8);
     else
        This.Periph.BRR.DIV_Fraction :=
          BRR_DIV_Fraction_Field (((Frac_Divider * 16) + 50) / 100 mod 16);
     end if;
     This.Periph.BRR.DIV_Mantissa :=
       BRR_DIV_Mantissa_Field (Int_Divider / 100);
  end Set_Baud_Rate;
  ---------------------------
  -- Set_Oversampling_Mode --
  ---------------------------
  procedure Set_Oversampling_Mode
    (This : in out USART;
     To   : Oversampling_Modes)
  is
  begin
     This.Periph.CR1.OVER8 := To = Oversampling_By_8;
  end Set_Oversampling_Mode;
  --------------
  -- Set_Mode --
  --------------
  procedure Set_Mode (This : in out USART;  To : UART_Modes) is
  begin
     This.Periph.CR1.RE := To /= Tx_Mode;
     This.Periph.CR1.TE := To /= Rx_Mode;
  end Set_Mode;
  ----------------------
  -- Set_Flow_Control --
  ----------------------
  procedure Set_Flow_Control (This : in out USART;  To : Flow_Control) is
  begin
     case To is
        when No_Flow_Control =>
           This.Periph.CR3.RTSE := False;
           This.Periph.CR3.CTSE := False;
        when RTS_Flow_Control =>
           This.Periph.CR3.RTSE := True;
           This.Periph.CR3.CTSE := False;
        when CTS_Flow_Control =>
           This.Periph.CR3.RTSE := False;
           This.Periph.CR3.CTSE := True;
        when RTS_CTS_Flow_Control =>
           This.Periph.CR3.RTSE := True;
           This.Periph.CR3.CTSE := True;
     end case;
  end Set_Flow_Control;
  ---------
  -- Put --
  ---------
  procedure Transmit (This : in out USART;  Data : UInt9) is
  begin
     This.Periph.TDR.TDR := TDR_TDR_Field (Data);
  end Transmit;
  ---------
  -- Get --
  ---------
  procedure Receive (This : USART;  Data : out UInt9) is
  begin
     Data := Current_Input (This);
  end Receive;
  -------------------
  -- Current_Input --
  -------------------
  function Current_Input (This : USART) return UInt9 is (Uint9 (This.Periph.RDR.RDR));
  --------------
  -- Tx_Ready --
  --------------
  function Tx_Ready (This : USART) return Boolean is
  begin
     return This.Periph.ISR.TXE;
  end Tx_Ready;
  --------------
  -- Rx_Ready --
  --------------
  function Rx_Ready (This : USART) return Boolean is
  begin
     return This.Periph.ISR.RXNE;
  end Rx_Ready;
  ------------
  -- Status --
  ------------
  function Status (This : USART; Flag : USART_Status_Flag) return Boolean is
  begin
     case Flag is
        when Parity_Error_Indicated =>
           return This.Periph.ISR.PE;
        when Framing_Error_Indicated =>
           return This.Periph.ISR.FE;
        when USART_Noise_Error_Indicated =>
           return This.Periph.ISR.NF;
        when Overrun_Error_Indicated =>
           return This.Periph.ISR.ORE;
        when Idle_Line_Detection_Indicated =>
           return This.Periph.ISR.IDLE;
        when Read_Data_Register_Not_Empty =>
           return This.Periph.ISR.RXNE;
        when Transmission_Complete_Indicated =>
           return This.Periph.ISR.TC;
        when Transmit_Data_Register_Empty =>
           return This.Periph.ISR.TXE;
        when Line_Break_Detection_Indicated =>
           return This.Periph.ISR.LBDF;
        when Clear_To_Send_Indicated =>
           return This.Periph.ISR.CTS;
     end case;
  end Status;
  ------------------
  -- Clear_Status --
  ------------------
  procedure Clear_Status (This : in out USART;  Flag : USART_Status_Flag) is
  begin
     case Flag is
        when Parity_Error_Indicated =>
           This.Periph.ISR.PE := False;
        when Framing_Error_Indicated =>
           This.Periph.ISR.FE := False;
        when USART_Noise_Error_Indicated =>
           This.Periph.ISR.NF := False;
        when Overrun_Error_Indicated =>
           This.Periph.ISR.ORE := False;
        when Idle_Line_Detection_Indicated =>
           This.Periph.ISR.IDLE := False;
        when Read_Data_Register_Not_Empty =>
           This.Periph.ISR.RXNE := False;
        when Transmission_Complete_Indicated =>
           This.Periph.ISR.TC := False;
        when Transmit_Data_Register_Empty =>
           This.Periph.ISR.TXE := False;
        when Line_Break_Detection_Indicated =>
           This.Periph.ISR.LBDF := False;
        when Clear_To_Send_Indicated =>
           This.Periph.ISR.CTS := False;
     end case;
  end Clear_Status;
  -----------------------
  -- Enable_Interrupts --
  -----------------------
  procedure Enable_Interrupts
    (This   : in out USART;
     Source : USART_Interrupt)
  is
  begin
     case Source is
        when Parity_Error =>
           This.Periph.CR1.PEIE := True;
        when Transmit_Data_Register_Empty =>
           This.Periph.CR1.TXEIE := True;
        when Transmission_Complete =>
           This.Periph.CR1.TCIE := True;
        when Received_Data_Not_Empty =>
           This.Periph.CR1.RXNEIE := True;
        when Idle_Line_Detection =>
           This.Periph.CR1.IDLEIE := True;
        when Line_Break_Detection =>
           This.Periph.CR2.LBDIE := True;
        when Clear_To_Send =>
           This.Periph.CR3.CTSIE := True;
        when Error =>
           This.Periph.CR3.EIE := True;
     end case;
  end Enable_Interrupts;
  ------------------------
  -- Disable_Interrupts --
  ------------------------
  procedure Disable_Interrupts
    (This   : in out USART;
     Source : USART_Interrupt)
  is
  begin
     case Source is
        when Parity_Error =>
           This.Periph.CR1.PEIE := False;
        when Transmit_Data_Register_Empty =>
           This.Periph.CR1.TXEIE := False;
        when Transmission_Complete =>
           This.Periph.CR1.TCIE := False;
        when Received_Data_Not_Empty =>
           This.Periph.CR1.RXNEIE := False;
        when Idle_Line_Detection =>
           This.Periph.CR1.IDLEIE := False;
        when Line_Break_Detection =>
           This.Periph.CR2.LBDIE := False;
        when Clear_To_Send =>
           This.Periph.CR3.CTSIE := False;
        when Error =>
           This.Periph.CR3.EIE := False;
     end case;
  end Disable_Interrupts;
  -----------------------
  -- Interrupt_Enabled --
  -----------------------
  function Interrupt_Enabled
    (This   : USART;
     Source : USART_Interrupt)
     return Boolean
  is
  begin
     case Source is
        when Parity_Error =>
           return This.Periph.CR1.PEIE;
        when Transmit_Data_Register_Empty =>
           return This.Periph.CR1.TXEIE;
        when Transmission_Complete =>
           return This.Periph.CR1.TCIE;
        when Received_Data_Not_Empty =>
           return This.Periph.CR1.RXNEIE;
        when Idle_Line_Detection =>
           return This.Periph.CR1.IDLEIE;
        when Line_Break_Detection =>
           return This.Periph.CR2.LBDIE;
        when Clear_To_Send =>
           return This.Periph.CR3.CTSIE;
        when Error =>
           return This.Periph.CR3.EIE;
     end case;
  end Interrupt_Enabled;
  ----------------------------------
  -- Enable_DMA_Transmit_Requests --
  ----------------------------------
  procedure Enable_DMA_Transmit_Requests (This : in out USART) is
  begin
     This.Periph.CR3.DMAT := True;
  end Enable_DMA_Transmit_Requests;
  ---------------------------------
  -- Enable_DMA_Receive_Requests --
  ---------------------------------
  procedure Enable_DMA_Receive_Requests (This : in out USART) is
  begin
     This.Periph.CR3.DMAR := True;
  end Enable_DMA_Receive_Requests;
  -----------------------------------
  -- Disable_DMA_Transmit_Requests --
  -----------------------------------
  procedure Disable_DMA_Transmit_Requests (This : in out USART) is
  begin
     This.Periph.CR3.DMAT := False;
  end Disable_DMA_Transmit_Requests;
  ----------------------------------
  -- Disable_DMA_Receive_Requests --
  ----------------------------------
  procedure Disable_DMA_Receive_Requests (This : in out USART) is
  begin
     This.Periph.CR3.DMAR := False;
  end Disable_DMA_Receive_Requests;
  -----------------------------------
  -- DMA_Transmit_Requests_Enabled --
  -----------------------------------
  function DMA_Transmit_Requests_Enabled  (This : USART) return Boolean is
     (This.Periph.CR3.DMAT);
  ----------------------------------
  -- DMA_Receive_Requests_Enabled --
  ----------------------------------
  function DMA_Receive_Requests_Enabled  (This : USART) return Boolean is
     (This.Periph.CR3.DMAR);
  -----------------------------
  -- Resume_DMA_Transmission --
  -----------------------------
  procedure Resume_DMA_Transmission (This : in out USART) is
  begin
     Enable_DMA_Transmit_Requests (This);
     if not Enabled (This) then
        Enable (This);
     end if;
  end Resume_DMA_Transmission;
  --------------------------
  -- Resume_DMA_Reception --
  --------------------------
  procedure Resume_DMA_Reception (This : in out USART) is
  begin
     Enable_DMA_Receive_Requests (This);
     if not Enabled (This) then
        Enable (This);
     end if;
  end Resume_DMA_Reception;
  ---------------------------
  -- Data_Register_Address --
  ---------------------------
  function Data_Receive_Register_Address (This : USART) return System.Address is
    (This.Periph.RDR'Address);
  ---------------------------
  -- Data_Register_Address --
  ---------------------------
  function Data_Transmit_Register_Address (This : USART) return System.Address is
        (This.Periph.TDR'Address);
  ---------------
  -- Data_Size --
  ---------------
  overriding
  function Data_Size (This : USART) return HAL.UART.UART_Data_Size
  is
  begin
     if This.Periph.CR1.M0 then
        return Data_Size_9b;
     else
        return Data_Size_8b;
     end if;
  end Data_Size;
  --------------
  -- Transmit --
  --------------
  overriding
  procedure Transmit
    (This    : in out USART;
     Data    : UART_Data_8b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
  is
     pragma Unreferenced (Status, Timeout);
  begin
     for Elt of Data loop
        loop
           exit when This.Tx_Ready;
        end loop;
        This.Transmit (UInt9 (Elt));
     end loop;
     Status := Ok;
  end Transmit;
  --------------
  -- Transmit --
  --------------
  overriding
  procedure Transmit
    (This    : in out USART;
     Data    : UART_Data_9b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
  is
     pragma Unreferenced (Status, Timeout);
  begin
     for Elt of Data loop
        loop
           exit when This.Tx_Ready;
        end loop;
        This.Transmit (Elt);
     end loop;
     Status := Ok;
  end Transmit;
  -------------
  -- Receive --
  -------------
  overriding
  procedure Receive
    (This    : in out USART;
     Data    : out UART_Data_8b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
  is
     pragma Unreferenced (Status, Timeout);
  begin
     for Elt of Data loop
        loop
           exit when This.Rx_Ready;
        end loop;
        This.Receive (UInt9 (Elt));
     end loop;
     Status := Ok;
  end Receive;
  -------------
  -- Receive --
  -------------
  overriding
  procedure Receive
    (This    : in out USART;
     Data    : out UART_Data_9b;
     Status  : out UART_Status;
     Timeout : Natural := 1000)
  is
     pragma Unreferenced (Status, Timeout);
  begin
     for Elt of Data loop
        loop
           exit when This.Rx_Ready;
        end loop;
        This.Receive (Elt);
     end loop;
     Status := Ok;
  end Receive;
end STM32.USARTs;


10步:结果
过滤后的SuperIMU在振动时更稳定。来自振动的噪声由多量程配置值和冗余度补偿。
结果非常酷,在图片中,我在垂直握手时抓住了滚动,桌子上有一些小的震动(更多的测试会有严重的振动):
48.jpg
红色:单个IMU,黄色:过滤SuperIMU(滚动)

测量的加速度和陀螺仪速率的加权平均值有助于减少振动环境中的噪声(例如,握手)。然而,成本与获得性能的使用可能不值得结果
49.jpg
红色:单个IMU,黄色:过滤的SuperIMU



最后,使用以下命令使用生产编译模式:
sudo gprbuild --target=arm-eabi -d superimu.gpr -XLCH=led -XRTS_Profile=ravenscar-full -XLOADER=ROM -XADL_BUILD_CHECKS=Disabled -XADL_BUILD=Production superimu.adb

我能够在1ms内达成执行:
50.jpg

结论
经滤波的SuperIMU允许相对于振动和高加速度计算显着的俯仰和行的稳定值。
使用STM32F746 @200MHz通过过滤13x IMU并在1毫秒内估计一段时间内的音调,取得了良好的效果。
似乎对于火箭的需求,配置12G范围的单个BMI088足以满足火箭需求,而不是为超精确IMU支付300美元,使用两个BMI088突破可能会很有趣,每个25美元。
另一方面,在这个项目中,我学会了如何为STM32制作驱动程序(更新SPI驱动程序,制作UART驱动程序)以及制作BMI088芯片驱动程序。在嵌入式领域中,进行预检查,检查后具有强类型定义的可能性是一个真正的好处。与以前使用的C / C ++相比,在Ada中我不能错过UInt4和UInt8

这是一次有趣的体验,在成功完成项目的这一重大步骤后,我感兴趣的是为我的个人项目挖掘更多并为其他板和传感器制作其他Ada驱动程序。并且还使用Adacore框架继续开发火箭的自动驾驶仪。





评分

参与人数 1 ST金币 +8 收起 理由
g921002 + 8 很给力!

查看全部评分

收藏 评论0 发布时间:2019-3-12 23:33

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版