一、STM32启动过程
1 概述
说明
每一款芯片的启动文件都值得去研究,因为它可是你的程序跑的最初一段路,不可以不知道。通过了解启动文件,我们可以体会到处理器的架构、指令集、中断向量安排等内容,是非常值得玩味的。
STM32作为一款高端 Cortex-M3系列单片机,有必要了解它的启动文件。打好基础,为以后优化程序,写出高质量的代码最准备。
本文以一个实际测试代码--START_TEST为例进行阐述。
整体过程
STM32整个启动过程是指从上电开始,一直到运行到 main函数之间的这段过程,步骤为(以使用微库为例):
①上电后硬件设置SP、PC
②设置系统时钟
③软件设置SP
④加载.data、.bss,并初始化栈区
⑤跳转到C文件的main函数
代码
启动过程涉及的文件不仅包含 startup_stm32f10x_hd.s,还涉及到了MDK自带的连接库文件 entry.o、entry2.o、entry5.o、entry7.o等(从生成的 map文件可以看出来)。
2 程序在Flash上的存储结构
在真正讲解启动过程之前,先要讲解程序下载到 Flash上的结构和程序运行时(执行到main函数)时的SRAM数据结构。程序在用户Flash上的结构如下图所示。下图是通过阅读hex文件和在MDK下调试综合提炼出来的。
上图中:
- MSP初始值由编译器生成,是主堆栈的初始值。
- 初始化数据段是.data
- 未初始化数据段是.bss
.data和.bss是在__main里进行初始化的,对于ARM Compiler,__main主要执行以下函数:
其中__scatterload会对.data和.bss进行初始化。
加载数据段和初始化栈的参数
加载数据段和初始化栈的参数分别有4个,这里只讲解加载数据段的参数,至于初始化栈的参数类似。
0x0800033c Flash上的数据段(初始化数据段和未初始化数据段)起始地址
0x20000000 加载到SRAM上的目的地址
0x0000000c 数据段的总大小
0x080002f4 调用函数_scatterload_copy
需要说明的是初始化栈的函数-- 0x08000304与加载数据段的函数不一样,为 _scatterload_zeroinit,它的目的就是将栈空间清零。
3 数据在SRAM上的结构
程序运行时(执行到main函数)时的SRAM数据结构
4 详细过程分析
有了以上的基础,现在详细分析启动过程
上电后硬件设置SP、PC
刚上电复位后,硬件会自动根据向量表偏移地址找到向量表,向量表偏移地址的定义如下:
调试现象如下:
看看我们的向量表内容(通过J-Flash打开hex文件)
硬件这时自动从0x0800 0000位置处读取数据赋给栈指针SP,然后自动从0x0800 0004位置处读取数据赋给PC,完成复位,结果为:
SP = 0x02000810
PC = 0x08000145
设置系统时钟
上一步中令 PC=0x08000145的地址没有对齐,硬件自动对齐到 0x08000144,执行 SystemInit函数初始化系统时钟。
软件设置SP
LDR R0,=__main
BX R0
执行上两条之类,跳转到 __main程序段运行,注意不是main函数, ___main的地址是0x0800 0130。
可以看到指令LDR.W sp,[pc,#12],结果SP=0x2000 0810。
加载.data、.bss,并初始化栈区
BL.W __scatterload_rt2
进入 __scatterload_rt2代码段。
__scatterload_rt2:
0x080001684C06 LDR r4,[pc,#24] ; @0x08000184
0x0800016A4D07 LDR r5,[pc,#28] ; @0x08000188
0x0800016C E006 B 0x0800017C
0x0800016E68E0 LDR r0,[r4,#0x0C]
0x08000170 F0400301 ORR r3,r0,#0x01
0x08000174 E8940007 LDM r4,{r0-r2}
0x080001784798 BLX r3
0x0800017A3410 ADDS r4,r4,#0x10
0x0800017C42AC CMP r4,r5
0x0800017E D3F6 BCC 0x0800016E
0x08000180 F7FFFFDA BL.W _main_init (0x08000138)
这段代码是个循环 (BCC0x0800016e),实际运行时候循环了两次。第一次运行的时候,读取“加载数据段的函数 (_scatterload_copy)”的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取“初始化栈的函数 (_scatterload_zeroinit)”的地址并跳转到该函数处运行。相应的代码如下:
0x0800016E68E0 LDR r0,[r4,#0x0C]
0x08000170 F0400301 ORR r3,r0,#0x01
0x08000174
0x080001784798 BLX r3
当然执行这两个函数的时候,还需要传入参数。至于参数,我们在“加载数据段和初始化栈的参数”环节已经阐述过了。当这两个函数都执行完后,结果就是“数据在SRAM上的结构”所展示的图。最后,也把事实加载和初始化的两个函数代码奉上如下:
__scatterload_copy:
0x080002F4 E002 B 0x080002FC
0x080002F6 C808 LDM r0!,{r3}
0x080002F81F12 SUBS r2,r2,#4
0x080002FA C108 STM r1!,{r3}
0x080002FC2A00 CMP r2,#0x00
0x080002FE D1FA BNE 0x080002F6
0x080003004770 BX lr
__scatterload_null:
0x080003024770 BX lr
__scatterload_zeroinit:
0x080003042000 MOVS r0,#0x00
0x08000306 E001 B 0x0800030C
0x08000308 C101 STM r1!,{r0}
0x0800030A1F12 SUBS r2,r2,#4
0x0800030C2A00 CMP r2,#0x00
0x0800030E D1FB BNE 0x08000308
0x080003104770 BX lr
跳转到C文件的main函数
_main_init:
0x080001384800 LDR r0,[pc,#0] ; @0x0800013C
0x0800013A4700 BX r0
5 异常向量与中断向量表
; VectorTableMapped to Address0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler; ResetHandler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler; HardFaultHandler
DCD MemManage_Handler; MPU FaultHandler
DCD BusFault_Handler; BusFaultHandler
DCD UsageFault_Handler; UsageFaultHandler
DCD 0; Reserved
DCD 0; Reserved
DCD 0; Reserved
DCD 0; Reserved
DCD SVC_Handler ; SVCallHandler
DCD DebugMon_Handler; DebugMonitorHandler
DCD 0; Reserved
DCD PendSV_Handler; PendSVHandler
DCD SysTick_Handler; SysTickHandler
; ExternalInterrupts
DCD WWDG_IRQHandler ; WindowWatchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
DCD EXTI3_IRQHandler ; EXTI Line3
DCD EXTI4_IRQHandler ; EXTI Line4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB HighPriority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB LowPriority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 CaptureCompare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line15..10
DCD RTCAlarm_IRQHandler; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 CaptureCompare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4& Channel5
__Vectors_End
这段代码就是定义异常向量表,在之前有一个“J-Flash打开hex文件”的图片跟这个表格是一一对应的。编译器根据我们定义的函数 Reset_Handler、NMI_Handler等,在连接程序阶段将这个向量表填入这些函数的地址。
startup_stm32f10x_hd.s内容:
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
stm32f10x_it.c中内容:
void NMI_Handler(void)
{
}
在启动汇编文件中已经定义了函数 NMI_Handler,但是使用了“弱”,它允许我们再重新定义一个 NMI_Handler函数,程序在编译的时候会将汇编文件中的弱函数“覆盖掉”--两个函数的代码在连接后都存在,只是在中断向量表中的地址填入的是我们重新定义函数的地址。
6 使用微库与不使用微库的区别
使用微库就意味着我们不想使用MDK提供的库函数,而想用自己定义的库函数,比如说printf函数。那么这一点是怎样实现的呢?我们以printf函数为例进行说明。
不使用微库而使用系统库
在连接程序时,肯定会把系统中包含printf函数的库拿来调用参与连接,即代码段有系统库的参与。
在启动过程中,不使用微库而使用系统库在初始化栈的时候,还需要初始化堆(猜测系统库需要用到堆),而使用微库则是不需要的。
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem+ Stack_Size)
LDR R2, = (Heap_Mem+ Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
另外,在执行 __main函数的过程中,不仅需要完成“使用微库”情况下的所有工作,额外的工作还需要进行库的初始化,才能使用系统库(这一部分我还没有深入探讨)。附上 __main函数的内容:
__main:
0x08000130 F000F802 BL.W __scatterload_rt2_thumb_only (0x08000138)
0x08000134 F000F83C BL.W __rt_entry_sh (0x080001B0)
__scatterload_rt2_thumb_only:
0x08000138 A00A ADR r0,{pc}+4; @0x08000164
0x0800013A E8900C00 LDM r0,{r10-r11}
0x0800013E4482 ADD r10,r10,r0
0x080001404483 ADD r11,r11,r0
0x08000142 F1AA0701 SUB r7,r10,#0x01
__scatterload_null:
0x0800014645DA CMP r10,r11
0x08000148 D101 BNE 0x0800014E
0x0800014A F000F831 BL.W __rt_entry_sh (0x080001B0)
0x0800014E F2AF0E09 ADR.W lr,{pc}-0x07; @0x08000147
0x08000152 E8BA000F LDM r10!,{r0-r3}
0x08000156 F0130F01 TST r3,#0x01
0x0800015A BF18 IT NE
0x0800015C1AFB SUBNE r3,r7,r3
0x0800015E F0430301 ORR r3,r3,#0x01
0x080001624718 BX r3
0x080001640298 LSLS r0,r3,#10
0x080001660000 MOVS r0,r0
0x0800016802B8 LSLS r0,r7,#10
0x0800016A0000 MOVS r0,r0
__scatterload_copy:
0x0800016C3A10 SUBS r2,r2,#0x10
0x0800016E BF24 ITT CS
0x08000170 C878 LDMCS r0!,{r3-r6}
0x08000172 C178 STMCS r1!,{r3-r6}
0x08000174 D8FA BHI __scatterload_copy (0x0800016C)
0x080001760752 LSLS r2,r2,#29
0x08000178 BF24 ITT CS
0x0800017A C830 LDMCS r0!,{r4-r5}
0x0800017C C130 STMCS r1!,{r4-r5}
0x0800017E BF44 ITT MI
0x080001806804 LDRMI r4,[r0,#0x00]
0x08000182600C STRMI r4,[r1,#0x00]
0x080001844770 BX lr
0x080001860000 MOVS r0,r0
__scatterload_zeroinit:
0x080001882300 MOVS r3,#0x00
0x0800018A2400 MOVS r4,#0x00
0x0800018C2500 MOVS r5,#0x00
0x0800018E2600 MOVS r6,#0x00
0x080001903A10 SUBS r2,r2,#0x10
0x08000192 BF28 IT CS
0x08000194 C178 STMCS r1!,{r3-r6}
0x08000196 D8FB BHI 0x08000190
0x080001980752 LSLS r2,r2,#29
0x0800019A BF28 IT CS
0x0800019C C130 STMCS r1!,{r4-r5}
0x0800019E BF48 IT MI
0x080001A0600B STRMI r3,[r1,#0x00]
0x080001A24770 BX lr
__rt_lib_init:
0x080001A4 B51F PUSH {r0-r4,lr}
0x080001A6 F3AF8000 NOP.W
__rt_lib_init_user_alloc_1:
0x080001AA BD1F POP {r0-r4,pc}
__rt_lib_shutdown:
0x080001AC B510 PUSH {r4,lr}
__rt_lib_shutdown_user_alloc_1:
0x080001AE BD10 POP {r4,pc}
__rt_entry_sh:
0x080001B0 F000F82F BL.W __user_setup_stackheap (0x08000212)
0x080001B44611 MOV r1,r2
__rt_entry_postsh_1:
0x080001B6 F7FFFFF5 BL.W __rt_lib_init (0x080001A4)
__rt_entry_postli_1:
0x080001BA F000F919 BL.W main (0x080003F0)
使用微库而不使用系统库
在程序连接时,不会把包含printf函数的库连接到终极目标文件中,而使用我们定义的库。
启动时需要完成的工作就是之前论述的步骤1、2、3、4、5,相比使用系统库,启动过程步骤更少。
二、如何中断单片机的中断?
如果外部中断来的频率足够快,上一个中断没有处理完成,新来的中断该如何处理?
中断一般是由硬件(例如外设、外部引脚)产生,当某种内部或外部事件发生时,MCU的中断系统将迫使 CPU 暂停正在执行的程序,转而去进行中断事件的处理,中断处理完毕后,又返回被中断的程序处,继续执行下去,所有的Cortex-M 内核系统都有一个用于中断处理的组件NVIC,主要负责处理中断,还处理其他需要服务的事件。嵌套向量式中断控制器(NVIC: Nested Vectored Interrupt Controller)集成在Cortex-M0处理器里,它与处理器内核紧密相连,并且提供了中断控制功能以及对系统异常的支持。
处理器中的NVIC能够处理多个可屏蔽中断通道和可编程优先级,中断输入请求可以是电平触发,也可以是最小的一个时钟周期的脉冲信号。每一个外部中断线都可以独立的使能、清除或挂起,并且挂起状态也可以手动地设置和清除。
主程序正在执行,当遇到中断请求(Interrupt Request)时,暂停主程序的执行转而去执行中断服务例程(Interrupt Service Routine,ISR),称为响应,中断服务例程执行完毕后返回到主程序断点处并继续执行主程序。多个中断是可以进行嵌套的。正在执行的较低优先级中断可以被较高优先级的中断所打断,在执行完高级中断后返回到低级中断里继续执行,采用“咬尾中断”机制。
内核中断(异常管理和休眠模式等),其中断优先级则由SCB寄存器来管理,IRQ的中断优先级是由NVIC来管理。
NVIC的寄存器经过了存储器映射,其寄存器的起始地址为0xE000E100,对其访问必须是每次32bit。
SCB寄存器的起始地址:0xE000ED00,也是每次32bit访问,SCB寄存器主要包含SysTick操作、异常管理和休眠模式控制。
NVIC具有以下特性:
- 灵活的中断管理:使能\清除、优先级配置
- 硬件嵌套中断支持
- 向量化的异常入口
- 中断屏蔽
1 中断使能和清除使能
ARM将处理器的中断使能设置和清除设置寄存器分在两个不同的地址,这种设计主要有如下优势:一方面这种方式减少了使能中断所需要的步骤,使能一个中断NVIC只需要访问一次,同时也减少了程序代码并且降低了执行时间,另一方面当多个应用程序进程同时访问寄存器或者在读写操作寄存器时有操作其他的中断使能位,这样就有可能导致寄存器丢失,设置和清除分成两个寄存器能够有效防止控制信号丢失。
因此我可以独立的操作每一个中断的使能和清除设置。
1.1 C代码
*(volatile unsigned long) (0xE000E100) = 0x4 ; //使能#2中断
*(volatile unsigned long) (0xE000E180) = 0x4 ; //清除#2中断
1.2 汇编代码
__asm void Interrupt_Enable()
{
LDR R0, =0xE000E100 ; //ISER寄存器的地址
MOVS R1, #04 ; //设置#2中断
STR R1, [R0] ; //使能中断#2
}
__asm void Interrupt_Disable()
{
LDR R0, =0xE000E180 ; //ICER寄存器的地址
MOVS R1, #04 ; //设置#2中断
STR R1, [R0] ; //使能中断#2
}
1.3 CMSIS标准设备驱动函数
//使能中断#IRQn
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
}
}
//清除中断#IRQn
__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ICER[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
__DSB();
__ISB();
}
}
//读取使能中断#IRQn
__STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return((uint32_t)(((NVIC->ISER[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
}
else {
return(0U);
}
}
2 中断挂起和清除挂起
如果一个中断发生了,却无法立即处理,这个中断请求将会被挂起。挂起状态保存在一个寄存器中,如果处理器的当前优先级还没有降低到可以处理挂起的请求,并且没有手动清除挂起状态,该状态将会一直保持。
可以通过操作中断设置挂起和中断清除挂起两个独立的寄存器来访问或者修改中断挂起状态,中断挂起寄存器也是通过两个地址来实现设置和清除相关位。这使得每一个位都可以独立修改,并且无需担心在两个应用程序进程竞争访问时出现的数据丢失。
中断挂起状态寄存器允许使用软件来触发中断。如果中断已经使能并且没有被屏蔽掉,当前还没有更高优先级的中断在运行,这时中断的服务程序就会立即得以执行。
2.1 C代码
*(volatile unsigned long)(0xE000E100) = 0x4 ; //使能中断#2
*(volatile unsigned long)(0xE000E200) = 0x4 ; //挂起中断#2
*(volatile unsigned long)(0xE000E280) = 0x4 ; //清除中断#2的挂起状态
2.2 汇编代码
__asm void Interrupt_Set_Pending()
{
LDR R0, =0xE000E100 ; //设置使能中断寄存器地址
MOVS R1, #0x4 ; //中断#2
STR R1, [R0] ; //使能#2中断
LDR R0, =0xE000E200 ; //设置挂起中断寄存器地址
MOVS R1, #0x4 ; //中断#2
STR R1, [R0] ; //挂起#2中断
}
__asm void Interrupt_Clear_Pending()
{
LDR R0, =0xE000E100 ; //设置使能中断寄存器地址
MOVS R1, #0x4 ; //中断#2
STR R1, [R0] ; //使能#2中断
LDR R0, =0xE000E280 ; //设置清除中断挂起寄存器地址
MOVS R1, #0x4 ; //中断#2
STR R1, [R0] ; //清除#2的挂起状态
}
2.3 CMSIS标准设备驱动函数
//设置一个中断挂起
__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ISPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
}
}
//清除中断挂起
__STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ICPR[0U] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
}
}
//读取中断挂起状态
__STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return((uint32_t)(((NVIC->ISPR[0U] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
}
else {
return(0U);
}
}
NVIC属于处理器内核部分,因此在MM32 MCU芯片的用户手册中只有简单的提及,没有重点讲述,需要深入了解相关寄存器和功能需要参考《Cortex-M0技术参考手册》。
三、几个实用的嵌入式C程序代码块
1 十六进制字符转整型数字
功能:
将16进制的字符串转换为10进制的数字。我是没有找到相应的库函数,所以参考网上的代码自己手动写了个函数来实现。
常用的函数有atoi,atol,他们都是将10进制的数字字符串转换为int或是long类型,所以在有些情况下不适用。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int c2i(char ch)
{
// 如果是数字,则用数字的ASCII码减去48, 如果ch = '2' ,则 '2' - 48 = 2
if(isdigit(ch))
return ch - 48;
// 如果是字母,但不是A~F,a~f则返回
if( ch < 'A' || (ch > 'F' && ch < 'a') || ch > 'z' )
return -1;
// 如果是大写字母,则用数字的ASCII码减去55, 如果ch = 'A' ,则 'A' - 55 = 10
// 如果是小写字母,则用数字的ASCII码减去87, 如果ch = 'a' ,则 'a' - 87 = 10
if(isalpha(ch))
return isupper(ch) ? ch - 55 : ch - 87;
return -1;
}
int hex2dec(char *hex)
{
int len;
int num = 0;
int temp;
int bits;
int i;
char str[64] = {0};
if(NULL==hex)
{
printf("input para error \n");
return 0;
}
if(('0'==hex[0])&&(('X'==hex[1])||('x'==hex[1])))
{
strcpy(str,&hex[2]);
}else
{
strcpy(str,hex);
}
printf("input num = %s \n",str);
// 此例中 str = "1de" 长度为3, hex是main函数传递的
len = strlen(str);
for (i=0, temp=0; i<len; i++, temp=0)
{
// 第一次:i=0, *(str + i) = *(str + 0) = '1', 即temp = 1
// 第二次:i=1, *(str + i) = *(str + 1) = 'd', 即temp = 13
// 第三次:i=2, *(str + i) = *(str + 2) = 'd', 即temp = 14
temp = c2i( *(str + i) );
// 总共3位,一个16进制位用 4 bit保存
// 第一次:'1'为最高位,所以temp左移 (len - i -1) * 4 = 2 * 4 = 8 位
// 第二次:'d'为次高位,所以temp左移 (len - i -1) * 4 = 1 * 4 = 4 位
// 第三次:'e'为最低位,所以temp左移 (len - i -1) * 4 = 0 * 4 = 0 位
bits = (len - i - 1) * 4;
temp = temp << bits;
// 此处也可以用 num += temp;进行累加
num = num | temp;
}
// 返回结果
return num;
}
int main(int argc, char **argv)
{
int l_s32Ret = 0;
if(2!=argc)
{
printf("=====ERROR!======\n");
printf("usage: %s Num \n", argv[0]);
printf("eg 1: %s 0x400\n", argv[0]);
return 0;
}
l_s32Ret = hex2dec(argv[1]);
printf("value hex = 0x%x \n",l_s32Ret);
printf("value dec = %d \n",l_s32Ret);
return 0;
}
运行结果:
biao@ubuntu:~/test/flash$ ./a.out 0x400
input num = 400
value hex = 0x400
value dec = 1024
biao@ubuntu:~/test/flash$
2 字符串转整型
功能:
将正常输入的16进制或是10进制的字符串转换为int数据类型。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int String2int(char *strChar)
{
int len=0;
const char *pstrCmp1="0123456789ABCDEF";
const char *pstrCmp2="0123456789abcdef";
char *pstr=NULL;
int uiValue=0;
int j=0;
unsigned int t=0;
int i=0;
if(NULL==strChar)
return -1;
if(0>=(len=strlen((const char *)strChar)))
return -1;
if(NULL!=(pstr=strstr(strChar,"0x"))||NULL!=(pstr=strstr(strChar,"0X")))
{
pstr=(char *)strChar+2;
if(0>=(len=strlen((const char *)pstr)))
return -1;
for(i=(len-1);i>=0;i--)
{
if(pstr[i]>'F')
{
for(t=0;t<strlen((const char *)pstrCmp2);t++)
{
if(pstrCmp2[t]==pstr[i])
uiValue|=(t<<(j++*4));
}
}
else
{
for(t=0;t<strlen((const char *)pstrCmp1);t++)
{
if(pstrCmp1[t]==pstr[i])
uiValue|=(t<<(j++*4));
}
}
}
}
else
{
uiValue=atoi((const char*)strChar);
}
return uiValue;
}
int main(int argc, char **argv)
{
int l_s32Ret = 0;
if(2!=argc)
{
printf("=====ERROR!======\n");
printf("usage: %s Num \n", argv[0]);
printf("eg 1: %s 0x400\n", argv[0]);
return 0;
}
l_s32Ret = String2int(argv[1]);
printf("value hex = 0x%x \n",l_s32Ret);
printf("value dec = %d \n",l_s32Ret);
return 0;
}
3 创建文件并填充固定数据
功能:
创建固定大小的一个文件,并且把这个文件填充为固定的数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
//#define FILL_DATA_VALUE 0xff
#define FILL_DATA_VALUE 0x30 //char 0
int c2i(char ch)
{
if(isdigit(ch))
return ch - 48;
if( ch < 'A' || (ch > 'F' && ch < 'a') || ch > 'z' )
return -1;
if(isalpha(ch))
return isupper(ch) ? ch - 55 : ch - 87;
return -1;
}
int hex2dec(char *hex)
{
int len;
int num = 0;
int temp;
int bits;
int i;
char str[64] = {0};
if(NULL==hex)
{
printf("input para error \n");
return 0;
}
if(('0'==hex[0])&&(('X'==hex[1])||('x'==hex[1])))
{
strcpy(str,&hex[2]);
}else
{
strcpy(str,hex);
}
printf("input num = %s \n",str);
len = strlen(str);
for (i=0, temp=0; i<len; i++, temp=0)
{
temp = c2i( *(str + i) );
bits = (len - i - 1) * 4;
temp = temp << bits;
num = num | temp;
}
return num;
}
int main(int argc, char **argv)
{
FILE *l_pFile = NULL;
int l_s32Rest = 0;
unsigned int l_WriteLen = 0;
unsigned int l_FileLen = 0;
unsigned char TempData[1024] = {FILL_DATA_VALUE};
if(3!=argc)
{
printf("usage: %s FileName FileLen \n ", argv[0]);
printf("eg: %s ./Outfile.bin 0x400 \n ", argv[0]);
return 0;
};
const char *l_pFileName = argv[1];
if(NULL==l_pFileName)
{
printf("input file name is NULL \n");
return -1;
}
if(('0'==argv[2][0])&&(('X'==argv[2][1])||('x'==argv[2][1])))
{
l_FileLen = hex2dec(argv[2]);
}else
{
l_FileLen = atoi(argv[2]);
}
printf("Need To Write Data Len %d \n",l_FileLen);
printf("Fill Data Vale = 0x%x \n",FILL_DATA_VALUE);
for(int i=0;i<1024;i++)
{
TempData[i] = FILL_DATA_VALUE;
}
l_pFile = fopen(l_pFileName,"w+");
if(l_pFile==NULL)
{
printf("open file %s error \n",l_pFileName);
return -1;
}
while(l_WriteLen<l_FileLen)
{
if(l_FileLen<1024)
{
l_s32Rest = fwrite(TempData,1,l_FileLen,l_pFile);
}
else
{
l_s32Rest = fwrite(TempData,1,1024,l_pFile);
}
if(l_s32Rest <= 0)
{
break;
};
l_WriteLen +=l_s32Rest;
}
if(NULL!=l_pFile)
{
fclose(l_pFile);
l_pFile = NULL;
}
return 0;
}
运行结果:
biao@ubuntu:~/test/flash$ gcc CreateFile.cpp
biao@ubuntu:~/test/flash$ ls
a.out CreateFile.cpp hex2dec.cpp main.cpp out.bin
biao@ubuntu:~/test/flash$ ./a.out ./out.bin 0x10
input num = 10
Need To Write Data Len 16
Fill Data Vale = 0x30
biao@ubuntu:~/test/flash$ ls
a.out CreateFile.cpp hex2dec.cpp main.cpp out.bin
biao@ubuntu:~/test/flash$ vim out.bin
1 0000000000000000
4 批量处理图片
功能:
批处理将图片前面固定的字节数删除。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#define START_READ_POSITION 128
#define PHOTO_START_TIME 83641
//l_s32PhotoTime = 92809;
int Cut_file(char * InputFile)
{
FILE *l_pFileInput = NULL;
FILE *l_pFileOutput = NULL;
char l_ars8OutputName[128] = {0};
unsigned char l_arru8TempData[1024] = {0};
int l_s32Ret = 0;
static unsigned int ls_u32Num = 0;
if(NULL== InputFile)
{
goto ERROR;
}
//sprintf(l_ars8OutputName,"./outfile/_%s",&InputFile[8]);
sprintf(l_ars8OutputName,"./outfile/00%d.jpg",ls_u32Num++);
//printf("out file name %s \n",l_ars8OutputName);
l_pFileInput = fopen(InputFile,"rb+");
if(NULL==l_pFileInput)
{
printf("input file open error\n");
goto ERROR;
}
l_pFileOutput = fopen(l_ars8OutputName,"w+");
if(NULL==l_pFileOutput)
{
printf("out file open error\n");
goto ERROR;
}
fseek(l_pFileInput,START_READ_POSITION,SEEK_SET);
while(!feof(l_pFileInput))
{
l_s32Ret = fread(l_arru8TempData,1,1024,l_pFileInput);
if(l_s32Ret<0)
{
break;
}
l_s32Ret = fwrite(l_arru8TempData,1,l_s32Ret,l_pFileOutput);
if(l_s32Ret<0)
{
break;
}
}
ERROR:
if(NULL!=l_pFileOutput)
{
fclose(l_pFileOutput);
l_pFileOutput =NULL;
};
if(NULL !=l_pFileInput);
{
fclose(l_pFileInput);
l_pFileInput =NULL;
}
}
int main(void)
{
char l_arrs8InputName[128] = {0};
char l_s8PhotoChannel = 0;
int l_s32PhotoTime = 0;
l_s8PhotoChannel = 3;
l_s32PhotoTime = PHOTO_START_TIME;
/**从第一通道开始**/
for(int j=1;j<l_s8PhotoChannel;j++)
{
for(int i=l_s32PhotoTime;i<235959;i++)
{
memset(l_arrs8InputName,0,sizeof(l_arrs8InputName));
sprintf(l_arrs8InputName,"./image/%dY%06d.jpg",j,i);
if(0==access(l_arrs8InputName,F_OK))
{
printf("%s\n",l_arrs8InputName);
Cut_file(l_arrs8InputName);
}
}
}
}
运行结果:
biao@ubuntu:~/test/photo$ gcc CutFile.cpp
biao@ubuntu:~/test/photo$ ls
a.out CutFile.cpp image outfile
biao@ubuntu:~/test/photo$ ./a.out
./image/1Y083642.jpg
./image/1Y083714.jpg
./image/1Y083747.jpg
./image/1Y083820.jpg
./image/1Y083853.jpg
./image/1Y083925.jpg
./image/1Y084157.jpg
./image/1Y084228.jpg
./image/1Y084301.jpg
./image/1Y084334.jpg
./image/1Y084406.jpg
./image/1Y084439.jpg
./image/1Y084711.jpg
./image/1Y084742.jpg
./image/1Y173524.jpg
./image/1Y173556.jpg
./image/1Y173629.jpg
./image/1Y173702.jpg
./image/1Y173933.jpg
./image/1Y174004.jpg
./image/1Y174244.jpg
./image/1Y174315.jpg
./image/1Y174348.jpg
./image/1Y174420.jpg
./image/1Y174454.jpg
./image/1Y174733.jpg
biao@ubuntu:~/test/photo$ tree
.
├── a.out
├── CutFile.cpp
├── image
│ ├── 1Y083642.jpg
│ ├── 1Y083714.jpg
│ ├── 1Y083747.jpg
│ ├── 1Y083820.jpg
│ ├── 1Y083853.jpg
│ ├── 1Y083925.jpg
│ ├── 1Y084157.jpg
│ ├── 1Y084228.jpg
│ ├── 1Y084301.jpg
│ ├── 1Y084334.jpg
│ ├── 1Y084406.jpg
│ ├── 1Y084439.jpg
│ ├── 1Y084711.jpg
│ ├── 1Y084742.jpg
│ ├── 1Y173524.jpg
│ ├── 1Y173556.jpg
│ ├── 1Y173629.jpg
│ ├── 1Y173702.jpg
│ ├── 1Y173933.jpg
│ ├── 1Y174004.jpg
│ ├── 1Y174244.jpg
│ ├── 1Y174315.jpg
│ ├── 1Y174348.jpg
│ ├── 1Y174420.jpg
│ ├── 1Y174454.jpg
│ └── 1Y174733.jpg
└── outfile
├── 000.jpg
├── 0010.jpg
├── 0011.jpg
├── 0012.jpg
├── 0013.jpg
├── 0014.jpg
├── 0015.jpg
├── 0016.jpg
├── 0017.jpg
├── 0018.jpg
├── 0019.jpg
├── 001.jpg
├── 0020.jpg
├── 0021.jpg
├── 0022.jpg
├── 0023.jpg
├── 0024.jpg
├── 0025.jpg
├── 002.jpg
├── 003.jpg
├── 004.jpg
├── 005.jpg
├── 006.jpg
├── 007.jpg
├── 008.jpg
└── 009.jpg
2 directories, 54 files
biao@ubuntu:~/test/photo$
运行前需要创建两个目录,image用来存放需要处理的图片,outfile用来存放处理过后的文件。这种处理文件批处理方式很暴力,偶尔用用还是可以的。
5 IO控制小程序
嵌入式设备系统一般为了节省空间,一般都会对系统进行裁剪,所以很多有用的命令都会被删除。在嵌入式设备中要调试代码也是比较麻烦的,一般只能看串口打印。现在写了个小程序,专门用来查看和控制海思Hi3520DV300芯片的IO电平状态。
#include <stdio.h>
#include <stdlib.h>
#include "hstGpioAL.h"
int PrintfInputTips(char *ps8Name)
{
printf("=========== error!!! ========\n\n");
printf("usage Write: %s GPIO bit value \n", ps8Name);
printf("usage Read : %s GPIO bit \n", ps8Name);
printf("eg Write 1 to GPIO1_bit02 : %s 1 2 1\n", ps8Name);
printf("eg Read GPIO1_bit02 Value : %s 1 2 \n\n", ps8Name);
printf("=============BT20==================\n")
printf("USB HUB GPIO_0_2 1_UP; 0_Down \n");
printf("RESET_HD GPIO_13_0 0_EN; 1_disEN\n");
printf("Power_HD GPIO_13_3 1_UP; 0_Down \n");
return 0;
}
int main(int argc, char **argv)
{
if((3!=argc)&&(4!=argc))
{
PrintfInputTips(argv[0]);
return -1;
}
unsigned char l_u8GPIONum = 0;
unsigned char l_u8GPIOBit = 0;
unsigned char l_u8SetValue = 0;
GPIO_GROUP_E l_eGpioGroup;
GPIO_BIT_E l_eBit;
GPIO_DATA_E l_eData;
l_u8GPIONum = atoi(argv[1]);
l_u8GPIOBit = atoi(argv[2]);
if(l_u8GPIONum<14)
{
l_eGpioGroup = (GPIO_GROUP_E)l_u8GPIONum;
}else
{
printf("l_u8GPIONum error l_u8GPIONum = %d\n",l_u8GPIONum);
return -1;
};
if(l_u8GPIOBit<8)
{
l_eBit = (GPIO_BIT_E)l_u8GPIOBit;
}else
{
printf("l_u8GPIOBit error l_u8GPIOBit = %d\n",l_u8GPIOBit);
return -1;
}
if(NULL!=argv[3])
{
l_u8SetValue = atoi(argv[3]);
if(0==l_u8SetValue)
{
l_eData = (GPIO_DATA_E)l_u8SetValue;
}else if(1==l_u8SetValue)
{
l_eData = (GPIO_DATA_E)l_u8SetValue;
}else
{
printf("l_u8SetValue error l_u8SetValue = %d\n",l_u8SetValue);
}
}
if(3==argc)
{/**read**/
printf("read GPIO%d Bit%d \n",l_u8GPIONum,l_u8GPIOBit);
/**set input**/
HstGpio_Set_Direction(l_eGpioGroup, l_eBit, GPIO_INPUT);
/**read **/
char l_s8bit_val = 0;
HstGpio_Get_Value(l_eGpioGroup, l_eBit, &l_s8bit_val);
printf("read Data = %d \n",l_s8bit_val);
}else if(4==argc)
{/**write**/
printf("Write GPIO %d; Bit %d; Value %d\n",l_u8GPIONum,l_u8GPIOBit,l_u8SetValue);
/***set IO output*/
HstGpio_Set_Direction(l_eGpioGroup, l_eBit, GPIO_OUPUT);
/**Write To IO**/
HstGpio_Set_Value(l_eGpioGroup,l_eBit,l_eData);
}else
{
}
return 0;
}
6 文件固定位置插入数据
在文件的固定位置插入固定的数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BASIC_FILE_NAME "./nandflash.bin"
#define UBOOT_FILE_NAME "./u-boot.bin"
#define KERNEL_FILE_NAME "./kernel.bin"
#define ROOTFS_FILE_NAME "./rootfs.bin"
#define APP_FILE_NAME "./app.bin"
#define UBOOT_POSITION 0x00
#define KERNEL_POSITION 0x100000
#define ROOTFS_POSITION 0x500000
#define APP_POSITION 0x2700000
int InsertData(FILE *pfBasic,FILE *psInsert,int s32Position)
{
int l_S32Ret = 0;
unsigned char l_arru8Temp[1024] = {0xff};
fseek(pfBasic,s32Position,SEEK_SET);
fseek(psInsert,0,SEEK_SET);
while(1)
{
l_S32Ret = fread(l_arru8Temp,1,1024,psInsert);
if(l_S32Ret > 0)
{
l_S32Ret = fwrite(l_arru8Temp,1,l_S32Ret,pfBasic);
if(l_S32Ret<=0)
{
printf("line %d error l_S32Ret = %d \n",__LINE__,l_S32Ret);
return -1;
}
}else
{
break;
}
}
return 0;
}
int main(void)
{
int l_s32Ret = 0;
FILE *l_pfBasec = NULL;
FILE *l_pfUboot = NULL;
FILE *l_pfKernel = NULL;
FILE *l_pfRootfs = NULL;
FILE *l_pfApp = NULL;
l_pfBasec = fopen(BASIC_FILE_NAME,"r+");
if(NULL==l_pfBasec)
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
l_pfUboot = fopen(UBOOT_FILE_NAME,"r");
if(NULL==l_pfUboot)
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
l_pfKernel = fopen(KERNEL_FILE_NAME,"r");
if(NULL==l_pfKernel)
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
l_pfRootfs = fopen(ROOTFS_FILE_NAME,"r");
if(NULL==l_pfRootfs)
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
l_pfApp = fopen(APP_FILE_NAME,"r");
if(NULL==l_pfApp)
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
if(0> InsertData(l_pfBasec,l_pfUboot,UBOOT_POSITION))
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
if(0> InsertData(l_pfBasec,l_pfKernel,KERNEL_POSITION))
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
if(0> InsertData(l_pfBasec,l_pfRootfs,ROOTFS_POSITION))
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
if(0> InsertData(l_pfBasec,l_pfApp,APP_POSITION))
{
printf("line %d error \n",__LINE__);
goto ERROR;
}
ERROR:
if(NULL!=l_pfBasec)
{
fclose(l_pfBasec);
l_pfBasec = NULL;
}
if(NULL!=l_pfUboot)
{
fclose(l_pfUboot);
l_pfUboot = NULL;
}
if(NULL!=l_pfKernel)
{
fclose(l_pfKernel);
l_pfKernel = NULL;
}
if(NULL!=l_pfRootfs)
{
fclose(l_pfRootfs);
l_pfRootfs = NULL;
}
if(NULL!=l_pfApp)
{
fclose(l_pfApp);
l_pfApp = NULL;
}
return 0;
}
7 获取本地IP地址
在linux设备中获取本地IP地址可以使用下面的程序,支持最大主机有三个网口的设备,当然这个网卡数可以修改。
#include <stdio.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
int get_local_ip(char *ps8IpList)
{
struct ifaddrs *ifAddrStruct;
char l_s8IpAddr[INET_ADDRSTRLEN];
void *tmpAddrPtr;
int l_s32IPCount = 0;
getifaddrs(&ifAddrStruct);
while (ifAddrStruct != NULL)
{
if (ifAddrStruct->ifa_addr->sa_family==AF_INET)
{
tmpAddrPtr=&((struct sockaddr_in *)ifAddrStruct->ifa_addr)->sin_addr;
inet_ntop(AF_INET, tmpAddrPtr, l_s8IpAddr, INET_ADDRSTRLEN);
if (strcmp(l_s8IpAddr, "127.0.0.1") != 0)
{
if(l_s32IPCount == 0)
{
memcpy(ps8IpList, l_s8IpAddr, INET_ADDRSTRLEN);
} else
{
memcpy(ps8IpList+INET_ADDRSTRLEN, l_s8IpAddr, INET_ADDRSTRLEN);
}
l_s32IPCount++;
}
}
ifAddrStruct=ifAddrStruct->ifa_next;
}
freeifaddrs(ifAddrStruct);
return l_s32IPCount;
}
int main()
{
char l_arrs8IpAddrList[3][INET_ADDRSTRLEN];
int l_s32AddrCount;
memset(l_arrs8IpAddrList, 0, sizeof(l_arrs8IpAddrList));
l_s32AddrCount = get_local_ip(*l_arrs8IpAddrList);
for(l_s32AddrCount;l_s32AddrCount>0;l_s32AddrCount--)
{
printf("Server Local IP%d: %s\n",l_s32AddrCount,l_arrs8IpAddrList[l_s32AddrCount-1]);
}
return 0;
}
四、开源MCU简易数字示波器项目
这是一款采用STC8A8K MCU制造的简单示波器,只有零星组件,易于成型。这些功能可以涵盖简单的测量:
该作品主要的规格如下:
- 单片机:STC8A8K64S4A12 @27MHz
- 显示屏:0.96“ OLED,分辨率为 128x64
- 控制器:一个 EC11 编码器
- 输入:单通道
- 秒/秒:500 毫秒、200 毫秒、100 毫秒、50 毫秒、20 毫秒、10 毫秒、5 毫秒、2 毫秒、1 毫秒、500us、200us、100us
100us( 仅在自动触发模式下可用) - 电压范围:0-30V
- 采样额定值:250kHz @100us/格
所有操作均由 EC11 编码器完成。输入包括单击,双击,长按,旋转和旋转时按。这似乎有点复杂,不用担心,下面有细节。该编码器的资源几乎已经耗尽。如果有新功能,可能需要额外的输入组件。
主界面 - 参数模式
- 单击编码器:运行/停止采样。
- 双击编码器:进入波形滚动模式。
- 长按编码器:进入设置界面。
- 旋转编码器:调整参数。
- 按下时旋转编码器:在选项之间切换。
- 切换自动和手动量程:连续顺时针旋转编码器以进入自动量程。逆时针旋转编码器以进入手动范围。
主界面 - 波浪滚动模式
- 单击编码器:运行/停止采样。
- 双击编码器:进入参数模式。
- 长按编码器:进入设置界面。
- 旋转编码器:水平滚动波形。(仅在采样停止时可用)
- 按下时旋转编码器:垂直滚动波形(仅在采样停止时可用)
设置界面
- 单击式编码器:不适用
- 双击编码器:不适用
- 长按编码器:返回主界面。
- 旋转编码器:调整参数。
- 按下时旋转编码器:在选项之间切换。
功能
- 触发电平:对于重复信号,触发电平可以使其在显示屏上稳定。对于单发信号,触发电平可以捕获它。
- 触发斜率:触发斜率确定触发点是在信号的上升沿还是下降沿。
- 触发模式:
- 自动模式:连续扫描。单击编码器可停止或运行采样。如果触发,波形将显示在显示屏上,触发位置将放在图表的中心。否则,波形将不规则地滚动,并且显示屏上将显示“Fail”。
- 正常模式:完成预采样后,可以输入信号。如果触发,波形将显示在显示屏上并等待新的触发。如果没有新的触发器,波形将被保留。
- 单模:完成预采样后,可以输入信号。如果触发,将显示波形并停止采样。用户需要单击编码器才能开始下一次采样。
- 对于正常模式和单模式,请确保已正确调整触发电平,否则显示屏上不会显示波形。
- 指标:通常,指标 on 表示采样正在运行。更重要的用途是在单触发和正常触发模式下,在进入触发阶段之前,需要预先采样。在预采样阶段,指示器不会亮起。在指标亮起之前,我们不应该输入信号。选择的时间尺度越长,预采样的等待时间就越长。
- 保存设置:退出设置界面时,设置和主界面中的所有参数都将保存在EEPROM中。
作品展示部分效果如下:
好了,最好放该项目代码以及资料白嫖地址了:
五、在STM32上实现驱动注册initcall机制
1、前言
每个硬件如LED控制,GPIO口需要初始化,初始化函数bsp_led_init();这个函数需要在主函数中调用初始化,类似这样:
void bsp_init(void)
{
bsp_rcc_init();
bsp_tick_init();
bsp_led_init();
bsp_usart_init();
}
这样存在的问题是:
当有很对驱动,加入100个硬件驱动,我们只用到了了50个,剩下的源文件不参与编译,此时如果忘记将主函数中的相应初始化删除,就会报错。这样操作很麻烦,不能很好的实现单个驱动文件的隔离。
那么现在就提供解决此问题的方式。这个方式源自于Linux内核--initcall机制。具体讲解网络上很多,在此不在详细说明。
可阅读:
keil 之Image:
linux的initcall机制(针对编译进内核的驱动) :
2、代码
头文件:
#ifndef _COLA_INIT_H_
#define _COLA_INIT_H_
#define __used __attribute__((__used__))
typedef void (*initcall_t)(void);
#define __define_initcall(fn, id) \
static const initcall_t __initcall_##fn##id __used \
__attribute__((__section__("initcall" #id "init"))) = fn;
#define pure_initcall(fn) __define_initcall(fn, 0) //可用作系统时钟初始化
#define fs_initcall(fn) __define_initcall(fn, 1) //tick和调试接口初始化
#define device_initcall(fn) __define_initcall(fn, 2) //驱动初始化
#define late_initcall(fn) __define_initcall(fn, 3) //其他初始化
void do_init_call(void);
#endif
源文件:
#include "cola_init.h"
void do_init_call(void)
{
extern initcall_t initcall0init$$Base[];
extern initcall_t initcall0init$$Limit[];
extern initcall_t initcall1init$$Base[];
extern initcall_t initcall1init$$Limit[];
extern initcall_t initcall2init$$Base[];
extern initcall_t initcall2init$$Limit[];
extern initcall_t initcall3init$$Base[];
extern initcall_t initcall3init$$Limit[];
initcall_t *fn;
for (fn = initcall0init$$Base;
fn < initcall0init$$Limit;
fn++)
{
if(fn)
(*fn)();
}
for (fn = initcall1init$$Base;
fn < initcall1init$$Limit;
fn++)
{
if(fn)
(*fn)();
}
for (fn = initcall2init$$Base;
fn < initcall2init$$Limit;
fn++)
{
if(fn)
(*fn)();
}
for (fn = initcall3init$$Base;
fn < initcall3init$$Limit;
fn++)
{
if(fn)
(*fn)();
}
}
在主进程中调用void do_init_call(void)进行驱动初始化,驱动注册初始化时调用:
pure_initcall(fn) //可用作系统时钟初始化
fs_initcall(fn) //tick和调试接口初始化
device_initcall(fn) //驱动初始化
late_initcall(fn)
举个例子:
static void led_register(void)
{
led_gpio_init();
led_dev.dops = &ops;
led_dev.name = "led";
cola_device_register(&led_dev);
}
device_initcall(led_register);
这样头文件中就没有有对外的接口函数了。
3、代码
gitee:
https://gitee.com/schuck/cola_os
girhub: