目錄
- 晶片簡介
- 代碼實作
-
- 封裝"IO口狀态轉換"為宏
- 初始化IO口
- 傳輸資料
- 傳輸字元串
- 結語
晶片簡介
本文使用一款 12MHZ、單指令周期(指令周期為1/13微秒)的晶片。在繼續閱讀文章之前,請先查閱如下段落并了解其指令、寄存器及其他說明。
AR:16位立即數寄存器,可指派一個16位的數值
eg: AR = 0x0000
AX:16位立即數寄存器,可指派一個16位的數值
eg: AX = 0x0000
CX:16位立即數寄存器,可指派一個16位的數值,配合LOOP指令使用,實作循環
eg: CX = 0x0006 ;; 循環7次
Your_Label:
AR = 0x0001
LOOP Your_Label ;; 每遇到LOOP指令,CX會自動 -1,當CX < 0時,會跳過此條指令向下運作
JMP Your_Label_1
P0:16為立即數寄存器,用于擷取ROM中某個位址,配合 PM[] 使用,取出該位址中的值
eg: P0 = #Your_Label
AR = PM[P0]
;; 此時的AR将變為 0x0001, 即取出P0指向位址中的值
Your_Label:
DW 0x0001, 0x0002
PUSH:将該寄存器數值入棧儲存
eg: PUSH AR
POP:出棧并将數值儲存至該寄存器
eg: POP AR
JEQ:當結果為0時跳轉(本文中僅了解為AR為0時跳轉)至該标簽
eg: AR = 0x0000
JEQ Your_Label;; 此時跳轉至 Your_Lable
AR = 0x0001
JEQ Your_Label;; 此時不跳轉,向下運作
JAC:C标志位是一個系統寄存器中的某一位,其作用是判斷AR等寄存器是否溢出。
此外左移、右移指令會将移出的一位存儲在C标志位中。JAC指令根據C标志位跳轉,1-跳轉;0-不跳轉
eg: AR = 0x0001
SRA AR ;; SRA為右移指令,将最低位移出到C标志位,其餘位依次向右移動1位
JAC Your_Label ;; 最低位為1,右移到C标志位,C标志位為1,則跳轉。
JMP:無條件跳轉
eg: JMP Your_Label
CALL:調用該函數
eg: CALL Your_Label
XCHG:互換寄存器儲存數值的高低位
eg: AR = 0x1234
XCHG AR ;; 此時AR的值變為 0x3412
MSTR:将字元串轉變為對應的ASCII值,類似于建表的操作。MSB格式。這一指令是該款晶片方提供的操作,具體可以看自己使用IC是否有這一操作。如果沒有的話,可以采用建表的方式實作字元串的轉換。
eg: MSTR "ABCD"
則對應 0x4142, 0x4344
在本文中僅講解了如何了解并實作UART,UART 具體是什麼請參考另一位作者的 概念了解。
- 我采用 1位起始位(下降沿) + 8位資料位 + 2位停止位(上升沿)的傳輸方式,沒有校驗位。
- 波特率為2M bsp,即每秒可以傳輸 2000000 bit 資料,也就是說 每 500納秒(1/2M)要發送一個脈沖信号(每一個脈沖信号發出後,要維持500納秒的時間)。
- 晶片頻率為12MHZ并且它是一個單指令周期晶片,即晶片運作一條指令的時間約為83納秒,也就是說在發送一個脈沖信号後,要維持該脈沖信号約6個指令周期(下文稱為Tw)後,才可以發送下一個脈沖信号。
代碼實作
封裝"IO口狀态轉換"為宏
// 将通信IO口設定為輸出口
MACRO M_UART_SET_PIN_OUTPUT
SET IO[IOC_PA].B0
NOP // 延遲一個指令周期(80nS)
ENDM
// 設定IO口輸出高電平
MACRO M_UART_SET_PIN_HIGH
SET IO[PORTA].B0
ENDM
// 設定IO口輸出低電平
MACRO M_UART_SET_PIN_LOW
CLR IO[PORTA].B0
ENDM
初始化IO口
// 此函數通過CALL調用
UART_Init:
// 調用宏将 IO口 設定為輸出口
M_UART_SET_PIN_OUTPUT
// 調用宏将 IO口 設定為輸出高
// UART協定中,在不傳輸資料時,資料線一直要處于高電平狀
// 此外 開始信号是下降沿開始(即高電平轉換到低電平),是以初始化時将資料線設定為高電平
M_UART_SET_PIN_HIGH
RETS // 退出函數調用,跳轉到調用該函數的下一個位址處
傳輸資料
在傳輸資料時,一定要特别注意脈沖信号穩定時間(Tw)問題。2M的波特率決定了每個脈沖信号要維持500納秒的時間,才可以繼續傳輸下一個脈沖信号。而每一條指令的執行都需要花費一定時間,在計算等待時長時要特别注意!
// ====================================================================================================
// Function: Print_Byte
// Description: Send 8bits through the serial port
// Input: AR
// Output: None
// ====================================================================================================
Print_Byte:
PUSH CX ;; 代碼片段中使用到了CX寄存器,為防止該片段對其他程式的影響,儲存CX的值,在退出該片段時在恢複CX的值
DSI INT0 ;; 關閉中斷,由于晶片的限制,序列槽隻能寫在主循環中,為了保證時間的準确,要暫時關閉中斷,每中斷一次,都會影響到主循環中的序列槽發送
;; Start signal
M_UART_SET_PIN_HIGH ;; 先将 IO 口輸出高,等待IO穩定後再開始傳輸資料
CALL UART_Delay ;; 根據自己的IC寫出的一個延時函數,這個函數實作了500ns的延時
M_UART_SET_PIN_LOW ;; 開始信号,下降沿後維持500ns的時間。
;; 注意下方标記的(1) ~ (5),這五條指令已經占據的5個指令周期也就是500ns,是以不需要再調用 UART_Delay 函數進行延時。
;; 這就是上文中提到的"要注意的等待時長問題",在寫各種通信協定時,一定要注意這些時序問題,哪怕是1條指令的差距都會影響到資料得傳輸。
NOP ;; (1) NOP 是一條空語句,相當于等待一個指令周期的時間,什麼都不做
CX = 0x0007 ;; (2) ;; 循環8次發送8位資料
JMP __Print_Var_Loop__ ;;(3)
__Print_Var_Loop__:
SRC AR ;; (4) 右移資料,将最低位放在 C 标志位中,根據 C标志位 進行跳轉
JAC __Print_Var_Set_High__ ;; (5) 如果最低位是1,則發送高電平,跳轉
;; 否則最低位是0,則發送低電平,向下運作
__Print_Var_Set_Low__:
M_UART_SET_PIN_LOW ;; IO口 輸出低電平,同時仍要注意 "要注意的等待時長問題",查指令的個數
JMP __Print_Var_Set_Confirm__
__Print_Var_Set_High__:
M_UART_SET_PIN_HIGH ;; IO口 輸出高電平,同時仍要注意 "要注意的等待時長問題",查指令的個數
JMP __Print_Var_Set_Confirm__
__Print_Var_Set_Confirm__:
NOP
LOOP __Print_Var_Loop__ ;; 循環8次發送8位資料
;; 這裡 NOP 也是 "要注意的等待時長問題",有興趣的可以想一想為什麼要這樣做
NOP
NOP
;; 至此,發送完了8位資料,要發送一個停止信号,即将 IO拉到高電平。
M_UART_SET_PIN_HIGH
PCH = UART_Delay
CALL UART_Delay
PCH = UART_Delay ;; 這裡等待兩個脈沖信号的時間,是因為停止位越多,容錯性就越強,是以我用了兩個停止位。
CALL UART_Delay ;; 不過這一說法可能有争議性,并不絕對
JMP __Print_Var_Exit__
__Print_Var_Exit__:
POP CX ;; 恢複CX寄存器
ENA INT0 ;; 恢複中斷的運作
ENI
RETS
傳輸字元串
;; MACRO 是建立宏的指令,在這裡将功能封裝為宏使用更友善,可以像 C、py、java等函數一樣傳入參數。
MACRO M_PRINT Content
__Print_Str_Start__:
MSTR Content ;; 将傳入的字元串轉換為ASCII碼,會自動在末尾添加 0x0000,用于判斷是否發送完畢
P0 = #__Print_Str_Start__ ;; 擷取字元串的首位址
__Print_Str_Loop__:
AR = PM[P0++] ;; 取出P0指向位址的值,并且将 P0 +1
PUSH AR ;; 儲存AR的值
XCHG AR ;; 序列槽發送資料從最低位發送,假如資料是 AB,那麼發送的資料順序是BA,是以此處将AR高低位互換,保證順序的正确
AX = 0x00FF ;; 判斷是否是 0x00,繼而判斷字元串是否到了末尾
AR &= AX
JEQ __Print_Str_Exit__ ;; AR 為0表示字元串到了末尾,結束發送
PCH = Print_Byte ;; 否則調用函數發送低八位
LCALL Print_Byte
POP AR ;; 取出儲存的AR值,發送剩下的8位資料,同樣判斷是否到了字元串末尾
AX = 0x00FF
AR &= AX
JEQ __Print_Str_Exit__
PCH = Print_Byte
LCALL Print_Byte
JMP __Print_Str_Loop__ ;; 跳轉,循環
__Print_Str_Exit__:
ENDM
結語
在編寫序列槽通信時,一定要特别注意時序的問題,UART對時間同步非常苛刻。有時多一條指令都會影響到功能的實作。
此外,按照此思路,也可以将晶片中的其他資訊列印出,比如說建表中的内容,寄存器的值等。