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        := \033[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!\033[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 $@` \033[0m"
	$(VECHO) "AR  $@"
	$(Q)$(AR) $(AFLAGS) $@ $^

# Compile user file
$(MYFILE_REL): $(MYFILE) $(TARGET_FWLIB)
	@$(EECHO) "$(TITLE_COLOR) - Compile user source code \033[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 \033[0m"
	$(VECHO) "CC $@"
	$(Q)$(CC) $^ $(DIR_OUTPUT)/$(FILE_HML_FWLIB) -o $@
$(DIR_OUTPUT)/$(TARGET).hex: $(DIR_OUTPUT)/$(TARGET).ihx
	@$(EECHO) "\033[36m - Generate .hex file \033[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 \033[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
 * \brief       get _sleep_1ms initial value
 * \param[in]   none
 * \return      none
 * \ingroup     UTIL
 * \remarks     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
 * \brief       sleep 1 ms
 * \param[in]   none
 * \return      none
 * \ingroup     UTIL
 * \remarks     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
 * \brief       software delay according to MCU clock frequency
 * \param[in]   t: how many one ms you want to delay
 * \return      none
 * \ingroup     UTIL
 * \remarks
******************************************************************************/
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;

    /**
     * \note 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的值進行微調.

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM