File Coverage

blib/lib/RPi/Serial.pm
Criterion Covered Total %
statement 6 70 8.5
branch 0 18 0.0
condition 0 12 0.0
subroutine 2 20 10.0
pod 13 13 100.0
total 21 133 15.7


line stmt bran cond sub pod time code
1             package RPi::Serial;
2              
3 1     1   112244 use strict;
  1         1  
  1         32  
4 1     1   4 use warnings;
  1         1  
  1         893  
5              
6              
7             our $VERSION = '3.02';
8              
9             require XSLoader;
10             XSLoader::load('RPi::Serial', $VERSION);
11              
12             sub new {
13 0     0 1   my ($class, $device, $baud) = @_;
14              
15 0           my $self = bless {
16             rx_data => '',
17             rx_started => 0,
18             rx_ended => 0,
19             }, $class;
20              
21 0           $self->fd(tty_open($device, $baud));
22              
23 0           return $self;
24             }
25             sub close {
26 0     0 1   tty_close($_[0]->fd);
27             }
28             sub crc {
29 0     0 1   my ($self, $data) = @_;
30 0           return crc16($data, length($data));
31             }
32             sub avail {
33 0     0 1   return tty_available($_[0]->fd);
34             }
35             sub fd {
36 0     0 1   my $self = shift;
37 0 0         $self->{fd} = shift if @_;
38 0           return $self->{fd};
39             }
40             sub flush {
41 0     0 1   tty_flush($_[0]->fd);
42             }
43             sub putc {
44 0     0 1   tty_putc($_[0]->fd, $_[1]);
45             }
46             sub puts {
47 0     0 1   tty_puts($_[0]->fd, $_[1]);
48             }
49             sub getc {
50 0     0 1   return tty_getc($_[0]->fd);
51             }
52             sub gets {
53             # Returns the exact bytes read (binary-safe); may be shorter than the
54             # requested count if the port's read timeout elapsed first.
55 0     0 1   return tty_gets($_[0]->fd, $_[1]);
56             }
57             sub write {
58 0     0 1   my ($self, $byte) = @_;
59 0 0         if (! defined $byte){
60 0           die "write() requires a byte of data sent in\n";
61             }
62 0           $self->putc(pack("C", $byte));
63             }
64             sub rx {
65 0     0 1   my ($self, $start, $end) = @_;
66              
67 0           my $c = chr $self->getc; # getc() returns the ord() val on a char* perl-wise
68              
69 0 0 0       if ($c ne $start && ! $self->{rx_started}){
70 0           $self->_rx_reset();
71 0           return;
72             }
73              
74 0 0         if ($c eq $start){
75 0           $self->{rx_started} = 1;
76 0           return;
77             }
78              
79 0 0         if ($c eq $end){
80 0           $self->{rx_ended} = 1;
81             }
82              
83 0 0 0       if ($self->{rx_started} && ! $self->{rx_ended}){
84 0           $self->{rx_data} .= $c;
85             }
86              
87 0 0 0       if ($self->{rx_started} && $self->{rx_ended}){
88              
89 0           my $l_crc = $self->_local_crc($self->{rx_data});
90 0           my $r_crc = $self->_remote_crc($self->{rx_data});
91              
92 0 0         if ($r_crc == $l_crc){
93 0           my $rx_data = $self->{rx_data};
94 0           $self->_rx_reset;
95 0           return $rx_data;
96             }
97             else {
98 0           warn "\ncompiled data '$self->{rx_data}' has mismatching CRC\n\n";
99 0           $self->_rx_reset;
100 0           return;
101             }
102             }
103             }
104             sub tx {
105 0     0 1   my ($self, $data, $tx_start, $tx_end) = @_;
106              
107 0           my $crc = $self->crc($data);
108 0           my $crc_msb = $crc >> 8;
109 0           my $crc_lsb = $crc & 0xFF;
110              
111 0           my $tx = $tx_start . $data . $tx_end;
112              
113 0           for (split //, $tx){
114 0           $self->write($_);
115             }
116              
117 0           $self->write($crc_msb);
118 0           $self->write($crc_lsb);
119             }
120              
121             sub DESTROY {
122 0     0     tty_close($_[0]->fd);
123             }
124              
125             sub _local_crc {
126 0     0     return $_[0]->crc($_[1]);
127             }
128             sub _remote_crc {
129 0     0     my ($self) = @_;
130              
131 0           while ($self->avail < 2){} # loop until we have two bytes to make up the CRC
132              
133 0           my $crc_msb = $self->getc;
134 0           my $crc_lsb = $self->getc;
135              
136 0           my $crc = ($crc_msb << 8) | $crc_lsb;
137              
138 0 0 0       return if $crc_msb == -1 || $crc_lsb == -1;
139 0           return $crc;
140             }
141             sub _rx_reset {
142 0     0     my ($self) = @_;
143 0           $self->{rx_started} = 0;
144 0           $self->{rx_ended} = 0;
145 0           $self->{rx_data} = '';
146             }
147       0     sub __placeholder {} # vim folds
148             1;
149              
150             =head1 NAME
151              
152             RPi::Serial - Basic read/write interface to a serial port
153              
154             =head1 SYNOPSIS
155              
156             use RPi::Serial;
157              
158             my $dev = "/dev/ttyAMA0";
159             my $baud = 115200;
160            
161             my $ser = RPi::Serial->new($dev, $baud);
162              
163             # Write a single char
164              
165             $ser->putc(5);
166              
167             # Write a string
168              
169             $ser->puts("hello, world!");
170              
171             # Write a single byte by its integer value (0-255)
172              
173             $ser->write(65);
174             my $char = $ser->getc;
175              
176             # Get a string
177              
178             my $num_bytes = 12;
179             my $str = $ser->gets($num_bytes);
180              
181             # Send a CRC-framed payload between start/end delimiters
182              
183             $ser->tx("payload", "<", ">");
184              
185             # Receive a CRC-framed payload (call in a loop until it returns the data)
186              
187             my $frame = $ser->rx("<", ">");
188              
189             my $crc = $ser->crc($str);
190              
191             $ser->flush;
192              
193             my $bytes_available = $ser->avail;
194              
195             $ser->close;
196              
197             =head1 DESCRIPTION
198              
199             Provides basic read and write functionality of a UART serial interface
200              
201             =head1 WARNING
202              
203             If using on a Raspberry Pi platform, the procedure to enable GPIO pins 14 (TXD)
204             and 15 (RXD) as a serial interface differs by board. On B boards, first
205             free the port from the kernel console: in C, under C
206             Options -E Serial Port>, answer B to the login shell and B to the
207             serial hardware.
208              
209             =head2 Raspberry Pi 3 / 4 (and Zero W)
210              
211             The on-board Bluetooth modem is wired to the primary PL011 UART, leaving GPIO
212             14/15 on the inferior, baud-unstable mini-UART (C). To move the good
213             UART onto the header pins you must disable Bluetooth. Edit
214             C (C on releases before Bookworm)
215             and add:
216              
217             enable_uart=1
218             dtoverlay=disable-bt
219              
220             With that overlay the header serial port becomes C.
221              
222             =head2 Raspberry Pi 5
223              
224             Bluetooth has its B and is B shared with the GPIO 14/15
225             pins, so there is nothing to disable. Just enable the header UART in
226             C:
227              
228             enable_uart=1
229              
230             The header serial port is C. (Note that on the Pi 5
231             C maps to the separate 3-pin debug-UART connector, B the
232             header pins.)
233              
234             Save the file, then reboot the Pi.
235              
236             =head1 METHODS
237              
238             =head2 new($device, $baud);
239              
240             Opens the specified serial port at the specified baud rate, and returns a new
241             L object.
242              
243             Parameters:
244              
245             $device
246              
247             Mandatory, String: The serial device to open (eg: C<"/dev/ttyAMA0">).
248              
249             $baud
250              
251             Mandatory, Integer: A valid baud rate to use.
252              
253             =head2 close
254              
255             Closes an already open serial device.
256              
257             =head2 avail
258              
259             Returns the number of bytes waiting to be read if any.
260              
261             =head2 flush
262              
263             Flush any data currently in the serial buffer.
264              
265             =head2 fd
266              
267             Returns the C file descriptor for the current serial object.
268              
269             =head2 getc
270              
271             Retrieve a single character from the serial port.
272              
273             =head2 gets($num_bytes)
274              
275             Read up to a specified number of bytes and return them as a string.
276              
277             The read blocks only until the port's configured read timeout (the C
278             value set when the port was opened) elapses, so the returned string may be
279             B than C<$num_bytes> if fewer bytes arrived in time (or the device
280             closed). The result is binary-safe: embedded C bytes and trailing
281             whitespace are preserved exactly as received.
282              
283             Parameters:
284              
285             $num_bytes
286              
287             Mandatory, Integer; The maximum number of bytes to read. If this number is
288             larger than what is available, the call returns the bytes received before the
289             read timeout elapsed (possibly an empty string).
290              
291             Returns: A string of the bytes actually read. Croaks on a read error.
292              
293             =head2 putc($char)
294              
295             Writes a single character to the serial device.
296              
297             Parameters:
298              
299             $char
300              
301             Mandatory, Unsigned Char: The character to write to the port.
302              
303             =head2 puts($string)
304              
305             Write a character string to the serial device.
306              
307             Parameters:
308              
309             $string
310              
311             Mandatory, String: Whatever you want to write to the serial line.
312              
313             =head2 crc($string)
314              
315             Calculate and return a CRC-16 checksum. Uses local B application to
316             generate the CRC.
317              
318             Parameters:
319              
320             $string
321              
322             Mandatory, String: The string to perform the checksum on.
323              
324             =head2 write($byte)
325              
326             Writes a single byte to the serial device. The byte is packed into an unsigned
327             char before being sent, making this a convenience wrapper around L
328             that accepts an integer value rather than a character.
329              
330             Parameters:
331              
332             $byte
333              
334             Mandatory, Unsigned Integer (0-255): The byte value to write to the port.
335             Croaks if not supplied.
336              
337             =head2 rx($start, $end)
338              
339             Reads a single character from the serial port and assembles framed data across
340             successive calls. A frame begins when the C<$start> delimiter is received and
341             ends when the C<$end> delimiter is received, at which point the two trailing
342             CRC-16 bytes are read and validated against the assembled payload.
343              
344             Call this repeatedly (eg: in a loop). Until a complete, CRC-valid frame has been
345             received it returns C; characters seen before the C<$start> delimiter are
346             discarded.
347              
348             Parameters:
349              
350             $start
351              
352             Mandatory, Char: The single character that marks the beginning of a frame.
353              
354             $end
355              
356             Mandatory, Char: The single character that marks the end of a frame.
357              
358             Returns: The assembled payload string once a full frame with a matching CRC has
359             been received, or C otherwise. Warns and discards the frame if the
360             received CRC does not match the locally computed one.
361              
362             =head2 tx($data, $tx_start, $tx_end)
363              
364             Transmits a frame of data. The C<$data> is wrapped between the C<$tx_start> and
365             C<$tx_end> delimiters and written to the port, followed by the two bytes (most
366             significant first) of the CRC-16 checksum calculated over C<$data>.
367              
368             Parameters:
369              
370             $data
371              
372             Mandatory, String: The payload to transmit.
373              
374             $tx_start
375              
376             Mandatory, Char: The single character to send before the payload.
377              
378             $tx_end
379              
380             Mandatory, Char: The single character to send after the payload.
381              
382             =head1 AUTHOR
383              
384             Steve Bertrand, C<< >>
385              
386             =head1 LICENSE AND COPYRIGHT
387              
388             Copyright 2026 Steve Bertrand.
389              
390             This program is free software; you can redistribute it and/or modify it
391             under the terms of either: the GNU General Public License as published
392             by the Free Software Foundation; or the Artistic License.
393              
394             See L for more information.