【2022蓝桥杯】模块实验笔记(持续更新)

led 实验


  1. 本次实验是点亮 led ,我在实验中添加了一个枚举类型,用来存放 573 的值。可以提高代码可读性,减少错误。

    //bug: 数码管段选和位选一定要写正确,当无法使用 573 控制器件时,应该检查该枚举类型
    enum
    {//清除         Y4C         Y5C         Y6C	        Y7C
    	clc = 0x1f, led = 0x80, rmb = 0xa0, com = 0xc0, seg = 0xe0	
    };
    
  2. 其次我添加了一个初始化程序,关闭蜂鸣器、继电器、led 和数码管。数码管是共阳极。数码管位选是高电平选中。

    void init(void)
    {
    	P2 = ((P2&clc) | rmb);	//关闭数码管、蜂鸣器、继电器
    	P0 = 0x00;
    	
    	P2 = ((P2&clc) | led);
    	P0 = 0xff;
    	
    	P2 = ((P2&clc) | seg);
    	P0 = 0xff;
    	
    	P2 = ((P2&clc) | com);
    	P0 = 0xff;
    }
    

中断实验


中断初始化函数应该考虑以下内容:

  1. 首先确定使用哪一个定时器;

  2. 通过 AUXR 配置各个定时器速率,[6:7] 分别为 T1 和 T0 速率控制位,[3] 为 T2 速率控制位;如果使用 T2 定时器,则需要在这里打开;
    AUXR

  3. 配置定时器工作模式,配置定时器初值;

  4. 打开定时器,打开定时器中断,打开总中断。

//exp for T1
void init_T1(void)
{
	TMOD = 0x00;	//选择模式0,16位重装载
	AUXR &= 0x3f;	//选择 12M 工作频率
	TL1 = 0xF0;
	TH1 = 0xD8;
	TR1 = 1;		//打开定时器
	ET1 = 1;		//打开定时器中断
	EA = 1;			//打开总中断
}

中断向量表

interrupt

usart 实验


重新复习了串口初始化以及串口中断。

因为比赛使用的是 stc 下载器,它能够生成延时函数。但如果时间过长,则会使用NOP。这个操作的定义在头文件 intrins.h中。所以要额外包含这个头文件。

//exp
#include "intrins.h"

void Delay100ms()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 144;
	k = 71;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

seg 实验


  1. 写 seg 花了好长时间,莫名其妙的问题。但也发现一些应该注意的点。由于 573 的转换需要时间,而单片机的运行速度很快,这就导致在数码管刷新过程中,573 还未锁存完成,P2 和 P0已经改变数值,导致其他数码管产生残影。为避免此情况产生,需要进行延时。每一位数码管应该留至少300us的等待时间,才能够保证正常显示。

  2. 数码管段码计算方式。如下图所示,数码管的八段代表八位,DP 为最高位,A 为最低位。比赛所使用的是共阳数码管,如果要显示数字 1,则应该 B 和 C 亮,其他位灭。所代表的 hex 为:11111001-0xf9

在这里插入图片描述

  1. 经过实验和测试,我采用 1ms 中断扫描来显示数码管,一次中断更新一位数码管。这样编写代码不仅可以有效避免残影的产生,而且方便快捷。代码如下所示:

    unsigned char code SEG[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xff};		//段码表
    unsigned char buf[] = {0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a};		//数码管缓冲区,储存所要显示的数值
    unsigned char slt[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};		//数码管位选数组,便于使用引索位选
    
    //中断内调用此函数
    void seg_scan()
    {
        static uchar i = 0;		//声明为静态变量,作为引索值
    	
    	P2 = ((P2&clc) | seg);
    	P0 = SEG[buf[i]];
    	
    	P2 = ((P2&clc) | com);
    	P0 = slt[i];
    	
    	P2 &= clc;
    	
    	i++;
    	if (i >= 8)		//满八次清零
    		i = 0;
    }
    

DS18B20


one-wire

一根数据线,当空闲时,总线为高;当数据来临,总线被主机或从机拉低,开始数据传输。以下是单总线传输协议。

应答协议(初始化)

主机拉低至少 480us 用来产生复位脉冲,之后释放总线。一段时间后从机会产生应答信号,拉低总线。此时如果程序检测到应答信号,则可证明从机已经准备好。

注意:程序检测应答信号时,一定要延时一段时间之后再进行检测。

读协议

主机发出读数据命令后,拉低总线,开启读时序。等待 1us 后从机发出数据,并保持 15us。结束后释放总线。

所有读时序应至少 60us。

写协议

写时序开始于主机拉低总线。

写 1:拉低总线,15us 后释放。

写 0:拉低总线,持续 60us。

所有写时序至少 60us。

函数编写

比赛提供了底层驱动文件,我们可以直接使用。下面我简单地介绍一下官方给的驱动文件。

我这里为了主程序的简洁,所以将编写的读温度函数放在了 .c 文件中。

onewire.h

在这个文件中,已经添加了一个读温度函数声明。但我们需要自己编写该函数。

#ifndef __ONEWIRE_H

#define __ONEWIRE_H

  

unsigned char rd_temperature(void); //; ;

  

#endif

onewire.c

//延时函数,由于我们使用的是 15 芯片,速度比 51 快 12 倍,所以我们要将该延时增长为
//12t
void Delay_OneWire(unsigned int t)
{

t = 12t;
 while(t--);

}

读取温度流程:

  1. 先调用应答函数,调用写数据函数,写入0xcc,因为单总线上只有一个18b20,所以可以跳过寻址。

  2. 写入0x44,开始进行温度转换。

  3. 再次调用应答函数,写入0xcc,写入0xbe,将数据输出。

  4. 数据低位先出,调用ReadByte先读取低八位,再读取高八位。

    18b20 的数据都是低位先出,编写ReadByte()函数时,应该注意数据方向。

unsigned char rd_temperature()
{
	bit s;
	uchar temp;
	uchar th, tl;
	uint tem;
	
	if (!(s = init_ds18b20()))
		return temp;
	Delay500us();
	Write_DS18B20(0xcc);
	Delay1us();
	Write_DS18B20(0x44);
	Delay100ms();
	
	
	if (!(s = init_ds18b20()))
		return temp;
	Delay500us();
	Write_DS18B20(0xcc);
	Delay1us();
	Write_DS18B20(0xbe);
	
	tl = Read_DS18B20();
	th = Read_DS18B20();
	
	tem = (th << 8) | tl;
	tem >>= 4;
	temp = (uchar)tem;
	return temp;
}

At24c02


IIC

该通信协议使用两根线,SDA(数据)、SCL(时钟)。

时序格式

IIC 在数据传输过程中,时钟线为高电平时,数据不允许变化时钟线为低电平时,数据的高低才允许变化

起始结束信号

起始信号:当时钟线为高电平时,数据线由高电平改变为低电平代表起始信号。

结束信号:当时钟线为高电平时,数据线由低电平改变为高电平代表结束信号。

数据格式

数据格式为 MSB 。

send:Saddr0AdatAstop
发送起始信号器件地址写标志应答数据应答结束
rec:Saddr0byte_addrASaddr1datNAstop
接收起始信号器件地址写标志字节地址应答起始信号字节地址读标志数据非应答结束

发送器件地址后,紧跟着一个读写标志,0 为写;1 为读

应答:主机发出一个字节信号后被从机接收,从机发出一个低电平信号。

At24c02

A2 A1 A0 三个引脚选择器件地址,由外部控制;WP 为写保护,0 正常,1 只读。

读写方式

字节写入:在一次数据帧中只访问一个单元。数据帧格式如上表格所示。

页写入:在一个数据周期内,连续访问一页的内容。此种方式如果操作不当,字节地址会重新返回页首地址,产生“上卷”现象,导致数据写入失败。

当前地址读:发送器件地址后,直接读取数据。

选择读:数据帧格式如上表格所示。

程序设计

使用官方给的驱动文件。自己编写读 / 取函数。

更新:在延时内不用再开启中断,如果把中断打开,则会影响数据的接收。

//延时函数,为了减少中断的影响,在延时期间打开中断
void IIC_Delay(unsigned char i)

{
 do{_nop_();}

 while(i--);
}
// add:字节地址;dat:数据
void At24c02Write(unsigned char add, unsigned char dat)

{

 EA = 0;		//关闭中断,防止中断影响传输过程

 IIC_Start();

 IIC_SendByte(SlaveAddrW);

 IIC_WaitAck();		//等待应答不能少

 IIC_SendByte(add);

 IIC_WaitAck();

 IIC_SendByte(dat);

 IIC_WaitAck();

 IIC_Stop();

 EA = 1;		//重新打开中断

}
unsigned char AT24c02Read(unsigned char add)

{

 unsigned char dat;

  

 EA = 0;

 IIC_Start();

 IIC_SendByte(SlaveAddrW);

 IIC_WaitAck();

 IIC_SendByte(add);

 IIC_WaitAck();

 IIC_Start();

 IIC_SendByte(SlaveAddrR);

 IIC_WaitAck();

 dat = IIC_RecByte();

 IIC_SendAck(1);

 IIC_Stop();

  

 EA = 1;

 return dat;

}

PCF8591


这款芯片是 AD、DA 一体的,通过写入控制字节,来选择通道输出数据。使用 IIC 通信协议。

地址字节

请添加图片描述

地址的高四位固定为 1001,A2、A1、 A0 为器件的地址,再跟一位读写控制字。

控制字节

0XXX0XXX
0DA 输出控制;写1使能0000通道通道

读写方式

一共三个字节,先地址,再控制,最后是 DA 数据。

读通道

我使用两个函数,一个是初始化函数,将通道配置好;另一个是读函数,写入地址后,直接读取数值。

DA 输出

DA 输出只能按顺序写入字节,先写地址-控制字打开 DA(这里要注意通道值,最好不要更改通道)-写入 DA 参数。


IIC 的读写不能一块调用,之间应该延时一段时间。

DS1302


信号传输格式

请添加图片描述

控制字节

请添加图片描述

[7] : 必须为1,如果为0,则 1302 不工作。

[6] : 选择是写日期还是写 RAM。

1302 拥有 31x8 的 RAM 空间

[1:5] : 指定了要操作的寄存器

[0] : 选择读写模式

数据字节

请添加图片描述

CH:停止位,如果为1,时钟停止计时;为0,开始计时。

写入格式为 BCD 码,不能使用十进制。

按键


比赛会使用两种操作,独立按键和矩阵按键。这里默认大家都知道按键原理。

独立按键

此种很简单,当按键按下时,管脚检测到低电平,代表按键按下。但我们在实际使用中,按键按下会发生抖动,导致程序多次检测到按键。为了避免此种情况,我们应该让程序在电平稳定的情况下测量,即消抖

消抖

一种我们使用延时消抖。经过实践,按键抖动一般有 10ms 左右,所以我们只需在检测到低电平后延时 10ms 就可以跳过抖动。此时我们再次检测按键就能得出正确结果。

但延时消抖会让程序死等,也会浪费资源,所以我们采用采样的方式消抖。

在中断服务函数内,对按键采样,如果定时 1ms ,则进一次中断,采一次样。如果采样 8 次,发现这 8 次都是低电平,那我们就可以认为按键已经按下。

这种消抖方式在中断内执行,并不会导致程序死等,避免了资源浪费。

实现方式

先上代码

void key_scan();
void key_drive();
void key_action(uchar keycode);

uchar KeySta[] = { 1, 1, 1, 1};
uchar KeyMap[] = { 0x01 , 0x02 , 0x03, 0x04};	//这是按键对应的键值,可以方便我们查看按键,也可和标准接轨。可以自己设定
uchar Backup[] = {1, 1, 1, 1};		//保存上一次的键值,防止多次触发按键


/**
* 这个函数在主循环内调用,持续判断是否有按键按下
**/
void key_drive()
{
    uchar i;
    
    for (i=0; i<4; i++)
    {
        if (KeySta[i] != 0)
        {
            if (Backup[i] != 1)
            {
                key_aciton(KeyMap[i]);		//触发按键动作
            }
            Backup[i] = KeySta[i];		//保存状态
        }
    }
}


void key_action(uchar keycode)
{
    // 编写按键动作
}


/**
* 这个函数在中断内调用,推荐时间 1ms
**/
void key_scan()
{
    static uchar KeyBuf[] = {0xff, 0xff, 0xff, 0xff};		//采样缓冲区
    uchar i;
    
    KeyBuf[0] = (KeyBuf[0] << 1) | P30;
    KeyBuf[1] = (KeyBuf[1] << 1) | P31;
    KeyBuf[2] = (KeyBuf[2] << 1) | P32;
    KeyBuf[3] = (KeyBuf[3] << 1) | P33;		//四个按键的采样
    
    for (i=0; i<4; i++)
    {
        if ((KeyBuf[i] & 0xff) == 0xff)
        {
            KeySta[i] = 1;		//8 次全为1,则认为按键没有按下
        }
        else if ((KeyBuf[i] & 0xff) == 0x00)
        {
            KeySta[i] = 0;		//8 次全为0,则认为按键按下
        }
    }
}

矩阵按键

矩阵按键比独立按键复杂许多,但思想是一模一样的。只是多出了几个按键。

上代码

void key_drive();
void key_scan();
void key_action(uchar);

uchar KeySta[4][4] = {		//4x4 的按键,所以使用 2 维数组。其实很简单,就是四个一维数组
	{1, 1, 1, 1},
	{1, 1, 1, 1},
	{1, 1, 1, 1},
	{1, 1, 1, 1}
};
uchar code KeyMap[4][4] = {		//对应板子上的值
	{0x07, 0x11, 0x15, 0x19},
	{0x06, 0x10, 0x14, 0x18},
	{0x05, 0x09, 0x13, 0x17},
	{0x04, 0x08, 0x12, 0x16}
};

void key_drive()
{
	static uchar back_up[4][4] = {
		{1, 1, 1, 1},
		{1, 1, 1, 1},
		{1, 1, 1, 1},
		{1, 1, 1, 1}
	};
	uchar i, j;
	
	for (i=0; i<4; i++)		//遍历所有二维数组
	{
		for (j=0; j<4; j++)
		{
			if (KeySta[i][j] != back_up[i][j])
			{
				if (back_up[i][j] != 0)
				{
					key_action(KeyMap[i][j]);
				}
				back_up[i][j] = KeySta[i][j];
			}
		}
	}
}

void key_action(uchar code)
{
}

void key_scan()
{
	static uchar key_buf[4][4] = {
		{0xff, 0xff, 0xff, 0xff},
		{0xff, 0xff, 0xff, 0xff},
		{0xff, 0xff, 0xff, 0xff},
		{0xff, 0xff, 0xff, 0xff}
	};
	static uchar key_index = 0;		//引索,代表本次扫描的行数
	uchar j;
	
	key_buf[key_index][0] = (key_buf[key_index][0] << 1) | P44;		//采样
	key_buf[key_index][1] = (key_buf[key_index][1] << 1) | P42;
	key_buf[key_index][2] = (key_buf[key_index][2] << 1) | P35;
	key_buf[key_index][3] = (key_buf[key_index][3] << 1) | P34;
	
	for (j=0; j<4; j++)
	{
		if ((key_buf[key_index][j] & 0x0f) == 0x0f)
		{
			KeySta[key_index][j] = 1;
		}
		else if ((key_buf[key_index][j] & 0x0f) == 0x00)
		{
			KeySta[key_index][j] = 0;
		}
	}
	
	key_index++;
	key_index &= 0x03;		//满 4 清零
	
	switch (key_index)		//拉低下一次要扫描的行数
	{
		case 0: P33 = 1; P30 = 0; break;
		case 1: P30 = 1; P31 = 0; break;
		case 2: P31 = 1; P32 = 0; break;
		case 3: P32 = 1; P33 = 0; break;
	}
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数码管是一种常见的数字显示器件,可以用于显示各种数字、字母等字符。在单片机应用中,数码管通常用于显示计数器、计时器、温度、湿度等实时数据。 数码管的种类有很多,包括共阳数码管、共阴数码管、共阳共阴混合数码管等。其中,共阳数码管是最常见的一种,也是本文所涉及的数码管类型。 单片机控制数码管的原理是通过对数码管的各个管脚进行控制,使其显示相应的数字或字符。数码管的控制方式有两种,即静态显示和动态显示。 静态显示是指将要显示的数字或字符的每一位分别输出到数码管的每个管脚上,然后使其保持不变,从而实现显示效果。静态显示的缺点是需要使用大量的I/O口,且不能灵活地改变显示内容。 动态显示是指将要显示的数字或字符的每一位依次输出到数码管的每个管脚上,并在短时间内快速切换下一个数字或字符,从而形成连续的显示效果。动态显示的优点是可以使用较少的I/O口,且可以灵活地改变显示内容。 以下是一个简单的动态显示数码管的实现示例: 1. 定义数码管的引脚 ```c #define DIG_PORT P2 // 数码管位选端口 #define DIG_COM 0x00 // 数码管位选端口初始值 #define LED_PORT P0 // 数码管段选端口 ``` 2. 定义数码管显示的数字或字符 ```c unsigned char code ledChar[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f }; ``` 3. 实现数码管动态显示函数 ```c void display(unsigned char i) { unsigned char j, k; for (j = 0; j < 8; j++) { DIG_PORT = DIG_COM | (1 << j); // 选择数码管位(从左到右) for (k = 0; k < 100; k++); // 延时,视情况可调整 LED_PORT = ledChar[i]; // 显示数码管上的数字或字符 } } ``` 4. 调用数码管动态显示函数 ```c int main() { unsigned char i = 0; while (1) { display(i % 10); // 显示数字 i 的个位数 i++; } return 0; } ``` 以上就是一个简单的数码管动态显示的实现示例。需要注意的是,数码管的控制方式和具体实现方法可能因不同的硬件平台和编程语言而有所不同。因此,在具体应用中需要根据实际情况进行适当的调整和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值