admin管理员组

文章数量:1579989

  
  
目前课设已完成,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/s/1r5pLaeCu5mieWKNu90GWDA
提取码:feng
跳转:可见光通信工程下载链接【提取码:feng】

  
【发射端main.c】

#include "stm32f10x.h"
#include "delay.h"
#include "sensor.h"
#include "usart.h"
#include "usmart.h"

/*
   程序整体思路:
	   ① 把每个要发射的数据转成二进制,再将二进制的每一位逐1发射出去;
		 ② 需要3个信号表示一个数据,分别用高电平持续的不同时间表示不同
		   信号:起始信号、0信号、1信号,分别对应p1,p2,p3;
		 ③ 默认状态LED是亮的,起始信号发射的时候,将LED引脚置高,并延迟
			 p1的时间,再进行翻转,两次这样的p1高电平时间即代表起始信号;
		 ④ 数据信号需要紧跟着起始信号,一次起始信号之后就是16位的0、1数
		   据信号,对于0信号,则是高电平持续p2时间,再翻转,对于1信号,
			 则是高电平持续p3时间;
		 ⑤ 因此只需要设置好每个信号之间的时间参数,使用滴答定时器计数的
		   延迟函数达到持续的效果,然后只要判断即将发射的数据的各个位,
			 即可将一个数据发射出去。
			 
 *	2020.8.30 —— by afeng
    1、优化程序,优化部分变量,让程序运行的更快,尽量与接收端同步。

 *	2020.8.25 —— by afeng
    1、优化发射端串口助手,使其能够实时自动刷新电脑可用的串口,当该串口掉线,则会自动关闭连接。
		2、优化串口助手的显示区域部分,通过设置光标的位置使得文字可以从上至下显示。

 *	2020.8.23 —— by afeng
    1、加入连续发射多个数据的函数,并支持不同起始数据和结束数据,然后
		   注册到USMART调试组件,以达到从串口助手来控制发射的数据。
		2、结合了自己的基于QT的串口助手,把本工程的USMART组件与该调试助手
		   结合起来使用,更方便调参。

 *	2020.8.20 —— by afeng
    1、开始使用USMART调试组件,可以在串口助手通过函数带参数的方式在线修改
		   各个参数,在后面不同距离下改变参数有着极大的便利性,免去了重复修改
			 、编译和烧录代码的繁琐过程。
	  2、解决了二进制数据位错误的BUG,是由于自己不够熟练位操作引起的,最后
		   决定使用数字1的移位操作来提取出每个数据的各个二进制位。
		3、实现了基于滴答定时器的长时间延迟,由于该定时器只有16位,基于ST库写
		   的延迟函数一次最大不能超过2s,经过额外编程实现能够一次延迟10s以上。
		4、剔除了每次数据都需要结束信号的方案,使得发射时间进一步提升,其实每
		   个数据的起始信号也可以作为每个数据的区分了。
			 
 *	2020.8.19 —— by afeng
    1、使用函数将十进制转二进制,并能够将每一个位通过亮-暗表示0-1将二进制
		   各个位成功发射出去,测得最小时间参数可以达到300us左右,但是由于接收
			 端的限制以及距离的影响,这个参数需要每次调整。
*/


//连续发射数据
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_

本文标签: 可见光通信系统课程设计代码