HML_FwLib_STC89/11
項目地址
- https://github.com/MCU-ZHISHAN-IoT/HML_FwLib_STC89
- https://github.com/MCU-ZHISHAN-IoT/HML_FwLib_STC11
這些項目主要是封裝了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
的值進行微調.
參考
- GNU Make 使用手冊: https://www.gnu.org/software/make/manual/
- A51/AX51匯編語言手冊 https://web.engr.uky.edu/~jel/course/587/datasheets/A51.pdf
- SDCC用戶參考 http://sdcc.sourceforge.net/doc/sdccman.pdf