网志

单片机简介-Ada-7段和捕捉乐橙云app

迈克·席尔瓦2014年9月22日5条评论

快速链接

7段Ada方式

这是上一部分中介绍的7段多路复用代码的Ada版本(我应该说AN Ada版本)。 现在的硬件是STM32F407 Discover板,这是Cortex M4F板。 GPIO和计时器设置有很多区别,但是,如果您不懂C中的先前代码,那么在Ada中理解此代码就不会有太多麻烦。

与Ada处理任务一样有趣的是,Ada能够检测运行时乐橙云app,如下所述,无论是计划内还是计划外,这都证明了这一点。

Ada对我来说仍然是新手,因此毫无疑问,我没有充分利用该语言,并且在“ Ada中的C”方面写得太多了。 启动任何一种新语言,尤其是功能强大的语言,都是一个问题。

压缩的源文件 

一些细节

定时器6用于生成我们的2.5毫秒多路复用中断,之所以选择该定时器是因为它是芯片上功能最差的定时器之一。 将更好的计时器保存下来,以备不时之需。 ISR是一个称为TimerP的受Ada保护的对象中的过程 并附加到正确的中断向量。  包含受保护对象的程序包中还有其他帮助程序子程序(请记住,子程序可以是函数-返回内容-或过程-不返回任何内容)。 一种过程将显示提供的值,并将其转换为7段显示的正确格式。 另一个过程调整显示更新 介于100 /秒之间 (400 /秒中断)和1 /秒(4 /秒中断)。

主程序循环使用Ada运行时(使用延迟直到)建立一个100ms的循环。 每次通过此循环,计数器值或显示更新率将传递到要显示的计时器代码。 同样,每次通过时,四个按钮的读取方式也一样。 这次按钮的操作略有不同,因为现在这四个按钮动作已清除,开始/停止,更新率降低 并提高更新速度。 由于现在将一个按钮用作开始/停止切换,因此在处理该按钮时需要更多的逻辑。 在确定之前,如果按住该按钮,则要执行STOP ... STOP ... STOP ... STOP ...的每个循环,但是我们不想执行STOP ... START。 ..STOP ... START ...在一个按下的按钮上,因此需要附加的逻辑。

通过计算2.5ms中断中的40个,可以很容易地完成100ms主循环,但是随后循环定时取决于数字更新定时。 在以前的版本中,我们通过在主循环中使用第二个计时器,然后在两个跳越匹配中断之间使用一个计时器来避免这种情况。 在Ada中,为100ms循环使用内置的延迟功能非常有意义,因为这样做(使用延迟直到)可以释放处理器执行其他任务的能力。 它也不是特定于硬件的,要保持特定于硬件的代码尽可能地被限制和包含,有话要说。

最后,请注意如何将8段线再次划分为3个端口(!)。 在Ada中,我真的很喜欢布尔打包数组和数组切片的这种用法。 我认为这使位分配更加清晰。 我本可以直接将段值数组声明为打包的布尔数组,但是我认为这会增加太多混乱(很多True,True,False,True,False ...),因此我将它们声明为以base-初始化的Bytes 2个常数,并使用未经检查的转换来转换字节 放入打包的布尔数组中以进行位数组切片。 我在其他一些地方也做同样的事情,我只是认为将布尔数组值编码为Word或HWord更加容易和干净。

这是leds.adb,在这里我编写了一些新的GPIO初始化过程,以使设置GPIO的细节更加易于管理: 

-- leds.adb

with Ada.Unchecked_Conversion;

with LCD;
with MUX_Timer;

package body LEDs is


   function As_Word is new Ada.Unchecked_Conversion
     (Source => User_LED, Target => Word);


   procedure On (This : User_LED) is
   begin
      GPIOD.BSRR := As_Word (This);
   end On;


   procedure Off (This : User_LED) is
   begin
      GPIOD.BSRR := Shift_Left (As_Word (This), 16);
   end Off;


   All_LEDs_On  : constant Word := Green'Enum_Rep or Red'Enum_Rep or
                                   Blue'Enum_Rep  or Orange'Enum_Rep;

   pragma Compile_Time_Error
     (All_LEDs_On /= 16#F000#,
      "Invalid representation for All_LEDs_On");

   All_LEDs_Off : constant Word := Shift_Left (All_LEDs_On, 16);


   procedure All_Off is
   begin
      GPIOD.BSRR := All_LEDs_Off;
   end All_Off;


   procedure All_On is
   begin
      GPIOD.BSRR := All_LEDs_On;
   end All_On;

   procedure Init_GPIO(GPIO : in out GPIO_Register; I : Natural;
                     Mode : Bits_2;
                     Otype : Bits_1;
                     Speed : Bits_2;
                    PUtype : Bits_2) is
   begin
      GPIO.MODER   (I) := Mode;
      GPIO.OTYPER  (I) := Otype;
      GPIO.OSPEEDR (I) := Speed;
      GPIO.PUPDR   (I) := PUtype;
   end Init_GPIO;

   procedure Init_GPIO(GPIO : in out GPIO_Register; LO : Natural; HI : Natural;
                     Mode : Bits_2;
                     Otype : Bits_1;
                     Speed : Bits_2;
                     PUtype : Bits_2) is
   begin
      for I in LO..HI loop
        Init_GPIO(GPIO, I, Mode, Otype, Speed, PUtype);
      end loop;
   end Init_GPIO;

   procedure Init_GPIO(GPIO : in out GPIO_Register; Sel : Bits_16;
                     Mode : Bits_2;
                     Otype : Bits_1;
                     Speed : Bits_2;
                     PUtype : Bits_2) is
   begin
      for I in Sel'Range loop
        if Sel(I) then
           Init_GPIO(GPIO, I, Mode, Otype, Speed, PUtype);
        end if;
      end loop;
   end Init_GPIO;

   procedure Initialize is
      RCC_AHB1ENR_GPIOB : constant Word := 2#0_0010#;
      RCC_AHB1ENR_GPIOC : constant Word := 2#0_0100#;
      RCC_AHB1ENR_GPIOD : constant Word := 2#0_1000#;
      RCC_AHB1ENR_GPIOE : constant Word := 2#1_0000#;
      GPIO_B_Sel        : constant HWord := 2#1111_0000_0010_0011#; --12-15, 5, 0-1
      GPIO_C_Sel        : constant HWord := 2#0000_1001_0101_0110#;
      GPIO_D_Sel        : constant HWord := 2#1111_0000_1100_1100#; --12-15, 6-7, 2-3
   begin
      --  Enable clock for GPIO-D 和 GPIO-E
      RCC.AHB1ENR := RCC.AHB1ENR
       or RCC_AHB1ENR_GPIOB
       or RCC_AHB1ENR_GPIOC
        or RCC_AHB1ENR_GPIOD
        or RCC_AHB1ENR_GPIOE;
      -- Configure B segment outputs 和 digit outputs
      Init_GPIO(GPIOB, B16_From_HW(GPIO_B_Sel),
              Mode_OUT,
              Type_PP,
              Speed_2MHz,
              No_Pull);
      -- Configure C segment outputs 和 LED outputs
      Init_GPIO(GPIOC, B16_From_HW(GPIO_C_Sel),
              Mode_OUT,
              Type_PP,
              Speed_2MHz,
              No_Pull);
      -- Configure D segment outputs
      Init_GPIO(GPIOD, B16_From_HW(GPIO_D_Sel),
              Mode_OUT,
              Type_PP,
              Speed_2MHz,
              No_Pull);
      --  Configure PE4-9 (4-7 LCD data, 8 LCD RW, 9 LCD E
      Init_GPIO(GPIOE, 4, 9,
              Mode_OUT,
              Type_PP,
              Speed_25MHz,
              No_Pull);
      --  Configure PE2
      Init_GPIO(GPIOE, 2,
              Mode_OUT,
              Type_PP,
              Speed_25MHz,
              No_Pull);
      --  Configure PE15
      Init_GPIO(GPIOE, 15,
              Mode_OUT,
              Type_PP,
              Speed_2MHz,
              No_Pull);
      -- Configure PE10-13 (button inputs)
      Init_GPIO(GPIOE, MUX_Timer.B_CLEAR, MUX_Timer.B_FAST,
              Mode_IN,
              Type_PP,      -- don't care
              Speed_2MHz,   -- don't care
              Pull_Up);
   end Initialize;


begin
   Initialize;
   MUX_Timer.Init;
   LCD.LCD_Init;
end LEDs;

这是MUX_Timer.adb,其中包含受ISR保护的对象以及所有其他显示格式和多路复用代码:

-- Timer package

with Ada.Interrupts.Names;
with 寄存器s;                   use 寄存器s;
with STM32F4;                     use STM32F4;
with STM32F4.Timer;

package body MUX_Timer is

   -- the 4 digit enable bits, in the same order as Digit_Vals below
   Digit_Bit : constant array(DIGIT) of Word := (16#1000#, 16#2000#, 16#4000#, 16#8000#);

   -- the 4 digits to be displayed.  These are indexes into array Segs below
   Digit_Vals  : array(DIGIT) of SEG_INDEX := (others => SEG_INDEX'Last);

   -- segment patterns for the 10 digits 和 Blank (Segment A is LSB)
   Segs  : constant array(SEG_INDEX) of Byte := (16#3F#, 16#06#, 16#5B#, 16#4F#, 16#66#,
                                           16#6D#, 16#7D#, 16#07#, 16#7F#, 16#6F#,
                                            16#00#);

   protected TimerP is
      pragma Interrupt_Priority;

   private
      -- which digit is now being displayed
      Cur_Digit : DIGIT := 0;

      procedure Interrupt_Handler;
      pragma Attach_Handler
         (Interrupt_Handler,
          Ada.Interrupts.Names.TIM6_DAC_Interrupt);

   end TimerP;

   protected body TimerP is

      -- handler for Timer 6
      procedure Interrupt_Handler is
        -- these clear the segment outputs (spread over 3 ports!)
        Clear_B   : constant Word := 16#03_0000#;
        Clear_C   : constant Word := 16#06_0000#;
        Clear_D   : constant Word := 16#CC_0000#;
        B8        : Bits_8;
        W32       : Bits_32;
      begin
         --  Clear interrupt
        TIM6.SR := TIM6.SR - STM32F4.Timer.UIF;
        -- turn off current digit
        GPIOB.BSRR := Shift_Left(Digit_Bit(Cur_Digit), 16);
        -- clear all segment outputs
        GPIOB.BSRR := Clear_B;
        GPIOC.BSRR := Clear_C;
        GPIOD.BSRR := Clear_D;
        -- advance to next digit (3 rolls over to 0)
        Cur_Digit := Cur_Digit + 1;
        -- get next segment pattern in form of boolean array
        B8 := B8_From_B(Segs(Digit_Vals(Cur_Digit)));
        -- set port C segment outputs
        W32(0..31) := (others => False);
        W32(1..2) := B8(6..7);
        GPIOC.BSRR := W_From_B32(W32);
        -- set port B segment outputs
        W32(0..31) := (others => False);
        W32(0..1) := B8(4..5);
        GPIOB.BSRR := W_From_B32(W32);
        -- set port D segment outputs
        W32(0..31) := (others => False);
        W32(6..7) := B8(2..3);
        W32(2..3) := B8(0..1);
        GPIOD.BSRR := W_From_B32(W32);
        -- enable our new current digit
        GPIOB.BSRR := Digit_Bit(Cur_Digit);
      end Interrupt_Handler;
   end TimerP;

   -- config Timer 6 to generate 2.5ms interrupts
   procedure Init is
   begin
      RCC.APB1ENR := RCC.APB1ENR or TIM6EN; -- Timer 6 enable
      TIM6.PSC  := CLK_US - 1;              -- 2x 42MHz
      TIM6.CR1 := STM32F4.Timer.URS;
      TIM6.DIER := TIM6.DIER or STM32F4.Timer.UIE;
      Set_Interrupt_Time(UPDATE_RATE'Last);
   end Init;

   -- enable Timer 6
   procedure Enable is
   begin
      TIM6.CR1:= TIM6.CR1or STM32F4.Timer.CEN;  -- enable timer
   end Enable;

   procedure Set_Val(Value : DISP_VAL; Blank_LZ : Boolean) is
      Val       : Natural;
      Digit_Val : Natural;
      Digit     : Natural;
      BLZ       : Boolean := Blank_LZ;
   begin
      Val := Natural(Value);
     Digit_Val := Val / 1000;
      if (Digit_val = 0) 和 BLZ then
        Digit := Blank;
      else
        Digit := Digit_Val;
        BLZ := False;
      end if;
      Digit_Vals(0) := SEG_INDEX(Digit);
      Val := Val mod 1000;

      Digit_Val := Val / 100;
      if (Digit_val = 0) 和 BLZ then
        Digit := Blank;
      else
        Digit := Digit_Val;
        BLZ := False;
      end if;
      Digit_Vals(1) := SEG_INDEX(Digit);
      Val := Val mod 100;

      Digit_Val := Val / 10;
      if (Digit_val = 0) 和 BLZ then
        Digit := Blank;
      else
        Digit := Digit_Val;
      end if;
      Digit_Vals(2) := SEG_INDEX(Digit);
      Val := Val mod 10;

      Digit := Val;
      Digit_Vals(3) := SEG_INDEX(Digit);
   end Set_Val;

   procedure Set_Interrupt_Time(Per_Sec : UPDATE_RATE) is
   begin
      TIM6.ARR:= HWord((((1_000_000 / PRE_US)/(4 *整数(Per_Sec)))-1);
   end Set_Interrupt_Time;


end MUX_Timer;

最后,主循环:

-- sevenseg.adb

with Last_Chance_Handler;  pragma Unreferenced (Last_Chance_Handler);
with System;
with Ada.Real_Time; use Ada.Real_Time;
with 寄存器s;     use 寄存器s;
with LEDs;          use LEDs;
with MUX_Timer;     use MUX_Timer;
with STM32F4;       use STM32F4;

procedure SevenSeg is
   pragma Priority (System.Priority'First);
   Next_Time : Time := Clock;
   Tenths    : DISP_VAL := 0;
   Per_Sec   : UPDATE_RATE := 100;
   Buttons   : Bits_32;
   Rate      : Boolean := False; -- True when showing rate, False showing Tenths
   Stop      : Boolean := False;
   Last_Stop : Boolean := False;
begin
   On(Green);
   MUX_Timer.Enable;
   loop
      MUX_Timer.Set_Interrupt_Time(Per_Sec);

      Buttons := B32_From_W(not GPIOE.IDR); -- read buttons @ 10..13
      if Buttons(B_CLEAR) then
        Tenths := 0;
        Rate := False;  -- showing tenths, not display rate
      end if;

      if Buttons(B_SS) 和 not Last_Stop then  -- found a leading button edge
        Stop := not Stop;
        Rate := False;  -- showing tenths, not display rate
      end if;
      Last_Stop := Buttons(B_SS);
      if not Stop then
        Tenths := Tenths + 1;
      end if;

      if Buttons(B_SLOW) then  -- slow down
        Per_Sec := Per_Sec - 1;
        Rate := True;  -- showing display rate
      end if;

      if Buttons(B_FAST) then  -- speed up
        Per_Sec := Per_Sec + 1;
        Rate := True;  -- showing display rate
      end if;

      if Rate then
        -- pass current time in 1/10 sec units to timer display
        MUX_Timer.Set_Val(DISP_VAL(Per_Sec), False);
      else
        -- pass current time in 1/10 sec units to timer display
        MUX_Timer.Set_Val(Tenths, True);
      end if;

     -- calculate next 100ms clock point 和 sleep until then
      Next_Time := Next_Time + Milliseconds(100);
      delay until Next_Time;
   end loop;
end SevenSeg;

知道了!

Ada的众多好处之一是,编译器可以生成自动检查,以确定是否有任何变量超出其分配的限制。 这包括但不限于数组索引(Ada中没有缓冲区溢出!)。 如果此类变量确实超出了其分配的限制,则会生成异常。 像所有异常一样,可以在本地捕获此异常(“处理”是Ada术语),也可以传播并处理该异常。 in an outer scope. 由于异常的传播涉及大量的代码和时间资源,因此在Ravenscar配置文件中是不允许的,但是有一个“最后机会处理程序”将捕获您的代码无法处理的任何异常 在当地情况下。

他们的ARM产品的AdaCore最终机会处理程序标识发生异常的源文件和行,因此您要做的就是编写代码以将该信息传达给用户。 因为我们的LCD显示屏可以正常工作,所以很容易将此异常信息写入到显示屏中。

要生成异常,我们可以通过增加(或减少)超出合法范围的范围值来简单地引起“约束乐橙云app”。 在sevenseg.adb中,我们有一个显示更新率的范围值,声明为1..100,因此,如果我们超出这些范围,我们将得到一个乐橙云app,并在显示屏上看到异常消息,将我们精确地指向违规行。 现在是文明编程! 这是一个人为的乐橙云app,旨在显示Ada的异常处理,但在测试过程中 它的Ada运行时也捕获了实际的超出范围的乐橙云app。 以后再说。 这是最后一次向LCD写入的机会处理程序:

-- last_chance_handler.adb

with System.Storage_Elements;  use System.Storage_Elements;
with Ada.Unchecked_Conversion;
with LEDs;                     use LEDs;
with LCD;                      use LCD;

package body Last_Chance_Handler is

   type Char_Pointer is access all Character;
   for Char_Pointer'Storage_Size use 0;

   function As_Char_Pointer is
      new Ada.Unchecked_Conversion (Integer_Address, Char_Pointer);

   -------------------------
   -- Last_Chance_Handler --
   -------------------------

   procedure Last_Chance_Handler (Msg : System.Address; Line : Integer) is
--      pragma Unreferenced (Msg, Line);
      P : Integer_Address;
   begin
      Off (Green);
      Off (Orange);
      Off (Blue);
      On (Red);
      --  No return procedure.
      pragma Warnings (Off, "*rewritten as loop");
      
      P := To_Integer (Msg);
      LCD_Clear;
      LCD_Put_String("--Exception--");
      LCD_Goto(0, 2);
      
      loop
         LCD_Put_Char (As_Char_Pointer (P).all);
         P := P + 1;
         exit when As_Char_Pointer (P).all = ASCII.Nul;
      end loop;
      
      LCD_Put_String(" " & Integer'Image(Line));
      
      <<spin>>旋转-是的,goto!
      pragma Warnings (On, "*rewritten as loop");
   end Last_Chance_Handler;

end Last_Chance_Handler;

第二件事第一!

我想在这里写一个有关Ada运行时为我捕获的经典乐橙云app的类型,但是首先,我将讨论另一个乐橙云app,这个乐橙云app是任何语言都找不到的。 在测试慢速/快速按钮时,我遇到了另一个乐橙云app,这是一个我知道可能会发生的乐橙云app,我打算针对此乐橙云app进行编码,但是我却以某种方式忘记了。 当您在计时器运行时使ARR值(或任何匹配寄存器值)变小时,可能会发生此问题。 如果您不走运(您一定会!),您将在计时器传递新值后写出较小的值,因此计时器将一直运行到其自然溢出(2 ^ 16或2 ^ 32个计数) ),然后再次进行正常操作。

为了给出一些具体的数字,假设您将ARR寄存器设置为2000,并且当您向ARR寄存器加载1000时,CNT现在传递1500。 CNT永远不会看到ARR中的2000,它将一直运行到自然溢出,然后才能看到新的1000 ARR值。 如您所见,如果加载是在CNT处于两个值之间的范围内时进行的,则在加载小于当前ARR值的ARR值时总是会发生此问题。 要查看该乐橙云app,请将更新率降低至接近1,然后按住“更快”按钮。 您将看到一个或多个次数停止并发光,然后正常行为继续。 这是计时器自然溢出时所显示的数字,导致其显示的时间比原本应显示的时间长得多。 计时器(PRE_US)为5而不是1时,该乐橙云app在视觉上会更明显,但是在任何一种情况下都将发生。 请参阅下一节以了解此注释。

通常,计时器硬件会以一种影子或缓冲寄存器的形式来避免此问题,该寄存器保留新的ARR值,并且仅在计时器变为零时才加载该寄存器(在此加载匹配寄存器始终是安全的)点)。 这是我要在计时器配置中设置的位(ARPE位强制使用 预加载寄存器),但忘记了。 在99%的情况下有效的另一种解决方案是在定时器中断ISR中手动加载新的ARR值。 在ISR中,您知道计时器值非常接近0-接近自从引发并响应计时器中断并达到ARR设置代码以来发生的计时器滴答次数。 这可能是一微秒。 只要新的ARR值超出该值,就可以安全地将其加载到ISR中。 在我们的例子中,我们加载的最小ARR值等于2.5ms或2500us,因此我们将完全 在计时器翻转到零后的前几微秒内可以安全地加载ARR。

要使用内置的预加载寄存器修复乐橙云app,请在MUX_Timer.adb过程中,通过Init替换该行

TIM6.CR1:= STM32F4.Timer.URS;

TIM6.CR1 := STM32F4.Timer.URS 或STM32F4.Timer.ARPE; -同步加载ARR 

现在,继续讨论Ada确实捕获的意外但经典的乐橙云app。

被我自己的代码破坏!

好吧,我继续进行测试,并尝试将显示更新率一直降低到1,然后是0,以生成异常,但是事情并没有按计划进行。 我降低了更新速度,从100 / sec开始,过了一会儿我遇到了异常,但是更新速度没有按照预期的那样从1变为0,并且不在我期望的文件中。 实际上是从4到3的更新速率发生的异常,它在以下行的Set_Interrupt_Time过程中:

 TIM6.ARR:= HWord((((1_000_000 / PRE_US)/(4 *整数(Per_Sec)))-1);

一旦查看了违规行,我立即知道了问题所在。 对于1us预分频器输出时钟和更新速率<= 3时,计算得出的值大于可以装入16位无符号寄存器的最大值(对于3,计算值为83,332)。 在将计算值83,332转换为16位HWord时产生了异常。 这类似于C强制转换,但当然要加上所有 适当的范围检查。 C会产生乐橙云app的值并破坏您的周末。

解决方案很简单,特别是考虑到该计时器硬件的多功能性。 向后计算,更新速率为1 /秒,转换为每250ms(250,000us)一次的中断速率。 使用我们的1us预分频器输出,我们可以坚持 16位ARR寄存器中的数字。 快速检查建议将预分频器输出更改为5us,这将产生最大 ARR值为50,000(实际上是49,999),这个数字在我们的16位范围内。 我是完全可配置的预分频器以及它们产生的不错的整数的忠实粉丝! 因此,进行一次简单的更改(从PRE_US:= 1到PRE_US:= 5)解决了该问题,现在,当尝试将更新速度降至1以下或100次以上时,代码确实会引起我想演示的乐橙云app。 尝试将PRE_US切换回1,然后从4变为3时,您将看到所讨论的乐橙云app。

这是一个视频,显示了代码的行为以及捕获到的两种约束乐橙云app。

所以呢?

我可以肯定有些人在想:“那又如何呢? 这些本来就是在C中也可以找到的简单乐橙云app。” 是的,至少对于有经验的程序员而言(毕竟这是一个很小的程序),但这与IMO无关。 有些乐橙云app需要几分钟才能找到并修复,而其他乐橙云app则需要几天或几周的时间。 您可以保证只引入以前的乐橙云app吗? 在竞争激烈的世界中,每一个被阻止或早早发现的乐橙云app都是一个优势。 持续花费较少时间和金钱来修复乐橙云app的开发人员或公司将提高生产力,并在市场上取得更好的成绩,这只是基本的bean计数数学。

如果您的CEO来问您是否正在使用最有生产力的工具,您的答案是什么? 如果您是自己的首席执行官,您是否曾问过自己这个问题?


[-]
评论者 阿斯特罗·杰里2014年9月21日
好文章迈克。我有关Ada和ARM Cortex的文章今天在“电子设计”上发布,网址为:
http://electronicdesign.com/dev-tools/armed-and-ready

对于使用Ada发现乐橙云app的优势,我完全同意。大多数都在编译期间被捕获,这使得不需要大量调试。
[-]
评论者 布赖恩·德拉蒙德2014年9月27日
异常指向实际乐橙云app的准确度可能令人毛骨悚然!当您遇到这种情况时,仅此一个令人信服的案例即可切换到Ada ...

如果您有兴趣,我可以在Ada上使用另一个7段显示驱动器(实际上是1970年代风格的数字手表),该驱动器在更小的处理器MSP430上运行-完整的手表不到一千字节...

在“示例”中 http://sourceforge.net/projects/msp430ada/ MSP430现在是官方目标(需要更新以更新gcc版本)(并且要显示真正的手表PCB ...直径为33mm,这是相当小的Ada目标)。这被拖延了,因为用于gcc4.7的“非官方” MSP430后端仍比官方gcc4.9产生更严格的代码...
[-]
评论者 BillyBits2014年9月27日
好东西。但是我想我在您的列表中发现了一个乐橙云app。

<>旋转-是的,goto!

我认为应该...

<>旋转-是的,更正后的goto。

纯粹主义者必须对使用goto视而不见,而完美主义者必须对乐橙云app进行视线。

保持良好的代码。
[-]
评论者 BillyBits2014年9月27日
现在,我看到网页上的内容正在吃掉Ada标签语法!我很抱歉。
[-]
评论者 亚诺2015年12月18日
大家好,

我想为Ada和Cortex-M4中的多直升机的可靠飞行控制器创建一个开源项目。它主要基于CAN总线,串行(用于IMU)和PWM输出,仅此而已。对于这个项目,我正在寻找撰稿人和Ada-ARM专家。我还觉得创建一个专门针对Ada-ARM的“小组”或“论坛”是一个好主意。有人对此感兴趣吗?我有开发硬件赠送:)
如有兴趣,请给我发送电子邮件:jarno @a2tech.it

要发布对评论的回复,请单击每个评论所附的“回复”按钮。要发布新评论(而不是回复评论),请查看评论顶部的“写评论”标签。

注册后,您可以参加所有相关网站上的论坛,并获得所有pdf下载的访问权限。

注册

我同意 使用条款隐私政策.

试试我们偶尔但很受欢迎的时事通讯。非常容易退订。
或登录