蓝桥杯单片机组(CT107D 开发板)总结

添加芯片头文件

keil c51默认并无携带STC芯片的固件库,添加固件库的步骤以下:html

一、在下载软件右侧tab界面选择“keil仿真设置”标签,并点击下方红框框出的按钮(“添加型号和头文件…到Keil中”按钮)进行添加。如图1所示。
图1
二、选择添加固件库的路径,如图2所示。务必注意要选择Keil软件的安装根目录,选择好以后点击“肯定”。
图2
当咱们再打开keil软件新建工程时,就能够看到咱们所须要的芯片型号了。如图3所示。
图3web

两种程序开头写法

使用 reg52.h 开头的程序

#include “reg52.h”
 
sfr P4 = 0xc0;			// reg52.h 中没有定义 P4 寄存器故本身定义
sbit P3_6 = P4^2;	// 位定义用 P3_6 在程序中替换 P4^2的功能
sbit P3_7 = P4^4;	// 同上

使用 stc15f2k60s2.h 开头的程序

#include “stc15f2k60s2.h”	// 该文件已定义 P4 寄存器故无需重复定义
 
sbit P3_6 = P4^2;	// 位定义用 P3_6 在程序中替换 P4^2 的功能
sbit P3_7 = P4^4;	// 同上

上电初始化

// 经过译码器选择对应的锁存器
void select(uchar num)
{
	switch (num)
	{
    	case 4: P2 = (P2 & 0x1f) | 0x80; break;
    	case 5: P2 = (P2 & 0x1f) | 0xa0; break;
    	case 6: P2 = (P2 & 0x1f) | 0xc0; break;
    	case 7: P2 = (P2 & 0x1f) | 0xe0; break;
    	default: P2 &= 0x1f; break;
	}
}

void init()
{
	// 关闭蜂鸣器、LED灯、数码管
	select(5);  // 关闭蜂鸣器
	P0 = 0x00;
	select(4);  // 关闭LED灯
	P0 = 0xff;
	select(6);  // 选中所有数码管
	P0 = 0xff;
	select(7);  // 关闭数码管
	P0 = 0xff;
	select(0);  // 锁存
}

软件延时(ms)

// Delayms
void delayms(uint time)
{
    uint i, j;
    for (i = time; i > 0; i--)
        for (j = 845; j > 0; j--);
}

数码管显示

uchar numChar[] = {0XC0, 0XF9, 0XA4, 0XB0, 0X99, 0X92, 0X82, 0XF8, 0X80, 0X90}; 
// 数码管
void shumaguan(uchar wei, uchar num)
{
    select(7);  // 关闭数码管
    P0 = 0xff;
    select(0);

    P0 = 0x00;
    select(6);  // 位选
    P0 = 0x80 >> wei;   // [0x80]从左至右 [0x01] 从右至左
    select(0);

    P0 = 0xff;
    select(7);
    P0 = numChar[num];
    select(0);

    Delay3ms();		// 延时 3ms
}

定时器初始化

T0

void Timer0Init(void)   // 10 毫秒 @11.0592MHz
{
	AUXR &= 0x7F;	// 定时器时钟 12T 模式
	TMOD &= 0xF0;	// 设置定时器模式
	TL0 = 0x00;		// 设置定时初值
	TH0 = 0xDC;		// 设置定时初值
	TF0 = 0;		// 清除 TF0 标志
	TR0 = 1;		// 定时器 0 开始计时

	EA = 1;
	ET0 = 1;
}

T1

void Timer1Init(void)   // 10 毫秒 @11.0592MHz
{
	AUXR &= 0xBF;	// 定时器时钟 12T 模式
	TMOD &= 0x0F;	// 设置定时器模式
	TL1 = 0x00;		// 设置定时初值
	TH1 = 0xDC;		// 设置定时初值
	TF1 = 0;		// 清除 TF1 标志
	TR1 = 1;		// 定时器 1 开始计时

	EA = 1;
	ET1 = 1;
}

T2

void Timer2Init(void)   // 10 毫秒 @11.0592MHz
{
	AUXR &= 0xFB;	// 定时器时钟 12T 模式
	T2L = 0x00;		// 设置定时初值
	T2H = 0xDC;		// 设置定时初值
	AUXR |= 0x10;   // 定时器 2 开始计时

	EA = 1;
	IE2 |= 0x04;
}

外部中断初始化

INT0

void initInter0()
{
	IT0 = 1;	// 中断触发方式:[0]低电平触发 [1]下沿触发
	EX0 = 1;	// 打开外部中断
	EA = 1; 	// 开启总中断
}

INT1

void initInter1()
{
	IT1 = 1;	// 中断触发方式:[0]低电平触发 [1]下沿触发
	EX1 = 1;	// 打开外部中断
	EA = 1; 	// 开启总中断
}

矩阵键盘扫描

注:koa

  1. 本程序与开发板上的按键编号并不对应,本代码返回的按键编号总左至右、从上到下依次为 1~16。
1 ~ 4
~ ~
11 ~ 16
// 矩阵键盘
// IAP15 芯片的 WR/RD 功能不是 P36/P37 引脚功能,故用 P42/P44 引脚代替
// 即:P3^6 => P42  P3^7 => P44
uchar keyScan()
{
    uchar keyValue = 0;
    // 键盘初始化
    P3 = 0x0f; P42 = 0; P44 = 0;
    // 键盘扫描
    if (P3 != 0x0f)
    {
        delayms(10);    // 消抖
        if (P3 != 0x0f)
        {
            switch (P3)
            {
                case 0x07: keyValue = 13;break;
                case 0x0b: keyValue = 9; break;
                case 0x0d: keyValue = 5; break;
                case 0x0e: keyValue = 1; break;
                default: return 0;
            }
            // 键盘翻转
            P3 = 0xf0; P42 = 1; P44 = 1;
            // 扫描
            if (!P34) keyValue += 3;
            else if (!P35) keyValue += 2;
            else if (!P42) keyValue += 1;
            else if (!P44) keyValue += 0;
            else return 0;
            // 等待按键抬起
            while (P3 != 0xf0);
            while (!P42);
            while (!P44);
        }
    }
    return keyValue;	// 返回按键值,如无按键按下则返回 0
}

EEPROM

注:svg

  1. AT24C02 地址 0xA0。

单字节读写

// EEPROM 设备地址 0xA0
void writeByte(uchar addr, uchar byt)
{
	IIC_Start();
	IIC_SendByte(devAddr);
	IIC_WaitAck();
	IIC_SendByte(addr);
	IIC_WaitAck();
	IIC_SendByte(byt);
	IIC_WaitAck();
	IIC_Stop();
}

uchar readByte(uchar addr)
{
	uchar dat;
	IIC_Start();
	IIC_SendByte(devAddr);
	IIC_WaitAck();
	IIC_SendByte(addr);
	IIC_WaitAck();
	IIC_Start();
	IIC_SendByte(devAddr | 0x01);
	IIC_WaitAck();
	dat = IIC_RecByte();
	IIC_Ack(0);
	IIC_Stop();
	return dat;
}

多字节读写

// AT24C02 共 256 个字节 分为 8 页 共 32 页
// 页写入
void write(uchar addr, uchar len, uchar *arr)
{
	while (len > 0)
	{
		while (1)
		{
			IIC_Start();
			IIC_SendByte(devAddr);
			if (IIC_WaitAck()) break;
		}
		IIC_SendByte(addr);
		IIC_WaitAck();
		while (len > 0)
		{
			IIC_SendByte(*arr++);
			IIC_WaitAck();
			len--;
			addr++;
			// 判断是否达到页边界
			if ((addr & 0x07) == 0) break;
		}
		IIC_Stop();
	}
}

void read(uchar addr, uchar len, uchar *arr)
{
	while (1)
	{
		IIC_Start();
		IIC_SendByte(devAddr);
		if (IIC_WaitAck()) break;
	}
	IIC_SendByte(addr);
	IIC_WaitAck();
	IIC_Start();
	IIC_SendByte(devAddr | 0x01);
	IIC_WaitAck();
	while (--len)
	{
		*arr++ = IIC_RecByte();
		IIC_Ack(1);
	}
	*arr = IIC_RecByte();
	IIC_Ack(0);
	IIC_Stop();
}

AD/DA

注:函数

  1. PCF8951 地址 0x90。

ADC

// [chl]选择通道
uchar ADC(uchar chl)
{
	uchar value;
	while (1)	// 等待设备应答
	{
		IIC_Start();
		IIC_SendByte(0x90);
		if (IIC_WaitAck()) break;
	}
	IIC_SendByte(chl);
	IIC_WaitAck();
	IIC_Stop();
	
	IIC_Start();
	IIC_SendByte(0x91);
	IIC_WaitAck();
	value = IIC_RecByte();
	IIC_Ack(0);
	IIC_Stop();
	return value;	// 需转换为电压值
}

DAC

void DAC(uchar value)	// 输入值须要转换
{
	while (1)
	{
		IIC_Start();
		IIC_SendByte(devAddr);
		if (IIC_WaitAck()) break;
	}
	IIC_SendByte(0x40);
	IIC_WaitAck();
	IIC_SendByte(value);
	IIC_WaitAck();
	IIC_Stop();
}

温度传感器

注:ui

  1. 本程序只读取正整数。
// DS18B20
// XXXX YYYY YYYY ZZZZ
// [X]正负(0-正 1-负) [Y]温度整数值 [Z]温度小数值(Z * 0.0625)
uchar Temper_Read()
{
	uchar temp, Tl, Th;
	Init_DS18B20();		   // DS18B20 初始化
	Write_DS18B20(0xcc);   // 跳过 ROM 的字节命令
	Write_DS18B20(0x44);   // 开始转换指令
	Delay_OneWire(200);	   // 延时一段时间

    Init_DS18B20();		   // DS18B20 初始化
	Write_DS18B20(0xcc);   // 跳过 ROM 的字节命令
	Write_DS18B20(0xbe);   // 读取指令
	
	Tl=Read_DS18B20();     // 读低八位
	Th=Read_DS18B20();	   // 读高八位
	temp = (Th << 4) | (Tl >> 4);   // 只取整数
	return temp;
}

时钟芯片

// DS1302
writeDS1302(0x8E, 0);   // 关闭写保护
// 突发模式:必须一次性读写所有 8 个字节
// [0xBF]读 [0xBE]写
writeByte(0xBF);    // 突发读
writeByte(0xBE);    // 突发写

串口

注:波特率为 9600bps.net

// UART
void UartInit(void)		//9600bps@11.0592MHz
{
	SCON = 0x50;	// 8位数据,可变波特率
	AUXR |= 0x40;	// 定时器1时钟为Fosc,即1T
	AUXR &= 0xFE;	// 串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;	// 设定定时器1为16位自动重装方式
	TL1 = 0xE0;		// 设定定时初值
	TH1 = 0xFE;		// 设定定时初值
	ET1 = 0;		// 禁止定时器1中断
	TR1 = 1;		// 启动定时器1
	ES = 1;         // 容许串口中断
	EA = 1;         // 开启总中断
}

void UARTInter() interrupt 4
{
	if (TI == 1)
	{
		TI = 0; // 手动清除中断标志
	}
	if (RI == 1)
	{
		RI = 0; // 手动清除中断标志
		SBUF = SBUF + 1;
	}
}

超声波测距模块

// 超声波测距
#define nop() {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();}
uint echo()
{
	uchar i;
	uint distance;
	initTimer();
	
	// 起始信号
	out = 0;
	for (i = 0; i < 16; i++)
	{
		out = ~out;
		nop();nop();nop();nop();nop();
		nop();nop();nop();nop();nop();
	}
	
	// 接收信号
	while (!in);
	TR0 = 1;
	while ((in == 1) && (TF0 == 0));	// 信号结束或计时器计满
	TR0 = 0;
	
	// 计算
	if (TF0)	// 计时器计满,超出测量范围
	{
		distance = 0;
		TF0 = 0;
	}
	else 
	{
		distance = (uint) (TH0 << 8) | TL0;
		distance *= 0.017 * 1.085;		// 单位 cm  (1.085 为偏差补偿)
	}
	return distance;
}

频率测量

注:code

  1. 经过定时器0进行外部触发计数,定时器1进行1s计时,定时器1每触发一次中断经过计算定时器0的计数次数求得频率。

定时器/计数器模式1示意图
OSC 框表示时钟频率,由于1个机器周期等于12个时钟周期,因此那个 d 就等于12。下边 GATE
右边的那个门是一个非门电路,再右侧是一个或门,再往右是一个与门电路。xml

图上能够看出来,下边部分电路是控制了上边部分,那咱们先来看下边是如何控制的,咱们以定时器0为例。htm

  1. TR0 和下边或门电路的结果要进行与运算,TR0 若是是0的话,与运算完了确定是0,因此若是要让定时器工做,那么 TR0 就必须置1。

  2. 这里的与门结果要想获得1,那么前面的或门出来的结果必须也得是1才行。在 GATE
    位为1的状况下,通过一个非门变成0,或门电路结果要想是1的话,那 INT0 即 P3.2 引脚必须是1的状况下,这个时候定时器才会工做,而
    INT0 引脚是0的状况下,定时器不工做,这就是 GATE 位的做用。

  3. 当 GATE 位为0的时候,通过一个非门会变成1,那么无论 INT0 引脚是什么电平,通过或门电路后都确定是1,定时器就会工做。

  4. 要想让定时器工做,就是自动加1,从图上看有两种方式,第一种方式是那个开关打到上边的箭头,就是 C/T =0 的时候,一个机器周期 TL
    就会加1一次,当开关打到下边的箭头,即 C/T =1 的时候,T0 引脚即 P3.4 引脚来一个脉冲,TL 就加1一次,这也就是计数器功能。
    极客学院_5.3 单片机定时器的寄存器:http://wiki.jikexueyuan.com/project/mcu-tutorial-one/timer-register.html

// 频率测量 NE555
uint fre;
uchar count = 0;

void initNE555Fre()
{
	TMOD = 0x15;	// T1:定时器 T0:计数器
	TH0 = TL0 = 0;  // 计数清零
	TL1 = 0x00;		// 设置定时初值
	TH1 = 0x4C;		// 设置定时初值
	TF0 = TF1 = 0;
	TR0 = TR1 = 1;
	ET1 = 1;
	EA = 1;
}

void timer() interrupt 3
{
	TL1 = 0x00;	// 设置重装
	TH1 = 0x4C;	// 设置重装
	if (count++ == 20)
	{
		count = 0;
		fre = (uint) (TH0 << 8) | TL0;
		TL0 = TH0 = 0;
	}
}

PWM

sbit PWM = P1^0;	// PWM 输出口

uchar HTH, HTL;	// 高电平持续时间
uchar LTH, LTL;	// 低电平持续时间
// 高电平计数, 低电平计数, 定时计数
uchar HC, LC, TC;
bit PWMF = 1;	// PWM 标志

void changePWM(uint fre, uchar dc)
{
	float HTime, LTime;
	float time = 1090000.0 / fre;	// 周期 = 10^6毫秒 / 频率(fre)
	LTime = (time * dc / 100) * 11059200 / 12 / 1000000;
	HTime = (time - LTime) * 11059200 / 12 / 1000000;
	if (dc <= 0 || dc >= 100 || fre <= 0)	
	{
		TC = 255;
		if (dc <= 0) PWM = 0;
		else PWM = 1;
		return;
	}
	
	// 计数计算
	if (65536 < HTime) HC = HTime / 65536;
	else HC = 0;
	TH0 = HTH = (65536 - HTime) / 256;
	TL0 = HTL = (uint) (65536 - HTime) % 256;
	if (65536 < LTime) LC = LTime / 65536;
	else LC = 0;
	LTH = (65536 - LTime) / 256;
	LTL = (uint) (65536 - LTime) % 256;
	
	// 初始化
	TH0 = TL0 = 0;
	TC = LC;
	if (HC != 0)
		TC = HC;
}

void startPWM(uint fre, uchar dc)
{
	// PWM 初始化
	PWM = 1;
	TC = HC = LC = 0;
	// 定时器初始化
	TMOD &= 0xf0;
	TMOD |= 0x01;
	changePWM(fre, dc);
	EA = 1;
	TF0 = 0;
	ET0 = 1;
	TR0 = 1;
}

void stopPWM()
{
	TR0 = 0;
	PWM = 1;
}

void PWMTimer() interrupt 1
{
	// 当占空比为 0 或 100 时保持电瓶不变
	if (TC == 255) return;
	
	if (!TC--)
	{
		if (PWM)
		{
			TH0 = HTH;
			TL0 = HTL;
			TC = HC;
		}
		else 
		{
			TH0 = LTH;
			TL0 = LTL;
			TC = LC;
		}
		PWM = ~PWM;
	}
}

注意事项

  1. 操纵时序结束后记得释放数据线:DAT = 1。
  2. IIC 总线:[0]应答 [1]非应答。
  3. IAP15芯片的WR/RD功能不是P36/P37引脚功能,故用P42/P44引脚代替。
  4. _nop_() 函数所在头文件为 intrins.h。

推荐阅读

单片机中文网:http://c.biancheng.net/cpp/danpianji/ 单片机入门基础教程: https://wiki.jikexueyuan.com/list/microcontrollers/ Bkoak 博客:http://www.bkoak.com/ EEPW 论坛:http://forum.eepw.com.cn/thread/302835/1