51单片机学习笔记
51单片机学习笔记
一、开发板各功能介绍
序号 | 板载资源 | 说明 |
---|---|---|
1 | 数码管模块 | 2个四位一体共阴数码管,可显示8位数字字符 |
2 | LCD1602接口 | 可兼容LCD6602/LCD9648液晶屏,可显示数字字符等 |
3 | LCD12864接口 | 兼容LCD9648/MiniLCD12864,可显示汉字/图像 |
4 | 8*8LED点阵 | 可显示数字/字符等 |
5 | LED模块 | 8个LED,可实现流水灯等显示 |
6 | 矩阵按键 | 4*4矩阵按键,输入装置 |
7 | 红外接收头 | NEC协议,可实现遥控 |
8 | DS18B20温度传感器 | 可实现温度采集 |
9 | NRF24L01接口 | 可实现2.4G远程遥控通信 |
10 | 独立按键 | 输入装置 |
11 | MicroUSB接口 | 电源输入,程序下载 |
12 | USB转TTL模块 | CH340C芯片,可作电脑USB与单片机串口下载和通信 |
13 | 3.3V电源模块 | ASM1117-3.3芯片,将5V转为3.3V |
14 | 电源开关 | 控制系统开关 |
15 | AD/DA | 可采集外部模拟信号和输出模拟电压 |
16 | EEPROM模块 | 可储存256字节数据,掉电不丢失 |
17 | 复位按键 | 系统复位 |
18 | 蜂鸣器 | 可作报警提示 |
19 | DS1302时钟模块 | 可作时钟发生 |
20 | 步进电机驱动模块 | ULN2003芯片,可作直流电机、28BYJ48步进电机驱动 |
21 | STC89C52单片机座及IO | 固定单片机,并将IO引出 |
22 | 晶振 | 提供基准频率 |
23 | 74HC595 | 扩展IO,控制LED点阵 |
24 | 74HC245 | 驱动数码管段选显示 |
25 | 74HC138 | 驱动数码管段选显示 |
二、LED
2-1 点亮LED
从该单片机的LED模块原理图可以看到是由 P2 接口控制。
#include <REGX52.H>//调用芯片头文件
int main(){
P2=0x55;//0101 0101(1为高电平,0为低电平)
//一般写为十六进制
}
由 Keil5 生成 .hex 文件,再通过 stc-isp 下载到单片机中成功点亮。
2-2 LED闪烁
LED闪烁需要用到延时函数,可用 stc-isp 的软件延时计算器生成。
#include <REGX52.H>
#include <INTRINS.H>//_nop_()函数所需头文件
void Delay500ms() //@11.0592MHz //晶振的频率
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main(){
while(1){
P2=0xFE;
Delay500ms();
P2=0xFF;
Delay500ms();
}
}
延时Plus版
//先生成延时1ms的函数,再进行循环xms次,达到延时目的
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
2-3 LED流水灯
通过循环加位运算实现
#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
int i;
void main(){
while(1){
for(i=0;i<8;i++){
P2=0xFF;
P2=P2^(1<<i);
Delay1ms(200);
}
}
}
三、独立按键
单片机上电的时候所有io口默认都是高电平
3-1 独立按键控制LED
由独立按键原理图可以看到其接口。
实现按下 K1 使 D1 亮起,松开熄灭。
//P3-1代表P31
//P2-0代表P20
#include <REGX52.H>
void main(){
while(1){
if(P3_1==0){
P2_0=0;
}else{
P2_0=1;
}
}
}
3-2 消除独立按键的抖动
使用 Delay 函数去消除按键抖动
#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main(){
while(1){
if(P3_1==0){
Delay1ms(20);//按下延时20ms
while(P3_1==0){}
Delay1ms(20);//松开延时20ms
P2_0=~P2_0;
}
}
}
3-3 独立按键控制LED显示二进制
#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main(){
int i,j;
unsigned int lnum=0xFF;
while(1){
if(P3_1==0){
Delay1ms(20);
while(!P3_1){}
Delay1ms(20);
lnum--;
//因为该单片机D1在左侧,所以需要进行高低位转换
for(i=0,j=7;i<8;i++,j--){
if(((P2>>j)&1)!=((lnum>>i)&1)) P2=P2^(1<<j);
}
}
}
}
3-4 独立按键控制LED移位
长按 K1 向左移动
长按 K2 向右移动
#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main(){
int l=0;//标记LED的位置
P2=0xFE;
while(1){
if(P3_1==0){
Delay1ms(20);
while(!P3_1){//实现长按
P2|=(1<<l);
l--;
if(l<0) l=7;
P2&=~(1<<l);
Delay1ms(200);
}
Delay1ms(20);
}
if(P3_0==0){
Delay1ms(20);
while(!P3_0){//实现长按
P2|=(1<<l);
l++;
if(l>7) l=0;
P2&=~(1<<l);
Delay1ms(200);
}
Delay1ms(20);
}
}
}
四、数码管
四位数码管的引脚
该数码管为共阴极接法
接法检测方法
- COM共阴极,若是GRN就是共阳极
- 供电平测试
4-1 74HC138译码器
使用 P22 P23 P24 三个I口控制八个输出端,从而控制数码管共极的高低电平。
控制方法
- 3位的二进制数转为十进制控制哪个输出端为低电平
- C控制高位,B控制中位,A控制低位
例如
C | B | A |
---|---|---|
P24 | P23 | P22 |
1 | 0 | 1 |
则 Y5 所接的 LED6 为0,其余都为1。
4-2 数码管动态显示
由原理图上的 P0 接口和 P22 P23 P24 便可控制单个数码管显示。
而实现动态显示只需要不停扫描即可。
#include <REGX52.H>
unsigned char Nixienum[15]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};//初始化
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void Nixie_tube(unsigned char location,number){
location=(8-location);
P2_2=(location>>0)&1;
P2_3=(location>>1)&1;
P2_4=(location>>2)&1;
P0=Nixienum[number];
Delay1ms(1);//消影
P0=0x00;
}
void main(){
int i;
while(1){
for(i=1;i<=8;i++){
Nixie_tube(i,i);
}
}
}
五、LCD1602调试工具
用封装好的 LED1602.c 进行调试
#include <REGX52.H>
#include "LCD1602.h"
void main(){
LCD_Init();
LCD_ShowString(1,1,"Hello World!");
while(1){}
}
其他函数与用法请看 LED1602.c
六、矩阵键盘
当按键按下时,接通两个接口。
可以进行扫描来确认哪个按键按下。
-
逐行扫描
-
逐列扫描
主函数
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char Keynum;
void main(){
LCD_Init();
LCD_ShowString(1,1,"MatrixKey!");
while(1){
Keynum=MatrixKey();
if(Keynum){//如果按下则输出
LCD_ShowNum(2,1,Keynum,2);
}
}
}
MatrixKey 模块(这里采用逐列扫描)
#include <REGX52.H>
#include "Delay.h"
unsigned char MatrixKey(){
unsigned char i,j,Keynumber=0;
for(i=0;i<4;i++){
P1=0xFF;//重置接口
P1=P1&(~(1<<i));//让第i列为0
for(j=4;j<8;j++){
if(!(P1>>j&1)){//检测(i,j)按键是否按下
Delay(20);
while(!(P1>>j&1)){}
Delay(20);
Keynumber=(4-i)+4*(7-j);//获得按键值
break;
}
}
}
return Keynumber;
}
七、定时器
7-1 定时器原理
待补
7-2 定时器使用
定时器一般可直接用 stc-isp 生成
按键控制LED流水方向
主函数
#include <REGX52.H>
#include <INTRINS.H>
#include "Timer0.h"
#include "Key.h"
unsigned int LEDMode;
void main(){
Timer0Init();
P2=0xFE;
while(1){
unsigned int Keynum=Key();
if(Keynum==1){//按K1向左
LEDMode=1;
}
if(Keynum==0){//按K2向右
LEDMode=0;
}
}
}
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=200){
T0Count=0;
if(LEDMode==0){//按K2向右
P2=_crol_(P2,1);
}
if(LEDMode==1){//按K1向左
P2=_cror_(P2,1);
}
}
}
Timer0 模块
#include <REGX52.H>
void Timer0Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1; //进入中断
EA=1;
PT0=0;
}
/*
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000){
T0Count=0;
}
}
*/
Key 模块
#include <REGX52.H>
#include "Delay.h"
unsigned char Key(){
if(P3_0==0){Delay(20);while(!P3_0){}Delay(20);return 0;}
if(P3_1==0){Delay(20);while(!P3_1){}Delay(20);return 1;}
if(P3_2==0){Delay(20);while(!P3_2){}Delay(20);return 2;}
if(P3_3==0){Delay(20);while(!P3_3){}Delay(20);return 3;}
return 4;
}
时钟
#include <REGX52.H>
#include "Timer0.h"
#include "LCD1602.h"
unsigned char hr,mins,sec;
void main(){
Timer0Init();
LCD_Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1," : :");
while(1){
LCD_ShowNum(2,1,hr,2);
LCD_ShowNum(2,4,mins,2);
LCD_ShowNum(2,7,sec,2);
}
}
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000){
T0Count=0;
sec++;
if(sec>=60) sec=0,mins++;
if(mins>=60) mins=0,hr++;
if(hr>=24) hr=0;
}
}
八、串口
8-1 串口通信原理
待补
8-2 串口与电脑数据传输
依然可以使用 stc-isp 去生成 UART_Iint 函数
电脑通过串口控制LED与发送数据
主函数
#include <REGX52.H>
#include "UART.h"
void main(){
UART_Init();
while(1){
}
}
void UART_Routine() interrupt 4
{
if(RI==1){
P2=SBUF;
UART_SendByte(SBUF);
RI=0;
}
}
UART 模块
#include <REGX52.H>
void UART_Init(void) //4800bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA=1; //进入中断
ES=1;
}
void UART_SendByte(unsigned char Byte){
SBUF=Byte;
while(TI==0);
TI=0;
}
//串口中断函数模板
/*
void UART_Routine() interrupt 4
{
if(RI==1){
RI=0;
}
}
*/
九、LED点阵屏
- LED点阵屏结构类似于数码管,只不过数码管把每列像素以“8”字排列
- LED点阵屏有共阴和共阳两种接法(此LED点阵屏为共阴接法)
- LED点阵屏需要进行逐行或逐列扫描,才能使所以LED同时显示
9-1 74HC595模块
- 74H595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出更多位,常用于IO口的扩展。
- 内部实现类似于栈,一直向栈顶输入,再并行输出。
9-2 LED点阵屏实现图像&动画
使用LED点阵屏时,需将单片机上 J24 中的 GND 和 OE 短接
输出 Hello!
主函数
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"
#include "Timer0.h"
unsigned char i=0,Offset=0;//Offset为偏移量
//code可以把数据存在flash里,防止占用过多内存
unsigned char code Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0x08,0x08,0x08,0xFF,0x00,0x0E,0x15,0x15,0x15,0x08,0x00,0xFE,0x01,0x02,0x00,
0xFE,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};//通过取模软件获得图像数据
void main(){
MatrixLED_Init();
Timer0_Init();
while(1){
for(i=0;i<8;i++){
MatrixLED_ShowColum(i,Animation[i+Offset]);
}
}
}
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=50){
T0Count=0;
Offset++;
if(Offset>44) Offset=0;
}
}
MatrixLED 模块
#include <REGX52.H>
#include "Delay.h"
/*
为方便记住各接口功能,这里采用重新定义
*/
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4;
#define MATRIX_LED_PORT P0
void MatrixLED_Init(){
SCK=0;
RCK=0;
}
void _74H595_WriteByte(unsigned char Byte){
unsigned char i;
for(i=0;i<8;i++){
SER=Byte&(0x80>>i);
SCK=1;
SCK=0;
}
RCK=1;
RCK=0;
}
/**
* @brief LED点阵屏显示一列数据
* @param Column 选择的列,范围0~7,0在左边
* @param Data 选择列显示的数据,高位在上,1为亮
* @retval 无
*/
void MatrixLED_ShowColum(unsigned char Colum,Data){
_74H595_WriteByte(Data);
MATRIX_LED_PORT=~(0x80>>Colum);
Delay(1);
MATRIX_LED_PORT=0xFF;
}
十、DS1302实时时钟
引脚名 | 作用 | 引脚名 | 作用 |
---|---|---|---|
VCC2 | 主电源 | CE | 芯片使能 |
VCC1 | 备用电源 | IO | 数据输入/输出 |
GND | 电源地 | SCLK | 串行时钟 |
X1,X2 | 32.768KHz晶振 |
寄存器定义
数据存储
DS1302数据存储为 BCD 码
- BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数
- 在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
- BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
- 十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
时钟
主函数
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
unsigned char Second;
void main(){
LCD_Init();
DS1302_Init();
DS1302_SetTime();
LCD_ShowString(1,1,"20 / /");
LCD_ShowString(2,1," : :");
while(1){
DS1302_ReadTime();
LCD_ShowNum(1,3,DS1302_Time[0],2);
LCD_ShowNum(1,6,DS1302_Time[1],2);
LCD_ShowNum(1,9,DS1302_Time[2],2);
LCD_ShowNum(2,1,DS1302_Time[3],2);
LCD_ShowNum(2,4,DS1302_Time[4],2);
LCD_ShowNum(2,7,DS1302_Time[5],2);
}
}
DS1302 模块
#include <REGX52.H>
sbit DS1302_CE=P3^5;
sbit DS1302_IO=P3^4;
sbit DS1302_SCLK=P3^6;
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E
unsigned char DS1302_Time[]={24,11,2,15,50,0,6};
void DS1302_Init()
{
DS1302_CE=0;
DS1302_SCLK=0;
}
/**
* @brief 按命令写入数据
* @param Command命令,Data写入的数据
* @retval 无
*/
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i;
DS1302_CE=1;
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
for(i=0;i<8;i++){
DS1302_IO=Data&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
/**
* @brief 按命令读取数据
* @param Command命令
* @retval 读出的数据
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data=0;
Command|=0x01;
DS1302_CE=1;
for(i=0;i<8;i++){
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=0;//为使时序对上,这里先赋0 再赋1
DS1302_SCLK=1;
}
for(i=0;i<8;i++){
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0;
return Data;
}
/**
* @brief 时钟的初始设定
* @param 无
* @retval 无
*/
void DS1302_SetTime()
{
DS1302_WriteByte(DS1302_WP,0x00);//写入保护关
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
DS1302_WriteByte(DS1302_WP,0x80);//写入保护开
}
/**
* @brief 读出时间
* @param 无
* @retval 无
*/
void DS1302_ReadTime()
{
unsigned char Temp;
Temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DATE);
DS1302_Time[2]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6]=Temp/16*10+Temp%16;
}
十一、蜂鸣器
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器
-
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
-
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
该蜂鸣器借用了 ULN2003D 的一个接口
需注意ULN2003D中有取反,既输入 1 输出 0
按键提示音
主函数
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Buzzer.h"
unsigned char KeyNum;
void main(){
while(1){
KeyNum=Key();
if(KeyNum){
Buzzer_Time(1000);
}
KeyNum=0;
}
}
Buzzer 模块
#include <REGX52.H>
#include <INTRINS.H>
sbit Buzzer=P2^5;
void Buzzer_Delay500us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 227;
while (--i);
}
void Buzzer_Time(unsigned int ms){
unsigned int i;
for(i=0;i<ms*2;i++){
Buzzer=!Buzzer;
Buzzer_Delay500us();
}
}
十二、AT24C02 (I2C总线)
- AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
- 存储介质:E2PROM
- 通讯接口:I2C总线
- 容量:256字节
12-1 I2C总线
- I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
- 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
- 同步、半双工,带数据应答
I2C时序结构
I2C数据帧
AT24C02数据存储
AT24C02数据帧
AT24C02写入与读取数据
主函数
#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.h"
unsigned char Data,KeyNum;
unsigned int Num;
void main(){
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1){
KeyNum=Key();
if(KeyNum==1){
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2){
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3){
AT24C02_WriteByte(0,Num%256);
Delay(5);
AT24C02_WriteByte(1,Num/256);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4){
//需要用Data进行转接,不然会出现错误
Data=AT24C02_ReadByte(0);
Num=Data;Data=AT24C02_ReadByte(1);
Num|=Data<<8;
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}
I2C 模块
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
void I2C_Start(){
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
void I2C_Stop(){
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
void I2C_SendByte(unsigned char Byte){
unsigned char i;
for(i=0;i<8;i++){
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
unsigned char I2C_ReceiveByte(){
unsigned char Byte=0x00,i;
I2C_SDA=1;
for(i=0;i<8;i++){
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
void I2C_SendAck(unsigned char AckBit){
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
unsigned char I2C_ReceiveAck(){
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
AT24C02 模块
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0
void AT24C02_WriteByte(unsigned char WordAddress,Data){
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
unsigned char AT24C02_ReadByte(unsigned char WordAddress){
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;
}
十三、DS18B20温度传感器
13-1 DS18B20介绍
- DS18B20是一种常见的数字温度传感器
- 测温范围:-55℃到+125℃
- 通信接口:1-Wire(单总线)
- 其他特征:可形成总线结构、内置温度报警器、可寄生供电
储存器结构
13-2 单总线介绍
- 一根通信线:DQ
- 异步,半双工
- 单总线只需一根线便可实现数据双向传输
时序
接收与发送一个字节时,都是低位在前
13-3 操作DS18B20
- 初始化:从机复位,主机判断从机是否响应
- ROM操作:ROM指令+本指令需要的读写操作
- 功能操作:功能指令+本指令需要的读写操作
ROM指令 | 功能指令 |
---|---|
SEARCH ROM [F0h] | CONVERT T [44h] |
READ ROM [33h] | WRITE SCRATCHPAD [4Eh] |
MATCH ROM [55h] | READ SCRATCHPAD [BEh] |
SKIP ROM [CCh] | COPY SCRATCHPAD [48h] |
ALARM SEARCH [ECh] | RECALL E2 [B8h] |
READ POWER SUPPLY [B4h] |
注:h代表16进制
数据帧
温度储存格式
由16位整数组成
前5位为符号位
温度显示
主函数
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
unsigned char Ack;
float T;
void main(){
//先进行温度检测
DS18B20_ConverT();
Delay(500);
LCD_Init();
LCD_ShowString(1,1,"Temperature:");
while(1){
DS18B20_ConverT();
T=DS18B20_ReadT();
if(T<0){
LCD_ShowChar(2,1,'-');
T=-T;
}else
LCD_ShowChar(2,1,'+');
LCD_ShowNum(2,2,T,3);//显示整数部分
LCD_ShowChar(2,5,'.');
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示小数部分
}
}
OneWire 模块
#include <REGX52.H>
#include <INTRINS.H>
sbit OneWire_DQ=P3^7;
unsigned char OneWire_Init(){
unsigned char i,Ack;
OneWire_DQ=1;
OneWire_DQ=0;
_nop_();i = 227;while (--i); //500us
OneWire_DQ=1;
i = 29;while (--i); //70us
Ack=OneWire_DQ;
_nop_();i = 135;while (--i); //300us
return Ack;
}
void OneWire_SendBit(unsigned char Bit){
unsigned char i;
OneWire_DQ=1;
OneWire_DQ=0;
_nop_();i = 3;while (--i); //10us
OneWire_DQ=Bit;
i = 25;while (--i); //55us
OneWire_DQ=1;
}
unsigned char OneWire_ReceiveBit(){
unsigned char i,Bit;
OneWire_DQ=1;
OneWire_DQ=0;
i = 2;while (--i); //5us
OneWire_DQ=1;
i = 2;while (--i); //5us
Bit=OneWire_DQ;
i = 25;while (--i); //55us
return Bit;
}
void OneWire_SendByte(unsigned char Byte){
unsigned char i;
for(i=0;i<8;i++){
OneWire_SendBit(Byte&(0x01<<i));
}
}
unsigned char OneWire_ReceiveByte(){
unsigned char i,Byte=0x00;
for(i=0;i<8;i++){
if(OneWire_ReceiveBit()) Byte|=0x01<<i;
}
return Byte;
}
DS18B20 模块
#include <REGX52.H>
#include "OneWire.h"
//ROM指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_SEARCH_ROM 0xF0
#define DS18B20_READ_ROM 0x33
#define DS18B20_MATCH_ROM 0x55
#define DS18B20_ALARM_SEARCH 0xEC
//功能指令
#define DS18B20_CONVERT_T 0x44
#define DS18B20_WRITE_SCRATCHPAD 0x4E
#define DS18B20_READ_SCRATCHPAD 0xBE
#define DS18B20_COPY_SCRATCHPAD 0x48
#define DS18B20_RECALL_E2 0xB8
#define DS18B20_READ_POWER_SUPPLY 0xB4
void DS18B20_ConverT(){
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_CONVERT_T);
}
float DS18B20_ReadT(){
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
TLSB=OneWire_ReceiveByte();
TMSB=OneWire_ReceiveByte();
Temp=(TMSB<<8)|TLSB;
T=Temp/16.0;//因为最低位是2^-4
return T;
}
温度报警器
主函数
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "Buzzer.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
unsigned char Ack,KeyNum;
float TShow,T;
char Tlow,Thigh;
void main(){
Thigh=AT24C02_ReadByte(1);
Tlow=AT24C02_ReadByte(0);
if(Thigh>125||Tlow<-55||Thigh<=Tlow){
Thigh=25;Tlow=0;
}
DS18B20_ConverT();
Delay(500);
Timer0Init();
LCD_Init();
LCD_ShowString(1,1,"T:");
LCD_ShowString(2,1,"TH:");
LCD_ShowString(2,10,"TL:");
LCD_ShowSignedNum(2,4,Thigh,3);
LCD_ShowSignedNum(2,13,Tlow,3);
while(1){
KeyNum=Key();
if(KeyNum){
if(KeyNum==1){
Thigh++;
if(Thigh>125){Thigh=125;}
}
if(KeyNum==2){
Thigh--;
if(Thigh<=Tlow){Thigh++;}
}
if(KeyNum==3){
Tlow++;
if(Tlow>=Thigh){Tlow--;}
}
if(KeyNum==4){
Tlow--;
if(Tlow<-55){Thigh=-55;}
}
LCD_ShowSignedNum(2,4,Thigh,3);
LCD_ShowSignedNum(2,13,Tlow,3);
AT24C02_WriteByte(1,Thigh);
AT24C02_WriteByte(0,Tlow);
}
}
}
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500){
//温度显示
DS18B20_ConverT();
T=DS18B20_ReadT();
TShow=T;
if(T<0){
LCD_ShowChar(1,3,'-');
TShow=-TShow;
}else
LCD_ShowChar(1,3,'+');
LCD_ShowNum(1,4,TShow,3);
LCD_ShowChar(1,7,'.');
LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);
//检测是否超出范围
if(T>Thigh){
LCD_ShowString(1,13,"OV:H");
Buzzer_Time(50);
}
else if(T<Tlow){
LCD_ShowString(1,13,"OV:L");
Buzzer_Time(50);
}else LCD_ShowString(1,12," ");
T0Count=0;
}
}
十四、直流电机
14-1 PWM介绍
- PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域
为周期- 频率 =
- 占空比 =
- 精度 = 占空比变化步距
(既通过开关的频率来控制亮度或速度)
14-2 LED呼吸灯
可以将呼吸放到定时器里
LED呼吸灯
#include <REGX52.H>
sbit LED=P2^0;
void Delay(unsigned int t){
while(t--);
}
void main(){
unsigned int Time,i;
while(1){
for(Time=0;Time<100;Time++){
for(i=0;i<20;i++){
LED=0;
Delay(100-Time);
LED=1;
Delay(Time);
}
}
for(Time=100;Time>0;Time--){
for(i=0;i<20;i++){
LED=0;
Delay(100-Time);
LED=1;
Delay(Time);
}
}
}
}
14-3 电机调速
电机调速
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"
sbit Motor = P1^0;
unsigned int Compare,KeyNum,Speed;
void main(){
Timer0Init();
while(1){
KeyNum=Key();
if(KeyNum==1){
Speed++;
Speed%=4;
}
if(Speed==0) Compare=0;
if(Speed==1) Compare=40;
if(Speed==2) Compare=80;
if(Speed==3) Compare=100;
}
}
void Timer0_Routine() interrupt 1{
static unsigned int T0Count;
TL0 = 0xA4; //设置定时初值
TH0 = 0xFF; //设置定时初值
T0Count++;
T0Count%=100;
if(T0Count>=Compare){
Motor=0;
}else{
Motor=1;
}
}
十五、AD/DA
15-1 AD/DA介绍
- AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号
- DA(Digital to Analog):数字-模拟转换,将计算机输出的数字信号转换为模拟信号
待补
十六、红外遥控器
16-1 红外遥控器
- 通信方式:单工,异步
- 红外LED波长:940nm
- 通信协议标准:NEC标准
Repeat为重复发送,比如一直按着按键不放
16-2 外部中断
发送红外信号时,需要触发外部中断进行接受信号
INT0和INT1为中断引脚
- STC89C52有4个外部中断
- STC89C52的外部中断有两种触发方式:
下降沿触发和低电平触发
外部中断寄存器与定时器类似
16-3 红外遥控
主函数
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "IR.h"
unsigned char Num;
unsigned char Command;
unsigned char Address;
void main(){
IR_Init();
LCD_Init();
LCD_ShowString(1,1,"ADDR CMD NUM");
LCD_ShowString(2,1,"00 00 000");
while(1){
if(IR_GetDataFlag()||IR_GetRepeatFlag()){
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,7,Command,2);
if(Command==IR_VOL_MINUS){
Num--;
}
if(Command==IR_VOL_ADD){
Num++;
}
LCD_ShowNum(2,12,Num,3);
}
}
}
Timer0 模块
#include <REGX52.H>
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
void Timer0_SetCounter(unsigned int Value){
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_GetCounter(){
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag){
TR0=Flag;
}
Int0 模块
#include <REGX52.H>
void Int0_Init(){
//启用外部中断
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
/*
void Int0_Routine() interrupt 0
{
}
*/
IR 模块
#include <REGX52.H>
#include "Int0.h"
#include "Timer0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4],IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
void IR_Init(){
Int0_Init();
Timer0_Init();
}
unsigned char IR_GetDataFlag(){
if(IR_DataFlag){
IR_DataFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag(){
if(IR_RepeatFlag){
IR_RepeatFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetAddress(){
return IR_Address;
}
unsigned char IR_GetCommand(){
return IR_Command;
}
void Int0_Routine(void) interrupt 0
{
if(IR_State==0){
Timer0_SetCounter(0);
Timer0_Run(1);
IR_State=1;
}
else if(IR_State==1){
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>12442-500&&IR_Time<12442+500){
IR_State=2;
}
else if(IR_Time>10368-500&&IR_Time<10368+500){//判断Repeat
IR_RepeatFlag=1;
Timer0_Run(0);
IR_State=0;
}
else {//防止出错
IR_State=1;
}
}
else if(IR_State==2){
IR_Time=Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time>1032-500&&IR_Time<1032+500){
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
IR_pData++;
}
else if(IR_Time>2074-500&&IR_Time<2074+500){
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
IR_pData++;
}
else {//防止出错
IR_pData=0;
IR_State=1;
}
if(IR_pData>=32){
IR_pData=0;
if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3])){
IR_Address=IR_Data[0];
IR_Command=IR_Data[2];
IR_DataFlag=1;
}
Timer0_Run(0);
IR_State=0;
}
}
}
#ifndef __IR_H__
#define __IR_H__
#define IR_POWER 0x45
#define IR_MODE 0x46
#define IR_MUTE 0x47
#define IR_START_STOP 0x44
#define IR_PREVIOUS 0x40
#define IR_NEXT 0x43
#define IR_EQ 0x07
#define IR_VOL_MINUS 0x15
#define IR_VOL_ADD 0x09
#define IR_0 0x16
#define IR_RPT 0x19
#define IR_USD 0x0D
#define IR_1 0x0C
#define IR_2 0x18
#define IR_3 0x5E
#define IR_4 0x08
#define IR_5 0x1C
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4A
void IR_Init();
unsigned char IR_GetDataFlag();
unsigned char IR_GetRepeatFlag();
unsigned char IR_GetAddress();
unsigned char IR_GetCommand();
#endif
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)