打印
[51单片机]

从业将近十年!手把手教你单片机程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
301
赞一个

使用特权

评论回复
302
loliweive| | 2014-5-15 21:13 | 只看该作者
支持楼主的共享精神,在这里学到了不少。。。建议单片机爱好者们都可以进来学习学习

使用特权

评论回复
303
loliweive| | 2014-5-16 09:07 | 只看该作者
听闻楼主有远距离多机通信编程的经历,希望楼主可以讲一讲RS485总线在工程中的应用实例,谢谢!

使用特权

评论回复
304
jianhong_wu|  楼主 | 2014-5-16 10:53 | 只看该作者
loliweive 发表于 2014-5-16 09:07
听闻楼主有远距离多机通信编程的经历,希望楼主可以讲一讲RS485总线在工程中的应用实例,谢谢! ...

485通讯跟普通串口通讯方式有一个最大的区别是,485通讯要多增加一个IO口来控制数据流的方向,输出低电平时表示接受数据的状态,输出高电平时表示发送数据的状态。485的通讯协议一般都是用主机对从机广播呼叫的模式。即平时所有从机处于接受数据的状态(流控IO口处于低电平),主机发送呼叫某个从机的地址,相应的从机收到数据后,马上切换到发送数据的状态(流控IO口处于高电平),然后往主机返回对应的数据,从机对主机发送完数据之后,从机又马上切换到接受数据的状态(流控IO口处于低电平)。从机在发送数据时,在程序上有一个要特别注意的细节:
void eusart_send(unsigned char ucSendData)
{

  ES = 0; //关串口中断
  TI = 0; //清零串口发送完成中断请求标志
  rede_dr=1;     //流控IO,切换至发送数据的状态,准备发送数据,  
  delay_short(4);  //此处最关键,必须插入几个空延时,等待芯片切换到发送数据的状态

  SBUF =ucSendData; //发送一个字节


  delay_short(400);  

  rede_dr=0;   //流控IO,发送完数据之后,务必马上把从机切换回接收数据的状态,把总线释放

  TI = 0; //清零串口发送完成中断请求标志
  ES = 1; //允许串口中断

}

使用特权

评论回复
305
loliweive| | 2014-5-16 10:57 | 只看该作者
jianhong_wu 发表于 2014-5-16 10:53
485通讯跟普通串口通讯方式有一个最大的区别是,485通讯要多增加一个IO口来控制数据流的方向,输出低电平 ...

嗯,不错,好好研究研究~~~谢谢楼主!

使用特权

评论回复
306
cjseng| | 2014-5-16 11:31 | 只看该作者
jianhong_wu 发表于 2014-5-16 10:53
485通讯跟普通串口通讯方式有一个最大的区别是,485通讯要多增加一个IO口来控制数据流的方向,输出低电平 ...

SBUF =ucSendData; //发送一个字节
delay_short(400);  

这种做法实在是令人无语啊!!!不用中断方式不是不可以,但为什么要用延时?你能确定delay_short(400); 刚好够发送一个字节?换用不同晶振还要调整这个延时时间,换用不同厂家的单片机可能也要调整,这样不累啊?

你不会判断TI啊?if(TI==1)表示一个字节表示一个字节发送结束,这么好的标志位不用,竟然用延时?

另外,485通讯要用一个IO来控制收发切换,这是不错,但是232转485模块,可没有多余的IO来控制,照样可以收发切换的。
实际上我做的485通讯,经常是不用单独的IO来控制收发切换的,我的程序也不对收发进行控制,有硬件自动完成。用好了485芯片,是有办法进行自动收发切换的。

使用特权

评论回复
307
zhjyuanji| | 2014-5-16 11:46 | 只看该作者
不明白 第40节 uiRcSize的作用

使用特权

评论回复
308
jianhong_wu|  楼主 | 2014-5-16 12:22 | 只看该作者
本帖最后由 jianhong_wu 于 2014-5-16 12:33 编辑
cjseng 发表于 2014-5-16 11:31
SBUF =ucSendData; //发送一个字节
delay_short(400);  

(1)如果你不想用delay_short(400)延时,我还有另外一种延时方式,请参考我第四十三节:通过串口用计数延时方式发送一串数据。但是大部分的串口发送数据项目,我还是喜欢直接用delay_short(400)延时 ,感觉简单好用。
(2)delay_short(400)延时的时间根据实际项目的晶振来调整一下就可以了,真不累。
(3)为什么不用 TI标志位?根据我个人的经验,我觉得PIC单片机和51单片机的TI都不是很可靠,即使判断了TI,还是要加点延时,才能保证发送一串数据时不丢失。但是, 在这里,我不得不夸一下STM32单片机,我用STM32单片机的时候,是直接判断标志位的,不用加延时,STM32的标志位还是比较可靠。
(4)至于232转485模块不用IO口控制收发切换的问题,它内部是如何自动切换的,这个秘密我没有深入研究过,如果你有时间,希望跟大家分享一下。

使用特权

评论回复
309
jianhong_wu|  楼主 | 2014-5-16 12:36 | 只看该作者
zhjyuanji 发表于 2014-5-16 11:46
不明白 第40节 uiRcSize的作用

uiRcSize代表当前接受到的一串数据中,除去数据头和数据尾,中间的有效数据有多少个字节。这个数据很关键,有了个数据,就可以累加计算最后一个字节的校验和。有了这个数据,就可以方便提取中间有效数据。

使用特权

评论回复
310
cjseng| | 2014-5-16 13:19 | 只看该作者
jianhong_wu 发表于 2014-5-16 12:22
(1)如果你不想用delay_short(400)延时,我还有另外一种延时方式,请参考我第四十三节:通过串口用计数延 ...

判断TI标志位会出错,最主要的原因是收发两端的波特率有误差,略加延时即可解决,比如增加一个位的发送时间即可,在你这里,基本上只要delay(2)就可以了,不需要400。这种现象经常出现在连续大数据量连续收发。
485自动切换收发,最简单的就是将TX、/RE、DE三个引脚直接相连。这不是秘密,是常用的方法。

使用特权

评论回复
311
zhjyuanji| | 2014-5-16 13:19 | 只看该作者
                         uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
                                         uiRcSize=uiRcSize<<8;
                                         uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
                                                                 
                                         ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验

又是赋值又是作为序号使用的 看不懂

使用特权

评论回复
312
jianhong_wu|  楼主 | 2014-5-16 13:45 | 只看该作者
cjseng 发表于 2014-5-16 13:19
判断TI标志位会出错,最主要的原因是收发两端的波特率有误差,略加延时即可解决,比如增加一个位的发送时 ...

(1)我这里的delay(400)已经包含了发送数据时候的时间。你说的方法先判断TI,再delay(2)可能也没问题。我认为差距不大的,而且两种办法,我认为直接delay(400)会更加简洁一些。
(2)TX、/RE、DE三个引脚直接相连,我没有安全感。因为在切换发送数据状态时就没有延时了,所以我还是会**多用一个IO口控制切换。

使用特权

评论回复
评论
jackhwang 2016-5-17 22:19 回复TA
不好意思,打错字了。是“我记得有个项目,在把485从接收切换到发送状态,接着就发送数据。测试发现一贞数据有时第一个数据会出错,后在程序... 
jackhwang 2016-5-17 22:17 回复TA
我记得有个项目,在把485从接收切换到发送状态,接着就发送数据。测试发现一贞数据有时第一个数据会出来,后在程序加延时就没有出现这种情况。 
313
jianhong_wu|  楼主 | 2014-5-16 13:48 | 只看该作者
zhjyuanji 发表于 2014-5-16 13:19
uiRcSize=ucRcregBuf;   //数据长度  两个字节
                                ...

这个只能多看多想多动手实验,最后靠个人悟性了,我也没法再深入解答了,只能回答到这里。

使用特权

评论回复
314
小鱼儿1045| | 2014-5-16 15:38 | 只看该作者
又是你?上次不是被删帖了?

使用特权

评论回复
315
yao1318| | 2014-5-17 09:50 | 只看该作者
支持,如果动手能力强的可用楼主提供的原理图自己撘电路实验,这才是软硬全能!

使用特权

评论回复
316
loliweive| | 2014-5-21 07:56 | 只看该作者
jianhong_wu 发表于 2014-3-6 09:53
我个人认为,做单片机项目开发,初学者最缺的就是如何搭建系统,组织框架。而我现在分享的恰好就是我做所 ...

各种项目的算法,顶楼主。。。

使用特权

评论回复
317
zhjyuanji| | 2014-5-21 08:42 | 只看该作者
loliweive 发表于 2014-5-21 07:56
各种项目的算法,顶楼主。。。

楼主更得太慢了啊   你可以先更几节 然后集中互动一下  还有你用过CRC校验码

使用特权

评论回复
318
lmx89| | 2014-5-21 09:59 | 只看该作者
cjseng 发表于 2014-5-16 13:19
判断TI标志位会出错,最主要的原因是收发两端的波特率有误差,略加延时即可解决,比如增加一个位的发送时 ...

讨教:如何确定TI标志位出错,有什么现象吗?会出现乱码?

使用特权

评论回复
319
jianhong_wu|  楼主 | 2014-5-21 10:55 | 只看该作者
第四十八节:利用DS1302做一个实时时钟  。

开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
       在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
第一个:DS1302做实时时钟时,修改时间和读取时间的常见程序框架。
第二个:如何编写监控备用电池电量耗尽的监控程序。
第三个:在往DS1302写入数据修改时间之前,必须注意调整一下“日”的变量,因为每个月的最大天数是不一样的,有的一个月28天,有的一个月29天,有的一个月30天,有的一个月31天。
第四个:本程序第一次出现了电平按键,跟之前讲的下降沿按键不一样,请留意我是何如用软件滤波的,以及它具体的实现代码。
第五个:本程序第一次出现了一个按键按下去后,如果不松手就会触发两次事件,第一次是短按,第二次是长按3秒。请留意我是如何在之前的按键上略做修改就实现此功能的具体代码。
第六个:继续加深了解按键与显示是如何紧密关联起来的程序框架。

具体内容,请看源代码讲解。

(1)        硬件平台.
基于朱兆祺51单片机学习板。
旧版的朱兆祺51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的朱兆祺51学习板已经改过来了。

(2)实现功能:
     本程序有2两个窗口。
     第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
     第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
     系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
     需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。

本程序在使用过程中的注意事项:
(a)第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
(b)第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。

(3)源代码讲解如下:
第四十八节源代码讲解.rar (7.87 KB)
总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器  。

(未完待续,下节更精彩,不要走开哦)

使用特权

评论回复
320
cjseng| | 2014-5-21 12:29 | 只看该作者
lmx89 发表于 2014-5-21 09:59
讨教:如何确定TI标志位出错,有什么现象吗?会出现乱码?

TI标志位不会出错的,它只是发送完一个字节的标志,发送完一个字节,TI由硬件自动置1.这个出错的几率基本为0。
但是,如果发送端和接收端波特率存在误差,那么在发送端发送完一个字节后,TI已经置1了,但是接收端还没有接收完这个字节,此时如果发送端继续发送下一个字节,这样就会出错。
接收端以波特率的16倍频对接收到的电平进行采样,在16个采样周期中以7、8、9三个采样结果进行评判,至少有2次采样结果一致就确定信号电平。如果波特率存在较大误差,接收端的7、8、9三个采样周期就会逐渐与发送端失去同步,导致接收不正确。
发送一个字节,包含起始位、8位数据、一个奇偶校验位(可选)、停止位,在起始位时双方进行同步,但是如果波特率存在较大误差,可能未接收到停止位,就已经失步了。
这种现象就是帧错误。波特率越高,帧错误就越容易发生。
所以,有的设备上可以选择停止位的长度:1位、1.5位、2位。通常选择2位时,帧错误的现象就基本没有了。
在单片机上,一般不能设置停止位的长度,我们可以在发送完一个字节(TI==1)的情况下,略作延时,延时一位的发送时间,就相当于加长了停止位,这样就能确保通讯可靠。

接收端出现乱码的另一个原因,就是接收缓冲区内的数据未及时处理,这种现象通常在大数据量通讯时出现,比如常用的串口调试助手连续接收大数据时就会出现。

使用特权

评论回复
评论
jackhwang 2016-5-17 22:23 回复TA
高手。说的清清楚楚! 
评分
参与人数 1威望 +1 收起 理由
LC1234 + 1 很给力!
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则