文章目錄
- 如何創建 Keil 工程
- 創建步驟
- 1. 創建文件夾結構
- 2. 打開 Keil
- 3. 創建新工程
- 4. 創建不同類型的內容
- 頭文件
- 明白頭文件包含了什麼
- 標題具體內容
- sfr
- sbit
- bit
- 延時函數(模塊化)
- delay.c
- delay.h
- 外部中斷
- 一、初始化配置
- 二、編寫中斷服務函數
- 關於定時器
- 一、初始化定時器
- 二、編寫中斷服務函數
- 模式一
- 模式二
- 三、啓動定時器
- 關於中斷與段號
- 中斷注意
- 串口通信
- 核心**通信參數**(“波特率” 是關鍵)
- 數據低位先發(對於串口)
- 擴展
- 數碼管
- 宏定義
- 管段與位選
- 顯示緩衝區初始化
- 數據更新函數
- 動態掃描
- 延時函數
- 定時器中斷
- lcd1602
- 關於指令碼
- `RS`
- `E`
- 接口擴展
- 三總線
- 1.1 數據總線(DB - Data Bus)
- 1.2 地址總線(AB - Address Bus)
- 1.3 控制總線(CB - Control Bus)
- EA
- PSEN和RD
- PSEN(自動讀指令)
- RD(受movx控制)
- WR (受movx控制)
- MOVX指令與控制信號的對應關係
- 接口芯片
- 代碼部分
- 擴展端口地址定義
- 數據準備部分
- 1、顯示數據緩衝區(相關顯示的初始化
- 2、編碼轉換表
- 3、狀態數據的維護
- 時序控制部分
- 進行模塊化
- 錯誤與警告
如何創建 Keil 工程
創建步驟
1. 創建文件夾結構
- 先創建一個文件夾
code保存你寫的所有文件 - 創建一個按照工程目的命名的文件夾
2. 打開 Keil
- 打開 Keil 軟件
3. 創建新工程
- 依據圖中選擇相應選項
- 選擇第二步創建的文件夾
- 將文件命名為
project(我一般這樣命名)
4. 創建不同類型的內容
如創建 .c 文件:
第一步
第二步
頭文件
明白頭文件包含了什麼
頭文件包含了該系列單片機的特殊功能寄存器(SFR)定義、位定義以及部分常用常量的聲明。
標題具體內容
右鍵點擊頭文件名(如 reg52.h),在彈出的菜單中選擇 Open document “reg52.h”。
或直接 按住 Ctrl 鍵,用鼠標左鍵點擊頭文件名,即可直接打開該頭文件。
sfr
(Special Function Register)
語法:
語法:sfr 寄存器名 = 地址;
sbit
(Special Bit)
語法:
① sbit 位名 = 寄存器名^位號;
② sbit 位名 = 位地址;
類似於一個標籤,如果用 sbit key = P1^2; 表示的就是在後續使用中,key 可以代表 P1 口的第 3 位,也可以用 sbit key = P1.2; 表示。
bit
一個特殊的數據類型,用於定義位變量。
取值範圍:只能是 0(假)或 1(真)
注意:
bit 是 Keil C51 編譯器的擴展類型,並非標準 C 語言的一部分
主要用於 8051 單片機編程
示例:
bit flag; // 定義一個位變量flag
bit led_state; // 定義一個表示LED狀態的位變量
延時函數(模塊化)
delay.c
#include "reg52.h"
// 假設使用11.0592MHz晶振,STC89C52RC單片機
// 此函數由STC-ISP延時函數生成器生成
/**
* @brief 微秒級延時函數
* @param us 延時微秒數,範圍:0~65535
*/
void delay_us(unsigned int us)
{
unsigned int i;
while(us--)
{
i = 2;
while(i--);
}
}
/**
* @brief 毫秒級延時函數
* @param ms 延時毫秒數,範圍:0~65535
*/
void delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 112; j++); // 11.0592MHz下約1ms的循環
}
delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
// 函數聲明
void delay_ms(unsigned int ms); // 毫秒級延時
void delay_us(unsigned int us); // 微秒級延時
#endif
外部中斷
初始化配置→編寫中斷服務函數 → 中斷觸發與執行
一、初始化配置
- EA:總中斷允許位(=1 允許總中斷)
- EX0:INT0 中斷允許位(P3.2
- EX1:INT1 中斷允許位(P3.3
void External0_Init(void) {
IT0 = 1; // 設置INT0為下降沿觸發方式
EX0 = 1; // 使能INT0中斷
EA = 1; // 開總中斷
}
觸發方式選擇(IT0 = 0 低電平觸發,= 1 下降沿觸發)如果不進行選擇的話就是 低電平觸發
二、編寫中斷服務函數
void External0_ISR(void) interrupt 0 {
// 假設連接在P2.0引腳的LED,每次中斷翻轉其狀態
P2 ^= 0x01;
}
外部中斷函數後綴是INT0 interrupt 0
注意INT1 中斷的中斷號是 2(interrupt 2
關於定時器
初始化定時器 → 編寫中斷服務函數 → 啓動定時器
一、初始化定時器
使用 TMOD 寄存器設置工作模式
常用模式:
模式 1:16 位定時器 / 計數器
模式 2:8 位自動重裝載
模式比較
根據晶振頻率和所需定時時間計算初值
裝入 THx 和 TLx 寄存器
開總中斷:EA = 1
開定時器中斷:ETx = 1
設置優先級 (可選):PTx = 0/1
二、編寫中斷服務函數
模式一
- T:想要的定時時間(單位:秒)
- fosc:晶振頻率(單位:Hz)
- 12:因為 51 單片機默認每個機器週期 = 12 個時鐘週期
定時時間公式:T = (2^N - 初值) × 12 /fosc
模式 1 (16 位) 初值計算:
初值 = 65536 - (T × fosc) / 12
示例:晶振 12MHz,定時 50ms
初值 = 65536 - (50000 × 12000000) / 12 = 15536
TH0 = 15536 / 256;
TL0 = 15536 % 256;
void Timer0_ISR(void) interrupt 1 {
// 1. 重裝載初值(模式1需要)
TH0 = (65536 - 初值) / 256;
TL0 = (65536 - 初值) % 256;
// 2. 執行定時任務
// ...
// 3. 設置標誌位(可選)
timer_flag = 1;
}
模式二
自動重裝載(8位)
8位模式下,初值高八位和低八位相同,即TH0=TL0=初值
// 定時器0初始化函數
void Timer0_Init(void) {
TMOD &= 0xF0; // 清除定時器0的模式(低4位)
TMOD |= 0x02; // 配置定時器0為模式2(8位自動重裝載)
TH0 = 156; // 設置自動重裝載的初值(高8位)
TL0 = 156; // 設置初始計數的初值(低8位)
ET0 = 1; // 使能定時器0中斷
EA = 1; // 使能總中斷
TR0 = 1; // 啓動定時器0
}
三、啓動定時器
TRx = 1; // 啓動定時器
關於中斷與段號
中斷注意
“中斷內部不能執行時間超過 100ms,否則循環不能完成”
核心邏輯:中斷的高優先級會搶佔 CPU,若 ISR 耗時過長,會導致主循環 / 關鍵任務 “得不到 CPU 資源”,要麼被頻繁打斷無法推進,要麼因中斷延遲 / 丟失導致數據異常,最終表現為 “循環不能完成”。
串口通信
什麼是串口
串口對應的就是並口
要傳遞信息,兩器件之間需要共地
核心通信參數(“波特率” 是關鍵)
串口通信前,收發雙方必須約定好以下 4 個參數(參數不匹配會導致通信失敗):
波特率(Baud Rate):數據傳輸的 “速度”,單位是 bps(bit per second,每秒傳輸的 bit 數)。常見值:9600bps、115200bps(最常用,約 11.5KB/s)、460800bps。類比:波特率就像 “説話速度”,雙方必須語速一致,否則一方説太快,另一方聽不清。
數據位(Data Bits):每次傳輸的 “有效數據位數”,通常是 8 位(1 個字節)。比如要傳字符 “a”(ASCII 碼 0x61,二進制 01100001),就用 8 位數據位傳輸。
停止位(Stop Bits):每傳輸完一個 “數據幀” 後,加 1~2 個 bit 的 “停止位”,用於標識 “一幀數據結束”。常見值:1 位停止位(默認)、2 位停止位(用於傳輸可靠性要求高的場景,如工業控制)。
校驗位(Parity Bit):可選參數,用於 “檢查數據是否傳錯”(糾錯機制)。常見類型:無校驗(None,最常用)、奇校驗(Odd)、偶校驗(Even)。比如 8 位數據位 + 奇校驗:8 個數據 bit 的 “1” 的個數是偶數,就加 1 個 “1” 使總個數為奇數;若傳輸後 “1” 的個數不對,説明數據出錯。
數據低位先發(對於串口)
舉例
如果數據是0x55(0101 0101)
那麼在波形圖中顯示應該是 1010 1010(從小往大看)
擴展
代碼流程
硬件初始化 → 發送數據 → 接收數據
初始化流程(以方式 1 為例)
方式 1 是最常用的:8 位異步通信,波特率可變(由定時器 1 溢出率決定)。
1、初始化步驟:
設置 SCON:
- SM0 = 0, SM1 = 1 → 方式 1
- REN = 1 → 允許接收
- 其餘位(SM2、TB8、RB8)可置 0
SCON = 0x50; // 0101 0000
2、設置波特率(用定時器 1,方式 2 自動重載):
- 晶振頻率 fosc 已知(如 11.0592MHz)
- 波特率公式:(波特率 = \frac{2^{SMOD}}{32} \times
\frac{fosc}{12 \times (256 - TH1)}) - 例:波特率
9600,fosc=11.0592MHz,SMOD=0:(TH1 = 256 - \frac{fosc}{32 \times 12
\times 波特率})(TH1 = 256 - \frac{11059200}{32 \times 12 \times 9600}
= 0xFD)
TMOD |= 0x20; // 定時器1 方式2
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 啓動定時器1
開啓中斷(可以選擇):
ES = 1; // 串口中斷允許
EA = 1; // 總中斷允許
- 發送數據流程
(1)查詢方式
void UartSendByte(unsigned char dat)
{
SBUF = dat; // 寫入要發送的數據
while(TI == 0); // 等待發送完成
TI = 0; // 軟件清零發送標誌
}
void UartSendString(unsigned char *str)
{
while(*str)
{
UartSendByte(*str++);
}
}
(2)中斷方式
unsigned char sendBuf[100];
unsigned int sendLen = 0;
unsigned int sendIndex = 0;
void UartSendString_IT(unsigned char *str)
{
sendLen = 0;
while(str[sendLen]) sendLen++;
sendIndex = 0;
SBUF = str[sendIndex++]; // 先發第一個字節
TI = 0;
ES = 1; // 允許串口中斷
}
void UartIsr(void) interrupt 4
{
if(TI) // 發送中斷
{
TI = 0;
if(sendIndex < sendLen)
{
SBUF = sendBuf[sendIndex++];
}
else
{
ES = 0; // 發送完成,關閉中斷
}
}
if(RI) // 接收中斷
{
RI = 0;
// 處理接收數據
}
}
- 接收數據流程
(1)查詢方式
unsigned char UartRecvByte(void)
{
while(RI == 0); // 等待接收完成
RI = 0; // 清標誌
return SBUF; // 返回接收到的數據
}
(2)中斷方式
unsigned char recvByte;
void UartIsr(void) interrupt 4
{
if(RI)
{
RI = 0;
recvByte = SBUF; // 讀取數據
// 在這裏處理接收到的數據
}
}
數碼管
宏定義——>管段與位選——>顯示緩衝區初始化——>數據更新函數——>動態掃描——>
宏定義
#define seg_sel P0 // 段選端口
#define dig_sel P2 // 位選端口
管段與位選
對於共陰極共陽極數碼管一般有不同的管段
比如以下就是一個共陰極數碼管管段
unsigned char code seg_code[] = {
0x3F, 0x06, 0x5B, 0x4F, 0x66, // 0-4
0x6D, 0x7D, 0x07, 0x7F, 0x6F, // 5-9
0x40, // - 10
0x00 // 全滅 索引11
};
以下是一個八位共陰極數碼管的位選,用於進行動態掃描
unsigned char code dig_code[] = {
0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F
};
顯示緩衝區初始化
//初始化
unsigned char disp_buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; //初始設置,根據你的段碼控制
//比如{0,0,10,0,0,10,0,0}表示00-00-00
數據更新函數
依據具體情況書寫,就是一個格式的規定
比如説這是一個時鐘的顯示更新00-00-00
unsigned char hour = 0, minute = 0, second = 0;
void update_display()
{
// 將時間變量轉換為數碼管顯示索引
disp_buf[0] = hour / 10; // 小時十位
disp_buf[1] = hour % 10; // 小時個位
disp_buf[3] = minute / 10; // 分鐘十位
disp_buf[4] = minute % 10; // 分鐘個位
disp_buf[6] = second / 10; // 秒鐘十位
disp_buf[7] = second % 10; // 秒鐘個位
// disp_buf[2]和[5]固定為10,顯示橫槓"-"
}
它的主要作用
動態掃描
一般情況下用延時進行顯示,但是主要還是使用定時器中斷來控制
延時函數
void display_with_delay(void)
{
unsigned char i;
for(i = 0; i < 8; i++)
{
dig_sel = 0xff; // 關閉所有數碼管
seg_sel = seg_code[disp_buf[i]]; // 送段選數據
dig_sel = dig_code[i]; // 送位選數據
DelayMS(2); // 延時2ms顯示
}
}
void main()
{
while(1)
{
display_with_delay(); // 數碼管顯示
check_keys(); // 按鍵檢測
// 其他任務...
}
}
定時器中斷
void timer0_int(void) interrupt 1
{
static unsigned char i = 0;//0-7位選
//在這裏添加你需要的變量或者flag
// 重裝定時器初值
TL0 = (65536 - 2000) % 256; // 2ms定時
TH0 = (65536 - 2000) / 256;
// 數碼管掃描
dig_sel = 0xff; 0 // 關閉所有位選,消除數碼管拖影
seg_sel = seg_code[disp_buf[i]]; // 送段選數據
dig_sel = dig_code[i]; // 送位選信號
if(++i >= 8) i = 0;
//一般會需要實現一些功能比如按鍵
update_display(); //所以會有數碼管段顯示的更新
}
void main()
{
// 定時器初始化
TMOD = 0x01;
TL0 = (65536 - 2000) % 256;
TH0 = (65536 - 2000) / 256;
TR0 = 1;
ET0 = 1;
EA = 1;
while(1)
{
check_keys(); // 按鍵檢測
// 其他任務...
}
}
lcd1602
“|”按位或運算
| 數據部分:0010 0000
| 控制部分:0000 0100
|最終結果:0010 0100
關於指令碼
二進制位:第7位 第6位 第5位 第4位 第3位 第2位 第1位 第0位
LCD1602指令碼的位功能核心是“標識位定類型,控制位定操作”,按指令類型簡潔總結如下:
//瞭解就行
- 功能設置(0x20~0x2F)
標識位:位7-5=001
控制位:位4(DL:8/4位總線)、位3(N:2/1行顯示)、位2(F:5×10/5×7點陣) - 顯示控制(0x08~0x0F)
標識位:位7-4=0000
控制位:位2(D:顯示開關)、位1(C:光標開關)、位0(B:光標閃爍) - 輸入模式設置(0x04~0x07)
標識位:位7-3=00000
控制位:位1(I/D:光標左右移)、位0(S:屏幕是否移位) - 光標/顯示移位(0x10~0x1F)
標識位:位7-4=0001
控制位:位3(S/C:顯示/光標移位)、位2(R/L:左右方向) - 置DDRAM地址(0x800x9F/0xC00xDF)
標識位:位7=1
控制位:位6-0(A5-A0:顯示位置地址,第一行0x000x1F,第二行0x400x5F) - 置CGRAM地址(0x40~0x5F)
標識位:位7-6=01
控制位:位5-0(A5-A0:自定義字符存儲地址) - 讀忙標誌
觸發:RS=0、RW=1
核心位:位7(BF:1=忙,0=空閒)+ 位6-0(當前地址) - 寫/讀數據
寫:RS=1、RW=0(數據寫入DDRAM/CGRAM)
讀:RS=1、RW=1(從DDRAM/CGRAM讀取數據)
RS
用於控制 RS(寄存器模式) 的位是最低位(第 0 位)
RS=0 → 命令模式(向 LCD 發送控制指令,如清屏、設置顯示模式等);
0x04(00000100):第 0 位 = 0 → RS=0(命令模式)
RS=1 → 數據模式(向 LCD 發送要顯示的字符數據,如 ‘A’、‘B’ 或字符串)。
0x05(00000101):第 0 位 = 1 → RS=1(數據模式)
E
用於控制E(使能信號)的位是第2位
E=1(使能高電平):用 |0x04 實現(0x04 二進制 00000100)
E=0(使能低電平):用 &0xFB 實現(0xFB 二進制 11111011)
接口擴展
比如説74LS373是一種常見的“地址鎖存器”:解決總線分時複用問題
38譯碼器則是常見的“地址譯碼器”:將地址信號轉換為設備選擇信號
三總線
1.1 數據總線(DB - Data Bus)
寬度:8位雙向
端口:P0口
特點:分時複用(地址/數據)
負載能力:可驅動8個TTL負載
1.2 地址總線(AB - Address Bus)
寬度:16位(最大64KB尋址)
組成:
P0口:低8位地址(A0-A7)
P2口:高8位地址(A8-A15)
尋址範圍:0000H-FFFFH
1.3 控制總線(CB - Control Bus)
主要信號:
|
信號
|
全稱
|
功能描述
|
|
ALE
|
Address Latch Enable
|
地址鎖存使能,鎖存低8位地址
|
|
PSEN
|
Program Store Enable
|
程序存儲使能,讀取外部程序存儲器
|
|
EA
|
External Access
|
外部訪問使能,選擇內部/外部程序存儲器
|
|
RD
|
Read
|
外部數據存儲器讀使能
|
|
WR
|
Write
|
外部數據存儲器寫使能
|
EA
功能:程序存儲器選擇
EA = 1:先訪問內部ROM,超過4K後訪問外部ROM
EA = 0:只訪問外部程序存儲器
PSEN和RD
PSEN(自動讀指令)
- 程序存儲器訪問
用途:讀取指令代碼
時序:每個機器週期有效兩次
對應指令:所有指令取指週期
RD(受movx控制)
- 數據存儲器訪問
用途:讀取數據
時序:執行MOVX指令時有效
對應指令:MOVX A, @DPTR 等
重要區別:
PSEN訪問程序空間(64KB)
RD訪問數據空間(64KB)
兩者獨立,地址空間重疊但通過不同信號區分
WR (受movx控制)
- 數據存儲器寫入
用途:向外部數據存儲器或外設寫入數據
時序:執行MOVX寫指令時有效
對應指令:MOVX @DPTR, A 或 MOVX @Ri, A
MOVX指令與控制信號的對應關係
MOVX指令:用於訪問外部數據存儲器
讀外部數據存儲器
MOVX A, @DPTR ; 產生RD信號
;寫外部數據存儲器
MOVX @DPTR, A ; 產生WR信號
讀外部程序存儲器(自動)
執行任何指令時,PSEN自動有效
接口芯片
74LS273
7432並
38譯碼器()
74ls373
代碼部分
擴展端口編程 = 地址定義 + 數據準備 + 時序控制
擴展端口地址定義
unsigned char xdata ADDR1 _at_ 0xFFFE;
unsigned char xdata ADDR2 _at_ 0xFFFD;
unsigned char xdata ADDR3 _at_ 0xFFFB;
unsigned char xdata ADDR4 _at_ 0xFFF7;
xdata 表示這是外部數據存儲器地址
at 指定具體的硬件地址
這些地址對應硬件譯碼器輸出的片選信號
功能重定義(提高可讀性)
相當於就是一個備註
#define seg_sel1 ADDR1 // 數碼管1段選
#define dig_sel1 ADDR2 // 數碼管1位選
#define seg_sel2 ADDR3 // 數碼管2段選
#define dig_sel2 ADDR4 // 數碼管2位選
數據準備部分
1、顯示數據緩衝區(相關顯示的初始化
// 原始數據存儲
unsigned char disp_buf1[8] = {2,0,2,5,1,1,1,8};
unsigned char disp_buf2[8] = {1,2,10,5,8,10,3,0};
// 數據處理示例
void process_data(void)
{
// 時間數據轉顯示數據
disp_buf1[0] = hour / 10; // 時的十位
disp_buf1[1] = hour % 10; // 時的個位
disp_buf1[2] = 10; // 分隔符"-"
disp_buf1[3] = minute / 10; // 分的十位
// ...
}
2、編碼轉換表
// 七段數碼管編碼(共陰極)
unsigned char code seg_code[] = {
0x3F, // 0 - 00111111
0x06, // 1 - 00000110
0x5B, // 2 - 01011011
// ...
0x40 // - - 01000000
};
// BCD碼轉七段碼
unsigned char bcd_to_7seg(unsigned char bcd)
{
return seg_code[bcd & 0x0F]; // 只取低4位
}
3、狀態數據的維護
// 系統狀態變量
unsigned char current_mode = 0; // 當前模式
unsigned char blink_flag = 0; // 閃爍標誌
unsigned char edit_position = 0; // 編輯位置
// 數據有效性檢查
if(hour > 23) hour = 0;
if(minute > 59) minute = 0;
時序控制部分
延時
定時器延時
動態掃描時序
狀態機控制
按鍵消抖時序
定時器中斷時序
進行模塊化
單獨創建一個文件夾儲存模塊化文件
.c和.h,當需要是複製到相關文件夾下,在將其添加到列表
注意!!!!
一定要看是不是在一個路徑下面,否則很有可能找不到相關文件
錯誤與警告
有函數未被調用
WARNING LI6: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS
面對類似警告,是因為有的函數未被調用,可以不用理會,忽略就好了