完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
大家好,我想从我的ISR返回跳过不必要的if语句,缩短ISR的处理时间,如果可能的话,我已经破解了Microchip论坛,虽然有关于这个主题的话题我没有找到一个坚实的答案。是否应该在我的ISR程序结束时放置GOTO标签,每当我需要一个快捷方式时就跳到那里,或者像正常函数一样,添加一个返回语句是否安全?这里是我的ISR。(我使用的是DSPIC33 EP64 GS804)。
以上来自于百度翻译 以下为原文 Hello people , I want to return from my ISR to skip the unnecessary if else statements and shorten the ISR process time if possible, I have seached the Microchip forums and although there are topics regarding the subject I failed to find a solid answer . Should ı just put a goto label at the end of my ISR routine and jump there whenever I want a shortcut or is it safe to add a return statement as I would do with normal functions ? Here ismy ISR. (I am using DSPIC33EP64GS804. ) void __attribute__ ( (interrupt, no_auto_psv) ) _U1RXInterrupt( void ) { IFS0bits.U1RXIF = 0; static uint8_t i = 0 ; receive_buffer = U1RXREG ; if (i == 1) { if (receive_buffer[1] == 0x03 && (device_address == receive_buffer[0] )) { STATE = READ_HOLDING_REGISTERS ; } else if ( receive_buffer[1] == 0x03 && (device_address != receive_buffer[0] ) ) { STATE = READ_HOLDING_REGISTERS_AND_DISCARD ; } else if (receive_buffer[1] == 0x10 && (device_address == receive_buffer[0] )) { STATE = PRESET_MULTIPLE_REGISTERS ; } else if (receive_buffer[1] == 0x10 && (device_address != receive_buffer[0] )) { STATE = PRESET_MULTIPLE_REGISTERS_AND_DISCARD ; } else if (receive_buffer[1] == 0x06 && (device_address == receive_buffer[0] )) { STATE = PRESET_SINGLE_REGISTER ; } else if (receive_buffer[1] == 0x06 && (device_address != receive_buffer[0] )) { STATE = PRESET_SINGLE_REGISTER_AND_DISCARD ; } } if (STATE == READ_HOLDING_REGISTERS && i == 5 ) { STATE = TRANSMIT_READ_HOLDING_REGISTERS ; buffer_size = i ; } else if (STATE == READ_HOLDING_REGISTERS_AND_DISCARD && i == 5 ) { STATE = RECEIVE ; i = 0; } else if (STATE == PRESET_MULTIPLE_REGISTERS && i == ( 6 + receive_buffer[6]) ) { STATE = TRANSMIT_PRESET_MULTIPLE_REGISTERS ; buffer_size = i + receive_buffer[6]; } else if (STATE == PRESET_MULTIPLE_REGISTERS_AND_DISCARD && i == ( 6 + receive_buffer[6]) ) { STATE = RECEIVE ; i = 0; } else if ((STATE == PRESET_SINGLE_REGISTER && i == 5 )) { STATE = TRANSMIT_PRESET_SINGLE_REGISTER ; buffer_size = i ; } else if ((STATE == READ_HOLDING_REGISTERS_AND_DISCARD && i == 5 )) { STATE = RECEIVE ; i = 0; } i++; } |
|
相关推荐
14个回答
|
|
“延迟”通常描述从中断条件发生到处理时的延迟。在假设有问题之前,应该检查编译器输出的汇编代码。
以上来自于百度翻译 以下为原文 "Latency" generally describes the delay from the interrupt condition occurring, to when you handle it. You should examine the assembly code output by the compiler before assuming you have a problem at all. |
|
|
|
“延迟”通常描述从中断条件发生到处理时的延迟。在假设有问题之前,应该检查编译器输出的汇编代码。对不起,我认为这意味着代码在ISR内部的时间。谢谢你的指正。
以上来自于百度翻译 以下为原文 "Latency" generally describes the delay from the interrupt condition occurring, to when you handle it. You should examine the assembly code output by the compiler before assuming you have a problem at all. I am sorry , I thought it means the time the code spends inside of the ISR. Thank you for the correction. |
|
|
|
同意,我认为通过优化代码或在ISR之外做更多的处理,您有可能缩短ISR等待时间(或者在ISR中花费的时间)。If elif if物质本身并不是罪魁祸首。汇编代码“跳转”无论如何,不需要让你的源代码更难读“GOTO”。
以上来自于百度翻译 以下为原文 Agreed, I think you have some potential to shorten your |
|
|
|
在ISR中使用返回是可以接受的。编译器会做正确的事情,或者清理并使用“来自中断的返回”指令,或者更可能跳转到共同的退出代码,也同意,检查汇编程序应该是“正常”程序的任何中断例程!
以上来自于百度翻译 以下为原文 Using a return is acceptable in an ISR. The compiler will do the right thing and either clean up and use the "return from interrupt" instruction or more likely jump to the common exit code that does that. Also agree, examining the assembler should be 'normal' procedure for any interrupt routine! |
|
|
|
公平地说,如果有多个中断源同时激活,具有相同的优先级,那么在一个中断服务中花费的任何时间将有助于另一个源的延迟。然而,最初的问题似乎更多地是围绕着流行语而不是实际理解。你可能有什么问题(如果有的话)。
以上来自于百度翻译 以下为原文 To be fair, if you have multiple interrupt sources all active at the same time, with the same priority, then any time spent inside one interrupt service will contribute to latency for the other source. However, your initial question seemed more about throwing around buzzwords than actually understanding what problem (if any) you might have. |
|
|
|
如果你从中断中删除所有的处理代码,只留下填充缓冲区的代码,那就更容易(而且对中断也更好)。然后,当你在缓冲区中有足够的数据处理时(在你的情况下看起来是5),你可以在主处理中同时处理。循环。通常可以在这种情况下使用的缓冲器是一个循环缓冲器,它由中断填充,然后在主循环中读取(谷歌IT)。这样的安排确保你不会丢失任何符号。在你的当前代码中,如果在处理过程中接收到两个符号,UART的FIFO中的一个点将丢失,这将最终溢出UART的FIFO,从而完全停止通信。记住,如果你有更高优先级的中断,它们可能发生在处理的中间,在中断中增加你的时间。检查汇编程序不会有什么害处,但是从这样的检查中得出任何结论是不好的,编译器可以改变它。任何时候。如果你从现在开始重新编译五年,编译器会产生不同的汇编输出,导致符号丢失,你需要很长的时间来解决这个问题。相反,给它足够的余量来满足你的时间,而不管编译器会向你扔什么。
以上来自于百度翻译 以下为原文 It would be easier (and better for the interrupt) if you removed all the processing code from the interrupt and only left the code which fills in the buffer. Then when you've got enough data in the buffer for processing (which appears to be 5 in your case), you could process it all at once in the main loop. Usually the kind of buffer which can be used in this situation is a circular buffer, which is filled in by the interrupt and then read in the main loop (google it). Such arrangement makes sure that you do not lose any symbols. With your current code, if you receive two symbols during your processing, one spot in UART's FIFO will be lost, which will eventually overflow the UART's FIFO halting the communication completely. And keep in mind that if you have higher priority interrupts, they may happen in the middle of your processing adding to your time inside the interrupt. Examining the assembler won't do any harm, but it is really bad idea to draw any conclusions from such examination - the compiler can change this at any time. If you recompile it five years from now and compiler produces different assembler output causing symbol losses, it'll take you really long time to figure this out. Instead, give it enough margin to meet your timing regardless of what the compiler may throw at you. |
|
|
|
不要使用GOTO,而是使用返回。编译器足够聪明,知道它是从IututBufftTyHuffl()返回的{BLAH(无论是真的){返回;//从中断处理程序的早期返回}正常处理,如果是,否则,等等…} / /在中断结束时隐含的返回。编译器会填充它,或者你可以做一个显式的返回。如果你真的返回;}编译器可能会跳转到最后一个返回来替换你的早期返回,因为在一个中断处理程序中,某些寄存器被保存到堆栈并自动恢复,所以在STAT中没有变化。e.
以上来自于百度翻译 以下为原文 Don't use a goto. Instead use a return. The compiler is smart enough to know that it is a return from interupt interrupt_handler() { blah if (whatever is true ) { return; // early return from interrupt handler } normal processing if then else etc... } // implied return at end of interrupt. compiler will fill it in, or you can do an explicit return here. if you do return; } the compiler will probably replace your early return with a jump to the last return, because in an interrupt handler, certain registers get saved to the stack and restored automatically, so there is no change in state. |
|
|
|
我已经实现了许多ISR状态机。我在ISR的代码就像你一样。这取决于你的波特率和你处理事情的速度。如果ISR是最高优先级,那么你就受到IRQ处理时间的限制。如果有更高优先级的中断,那么你就被它们限制了。我同意NothGee,最好使用中断来缓冲字符,A。设置一个信号量,或者提供一些主回路可以扫描的指示,并且尽可能短地保持中断。我最近在8051中遇到了这个确切的问题。在250000波特的数据流中发送一个数据流,其中处理器时钟为24000000,在到达的字符之间只留下1000个时钟滴答。本质上,250000意味着每个字符45个ESEC。如果你有一个更高优先级的中断,它占用了超过45的硬件缓冲区的USEC*深度,你就失去了和我一样的字符。我有一个125 USEC范围的高优先级中断,这可能导致UART中断的阻塞足够长,导致8051 UART中的1个字符的深度FIFO被重写了两倍。在这种情况下,我的解决方案是在进入中断时和在PROC之后增加硬件PIN。字符(不管花了多长时间),然后放下硬件引脚。发送方将只发送下一个字符时,看到PIN是低。只要引脚高,它就会挂起,然后将字符发送到PIC生成的串行处理程序。这当然有问题。如果插销永远不会变低怎么办?所以我通过把PIC上的输入引脚变成通知中断的改变来修复它。(传输在主回路中)。cn中断设置了几个位标志,这样我就可以检查它们。一个设置在一个低到高的过渡,第二个设置在一个高到低的过渡,并且记录时间(基于一个在全站时钟速度下没有中断的计时器),所以我知道脉冲的宽度。在主要的发送程序中,我检查了这个结果。AKE介于8051和PIC之间,其中8051个将通知PIC,它已经成功接收了发送的字符。我没有与8051的追索权。您可以在主循环中创建状态机,通过读取ISRSTREST中的循环缓冲区中的字符,而不是B。UNIH如果I/ER语句尝试这个:状态=WaITyStx;在Irq()内{get字符,缓冲它。开关(状态)情况WAITYSTX:获得消息的开始,缓冲它,并将状态设置为WaiTyeOOT中断;CASE WaITyOOT:如果(字符不是EOT,缓冲它并返回),否则状态=完成;/ /告诉地面,你现在有一个消息。中断;事件完成:中断;}这是一个简单的状态机,它将某些字符模式识别为消息的开始,而一些字符模式作为消息的结尾。对于二进制协议,需要以不同的方式进行。我最喜欢的是引用DLE.DLE STX是两个字符开始序列DLE是发送每当你需要发送一个独立的DLE作为DATADLE EX的一部分是两个字符结束序列状态机是相当简单的。在坚果外壳中,当你看到一个DLE时,你会进入一个中间状态,观察下一个字符。如果是STXX,则清除缓冲区和缓冲区DLE STX(如果您想要整个包),或者只清除缓冲区。然后将状态设置为GoTyStxx,如果字符是EOT(在你看到一个DLE之后),那么你就有了消息的末尾,如果你想要它,Del-STX缓冲区,或者只要字符是一个DLE(DLE),然后缓冲1 DLE,然后将状态恢复到Waig-OutHooSooDooDy。
以上来自于百度翻译 以下为原文 I have implemented many isr state machines. I have code in isr's like you have. It all depends on your baudrate and how fast you need to handle things. If the isr is highest priority, then you are bounded by the IRQ handling time. if there are higher priority interrupts, then you are bounded by them. I agree with NorthGuy, it is better to use the interrupt to buffer characters, and set a semaphore or provide some other indication that the main loop can scan, and keep the interrupt as short as possible. I recently encountered this exact problem in an 8051. Sending it a stream of data at 250,000 baud, where the processor clock is 24,000,000 leaves just 1000 clock ticks between characters arriving. Essentially, 250,000 means 45 usec per character. If you have a higher priority interrupt, that takes more than 45 usec * depth of the hardware buffer, you lose characters like I was. I had a 125 usec wide high priority interrupt that could result in a blocking of the uart interrupt long enough, to cause the 1 character deep fifo in the 8051 uart to be overwritten as many as two times. My solution in that case, was to raise a hardware pin on entering the interrupt, and after processing the character (no matter how long that took) then drop the hardware pin. The sender would only send the next character when it saw the pin was low. It would hang as long as the pin was high, and then send the character to the PIC generated serial handler. that of course had a problem. What if the pin never went low. So I wound up fixing that by making the input pin on the pic into a change on notification interrupt. (the transmission was in the main loop). The CN interrupt set a couple of bit flags such that I could examine them. One was set on a low to high transition, and the second was set on a high to low transition, and the time (based off a timer running without interrupts at full sys clock speed) was recorded, so I knew the width of the pulse. In the transmit routine in the main, I checked that. The upshot was a handshake between the 8051 and the pic, where the 8051 would inform the pic that it had successfully received a sent character. I had no recourse with the 8051. You can create your state machine in the main loop, by reading the characters out of the circular buffer that the isr maintains. And rather than a bunch if if/else statements try this: state=WAIT_STX; inside of your irq() { get character, buffer it. switch (state) case WAIT_STX: got start of message, buffer it, and set the state to WAIT_EOT break; case WAIT_EOT: if (character is not EOT, buffer it and return) else state=DONE; // tell the forground that you now have a message. break; case DONE: break; } This is a simple state machine that recognizes some character pattern as the start of a message, and some character pattern as the end of the message. For a binary protocol, one needs to do it differently. My favorite is a quoted DLE. DLE STX is the two character start sequence DLE DLE is send whenever you need to send a standalone DLE as part of the data DLE ETX is the two character end sequence The state machine is pretty simple. in a nut shell, when you see a dle, you go to an intermediate state that looks at the next character. If it is a STX then clear your buffer and buffer DLE STX (if you want the entire packet) or just clear the buffer if not. Then set the state to GOT_STX if the character is a EOT (after you see a DLE) then you have the end of message, buffer either the DLE STX if you want it, or just stet the state to DONE if the character is a DLE ( DLE DLE) then buffer 1 DLE, and change the state back to the WAIT_EOT Cheers Woody |
|
|
|
我只想补充一下,如果你的UART模块有一个状态指示RX缓冲器有数据,那么你可以在一个中断服务调用中读取所有可用字符。实际上,Do…在这种情况下会更有效。文斯。
以上来自于百度翻译 以下为原文 I would just add that if your UART module has a status indicating the Rx buffer has data, then you could read all available chars on one interrupt service call. while(UART_Rx_buffer_has_chars) { // read the next char // handle the char via state machine, etc. } Actually, a do..while would be more efficient in this case. Vince |
|
|
|
谢谢这些令人惊奇的答案。只使用RX ISR来填充我的循环缓冲器听起来不错,但由于我正试图实现RTU Modbus,所以包的开始和结束用3,5个字符长的延迟来表示,只有包长度信息留给我来知道会发生什么。(包长度是由第二个接收字节加上DATAL长度字节计算),所以我必须有一个先例,它帮助我识别我正在接收什么样的包,以及在接收ISR期间我的包要多长时间,这样我就可以知道什么时候开始处理已接收的包。我会尝试代码的回报和希望,太多的if语句不会破坏我的处理时间。再次感谢:新版本:
以上来自于百度翻译 以下为原文 Thanks for the amazing answers . Using RX ISR solely for filling up my circular buffer sounds nice but since I am trying to implement RTU MODBUS , start and end of packets are indicated by 3,5 characters long delays ,only package length information is left for me to know what to expect. (packet length is calculated by the second recieved byte plus the data_length byte ) , so I have to have a precedure which helps me identfy what kind of package I am receiving and how long will my package be during the receiving ISR so I can know when to start processing the recieved package. I will try the code with returns and hope , too many if statements wont ruin my handling time. Thank you again :) New version : void __attribute__ ( (interrupt, no_auto_psv) ) _U1RXInterrupt( void ) { IFS0bits.U1RXIF = 0; static uint8_t i = 0 ; receive_buffer[i++] = U1RXREG ; if (i == 2) { if (receive_buffer[1] == 0x03 && (device_address == receive_buffer[0] )) { STATE = READ_HOLDING_REGISTERS ; return ; } else if ( receive_buffer[1] == 0x03 && (device_address != receive_buffer[0] ) ) { STATE = READ_HOLDING_REGISTERS_AND_DISCARD ; return ; } else if (receive_buffer[1] == 0x10 && (device_address == receive_buffer[0] )) { STATE = PRESET_MULTIPLE_REGISTERS ; return ; } else if (receive_buffer[1] == 0x10 && (device_address != receive_buffer[0] )) { STATE = PRESET_MULTIPLE_REGISTERS_AND_DISCARD ; return ; } else if (receive_buffer[1] == 0x06 && (device_address == receive_buffer[0] )) { STATE = PRESET_SINGLE_REGISTER ; return ; } else if (receive_buffer[1] == 0x06 && (device_address != receive_buffer[0] )) { STATE = PRESET_SINGLE_REGISTER_AND_DISCARD ; return ; } } else if (i==8) { if (STATE == READ_HOLDING_REGISTERS ) { STATE = TRANSMIT_READ_HOLDING_REGISTERS ; receive_buffer_size = i ; return ; } else if (STATE == READ_HOLDING_REGISTERS_AND_DISCARD ) { STATE = RECEIVE ; i = 0; return ; } else if (STATE == PRESET_SINGLE_REGISTER ) { STATE = TRANSMIT_PRESET_SINGLE_REGISTER ; receive_buffer_size = i ; return ; } else if (STATE == READ_HOLDING_REGISTERS_AND_DISCARD ) { STATE = RECEIVE ; i = 0; return ; } } else if ( i == ( 9 + receive_buffer[6])) { if (STATE == PRESET_MULTIPLE_REGISTERS ) { STATE = TRANSMIT_PRESET_MULTIPLE_REGISTERS ; receive_buffer_size = i + receive_buffer[6]; return ; } else if (STATE == PRESET_MULTIPLE_REGISTERS_AND_DISCARD ) { STATE = RECEIVE ; i = 0; return ; } } } |
|
|
|
事情是用Modbus,3.5个字符延迟是必要的,如果你在RS485上运行,在3.5个字符延迟中开始响应,而另一端没有回绕它的发送器,那么这是你的错,他们没有看到响应,而不是他们的。还有一件需要注意的事情,3.5个CH。ARCATES只在较低的波特率下,在较高的波特率下,它是固定的最小间隔,它在Modbus系列规范中都有。
以上来自于百度翻译 以下为原文 Thing is with modbus, the 3.5 character delay is necessary, if you're running on RS485 and start responding within the 3.5 character delay and the other end hasn't turned round its transmitter yet it will be your fault that they don't see the response, not theirs. One other thing to note, the 3.5 characters is only at lower baud rates, at higher baud rates it's a fixed minimum interval, it's all there in the modbus serial specification. |
|
|
|
我会这样做来减少条件测试:
以上来自于百度翻译 以下为原文 I would do this to cut down the conditional tests: void __attribute__ ( (interrupt, no_auto_psv) ) _U1RXInterrupt( void ) { IFS0bits.U1RXIF = 0; static uint8_t i = 0 ; receive_buffer[i++] = U1RXREG ; if (i == 2) { if (device_address == receive_buffer[0]) { if (receive_buffer[1] == 0x03) { STATE = READ_HOLDING_REGISTERS ; return ; } else if (receive_buffer[1] == 0x10) { STATE = PRESET_MULTIPLE_REGISTERS ; return ; } else if (receive_buffer[1] == 0x06) { STATE = PRESET_SINGLE_REGISTER ; return ; } } else { if (receive_buffer[1] == 0x03) { STATE = READ_HOLDING_REGISTERS_AND_DISCARD ; return ; } else if (receive_buffer[1] == 0x10) { STATE = PRESET_MULTIPLE_REGISTERS_AND_DISCARD ; return ; } else if (receive_buffer[1] == 0x06) { STATE = PRESET_SINGLE_REGISTER_AND_DISCARD ; return ; } } } // i == 2 else if (i == 8} { if (STATE == READ_HOLDING_REGISTERS) { STATE = TRANSMIT_READ_HOLDING_REGISTERS ; receive_buffer_size = i ; return ; } else if (STATE == READ_HOLDING_REGISTERS_AND_DISCARD) { STATE = RECEIVE ; i = 0; return ; } else if (STATE == PRESET_SINGLE_REGISTER) { STATE = TRANSMIT_PRESET_SINGLE_REGISTER ; receive_buffer_size = i ; return ; } else if (STATE == READ_HOLDING_REGISTERS_AND_DISCARD) { STATE = RECEIVE ; i = 0; return ; } } // i == 8 else if (i == 9 + receive_buffer[6]) { if (STATE == PRESET_MULTIPLE_REGISTERS) { STATE = TRANSMIT_PRESET_MULTIPLE_REGISTERS ; receive_buffer_size = i + receive_buffer[6]; return ; } else if (STATE == PRESET_MULTIPLE_REGISTERS_AND_DISCARD) { STATE = RECEIVE ; i = 0; return ; } } // i == 9 + receive_buffer[6] } |
|
|
|
不编辑我以前的帖子,因为这个论坛烦恼。无论如何,它应该是一个结束)
以上来自于百度翻译 以下为原文 Not editing my previous post because of this forum annoyance. Anyway, it should be a closing ) for else if (i == 8} |
|
|
|
我同意1和0,但我会进一步说,有很多事情可以缩短ISR执行时间1),通过只处理每个PAS2中的当前字节来最小化撤销引用(即缓冲区[X])避免重复“= = Tests3”。跳过Tab雷就把你的代码重新排列为一个例子,我在几分钟内就把它敲了出来,完全没有经过测试,所以期待一连串的错误。
以上来自于百度翻译 以下为原文 I agree with 1and0, but I would go further There are a number of things that can shorten the ISR execution time 1) minimize the dereferencing (i.e buffer[x]) by only processing the current byte in each pass 2) Avoid duplicating the == tests 3) Use a switch statement to allow the compiler to optimize into a jump table I took the liberty of rearranging your code as an example, I banged it out in a few minutes and its totally untested, so expect a bunch of errors. void __attribute__ ( (interrupt, no_auto_psv) ) _U1RXInterrupt( void ) { static bool Discard = FALSE; static int i = 0; static int State = DEVICE_BYTE; static int DataBytes = 0; static char buffer[200]; bool DiscardMessage; char Rx; DiscardMessage = FALSE; Rx = U1RXREG; buffer = Rx; switch(State) { case DEVICE_BYTE: if(Rx == device_address) { Discard = TRUE; } State = CONTROL_BYTE: break; case CONTROL_BYTE: if(Rx == 0x03) { State = HOLDING_REG; } else if(Rx == 0x10) { State = MULTIPLE_REG; } else if(Rx == 0x06) { State = SINGLE_REG; } break; case HOLDING_REG: if(i == 5) { DiscardMessage = Discard; State = TRANSMIT_READ_HOLDING_REGISTERS; buffer_size = i; } break; case MULTIPLE_REG: if(i == 6) { DatatBytes = Rx + 6; } if(i == DataBytes) DiscardMessage = Discard; State = STATE = TRANSMIT_PRESET_MULTIPLE_REGISTERS; buffer_size = DataBytes; } break; case SINGLE_REG if(i == 5) { DiscardMessage = Discard; State = TRANSMIT_PRESET_SINGLE_REGISTER; buffer_size = i; } break; } if(DiscardMessage) { Discard = FALSE; STATE = RECEIVE ; i = 0; } i++ } |
|
|
|
只有小组成员才能发言,加入小组>>
5242 浏览 9 评论
2031 浏览 8 评论
1955 浏览 10 评论
请问是否能把一个ADC值转换成两个字节用来设置PWM占空比?
3207 浏览 3 评论
请问电源和晶体值之间有什么关系吗?PIC在正常条件下运行4MHz需要多少电压?
2256 浏览 5 评论
778浏览 1评论
668浏览 1评论
有偿咨询,关于MPLAB X IPE烧录PIC32MX所遇到的问题
595浏览 1评论
PIC Kit3出现目标设备ID(00000000)与预期的设备ID(02c20000)不匹配。是什么原因
677浏览 0评论
576浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 15:27 , Processed in 1.839459 second(s), Total 103, Slave 86 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号