zoukankan      html  css  js  c++  java
  • 51单片机封装库HML_FwLib_STC89/STC11

    HML_FwLib_STC89/11

    项目地址

    这些项目主要是封装了8051和STC89, STC11的寄存器配置信息, 提供接口方法给上层调用. 因为传统的代码都是直接用八进制值给寄存器赋值进行操作, 不便于记忆, 用这个封装库就可以使用类似于STM的高级语言方式进行开发, 解决了开发过程极度依赖手册的问题. 如果使用STC12C5A60S2系列, 可以用STC11封装库.

    目录结构

    HML_FwLib_STC89
    ├─doc      # store related documents about HML_FwLib_STC89
    ├─example  # 示例代码, 每个c文件代表一个功能示例
    ├─inc      # 头文件, 里面 macro.h 有这个库支持的芯片型号列表
    ├─obj      # 编译输出
    ├─src      # 这个库的c文件, 有部分代码是用汇编写的
    ├─usr      # 这个目录用于存储用户代码, 以及Makefile, 项目带了一个很完整的示例
    ├─LICENSE  # license of HML_FwLib_STC89
    └─VERSION  # version code of HML_FwLib_STC89
    

    这个库实现的类STM32的方法定义, 都在inc/hml/下的.h文件中, 可以对照参考.

    快速开始

    前置条件

    • 首先要安装编译器, sdcc, Ubuntu20.04自带的为3.8.0, 最新的为4.1.0, 可以自己编译安装
      执行sdcc --print-search-dirs查看库文件的位置. 默认的8051的库文件位置为/usr/local/share/sdcc/include/mcs51/
    • 安装烧录工具, stcgal, 直接用pip3 install stcgal安装然后执行stcgal -h查看输出

    使用

    • 将代码clone到本地后, cd进入usr目录
    • 执行make help查看帮助, 这里有很详细的说明
    • 执行make -j编译

    如果过程没问题, 你应当看到这样的输出

    somewhere:~$ make -j
     - Collect MCU config information
    [mcu-model] STC89C52RC (code=8192B, iram=256B, xram=256B)
    [mcu-clock] 11.059200 MHz
    [prescaler] 12T mode
     - Start to build!
    CC  ../src/mem.c
    CC  ../src/tim.c
    CC  ../src/exti.c
    CC  ../src/uart.c
    CC  ../src/gpio.c
    CC  ../src/rst.c
    CC  ../src/util.c
    CC  ../src/isp.c
    CC  ../src/tim2.c
    CC  ../src/wdt.c
     - Make static link library libhml_stc89.lib 
    AR  ../obj/libhml_stc89.lib
     - Compile user source code 
    CC  test.c
     - Generate .ihx file 
    CC ../obj/output.ihx
     - Generate .hex file 
    packihx: read 89 lines, wrote 160: OK.
    ===================================================
    Make done!
    ---------------------------------------------------
    completed at 2021-08-06 14:18:35
    ===================================================
    

    如果需要使用其他编译选项, 例如指定芯片型号, 可以用

    make -j MCU=stc89c52rc"
    make rebuild MCU=stc89c52rc JOBS=4
    

    用stcgal烧录

    stcgal -P stc89 -b 115200 output.ihx
    

    Makefile 分析

    GNU Make 使用手册: https://www.gnu.org/software/make/manual/

    在用户代码目录里有多个Makefile相关文件

    Makefile          # make命令入口文件
    Makefile.config   # 默认的make配置, 如果make时指定了CONF参数, 则使用CONF对应的配置文件
    Makefile.help
    Makefile.mcu
    Makefile.version
    

    分析一下主文件Makefile

    #!/usr/bin/make
    
    # ------------------------------------------------------------------------
    # Author     : Weilun Fong | wlf@zhishan-iot.tk
    # Date       : 2020-02-06
    # Description: project Makefile
    # E-mail     : mcu@zhishan-iot.tk
    # Make-tool  : GNU Make (http://www.gnu.org/software/make/manual/make.html)
    # Page       : https://hw.zhishan-iot.tk/page/hml/detail/fwlib_stc89.html
    # Project    : HML_FwLib_STC89
    # Version    : v0.3.1
    # ------------------------------------------------------------------------
    
    # Package Bash shell command 统一基础命令, 可以复用
    export SHELL       := /bin/bash
    export AWK         := awk
    export BASENAME    := basename
    export CAT         := cat
    export CD          := cd
    export DATE        := date
    export ECHO        := echo
    export EECHO       := $(ECHO) -e
    export GREP        := grep
    export LS          := ls
    export RM          := rm -f
    export TR          := tr
    export TRUE        := true
    export XARGS       := xargs
    
    # Definition of toolchain 统一编译工具, 可以复用
    CROSS_COMPILE      := sd
    AR                 := $(CROSS_COMPILE)ar
    CC                 := $(CROSS_COMPILE)cc
    MAKE               := make --no-print-directory
    PACKIHX            := packihx
    
    # Mark special phony targets 定义编译目标, 可以复用
    PHONY_LIST_IN      := clean distclean help rebuild version
    
    # Definition of project basic path 项目路径定义, 可以复用
    DIR_ROOT           := ..
    DIR_INC            := $(DIR_ROOT)/inc
    DIR_OUTPUT         := $(DIR_ROOT)/obj
    DIR_SRC            := $(DIR_ROOT)/src
    
    # Configure all custom parameters 这里处理自定义参数 CONF, 
    SPACE              := $(empty) $(empty)
    TITLE_COLOR        := 33[36m
    ifeq ($(findstring $(MAKECMDGOALS), $(PHONY_LIST_IN)),) # 符合条件的才去包含, 
                                                            # 如果不在 PHONY_LIST_IN 中, 后面会提示 *** No rule to make target
        ifneq ($(CONF),)          # 如果 CONF 不为空则包含自定义的文件, 这里第二个参数为空值
            include $(CONF)
        else
            include Makefile.config
        endif
        include Makefile.mcu      # 这里包含了芯片型号对应的定义
    endif
    
    #  Definition of of print format 定义输出, 如果VERBOSE为1则输出命令, 且输出提示
    ifeq ("$(VERBOSE)", "1")
        Q :=
        VECHO := @$(TRUE)
    else
        Q := @
        VECHO := @$(ECHO)
    endif
    
    # Important file 组织编译中的目标文件, 输入文件, 输入参数等
    FILE_HML_FWLIB     := libhml_stc89.lib    # 这是最后生成的库文件名称
    HML_SRC_FILES      := $(wildcard $(DIR_SRC)/*.c) # wildcard会列出符合这个文件名格式的文件, 产生一个以空格分隔的字符串列表.
    HML_REL_FILES      := $(patsubst $(DIR_SRC)/%.c, $(DIR_OUTPUT)/%.rel, $(HML_SRC_FILES)) # patsubst 在字符串中替换匹配的串
                                                                                            # 将c文件名列表变为rel文件列表
    MYFILE             ?= test.c # := 是覆盖之前的值, ?= 是如果没有被赋值过就赋予等号后面的值, += 是添加等号后面的值
        # file check
        ifeq ($(findstring $(MAKECMDGOALS),$(PHONY_LIST_IN)),)
            ifneq ($(wildcard $(MYFILE)),$(MYFILE))
                $(error no such file $(CURDIR)/$(MYFILE))
            endif
        endif
    MYFILE_NAME        := $(shell $(BASENAME) $(MYFILE) .c) # 取出MYFILE不带扩展名的文件名
    MYFILE_REL         := $(DIR_OUTPUT)/$(MYFILE_NAME).rel
    # Target file
    TARGET             := output                            # 编译输出结果文件名
    TARGET_FWLIB       := $(DIR_OUTPUT)/$(FILE_HML_FWLIB)
    
    all: $(DIR_OUTPUT)/$(TARGET).hex
    	@$(ECHO) ===================================================
    	@$(ECHO) Make $(MAKECMDGOALS) done!
    	@$(ECHO) ---------------------------------------------------
    	@$(ECHO) completed at `$(DATE) "+%Y-%m-%d %H:%M:%S"`
    	@$(ECHO) ===================================================
    
    # Startup
    startup:
    	@$(EECHO) "$(TITLE_COLOR) - Start to build!33[0m"
    
    # 下面的格式就是标准的Makefile编译
    # 目标 : 需要的文件
    #   CC命令
    # 自动变量, 参考 https://www.gnu.org/software/make/manual/make.html#Automatic-Variables
    # ‘$<’ 第一个需要的文件名
    # ‘$^‘ 所有需要的文件名, 用空格分隔
    # ‘$@’ 目标文件名
    
    # Compile HML source file(*.c) 先编译HML源文件
    $(HML_SRC_FILES): startup
    $(HML_REL_FILES): $(DIR_OUTPUT)/%.rel:$(DIR_SRC)/%.c
    	$(VECHO) "CC  $<"
    	$(Q)$(CC) $< $(CFLAGS) -o $@
    
    # Generate static library
    $(TARGET_FWLIB): $(HML_REL_FILES)
    	@$(EECHO) "$(TITLE_COLOR) - Make static link library `basename $@` 33[0m"
    	$(VECHO) "AR  $@"
    	$(Q)$(AR) $(AFLAGS) $@ $^
    
    # Compile user file
    $(MYFILE_REL): $(MYFILE) $(TARGET_FWLIB)
    	@$(EECHO) "$(TITLE_COLOR) - Compile user source code 33[0m"
    	$(VECHO) "CC  $<"
    	$(Q)$(CC) $< $(CFLAGS) -L$(DIR_OUTPUT) -lhml_stc89 -o $(DIR_OUTPUT)/`$(BASENAME) $@`
    
    # Generate .hex file
    $(DIR_OUTPUT)/$(TARGET).ihx: $(MYFILE_REL)
    	@$(EECHO) "$(TITLE_COLOR) - Generate .ihx file 33[0m"
    	$(VECHO) "CC $@"
    	$(Q)$(CC) $^ $(DIR_OUTPUT)/$(FILE_HML_FWLIB) -o $@
    $(DIR_OUTPUT)/$(TARGET).hex: $(DIR_OUTPUT)/$(TARGET).ihx
    	@$(EECHO) "33[36m - Generate .hex file 33[0m"
    	$(Q)$(PACKIHX) $< > $@
    
    # Phony targets 定义虚目标
    
    # .PHONY用于定义一个虚目标. 正常情况, 按上面的格式, 冒号前面是编译中间产生的文件, 如果这个文件不存在, 那么这个编译在执行
    # make的时候就会被执行, 比如下面的clean, 如果目录里没有clean这个文件, 那么每次都会执行这个clean. 而另一个问题就是如果
    # 目录中已经有一个clean了, 那么在make clean的时候实际上就会不执行. 将其定义为.PHONY就可以避免这个问题, 格式:
    # .PHONY: clean
    # clean:
    #         rm *.o temp
    
    # [+] clean
    .PHONY: clean
    clean:
    	$(CD) $(DIR_OUTPUT) && $(LS) | $(GREP) -vE -e ".gitkeep" -e ^$(MYFILE_NAME)* -e *.lib$$ -e *.hex$$ | $(XARGS) $(RM)
    # [+] distclean
    .PHONY: distclean
    distclean:
    	$(CD) $(DIR_OUTPUT) && $(LS) | $(GREP) -v ".gitkeep" | $(XARGS) $(RM)
    # [+] help
    .PHONY: help
    help:
    	@$(MAKE) -s -f Makefile.help
    # [+] library
    .PHONY: library
    library: $(TARGET_FWLIB)
    	@$(ECHO) ===================================================
    	@$(ECHO) Make $(MAKECMDGOALS) done!
    	@$(ECHO) ---------------------------------------------------
    	@$(ECHO) completed at `$(DATE) "+%Y-%m-%d %H:%M:%S"`
    	@$(ECHO) ===================================================
    # [+] rebuild
    .PHONY: rebuild
    rebuild:
    	@$(EECHO) "$(TITLE_COLOR) - Clean previous files 33[0m"
    	@$(MAKE) distclean
    	@$(MAKE) -f Makefile -j$(JOBS) $(MAKEFLAGS)
    # [+] version
    .PHONY: version
    version:
    	@$(MAKE) -s -f Makefile.version
    

    代码分析

    src/util.c

    这里混合了汇编语言和C语言代码

    • void sleep(uint16_t t)在ASM中调用了前面的两个函数lcall __sleep_getInitValue, lcall __sleep_1ms, 注意这里的函数名相对于C语言的函数, 前面都增加了下划线.

    • 传参使用DPL和DPH, 以及B和ACC, 根据SDCC用户参考 http://sdcc.sourceforge.net/doc/sdccman.pdf 其中的

      Notes on supported Processors->MCS51 variants->Interfacing with Assembler Code, The compiler always uses the global registers DPL, DPH, B and ACC to pass the first (non-bit) parameter to a function, and also to pass the return value of function; according to the following scheme: one byte return value in DPL, two byte value in DPL (LSB) and DPH (MSB). three byte values (generic pointers) in DPH, DPL and B, and four byte values in DPH, DPL, B and ACC.

    代码

    /*****************************************************************************/
    /**
     * author      Qiyuan Chen & Jiabin Hsu
     * date        2020/01/28
     * rief       get _sleep_1ms initial value
     * param[in]   none
     * 
    eturn      none
     * ingroup     UTIL
     * 
    emarks     private function, don' use it
    ******************************************************************************/
    uint16_t _sleep_getInitValue(void)
    {
        return (uint16_t)(MCU_FRE_CLK/(float)12000/8) - 2;
    }
    
    /*****************************************************************************/
    /**
     * author      Qiyuan Chen
     * date        2020/01/28
     * rief       sleep 1 ms
     * param[in]   none
     * 
    eturn      none
     * ingroup     UTIL
     * 
    emarks     private function, don' use it
    ******************************************************************************/
    void _sleep_1ms(void)
    {
        __asm
            mov ar5, r6                 ;#2
        delay1ms_loop$:
            nop                         ;#1
            nop                         ;#1
            nop                         ;#1
            nop                         ;#1
            nop                         ;#1
            nop                         ;#1
            djnz r5, delay1ms_loop$     ;#2
            ret                         ;#2
        __endasm;
    }
    
    /*****************************************************************************/
    /**
     * author      Jiabin Hsu
     * date        2020/01/28
     * rief       software delay according to MCU clock frequency
     * param[in]   t: how many one ms you want to delay
     * 
    eturn      none
     * ingroup     UTIL
     * 
    emarks
    ******************************************************************************/
    void sleep(uint16_t t)
    {
        __asm
            push ar5        ; 当前ar5,ar6,ar7的值入栈保存
            push ar6
            push ar7
    
            push dph        ; 将入参入栈保存
            push dpl
    
        ; freq -> r6,r7
            lcall __sleep_getInitValue ; 根据当前的频率, 计算得到1ms对应的周期数
            mov ar6,dpl                ; 将结果赋值给ar6, ar7
            mov ar7,dph
    
        ; t -> dptr
            pop dpl         ; 恢复入参到dpl,dph
            pop dph
    
        ; 0xFFFF - t
            clr c           ; 清理借位进位 carry
            mov a,#0xFF     ; 带借位的减法, 用ff减去dpl, 然后将结果存入dpl
            subb a,dpl      
            mov dpl,a
            mov a,#0xFF     ; 带借位的减法, 用ff减去dph, 结果存入dph
            subb a,dph
            mov dph,a
    
        ; return if time equals 0
            mov a,dpl       ; dpl存入a
            anl a,dph       ; 与dph按位做AND运算
            cpl a           ; 按位进行取反,原先是1就变为0,原先是0就变为1
            jz ENDL$        ; 为0则跳到结束
    
        ; 对上面的代码分析一下: 
        ;   先用FFFF减去入参, 只要入参不为0, 那么产生的16个bit中一定会有0
        ;   将上下8位做与运算, 一定会把0保留
        ;   再按位取反, 一定会带1, 所以一定不为0
    
        ; loop for sleep
        ; loop from (0xFFFF - t) to (0xFFFF)
        LOOP$:
            lcall __sleep_1ms               ;#8*(frep/12000) - 10 
            inc dptr                        ;#2 在上面的操作之后, dptr里存的就是 ffff减去原入参的值
            mov a,dpl                       ;#1 每次循环加1之后, 做一次判断是否为0, 如果不为0则继续循环
            anl a,dph                       ;#1 这样循环的次数就是入参的值
            cpl a                           ;#1
            nop                             ;#1
            nop                             ;#1
            nop                             ;#1
            jnz LOOP$                       ;#2
        ENDL$:
            pop ar7
            pop ar6
            pop ar5
            ret
        __endasm;
    
        /**
         * 
    ote disable SDCC warning
         */
        t = 0;
    }
    

    HML_FwLib_STC11项目下的util.c代码和STC89是一样的, 在1T模式下, 时钟速度明显快很多, 需要做一些调整, 如果是11.0592晶振, 可以将_sleep_1ms方法替换为

    void _sleep_1ms(void)
    {
        __asm
            nop
            nop
            nop
            nop
            push ar5
            push ar6
            mov ar5,#9
            mov ar6,#148
        NEXT:
            djnz ar6,NEXT
            djnz ar5,NEXT
            pop ar6
            pop ar5
            ret
        __endasm;
    }
    

    这样延迟就明显准确多了. 因为晶振个体会有一些误差, 有些晶振是11.03xxMHz, 比标称值小, 如果需要更准确的定时, 可以通过减小mov ar6,#148的值进行微调.

    参考

  • 相关阅读:
    TCP性能调优
    Qt 实现应用程序单实例运行
    table多选
    多选删除最佳处理
    获取路由或路径
    当前页面打开新页面
    vue版本更新index.html缓存
    vue项目js和css文件名避免浏览器缓存再vue.config.js中配置
    vue动态表格
    IE网页被缓存,get接口缓存
  • 原文地址:https://www.cnblogs.com/milton/p/15110124.html
Copyright © 2011-2022 走看看