这两天带博主的工程师给了一个小任务给我,使用7个小器件进行半双工的工业485总线通信,使用公司内部标准帧格式,采用主动上传方式每隔一定的定量时间进行数据上传。当然,实现一个器件的数据上传是十分简单的,但是使用大于两个器件进行485工业数据传输的时候就必须要考虑数据碰撞的问题了,因为由于485半双工的特性,同一时间数据只能上传或者下载,但是当多个数据进行数据通信的时候就要小心数据的交叉碰撞。
项目要求,在符合帧格式的前提下使用6台工作器件进行数据主动上传,因为前期设定每台设备的主动上传时间为2min,所以不可避免的遇到一个问题,就是如何进行数据的总线判“忙”检测,并且在其他的数据帧发送完毕后进行数据的发送?
经过博主的资料的查询和带我的工程师的指导下,博主总结了判断线忙的主要方法:
(1)由于数据主动上传,所以定时器必须给一个定时,定时2min,到时间了就上传一帧数据,数据上传帧还是通过填充中断首字母的方式进行,不懂的同学们可以看一看博主前面的博文。
(2)数据上传就必须要进行判断的检测,经过现在项目的完成经验来看,一开始需要定义两个变量 sendok 和sending,因为我们知道数据的判忙不是说判断总线现在的时刻不忙就可以发送一帧数据,数据是连续的,如果仅仅是一个时刻的不忙就发送那么帧和帧之间还是会乱码,会碰撞,这个时候的sendok变量就是判断当下是不是有一个大于等于帧长的时间的间隔,如果有,则说明现在有一个连续的空闲时间可以发送,这个sendok博主的处理是放在定时器2中,使用一个专门的定时器进行判断,具体方法是在收中断中进行定时器的开启,接下来判断完毕后更改变量的值,发中断发送完毕后就关闭定时器:
定时器2
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{
TCNT2=0xF8;
t2cnt++;
#asm("wdr");
if(t2cnt>100)
{
t2cnt=0;
sendok=1;
}
}
收中断
// USART Receiver interrupt service routine 串口接收中断服务程序
interrupt [USART_RXC] void usart_rx_isr(void) //接在中断接收引脚上面 结束中断向量USART_RXC 连接串口与中断的好东西
{ //这里的收中断 需要写一个判断是否空闲10ms,如果空闲则可以发送
u16 crctemp;
char status,data;
status=UCSRA;
data=UDR;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
{
rxbuf[rxcnt++]=data;
if(rxcnt>99)rxcnt=0;
//01 15 01 00 xx xx
t2cnt=0;
TCCR2=0x05;
}
}
发中断
interrupt [USART_TXC] void usart_tx_isr(void) //发送引脚中断函数
{
static u8 TxCont;
if(--txcnt)
{
sending=0;
UDR=txbuf[TxCont+1];
TxCont++;
}
else
{ sending=1;
UCSRA |=0x60;
TxCont=0;
TCCR2=0X00;
TCCR0=0x05;
Rx;
}
}
那么sending这个变量呢,就是监测的第二步,因为我们第一步仅仅是监测了当前的空闲时间是不是有足够的时间可能发送,可以说第一个变量是我们帧可以发送的充分不必要条件,第二个变量sending就是说监测是不是一直在发送中,如果我们的帧在发送了,那么sending就置位,表明当前是有数据在“”持续不断“”的发送,这样就可以判断是不是说数据已经发完了,如果sending返回初始化的值后,就可以接着发送了。变量的改变主要是在发中断中,只要在发就持续不断的置位sending。
(2)变量的“持续性”,在判断变量的变化时候博主一开始犯了一个错误,就是使用if()来判断。后来反省了一下,在数据是一个位一个位上传的时候当然可以这样判断,还是那句话,一位一位的数据上传没有“持续性”,那我们就需要一个机制可以使得如果不满足变量改变的时候程序是一直等待的。解决方法如下,使用while
while(!sending){
while(!sendok);}
改语句的用途就是只要当前不符合数据发送的时间或者当前还有数据进行发送,下一帧的发送就不会进行。
(3)此时是不是在程序上就能实现数据帧的传送了呢?博主尝试后发现,并不能实现数据帧无损的传输,原因经过博主的分析,发现由于程序定时器0定死2min发送一帧,虽然在前面的程序中做了一些判忙的措施,但是唯独没有想到的就是一开始的数据发送情况,一开始大家都没有发送,一到了2min,于是大家都抢着发送各自的数据帧,此时由于是第一帧,所以sending变量判断的功能就失效了,由于是一开始到了2min就发送数据,所以sendok就发送失效,这样带来的后果就是,第二个2min,第三个2min,这样的情况会一直持续下去,最直观的结果就是在串口助手上我们可以发现每隔2min 7个avr就会发送一堆不知道是什么东西的乱码。
那么到了这一步,该怎么解决呢?由于判忙机制是没有错误的,问题出在哪里呢?很明显就是出现在第一次的发送,就是所谓的定时发送,定时发送由于avr公司设计的外设晶振采用的是比较优质的晶振,所以定时的准确性是毋庸置疑的,所以就导致了一开始的碰撞。博主考虑的方法就是在2min的时间内能不能做一个随机的时间差,就想是121s 122s 或者190s 的时候发送,就是说在2min的左右的范围内,后期博主测定的范围在±3s内随机发送数据帧,这样就可以完美的利用前面写的判忙程序进行数据帧的发送了。
(4)那么现在问题来了,如何写一个真的随机数而不是伪随机数呢?
首先先要百度了解一下 rand() random() srand()这几个函数,了解真随机,伪随机以及随机种子的概念,了解这些后再考虑如何在avr这个没有time.h头文件的单片机工作平台上实现一个随机种子。
先不说随机种子的事情,博主把真随机的函数写出来(AVR单片机内适合使用)
/*随机函数*/
unsigned int random(unsigned int input) //随机函数的定义,输入作为参数放到srand里面
{
unsigned int value;
srand(input*2);
value = rand() % (MAX + 1 - MIN)+ MIN; //获取一个随机数(100-400)
return value;
}
那么现在的问题就是集中在一点上,如何产生一个真正随机的随机种子呢?
其实在其他人的博客上可以看到有许多方法,博主使用的办法是通过ADC采集浮空引脚进行小数点的采集,然后将小数点的数值当做一个随机种子。放入随机函数的形参内。
ADC采集每个单片机有不同的配置方法,博主在这里贴出博主配置的ADC采集函数
/*ADC采集函数*/
unsigned int read_adc()
{
ADMUX|=0x46;// | (ADC_VREF_TYPE & 0xff); ADC6
delay_us(10);
ADCSRA|=0xE7; //128分频
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCW;
}
不理解的同学可以查询AVR单片机ATmega8的数据手册进行查询。
(5)
以上就是博主的串口助手接收的数据帧,7台设备接入总线。定时发送,一帧一帧的完整发送成功了。
上一篇:在ATmega8中真实可用的ADC转换器写法
下一篇:AVR单片机中ATmega8的AD转换探究
推荐阅读最新更新时间:2024-03-16 16:08