njslmcc 发表于 2025-9-26 12:18:33

第十六集 DS18B20测温 学习笔记

一.      DS18B20简介
DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样。主要根据应用场合的不同而改变其外观。封装后的DS18B20可用于电缆沟测温,高炉水循环测温,锅炉测温,机房测温,农业大棚测温,洁净室测温,弹药库测温等各种非极限温度场合。耐磨耐碰,体积小,使用方便,封装形式多样,适用于各种狭小空间设备数字测温和控制领域
①、独特的单线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实现微处理器与DS18B20的双向通讯。
②、测温范围 -55℃~+125℃,固有测温误差1℃。
③、支持多点组网功能,多个DS18B20可以并联在唯一的三线上,最多只能并联8个,实现多点测温,如果数量过多,会使供电电源电压过低,从而造成信号传输的不稳定。
④、工作电源: 3.0~5.5V/DC (可以数据线寄生电源)
⑤、在使用中不需要任何外围元件
⑥、测量结果以9~12位数字量方式串行传送

二.硬件连接
用1k的上拉电阻

三.功能描述
正数补码:本身不变
负数补码:按位取反再+1,加个-号

四.代码编写

1)复位和存在
复位(输出0保持480us,输出1保持60us,读取当前电平,延时420us)

2)写0,写1
写0(输出0保持60us+,输出1保持1us+)
写1(输出0保持1us+,输出1保持60us+)

3)读0,读1
读0/1(输出0保持1us+,输出1保持1us+,读取当前电平,延时60us)

4.1 底层驱动
复位(输出0保持480us,输出1保持60us,读取当前电平,延时420us)
写0(输出0保持60us+,输出1保持1us+)
写1(输出0保持1us+,输出1保持60us+)
读0/1(输出0保持1us+,输出1保持1us+,读取当前电平,延时60us)

4.2 接口函数
写1字节(先输出低位,在输出高位)
读1字节(先读到的是低位,后读到的是高位

4.3 用户功能函数
温度读取换算函数
(复位-CCH-44H-等待-复位-CCH-BEH-读取2字节温度数据-换算)

在main.c中写入
#include "18b20.h"

SEG_Show_U32(Temp_18b20);                //在USB初始化后

生成新文件18B20.c,并写入

#include "18b20.h"

u8MinusFlag = 0;                //如果等于0 ,正数;等于1,负数
u32 Temp_18b20;                //最终的温度,0.0625

void Delay480us(void)      //@24.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 2878UL;
      while (i) i--;
}

void Delay60us(void)      //@24.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 358UL;
      while (i) i--;
}

void Delay1us(void)      //@24.000MHz
{
      unsigned long edata i;

      _nop_();
      _nop_();
      _nop_();
      i = 4UL;
      while (i) i--;
}

//复位(输出0保持480us,输出1保持60us,读取当前电平,延时420us)
void DS18B20_Reset(void)
{
      u8 flag = 1;
      
      while( flag )                //只要括号里的变量大于0,就会一直执行
      {
                DQ = 0;
                Delay480us();
                DQ = 1;
                Delay60us();
                flag = DQ;                        //设备存在,会拉低总线
                Delay480us();
      }
}
      
//写逻辑0(输出0保持60us+,输出1保持1us+)
void DS18B20_Write_0(void)
{
      DQ = 0;
      Delay60us();
      DQ = 1;
      Delay1us();
      Delay1us();
}

//写逻辑1(输出0保持1us+,输出1保持60us+)
void DS18B20_Write_1(void)
{
      DQ = 0;
      Delay1us();
      Delay1us();
      DQ = 1;
      Delay60us();
}

//读逻辑0/1(输出0保持1us+,输出1保持1us+,读取当前电平,延时60us)
bit DS18B20_Read(void)
{
      bit state = 0;
      
      DQ = 0;
      Delay1us();
      Delay1us();      
      DQ = 1;
      Delay1us();
      Delay1us();      
      state = DQ;                //暂时保存这个DQ的数值
      Delay60us();
      
      return state;
}

//写1字节(先输出低位,在输出高位)
void DS18B20_WriteByte( u8 dat )
{
      u8 i;
      for(i=0;i<8;i++)
      {
                if( dat & 0x01 )      //最低位是1.发逻辑1电平
                {
                        DS18B20_Write_1();
                }
                else                              //否则.发逻辑0电平
                {
                        DS18B20_Write_0();
                }
                dat >>= 1;                        //dat = (dat>>1);
      }
}

//读1字节(先读到的是低位,后读到的是高位)
u8 DS18B20_ReadByte(void )
{
      u8 i;
      u8 dat=0;                              //数据暂存,
      
      for(i=0;i<8;i++)                //循环读取八次
      {
                dat >>= 1;
                if( DS18B20_Read() )      //如果读回来的是逻辑1   //0000 0000->1000 0000 -> 0100 0000
                {
                        dat |= 0x80;
                }
                else
                {
                }
      }
      return dat;
}

//复位-CCH-44H-等待-复位-CCH-BEH-读取2字节温度数据-换算)
void DS18B20_ReadTemp(void)
{
      u8 TempH = 0;
      u8 TempL = 0;
      u16 Temp = 0;
//---------------------发送检测命令---------------------      
      DS18B20_Reset();                        //1.发送复位命令
      DS18B20_WriteByte(0xcc);      //2.跳过ROM命令
      DS18B20_WriteByte(0x44);      //3.开始转化命令
      while( !DQ );                              //4.等待这个引脚变成高电平
      
//---------------------发送读取命令---------------------      
      DS18B20_Reset();                        //1.发送复位命令
      DS18B20_WriteByte(0xcc);      //2.跳过ROM命令
      DS18B20_WriteByte(0xBE);      //3.开始转化命令      
      TempL = DS18B20_ReadByte(); //读取低字节温度
      TempH = DS18B20_ReadByte(); //读取高字节温度      
      
      if( TempH & 0x80 )                        //如果最高位是1,这个就是负数
      {
                MinusFlag = 1;
                Temp = (((u16)TempH << 8) | ((u16)TempL << 0));
                Temp = (~Temp) +1;
                Temp_18b20 = (u32)Temp*625;
      }
      else
      {
                MinusFlag = 0;
                Temp = (((u16)TempH << 8) | ((u16)TempL << 0));
                Temp_18b20 = (u32)Temp*625;
      }
}

在18B20.h中写入

#ifndef __18B20_H
#define __18B20_H

#include "config.h"                        //调用头文件

#define DQP33

extern u8MinusFlag ;                        //如果等于0 ,正数;等于1,负数
extern u32 Temp_18b20;                        //最终的温度,放大了10000倍

void DS18B20_ReadTemp(void);

#endif

在task.c中写入两个任务
#include "18b20.h"

      {0, 800, 800,    DS18B20_ReadTemp}, //温度读取
      {0,1,   1,   SEG_Task},             //显示数码管

在io.c中写入
void SEG_Show_U32(u32 num)
{
      u8 i;
      for(i=0;i<8;i++)
      {
                passward = num%10;
                num /= 10;
      }
}

在io.h中写入
void SEG_Show_U32(u32 num);

至此,18B20的读取与显示就完成了


课后小练
测温计:
1.按下开关机按钮开机
2.开机后显示三个横杠和单位 “- - - C”/“- - - F”
3.在2秒内显示出当前温度
4.有一个按键可以切换摄氏和华氏
5.按下开关机按钮后关机,或者无操作30秒自动关机

njslmcc 发表于 2025-9-27 21:59:52

第十七集 串口的简单应用 学习笔记

一.串口通信的基础知识

通信指设备之间通过一定的协议进行的信息交换
串口1/2和3/4的主要区别就在工作模式和工作方式上,这节课主要讲异步串口。
每次发送一位数据的称为串行通信,多位一起传输的称为并行通信。
今天要讲的串口通信是串行通信的其中的一种!

①异步串口通信不需要统一的时钟信号,每个数据字节的传输都是独立的。所以需要发送和接收设备的时钟比较接近
②每个数据字节通常会被起始位和停止位包围,起始位用于通知接收端数据传输的开始,停止位用于标志数据字节的结束。
③由于没有统一的时钟信号,异步串口的传输速率相对较低,且需要额外的起始位和停止位,因此效率较低

①同步串口通信依赖于一个统一的时钟信号来同步发送端和接收端的数据传输。
②发送端和接收端共享同一个时钟信号,或者通过某种方式(如曼彻斯特编码)在数据流中嵌入时钟信息。
③由于有时钟信号的同步,数据传输的速率可以较高,且不需要起始位和停止位来界定每个数据字节,因此效率较高

二.串口通信的寄存器配置
全双工,波特率,数据位,校验位,停止位
传输距离:RS232,15米 / RS422,1200米 /RS485(半双工)1200米
传输速度:20k/s         10M/s             10M/s
串口转以太网,WIFI,蓝牙,zigbee

三.串口通信的硬件连接

四.使用ISP软件自动生成串口初始化代码

在main.c中
#include “usart.h”

Uart2_Init();                //在EA = 1;之前,串口2初始化


生成usart.c,在其中输入(ISP生成的)
#include “usart.h”
#include “io.h”

u8 Rec_Dat;                        //接收缓冲区
u8 Rec_Num = 0;                //接收计数
bit B_TX2_Busy = 0;

void Uart2_Isr(void) interrupt 8
{
      if (S2CON & 0x02)      //检测串口2发送中断
      {
                S2CON &= ~0x02;      //清除串口2发送中断请求位
                B_TX2_Busy = 0;
      }

      if (S2CON & 0x01)      //检测串口2接收中断
      {
                S2CON &= ~0x01;      //清除串口2接收中断请求位
                Rec_Dat = S2BUF;
      }
}

void Uart2_Init(void)      //9600bps@24.000MHz
{
      P_SW2 |= 0x01;                     //功能脚切换,UART2/USART2: RxD2(P4.2), TxD2(P4.3)

      S2CON = 0x50;                        //8位数据,可变波特率
      AUXR |= 0x04;                        //定时器时钟1T模式
      T2L = 0x8F;                        //设置定时初始值
      T2H = 0xFD;                        //设置定时初始值
      AUXR |= 0x10;                        //定时器2开始计时
      IE2 |= 0x01;                        //使能串口2中断
}

void Uart2_SendStr( u8 *puts )                //串口数据发送函数
{
    for (; *puts != 0;puts++)   //遇到停止符0结束
    {
      S2BUF = *puts;
      B_TX2_Busy = 1;
      while(B_TX2_Busy);
    }      
}

void Usart2_RunTask(void)
{
      if( Rec_Num >= 6 )                //是否接收到了6位以上的数据
      {
                if(( Rec_Dat == '\n' ) && ( Rec_Dat == '\r' ) )      //末尾判断
                {      //1.发送OPEN\r\n打开数码管,数码管显示“- - - -” 11 22 OPEN\r\n
                        if( ( Rec_Dat == 'O' ) &&
                            ( Rec_Dat == 'P' ) &&
                            ( Rec_Dat == 'E' ) &&
                            ( Rec_Dat == 'N' ) )
                        {
                              passward = 16;
                              passward = 16;
                              passward = 16;
                              passward = 16;
                              Uart2_SendStr( "打开成功!\r\n" );
                        }

                        //2.发送CLOSE\r\n打开数码管,数码管全部熄灭
                        else if( ( Rec_Dat == 'C' ) &&
                               ( Rec_Dat == 'L' ) &&
                               ( Rec_Dat == 'O' ) &&
                               ( Rec_Dat == 'S' ) &&
                               ( Rec_Dat == 'E' ) )
                        {
                              passward = 17;
                              passward = 17;
                              passward = 17;
                              passward = 17;
                              Uart2_SendStr( "关闭成功!\r\n" );
                        }

                        //3.再打开的情况下,串口发送DAT+123\r\n,数码管显示数值“123”
                        else if( ( Rec_Dat == 'D' ) &&
                               ( Rec_Dat == 'A' ) &&
                               ( Rec_Dat == 'T' ) &&
                               ( Rec_Dat == '+' ) &&
                               ( Rec_Dat == '1' ) &&
                               ( Rec_Dat == '2' ) &&
                               ( Rec_Dat == '3' ) )
                        {
                              passward = 17;
                              passward = 1;
                              passward = 2;
                              passward = 3;
                        }
                        Rec_Num = 0;
                }
      }
}

生成usart.h,在其中输入
#ifndef __USART_H
#define __USART_H

#include "config.h"                        //调用头文件
      
void Uart2_Init(void);      //9600bps@24.000MHz
void Usart2_RunTask(void);

#endif

在Task.c中增加1个任务
#include “usart.h”

      {0, 1, 1,    Usart2_RunTask},         //串口2运行任务

至此,任务1,任务2,任务3,课后小练都已完成

课后小练
智能数码管显示屏
1.发送OPEN\r\n打开数码管,数码管显示“- - - -”
2.发送CLOSE\r\n打开数码管,数码管全部熄灭
3.再打开的情况下,串口发送DAT+123\r\n,数码管显示数值“123”
(前面已完成)

njslmcc 发表于 2025-9-28 07:32:43

第十八集 串口高级应用 学习笔记

一.串口通信的奇偶校验

最常用的通信格式为8-N-1(1包为10位数据)
8:代表数据位为8位
N:代表None,无校验 (ODD 奇 ;EVEN 偶)
1:代表1位停止位

偶校验(even parity):让传输的数据(包含校验位)中1的个数为偶数。真0,假1。
即:如果传输字节中1的个数是偶数,则校验位为“0”,奇数相反。

奇校验(odd parity):让传输的数据(包含校验位)中1的个数为奇数。真0,假1。
即:如果传输字节中1的个数是奇数,则校验位为“0”,偶数相反。

为什么需要加校验呢?
传输过程是单向的,可能存在出错的可能!

奇偶校验的优缺点?
优点1:可以减少数据出错的可能
优点2:使用简单便捷
缺点1:奇偶校验的检错率只有50%,因为只有奇数个数据位发生变化能检测到,如果偶数个数据位发生变化则无能为力了
缺点2:奇偶校验每传输一个字节都需要加一位校验位,对传输效率影响很大

程序如何实现?使用9位数据位(8位数据+1位校验)
方案1:可以利用二进制数相加的特点:
0+0=0、1+0=1、1+1=0
可以看出,如果我们将一个字节的所有位相加
•      有奇数个“1”的字节的和为1
•      有偶数个“1”的字节的和为0

方案2:可以利用ACC(累加器)和P(奇偶校验位)
•      将需要运算的数值存入ACC寄存器
•      打印读取P位用来表示结果中“1”的个数是奇数还是偶数。结果等于1为奇数!
•                ACC = dat;
•                if(P)      //奇数个1
•                else      //偶数个1

任务1:使用奇校验,8位数据位,1位停止位的数据,发送OPEN\r\n打开数码管,数码管显示“- - - -“

在main.c中
#include “usart.h”

Uart2_Init();                //在EA = 1;之前,串口2初始化

生成usart.c,在其中输入(ISP生成的)
#include “usart.h”
#include “io.h”

u8 Rec_Dat;                  //接收缓冲区
u8 Rec_Num = 0;                //接收计数
bit B_TX2_Busy = 0;

void Uart2_Isr(void) interrupt 8
{
      u8 dat;

      if (S2CON & 0x02)               //检测串口2发送中断
      {
                S2CON &= ~0x02;       //清除串口2发送中断请求位
                B_TX2_Busy = 0;
      }

      if (S2CON & 0x01)                //检测串口2接收中断
      {
                S2CON &= ~0x01;       //清除串口2接收中断请求位
                dat = S2BUF;
                ACC = dat;
                if(S2RB8) = (!P) )         //奇校验
                {
                        Rec_Dat = dat;
                }               
      }
}

void Uart2_Init(void)                     //9600bps@24.000MHz
{
      P_SW2 |= 0x01;                   //功能脚切换,UART2/USART2: RxD2(P4.2), TxD2(P4.3)

      S2CON = 0xD0;                  //9位数据,可变波特率
      AUXR |= 0x04;                     //定时器时钟1T模式
      T2L = 0x8F;                         //设置定时初始值
      T2H = 0xFD;                        //设置定时初始值
      AUXR |= 0x10;                     //定时器2开始计时
      IE2 |= 0x01;                        //使能串口2中断
}

void Uart2_SendStr( u8 *puts )                //串口数据发送函数
{
      u8 dat;
            for (; *puts != 0;puts++)          //遇到停止符0结束
            {
                dat = *puts;
                ACC = dat;
                if(P)
                        S2RB8 = 0;
                else
                        S2RB8 = 1;

                S2BUF = *puts;
                B_TX2_Busy = 1;
                while(B_TX2_Busy);
      }      
}

void Usart2_RunTask(void)
{
      if( Rec_Num >= 6 )                //是否接收到了6位以上的数据
      {
                if(( Rec_Dat == '\n' ) && ( Rec_Dat == '\r' ) )      //末尾判断
                {
                        //1.发送OPEN\r\n打开数码管,数码管显示“- - - -” 11 22 OPEN\r\n
                        if( ( Rec_Dat == 'O' ) &&
                            ( Rec_Dat == 'P' ) &&
                            ( Rec_Dat == 'E' ) &&
                            ( Rec_Dat == 'N' ) )
                        {
                              passward = 16;
                              passward = 16;
                              passward = 16;
                              passward = 16;
                              Uart2_SendStr( "打开成功!\r\n" );
                        }

                        //2.发送CLOSE\r\n打开数码管,数码管全部熄灭
                        else if( ( Rec_Dat == 'C' ) &&
                               ( Rec_Dat == 'L' ) &&
                               ( Rec_Dat == 'O' ) &&
                               ( Rec_Dat == 'S' ) &&
                               ( Rec_Dat == 'E' ) )
                        {
                              passward = 17;
                              passward = 17;
                              passward = 17;
                              passward = 17;
                              Uart2_SendStr( "关闭成功!\r\n" );
                        }

                        //3.再打开的情况下,串口发送DAT+123\r\n,数码管显示数值“123”
                        else if( ( Rec_Dat == 'D' ) &&
                               ( Rec_Dat == 'A' ) &&
                               ( Rec_Dat == 'T' ) &&
                               ( Rec_Dat == '+' ) &&
                               ( Rec_Dat == '1' ) &&
                               ( Rec_Dat == '2' ) &&
                               ( Rec_Dat == '3' ) )
                        {
                              passward = 17;
                              passward = 1;
                              passward = 2;
                              passward = 3;
                        }
                        Rec_Num = 0;
                }
      }
}

生成usart.h,在其中输入
#ifndef __USART_H
#define __USART_H

#include "config.h"                     //调用头文件
      
void Uart2_Init(void);                  //9600bps@24.000MHz
void Usart2_RunTask(void);

#endif

在Task.c中增加1个任务
#include “usart.h”

      {0, 1, 1,    Usart2_RunTask},         //串口2运行任务

编译,下载
先点无校验,无响应,
加上奇校验,OPEN和CLOSE反应都正确
课后可以试试偶校验。

二.串口通信的超时中断

当我们在做串口接收的程序的时候,往往需要当接收到一串数据的时候需要及时的响应。但是响应的第一步就是我们得先判断这一包数据是否已经接收完成,必须要接收完成了才能进行下一步动作;
    案例一:常见的AT指令集,带固定帧尾的数据。假设我们用单片机模拟一个ESP的模块,是不是当串口发来一个"AT\r\n"的时候需要回应一个"OK\r\n";这个可以串口接收函数里判断结尾是不是"\r\n",监测到这个才是结尾。
    案例二:串口接收定长输出,每一包的数据都是固定的字节,接受到这个长度的数据就是这一包结束了。
    案例三:每一帧的数据里包含数据长度,比如每一包数据的第一个字节是长度,长度多长后面就跟几个字节的数据。
    上面的几种常见案例虽然都能实现数据的完整接收,但是在实际使用的时候数据包里有没有长度,有没有固定帧尾都不是我们做从机的时候能决定的,那我们怎么样才能用最好的办法来判断一个数据包也没有接收完成呢!答案是有的————串口超时中断。

UR2TOCR这个寄存器其实在我们接收的时候再打开即可,ENTO不用说坑定需要使能,ENTOI是中断,可以及时响应。SCALE的话这里为了时钟相对准,我们选择系统时钟即可。这里需要注意如果选择1us时钟的话务必要设置IAP_TPS寄存器
这里三个寄存器共同组成了一个计时器,最终的计时时间 = 1/系统时钟(单位mhz)*计时器数值,假设计时器数值为44000(0XABE0),那么UR1TOTL = 0XE0;UR1TOTH = 0XAB;UR1TOTE = 0X00;需要注意的是这里一定要从低位开始写,且这三个寄存器一定都要写,哪怕这个寄存器的数值是0也必须要写一次。需要注意的是这个计时器的数值不能全为0!
这个寄存器就很简单了,只有一个位,触发的时候这个位会置1,我们手动清0即可。需要注意的是这个和串口发送和接收中断共用同一个中断向量号!串口1的中断号是4,那么串口1的超时中断号也是4.

串口通信的超时中断

没有固定帧尾的时候,虽然数据可能是定长的,但是如果数据是错误的呢?
在modbus rtu协议中,假设帧1是主机发送的,帧2是从机的回复命令,按照标准的modbus rtu协议来说,中间应该要间隔3.5个字符周期
有校验位:假设 1个字符=1(起始位)+8(数据位)+1(奇偶校验位)+1(停止位)= 11位
3.5个字符=3.5*11=38.5位,如果波特率=9600bps,则3.5个字符间隔时间为38.5/9.6=4.0104167毫秒
无校验位:假设 1个字符=1(起始位)+8(数据位)+0(无校验位)+1(停止位)=10位
3.5个字符=3.5*10=35位
9600波特率下,8N1的空闲时间就是 1/9600*35 ≈3.646ms;带入公式3646= 1/22.1184*计时器数值,得出计时器数值 = 87504 = 0x013b04;

1.初始化配置
    UR2TOCR = 0x00;                                 
    UR2TOTL = 0x04;                                 
    UR2TOTH = 0x3b;                              
    UR2TOTE = 0x01;                              
            
2.中断接收+
    UR2TOCR = 0xe0;                            //开启超时中断,使用系统时钟

3.超时中断处理
    if(UR2TOSR & 0x01)                         //串口超时中断
    {
      B_RX2_OK = 1;                           //接收完成标志位
      UR2TOSR = 0x00;                     //清除超时标志位
      UR2TOCR = 0x00;                     //关闭超时中断
    }

具体的软件实现如下:

在main.c中
#include “usart.h”

Uart2_Init();                              //在EA = 1;之前,串口2初始化

while(1)
{
                Usart2_RunTask();      // 在USB之前
…………
}

生成usart.c,在其中输入(ISP生成的)
#include “usart.h”
#include “io.h”

u8 Rec_Dat;                         //接收缓冲区
u8 Rec_Num = 0;                     //接收计数
bit B_TX2_Busy = 0;
u8 B_RX2_OK = 0;                     //串口接收完成标志位

void Uart2_Isr(void) interrupt 8
{
      u8 dat;

      if (S2CON & 0x02)             //检测串口2发送中断
      {
                S2CON &= ~0x02;   //清除串口2发送中断请求位
                B_TX2_Busy = 0;
      }

      if (S2CON & 0x01)             //检测串口2接收中断
      {
                S2CON &= ~0x01;   //清除串口2接收中断请求位
                UR2TOCR = 0xe0;   //开启超时中断,使用系统时钟

                Rec_Dat = S2BUF;               
      }
      
      if(UR2TOSR & 0x01)          //串口超时中断
      {
                B_RX2_OK = 1;      //接收完成标志位
                UR2TOSR = 0x00;    //清除超时标志位
                UR2TOCR = 0x00;    //关闭超时中断
      }
}

void Uart2_Init(void)               //9600bps@24.000MHz
{
      P_SW2 |= 0x01;               //功能脚切换,UART2/USART2: RxD2(P4.2), TxD2(P4.3)

      S2CON = 0xD0;                //9位数据,可变波特率
      AUXR |= 0x04;               //定时器时钟1T 模式
      T2L = 0x8F;                     //设置定时初始值
      T2H = 0xFD;                  //设置定时初始值
      AUXR |= 0x10;               //定时器2开始计时
      IE2 |= 0x01;                  //使能串口2中断

      UR2TOCR = 0x00;            //先关闭超时中断               
      UR2TOTL = 0x04;            //9600,8N1下的超时时间               
      UR2TOTH = 0x3b;                              
      UR2TOTE = 0x01;
      Rec_Num = 0;
      B_TX2_Busy = 0;
      B_RX2_OK = 0;
}

void Uart2_SendStr( u8 *puts )                //串口数据发送函数
{
      u8 dat;

            for (; *puts != 0;puts++)         //遇到停止符0结束
            {
                dat = *puts;
                ACC = dat;
                if(P)
                        S2RB8 = 0;
                else
                        S2RB8 = 1;

                S2BUF = *puts;
                B_TX2_Busy = 1;
                while(B_TX2_Busy);
      }      
}

void Usart2_RunTask(void)
{
      if (B_RX2_OK==1)
      {
                B_RX2_OK = 0;
                Uart2_SendStr( "接收成功!\r\n" );
      }
}

生成usart.h,在其中输入
#ifndef __USART_H
#define __USART_H

#include "config.h"                        //调用头文件
      
void Uart2_Init(void);                  //9600bps@24.000MHz
void Usart2_RunTask(void);

#endif

在Task.c中增加1个任务
#include “usart.h”

      {0, 1, 1,    Usart2_RunTask},         //串口2运行任务

编译,下载,运行OK

课后小练
智能数码管显示屏(MODBUS RTU版本,使用9600,8-N-1通信)
1.发送01 06 00 00 00 01 48 0A,数码管显示数值1,   返回01 06 00 00 00 01 48 0A
2.发送01 06 00 00 03 E8 89 74,数码管显示数值1000,返回01 06 00 00 03 E8 89 74
3.发送01 03 00 03 00 01 74 0A,即可读取当前显示的数值,
返回数值为1000时,返回:01 03 02 03 E8 B8 FA
返回数值为1   时,返回:01 03 02 00 01 79 84

njslmcc 发表于 2025-9-29 14:51:45

第十九集 ADC 学习笔记

一.数转换器(ADC)是什么

模数转换器即A/D转换器,或简称ADC(Analog-to-digital converter),通常是指一个将模拟信号转变为数字信号的电子元件。
如右图所示,采集一个模拟电压得到一个量化的数值的过程,就叫做ADC。
模拟信号->电压
数字信号->电压的具体数值

当然,我们的单片机内也带有ADC的功能,也可以轻松的实现如右图所示的电压检测的功能!具体是怎么实现的呢?我们一起看一段视频。
(视频来源于网络,暂未联系上原作者,如有侵权请联系我删除)

ADC的必要因素:
1.基准(总长度)
2.分度(1/2,1/4的分度)

二.Ai8051U单片机ADC使用原理

三.编写最简单的ADC采集代码

注意:ADC引脚需要设置为高阻输入
任务1:编写ADC读取函数,
并在数码管显示当前ADC数值

新建两个文件adc.c,adc.h,保存到user下

在main.c中输入
#include ”adc.h”
      Sys_init();                        //系统初始化
      usb_init();                        //USB CDC接口配置

      IE2 |= 0x80;                  //使能USB中断
      Timer0_Init();                  //定时器初始化
      Init_595();
      
      Timer1_Init();
      ADC_Init();                     //在EA= 1之前

在 while中增加
Task_Pro_Handle_Callback();                  //执行功能函数
SEG_Show_U32(ADC_Read(0));                //P1.0,且设为高阻状态

在adc.c中,输入
(由ISP生成)
#include "adc.h"

void AdcSetRate (void)                        //50KSPS@24.000MHz
{
      ADCCFG &= ~0x0f;
      ADCCFG |= 0x04;                      //SPEED(4)
      ADCTIM = 0xbf;                        //CSSETUP(1), CSHOLD(1), SMPDUTY(31)
}

void ADC_Init (void)
{
      //1.初始化IO为高阻输入
      P1M0 &= ~0x01;
      P1M1 |= 0x01;

      //2.初始化ADC速度
      AdcSetRate();
      
      //3.对齐模式,右对齐
      ADCCFG |= 0x20;
      
      //4.打开ADC电源
      ADC_POWER = 1;
}

u16 ADC_Read(u8 no)
{//参考手册
      u16 adcval = 0;

      ADC_CONTR &= 0Xf0;               //清空低四位
      ADC_CONTR |= no;
      
      ADC_START = 1;                     //启动ADC转化
      _nop_();
      _nop_();
      while( !ADC_FLAG );                  //等待采集完成
      ADC_FLAG = 0;                         //手动清空
      
      adcval = (((u16)ADC_RES) << 8) + (ADC_RESL);      //获取到最终的ADC数值
      
      return adcval;
}

在adc.h中输入
#ifndef __ADC_H
#define __ADC_H

#include "config.h"                        //调用头文件

void ADC_Init(void);
u16 ADC_Read(u8 no);
u8 ADC_KEY_READ( u16 adc );

#endif

在task.c中增加一个任务

(0,1,1,SEG-Task());

编译下载,运行正常,至此,任务1完成

任务2:编写ADC按键读取函数,
并在数码管上显示当前按键号

在main.c,while循环中
SEG_Show_U32(ADC_KEY_READ(ADC_Read(0)));

在uart.c中增加
#define ADC_OFFSET 64
u8ADC_KEY_READ ( u16 adc)                        // 分别对应1-16个键值
{
      //1.判断当前有没有键按下
      if (adc < ( 254 - ADC_OFFSET )
      {
                return 0;
      }
      else
      {
                for( i = 0; i < 16; i++)
                {
                        if ( ( adc>= ( i * 254 -ADC_OFFSET) && adc>= ( i * 254 +ADC_OFFSET))
                        {
                              return i;
                        }
                }
      }      
      return 0;


在adc.h添加一个申明
u8ADC_KEY_READ ( u16 adc); 
编译正确,下载,运行OK
至此,任务2就完成了

课后小练:
简易电压表:
1.用四位数码管显示当前P10引脚的电压

njslmcc 发表于 2025-10-2 07:18:34

第二十集 ADC_NTC测温,学习笔记

一.ADC的用途分析
虽然ADC只能测量电压,但是通过配合外部的传感器,就可以实现多种的信号检测!
NTC(Negative Temperature Coefficient)是指随温度上升电阻呈指数关系减小、具有负温度系数的热敏电阻现象和材料。该材料是利用锰、铜、硅、钴、铁、镍、锌等两种或两种以上的金属氧化物进行充分混合、成型、烧结等工艺而成的半导体陶瓷,可制成具有负温度系数(NTC)的热敏电阻。其电阻率和材料常数随材料成分比例、烧结气氛、烧结温度和结构状态不同而变化。现在还出现了以碳化硅、硒化锡、氮化钽等为代表的非氧化物系NTC热敏电阻材料。
Rt = RT0*EXP(Bn*(1/T-1/T0))
式中RT、RT0分别为温度T、T0时的电阻值,Bn为材料常数.陶瓷晶粒本身由于温度变化而使电阻率发生变化,这是由半导体特性决定的。
要想做一个电压表,就必须要有电压基准,如果直接用锂电池供电,电压基准会变化。如果加额外的电源芯片会浪费成本,故而可以用内部1.19V直接反推出电源电压!

二.ADC采集NTC换算内温度
P5.1推挽输出,10K电阻,加NTC
用P1.3引脚进行采集,设置为高阻输入
R2/(R1+R2)
端口初始化,用ISP的端口配置功能,

在main.c中输入
#include ”adc.h”
      Sys_init();                        //系统初始化
      usb_init();                        //USB CDC接口配置

      IE2 |= 0x80;                  //使能USB中断
      Timer0_Init();                  //定时器初始化
      Init_595();
      
      Timer1_Init();
      ADC_Init();                     //在EA= 1之前

在 while中增加
Task_Pro_Handle_Callback();                                     //执行功能函数
SEG_Show_U32(Temp+Cal(ADC_Read(3)));                //P1.0,且设为高阻状态

在adc.c中,输入
(由ISP生成)
#include "adc.h"

void AdcSetRate (void)                        //50KSPS@24.000MHz
{
      ADCCFG &= ~0x0f;
      ADCCFG |= 0x04;                      //SPEED(4)
      ADCTIM = 0xbf;                        //CSSETUP(1), CSHOLD(1), SMPDUTY(31)
}

void ADC_Init (void)
{
      P5M0 |= 0x02; P5M1 &= ~0x02;                //P51推挽输出
      P1M0 &= ~0x08; P1M1 |= 0x08;                //P13高阻输入
      P51 = 1;

      //1.初始化IO为高阻输入
      P1M0 &= ~0x01;
      P1M1 |= 0x01;

      //2.初始化ADC速度
      AdcSetRate();
      
      //3.对齐模式,右对齐
      ADCCFG |= 0x20;
      
      //4.打开ADC电源
      ADC_POWER = 1;
}

u16 code Temp_Table[]=
{
      140,
      149,
      159,
      168,
      178,
      188,
      199,
      210,
      222,
      233,
      246,
      259,
      272,
      286,
      。。。。,
};

u16 Temp+Cal(u16 adc)      //返回结果是放大了10倍后的数值
{
      u8 j = 0;
      u16 k = 0;
      u16 min;                                                 //最小值
      u16 max;                                                //最大值
      u16 i;                                                   //温度

      adc = 4096 - adc;                                    //得到当前的adc数值
      
      if( adc < Temp_Tab )                           //温度最小值检测
                return 0xfffe;
      if( adc > Temp_Tab )                     //温度最大值检测
                return 0xfffF;      
      
      min = 0;
      max = 160;
      
      for( j=0;j<5;j++ )                                 //实现5次二分法查询
      {
                k = (min + max)/2;
                if( adc <= Temp_Tab )
                        max = k;
                else
                        min = k;
      }
      
      if( adc == Temp_Tab )
                i = min *10;                              //(20*10 - 400)/10 =-20.0
      else if( adc == Temp_Tab )
                i = max * 10;
      else                                                    //50 -51之间
      {
                while(min <= max )
                {
                        min++;
                        if( Temp_Tab == adc )
                        {
                              i = min * 10;
                              break;
                        }
                        else if( adc < Temp_Tab)                     //超过这一档的温度的adc
                        {
                              min --;
                              i = Temp_Tab;                              //上一档的adc数值记录下来
                              j = Temp_Tab -Temp_Tab ;//两档之前的差值2-8
                              j = ( adc - i )*10/j;
                              i = min*10+j;
                              break;
                        }
                }
      }
      return i;
}

u16 ADC_Read(u8 no)
{//参考手册
      u16 adcval = 0;

      ADC_CONTR &= 0Xf0;               //清空低四位
      ADC_CONTR |= no;
      
      ADC_START = 1;                     //启动ADC转化
      _nop_();
      _nop_();
      while( !ADC_FLAG );                  //等待采集完成
      ADC_FLAG = 0;                         //手动清空
      
      adcval = (((u16)ADC_RES) << 8) + (ADC_RESL);      //获取到最终的ADC数值
      
      return adcval;
}

在adc.h中输入
#ifndef __ADC_H
#define __ADC_H

#include "config.h"                        //调用头文件

void ADC_Init(void);
u16 ADC_Read(u8 no);
u8 ADC_KEY_READ( u16 adc );
u16 Temp+Cal(u16 adc);

#endif

在task.c中增加一个任务

(0,1,1,SEG-Task());

编译下载,运行正常,至此,任务1完成,实际温度=显示值-40

三.使用ADC15测量内部1.19V信号源,反推电源电压
锂电池越来越低,此时就要用到15通道的基准值

仅把main.c中SEG_Show_U32改一下
SEG_Show_U32 ( (4096 * 119 / (u32) ADC+Read ( 15 ) ) );                //实际显示330,正确

课后小练
简易温度检测器:
1.前位数码管显示当前的电压
2.后四位数码管显示当前温度
3.按下设置键设置报警温度,后四位以1秒的闪烁频率显示当前的高温预警温度数值,可通过几个按键修改这个数值,按确认键退出
4.如当前温度超过这个预警温度,后四位数码管显示“- - - -”,恢复正常后显示当前温度

页: 1 2 [3]
查看完整版本: 学习心得