第六周 DS18B20模块笔记
-
关闭外设函数编写
:
-
基本流程:先关闭 LED,点亮 LED 是 0 亮,之后打开 P2,P2 先与上 0X7F 再或上 0X80 选中 Y4C 即 LED 通道,之后 P2 与等于 0XEF 关闭通道。
-
关闭继电器和蜂鸣器:与关闭 LED 同理,将相关参数改为 0X00 和 0XA0,选中外设通道后关闭通道。
-
函数声明:将该函数在.h 文件里声明,注意使用英文分号。
-
-
LED 底层代码编写
:
-
引用头文件:引用相关头文件,在.c 文件中先引用自身的.h 文件。
-
函数参数与变量:采用两个入口参数的底层,一个是地址,一个是使能标志位,定义两个局部静态变量。
-
点亮与熄灭操作:使能时,temp 或等于 0X01 左移相应位数点亮 LED;否则与等于取反熄灭 LED。
-
数据刷新处理:比较 time 值和 time old 值,若不相等则将 time 值传给 old 值,且对 time 取反,避免出现电流声。
-
函数声明:将该函数声明到.h 文件中。
-
-
按键底层代码编写
:
-
引用头文件:引用相关头文件和自身的.h 文件。
-
返回值与变量初始化:函数需要返回值,定义变量 time 并初始化为 0,否则按键可能出现问题。
-
按键判断:以 P44(仿真中为 P37)选中第一行,逐行判断按键,如 P33 等于 0 时 time 值等于 4 等,复制粘贴修改选中行和对应数字完成其他行判断。
-
返回值与声明:将 time 值返回,在.h 文件中声明该函数。
-
-
数码管底层代码编写
:
-
引用头文件与声明数组:引用相关头文件和自身的.h 文件,在.h 文件中声明段选和位选两个数组,段选复制官方资料包中的共阳极数码管段选表,位选按 8421 规律编写。
-
函数参数与消隐:函数有断码、位码、是否需要小数点三个入口参数,先进行消隐操作,P2 与上 0XEF 或上 0X1 选中后再 P2 与等于 0XEF 关闭。
-
位选与段选:给位选数组中相应位赋值,选中位选(0XC0),再给段选,若需要小数点,P0 等于数码管段选后与上 0X7F
-
模块程序设计训练
新建文件夹
添加user driver
修改设置
添加底层
编写init.c
void 初始化
关闭所有的外设
然后
然后可以开始写我们的led模块了
同样的新建.c .h文件,引用头文件
seg底层也是一样
大模板最后就是这样的
e <STC15F2K60S2.H>
#include <key.h>
#include <led.h>
#include <seg.h>
#include <init.h>
/*变量声明*/
unsigned char Key_Slow_Down;//按键减速变量
unsigned char Key_Val,Key_Old,Key_Down,Key_Up;//按键扫描变量
unsigned int Seg_Slow_Down;//数码管减速专用变量
unsigned char Seg_Pos;//数码管扫描专用变量
unsigned char Seg_Buf[8] = {1,2,3,4,5,6,7,8};//数码管数据存放数组,默认全部熄灭
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点数组,默认熄灭
unsigned char ucLed[8]={0,0,0,0,0,0,0,0};//led数据存放数组
/* 按键处理函数*/
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down =1;
Key_Val = Key_Read();//读取键码
Key_Down = Key_Val &(Key_Old ^ Key_Val);//下降沿
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//上升沿
Key_Old = Key_Val;//辅助扫描专属变量
}
/* 信息处理函数 */
void Seg_Proc()
{
if(Seg_Slow_Down) return;
Seg_Slow_Down =1;
}
/* 其他显示函数 */
void Led_Proc()
{
}
/*定时器0初始化函数*/
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
}
/* 定时器0中断服务函数*/
void Timer0Server() interrupt 1
{
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;
if(++Seg_Pos == 8 ) Seg_Pos = 0;
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
Led_Disp(Seg_Pos,ucLed[Seg_Pos]);
}
/*main函数 */
void main()
{
Sys_Init();
Timer0_Init();
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
搭配上init,led,key,seg四个底层的.c.h文件
出现的问题
1 为什么编译后仿真的显示有问题?
按道理来说应该显示12345678,因为在显示的数组seg_buf是12345678,但是是可以编译进去的
查找和显示有关的代码,底层
问题就出在底层这个段选的数组里面
写了两个0x92,删去一个
但是12的显示还是有问题的,继续查错
![]()
对数组的前两位分别改成10,2和1,10
发现都显示在了第一位
考虑是点亮的数码管的位置有错误,找到位选的数组
发现第一和第二的位置的位码是一样的,改成 0x01 0x02后,编译成功
强制转换,让ucled等于 tem/ctrol 如果结果大于1, 强制转换后1就会点亮,如果小于1,强制转换之后就是0,就是灭
第89 10 讲笔记
蓝桥杯PCF8591芯片及应用技术总结
一、会议核心主题与目的
本次会议聚焦蓝桥杯竞赛中对PCF8591芯片的考核要求,系统讲解了该芯片的工作原理、I2C通信协议以及AD/DA转换的实现方法。会议旨在帮助参赛者掌握模拟信号采集与数字控制的核心技术,为应对竞赛中相关的硬件编程题目打下坚实基础。
二、PCF8591芯片核心知识详解
2.1 芯片基础特性
PCF8591是一款集成了模数转换器和数模转换器的单芯片解决方案,通过I2C总线进行通信。
-
接口特性:采用双线制串行通信(SDA数据线、SCL时钟线)
-
转换精度:8位分辨率,提供4路模拟输入和1路模拟输出
-
供电电压:典型工作电压为5V,范围2.5V–6V
2.2 硬件地址设计
PCF8591通过硬件地址引脚(A0、A1、A2)进行设备寻址:
-
地址格式:固定部分(1001) + 可编程部分(A2,A1,A0)
-
读写控制:地址字节最后一位为读写位(0-写操作,1-读操作)
-
典型地址:写地址0x90,读地址0x91
2.3 控制字节结构
控制字节用于配置芯片工作模式,具体结构如下:
| 位 | 功能 | 说明 |
|---|---|---|
| D7 | 固定为0 | 必须设置为0 |
| D6 | 模拟输出使能 | 0-允许DA输出,1-禁止输出 |
| D5-D4 | 输入模式选择 | 00-4路单端输入 |
| D3 | 自动增量标志 | 1-启用通道自动递增 |
| D2-D1 | 通道选择 | 00-通道0,01-通道1,10-通道2,11-通道3 |
| D0 | 保留位 | 通常设为0 |
常用配置:
-
光敏电阻(AIN1):控制字0x01
-
滑动变阻器(AIN3):控制字0x03
-
DA输出使能:控制字0x40
三、I2C协议核心原理
3.1 协议基础
I2C是一种同步串行通信协议,由飞利浦公司开发,采用两线制设计:
-
SDA:串行数据线,用于数据传输
-
SCL:串行时钟线,提供同步时钟信号
-
通信特性:支持多主从设备连接,总线空闲时保持高电平
3.2 关键信号时序
I2C协议包含三种基本信号:
-
起始信号:SCL为高电平时,SDA由高变低
-
停止信号:SCL为高电平时,SDA由低变高
-
应答信号:每传输完一个字节,接收方产生低电平应答
四、AD/DA转换原理与实现
4.1 转换基本原理
-
AD转换:将连续模拟信号转换为离散数字量(0-255)
-
DA转换:将数字量转换为模拟电压输出
-
分辨率计算:8位AD的分辨率 = 参考电压/255
4.2 代码实现框架
以下是PCF8591的基本操作函数框架:
AD转换函数
unsigned char AD_Read(unsigned char channel)
{
I2C_Start();
I2C_SendByte(0x90); // 发送写地址
I2C_WaitAck();
I2C_SendByte(channel); // 发送控制字选择通道
I2C_WaitAck();
I2C_Stop();
I2C_Start();
I2C_SendByte(0x91); // 发送读地址
I2C_WaitAck();
temp = I2C_ReceiveByte(); // 读取转换结果
I2C_SendAck(1);
I2C_Stop();
return temp;
}
DA转换函数
void DA_Write(unsigned char data)
{
I2C_Start();
I2C_SendByte(0x90);
I2C_WaitAck();
I2C_SendByte(0x40); // DA输出使能
I2C_WaitAck();
I2C_SendByte(data); // 发送数字量
I2C_WaitAck();
I2C_Stop();
}
五、蓝桥杯竞赛实操要点
5.1 电压换算公式
竞赛中常用的电压换算关系:
-
数字量转电压:电压值 = (数字量 × 参考电压) / 255
-
电压转数字量:数字量 = (目标电压 × 255) / 参考电压
5.2 常见问题与解决方案
-
通信失败:检查I2C总线初始化、地址设置是否正确
-
数据错误:验证控制字配置、时序延时是否合适
-
通道选择错误:确认AIN1和AIN3对应的控制字
六、总结与备赛建议
本次会议系统梳理了PCF8591在蓝桥杯竞赛中的核心应用技术。建议参赛者:
-
深入理解I2C协议的工作原理和时序要求
-
熟练掌握AD/DA转换的编程实现方法
-
注重实践操作,通过实际调试加深理解
-
参考往届真题进行针对性训练
实际编写问题
行线引脚不同!实物用P44/P42,仿真用P37/P36!
Voltage_Read = AD_Read(0x43) / 51.0;//读取实时电压值
Voltage_Output = Voltage_Mode ? Voltage_Read : 2;
这串代码实现了一个电压读取与可配置输出的逻辑,核心是使用ADC读取电压,并根据模式选择决定最终的输出电压值。下面这个表格能帮你快速理解两行代码各自的分工
| 代码行 | 核心功能 | 逻辑分解 | 关键点 |
|---|---|---|---|
Voltage_Read = AD_Read(0x43) / 51.0; |
电压采样与转换 | 1. AD_Read(0x43):从PCF8591芯片的特定通道(0x43对应旋转电位器)读取ADC原始值(范围0-255)。 2. / 51.0:将原始值转换为实际电压值。因为255对应5V,所以每个数字量代表的电压为 5V / 255 ≈ 0.0196V。换算一下,255 / 5 = 51,因此除以51即可得到电压值(Voltage_Read = 原始值 / 51)。使用51.0是为了避免整数除法丢失小数精度。 |
实现模拟量到数字量的转换和标度变换。 |
Voltage_Output = Voltage_Mode ? Voltage_Read : 2; |
输出模式选择 | 1. Voltage_Mode:这是一个标志位(Flag),它的值由按键操作设置(如代码中的case 5)。 2. ? :(三目运算符):这是一个条件判断。 - 如果 Voltage_Mode为 1(真),则 Voltage_Output等于 Voltage_Read(即输出跟随输入)。 - 如果 Voltage_Mode为 0(假),则 Voltage_Output等于固定值 2(即固定输出2V) |
实现输出模式灵活切换 |
这串代码实现了一个电压读取与可配置输出的逻辑,核心是使用ADC读取电压,并根据模式选择决定最终的输出电压值。下面这个表格能帮你快速理解两行代码各自的分工:
| 代码行 | 核心功能 | 逻辑分解 | 关键点 |
|---|---|---|---|
Voltage_Read = AD_Read(0x43) / 51.0; |
电压采样与转换 | 1. AD_Read(0x43):从PCF8591芯片的特定通道(0x43对应旋转电位器)读取ADC原始值(范围0-255)。 2. / 51.0:将原始值转换为实际电压值。因为255对应5V,所以每个数字量代表的电压为 5V / 255 ≈ 0.0196V。换算一下,255 / 5 = 51,因此除以51即可得到电压值(Voltage_Read = 原始值 / 51)。使用51.0是为了避免整数除法丢失小数精度。 |
实现模拟量到数字量的转换和标度变换。 |
Voltage_Output = Voltage_Mode ? Voltage_Read : 2; |
输出模式选择 | 1. Voltage_Mode:这是一个标志位(Flag),它的值由按键操作设置(如代码中的case 5)。 2. ? :(三目运算符):这是一个条件判断。 - 如果 Voltage_Mode为 1(真),则 Voltage_Output等于 Voltage_Read(即输出跟随输入)。 - 如果 Voltage_Mode为 0(假),则 Voltage_Output等于固定值 2(即固定输出2V)。 |
实现输出模式灵活切换。 |
逻辑全景与应用场景
这个设计非常实用,常见于以下场景:
-
设备调试与校准:固定输出一个已知电压(如2V),可以用来检验其他电路或测量工具是否正常工作。
-
两种工作状态切换:例如,一个设备可以设置为“手动模式”(固定输出)和“自动模式”(跟随传感器输入),通过按键轻松切换。
总而言之,这段代码是一个简洁但功能完整的数据采集与输出控制模块,它负责将物理世界的模拟信号(电压)转换为单片机可以处理的数字信息,再根据用户指令,灵活地决定输出信号的特征。
需要除以51的原因:
这个操作是模数转换(ADC)后的标度变换,目的是将ADC读取的原始数字值转换为实际的电压值。它与温度和华氏度没有任何关系。
其核心逻辑如下:
-
1.硬件前提:ADC的参考电压是 5V,且ADC是 8位精度(最大数值为255)。
-
2.计算原理:
-
•每个数字量(1)代表的电压值 = 5V / 255 ≈ 0.0196V。
-
•因此,
实际电压值 = 原始数值 × (5 / 255)。 -
•将分数简化:
5 / 255 = 1 / 51。所以,实际电压值 = 原始数值 / 51。
-
简单来说,除以51是一个将数值范围从 0-255线性映射到电压范围 0V-5V的数学转换
总结
-
AD 转换核心:读取 8 位数字量(0~255),通过
/51.0换算为 0~5V 的实际电压值; -
DA 转换核心:将目标电压通过
×51.0还原为 8 位数字量,写入芯片后输出对应模拟电压; -
模式关联:DA 输出电压由
Voltage_Output_Mode控制,可固定 2V 或跟随 AD 读取的电压值
在实时变化的电压场景下,AD/DA 转换的必要性,以及为什么先除以 51、又再乘以 51—— 本质上这是模拟量和数字量双向转换的必然过程,两个操作分别对应 “读模拟电压” 和 “输出模拟电压”,并不是无意义的重复
AD/DA 转换的必要性:单片机只能处理数字信号,而实时电压是模拟信号,必须通过 AD 把模拟电压转数字量(让单片机 “读得懂”),通过 DA 把数字量转模拟电压(让外部 “用得到”);
除以 51 的作用:把 AD 读取的数字量还原成实际电压值(数字→模拟,供单片机识别);
乘以 51 的作用:把目标电压值转换成 DA 能识别的数字量(模拟→数字,供 DA 输出)。
AD + 除以 51:让单片机看懂了外界的电压(数字量 → 实际电压值)。
乘以 51 + DA:让单片机表达出想要的电压(实际电压值 → 数字量,输出出去)






