51單片機學習 一直想給女兒做一個平衡小車玩具,想用PLC做,感覺難度,用單片機吧,都快20年沒用了。 最近考試考完了,時間和資源都有,正好可以搗鼓一下。看了郭天翔的視頻,講的很不錯, 邊學邊寫。 定時器使用 控制任務: P1.0 控制一個LED燈,亮0.5s,滅0.5s。 設計思路:這裡我們只用定 ...
51單片機學習
一直想給女兒做一個平衡小車玩具,想用PLC做,感覺難度,用單片機吧,都快20年沒用了。
最近考試考完了,時間和資源都有,正好可以搗鼓一下。看了郭天翔的視頻,講的很不錯,
邊學邊寫。
定時器使用
控制任務:
P1.0 控制一個LED燈,亮0.5s,滅0.5s。
設計思路:這裡我們只用定時器,不用軟延時。51的定時器最多定時60ms,所以我們設置定時器每
50ms中斷一次,通過在中斷程式設置一個變數來統計中斷次數,從而實現較長時間的定時。這裡我們
是每500ms執行一次燈亮燈滅的動作,所以每10個中斷等於500ms(50ms x 10)。第6行,全局變數
timer50msCount 就是中斷次數。第19-23行,當timer50msCount 為10時,代表500ms時間到,把
P1.0 取反,動作一次。
這裡有個繁瑣的地方,定時器的初值需要手工計算。不過前人開發了一下小程式,直接拿過來用就可以了。
見下圖。把自動生成代碼中的第一行刪掉就可以了。
程式如下:
#include <reg52.h>
typedef unsigned int uint;
typedef unsigned char uchar;
sbit P10 = P1 ^ 0;
// 第 6 行
uchar timer50msCount = 0;
void Timer0Init(void);
void main()
{
EA = 1; // 開總中斷
ET0 = 1; // 開定時器 0 中斷
Timer0Init();
while (1)
{
// 每500ms允許if語句塊中的程式
// 19 - 23 行
if (timer50msCount == 10)
{
timer50msCount = 0;
P10 = ~P10;
}
}
}
void Timer0Init(void) //[email protected]
{
TMOD &= 0xF0; // 設置定時器模式,這裡為T0
TMOD |= 0x01; // 設置定時器工作方式1,為16為定時器
TL0 = 0x00; // 設置定時器低位初值
TH0 = 0x4C; // 設置定時器高位初值
TF0 = 0; // 清楚TF0溢出標誌位
TR0 = 1; // 啟動定時器0開始計時
}
// 定時器0中斷子程式
void timer0Interrupt() interrupt 1
{
timer50msCount++;
// 每次中斷時,定時器初值為0,需重新設置定時器初值,保持50ms
// 時間不變
TL0 = 0x00;
TH0 = 0x4C;
}
獨立鍵盤
控制任務:
按下 s2 鍵,led 燈亮,鬆手燈滅。
見面的程式用到一個10ms的延時函數,可以用軟體自動生成,見下圖。
圖片選了1ms,和10ms的延時函數是不一致。1ms的程式里包含了_nop()_指令,
使用這條指令,需要在在代碼中使用頭文件intrins.h。
#include <intrins.h>
程式如下:
#include <reg52.h>
typedef unsigned int uint;
typedef unsigned char uchar;
sbit wela = P2 ^ 7;
sbit dula = P2 ^ 6;
sbit s2 = P3 ^ 4; // s2 按鍵
sbit d1 = P1 ^ 0; // led 燈
uchar code table[] = {
0x3f, 0x06, 0x5b, 0x4f,
0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c,
0x39, 0x5e, 0x79, 0x71};
uchar code welaTable[] = {
0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf};
void Delay10ms();
void main()
{
uchar num = 0;
wela = 1;
P0 = 0xfe;
wela = 0;
// 把P3口置高電平
P3 = 0xff;
while (1)
{
// s2 == 0, 表示 s2 變為低電平,此時按鍵按下
if (s2 == 0)
{
Delay10ms(); // 去按下時的抖動
if (s2 == 0)
{
d1 = 0; // 點亮led
num++; // 按鍵次數計數
if (num > 9) // led 數位管計數最大為9, 超出置為0
{
num = 0;
}
}
// 按鍵從按下到釋放,時間有100-200ms,單片機的
// 掃描時間很快,此處防止鍵盤按的過程中,num值反覆加
while (!s2)
;
// 鍵盤鬆手是去毛刺延時
Delay10ms();
}
else
{
d1 = 1;
}
dula = 1;
P0 = table[num];
dula = 0;
}
}
void Delay10ms() //@11.0592MHz
{
unsigned char i, j;
i = 18;
j = 235;
do
{
while (--j)
;
} while (--i);
}
矩陣鍵盤
控制任務:
4 x 4 的矩陣鍵盤,按下第0個鍵,6個數位管都顯示0,按下第一個鍵,6個數位管顯示1,一次類推。
程式如下:
#include <reg52.h>
typedef unsigned int uint;
typedef unsigned char uchar;
sbit wela = P2 ^ 7;
sbit dula = P2 ^ 6;
// 數字碼表,數字最後個0x00為不顯示.
uchar code numberTable[] = {
0x3f, 0x06, 0x5b, 0x4f,
0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c,
0x39, 0x5e, 0x79, 0x71,
0x00};
// 行掃描編碼表, 每個元素代表一行
uchar code keyTable[] = {
0xfe, 0xfd, 0xfb, 0xf7};
void delay10ms();
uchar keyScan();
void show(uchar num);
// 51的c編譯器比較原始,函數中返回的值只能用
// 全局變數
uchar num = 16;
void main()
{
// 此處傳入16,數字碼表選擇0x00,
// 上電時關閉數位管,防止顯示亂碼,
show(16);
wela = 1;
P0 = 0xc0;
wela = 0;
while (1)
{
// keyScan函數返回 0 - 15 數字
uchar keyNum = keyScan();
show(keyNum);
}
}
uchar keyScan()
{
// 鍵盤為4X4的鍵盤矩陣,每個for迴圈掃描一行,while迴圈中掃描列,
// num變數返回這個鍵盤矩陣的鍵位,
// 比如第0個鍵按下,返回0,第一個返回1,以此類推
uchar i = 0, temp;
for (; i < 4; i++)
{
P3 = keyTable[i];
temp = P3 & 0xf0;
while (temp != 0xf0)
{
delay10ms();
temp = P3 & 0xf0;
switch (temp)
{
case 0xe0:
num = 4 * i + 0;
break;
case 0xd0:
num = 4 * i + 1;
break;
case 0xb0:
num = 4 * i + 2;
break;
case 0x70:
num = 4 * i + 3;
break;
}
}
}
return num;
}
void show(uchar num2)
{
dula = 1;
P0 = numberTable[num2];
dula = 0;
}
void delay10ms() //@11.0592MHz
{
unsigned char i, j;
i = 18;
j = 235;
do
{
while (--j)
;
} while (--i);
}
動態顯示
控制任務:
在六個數位管上分別顯示1,2,3,4,5,6
程式如下:
#include <reg52.h>
typedef unsigned int uint;
typedef unsigned char uchar;
sbit wela = P2 ^ 7;
sbit dula = P2 ^ 6;
// 段碼表
uchar code table[] = {
0x3f, 0x06, 0x5b, 0x4f,
0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c,
0x39, 0x5e, 0x79, 0x71};
// 位碼表
uchar code welaTable[] = {
0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf};
void Delay1ms();
void main()
{
uchar i;
while (1)
{
// 依次只顯示一個數位管,但因速度很快,
// 從而造成眼睛的錯覺
for (i = 0; i < 6; i++)
{
P0 = 0xFF; // 消影
wela = 1;
P0 = welaTable[i];
wela = 0;
dula = 1;
P0 = table[i + 1];
dula = 0;
Delay1ms(); // 務必要加延時
}
}
}
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j)
;
} while (--i);
}
AD 轉換
/************************
* 通過AD晶元把模擬量電壓值轉換為數字量 *
* 轉換後的數字量顯示在LED數位管上 *
************************/
#include <reg52.h>
#include <intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit dula = P2 ^ 6; // 段選信號
sbit wela = P2 ^ 7; // 位選信號
sbit adWr = P3 ^ 6; // AD 開始轉換信號,低電平有效
sbit adRd = P3 ^ 7; // AD 開始讀取信號,低電平有效
sbit adCs = P0 ^ 7; // AD 片選信號,低電平有效
// LED 數位管段選碼表
uchar code numberTable[] = {
0x3f, 0x06, 0x5b, 0x4f,
0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c,
0x39, 0x5e, 0x79, 0x71,
0x00};
// LED 數位管位選碼表
uchar code welaTable[] = {
0x5f, 0x6f, 0x77, 0x7b, 0x7d, 0x7e};
void delay5ms();
void showNumOnSelectedLED(uchar number, uchar welaNum);
void main()
{
uchar i, onesPlace, tensPlace, hundredsPlace, adValue;
// AD 晶元片選使能
adCs = 0;
while (1)
{
// 轉換開始
_nop_();
adWr = 0;
_nop_();
adWr = 1;
// 顯示數值,放在adWr後,是為了增加AD晶元的轉換時間
for (i = 0; i < 10; i++)
{
showNumOnSelectedLED(onesPlace, 0);
showNumOnSelectedLED(tensPlace, 1);
showNumOnSelectedLED(hundredsPlace, 2);
}
// adRd讀取轉換後的數字量
_nop_();
adRd = 0;
_nop_();
adValue = P1;
adRd = 1;
// adValue 是讀取到的數字量,然後把個、十、百分離開來
hundredsPlace = adValue / 100;
tensPlace = adValue % 100 / 10;
onesPlace = adValue % 10;
}
}
/**
* @brief 在指定的LED數位上顯示指定的數字
* @note 例如,number = 3, placeOfNum = 0, 那麼為最右邊的第0個數位管顯示數字3
* @param number: 顯示的數字 0 - F
* @param placeOfNum: 顯示的位置,從右到左,分別是 0 - 6 位
* @retval None
*/
void showNumOnSelectedLED(uchar number, uchar placeOfNum)
{
// 送位選數據前關閉所有顯示,防止打開位選鎖存時,
// 原來段選數據通過位選鎖存器造成混亂
P0 = 0xff;
wela = 1;
P0 = welaTable[placeOfNum];
wela = 0;
dula = 1;
P0 = numberTable[number];
dula = 0;
delay5ms();
}
/**
* @brief 延時函數
* @note
* @retval None
*/
void delay5ms() //@11.0592MHz
{
unsigned char i, j;
i = 9;
j = 244;
do
{
while (--j)
;
} while (--i);
}