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) //50ms@11.0592MHz
{
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);
}
ADC0804 模数转换
/************************
* 通过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);
}
DAC0832 数模转换
/*
单片机控制DAC0832芯片输出电流,让发光二极管D12由灭均匀变到
最亮,再有最亮均匀熄灭。最亮和最暗时蜂鸣器发声
*/
#include <intrins.h>
#include <reg52.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit dula = P2 ^ 6;
sbit wela = P2 ^ 7;
sbit daWr = P3 ^ 6;
sbit daCs = P3 ^ 2;
sbit beep = P2 ^ 3; // 蜂鸣器
void delay100ms();
void delay50ms();
void main() {
uchar flag, value;
// 关闭数码管
dula = 0;
wela = 0;
// daCs 和 daWr 低电平时,开始DA开始转换
daCs = 0;
daWr = 0;
// P0 口清零
P0 = 0x00;
while (1) {
if (flag == 0) {
value += 5;
P0 = value;
if (value == 255) {
flag = 1;
beep = 0;
delay100ms();
beep = 1;
}
delay50ms();
} else {
value -= 5;
P0 = value;
if (value == 0) {
flag = 0;
beep = 0;
delay50ms();
beep = 1;
}
delay50ms();
}
}
}
void delay100ms() //@11.0592MHz
{
unsigned char i, j;
i = 180;
j = 73;
do {
while (--j)
;
} while (--i);
}
void delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do {
while (--j)
;
} while (--i);
}