目前课设已完成,2m距离,传输10000个连续数字,每个数字两字节大小,即总共20000个字节160000bit,用时7s,大约2.3万bit/s,即22.4kB/s,误码率为0,视频演示链接
另外,自己写了一个基于QT的串口上位机,结合USMART,可以非常方便修改参数,所有源码在文后,一开始做的时候资料难求,网上都是要收费的,希望不要有人把我的资料拿去卖啦,分享分享,受益于互联网,回馈于互联网,如果你也有这个课设任务,希望对你有所帮助
课程设计题目如下:
关键字:STM32、可见光、距离1.5米、1万个连续数字、记录接收时间、通信速率、误码率。
本文说明:只是通过C程序简单实现通信过程,不涉及高深的理论,所使用的模块也是廉价实惠的,争取以最低成本通过代码学习、理解并实现可见光通信即可。(可能废话有点多了,可以看到文末那边的框图,整体的思路写在后面,所有的工程文件在文末,对应着代码来看最好)
先看硬件部分需要什么
在STM32板子部分,因为接收与发送要尽量同时进行,因此需要使用两个开发板分别进行收发(使用RTOS系统的话没试过,感觉会影响通信),一开始以为需要定义一个1万的大数组,但是没必要,直接使用for循环进行赋值发送就好,因此可以使用最简单的stm32核心板就好(网购8元左右),再加usb下载线或者STLINK就可以使用了。
在发射部分,可以使用二极管白光LED就好了,通过白光的亮-暗表示二进制的0-1,适合在比较近距离的时候,再加一个灯罩效果会更佳,STM32普通的GPIO能够输出的电平最高只有3.3V,使用一个3W的白光LED也可以控制,不过最好是将LED的阳极接在单片机板载输出的5V引脚上,阴极则接到一个普通的GPIO上即可,该GPIO输出低就能让LED获得更大的发射功率,在远距离的情况下可以使用二极管激光LED,用法和白光LED的一样。
在接收部分,使用一块钱左右的光敏电阻模块,该模块自带了信号放大电路和模数转换功能,通过感应周围光线的亮暗就能够将0、1电平通过引脚输出,因此只要将光敏模块的数字输出引脚接至STM32的一个普通GPIO上,STM32就能够通过读取该GPIO获知当前环境的光线是亮还是暗了(不要担心环境光线影响大,该模块自带一个滑动电阻可以调节灵敏度,只要设置好在当前环境下该模块输出为暗即可)。
了解一些概念
可见光,就直白的理解就是人眼能够看见的光都可以称之为可见光嘛,不一定非得是白光,红光、绿光、蓝光或者光纤都可以称为可见光,注意红外线紫外线是不可见光。
信号调制和解调,也可以先简单地理解成以什么样的方式表示一个信号,不同方式可以代表不同的信号,而解调就是根据识别到的方式判断其为何种信号的过程,这更像是两者之间的一种协议。比如二进制0信号,使用1.6ms的高电平和0.7ms的低电平表示,二进制信号1可以使用1.6ms的高电平和1.5ms的低电平表示,相对于发送端来说,这些高低电平就体现在LED的暗-亮上,通过暗亮持续的时间不同代表不同的信号;而对于接收端来说,则需要能读取出与光敏模块连接的GPIO的电平持续时间,进而就可以判断出是什么信号。当然,还可以使用pwm不同占空比来代表不同信号,或者其他的论文里提到的方式。
对于上面提到的读取引脚持续的电平时间,或者是记录接收用时,需要用到定时器,STM32有好多并且功能强大的定时器,相信只要学过stm32的朋友都有了解,关于定时器的理解,我通常会和别人解释为它就是一种计数器,你可以设置它的频率来控制它计数一次的时间,最终得到的计时时间实则为计数的次数乘以该时间,你还可以设置它计数到多少数值的时候让它引发中断,当然也可以在计数溢出的时候发生中断,溢出是因为stm32f1的定时器是16位的寄存器,最多只能计数2^16即65536次,溢出了它还能自动重装载,循环计数,最要命还有一点,你可以把它看成是与CPU并行执行、两者独立的,比如你开启定时器计数后,不会影响到CPU执行其他的代码,两者各司其职,是相互独立的,不同于延迟函数,在延迟期间,CPU不能去做其他事。
对于串口通信就没什么好说的啦,就是开启了串口中断,如果需要写数据显示到电脑时,会把数据写入DR寄存器,此时就会引发串口中断,把数据传送出去,而电脑传数据回单片机时也会引发中断,只要与上位机定好协议就能够解析出接收到的数据是什么,这里可以直接使用正点原子的工程模板,自带了串口数据处理的功能,非常方便。另外,还可以使用正点原子自己写的一个串口调试组件USMART,有什么作用呢?比如在代码烧录运行后,你想要修改某个函数的传入参数时,就可以通过USMART来实现,免去了重复修改代码、编译、烧录的过程,其原理也是基于串口通信,将串口接收到的字符串解析出来,然后调用该函数并赋予函数入口参数新的数值。链接 -》第40讲 USMART调试组件实验-M3h
进入正题,发射端的数据到底应该怎么发送?从上面已知,白光只有两种状态可以被识别,就是亮或者暗,因此传输所有的数据,都需要将该十进制数据转换为2进制,将二进制数据发送出去,接收端只要读取到该二进制数据,就意味着最简单的可见光通信完成了。所以有两件事需要做,一是将十进制转二进制,二是规定好以什么样的方式表示不同的信号,哦还有一件,就是一个数据需都需要什么信号呢?
我的做法很简单,对于十进制转二进制,可以通过位操作的方式判断该数据的每一个位是什么就好了,因为需要传输1万个数字,所以需要定义 uint16_t 的数据类型,也就是 unsigned short int 数据类型,使用16位的二进制表示一个数据,可表示的范围是0到65535,对某个数据的位操作如下:
//转化一个数据(2字节)
void transData(uint16_t data)
{
uint8_t j;
uint16_t tmp;
printf("%d ",data);
for(j=16;j>0;j--)
{
tmp = (data)&(1<<(j-1)); //从第16位由高往低位发送
if(tmp) printf("1");
else printf("0");
}
}
//比如提取data的第16位时,让1左移15位,则1就在第16位了,其后都是0
//再与data相与,那么只有data的第16为的数据可以被保留,其他的全都为0
//赋值给tmp,tmp的状态就是data的第16位状态了
对于以什么样的方式表示不同信号,可以采取不同高电平持续时间的不同来区分不同的信号,比如我高电平持续t1时间表示0,高电平持续t2时间表示1,这样就能较快速并且方便地被接收端识别;与此同时,为了区分数据或者说避免外界信号的干扰,可以在每个数据前都再加上一个起始信号,比如可以用连续两次的高电平均持续t3时间来表示起始信号,即表示一个数据的开始。总共需要三种信号来表示一个数据。
发送端的基本思路已解决,再看看接收端,其实已知多长高电平对应的是什么信号了,接收端需要做的,就是读取出该高电平时间,然后判断是什么信号就好。关键点在于,如何读取引脚的高电平时间。
一开始的思路很狭隘,就是开启该引脚的外部中断,遇到上升沿就触发中断,然后接收端就进入一个延迟函数等待,等待若干时间后再读取引脚电平来区分信号的不同。其实可以使用定时器来完成这部分计时,好处就在于和上文提到的一样,定时器计时期间不会影响CPU的工作,因此可以使用输入捕获,其本质也是使用定时器计时的。比如可以这样配置定时器,先开启上升沿中断和更新中断,也就是当遇到一个高电平时会触发该中断,接着在该中断里开启定时器计数,并且同时将定时器设为下降沿中断,因此在本次高电平结束的时候,又一次触发该中断,然后再在该中断里停止定时器计数并读出定时器的计数数值,即可得到一次高电平捕获的时间,同时还要将定时器设为上升沿中断以备下次的捕获。总结来说,每一次获取一个高电平时,只需要在一个while循环里一直等待,直到捕获完一次高电平信号,就可以开始判断该信号是什么类型的信号了。
对于以上框图的解释如下,从发射端到接收端:
USMART是一个开源的串口调试组件,它可以通过串口助手调用程序里面的任何函数并执行,可以随意更改函数的输入参数(支持数字(10/16进制)、字符串、函数入口地址等作为参数)。当串口接收到数据时,USMART会将接收到的字符串解析,通过对应的函数指针即可访问到程序里的任意函数。因此加入USMART,可实现在芯片已经烧录好的情况下,在线修改发射相关的参数,非常方便。
在发射端如果收到了发射连续数据的指令,则程序先将起始数据、结束数据和长度发射给接收端用于验证,随后再在for循环里将数据循环发射出去。对于发射一个数据时,则是通过循环移位逐个将二进制位提取出来并判断。
在接收端初始化了定时器3和定时器5,分别用来计算接收总用时和捕获光敏电阻引脚高电平持续时间的;初始化FSMC则是为了使用外部SRAM和LCD,两者均由FSMC来配置和管理。
刚初始化完成,会先对缓冲数组清零,在后面接收数据期间,如果数据有错误,错误的数据不会被保存到数组里,那么该位则仍为0,以便在数据统计误码时,可以直接通过数组里的数值是否为0判断该接收数据位是否正确。
在每一次接收数据时,会先接收3个信息头,分别是数据块的起始数据、结束数据和数据长度,只有当这3个均接收正确并验证成功(比如结束数据+1-起始数据=长度),才会往下循环接收真正的数据。
在循环接收数据期间,先给i赋值为起始位,如果数据没有出错,则i和所接收到的数值是一样的(每次循环步进相等),因此可以通过检验i和所接收的数据是否相等来确定该数据有没有出错。由于当接收端接收错误,比如光线被挡住了,但这并不影响发射端以固定的步进发射数据,而接收端则会因为读取不到数据,步进停止,因此在每次出现错误数据后,都需要下一次的正确数据来矫正接收端里的循环步进值。
另外,在统计误码之后,需要对缓冲数组清零,否则在进行下一次数据接收与判断时会出错。
全部工程下载链接:
链接:https://pan.baidu.com/s/1r5pLaeCu5mieWKNu90GWDA
提取码:feng
跳转:可见光通信工程下载链接【提取码:feng】
【发射端main.c】
#include "stm32f10x.h"
#include "delay.h"
#include "sensor.h"
#include "usart.h"
#include "usmart.h"
//连续发射数据
void emitSeriesData(uint16_t start,uint16_t end)
{
uint16_t i,len=end+1;
if((start>0) && (end>start))
{
printf("发送从 %d 到 %d ,共 %d 个数据。\r\n",start,end,(end+1-start));
printf("正在发送……\r\n");
emitData(start); emitData(end); emitData((end+1-start)); //先发送本次数据块的信息
delay_ms(10);
for(i=start;i<len;i++)
{
emitData(i); //开始循环发送,从开始数据位开始发送
if(i%100 == 0) printf("%d ",i);
}
LED = 0;
printf("\r\n本次已发送完成。\r\n");
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
delay_init(); //延时函数初始化
LedConfigInit(); //初始化光源引脚及发射相关参数
usmart_dev.init(SystemCoreClock/1000000); //初始化 USMART,实现在串口修改发射相关参数
LED = 0; //发射前默认光源开启
printf("当前参数:\r\n"); lookUpCurrentParameter();
printf("\r\n等待发射数据……\r\n");
emitSeriesData(1,5);
while(1)
{
//直到等到发射命令从串口进入,才进行发射
}
}
【发射端sensor.c】
#include "sensor.h"
#include "delay.h"
#include "usart.h"
#define DATABIT 14
uint32_t startSignalHold_time=600; //起始信号持续高电平
uint32_t start_Wait_time=800; //发送二进制0时 高电平 持续的时间
uint32_t data_0_Hold_time=1000; //发送二进制1时 高电平 持续的时间
//以下是低电平等待时间
uint32_t data_0_Wait_time=300;
uint32_t data_1_Hold_time=300;
uint32_t data_1_Wait_time=300;
timeParameter T = {0,0,0,0,0,0}; //用于延迟部分
//初始化光源引脚及发送参数
void LedConfigInit()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_4); //LED光源
}
//发送一个数据(2字节)
void emitData(uint16_t data)
{
u8 j;
u16 tmp;
emitStartSignal();
for(j=DATABIT;j>0;j--) //最大可表示到65535的数据
{
tmp = (data)&(1<<(j-1)); //由高位往低位发送
if(tmp) {emitData_1();}
else {emitData_0();}
}
}
//配置发送参数,这样定义是为了方便调试,使用usmart
void configEmitParameter(u32 p1,u32 p2,u32 p3,u32 p4,u32 p5,u32 p6)
{
startSignalHold_time = p1;
data_0_Hold_time = p2;
data_1_Hold_time = p3;
start_Wait_time = p4;
data_0_Wait_time = p5;
data_1_Wait_time = p6;
}
//发射一个数据前,先发射一个起始信号
void emitStartSignal()
{
LED = 1; emitProcessDelay_us(startSignalHold_time);
LED = 0; emitProcessDelay_us(start_Wait_time);
//LED = 1; emitProcessDelay_us(startSignalHold_time);
//LED = 0; emitProcessDelay_us(start_Wait_time);
}
//发射1信号
void emitData_1()
{
LED = 1; emitProcessDelay_us(data_1_Hold_time);
LED = 0; emitProcessDelay_us(data_1_Wait_time);
}
//发射0信号
void emitData_0()
{
LED = 1; emitProcessDelay_us(data_0_Hold_time);
LED = 0; emitProcessDelay_us(data_0_Wait_time);
}
//发射结束信号
void emitEndSignal()
{
LED = 0; emitProcessDelay_us(data_0_Hold_time);
emitProcessDelay_us(data_0_Hold_time);
}
void lookUpCurrentParameter() //查看当前发射参数
{
printf("HoldTime: start: %d 0信号: %d 1信号: %d\r\nWaitTime: start: %d 0信号: %d 1信号: %d\r\n",\
startSignalHold_time,data_0_Hold_time,data_1_Hold_time,start_Wait_time,data_0_Wait_time,data_1_Wait_time);
}
//传入us的个数,支持到每次最大10s的延迟,即10000000us
void emitProcessDelay_us(uint32_t time)
{
uint8_t i;
if(time<=23000) delay_us(time); //直接us延迟
else if(time>23000 && time<1000000) //毫秒转化
{
T._ms = time/1000.0;
T._1ms = (int)T._ms; //1ms的个数
T._us = (T._ms-T._1ms)*1000; //us的数值
delay_ms(T._1ms);
if(T._us) delay_us(T._us);
}
else if(time >= 1000000) //大于1s的时候
{
T._s = time/1000000.0; //求出有多少个1000ms(即1s),因为delay_ms最大支持到1800ms
T._1s = (int)T._s; //1s的个数
T._ms = (T._s-T._1s)*1000; //ms的数值,肯定小于1000
for(i=0;i<T._1s;i++) delay_ms(1000);
if(T._ms) delay_ms(T._ms);
}
}
【接收端:main.c】
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "sensor.h"
#include "usmart.h"
#include "timer.h"
#include "sram.h"
#include <string.h>
#include "lcd.h"
#define dataLen 12000
uint16_t dataBuf[dataLen] __attribute__((at(Bank1_SRAM3_ADDR)));
uint16_t dataCurrentPos=0;
int main(void)
{
uint16_t recvWorkCounts=0;
uint8_t j=0,startRecvFlag=0;
uint16_t i=0,recvData,dataInfo[3]; //dataInfo用来检验一次数据接收的起始数据和结尾数据
uint16_t errorCounts=0; //用来记录接收错误的数据个数
uint32_t recvTime_us; //用来记录接收用时
delay_init(); //延时函数初始化
uart_init(115200); //串口初始化为115200
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
TIM3_CountTime_Init(); //用于计时接收数据的时间
LdrConfigInit(); //初始化光敏传感器的引脚和输入捕获功能
usmart_dev.init(SystemCoreClock/1000000); //初始化 USMART,实现在串口修改接收相关参数
FSMC_SRAM_Init(); //初始化外部SRAM
LCD_Init();
memset(dataBuf,0,sizeof(dataBuf)); //全都初始化为0
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"My Class Design");
LCD_ShowString(60,70,200,16,16,"2020-9-4 @By afeng");
LCD_ShowString(60,90,230,16,16,"Visible Light Communication.");
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(60,160,200,16,24,"WorkCounts :");
LCD_ShowString(60,200,200,16,24,"errorCounts :"); //LCD_ShowNum(230,200,10000,5,24)
LCD_ShowString(60,240,200,16,24,"usingTime/us:"); //LCD_ShowNum(230,240,200000000,9,24)
POINT_COLOR=BLACK;
LCD_ShowNum(230,160,0,9,24);
LCD_ShowNum(230,200,0,9,24);
LCD_ShowNum(230,240,0,9,24);
printf("等待接收数据……\r\n");
while(1)
{
recvData = receiveData(); //阻塞等待接收一个数据
if((!startRecvFlag) && recvData) //先接收起始数据和结束数据、及总的长度
{
dataInfo[j] = recvData;
j++;
if(j==3)
{
j = 0; //若结束+1-起始 = 长度,则准备进入接收数据
if((dataInfo[1]+1-dataInfo[0]) == dataInfo[2]) startRecvFlag = 1;
else printf("此次接收失败!请重新发送数据!\r\n");
}
}
if(startRecvFlag) //开始接收数据
{
printf("receiving...\r\n");
startRecvFlag = 0;
startCountRunningTime(); //开始计时
for(i=dataInfo[0];i<(dataInfo[1]+1);i++) //循环接收数据
{
recvData = receiveData(); //阻塞接收
if(recvData==i) dataBuf[dataCurrentPos++] = recvData;
else
{
if(recvData!=0)
{
dataCurrentPos = recvData-i;
dataBuf[dataCurrentPos++] = recvData;
i = recvData;
}
}
}
recvTime_us = getRunningTime(); //结束接收计时并得到时间
printf("接收完成!正在检验数据……\r\n"); //检验数据
dataCurrentPos=0;
for(i=dataInfo[0];i<(dataInfo[1]+1);i++)
{
if(dataBuf[dataCurrentPos]!=i)
{
errorCounts++;
printf("【err】 ");
}
else printf("%d ",dataBuf[dataCurrentPos]);
dataCurrentPos++;
}
recvWorkCounts++;
printf("\r\n");
printf("-----------------------------------------------------\r\n");
printf("处理完成! 错误数据: %d 个 总接收用时: %d us (%.3f ms --> %.2f s)\r\n", \
errorCounts,recvTime_us,(recvTime_us/1000.0),(recvTime_us/1000000.0));
printf("-----------------------------------------------------\r\n\r\n");
LCD_ShowNum(230,200,recvWorkCounts,9,24);
LCD_ShowNum(230,200,errorCounts,9,24);
LCD_ShowNum(230,240,recvTime_us,9,24);
errorCounts = 0;
recvData = 0;
dataCurrentPos=0;
memset(dataBuf,0,sizeof(dataBuf));
printf("等待接收数据……\r\n");
}
}
}
【接收端 sensor.c】
#include "sensor.h"
#include "usart.h"
#include "timer.h"
#define DATABIT 14
extern u8 TIM5CH1_CAPTURE_STA;
uint32_t startSignal_time;
uint32_t data_0_time;
uint32_t data_1_time;
uint32_t upperAddValue;
uint32_t lowerDecValue;
//上下限时间,先定义变量再赋值,为了接收更快,优化
uint32_t start_upperBound,start_lowerBound;
uint32_t data_0_upperBound,data_1_upperBound,data_0_lowerBound,data_1_lowerBound;
void LdrConfigInit()
{
TIM5_Cap_Init(); //PA0接光敏传感器引脚,采用定时器输入捕获
startSignal_time = 600; //起始信号高电平维持时间
data_0_time = 800; //信号0高电平持续时间
data_1_time = 1000; //信号1高电平持续时间
upperAddValue = 100; //向上容许增加的时间范围
lowerDecValue = 100; //向下容许减小的时间范围
start_upperBound = startSignal_time+upperAddValue;
start_lowerBound = startSignal_time-lowerDecValue;
data_0_upperBound = data_0_time+upperAddValue;
data_0_lowerBound = data_0_time-lowerDecValue;
data_1_upperBound = data_1_time+upperAddValue;
data_1_lowerBound = data_1_time-lowerDecValue;
}
uint16_t receiveData()
{
uint32_t highVolTime = 0;
short int dataBit = DATABIT;
uint8_t dataFlag = 0;
uint16_t data = 0;
while(1)
{
if(TIM5CH1_CAPTURE_STA&0x80) //成功捕获到起始的信号
{
highVolTime = getHighVolTime();
if((highVolTime<=start_upperBound) && (highVolTime>=start_lowerBound)) //第一次起始信号正确
{
while(1)
{
if(TIM5CH1_CAPTURE_STA&0x80) //数据信号
{
highVolTime = getHighVolTime();
if((highVolTime>=data_0_lowerBound) && (highVolTime<=data_0_upperBound))
{
data &= ~(1<<(dataBit-1));
dataBit--;
if(dataBit==0)
{
dataFlag = 1;break;
}
}
else if((highVolTime>=data_1_lowerBound) && (highVolTime<=data_1_upperBound))
{
data |= (1<<(dataBit-1));
dataBit--;
if(dataBit==0)
{
dataFlag = 1;break;
}
}
else {printf("dataError:highVolTime:%d\r\n",highVolTime);break;}
}
}
break;
}
else printf("startError:highVolTime:%d\r\n",highVolTime);
}
}
if(dataFlag) return data;
else return 0;
}
//配置接收相关的参数,
void configRecvParameter(uint32_t p1,uint32_t p2,uint32_t p3,uint32_t p4,uint32_t p5)
{
startSignal_time = p1; //起始信号持续时间
data_0_time = p2; //信号0持续时间
data_1_time = p3; //信号1持续时间
upperAddValue = p4; //往上增加范围
lowerDecValue = p5; //往下减小的范围
start_upperBound = startSignal_time+upperAddValue;
start_lowerBound = startSignal_time-lowerDecValue;
data_0_upperBound = data_0_time+upperAddValue;
data_0_lowerBound = data_0_time-lowerDecValue;
data_1_upperBound = data_1_time+upperAddValue;
data_1_lowerBound = data_1_time-lowerDecValue;
}
void lookUpCurrentParameter() //查看当前发射参数
{
printf("HoldTime: start: %d 0信号: %d 1信号: %d\r\nWaitTime: upper: %d lower: %d\r\n", \
startSignal_time,data_0_time,data_1_time, \
upperAddValue,lowerDecValue);
}
【接收端 timer.c】
#include "timer.h"
#include "usart.h"
u8 TIM5CH1_CAPTURE_STA = 0; //输入捕获状态
u16 TIM5CH1_CAPTURE_VAL=0; //输入捕获值
u8 TIM3_CountTime_STA = 0; //计时状态
u8 counts = 0; //计时溢出的次数
#define TIM3_ARR 0xFFFF
#define TIM3_PSC 7199 //10Khz的计数频率,计数一次为100us
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_CountTime_Init() //最大计数65536
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = TIM3_ARR; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = TIM3_PSC; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
// TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx更新中断标志
if((TIM3_CountTime_STA)==1) //还在计时
{
counts++;
}
}
}
//开始计时
void startCountRunningTime()
{
TIM_Cmd(TIM3, ENABLE);
TIM3_CountTime_STA = 1;
}
//返回代码运行计时时间
u32 getRunningTime()
{
u32 times_us;
times_us = ((counts*65536)+TIM3->CNT)*100;
TIM3_CountTime_STA = 0;
TIM3->CNT = 0;
counts = 0;
TIM_Cmd(TIM3, DISABLE);
return times_us;
}
uint32_t getHighVolTime(void)
{
uint32_t time_us = 0;
time_us = TIM5CH1_CAPTURE_STA&0x3F; //定时器溢出的次数
if(time_us) time_us *= 65536; //溢出时间总和
time_us += TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
TIM5CH1_CAPTURE_STA = 0;//开启下一次捕获
return time_us;
}
#define TIM5_ARR 0xFFFF //最大65536us
#define TIM5_PSC 71 //1Mhz的计数频率,计数一次为1us
void TIM5_Cap_Init()
{
//定时器5通道1输入捕获配置,用来读取高电平时间
TIM_ICInitTypeDef TIM5_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //使能TIM5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0 清除之前设置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0 下拉
//初始化定时器5 TIM5
TIM_TimeBaseStructure.TIM_Period = TIM5_ARR; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = TIM5_PSC; //预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM5输入捕获参数
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 选择输入端 IC1映射到TI1上
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
//中断分组初始化
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断
TIM_Cmd(TIM5,ENABLE ); //使能定时器5
}
//定时器5中断服务程序
void TIM5_IRQHandler(void)
{
if((TIM5CH1_CAPTURE_STA&0x80)==0)//还未成功捕获
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
if(TIM5CH1_CAPTURE_STA&0x40)//已经捕获到高电平了
{
if((TIM5CH1_CAPTURE_STA&0x3F)==0x3F)//高电平太长了
{
TIM5CH1_CAPTURE_STA|=0x80;//标记成功捕获了一次
TIM5CH1_CAPTURE_VAL=0xFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
{
if(TIM5CH1_CAPTURE_STA&0x40) //捕获到一个下降沿
{
TIM5CH1_CAPTURE_STA|=0x80; //标记成功捕获到一次高电平脉宽
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0x40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
全部工程下载链接:
链接:https://pan.baidu.com/s/1r5pLaeCu5mieWKNu90GWDA
提取码:feng
跳转:可见光通信工程下载链接【提取码:feng】
如有错误,还望指正,谢谢!