最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

单片机无线通信模块开发与应用

来源:动视网 责编:小OO 时间:2025-10-02 15:31:33
文档

单片机无线通信模块开发与应用

单片机无线通信模块开发与应用(一)现在的单片机越来越便宜,使我们可以开始考虑如何将这些东西应用到生活中去,那么,让我们开始吧,从今天开始,我们要构造一个智能家居平台。其实这东西很多人都想过要做的,但想象是一回事,动起手来又觉得迷茫得很,因为,万事开头难嘛,这么着,让我来带路吧。先做硬件平台。电路图如下,注意,实际制作的时候电路改动了,继电器边上的电阻电容被省掉了,继电器直接接单片机。周末结束后我会再发第二贴,让那三个发光管先闪闪亮起来。我会顺便插上一些基本的C教程。还有,我正在做印刷电路板,做
推荐度:
导读单片机无线通信模块开发与应用(一)现在的单片机越来越便宜,使我们可以开始考虑如何将这些东西应用到生活中去,那么,让我们开始吧,从今天开始,我们要构造一个智能家居平台。其实这东西很多人都想过要做的,但想象是一回事,动起手来又觉得迷茫得很,因为,万事开头难嘛,这么着,让我来带路吧。先做硬件平台。电路图如下,注意,实际制作的时候电路改动了,继电器边上的电阻电容被省掉了,继电器直接接单片机。周末结束后我会再发第二贴,让那三个发光管先闪闪亮起来。我会顺便插上一些基本的C教程。还有,我正在做印刷电路板,做
单片机无线通信模块开发与应用(一)

现在的单片机越来越便宜,使我们可以开始考虑如何将这些东西应用到生活中去,那么,让我们开始吧,从今天开始,我们要构造一个智能家居平台。

其实这东西很多人都想过要做的,但想象是一回事,动起手来又觉得迷茫得很,因为,万事开头难嘛,这么着,让我来带路吧。

先做硬件平台。电路图如下,注意,实际制作的时候电路改动了,继电器边上的电阻电容被省掉了,继电器直接接单片机。

周末结束后我会再发第二贴,让那三个发光管先闪闪亮起来。我会顺便插上一些基本的C教程。还有,我正在做印刷电路板,做好会通知大家。

单片机无线通信模块开发与应用(一)

 (16.58 KB, 下载次数: 2)

单片机无线通信模块开发与应用(一)

 (14.41 KB, 下载次数: 4)

单片机无线通信模块开发与应用(一)

 (90.74 KB, 下载次数: 4)

单片机无线通信模块开发与应用(一)

 (86.66 KB, 下载次数: 5)

单片机无线通信模块开发与应用(一)

 (100.05 KB, 下载次数: 3)

单片机无线通信模块开发与应用(一)

 (100.05 KB, 下载次数: 3)

单片机无线通信模块开发与应用(一)

 (13.37 KB, 下载次数: 8)

单片机无线通信模块开发与应用(一)

单片机无线通信模块开发与应用(二)

看来有些朋友已经等不及了,只好提前发第二贴了。上一讲里面大家已经做好了硬件,那么这一讲,我们搞搞软件。

怎么才能让单片机工作起来呢?单片机是一个计算机系统,没有编程是不能工作的,既使用的硬件做得再漂亮也只是个艺术品。

本讲座将全部采用kiel C作为编程语言,因此你要先去下载一个kiel C编译器回来,下载地址如下:

kiel C 编译器已注册版: http://202.103.67.224/shaoshan/mu/mcu/kiel.rar  请解压在C:\下,否则可能不能使用,压缩时已带路径,解压不需设置路径.

C2051中文资料: http://202.103.67.224/shaoshan/mu/mcu/2051.rar   由于手头没有C2051的资料,用97C2051的代替,引脚是一样的,够用了.

下面的地址是一个写好了测试程序,发出约100us占空比50%的脉冲,已经编译出hex文件,烧进去就可以用:

 pWutGKkj.rar (12.31 KB, 下载次数: 982)

如果想自已修改和编译,这里讲下kiel C的使用方法:

1   打开工程     运行C:\\Keil\\UV2\\Uv2.exe,进入kiel C的界面,打开菜单project->open project,在弹出的文件窗口中找到源程序的解压目录,可以看到一个后辍为uv2的文件,双击就可以了。

2   编译程序     按F7或在菜单project中选build project就行了,我已经设置了编译输出hex文件,编译完后去源程序的目录中去找test.hex,并烧入单片机就可以了。

接下来,我讲讲程序的工作原理吧:

首先,程序的开头都要加载头文件,如下:

#include 

什么是头文件呢?C语言为了方便程序员,允许大家在一个文件中“copy”进另一个文件的内容,这样就可以使你的主文件里看起来干净多了。reg51.h文件内定义了单机的大部分SFR,P0 P1 P2 P3端口等东东,如果不加载这些文件,你在用这些东西的时候就得自已定义了。

接下来是定义自已的变量和引脚了。

sbit W_OUT=P3^4;

sbit W_IN=P3^5;

sbit LED1=P1^2;

sbit LED2=P1^4;

拿出其中一行来讲一下

sbit LED2=P1^4;

这一句的意思是,给P1.4一个别名,叫作LED2。用LED2这个名字显然比用P1.4直观多了。当然,还有别的原因使我不得不给他定义名字,因为在程序里是不允许出现"P1^4"这样的语句。呵呵。

如果没有别的东西要定义了,程序就可以开始了,C语言里是分语句块和函数的,用汇编的朋友在这里要买惯一下。函数相当于汇编里的子程序。最简单的函数由函数名,括号和花括号组成,举个例子:

delay(){

      函数内容这里就不写了,你可以一行都不写,也不会有问题的

}

以后我会告诉大家如何给函数传参数以及函数如何传回执行结果,也就是返回值,这里先不讲了。

函数名可以用字母开头的任何字符串取名,除了一些特殊符号。另外就是,有些字符串已经被系统用掉了,例如"sbit""unsigned"这样的串,这些也不能用来当函数名,顺便说一下,这些串叫作“保留字”。C语言里有一个函数名很特别: main函数。这个函数特别之处在于,程序就是从这里开始的,main的意义相当于汇编里面的ORG指令。

现在我们把main写出来吧

main(){

}

好了,现在整个程序变成了这个样子:

#include 

sbit W_OUT=P3^4;

sbit W_IN=P3^5;

sbit LED1=P1^2;

sbit LED2=P1^4;

main(){

}

这样的程序已经是一个完整的程序了,可以正确的编译,但是,现在还没任何语句指挥CPU去做点中,这样的话,我们继续努力:先给它来个死循环。

main(){

     while(1){

     }

}

while语句是这样用的:

while(条件){循环体}

当条件符合时,从上往下反复执行循环体,直到条件不满足。C语言里0为“否”,其它为“是”,所以while(1)的意思就是无条件的循环啦,上面说了,除了0,其它都为“是”,那就是说,我用while(2)也可以咯?当然,你完全可以这么用,但别的程序员一定会笑掉大牙。用“1”是人为约定的习惯用法,好比你在说话是绝不会跟人说“我去进食”一样。

有了循环,程序就能够不停的运转了,那么,我们该放到什么东西到这个循环里去呢?多了,看:

while(1){

     LED1=0;    //注意,LED1在上面已经定义为P1.2了,否则你用“LED1”时,编译器不认识的。

     LED1=1;

}

这可以让P1.2上接的小灯不停地一亮一灭,不过,哈哈,速度太快了,你看不出来哦。怎和办?加个延时吧,这个以后再讲。

咱们现在不是在做无线数传吗?回到正题上来,让程序做点事。

从原理图上可以看出,发射模块跨接在VCC与P3.4之间,当P3.4为高电平时1,模块不通电,为低电平0时通电,模块发射315M信号。这样,写点语句让它发送方波吧,程序从上面抄下来改改就行了:

while(1){

      W_OUT=0;//发射

      W_OUT=1;//停止

}

现在,把程序弄复杂点,以便能做更多的事:

unsigned char i=0;//定义一个变量,值域为0到255,无符号。后面的“=0”表示在程序开始前就设成0,好比汇编里的“DB”指令。

bit out;   //定义一个变量,只能表示0和1

bit rsv;   //定义一个变量,只能表示0和1

while(1){

      i++; //  i++指令好比汇编里的INC指令,变量增1。

             //i从零开如,每循环一次增1,到大于10的时候进入下面的语句块,小于等于10时不执行,直接跳到if(){}的花括号后面去了。

      if (i>10){  

           i=0;    //将i重新设成0

           out=!out;   //变量取反,out原来为1的话,执行完就为0,原来为0,执行完就为1了

      }

      W_OUT=out;   //将out的值传给引脚,以打开/关闭发射模块

      rsv=W_IN;       //取接收模块的输出,存在rsv变量中

      LED1=rsv;       //将输出值显示出来

      LED2=!rsv;      //将输出值取后后显示出来

}

好了,讲到这里,完整的程序就出来了,如下

//载入头文件,以便使用已定好的引脚寄存器等符号

#include 

//引脚定义部分

sbit W_OUT=P3^4;

sbit W_IN=P3^5;

sbit LED1=P1^2;

sbit LED2=P1^4;

//主函数

void main(){

bit rsv,out;

unsigned char i=0;

// P1=0xFF;          //这句是给初哥们看的,由于上电复位后所有端口本来就是高电平,所以这句是不必要的。但要注意,P1.0和P1.1没有上拉,所以这两个脚用万用表量是量不出来的。

while(1){

  if (i++>10){

   i=0;

   out=!out;

  }

  W_OUT=out;

  rsv=W_IN;

  LED1=rsv;

  LED2=!rsv;

}

}

上面的程序输入的方波频率极高,有几十K,如果想它慢一点,可以将

if (i++>10)

中间的10改成100或更大,越大越慢,但由于变量i是一字节型变量,最大只能到255,所以不要超过255。如果想超过255,请将  unsigned char i    那句改成  unsigned int i    这样,你最大可以设成65535了,不过无线模块的脉宽是受的,一般不能超过1ms,过宽会导至发射效率降低以及接收信号有误码,所以,如果看到信号不对时不要以为是电路坏了哦。

灯的闪烁频率较快时,要观察它有个方法,下面教你做个最简单的示波器,而且全免费的:拿着模块快速左右晃动,看出它的波形出来了吧?呵呵,一分钱成本都不要。在上面的例子中,if (i++>100)时能明显看出闪烁,if (i++>10)时就有点困难了,手要快才行。

单片机无线通信模块开发与应用(三)

这一讲我们来看看如何驱动外部设备。

其实上一讲写的小程序里已经暗示过大家了,只是没仔细讲解而已,这一讲就为新手们讲讲吧。

驱动外部设备,最简单的就是设备直接接单片机的引脚,复杂一点的要加个放大电路,例如三级管缓冲器什么的,也有的是加光偶隔离电路。我们暂时不搞那么“复杂”的东西,先来点最简单的吧。

先来看看单片机如何输入控制信号吧。

第二讲里的小例子中已经讲过如何接发光二极管,大家先把发光管接上去。一共3个发光管,负极分别接在P1.2 P1.3 P1.4上,正极接在一起,然后再串一个1K的电阻接VCC。

硬件弄好了,讲讲如何驱动:

由于发光管是接在VCC与引脚之间,那么,只有引脚为负逻辑的时候,发光管才会亮,单片机复位后,所有引脚都是正逻辑,因此,所有发光管都是关闭的,这样,我们要让它亮的话,要将电平先拉下来,写个小程序吧:

将第二讲里下载回的那个源程序包中的代码删了,粘贴以下几行:

#include

//引脚定义部分

sbit LED1=P1^2;

//主函数

void main(){

LED1=0;

}

写完后按F7就会编译,然后从源程序的目录中找到test.hex。将hex文件烧入2051芯片,插上实验板,打开电源,就可以看到灯亮了。

但是,灯只是亮着没有一点生气,我们让它闪动一下吧,这里我们会用到延时程序,新手们在这里可以学会如何调用函数,也就是汇编里所说的子程序:

#include

//引脚定义部分

sbit LED1=P1^2;

//延时函数

void delay(void){

unsigned int i;

for(i=0; i<60000; i++)    //将i<60000改成i<6000,闪烁会快10倍

  i=i;  //i=i这一行没任何意义,的目的只是为了增加每次循环消耗的时间

}

//主函数

void main(){

while(1){

  LED1=0;

  delay();

  LED1=1;

  delay();

}

}

按照前面说的方法编译程序,烧入芯片,通电,就可以看到灯一闪一闪了,改变delay()函数中for(i=0; i<60000; i++)这一行"i<"后面的数字可以改变闪烁的频率,数字越大越慢,但不要超过65535,因为i的类型是unsigned int 型,最大只能表示K。

下面给出一个流水灯的程序,大家试试吧:

//载入头文件,以便使用已定好的引脚寄存器等符号

#include 

//引脚定义部分

sbit LED1=P1^2;

sbit LED2=P1^3;

sbit LED3=P1^4;

//延时函数

void delay(void){

unsigned int i;

for(i=0; i<60000; i++)

  i=i;

}

//主函数

void main(){

unsigned char i=0;

while(1){

  LED1=LED2=LED3=1;//关闭所有

  if (i==0)

   LED1=0;  //i=1时打开第1个灯

  if (i==1)

   LED2=0;  //i=2时打开第2个灯

  if (i==2)

   LED3=0;  //i=2时打开第3个灯

  if (i==2)

   i=0; //3个灯全部亮过一次了,从头来过

  i++;

  delay();//延时

}

}

好了,现在打开电路图,电路图大家都存下了吧?在第一讲里有

从电路图上可以看到,继电器是直接接在VCC与P3.5之间的,既然上面的例子可以驱动小灯,那同样也可以驱动继电器咯:

#include 

//引脚定义部分

sbit J1=P3^5;

//延时函数

void delay(void){

unsigned int i;

for(i=0; i<60000; i++)    //将i<60000改成i<6000,闪烁会快10倍

  i=i;  //i=i这一行没任何意义,的目的只是为了增加每次循环消耗的时间

}

//主函数

void main(){

while(1){

  J1=0;

  delay();

  J1=1;

  delay();

}

}

看出来了吧,与前面说过的一个例子很像,不同的就是改变了引脚而已。运行程序后,可以听到继电器叭嗒叭嗒的不停响。

现在我们说说高级应用。

大家都知道继电器在吸合时要求的电流较大,之后只需要很小的电流维持,根据这一特性,我们可以节省大量的电力:

#include 

//引脚定义部分

sbit J1=P3^5;

//延时函数

void delay(unsigned int t){ //括号中为参数

unsigned int i;

for(i=0; i  i=i;  //i=i这一行没任何意义,的目的只是为了增加每次循环消耗的时间

}

//主函数

void main(){

unsigned int i,j;

J1=0;  //打开继电器

delay(3000); //机械是很慢的东西,所以程序运行到这里时,继电器还没吸合上呢,等会儿它吧。

//以下是节电程序,方法是利用继电器的惯性,在很短时时间停止给继电器送电,断电器不会弹开。

for(i=0; i<10; i++){

  if(i<3; i++){ //将时间分成10份,3份是送电,7份关闭,这样可节电70%,改变i的值可以改变节电效果,i越小越省电。

   J1=0;

  }else{

   J1=1;

  }

}

}

这一次我们学会了给函数传递参数了。改变调用延时函数时括号中的数字,就可以改变延时值。

用同样的方法,我们可以控制发光二极管的亮度,看下面的例子:

#include 

//引脚定义部分

sbit LED1=P1^2;

//延时函数

void delay(unsigned int t){ //括号中为参数

unsigned int i;

for(i=0; i  i=i;  //i=i这一行没任何意义,的目的只是为了增加每次循环消耗的时间

}

//主函数

void main(){

unsigned int i,j;

LED1=1;

j=0;

while(1){

  for(i=0; i<100; i++){

   if(i    LED1=0;

   }else{

    LED1=1;

   }

  }

  if (j++==100)

   j=0;

}

}

程序的原理就是,在while中每次循环一次,j值增大1,j值在for循环中决定了LED1打开的时间,因此j值就决定了LED1的亮度,整个程序的效果就是,LED1慢慢的亮起来。

烧程序试试看,运行----------怎么一下子就亮了?呵呵,别急,由于实在太快了,你看不出来灯是渐亮的。

不要怪我哦,我只是想从原理讲起,如果一下子把程序写完,很多人就会看不懂了。下面是完整的程序:

#include

//引脚定义部分

sbit LED1=P1^2;

//延时函数

void delay(unsigned int t){ //括号中为参数

unsigned int i;

for(i=0; i  i=i;  //i=i这一行没任何意义,的目的只是为了增加每次循环消耗的时间

}

//主函数

void main(){

unsigned int i,j;

LED1=1;

j=0;

while(1){

  for(i=0; i<100; i++){

   if(i    LED1=0;

   }else{

    LED1=1;

   }

  }

  if ((j++/600)==100)

   j=0;

}

}

看到没,只是改了一个地方:if ((j++/600)==100)

这样,时间被拉长了600倍。

新学C语言的可能看不懂这一行的意思,我分开写一下,这句话等效于:

j=j+1;

if (j/600 == 100)

.....

就这么简单。

最后还是要说一下,新学C语言一定要小心,在C语言里“=”和“==”是不一样的,“=”是赋值,“==”是比较,如果写错了就麻烦了。便例如:

x=1;

LED=1;

if (x==2)

LED=0;

这句执行后,LED是灭的,如果写成

x=0;

LED=1;

if (x=1)

LED=0;

那好,不管x的值是多少,LED都是亮的,因为,由于你写错了运算符,在if()中,x被改成了1,等效于:

x=1;

if (x)

好了,这一讲完了,继续上班。

单片机无线通信模块开发与应用(四)增加了通信程序

国庆放假了,大家一定都想好好玩玩吧?那这次就做个简单一点的东西,免得占用兄弟们太多的休假,不然我的罪可就大了.

这次要做的东西,说简单也真简单,说不简单,这是个相当重要的部件.既是想跟电脑连接,接口肯定少不了的,而目前最成熟可靠而又简单的电路要属MAX232了,一个芯片加4个小容便可搞定.电路图如下,是从论坛上另一位兄弟的贴子上复制下来的.做的时候千万不要把电容极性搞反了,图中的极性是正确的.接PC的时候注意,如果是用串行线连接PC和232板子,不能用交差线,用直连就可以了,有卖的.我是直接做到232插头的盒子里,所以不需要考虑这个问题.

 vrhlZjgn.rar (8.06 KB, 下载次数: 1200)

晶振为22.187时波特率115200,11.059时57600,在PC端改一下就可以了.

有兴趣的朋友要跟上哦,不然就来不及了.后续做的实验全部是很好玩的东西了,虽然离产品有一定差距,但在自已家里用已经很有实用价值了.

[此贴子已经被作者于2004-10-13 20:48:37编辑过]

 (4.25 KB, 下载次数: 34)

单片机无线通信模块开发与应用(三)

 (9.63 KB, 下载次数: 22)

单片机无线通信模块开发与应用(三)

 (13.38 KB, 下载次数: 21)

单片机无线通信模块开发与应用(三)

a1lWnJFt.zip

36.74 KB, 下载次数: 1182

单片机无线通信模块开发与应用(四)

单片机无线通信模块开发与应用(五)

好久没发贴了,这场病病得不轻啊,不过病早好了,这次延误是因为在北京接了个项目,而且正好是关于这套系统的应用,所以干脆就拖了一段时间.

说正题了.前面那么多贴子只是一些外围的制作和设计,但没有外围的建设怎么能做出好东西呢?呵呵,这次给大家发点正经东西,相信这就是大伙儿最关心的部分---通信协议,其实也不能称其为协议,只能叫做射频编码,为了便于理解起见才叫它通信协议的,大家心里清楚这点就行了,免得说我混淆视听.通信协议分成硬件层和软件层,硬件层,即数据的电信号表示方法,而软件层,指的是数据包的处理.由于软件层定义很广,且跟应用场合相关,不同的应用可能使用完全不同的协议,所以这里就只说说如何传输数据包吧.相信大家都有这能力进行下一步的扩展.我也会在今后的贴子里给出一些应用的实例,以供参考.

我看到论坛上有些朋友之前也做过无线模块的应用,却不成功,例如明浩提过他做的232无线模块,干扰很大,通信不能进行.为什么会这样呢?要解释这问题,先要说说无线模块的结构和特性:

发射:无线模块使用一个三级管进行射频发射,从说明书上可看到,当连续发送时间高于5毫秒时,发射效率会降低.

接收:超再生电路.超再生电路有一个特性,即在没有信号时会收到大量的白噪声,接收模块已经对该噪声进行了处理,白噪声被大幅度削弱了,但是,这并不是说噪声就完全消除了,事实上,当信号源停止发射后几毫秒,噪声会再次出现,也就是所谓的"零电平干扰根据说明书的提示,这段时间大约为5毫秒.

别外,说明书上也指出,信号发射的宽度不应小于0.08毫秒,占空比也不能太大,否则很容易受到干扰.

从上面的资料,我们可以很轻易地分析出干扰来源.

根据资料,我们可以得出一个大概的设计原则:

1.占空比有,我们人为到1:4之内.

2.发射时间小于3毫秒.

3.两次发射的间隔小于3毫秒.

4.正式发射信号前要使用前导信号,以消除"零电平干扰".

根据上面几点,我参考红外信号算法,写出了发送一字节的算法:

1.高低信号电平交替使用,与实际被发送数据的电平值无关,而发送宽度及两次发送的间隔宽度,与被发送数据的电平值相关,对应关系在后面作出描述.

2.以宽度为0.6毫秒的宽度表示位低电平.

3.以宽度为1.2毫秒的宽度表示位高电平.

4.以宽度为1.8毫秒的宽度表示数据正文的发送与结束.

以下给出流程:

1.从零电平开始,交替发送/停止宽度为0.6毫秒的信号,数量为单数个,最少要有2个,发送完后信号电平自然回到高电平,这里,我称该组信号为"前导信号用来清除"零电平干扰".前导信号的第一个信号很可能会丢失,但其设计目的本来就是用来丢失的,所以无须关心接收方实际收到的数量,该信号在接收方接收时只要收到一个即可.

2.发送一个宽脉冲,作为数据引导,指示下一个信号将是数据正文.由于有前导信号保护,该信号不会丢失.

3.发送数据正文的各个位,低位在前,从bit0开始,位的值为0时发送0.6毫秒信号,值为1时发送1.2毫秒脉冲.这里要再次说明,所谓"发送信号并不等于发送射频信号,关闭射频同样是发送信号.

4.发送一个宽脉冲,作为结束信号,表示数据发送完毕,脉冲结束后射频信号正好自然转为停止发送,即零电平.

上面的文字说明有点复杂,下面给出图示,图示中的字节数据值为十六进制数A6,图中高电平时为发送射频信号:

                         1 0  1 0 0 1   1 0

_________|-|_|-|_|-|___|--|_|--|_|-|__|--|_|---|_________

   A         B       C   D                   E    F

A: 无关信号,可能为任何电平值.此时数据还未开始发送,不关心其电平为何值.

B: 前导信号,交替发送/静默0.6毫秒.

C: 引导信号,静默1.8毫秒.

D: 一字节数据正文,用发送/静默0.6毫秒表示0,发送/静默0.6毫秒1.2毫秒表示1.

E: 结束信号,发送射频1.8毫秒.

F: 无关信号,可能为任何电平值.此时数据已经发送完毕,不关心其电平为何值.

看图是不是清楚多了?如果还不懂,那我也没办法了.

接下来是一个写好的例子,发送和接收例程都有,一次发送或接收24个字节定长数据包.程序使用了并行工作机制,发送和接收可同时进行,但由于发送与接收共用同一个内存块作缓冲区,所以应用时不能时调用,有兴趣的可以自已改改程序,使用的缓冲区,这样就可以同时发送和接收了,但我个人认为意义不大,因为收到的数据就是自已发的数据,没什么实际用处.晶振采用22.1184M,串口通信速率115200bps如果使用11.059M,232串口通信速率要改成57600.

MCU端源代码下载:http://202.103.67.224/shaoshan/mu/mcu/rftest1.rar

PC端源代码下载:http://202.103.67.224/shaoshan/mu/mcu/scommtest.rar

PC端代码默认端口是COM4,自已改改吧,改成你要的就行了,具体怎么改,去上一讲里面找.

由于单片机的内存,数据包不能做得太大,同时,长数据包比短数据包的受干扰机率更大,所以,24字节是一个比较实用的值.经测试,发送接收全部不用天线,发射电压为5V时,传输距离5米,如果加上25公分天线,传个几十米不成问题,想要更远则要提高发射电压了,电压上限为12V,理论上应该有300米,由于我自已的应用场合是家用,所以没测试过,不知道实际有多远.

使用例子的时候注意一下,测试程序main()的发送与接收是用条件编译分开的,下面的代码中已有说明.发送与接收要单独编译和烧片,要两套硬件才能完成测试.在正式应用中,应注意一点:收发程序都并非退出后就有数据的,一定要重复调用,直到满足特定条件.并且,每两次调用的时间间隔不能超过50微秒,否则会丢失数据.为什么要将程序写成这样?是因为,写成这样子,我们就可以在发送和接发数据的同时作一些别的事情,例如键盘扫描,红外发送接收,LED数码管驱动等.

#if 1  //测试时,发送方写#if 1   接收改成   #if 0

   //正式应用的话,请将程序插入你自已的应用中,收发不能同时执行.

   //调用格式:

   //  while(w_send_step < 6 + W_BUF_LEN * 8){

   //   w_send();

   //   //这里可写些别的代码,例如键盘扫描,LED驱动等,但时间不能超过50uS

   //  }

   //  while(w_recv_step == 3 + W_BUF_LEN * 8)

   //   w_recv();

   //   //这里可写些别的代码,例如键盘扫描,LED驱动等,但时间不能超过50uS

   //  }

  if(send_over){

   for(i=0; i    w_buf = out_data++;

   START_SEND();

  }

  w_send();

#else

  w_recv();

  if(recv_over){

   for(i=0; i<4; i++)

    serial_out(w_buf);

   START_RECV();

  }

#endif

测试应注意,例子中用到了串口调试,用于显示接收到的数据,所以得做一个上一节所讲的串口通信线才行,不然看不到效果.再次说明,晶振采用22.1184M,串口通信速率115200bps如果使用11.059M,232串口通信速率要改成57600.

另外,每次接收到的数据只输出了头4个,不要以为是每次只收到了4个字节,因为输出例程没有使用并行工作机制,一次输出24个字节的数据会占用大量时间,使射频接收程序错过数据接收时机,造成数据包丢失.事实上,以程序中用到的115200bps为例,每次最多只能向串口输出10个左右数据.后面的数据显示出来意义也不大,只要头几个字节接收正常,后面的数据一般都不会有问题,如果你愿意,可以在数据包内再加上校验和.

例子中的引脚定义原来的设计有些不同,我原来的设计是P3.5接接收模块,后来改成P3.2了,大家可以根据自已实际的接法改一下程序,也可以直接改一下接线,由于程序工作于查询模式而不是中断方式,所以接哪个脚都可以.其它引脚和第一讲里的图上所画的相同.引脚都在程序的开头定义,想改的话自已改改吧.代码如下:

sbit LED1 = P1^2;

sbit LED2 = P1^4;

//sbit LED1 = P3^6;

//sbit LED2 = P3^7;

sbit W_IN = P3^2; //接收模块所连引脚

sbit W_OUT = P3^4; //发射模块所连引脚

单片机无线通信模块开发与应用(六)

上一讲有没有人做成功啊?由于数据是单收单发的,而且是反复发送,应该是很容易做的.

这次要讲的可就复杂点了.单收单发对于我们的这系统来说是没有意义的,因为我的的目的是双向通信.那么这次就来讲讲双向通信吧.

其实我程序已经写完大半了,但这次调试突然发现一些问题,所以只好半路发贴,因为如果一步到位完成命令执行部分,部分因"某此"原因不成功的朋友一定会死得吐血,因为那个问题真的不好发现,当你的通信不能成功时,你完全不知道问题会出在哪个环节中.这个贴子既能给水平高点的朋友热热身,又能给那些初学者们一个缓冲的余地.程序中的命令处理部分已经写好函数了,只是还没填内容,对于C语言熟的朋友来说,自已改改是很容易做到的.

先不说上面提到的"某些原因"到底是什么,等会儿看到合适的地方自然就知道了.

继续继承程序员优良作风---抄,这次和序是在上次的基础上增改的,前面几讲已经讲了大量的C语言知识了,所以这次不会再讲跟C语言基础知道有关的东西了.

我看先来看看要实现的功能是什么吧:

两套系统,一个是主控端,通过串口跟PC相连,另一个是节点,暂时不接任何设备,下一讲会接电器设备.

主控端端的工作流程是这样设计的:

没有任何任务时运行射频接收程序和串口指令,一旦接收到射频数据包,则马上发给串口传到PC主控台上;一旦接收到串口指令,则停止射频接收程序,并转入执行上位机指令.指令在附带的PC端程序里输入,为了方便,这次将界面改了一下,放了两个命令输入栏,不用再删来删去了.

目前定义了两个串口指令,"f"和"s

f指令:将指定内容填入射频缓冲区,然后从射频发射模块发射走.

格式: f 长度字节1 字节2 字节3 ....  注意,后面的字节数一定要大于等于前面指定的长度.

s指令:读取射频缓冲区内容,如果无内容,则返回0x20

由于上面提到的"某些原因",s指令暂时没用,因为我在上面加了一段程序,在射频接收端接收到数据包时立刻就传给串口了,再用s指令读的时候已经空了.

下面是主控端的main()函数框架,内容我已经删掉了,这样大家可以很清楚的看到程序的流程.

void main(){

sys_init();

while(1){

  unsigned char scmd;

  sys_thread();//接收程序和将来要增加的键盘扫描测温测光等等所有程序都在这里执行

  if(recv_over){

   //输出接收到的数据到串口

  }

  if(RI && wait_serial(&scmd, 1, 0) == 0){//串口命令处理程序

   switch(scmd){

    case 'f':{  //fill w_buf and send

     //执行f指令

    }

    case 'g':{  //get w_buf

     //执行g指令

    }

   }

   START_RECV();

  }

}

}

由于程序比较臃肿,我去掉了并行收发,以便于调试,这不能不说是一个遗憾.

节点端的流程比较简单:

没事的时候等待射频数据,一旦接收到数据,重新用射频发出.这次试验只用了两个节点,所以地址之类的东西都不管它了,我全没处理,其实程序都写了,不过在流程中把它们强行中断了而已,水平好的一看就能看出来那些代码在哪里.这些代码将在下一讲复活过来.

下面是节点的main(),代码较少,我就全贴出来了,结构已经非常清析了.

void main(){

sys_init();

while(1){

  unsigned char ret;

  package_t *package = (package_t*)w_buf;

  while(!recv_over)//等待数据包

   sys_thread();

  //sleep(3000);

  send_package(package);

  START_RECV();//数据包处理完成,重新开启接收程序

  continue;//这4句是临时加的,直接把数据包原封不动发回去,序到这里就跳回while的入口,后面的代码全都没用了,不用看了.当然,看看也行.

  ret = recv_package((package_t*)w_buf);//处理接收到的数据包,包括检查数据包的正确性

  if(ret){

   package_t *package = (package_t*)w_buf;

   START_RECV();//数据包处理完成,重新开启接收程序

   package->head.target_id = 0xaa;

   send_package(package);

   START_RECV();//数据包处理完成,重新开启接收程序

  }else{

   ret = command_process((package_t*)w_buf);

  }

}

}

那么整体思路是这样的:从PC的控制台上发送指令如:f 5 1 1 1 1 1    数据一发出,立刻会被节点转发回来,于是可以在制台上看到显示出一串数据.由于数据已被打过包并作了校验和处理,所以收到的内容中前几个字节和原来的不一样了,但第5个字节之后的是一样的,你可以多发送几个字节试试:

f 10 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1     注意,这里全是用十六进制,10代表的是16个字节而不是10个,所以后面跟的数据要16个以上,超出的部分被忽略.

现在让我们来试试吧:

1.下载单片机源码

 wNTOApHC.rar (49.51 KB, 下载次数: 1061)

片子烧好,插入对应的板中,主控板连上串口线,可以试试了.注意,你的主控板千万不能是那个用电容降压的电源,不然我不知道会不会把你的电脑打坏,我没试过,你想试试的话,请把结果告诉我.做个变压器电源或开关电源吧.

先看两块板子,两块板上的接收指令灯都不停的闪动,说明接收程序正在工作.没闪就是有问题了,要检查哪里错了.

发送上面说的命令f 5 1 1 1 1 1  如果马上在界面里看到有一串数据显示,说明成功了,否则看下面几种情况:

1.点发送后一点反应都没有.

检查你输入的命令是否正确,命令字是否写对,大小写是不是正确,必须小写;字节数是否正确,数目不得少于前面输入的长度指示.检查你的串口线是不是好的.

2.点发送后,发射指示灯没闪,但能收到回应"0"

说明你单片机有问题.正常的情况下,点击发送后单片机的发射指示会闪一下,然后返回"0"到PC控制台,表示发送成功.

3.主控的发射指示闪了一下,但节点的接收灯没有任何"反应".

正常情况下,节点的灯在收信时后与平时表现不一样,不会无规则的乱闪,而且比较亮.如果还和平时一样乱闪,说明主控的发射模块或节点的接收模块坏了或接收不正确.这里顺便重提一下,我将接收信号线改到中断0去了,如果你的硬件还是照着第一讲里的图里接的线,那一定要改线或改程序里的引脚定义:

我现在用的

sbit W_IN = P3^2;

改成原来的

sbit W_IN = P3^5;

4.节点的接收灯有反应,但除此之外没有任何别的动静.

这可能是由于干扰所引起的,如果没什么好办法,教你一招:在程序里将下面一行

  #define HEAD_CODE 100 //前导码个数

的数值改大点,改到1000应该就行了,还不行就没办法了.这个值是引导码的长度.太长会占用太多带宽资源,改到够用就行了.

5.节点的接收灯有反应,之后发射灯亮了一下,但主控端没收到任何数据.

这下子你麻烦了,我也碰到这问题了,这个问题也就是我前面提到的"某些问题"了.这是你买到的接收模块不行.机制是这样的:当发射模块发射时,本机上的接收模块也在接收,由于太近,信号很强,于是接收模块内的电路自动调整增益(放大倍数)到一个合理的值,但这个值对于接收远处的节点端发来的信号来说,显然是不合理的,这样,节点发来的信号就完全收不到了.什么时候能收到呢?要等一会儿,我称这个时间值为"恢复时间".恢复时间是多长呢?问题出在这儿了,每个模块的恢复时间是不同的,有的模块只需要几百微秒,有的模块却能花上几秒.如果买到这程模块,除了换,没别的办法.当然试验还是有办法做下去,把(4)中间提到的数值再改大点,改成几万应该就行了,最大可改成65532.哈哈.或者把节点main()里的那行

  //sleep(3000);

前面的"//"去掉,这是延时程序,3000表示300毫秒,少了再加,加到60000都可以(6秒),不知道不知道加到这么大的话,就算成功了又有什么意义.如果这都还不行,那就是主控板上的接收模块是坏的.

这一讲暂时到这里了,如果还有什么新情况,我随时修改,大家留意一下.这一讲的实验会碰到比较多的麻烦事,这也是我为什么要临时抽出这些内容单独作为一贴的原因,有问题随时问,我一般都在线,但你得把现象尽可能的描述清楚.

最后再申明一次,本次的实验有点难度,希望大家在碰到问题的时候一定要表现出超人的耐心.毕竟是个实验,比起做项目时碰到的问题,这实在不算什么.

单片机无线通信模块开发与应用(七)

想想也有两个月没发贴了吧。如果大家等得不耐烦了,我个人认为这是很正常的。大家一定很奇怪我这两个月干什么去了,回答是--私事。

少说话,多做事,这次我将代码全部重写了一遍,性能提高了不少。代码仍采用前/后台伪多任务方式,以避开相对复杂的RTOS。事实上不使用RTOS的原因并不是为了照顾新手,如果使用RTOS,代码量会更小,程序也更精简及有条理,但占用资源非常大,所以没使用它。

代码修正如下:

1.收发缓冲区,各16字节。如果你用S52或C52,可以定义得更大些。

2.可收发变长数据包,而非原来的定长数据包。

2.增加碰撞检测,实现“抢占式”收发而非查询方式。由于无线模块本身的,碰撞检测为非完全检测,只能在发送前检测是否有其它单元在使用信道,在发送过程中受到的碰撞干扰是无法检测到的.经测试,效果还行.

3.增加LCD驱动.LCD模块型号为SO128FPD-12ASWE,这是一款SPI串行接口的LCD,只需要5根数据线,白色背光,无需负电,40元买的。如果喜欢蓝色背光,可选SO128FPD-12ASBE。如果买不到这种模块,可以用3310的代替,硬件几乎一样,只是接脚和指令不同,稍改一下代码就行了。

4.增强232调试接口。232接口是我在做LCD驱动时为了调试方便而做的,由于有了LCD显示,因此实际应用中并不需要那部分代码。

下面说说源代码的使用方法:

为了方便讲述,我将main()函数的主要部分贴了出来。

void main(){

     sys_init();

     LED1 = 0;

     LED2 = 1;

     lcd_reset();

     lcd_power_on();

     lcd_self_test();

     while(1){

          if (jiffies - last_jiffies >= 20){//每次中断为50us,所以要20次才为1ms

                //毫秒事件,每毫秒进入该语句块一次.需要计时器的,可以在这里进行计时器处理

                last_jiffies = jiffies;

                heart_beat_timer--;

                if (send_delay)

                     send_delay--;

          }

          if (heart_beat_timer == 0){

                heart_beat_timer = HEART_BEAT_INTERVAL;

                LED1 = ~LED1;

                LED2 = ~LED2;

          }

#if 1

          if (send_stat == 0){

                //碰撞测试

                START_SEND(4);

                send_buf[1]++;

                lcd_out_string("sending:");

                printhex(send_buf[0]);

                printhex(send_buf[1]);

          }

#endif

          wirless();

          if (send_stat == SEND_FAILED){

                lcd_out_string("err:");

                printhex(send_byte_p);

                printhex(8 - send_bit_p);

                OutChar('\\n');

                send_stat = 0;

          }else if (send_stat == SEND_SUCCESS){

                send_stat = 0;

                lcd_out_string("OK");

                OutChar('\\n');

          }

          if (recv_stat == RECV_SUCCESS){

#if 0

                unsigned int i;

                for(i=0; i                     serial_out(recv_buf);     //输出接收到的字符

#else

                lcd_out_string("data recived:");

                printhex(recv_buf[0]);

                printhex(recv_buf[1]);

                OutChar('\\n');

#endif

                recv_stat = 0;

          }

#if 0

          if (RI){

                232 接口程序

                .....

          }

#endif

     }

}

1.毫秒事件

     顾名思议,每毫秒进入条件一次,用法是定义一个全局变量,并在花括号内进行加/减。例如想要定时1秒然后做一件事,可以这样做:

          unsigned int second_event = 1000;

          ...

          main(){

          ...

                if (jiffies - last_jiffies >= 20){//每次中断为50us,所以要20次才为1ms

                ...

                     if (second_event)     ----- 在这里处理计时器

                          second_event--;

                }

                ...

                //在这里响应事件

                if(second_event == 0){

                     ...

                }

                ...

          }

     源代码中已有现成的例子:

          #define HEART_BEAT_INTERVAL 100          //心跳间隔 X / 1000    秒

          unsigned int heart_beat_timer = HEART_BEAT_INTERVAL;

          注意,上面的变量是全局变量,不要定义成局部变量了

          ...

          if (jiffies - last_jiffies >= 20){//每次中断为50us,所以要20次才为1ms

                //毫秒事件,每毫秒进入该语句块一次.需要计时器的,可以在这里进行计时器处理

                last_jiffies = jiffies;

                heart_beat_timer--;

                if (send_delay)

                     send_delay--;

          }

          if (heart_beat_timer == 0){

                heart_beat_timer = HEART_BEAT_INTERVAL;

                LED1 = ~LED1;

                LED2 = ~LED2;

          }

     该段代码的意思是每100毫秒将LED1和LED2变换一次状态。因为在程序调式过程中总是发生死机,所以写了这段代码,用来告诉我单片机还活着。

2.向无线端口发送数据 START_SEND(byts)

     将要发送的数据填充到发送缓冲区中,从w_buf[1]开始填充,w_buf[0]是用来保存发送缓冲区长度的,不要用。

     用法如下:

          首先要检查发送程序是否空闲,否则将会中断正在进行的发送过程。检测方法是读send_stat的值。当send_stat值为SEND_PROGRESSING时说明正在发送上一个数据包。

          接下来调用START_SEND()宏,将发送的字节数放在参数中。

          调用wirless()。

          读取send_stat的值,发送完成时为SEND_SUCCESS,正在发送时为SEND_PROGRESSING,失败时为SEND_FAILED。如果值为SEND_PROGRESSING,须继续调用wirless(),直到值不为SEND_PROGRESSING。

     下面的代码取自源代码中,基本上能说明用法:

          while(1){

          ...

                if (send_stat == 0){

                     START_SEND(4);  ----- 发送4个字节

                     send_buf[1]++;  ----- 填充缓冲区,这里图方便,只填充了一个字节

                     ...

                }

                wirless();

                if (send_stat == SEND_FAILED){

                     ...             ----- 失败处理

                     send_stat = 0;  ----- 请将这句代码放在语句块的最后,这是写多任务程序时留下的“良好”习惯

                }else if (send_stat == SEND_SUCCESS){

                     ...             ----- 成功处理

                     send_stat = 0;

                }

          ...

          }

          如果将main()函数清空,只填入以上代码,单片机将会不停的向外发送数据,由于作了碰撞检测,如果另一台单片机也在发送,则看谁先抢到信道。但由于硬件本身的,有时仍有发生碰撞的可能,这要视乎你所用无线模块的质量如何。

3.读取接收缓冲区

          对接收缓冲区的处理比发送要简单得多,接收程序在开机后就一直不停运行,直到收到一个完整的数据包或发送程序正在运行。这些都已由系统自身完成,只需读取recv_stat即可。当recv_stat值为RECV_SUCCESS时数据包接收完成,此时可取出且应该立刻取出数据,否则接收程序将停止工作,以防止下一个数据包冲毁接收缓冲区的内容。如果强行将recv_stat置为0,则数据可保存到下一个数据包的第一个字节完全到达。与发送相同,send_buf[0]也被系统占用,用于保存数据包长度。

          while(1){

          ...

                if (recv_stat == RECV_SUCCESS){

                     ...             ----- 处理收到的数据

                     recv_stat = 0;  ----- 请将这句代码放在语句块的最后,这是写多任务程序时留下的“良好”习惯

                }

          }

4.LCD控制

void lcd_reset();    ---- 复位LCD,基本上没什么用处

void lcd_power_on();  ---- LCD上电

void lcd_self_test();  ---- LCD自检。该函数是我做着好玩的

void lcd_clear();        ----- 清屏,光标回到左上角

void lcd_out_string(unsigned char *str);    -----显示字符串,例如lcd_out_string("hello\\n"),只能显示字符表中的字符,其它字符只能看到黑色的方块。该函数已处理了滚屏,换行和回车

void OutChar(unsigned char c);              ------显示一个字符

void SetLine(unsigned char line);          ------设置屏幕顶部的行号,具体用途叙述麻烦,自已试试就知道了。

void SetXY(unsigned char x, unsigned char y);     -----设置光标位置,当发生滚屏后,Y(行)的位置在再与视觉上的Y相对应,而是等于输入的Y加上lcd_line的值

编码方式说明:

以前所用的编码方式是用半个宽窄脉冲来表示高低电平,这种方式很省带宽,同样的码率能传输更多的数据。但这方法有个毛病,当信号较弱时信号发生移位,数据错乱。

这次用的方法比原来的慢些,是用一个完整的周期来表示一个电平值,这样,当信号差或受干扰时,最多只是丢失数据,而不会数据错乱,这可以免去校验和之类费时费力的检测,总的来的算是利大于弊.

由于程序整体上改进较多,实际传输能力与原来相差不大。

下面的图是波形,得粘贴到写字板里看。不知道为何贴上来就变成这样了。

_________________|-|_|-|_|-|_|-|_|-|___|---|_|-|__|--|________

    a             ________ b _______ __ C__  D  _ E _

a: 无关信号

b: 前导信号

c: 引导信号

d: 0

e: 1

下载:  OtxHFf1U.rar (78.2 KB, 下载次数: 1016)

做好硬件,接好LCD,将HEX烧到2051中就可以了。硬件有点小改动,P1.3-P1.7用于连接LCD,所以接在这几个脚上的LED得去掉,将LED改接到P1.1和P1.2上。不接也可以。其它不变

没有LCD的也可以做实验,但没法看效果。可以自已动动手改改代码,将显示到LCD的内容发到串口上去,调用serial_out()写一个serial_outstr()就行了。手头没相机,所以实物照片改天放上,很漂亮。工作电压5V,传输距离10-20米。如果觉得距离不够,加两个三级管,发射模块用12V,距离可以超过100米。

做两套以上硬件,软件和硬件都完全相同。做好后接通电源,可以看到LCD上不断滚动,显示当前收到的数据和发送的数据摘要,多套硬件同时工作时基本上不发生碰撞,但发包速度会随硬件数量而下降,相信这也是理所当然的。

文档

单片机无线通信模块开发与应用

单片机无线通信模块开发与应用(一)现在的单片机越来越便宜,使我们可以开始考虑如何将这些东西应用到生活中去,那么,让我们开始吧,从今天开始,我们要构造一个智能家居平台。其实这东西很多人都想过要做的,但想象是一回事,动起手来又觉得迷茫得很,因为,万事开头难嘛,这么着,让我来带路吧。先做硬件平台。电路图如下,注意,实际制作的时候电路改动了,继电器边上的电阻电容被省掉了,继电器直接接单片机。周末结束后我会再发第二贴,让那三个发光管先闪闪亮起来。我会顺便插上一些基本的C教程。还有,我正在做印刷电路板,做
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top