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的值进行微调.

参考

原文地址:https://www.cnblogs.com/milton/p/15110124.html