单片机简介-Ada-7段和捕捉乐橙云app
快速链接
- 第1部分: 单片机简介-入门
- 第2部分: 单片机简介-进一步的入门
- 第3部分: 单片机简介-Hello World
- 第4部分: 微控制器介绍-有关GPIO的更多信息
- 第5部分: 微控制器简介-中断
- 第6部分: 单片机简介-有关中断的更多信息
- 第7部分: 单片机简介-计时器
- 第8部分: 微控制器简介-添加一些实际硬件
- 第9部分: 单片机简介-更多计时器和显示
- 第10部分: 单片机简介-按钮和弹跳
- 第11部分: 单片机简介-按钮矩阵和自动重复
- 第12部分: 微控制器简介-驱动WS2812 RGB LED
- 第13部分: 单片机简介-7段显示器和多路复用
- 第14部分: 单片机简介-Ada-7段和捕捉乐橙云app
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的优势,我完全同意。大多数都在编译期间被捕获,这使得不需要大量调试。
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产生更严格的代码...
如果您有兴趣,我可以在Ada上使用另一个7段显示驱动器(实际上是1970年代风格的数字手表),该驱动器在更小的处理器MSP430上运行-完整的手表不到一千字节...
在“示例”中 http://sourceforge.net/projects/msp430ada/ MSP430现在是官方目标(需要更新以更新gcc版本)(并且要显示真正的手表PCB ...直径为33mm,这是相当小的Ada目标)。这被拖延了,因为用于gcc4.7的“非官方” MSP430后端仍比官方gcc4.9产生更严格的代码...
[-]
评论者 ●2014年9月27日

好东西。但是我想我在您的列表中发现了一个乐橙云app。
<>旋转-是的,goto!
我认为应该...
<>旋转-是的,更正后的goto。
纯粹主义者必须对使用goto视而不见,而完美主义者必须对乐橙云app进行视线。
保持良好的代码。
<>旋转-是的,goto!
我认为应该...
<>旋转-是的,更正后的goto。
纯粹主义者必须对使用goto视而不见,而完美主义者必须对乐橙云app进行视线。
保持良好的代码。
[-]
评论者 ●2014年9月27日

现在,我看到网页上的内容正在吃掉Ada标签语法!我很抱歉。
[-]
评论者 ●2015年12月18日

大家好,
我想为Ada和Cortex-M4中的多直升机的可靠飞行控制器创建一个开源项目。它主要基于CAN总线,串行(用于IMU)和PWM输出,仅此而已。对于这个项目,我正在寻找撰稿人和Ada-ARM专家。我还觉得创建一个专门针对Ada-ARM的“小组”或“论坛”是一个好主意。有人对此感兴趣吗?我有开发硬件赠送:)
如有兴趣,请给我发送电子邮件:jarno @a2tech.it
我想为Ada和Cortex-M4中的多直升机的可靠飞行控制器创建一个开源项目。它主要基于CAN总线,串行(用于IMU)和PWM输出,仅此而已。对于这个项目,我正在寻找撰稿人和Ada-ARM专家。我还觉得创建一个专门针对Ada-ARM的“小组”或“论坛”是一个好主意。有人对此感兴趣吗?我有开发硬件赠送:)
如有兴趣,请给我发送电子邮件:jarno @a2tech.it
要发布对评论的回复,请单击每个评论所附的“回复”按钮。要发布新评论(而不是回复评论),请查看评论顶部的“写评论”标签。
注册后,您可以参加所有相关网站上的论坛,并获得所有pdf下载的访问权限。