ARM汇编---程序获取符号的物理地址
2024-12-23 来源:cnblogs
在移植u-boot的过程看到过u-boot在重定向时的实现,当时不知道怎么就觉得很好理解就把这个知识点没怎么深入的理解,最近在看华为的鸿蒙OS在Cortex-A平台上的实现过程时再次遇到一时间看不太懂了,所以花了点时间研究了一下这里做一下记录,后续有时间再把u-boot的实现再复盘一下加深理解。具体的代码如下
1 /*
2 * Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.
3 * Copyright (c) 2020, Huawei Device Co., Ltd. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this list of
9 * conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 * of conditions and the following disclaimer in the documentation and/or other materials
13 * provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
16 * to endorse or promote products derived from this software without specific prior written
17 * permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #define ASSEMBLY
33 #include 'arch_config.h'
34 #include 'los_vm_boot.h'
35 #include 'los_vm_zone.h'
36 #include 'los_mmu_descriptor_v6.h'
37 #undef ASSEMBLY
38
39
40 .global __exc_stack_top
41 .global __irq_stack_top
42 .global __fiq_stack_top
43 .global __svc_stack_top
44 .global __abt_stack_top
45 .global __undef_stack_top
46 .global __exc_stack
47 .global __irq_stack
48 .global __fiq_stack
49 .global __svc_stack
50 .global __abt_stack
51 .global __undef_stack
52
53 .extern __bss_start
54 .extern __bss_end
55 .extern hal_clock_initialize_start
56 .extern los_bss_init
57 .extern _osExceptFiqHdl
58 .extern _osExceptAddrAbortHdl
59 .extern _osExceptDataAbortHdl
60 .extern _osExceptPrefetchAbortHdl
61 .extern _osExceptSwiHdl
62 .extern _osExceptUndefInstrHdl
63 .extern __stack_chk_guard_setup
64 .extern g_firstPageTable
65 .extern g_mmuJumpPageTable
66
67 .equ MPIDR_CPUID_MASK, 0xffU
68
69 .fpu vfpv4
70 .arm
71
72 /* param0 is stack bottom, param1 is stack size, r11 hold cpu id */
73 .macro EXC_SP_SET param0, param1
74 ldr r1, =param0
75 mov r0, param1
76 bl sp_set
77 .endm
78
79 /* param0 is stack top, param1 is stack size, param2 is magic num */
80 .macro STACK_MAGIC_SET param0, param1, param2
81 ldr r0, =param0
82 mov r1, param1
83 ldr r2, =param2
84 bl excstack_magic
85 .endm
86
87 /* param0 is physical address, param1 virtual address, param2 is sizes, param3 is flag */
88 .macro PAGE_TABLE_SET param0, param1, param2, param3
89 ldr r6, =param0
90 ldr r7, =param1
91 ldr r8, =param2
92 ldr r10, =param3
93 bl page_table_build
94 .endm
95 .code 32
96 .section '.vectors','ax'
97
98 __exception_handlers:
99 /*
100 *Assumption: ROM code has these vectors at the hardware reset address.
101 *A simple jump removes any address-space dependencies [i.e. safer]
102 */
103 b reset_vector
104 b _osExceptUndefInstrHdl
105 b _osExceptSwiHdl
106 b _osExceptPrefetchAbortHdl
107 b _osExceptDataAbortHdl
108 b _osExceptAddrAbortHdl
109 b OsIrqHandler
110 b _osExceptFiqHdl
111
112 /* Startup code which will get the machine into supervisor mode */
113 .global reset_vector
114 .type reset_vector,function
115 reset_vector:
116 /* do some early cpu setup: i/d cache disable, mmu disabled */
117 mrc p15, 0, r0, c1, c0, 0
118 bic r0, #(1<<12)
119 bic r0, #(1<<2 | 1<<0)
120 mcr p15, 0, r0, c1, c0, 0
121
122 /* r11: delta of physical address and virtual address */
123 adr r11, pa_va_offset;此时r11为物理地址 具体原因是硬件决定了第一条指令的地址,当执行到这里pc此时是当前的指令的地址(自然是物理地址)
124 ;然后而adr伪指令的作用就是得到了当前标识pa_va_offset和当前指令的offset和保存在r11,而代码的实现在这个标识处定
125 ;义了一个连接地址相关的标识'.'所以按照程序连接指定的运行地址(虚拟的)这里保存的值肯定是连接实际的虚拟运行地址所以r0为虚拟地址
126 ldr r0, [r11]
127 sub r11, r11, r0 ;进而物理地址减去虚拟地址(连接地址)即就是物理地址和虚拟地址的差。
128
129 /* if we need to relocate to proper location or not */
130 adr r4, __exception_handlers /* r4: base of load address */
131 ldr r5, =SYS_MEM_BASE /* r5: base of physical address */
132 subs r12, r4, r5 /* r12: delta of load address and physical address */
133 beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */
134
135 /* we need to relocate image at the bottom of physical address */
136 ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */
137 ldr r6, =__bss_start /* r6: end of linked address (or vm address) */
138 sub r6, r7 /* r6: delta of linked address (or vm address) */
139 add r6, r4 /* r6: end of load address */
140
141 reloc_img_to_bottom_loop:
142 ldr r7, [r4], #4
143 str r7, [r5], #4
144 cmp r4, r6
145 bne reloc_img_to_bottom_loop
146 sub pc, r12
147 nop
148 sub r11, r11, r12 /* r11: eventual address offset */
149
150 reloc_img_to_bottom_done:
151 ldr r4, =g_firstPageTable /* r4: physical address of translation table and clear it */
152 add r4, r4, r11
153 bl page_table_clear
154
155 PAGE_TABLE_SET SYS_MEM_BASE, KERNEL_VMM_BASE, KERNEL_VMM_SIZE, MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
156 PAGE_TABLE_SET SYS_MEM_BASE, UNCACHED_VMM_BASE, UNCACHED_VMM_SIZE, MMU_INITIAL_MAP_STRONGLY_ORDERED
157 PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_DEVICE_BASE, PERIPH_DEVICE_SIZE, MMU_INITIAL_MAP_DEVICE
158 PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_CACHED_BASE, PERIPH_CACHED_SIZE, MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
159 PAGE_TABLE_SET PERIPH_PMM_BASE, PERIPH_UNCACHED_BASE, PERIPH_UNCACHED_SIZE, MMU_INITIAL_MAP_STRONGLY_ORDERED
160
161 orr r8, r4, #MMU_TTBRx_FLAGS /* r8 = r4 and set cacheable attributes on translation walk */
162 ldr r4, =g_mmuJumpPageTable /* r4: jump pagetable vaddr */
163 add r4, r4, r11
164 ldr r4, [r4]
165 add r4, r4, r11 /* r4: jump pagetable paddr */
166 bl page_table_clear
167
168 /* build 1M section mapping, in order to jump va during turing on mmu:pa == pa, va == pa */
169 mov r6, pc
170 mov r7, r6 /* r7: pa (MB aligned)*/
171 lsr r6, r6, #20 /* r6: va l1 index */
172 ldr r10, =MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS
173 add r12, r10, r6, lsl #20 /* r12: pa |flags */
174 str r12, [r4, r7, lsr #(20 - 2)] /* jumpTable[paIndex] = pt entry */
175 rsb r7, r11, r6, lsl #20 /* r7: va */
176 str r12, [r4, r7, lsr #(20 - 2)] /* jumpTable[vaIndex] = pt entry */
177
178 bl mmu_setup /* set up the mmu */
179
180 /* get cpuid and keep it in r11 */
181 mrc p15, 0, r11, c0, c0, 5
182 and r11, r11, #MPIDR_CPUID_MASK
183 cmp r11, #0
184 bne excstatck_loop_done
185
186 excstatck_loop:
187 /* clear out the interrupt and exception stack and set magic num to check the overflow */
188 ldr r0, =__undef_stack
189 ldr r1, =__exc_stack_top
190 bl stack_init
191
192 STACK_MAGIC_SET __undef_stack, #OS_EXC_UNDEF_STACK_SIZE, OS_STACK_MAGIC_WORD
193 STACK_MAGIC_SET __abt_stack, #OS_EXC_ABT_STACK_SIZE, OS_STACK_MAGIC_WORD
194 STACK_MAGIC_SET __irq_stack, #OS_EXC_IRQ_STACK_SIZE, OS_STACK_MAGIC_WORD
195 STACK_MAGIC_SET __fiq_stack, #OS_EXC_FIQ_STACK_SIZE, OS_STACK_MAGIC_WORD
196 STACK_MAGIC_SET __svc_stack, #OS_EXC_SVC_STACK_SIZE, OS_STACK_MAGIC_WORD
197 STACK_MAGIC_SET __exc_stack, #OS_EXC_STACK_SIZE, OS_STACK_MAGIC_WORD
198
199 excstatck_loop_done:
200 warm_reset:
201 /* initialize interrupt/exception environments */
202 mov r0, #(CPSR_IRQ_DISABLE |CPSR_FIQ_DISABLE|CPSR_IRQ_MODE)
203 msr cpsr, r0
204 EXC_SP_SET __irq_stack_top, #OS_EXC_IRQ_STACK_SIZE
205
206 mov r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_UNDEF_MODE)
207 msr cpsr, r0
208 EXC_SP_SET __undef_stack_top, #OS_EXC_UNDEF_STACK_SIZE
209
210 mov r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_ABT_MODE)
211 msr cpsr, r0
212 EXC_SP_SET __abt_stack_top, #OS_EXC_ABT_STACK_SIZE
213
214 mov r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_FIQ_MODE)
215 msr cpsr, r0
216 EXC_SP_SET __fiq_stack_top, #OS_EXC_FIQ_STACK_SIZE
217
218 /* initialize CPSR (machine state register) */
219 mov r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE)
220 msr cpsr, r0
221
222 /* Note: some functions in LIBGCC1 will cause a 'restore from SPSR'!! */
223 msr spsr, r0
224
225 /* set svc stack, every cpu has OS_EXC_SVC_STACK_SIZE stack */
226 ldr r0, =__svc_stack_top
227 mov r2, #OS_EXC_SVC_STACK_SIZE
228 mul r2, r2, r11
229 sub r0, r0, r2
230 mov sp, r0
231
232 /* enable fpu+neon */
233 MRC p15, 0, r0, c1, c1, 2
234 ORR r0, r0, #0xC00
235 BIC r0, r0, #0xC000
236 MCR p15, 0, r0, c1, c1, 2
237
238 LDR r0, =(0xF << 20)
239 MCR p15, 0, r0, c1, c0, 2
240
241 MOV r3, #0x40000000
242 VMSR FPEXC, r3
243
244 LDR r0, =__exception_handlers
245 MCR p15, 0, r0, c12, c0, 0
246
247 cmp r11, #0
248 bne cpu_start
249
250 clear_bss:
251 ldr r1, =__bss_start
252 ldr r2, =__bss_end
253 mov r0, #0
254
255 bss_loop:
256 cmp r1, r2
257 strlo r0, [r1], #4
258 blo bss_loop
259
260 #if defined(LOSCFG_CC_STACKPROTECTOR_ALL) ||
261 defined(LOSCFG_CC_STACKPROTECTOR_STRONG) ||
262 defined(LOSCFG_CC_STACKPROTECTOR)
263 bl __stack_chk_guard_setup
264 #endif
265
266 #ifdef LOSCFG_GDB_DEBUG
267 /* GDB_START - generate a compiled_breadk,This function will get GDB stubs started, with a proper environment */
268 bl GDB_START
269 .word 0xe7ffdeff
270 #endif
271
272 bl main
273
274 _start_hang:
275 b _start_hang
276
277 mmu_setup:
278 mov r12, #0
279 mcr p15, 0, r12, c8, c7, 0 /* Set c8 to control the TLB and set the mapping to invalid */
280 isb
281
282 mcr p15, 0, r12, c2, c0, 2 /* Initialize the c2 register */
283 isb
284
285 orr r12, r4, #MMU_TTBRx_FLAGS
286 mcr p15, 0, r12, c2, c0, 0 /* Set attributes and set temp page table */
287 isb
288
289 mov r12, #0x7 /* 0b0111 */
290 mcr p15, 0, r12, c3, c0, 0 /* Set DACR with 0b0111, client and manager domian */
291 isb
292
293 mrc p15, 0, r12, c1, c0, 0
294 bic r12, #(1 << 29 | 1 << 28)
295 orr r12, #(1 << 0)
296 bic r12, #(1 << 1)
297 orr r12, #(1 << 2)
298 orr r12, #(1 << 12)
299 mcr p15, 0, r12, c1, c0, 0 /* Set SCTLR with r12: Turn on the MMU, I/D cache Disable TRE/AFE */
300 isb
301
302 ldr pc, =1f /* Convert to VA */
303 1:
304 mcr p15, 0, r8, c2, c0, 0 /* Go to the base address saved in C2: Jump to the page table */
305 isb
306
307 mov r12, #0
308 mcr p15, 0, r12, c8, c7, 0
309 isb
310
311 sub lr, r11 /* adjust lr with delta of physical address and virtual address */
312 bx lr
313
314 .code 32
315
316 .global reset_platform
317 .type reset_platform,function
318 reset_platform:
319 #ifdef A7SEM_HAL_ROM_MONITOR
320 /* initialize CPSR (machine state register) */
321 mov r0, #(CPSR_IRQ_DISABLE|CPSR_FIQ_DISABLE|CPSR_SVC_MODE)
322 msr cpsr, r0
323 b warm_reset
324 #else
325 mov r0, #0
326 mov pc, r0 // Jump to reset vector
327 #endif
328 cpu_start:
329 bl secondary_cpu_start
330 b .
331
332
333
334 /*
335 * set sp for current cpu
336 * r1 is stack bottom, r0 is stack size, r11 hold cpu id
337 */
338 sp_set:
339 mul r3, r0, r11
340 sub r2, r1, r3
341 mov sp, r2
342 bx lr /* set sp */
343
344 /*
345 * r4: page table base address
346 * r5 and r6 will be used as variable
347 */
348 page_table_clear:
349 mov r5, #0
350 mov r6, #0
351 0:
352 str r5, [r4, r6, lsl #2]
353 add r6, #1
354 cmp r6, #0x1000 /* r6 < 4096 */
355 blt 0b
356 bx lr
357
358 /*
359 * r4: page table base address
360 * r6: physical address
361 * r7: virtual address
362 * r8: sizes
363 * r10: flags
364 * r9 and r12 will be used as variable
365 */
366 page_table_build:
367 mov r9, r6
368 bfc r9, #20, #12 /* r9: pa % MB */
369 add r8, r8, r9
370 add r8, r8, #(1 << 20)
371 sub r8, r8, #1
372 lsr r6, #20 /* r6 = physical address / MB */
373 lsr r7, #20 /* r7 = virtual address / MB */
374 lsr r8, #20 /* r8 = roundup(size, MB) */
375
376 page_table_build_loop:
377 orr r12, r10, r6, lsl #20 /* r12: flags | physAddr */
378 str r12, [r4, r7, lsl #2] /* gPgTable[l1Index] = physAddr | flags */
379 add r6, #1 /* physAddr+ */
380 add r7, #1 /* l1Index++ */
381 subs r8, #1 /* sizes-- */
382 bne page_table_build_loop
383 bx lr
384
385 /*
386 * init stack to initial value
387 * r0 is stack mem start, r1 is stack mem end
388 */
389 stack_init:
390 ldr r2, =OS_STACK_INIT
391 ldr r3, =OS_STACK_INIT
392 /* Main loop sets 32 bytes at a time. */
393 stack_init_loop:
394 .irp offset, #0, #8, #16, #24
395 strd r2, r3, [r0, offset]
396 .endr
397 add r0, #32
398 cmp r0, r1
399 blt stack_init_loop
400 bx lr
401
402 pa_va_offset:
403 .word .
404
405 /*
406 * set magic num to stack top for all cpu
407 * r0 is stack top, r1 is stack size, r2 is magic num
408 */
409 excstack_magic:
410 mov r3, #0
411 excstack_magic_loop:
412 str r2, [r0]
413 add r0, r0, r1
414 add r3, r3, #1
415 cmp r3, #CORE_NUM
416 blt excstack_magic_loop
417 bx lr
418
419 /*
420 * 0xe51ff004 = 'ldr pc, [pc, #-4]'
421 * next addr value will be the real booting addr
422 */
423 _bootaddr_setup:
424 mov r0, #0
425 ldr r1, =0xe51ff004
426 str r1, [r0]
427
428 add r0, r0, #4
429 ldr r1, =SYS_MEM_BASE
430 str r1, [r0]
431
432 dsb
433 isb
434
435 bx lr
436
437 init_done:
438 .long 0xDEADB00B
439
440 .code 32
441 .data
442
443 init_flag:
444 .balign 4
445 .long 0
446
447 /*
448 * Temporary interrupt stack
449 */
450 .section '.int_stack', 'wa', %nobits
451 .align 3
452
453 __undef_stack:
454 .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM
455 __undef_stack_top:
456
457 __abt_stack:
458 .space OS_EXC_ABT_STACK_SIZE * CORE_NUM
459 __abt_stack_top:
460
461 __irq_stack:
462 .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM
463 __irq_stack_top:
464
465 __fiq_stack:
466 .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM
467 __fiq_stack_top:
468
469 __svc_stack:
470 .space OS_EXC_SVC_STACK_SIZE * CORE_NUM
471 __svc_stack_top:
472
473 __exc_stack:
474 .space OS_EXC_STACK_SIZE * CORE_NUM
475 __exc_stack_top:
看不太明白就是复位操作其中的计算虚拟地址和物理地址差的部分实现这里单独贴出来。
1 /* r11: delta of physical address and virtual address */
2 adr r11, pa_va_offset;此时r11为物理地址 具体原因是硬件决定了第一条指令的地址,当执行到这里pc此时是当前的指令的地址(自然是物理地址)
3然后而adr伪指令的作用就是得到了当前标识pa_va_offset和当前指令的offset和保存在r11,而代码的实现在这个标识处定
4;义了一个连接地址相关的标识'.'所以按照程序连接指定的运行地址(虚拟的)这里保存的值肯定是连接实际的虚拟运行地址所以r0为虚拟地址
5 ldr r0, [r11]
6 sub r11, r11, r0 ;进而物理地址减去虚拟地址(连接地址)即就是物理地址和虚拟地址的差。
这里主要是对adr这一句的汇编理解的不是很深入,详细了解之后才知道这是一条伪指令在汇编器汇编的时候他会被汇编为sub这个机器码。在程序连接完成后 pa_va_offset 的地址是固定的,并且这一条伪指令的地址也是确定的所以他俩之间的偏移就是确定的,这里记住是后面的符号的地址和 adr r11, pa_va_offset ;这条语句的相对偏移是固定的。所以在汇编这个语句的时候实际上就已经知道他俩的地址差offset,并且这条伪指令在Rn不为pc时最后的结果就如同 sub Rn pc #offset。 所以这里的r11 保存的就是程序的运行时刻的物理地址这一点比较饶,但是从硬件的行为考虑就很容易理解了---硬件启动之后从物理地址的那个地址开始运行具体的硬件模式确定后也是确定的所以这个最后在程序运行到这里的时候PC寄存器就是这一条伪指令的真实的物理地址,所以前面红色字体的内容就能理解了。然后就是后面两句就得到了虚拟地址和物理地址的差。这里也费解但是如果把这句话换一下就好理解多了---实际得到的是链接地址和物理地址的差。具体还要看 pa_va_offset 标识符下的实现下面贴出来:
1 pa_va_offset:
2 .word .
这简单的一句话其实就是在当前符号连接的地址处放置一个32位的值这个值是程序连接到这里时的连接地址如果熟悉连接脚本就很容易想到了。所以这里在这个符号的地址处存放了这个符号的连接地址,还是有点绕,换个说法就是在一个内存地址(链接地址)上存放了这块内存自己的链接地址,但是在实际存放时具体本存放在那里程序本身自己决定不了,所以这一块内容永远保存的是链接时指定的地址,所以上面代码的实现就得到了程序的链接地址和实际运行物理地址的差,再反过来说就是如果把编译出来的可执行文件放到了链接时指定的地址上的话符号地址里面的内容就是符号的地址。
之所以注释说是虚拟地址和物理地址的差是因为OS程序在链接时指定的连接地址实际上是在开启了MMU之后的虚拟地址,将来是要拷贝到DRAM中运行的。所以这里的虚拟地址实际上就是链接地址因为必须这样否则系统是无法启动的。最后这里还有一个知识点就是链接地址和运行地址不同为什么还能正常运行呢,这是因为部分代码是PIC的即不关心load地址,也可以通过编译时指定部分代码编译为位置无关的代码,也可查看前面写的博客编译过程中的链接地址和实际运行地址。
- 基于ExecuTorch与Arm SME2的端侧机器学习推理加速
- 深入解析Arm Neoverse计算平台的技术架构与性能优势
- 端侧AI爆发催生芯片设计新范式,Arm技术授权订阅模式为产业铺就“快车道”
- Arm 执行副总裁:尚未向合作伙伴提供适用于 Windows 操作系统的 GPU
- Arm 赋能机器人:产品落地的底层技术
- 行业评论 从工具到平台:如何化解跨架构时代的工程开发和管理难题
- Arm Flexible Access 扩容升级,赋能更多企业加速芯片开发
- Microchip推出PIC32CM PL10 MCU系列,进一步扩展其 Arm@ Cortex@-M0+产品组合
- 云开发者正加速向 Arm 架构迁移:构建面向 AI 时代的未来基础设施
- Arm 计算平台加持,联想车计算推动 L4 级自动驾驶出租车规模化落地
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




