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)
# 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>
#
@ -34,9 +41,9 @@ class BinaryDebug(object):
self.f.close()
def write_debug_message(message, debug_file = "tx_idle_message.txt"):
f = open(debug_file,'w')
f.write(message)
f.close()
#f = open(debug_file,'w')
#f.write(message)
#f.close()
print("DEBUG MSG: %s" % message)
class PacketTX(object):
@ -59,8 +66,9 @@ class PacketTX(object):
self.payload_length = payload_length
self.crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
self.fec = fec
self.callsign = callsign
self.idle_message = "DE %s" % callsign
self.fec = fec
def start_tx(self):
self.transmit_active = True
@ -82,18 +90,17 @@ class PacketTX(object):
else:
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.
# This might be a useful way of getting error messages down from the payload.
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
return "\x00" + idle_data
return "\x00" + "DE %s: \t%s" % (self.callsign,self.idle_message)
def tx_thread(self):
@ -104,7 +111,7 @@ class PacketTX(object):
else:
if not self.debug:
#self.s.write(self.idle_sequence)
self.s.write(self.frame_packet(self.generate_idle_message()))
self.s.write(self.idle_message)
else:
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
# Author: Mark Jessop <vk5qi@rfhead.net>
# LDPC Encoder Functions.
# 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 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
# #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):
def ldpc_encode_string(payload, Nibits = 2064, Npbits = 516):
if len(payload) != 258:
raise TypeError("Payload MUST be 258 bytes in length! (2064 bit codeword)")
# Convert to bits.
raw_data = np.array([],dtype=np.uint8)
ibits = np.unpackbits(np.fromstring(payload,dtype=np.uint8))
# Get input data into the right form (list of 0s and 1s)
ibits = np.unpackbits(np.fromstring(payload,dtype=np.uint8)).astype(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()
#
# Testing Stuff
#
# Some testing functions, to time encoding performance.
def generate_dummy_packet():
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.
return payload
#@profile
def main():
# Generate a dummy test packet, and convert it to an array of 0 and 1.
payload = generate_dummy_packet()
@ -97,12 +56,17 @@ def main():
# Now run ldpc_encode over it X times.
parity = ""
for x in xrange(100):
parity = ldpc_encode_string(payload)
start = time.time()
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("Done!")
# Some basic test code.
if __name__ == "__main__":
main()
main()

Wyświetl plik

@ -15,19 +15,19 @@ try:
if len(callsign)>6:
callsign = callsign[:6]
except:
print("Usage: python tx_picam.py CALLSIGN")
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.
def transmit_file(filename, tx_object):
file_size = os.path.getsize(filename)
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
print("Transmitting %d Packets." % (file_size/256))
@ -43,7 +43,7 @@ def transmit_file(filename, tx_object):
tx_object.wait()
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign)
tx = PacketTX.PacketTX(debug=debug_output, callsign=callsign, fec=fec)
tx.start_tx()
image_id = 0
@ -53,10 +53,10 @@ try:
# 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)
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
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)
# SSDV'ify the image.
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
# 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)
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 = PacketTX.PacketTX(debug=debug_output)
tx = PacketTX.PacketTX(debug=debug_output,fec=False)
tx.start_tx()
print("TX Started. Press Ctrl-C to stop.")
try:
while True:
for img in image_numbers:
filename = file_path % img
print("\nTXing: %s" % filename)
transmit_file(filename,tx)
for img in image_numbers:
filename = file_path % img
print("\nTXing: %s" % filename)
transmit_file(filename,tx)
tx.close()
except KeyboardInterrupt:
print("Closing...")
tx.close()