Much faster LDPC encoder.

pull/1/head
Mark Jessop 2016-09-09 22:42:31 +09:30
rodzic 72a1e00678
commit 2afb7bdcdd
7 zmienionych plików z 202 dodań i 102 usunięć

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -5,6 +5,13 @@
# Frames packets (preamble, unique word, checksum) # Frames packets (preamble, unique word, checksum)
# and transmits them out of a serial port. # and transmits them out of a serial port.
# #
# RPI UART Calibration
# 9600 -> 9600.1536
# 19200 -> 19200.307
# 38400 -> 38339.148
# 57600 -> 57693.417
# 115200 -> 115386.834
#
# Mark Jessop <vk5qi@rfhead.net> # Mark Jessop <vk5qi@rfhead.net>
# #
@ -34,9 +41,9 @@ class BinaryDebug(object):
self.f.close() self.f.close()
def write_debug_message(message, debug_file = "tx_idle_message.txt"): def write_debug_message(message, debug_file = "tx_idle_message.txt"):
f = open(debug_file,'w') #f = open(debug_file,'w')
f.write(message) #f.write(message)
f.close() #f.close()
print("DEBUG MSG: %s" % message) print("DEBUG MSG: %s" % message)
class PacketTX(object): class PacketTX(object):
@ -59,8 +66,9 @@ class PacketTX(object):
self.payload_length = payload_length self.payload_length = payload_length
self.crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false') self.crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
self.fec = fec
self.callsign = callsign self.callsign = callsign
self.idle_message = "DE %s" % callsign
self.fec = fec
def start_tx(self): def start_tx(self):
self.transmit_active = True self.transmit_active = True
@ -82,18 +90,17 @@ class PacketTX(object):
else: else:
return self.preamble + self.unique_word + packet + crc return self.preamble + self.unique_word + packet + crc
def set_idle_message(self, message):
temp_msg = "\x00" + "DE %s: \t%s" % (self.callsign, message)
self.idle_message = self.frame_packet(temp_msg)
# Either generate an idle message, or read one in from a file (tx_idle_message.txt) if it exists. # Either generate an idle message, or read one in from a file (tx_idle_message.txt) if it exists.
# This might be a useful way of getting error messages down from the payload. # This might be a useful way of getting error messages down from the payload.
def generate_idle_message(self): def generate_idle_message(self):
# Try and read in a message from a file.
try:
f = open("tx_idle_message.txt")
idle_data = "DE %s: \t%s" % (self.callsign,f.read())
f.close()
except:
idle_data = "DE %s Wenet High-Speed FSK Transmitter" % self.callsign
# Append a \x00 control code before the data # Append a \x00 control code before the data
return "\x00" + idle_data return "\x00" + "DE %s: \t%s" % (self.callsign,self.idle_message)
def tx_thread(self): def tx_thread(self):
@ -104,7 +111,7 @@ class PacketTX(object):
else: else:
if not self.debug: if not self.debug:
#self.s.write(self.idle_sequence) #self.s.write(self.idle_sequence)
self.s.write(self.frame_packet(self.generate_idle_message())) self.s.write(self.idle_message)
else: else:
sleep(0.05) sleep(0.05)

40
ldpc_enc.c 100644
Wyświetl plik

@ -0,0 +1,40 @@
/*
LDPC Encoder, using a 'RA' encoder written by Bill Cowley VK5DSP in March 2016.
Compile with:
gcc -fPIC -shared -o ldpc_enc.so ldpc_enc.c
*/
#include<stdio.h>
#include<stdlib.h>
#define Nibits 2064
#define Npbits 516
#define Nwt 12
unsigned short hrows[] = {
// read from file created via make_Hrows_txt.m
// use the new code of March 2016
#include "Hrow2064.txt"
};
void encode(unsigned char *ibits, unsigned char *pbits) {
unsigned int p, i, tmp, par, prev=0;
char c;
for (p=0; p<Npbits; p++) {
par =0;
for (i=0; i<Nwt; i++)
par = par + ibits[hrows[p*Nwt+i]-1];
// -1 as matlab arrays start from 1, C from 0
tmp = par + prev;
//printf(" p ind %d, parity %d \n", p, tmp);
//c = getchar();
tmp &= 1; // only retain the lsb
prev = tmp;
pbits[p] =tmp;
}
}

Wyświetl plik

@ -1,94 +1,53 @@
#!/usr/bin/env python2.7 #!/usr/bin/env python
# #
# LDPC Encoder based on C code from Bill Cowley in March 2016 # LDPC Encoder Functions.
# Author: Mark Jessop <vk5qi@rfhead.net> # Uses ctypes to call the encode function from ldpc_enc.c
# #
# ldpc_enc.c needs to be compiled to a .so before this will work, with:
# gcc -fPIC -shared -o ldpc_enc.so ldpc_enc.c
#
# Mark Jessop <vk5qi@rfhead.net>
#
import ctypes
from numpy.ctypeslib import ndpointer
import numpy as np import numpy as np
import time
# Attempt to load in ldpc_enc.so on startup.
try:
_ldpc_enc = ctypes.CDLL("./ldpc_enc.so")
_ldpc_enc.encode.restype = None
_ldpc_enc.encode.argtypes = (ndpointer(ctypes.c_ubyte, flags="C_CONTIGUOUS"), ndpointer(ctypes.c_ubyte, flags="C_CONTIGUOUS"))
except OSError as e:
raise OSError("Could not find ldpc_enc.so! Have you compiled ldpc_enc.c?")
# #
# ORIGINAL C CODE FOLLOWS # LDPC Encoder.
# Accepts a 258 byte string as input, returns the LDPC parity bits.
# #
# #define Nibits 2064 def ldpc_encode_string(payload, Nibits = 2064, Npbits = 516):
# #define Npbits 516
# #define Nwt 12
# unsigned short hrows[] = {
# // read from file created via make_Hrows_txt.m
# // use the new code of March 2016
# #include "Hrow2064.txt"
# };
# unsigned char ibits[Nibits]; // info array
# unsigned char pbits[Npbits]; // parity array
# void encode() {
# unsigned int p, i, tmp, par, prev=0;
# char c;
# for (p=0; p<Npbits; p++) {
# par =0;
# for (i=0; i<Nwt; i++)
# par = par + ibits[hrows[p*Nwt+i]-1];
# // -1 as matlab arrays start from 1, C from 0
# tmp = par + prev;
# // printf(" p ind %d, parity %d \n", p, tmp);
# //c = getchar();
# tmp &= 1; // only retain the lsb
# prev = tmp;
# pbits[p] =tmp;
# }
# }
# Load Parity table upon load of this library.
hrows_2064 = np.loadtxt("ldpc_2064.txt",delimiter=',',comments='#').astype(np.int)
# Direct port of encode() above.
# Takes an input array of 0s and 1s.
# Should look at vectorising this somehow.
#@profile
def ldpc_encode(ibits,Nibits=2064,Npbits=516,Nwt=12,hrows=hrows_2064):
prev = 0
pbits = np.zeros(Npbits)
for p in xrange(Npbits):
#par = 0
#for i in xrange(Nwt):
# par = par + ibits[hrows[p*Nwt+i]-1]
par = int(np.sum(ibits[hrows[(p*Nwt+np.arange(Nwt))]-1])) # Some vectorisation of the above.
tmp = (par + prev)&1
prev = tmp
pbits[p] = tmp
return pbits
# Wrapper function for the above, allowing LDPC coding of a 258 byte long string.
def ldpc_encode_string(payload,Nibits=2064,Npbits=516,Nwt=12,hrows=hrows_2064):
if len(payload) != 258: if len(payload) != 258:
raise TypeError("Payload MUST be 258 bytes in length! (2064 bit codeword)") raise TypeError("Payload MUST be 258 bytes in length! (2064 bit codeword)")
# Convert to bits. # Get input data into the right form (list of 0s and 1s)
raw_data = np.array([],dtype=np.uint8) ibits = np.unpackbits(np.fromstring(payload,dtype=np.uint8)).astype(np.uint8)
ibits = np.unpackbits(np.fromstring(payload,dtype=np.uint8)) pbits = np.zeros(Npbits).astype(np.uint8)
parity = ldpc_encode(ibits,Nibits,Npbits,Nwt,hrows) _ldpc_enc.encode(ibits, pbits)
return np.packbits(parity.astype(np.uint8)).tostring() return np.packbits(np.array(list(pbits)).astype(np.uint8)).tostring()
# # Some testing functions, to time encoding performance.
# Testing Stuff
#
def generate_dummy_packet(): def generate_dummy_packet():
payload = np.arange(0,256,1) payload = np.arange(0,256,1)
payload = np.append(payload,[0,0]).astype(np.uint8).tostring() # Add on dummy checksum, for a total of 258 bytes. payload = np.append(payload,[0,0]).astype(np.uint8).tostring() # Add on dummy checksum, for a total of 258 bytes.
return payload return payload
#@profile
def main(): def main():
# Generate a dummy test packet, and convert it to an array of 0 and 1. # Generate a dummy test packet, and convert it to an array of 0 and 1.
payload = generate_dummy_packet() payload = generate_dummy_packet()
@ -97,12 +56,17 @@ def main():
# Now run ldpc_encode over it X times. # Now run ldpc_encode over it X times.
parity = "" parity = ""
for x in xrange(100): start = time.time()
parity = ldpc_encode_string(payload) for x in xrange(1000):
#print(x)
parity = ldpc_encode(payload)
stop = time.time()
print("time delta: %.3f" % (stop-start))
print("LDPC Parity Bits (hex): %s" % ("".join("{:02x}".format(ord(c)) for c in parity))) print("LDPC Parity Bits (hex): %s" % ("".join("{:02x}".format(ord(c)) for c in parity)))
print("Done!") print("Done!")
# Some basic test code. # Some basic test code.
if __name__ == "__main__": if __name__ == "__main__":
main() main()

Wyświetl plik

@ -15,19 +15,19 @@ try:
if len(callsign)>6: if len(callsign)>6:
callsign = callsign[:6] callsign = callsign[:6]
except: except:
print("Usage: python tx_picam.py CALLSIGN") print("Usage: python tx_picam.py CALLSIGN ")
sys.exit(1) sys.exit(1)
print("Using callsign: %s" % callsign) print("Using callsign: %s" % callsign)
fec = False
debug_output = False # If True, packet bits are saved to debug.bin as one char per bit. debug_output = False # If True, packet bits are saved to debug.bin as one char per bit.
def transmit_file(filename, tx_object): def transmit_file(filename, tx_object):
file_size = os.path.getsize(filename) file_size = os.path.getsize(filename)
if file_size % 256 > 0: if file_size % 256 > 0:
write_debug_message("File size not a multiple of 256 bytes!") tx.set_idle_message("File size not a multiple of 256 bytes!")
return return
print("Transmitting %d Packets." % (file_size/256)) print("Transmitting %d Packets." % (file_size/256))
@ -43,7 +43,7 @@ def transmit_file(filename, tx_object):
tx_object.wait() tx_object.wait()
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign) tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign, fec=fec)
tx.start_tx() tx.start_tx()
image_id = 0 image_id = 0
@ -53,10 +53,10 @@ try:
# Capture image using PiCam # Capture image using PiCam
print("Capturing Image...") print("Capturing Image...")
capture_time = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ") capture_time = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ")
capture_multiple(filename="./tx_images/%s.jpg"%capture_time) capture_multiple(filename="./tx_images/%s.jpg"%capture_time, debug_ptr = tx.set_idle_message)
#os.system("raspistill -t 100 -o ./tx_images/%s.jpg -vf -hf -w 1024 -h 768" % capture_time) #os.system("raspistill -t 100 -o ./tx_images/%s.jpg -vf -hf -w 1024 -h 768" % capture_time)
# Resize using convert # Resize using convert
write_debug_message("Converting Image to SSDV...") tx.set_idle_message("Converting Image to SSDV...")
#os.system("convert temp.jpg -resize %s\! temp.jpg" % tx_resolution) #os.system("convert temp.jpg -resize %s\! temp.jpg" % tx_resolution)
# SSDV'ify the image. # SSDV'ify the image.
os.system("ssdv -e -n -c %s -i %d ./tx_images/%s.jpg temp.ssdv" % (callsign,image_id,capture_time)) os.system("ssdv -e -n -c %s -i %d ./tx_images/%s.jpg temp.ssdv" % (callsign,image_id,capture_time))

Wyświetl plik

@ -0,0 +1,90 @@
#!/usr/bin/env python
#
# PiCam Transmitter Script
# Capture images from the PiCam, and transmit them.
# Multiple baud rate test version.
#
# Mark Jessop <vk5qi@rfhead.net>
#
import PacketTX, sys, os, datetime, time
from PacketTX import write_debug_message
from wenet_util import *
try:
callsign = sys.argv[1]
if len(callsign)>6:
callsign = callsign[:6]
except:
print("Usage: python tx_picam.py CALLSIGN ")
sys.exit(1)
print("Using callsign: %s" % callsign)
fec = False
debug_output = False # If True, packet bits are saved to debug.bin as one char per bit.
baud_rate_1 = 115200
baud_rate_2 = 19200
def transmit_file(filename, tx_object):
file_size = os.path.getsize(filename)
if file_size % 256 > 0:
tx.set_idle_message("File size not a multiple of 256 bytes!")
return
print("Transmitting %d Packets." % (file_size/256))
f = open(filename,'rb')
for x in range(file_size/256):
data = f.read(256)
tx_object.tx_packet(data)
f.close()
print("Waiting for tx queue to empty...")
tx_object.wait()
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign,serial_baud=baud_rate_1)
tx.start_tx()
image_id = 0
try:
while True:
# Capture image using PiCam
print("Capturing Image...")
capture_time = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ")
capture_multiple(filename="./tx_images/%s.jpg"%capture_time, debug_ptr = tx.set_idle_message)
#os.system("raspistill -t 100 -o ./tx_images/%s.jpg -vf -hf -w 1024 -h 768" % capture_time)
# Resize using convert
tx.set_idle_message("Converting Image to SSDV...")
#os.system("convert temp.jpg -resize %s\! temp.jpg" % tx_resolution)
# SSDV'ify the image.
os.system("ssdv -e -n -c %s -i %d ./tx_images/%s.jpg temp.ssdv" % (callsign,image_id,capture_time))
# Transmit image
print("Transmitting...")
# Transmit first in 115200 baud.
transmit_file("temp.ssdv",tx)
tx.close()
time.sleep(2)
# Transmit again in 19200 baud
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign, serial_baud=baud_rate_2, fec = fec)
tx.start_tx()
transmit_file("temp.ssdv",tx)
tx.close()
time.sleep(2)
# Re-open TX in 115200 baud.
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign, serial_baud=baud_rate_1, fec = fec)
tx.start_tx()
# Increment Counter
image_id = (image_id+1)%256
except KeyboardInterrupt:
print("Closing")
tx.close()

Wyświetl plik

@ -9,7 +9,7 @@
import PacketTX, sys, os import PacketTX, sys, os
# Set to whatever resolution you want to test. # Set to whatever resolution you want to test.
file_path = "./test_images/%d_320x240.ssdv" # _raw, _800x608, _640x480, _320x240 file_path = "./test_images/%d_raw.ssdv" # _raw, _800x608, _640x480, _320x240
image_numbers = xrange(1,14) image_numbers = xrange(1,14)
debug_output = True # If True, packet bits are saved to debug.bin as one char per bit. debug_output = True # If True, packet bits are saved to debug.bin as one char per bit.
@ -34,15 +34,15 @@ def transmit_file(filename, tx_object):
tx_object.wait() tx_object.wait()
tx = PacketTX.PacketTX(debug=debug_output) tx = PacketTX.PacketTX(debug=debug_output,fec=False)
tx.start_tx() tx.start_tx()
print("TX Started. Press Ctrl-C to stop.") print("TX Started. Press Ctrl-C to stop.")
try: try:
while True: for img in image_numbers:
for img in image_numbers: filename = file_path % img
filename = file_path % img print("\nTXing: %s" % filename)
print("\nTXing: %s" % filename) transmit_file(filename,tx)
transmit_file(filename,tx) tx.close()
except KeyboardInterrupt: except KeyboardInterrupt:
print("Closing...") print("Closing...")
tx.close() tx.close()