(1)实验平台:
普中 51-Ai8051 开发板
https://item.taobao.com/item.htm?abbucket=17&id=1026052331067(2)资料下载 :普中科技-各型号产品资料下载链接
在学习 PWM 呼吸灯实验时, 我们知道 Ai8051U 系列单片机内部集成了 3 组可编程计数器阵列(PCA/CCP/PWM) 模块, 可用于软件定时器、 外部脉冲捕获、 高速脉冲输出和 PWM 脉宽调制输出。 本章我们来学习 PCA 的外部脉冲捕获功能。 本章分为如下几部分内容:
[20.1 实验介绍](#20.1 实验介绍)
[20.1.1 实验简介](#20.1.1 实验简介)
[20.1.2 实验目的](#20.1.2 实验目的)
[20.2 硬件设计](#20.2 硬件设计)
[20.3 软件设计](#20.3 软件设计)
[20.3.1 创建和配置工程](#20.3.1 创建和配置工程)
[20.3.1.1 GPIO 配置](#20.3.1.1 GPIO 配置)
[20.3.1.2 配置 UART1](#20.3.1.2 配置 UART1)
[20.3.1.3 配置 PCA 模块](#20.3.1.3 配置 PCA 模块)
[20.3.1.4 生成工程](#20.3.1.4 生成工程)
[20.3.2 添加用户驱动代码](#20.3.2 添加用户驱动代码)
[20.3.2.1 pca.h 文件](#20.3.2.1 pca.h 文件)
[20.3.2.2 pca.c 文件](#20.3.2.2 pca.c 文件)
[20.3.2.3 main.c 文件](#20.3.2.3 main.c 文件)
[20.4 实验现象](#20.4 实验现象)
20.1 实验介绍
20.1.1 实验简介
要使一个 PCA 模块工作在捕获模式, 寄存器 CCAPMn 中的 CAPNn 和 CAPPn 至少有一位必须置 1(也可两位都置 1)。 PCA 模块工作于捕获模式时, 对模块的外部 CCPO/CCP1/CCP2 管脚的输入跳变进行采样。 当采样到有效跳变时, PCA 控制器立即将 PCA 计数器 CH 和 CL 中的计数值装载到模块的捕获寄存器中 CCAPnL 和CCAPnH, 同时将 CCON 寄存器中相应的 CCFn 置 1。 若 CCAPMn 中的 ECCFn 位被设置为 1, 将产生中断。 由于所有 PCA 模块的中断入口地址是共享的, 所以在中断服务程序中需要判断是哪一个模块产生了中断, 并注意中断标志位需要软件清零。
PCA 模块工作于捕获模式的结构图如下所示:

PCA 模块管脚分布图如下所示:

20.1.2 实验目的
使用 PCA 模块 0 检测该通道低电平时间, 即 K1 键按下后松开, 串口助手输出检测低电平时间。
20.2 硬件设计
本实验使用到硬件资源如下:
(1) GPIO
(2) UART1 串口
(3) USB 转 TTL 模块
相关电路在前面章节已经介绍过, 此处省略。
20.3 软件设计
20.3.1 创建和配置工程
按照前面章节内容创建一份新工程, 并命名为 17-input_cap, 如下图所示:

20.3.1.1 GPIO 配置
使能端口和时钟, 将 P30 和 P31 管脚分配为 UART1, 并使能 UART1, 使能 PCA0模块, 其对应 P20 管脚。 如下图所示:

20.3.1.2 配置 UART1
在 UART1 的参数设置界面中, 可选择"与 printf 函数关联" , 这样就可以非常方便的使用 printf 输出调试结果信息。 如下图所示:

20.3.1.3 配置 PCA 模块
将 PCA 时钟源设置为系统时钟的 4 分频, 初始值为 0,16 位捕获输入模式,下降沿捕获, 使能周期中断的目的是考虑对低电平计数出现溢出时的计算, 使能模块 0 中断。 如下图所示:

20.3.1.4 生成工程
配置完成后, 按下代码生成按钮, 自动创建工程, 系统开始生成初始化代码。生成工程文件目录如下图所示:

PCA 模块配置代码自动生成为 pca.c 文件中。
20.3.2 添加用户驱动代码
20.3.2.1 pca.h 文件
cpp
extern u16 CH0_CAPTURE_STA; //输入捕获的状态
extern u16 CH0_CAPTURE_VAL; //输入捕获值
20.3.2.2 pca.c 文件
cpp
u16 CH0_CAPTURE_STA; //输入捕获状态
u16 CH0_CAPTURE_VAL;//输入捕获值
////////////////////////////////////////
// PCA中断服务程序
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void PCA_ISR(void) interrupt PCA_VECTOR
{
//<<AICUBE_USER_PCA_ISR_CODE1_BEGIN>>
if((CH0_CAPTURE_STA&0x8000)==0) //还未成功捕获
{
// 在此添加中断函数用户代码
if (PCA_CheckCounterFlag()) //判断PCA模组计数器溢出中断
{
PCA_ClearCounterFlag(); //清除PCA模组计数器溢出中断标志
if(CH0_CAPTURE_STA&0X4000)//捕获到了低电平
{
if((CH0_CAPTURE_STA&0x3fff)==0x3fff) //低电平时间太长
{
CH0_CAPTURE_STA|=0x8000; //标志一次捕获成功
CH0_CAPTURE_VAL=0xffff;
}
else
{
CH0_CAPTURE_STA++;
}
}
}
if (PCA_CheckCCF0Flag()) //判断PCA模块0中断
{
PCA_ClearCCF0Flag(); //清除PCA模块0中断标志
if(CH0_CAPTURE_STA&0X4000)//捕获到了高电平
{
CH0_CAPTURE_STA|=0x8000; //成功捕获一次低电平
CH0_CAPTURE_VAL=PCA_ReadC0Capture();
PCA_SetC0AsCaptureNegMode();//设置下降沿捕获
}
else
{
CH0_CAPTURE_STA=0;
CH0_CAPTURE_VAL=0;
CH0_CAPTURE_STA|=0x4000; //捕获到低电平标志
PCA_Stop();
PCA_SetCounter(0); //设置PCA模组计数初始值
PCA_SetC0AsCapturePosMode(); //设置上升沿捕获
PCA_Run();
}
// PCA_ReadC0Capture(); //读取PCA模块0的16位捕获值
}
}
//<<AICUBE_USER_PCA_ISR_CODE1_END>>
}
程序中我们定义了一个 u16 类型的全局变量 CH0_CAPTURE_STA, 此变量可以看成类似一个 16 位的寄存器, bit15 用来表示捕获完成, bit14 用来表示捕获到低电平, bit13-0 用来表示捕获低电平后定时器溢出次数。 程序中还定义了一个u16 类型的全局变量 CH0_CAPTURE_VAL, 用来记录捕获到下降沿时 PCA0 计数值。这两个全部变量的申明在 pca.h 文件中, 在 pca.c 文件处有它们的定义。
要计算输入信号的低电平脉宽, 根据输入捕获的工作原理, 需要先捕获一次下降沿, 然后再捕获一次上升沿, 在低电平期间还需要对 PCA 定时器溢出进行判断, 统计定时器溢出次数。 由于输入捕获初始化函数中就已经设置为下降沿捕获,并且 CH0_CAPTURE_STA 变量初始值为 0, 所以当捕获到第一个下降沿时, 将CH0_CAPTURE_STA、CH0_CAPTURE_VAL 和 PCA 计数器清 0, 然后将 CH0_CAPTURE_STA的 bit14 置 1, 表示已经捕获到低电平, 此时还需要将输入捕获极性设置为上升沿捕获, 等待上升沿到来。 如果等待上升沿期间, 计数器出现溢出, 就在CH0_CAPTURE_STA 内累计溢出次数, 如果 CH0_CAPTURE_STA 的 bit13-0 都累计满,我们就强制标记捕获完成(CH0_CAPTURE_STA 的 bit16 置 1, 虽然此时还没有捕获到上升沿) , 当上升沿到来的时, 首先将 CH0_CAPTURE_STA 的 bit15 置 1, 表示成功捕获一次低电平, 然后读取 PCA0 计数器内值保存到 CH0_CAPTURE_VAL 内,最后设置捕获极性为下降沿, 回到初始捕获状态。
这样我们就完成了一次低电平的捕获, 只要 CH0_CAPTURE_STA 的 bit15 一直为 1, 就不会进行第二次捕获, 这个时候如果主函数把捕获到低电平数据处理后,将 CH0_CAPTURE_STA 的 bit15 清零就可以进入第二次捕获了。
20.3.2.3 main.c 文件
cpp
//<<AICUBE_USER_HEADER_REMARK_BEGIN>>
/* 深圳市普中科技有限公司(PRECHIN 普中)
* 在线视频:https://space.bilibili.com/2146492485/video
官网:www.prechin.cn
* 实验名称:输入捕获实验
*
* 接线说明:参考电路图
*
* 实验现象:程序下载成功后,K1键按下后松开,串口助手输出检测低电平时间
*
* 注意事项:使用一根导线,将P32与P20口连接一起
*
*/
//<<AICUBE_USER_HEADER_REMARK_END>>
#include "config.h" //默认已包含stdio.h、intrins.h、ai_usb.h等头文件
////////////////////////////////////////
// 项目主函数
// 入口参数: 无
// 函数返回: 无
////////////////////////////////////////
void main(void)
{
//<<AICUBE_USER_MAIN_INITIAL_BEGIN>>
// 在此添加用户主函数初始化代码
u32 indata=0;
//<<AICUBE_USER_MAIN_INITIAL_END>>
SYS_Init();
printf("Hello World !\n");
//<<AICUBE_USER_MAIN_CODE_BEGIN>>
// 在此添加主函数中运行一次的用户代码
//<<AICUBE_USER_MAIN_CODE_END>>
while (1)
{
//<<AICUBE_USER_MAIN_LOOP_BEGIN>>
// 在此添加主函数中用户主循环代码
if(CH0_CAPTURE_STA&0x8000) //成功捕获
{
indata=CH0_CAPTURE_STA&0x3fff;
indata*=0xffff; //溢出次数乘以一次的计数次数时间*0.1us
indata+=CH0_CAPTURE_VAL;//加上低电平捕获的时间
printf("低电平持续时间:%.1f us\r\n",(float)indata/10);
CH0_CAPTURE_STA=0; //开始下一次捕获
}
//<<AICUBE_USER_MAIN_LOOP_END>>
}
}
主函数实现功能主要初始化系统时钟及相应外设端口, while 循环内就不断检测 CH0_CAPTURE_STA 的 bit15 标志是否为 1, 即是否成功捕获到低电平。 如果为 1 表示成功捕获, 然后读取 CH0_CAPTURE_STA 变量内 bit13-0 溢出次数, 每一次溢出时间是0xffff*0.1us, 最后还要加上CH0_CAPTURE_VAL的值保存在indata内, 通过 printf 函数打印低电平时间, 在输出函数内将 indata/10 是将前面的0.1us 换算为 us 单位, 然后将 CH0_CAPTURE_STA 变量清 0 即可, 等待下次捕获。
20.4 实验现象
将程序编译下载到目标板运行, 打开串口助手"\5--开发工具\5-串口调试助手\串口调试助手(丁丁) \sscom5.13.1.exe" , 实验现象: 使用一根导线,将 P20 管脚与 P32 管脚连接, 按下 K1 键后松开可在串口助手上输出捕获到低电平的时间。
