找回密码
 立即注册
楼主: njslmcc

学习心得

[复制链接]
  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 2025-9-21 22:09:39 | 显示全部楼层
第十三集 中断系统 学习笔记

1、中断系统介绍
当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求,CPU 暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统。
● CPU 总是先响应优先级别最高的中断请求
● CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,
● 每一个中断源可以用软件独立地控制为开中断或关中断
● 部分中断的优先级别均可用软件设置。高优先级的中断请求可以打断低优先级的中断
当中央处理机 CPU 正在处理某件事的时候外界发生了紧急事件请求,CPU 暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统。
● CPU 总是先响应优先级别最高的中断请求
● CPU 能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,
● 每一个中断源可以用软件独立地控制为开中断或关中断
● 部分中断的优先级别均可用软件设置。高优先级的中断请求可以打断低优先级的中断

2、外部中断介绍
外部中断就是在单片机的一个引脚上(带INT的),由于外部因素导致了一个电平的变化(比如由高变低),而通过捕获这个变化,单片机内部自主运行的程序就会被暂时打断,转而去执行相应的中断处理程序,执行完后又回到原来中断的地方继续执行原来的程序
外部中断0:IT0,IE0,EX0,EA
外部中断1:IT1,IE1,EX1,EA
外部中断x:x=2-4,INTxIF,EXn,EA

3、外部中断用法

任务一、编写外部中断1的程序
在main.c中
        INT1_Init();                                //外部中断1初始化
        EA = 1;                                      //IE |= 0X80;

在io.c中
void INT1_Init(void)
{
        IT1 = 1;                        //下降沿中断
        EX1 = 1;                       //打开中断允许
        EA = 1;                        //打开总中断
}

void INT_ISR(void) interrupt 2
{
        P01 = !P01;
}

课后小练
雕刻机保护系统
1.按下矩阵键盘上的按钮1开始工作
2.工作时打开LED0,表示雕刻机电源已打开
3.当外部中断1端口导通时,立刻关闭LED0,
    表示切断雕刻机电源,从而实现保护功能。

与任务一同样的:
在main.c中
        INT1_Init();                                //外部中断1初始化
        EA = 1;                                      //IE |= 0X80;

在Task.c中
        {0, 10,   10,    KEY_Task},      /* task 1 Period: 10ms */

在io.c中
u8 Key_Vol = 0;

void KEY_Task(void)
{
        if( P32 == 0 )
        {
                Key_Vol++;
                if( Key_Vol==5 )
                {
                        //按键按下的任务
                        P01 = 1;                        //打开电源
                }
        }
        else
        {
                Key_Vol = 0;
        }        
}

void INT1_Init(void)
{
        IT1 = 1;                        //下降沿中断
        EX1 = 1;                       //打开中断允许
        EA = 1;                         //打开总中断
}

void INT_ISR(void) interrupt 2
{
        P01 = 0;                        //关闭电源
}

在io.h 中添加定义
void KEY_Task(void);
void INT1_Init(void);


回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 2025-9-23 08:15:09 | 显示全部楼层
第十四集 IO中断(所有普通IO都支持的“外部中断”)

学习笔记

摘要
一、IO中断介绍

相比于外部中断,IO中断有以下优点:
1.支持所有的IO口(外部中断只有特定的外部中断的引脚)
2.可以任意配置上升沿/下降沿/高电平/低电平(外部中断只有上升/下降沿中断)
缺点:
IO中断同时只能支持一种中断模式,外部中断0和1可以同时支持上升/下降沿中断(可以用两个IO端口实现双边沿检测!)。

二、IO中断用法
1、选择合适的中断模式:PnIM0,PnIM1,下降沿,上升沿,低电平,高电平
2、打开端口的中断功能:PnINTE.x,0关1开,n 、x =(0~7),
3、配置IO口的中断:PnINTF.x,0无1有,标志位软件清0
4、中断号处理:装大于31的拓展工具或13号空中断跳转

任务1:编写IO中断的程序
在main.c中写入
P3_IO_Init();                //IO中断初始化
EA = 1;

在io.c中写入
void P3_IO_Init(void)
{
        P3IM0 = 0x00;                //IO中断模式为下降沿
        P3IM1 = 0x00;

        P3INTE = 0x08;        //打开中断
}

void P3_IO_ISR(void) interrupt 40
{
        u8 intf;
        intf = P3INTF;

        if( intf )                //判断有没有IO中断
        {
                P3INTF = 0;
                If( intf & 0x08)                //判断是否是P33按下
                {
                        P01 = !P01;
                }               
        }
}

在io.h中写入
//void INT1_Init(void);
void P3_IO_Init(void);

三、中断优先级的设置
相同优先级,靠前的中断源先执行,执行完之后再执行低中断源,且一个中断源在执行的时候不能被打断。
定时器0和P3中断都是最低优先级,定时器0中断号1,P3中断号40,执行完定时器0,再执行P3,再执行定定时器0,再执行...
PINIPL和PINIPH,00为最低级,11为最高级

任务2:编写P4端口的IO中断 打断 P3低电平中断的的程序(注意优先级)

开显示任务,
平时显示0
P33中断显示1,
P47中断显示2,
低平触发

在main.c中写入
P3_IO_Init();                //IO中断初始化
P4_IO_Init();
EA = 1;

while(1)
{
        ……..
        Task_Pro_Handler_Callback();                //执行功能函数
        password[0] = 0;
}

在io.c中写入
void P3_IO_Init(void)
{
        P3IM0 = 0x00;                //IO中断模式为低电平
        P3IM1 = 0xff;

        P3INTE = 0x08;                //打开中断
}

void P3_IO_ISR(void) interrupt 40
{
        u8 intf;
        intf = P3INTF;

        if( intf )                //判断有没有IO中断
        {
                P3INTF = 0;
                If( intf & 0x08)                //判断是否是P33按下
                {
                        password[0] = 1;
                }               
        }
}

void P4_IO_Init(void)
{
        P4IM0 = 0x00;                //IO中断模式为低电平
        P4IM1 = 0xff;

        P4INTE = 0x80;                //打开中断P4.7
}

void P4_IO_ISR(void) interrupt 41
{
        u8 intf;
        intf = P4INTF;

        if( intf )                //判断有没有IO中断
        {
                P4INTF = 0;
                If( intf & 0x80)                //判断是否是P47按下
                {
                        password[0] = 2;
                }               
        }
}

在io.h中写入
//void INT1_Init(void);
extern u8 key_num ;
extern u8 passward[8];
void P3_IO_Init(void);
void P4_IO_Init(void);

现在就可以看到P3中断的优先级是高于P4中断的优先级的

如果我们要反过来,让P4中断的优先级高于P3中断的优先级

在void P4_IO_Init(void)中增加
PINIPH |=(1<<4);        //与入最高优先级
PINIPL |=(1<<4);

在config.c的void Timer0_Init(void)中增加
        IPH |= (1<<1);
        IP  |= (1<<1);

这样P4 优先级就高于P3了

相同优先级:靠前的中断源先执行,执行完之后再执行低中断源,且一个中断源在执行的时候不能被打断。
定时器0和P3中断都是最低优先级,定时器0中断号1,P3中断号40,执行完定时器0再执行P3

课后小练
多路抢答器
1.上电后一位数码管显示0,表示没有按下
2.选择任意三个独立按键作为三个用户,分别
   代表“用户1”,“用户2”,“用户3”
3.谁第一个按下按钮,数码管就显示数字几
   代表抢答成功

在main.c中写入
P3_IO_Init();                //IO中断初始化
EA = 1;

while(1)
{
        ……..
        Task_Pro_Handler_Callback();                //执行功能函数
        password[0] = 0;
}

在io.c中写入
void P3_IO_Init(void)
{
        P3IM0 = 0x00;                //IO中断模式为低电平
        P3IM1 = 0xff;

        P3INTE = 0x08;                //打开中断
}

void P3_IO_ISR(void) interrupt 40
{
        u8 intf;

        intf = P3INTF;

        if( intf )                //判断有没有IO中断
        {
                //P3INTF = 0;
                if( intf & 0x02)                //判断是否是P31按下
                {
                        password[0] = 1;
                }               
                else if( intf & 0x04)                //判断是否是P32按下
                {
                        password[0] = 2;
                }
                else if( intf & 0x08)                //判断是否是P33按下
                {
                        password[0] = 3;
                }
        }
}
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 2025-9-26 08:26:15 | 显示全部楼层
第十五集 定时器做计数器 学习笔记

一. 计数器的作用
只要输出信号是高低电平变化的传感器,想要计算个数的就可以用计数器的功能。

二. 定时器做为计数器的用法


任务1:编写定时器1计数的的程序
(为了方便计数,10个脉冲中断一次)
新建两个文件,tim.c和tim.h
在main.c中写入
#include “tim.h”

TIM1_Count_Init();                //在EA = 1之前

在tim.c中写入
#include “tim.h”
u32 Count_T1 = 0;

void TIM1_Count_Init(void)
{
        //TMOD  搜索,找到相关定义
        T1_CT = 1;                //设置外部计数
        T1_M1 = 0;                //设置为16位自动重载
        T1_M0 = 0;
        T0_GATE = 0;

        TH1 = (65536-Count_num)>>8        //65526
        TL1 = (65536-Count_num);

        P3PU |= 0x20;                //上拉电阻
        TR1 = 1;
        ET1 = 1;
}

void Timer1_Isr(void) interrupt 3                //1MS执行一次
{
        Count_T1 ++;                //T1引脚测到10个脉冲就溢出1次
                                        //不要过于频繁地中断,影响稳定性
}

//因P35 有冲突,故用ISP里的万能虚拟数码管显示
void T1_RunTask(void)
{
        u32 count_th_tl = 0;
        count_th_tl = ((u16)TH1<<8 + (u16)TL1);
        count_th_tl -= 65526;
        SEG7_ShowLong(Count_T1 * Count_num + count_ th_tl, 10);
}

在tim.h中写入
#ifndef __TIM_H
#define __TIM_H
#include “config.h”

#define Count_num 10        //累计脉冲单位

void TIM1_Count_Init(void);
void T1_RunTask(void);

#endif

在task.c中写入
#include “tim.h”

增加一个任务
(0,100,100,T1_RunTask),//定时器1的计数功能

时钟选24M,下载,三个勾选,USB不停电下载
打开仿真调试接口,显示按键次数

至此,任务1就完成了。

三.定时器1测量INT1引脚低电平脉冲宽度

任务2:编写INT1测量低电平时间
(由按键模拟信号,100us的计数周期计数!)
ISP定时器计算工具,24MHz,100微秒,使能中断
在main.c中写入
#include “tim.h”

Timer1_Init();                //在EA = 1之前

在tim.c中
u32 Count_T1 = 0;
void Timer1_Isr(void) interrupt 3
{
        static u32 count_p33 = 0;
        
        if( P33 == 0 )                //按键按下开始计数
        {
                count_p33 ++ ;
        }
        else
        {
                if( count_p33>0 )                //表示之前按下了这个按键
                {
                        Count_T1 = count_p33;
                }
                count_p33 = 0;
        }
}

void Timer1_Init(void)                //100微秒@24.000MHz
{
        AUXR &= 0xBF;                        //定时器时钟12T模式
        TMOD &= 0x0F;                        //设置定时器模式
        TL1 = 0x38;                                //设置定时初始值
        TH1 = 0xFF;                                //设置定时初始值
        TF1 = 0;                                //清除TF1标志
        TR1 = 1;                                //定时器1开始计时
        ET1 = 1;                                //使能定时器1中断
}

void T1_RunTask(void)
{
        //SEG7_ShowLong(Count_T1, 10);                             //显示整数
        SEG7_ShowString("%07.01f",((float)Count_T1)/10);   //显示1位小数
}

在tim.h中
#define Count_num 10                //累计脉冲单位

//void TIM1_Count_Init(void);
void Timer1_Init(void);                //100微秒@24.000MHz
        
void T1_RunTask(void);

在task.c中写入
#include “tim.h”

有一个任务
(0,100,100,T1_RunTask),//定时器1的计数功能
至此,任务2就完成了。

课后小练
CT计数器:
在设备的出料端口有一个感应器,每次有成品出来就会有一个低电平出来,计算相邻的两个产品出来的时间差来计算CT时间。
1.计算P33引脚的相邻两次按下的时间,精确到100ms(即单个时间)
2.计算按下P33的次数(即总产量)
3.数码管前四位显示单个时间 ,后四位显示次数

回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 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"

u8  MinusFlag = 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 DQ  P33

extern u8  MinusFlag ;                        //如果等于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[7-i] = num%10;
                num /= 10;
        }
}

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

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


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

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 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[50];                        //接收缓冲区
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[Rec_Num++] = 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[Rec_Num-1] == '\n' ) && ( Rec_Dat[Rec_Num-2] == '\r' ) )        //末尾判断
                {      //1.发送OPEN\r\n打开数码管,数码管显示“- - - -” 11 22 OPEN\r\n
                        if( ( Rec_Dat[Rec_Num-6] == 'O' ) &&
                            ( Rec_Dat[Rec_Num-5] == 'P' ) &&
                            ( Rec_Dat[Rec_Num-4] == 'E' ) &&
                            ( Rec_Dat[Rec_Num-3] == 'N' ) )
                        {
                                passward[0] = 16;
                                passward[1] = 16;
                                passward[2] = 16;
                                passward[3] = 16;
                                Uart2_SendStr( "打开成功!\r\n" );
                        }

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

                        //3.再打开的情况下,串口发送DAT+123\r\n,数码管显示数值“123”
                        else if( ( Rec_Dat[Rec_Num-9] == 'D' ) &&
                               ( Rec_Dat[Rec_Num-8] == 'A' ) &&
                               ( Rec_Dat[Rec_Num-7] == 'T' ) &&
                               ( Rec_Dat[Rec_Num-6] == '+' ) &&
                               ( Rec_Dat[Rec_Num-5] == '1' ) &&
                               ( Rec_Dat[Rec_Num-4] == '2' ) &&
                               ( Rec_Dat[Rec_Num-3] == '3' ) )
                        {
                                passward[0] = 17;
                                passward[1] = 1;
                                passward[2] = 2;
                                passward[3] = 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”
(前面已完成)
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 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[50];                  //接收缓冲区
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[Rec_Num++] = 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[Rec_Num-1] == '\n' ) && ( Rec_Dat[Rec_Num-2] == '\r' ) )        //末尾判断
                {
                        //1.发送OPEN\r\n打开数码管,数码管显示“- - - -” 11 22 OPEN\r\n
                        if( ( Rec_Dat[Rec_Num-6] == 'O' ) &&
                            ( Rec_Dat[Rec_Num-5] == 'P' ) &&
                            ( Rec_Dat[Rec_Num-4] == 'E' ) &&
                            ( Rec_Dat[Rec_Num-3] == 'N' ) )
                        {
                                passward[0] = 16;
                                passward[1] = 16;
                                passward[2] = 16;
                                passward[3] = 16;
                                Uart2_SendStr( "打开成功!\r\n" );
                        }

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

                        //3.再打开的情况下,串口发送DAT+123\r\n,数码管显示数值“123”
                        else if( ( Rec_Dat[Rec_Num-9] == 'D' ) &&
                               ( Rec_Dat[Rec_Num-8] == 'A' ) &&
                               ( Rec_Dat[Rec_Num-7] == 'T' ) &&
                               ( Rec_Dat[Rec_Num-6] == '+' ) &&
                               ( Rec_Dat[Rec_Num-5] == '1' ) &&
                               ( Rec_Dat[Rec_Num-4] == '2' ) &&
                               ( Rec_Dat[Rec_Num-3] == '3' ) )
                        {
                                passward[0] = 17;
                                passward[1] = 1;
                                passward[2] = 2;
                                passward[3] = 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[50];                         //接收缓冲区
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[Rec_Num++] = 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
回复

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 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
u8  ADC_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添加一个申明
u8  ADC_KEY_READ ( u16 adc); 
编译正确,下载,运行OK
至此,任务2就完成了

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

使用道具 举报 送花

  • 打卡等级:偶尔看看III
  • 打卡总天数:39
  • 最近打卡:2025-10-15 18:27:22
已绑定手机

1

主题

26

回帖

255

积分

中级会员

积分
255
发表于 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[0] )                           //温度最小值检测
                return 0xfffe;
        if( adc > Temp_Tab[160] )                       //温度最大值检测
                return 0xfffF;        
        
        min = 0;
        max = 160;
        
        for( j=0;j<5;j++ )                                 //实现5次二分法查询
        {
                k = (min + max)/2;
                if( adc <= Temp_Tab[k] )
                        max = k;
                else
                        min = k;
        }
        
        if( adc == Temp_Tab[min] )
                i = min *10;                                //(20*10 - 400)/10 =  -20.0
        else if( adc == Temp_Tab[max] )
                i = max * 10;
        else                                                    //50 -51之间
        {
                while(min <= max )
                {
                        min++;
                        if( Temp_Tab[min] == adc )
                        {
                                i = min * 10;
                                break;
                        }
                        else if( adc < Temp_Tab[min]  )                       //超过这一档的温度的adc
                        {
                                min --;
                                i = Temp_Tab[min];                                //上一档的adc数值记录下来
                                j = Temp_Tab[min+1] -Temp_Tab[min] ;  //两档之前的差值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.如当前温度超过这个预警温度,后四位数码管显示“- - - -”,恢复正常后显示当前温度

回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-10-16 05:38 , Processed in 0.120645 second(s), 81 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表