c语言-嵌入式专辑9~

一、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 = 0x02000810PC = 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函数的内容:        whaosoft aiot http://143ai.com


__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$ lsa.out  CreateFile.cpp  hex2dec.cpp  main.cpp  out.binbiao@ubuntu:~/test/flash$ ./a.out ./out.bin 0x10input num = 10 Need To Write Data Len 16 Fill Data Vale = 0x30 biao@ubuntu:~/test/flash$ lsa.out  CreateFile.cpp  hex2dec.cpp  main.cpp  out.binbiao@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中。

    作品展示部分效果如下:

    好了,最好放该项目代码以及资料白嫖地址了:

https://github.com/CreativeLau/Mini-DSO

五、在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:

    https://www.cnblogs.com/idle_man/archive/2010/12/18/1910158.html

    linux的initcall机制(针对编译进内核的驱动) :

    https://www.cnblogs.com/downey-blog/p/10486653.html

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:

https://github.com/sckuck-bit/cola_os

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值