Serial Port programming - Reading DSR from port

M

msalerno

I have a device that uses the dsr for communications. Basically what I
am trying to do is get a count of how many pulses it sends per second.
I have a script that counts the pulses, but there is something wrong.
It is counting too many pulses. I think that what is happening is that
it is counting one pulse many times. I know my count is wrong because I
have some windows software that works, and when I compare the output of
the windows software to the output of my script, the script is way off.
If anyone out there could point me in the correct direction I would be
very happy. It's tough to find informaion about monitoring the DSR
lines. Does anyone here have any experience with a similar situation?

#!/usr/bin/perl -w
use strict;
use Time::HiRes qw(usleep);

use Device::SerialPort qw( :pARAM :STAT 0.07 );

my %logged;
my @then;

# Open Serial Port
my $PortObj = new Device::SerialPort ('/dev/ttyS0') || die "Cannot open
Port: $!\n";

while () {
my $ModemStatus = $PortObj->modemlines;

my $hour = ((localtime)[2]); $hour = "0$hour" if length($hour)
< 2;
my $min = ((localtime)[1]); $min = "0$min" if length($min) < 2;
my $sec = ((localtime)[0]); $sec = "0$sec" if length($sec) < 2;
my $now = "$hour-$min-$sec";

push @then, $now;
push @then, $now if $#then < 1;
shift @then if $#then > 1;

if (!exists($logged{$now})){
$logged{$now} = 0;
my $val1 = sprintf "%.2f", $logged{$then[-2]}/3.4;
my $val2 = sprintf "%.2f", $logged{$then[-2]}/2.5;
print "Then:$then[-2] - $logged{$then[-2]} - $val1 -
$val2\n";
}

my $pulse = 0!=($ModemStatus & MS_DSR_ON);

$pulse = 0 if !$pulse;

$logged{$now} = $pulse + $logged{$now};
if ($pulse == 1){
usleep(1); # sleep one milisecond if change detected.
}
}

$PortObj->close;
 
J

John W. Krahn

Jim said:
my $hour = ((localtime)[2]); $hour = "0$hour" if length($hour)
< 2;
my $min = ((localtime)[1]); $min = "0$min" if length($min) < 2;
my $sec = ((localtime)[0]); $sec = "0$sec" if length($sec) < 2;
my $now = "$hour-$min-$sec";

Better, because you are calling localtime 3 times (untested):

my $now = sprintf( "%.2d-%.2d-%.2d", (localtime)[0..2] );

It looks like the OP wants this instead:

my $now = sprintf '%02d-%02d-%02d', ( localtime )[ 2, 1, 0 ];



John
 
M

msalerno

I changed the $now variable to use the sprintf. Thanks for that one.
I changed the usleep(1) to usleep(1000), again thanks.

The device (an anemometer) does not use a baud rate. The windows
software looks at the signal from the reed switch in the anemometer
head and measures the time between transitions of the switch. It sends
the signal through the DSR channel. The conversion between switch
pulses per second and MPH are either 1 pulse/second = 2.5 or 3.4 MPH. I
think that I need to debounce the opening and/or closing transitions of
the reed switch, I am pretty sure that this is where my problem is, but
I don't know how to solve it. I added/removed/changed a 1 millisecond
delay once I see a 1 in the $pulse variable, but I am still getting
crazy numbers.

I have also commented out the lines that check for $pulse == 1, and
left the usleep in, so it will always sleep. The numbers are still
off.

Thanks for the help, any other ideas?
 
I

Ilmari Karonen

msalerno said:
I have a device that uses the dsr for communications. Basically what I
am trying to do is get a count of how many pulses it sends per second. [snip]
my $pulse = 0!=($ModemStatus & MS_DSR_ON); [snip]
if ($pulse == 1){
usleep(1); # sleep one milisecond if change detected.
}

Here's one major problem: $pulse will be 1 if the DSR line is on, not
if it has changed.

Anyway, here's how I'd rewrite your script:

#!/usr/bin/perl -w
use strict;
use Time::HiRes qw(time sleep);
use Device::SerialPort qw( :pARAM :STAT 0.07 );

use constant SAMPLE_STEP => 1/1000; # sample once per msec
use constant REPORT_STEP => 1; # report once per second

# Open Serial Port
my $PortObj = new Device::SerialPort ('/dev/ttyS0')
or die "Cannot open Port: $!\n";

my $lastdsr = $PortObj->modemlines & MS_DSR_ON;
my $t0 = time;

while (1) {
my $changes = 0;
while (time < $t0 + REPORT_STEP) {
my $dsr = $PortObj->modemlines & MS_DSR_ON;
$changes++ if $dsr xor $lastdsr;
$lastdsr = $dsr;
sleep SAMPLE_STEP;
}
printf "%02d-%02d-%02d: %d - %.2f - %.2f\n",
(localtime int $t0)[2, 1, 0], $changes,
$changes / (3.4 * REPORT_STEP),
$changes / (2.5 * REPORT_STEP);
$t0 += REPORT_STEP;
}

I haven't tested this code, as I don't have Device::SerialPort
installed, nor do I have any suitable signal source available either.
But I'm fairly confident it should work.

The code counts state changes in both directions. If you only want to
count transitions from off to on, replace "xor" with "and not".

I'm assuming that SAMPLE_STEP is sufficiently small that no pulses can
ever be missed. Therefore I haven't bothered to synchronize the inner
loop; the actual time between samples will be SAMPLE_STEP plus however
long it takes to execute the inner loop.

The outer loop, however, is synchronized, which means you'll get
reports exactly once every REPORT_STEP seconds on average, and there
should be no statistical bias in the output (ignoring possible
floating point roundoff errors). Actually, I think your original code
would've worked the same way, had it worked correctly at all.

Note that there's really nothing Perl-specific about this. The same
code could be trivially ported to C or Java or just about any other
language. Only the interfaces to the serial port and the system timer
would change.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top