Adding Even Parity to One Port in Tracy Allen's 4-Port Serial Object

I was going to post this in Tracy Allen's four port serial thread but I didn't want to hijack the thread so I'm starting at new thread to ask my question about even parity.
I'm hoping to add even parity to one of the port's tx line.
In an attempt to change port #1 to output even parity, I changed the "transmit 1" code from:
To:
Does this look like it should work?
As I understand it, the parity bit is the second to the last bit. "shl txdata1,#2" makes room for two stop bits. Only one of the stop bits is high in the original code. Am I correct in thinking I want the second to the last bit also high if there are an odd number of bits in "txdata1"?
"txbitor" (1,025 or $401) adds two bits to txdata1. One bit is the start bit in the tenth position from the right and the other is the final stop bit in the far right position.
From my reading on the internet, it looks like even parity means the total number of bits sent per character should be even. The parity bit is added when the total number of bits would otherwise be odd.
I haven't tested this. I think I have a program that will provide even parity output so I'll likely compare the output from the Propeller with the output from the even parity program (Modbus Poll).
Is there a better way to do this?
I'm hoping to add even parity to one of the port's tx line.
In an attempt to change port #1 to output even parity, I changed the "transmit 1" code from:
'' Transmit1 -------------------------------------------------------------------------------------
'
transmit1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return
txcts1 test ctsmask1,ina wc 'if flow-controlled dont send
rdlong t1,tx_head_ptr1
cmp t1,tx_tail1 wz
ctsi1 if_z jmp #transmit1 'may be patched to if_z_or_c or if_z_or_nc
rdbyte txdata1,txbuff_tail_ptr1
add tx_tail1,#1
cmpsub tx_tail1,txsize1 wz ' (TTA) for individually sized buffers, will zero at rollover
wrlong tx_tail1,tx_tail_ptr1
if_z mov txbuff_tail_ptr1,txbuff_ptr1 'reset tail_ptr if we wrapped
if_nz add txbuff_tail_ptr1,#1 'otherwise add 1
jmpret txcode1,rxcode2 'run a chunk of receive code, then return
shl txdata1,#2
[COLOR=#ff0000] [B]or txdata1,txbitor 'ready byte to transmit[/B][/COLOR]
mov txbits1,#11
mov txcnt1,cnt
txbit1 shr txdata1,#1 wc
txout1 muxc outa,txmask1 'maybe patched to muxnc dira,txmask
add txcnt1,bit_ticks1 'ready next cnt
:wait1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return
mov t1,txcnt1 'check if bit transmit period done
sub t1,cnt
cmps t1,#0 wc
if_nc jmp #:wait1
djnz txbits1,#txbit1 'another bit to transmit?
txjmp1 jmp ctsi1 'byte done, transmit next byte
To:
shl txdata1,#2
[COLOR=#ff0000] or txdata1,txbitor wc 'ready byte to transmit[/COLOR]
[COLOR=#ff0000] if_c or txdata1,#2 'add even parity if needed? [/COLOR]
mov txbits1,#11
Does this look like it should work?
As I understand it, the parity bit is the second to the last bit. "shl txdata1,#2" makes room for two stop bits. Only one of the stop bits is high in the original code. Am I correct in thinking I want the second to the last bit also high if there are an odd number of bits in "txdata1"?
"txbitor" (1,025 or $401) adds two bits to txdata1. One bit is the start bit in the tenth position from the right and the other is the final stop bit in the far right position.
From my reading on the internet, it looks like even parity means the total number of bits sent per character should be even. The parity bit is added when the total number of bits would otherwise be odd.
I haven't tested this. I think I have a program that will provide even parity output so I'll likely compare the output from the Propeller with the output from the even parity program (Modbus Poll).
Is there a better way to do this?
Comments
Here's the original
PUB tx(port,txbyte) '' Send byte (may wait for room in buffer) if port > 3 abort repeat until (tx_tail[port] <> (tx_head[port] + 1) // txsize[port]) byte[txbuff_ptr[port]+tx_head[port]] := txbyte tx_head[port] := (tx_head[port] + 1) // txsize[port] if rxtx_mode[port] & NOECHO rx(port)
Here's the changed version:PUB tx(port,txbyte) '' Send byte (may wait for room in buffer) if port > 3 abort repeat until (tx_tail[port] <> (tx_head[port] + 1) // txsize[port]) if port == 1 ' The following computes the odd parity, then inverts it and places the bit into the high order bit of txbyte. It could be done faster in PASM, but this works. txbyte |= (((txbyte & $40 << 1) ^ (txbyte & $20 << 2) ^ (txbyte & $10 << 3) ^ (txbyte & $08 << 4) ^ (txbyte & $04 << 5) ^ (txbyte & $02 << 6) ^ (txbyte & $01 << 7)) & $80) ^ $80 byte[txbuff_ptr[port]+tx_head[port]] := txbyte tx_head[port] := (tx_head[port] + 1) // txsize[port] if rxtx_mode[port] & NOECHO rx(port)
For now the planned baud is 19,200 bps.
I noticed in the Propeller manual the "or" command will set the c flag based on the parity. I thought I'd take advantage of this and make the change in PASM.
My first attempt as posted above did not work correctly. I'll change the "if_c" to "if_nc" and see if it works. If not, I'll use your Spin code.
Thank you very much.
BTW, This port will be communicating via Modbus. I was fortunate to come across one of your earlier posts with Modbus CRC code which worked perfectly. Thanks for this and the CRC code both!
RS232 data is shifted out lsb first. So you have the parity bit in the wrong position.
[SIZE=1][FONT=courier new]txbit1 shr txdata1,#1 wc ' <--- shift right, stop, start, lsb to msb, stop txout1 muxc outa,txmask1 [/FONT][/SIZE]
The msb is at bit position 9, so, I think the following should work. (You can't use #512 as a literal.)[SIZE=1][FONT=courier new] or txdata1,txbitor wc 'ready byte to transmit, test parity muxc txdata1,parityBit 'add even parity if needed? ... DAT txbitor long $0401 parityBit long %10_0000_0000 [/FONT][/SIZE]
This is predicated on having a zero in the msb at entry. XOR could work even if a one bit wiggled in there.[SIZE=1][FONT=courier new] if_c xor txdata1,parityBit [/FONT][/SIZE]
I think your logic for creating even parity is otherwise correct. The OR operation will set carry if parity is odd, so inserting the "1" bit will make it even. The pasm is cleaner than the Spin in the sense that pasm calculates the parity for you.It's supposed to be 8-bit even parity.
The lsb first thing was just occurring to me as I was looking at the logic traces.
I noticed the Propeller didn't like receiving 8-bit even one stop bit from Modbus Poll. I had to set the the parity to none for the Propeller to receive the command. As I mentioned earlier, I'm using your 4-port object (probably incorrectly).
I think I might need to go work on a robot for a while. I'm feeling the insanity creeping in from watching too many bits go by on the LA.
I'll report back on progress (or not) later.
Thank you very much Tracy and Mike. I'm not sure what I'd do without the help I receive on the forum.
Right now I'm not sure about this.
This is the setting on Modbus Poll I'm hoping to use when communicating with the Propeller.
Am I correct in thinking this setting corresponds with the notation 8E1?
Here's a LA trace from the output using the above settings. As indicated by the LA, the output is a zero followed by a one.
I don't want you to have to spend your time teaching me basic serial communication. I need to spend some time and get start and stop bits straight in my mind. I'm guessing the even parity bit takes the place of what had been the stop bit?
I'll do some more testing (and reading) and try to come back with some (hopefully) intelligent questions.
Thank you for your help so far.
Edit: I'm pretty sure I can make the changes to the serial driver you suggest. I just need to figure out what I really want the driver to do.
Yes, 8E1. 9 bits total between start and stop, compared with 8 bits between start and stop for 8N1. There is still a stop bit, after parity.
That's exactly want I wanted to know.
The code didn't change much from post #1 but Tracy's diagram of the various bits was just what I need to figure this out.
shl txdata1,#2 or txdata1,[COLOR=#ff0000]txbitor1 wc[/COLOR] 'ready byte to transmit [COLOR=#ff0000]if_c or txdata1,parity[/COLOR] 'add even parity if needed? mov txbits1,#11
I originally used:
mov txbits1,#[COLOR=#ff0000]12[/COLOR]
But I found the LA traces included two stop bits rather than just one. I realize now the normal code uses two stop bits not just one.
The Modbus Poll program read the 8E2 data just fine but I changed the "#12" back to "#11" and got rid of the extra stop bit. I believe the code now outputs 8E1 serial data!
I shifted the msb of txbitor left one position and stored in the variable "txbitor1"
As Tracy pointed out the parity bit couldn't be added as a literal so I added one more variable "parity".
I added these extra variables to the list after "txbitor".
txbitor long $0401 'bits to or for transmitting, adding start and stop bits [COLOR=#ff0000]txbitor1 long $0801[/COLOR] 'bits to or for transmitting, adding start and stop bits [COLOR=#ff0000]parity long $0400[/COLOR] ' 1 << 10 ' where stop bit had been for no parity
I changed the number of bits read in the rx portion of port #1 from 9 to 10 and modified the "justify and trim" line.
mov rxbits1,#[COLOR=#ff0000]10[/COLOR] 'ready to receive byte +1 DWD
shr rxdata1,#32-[COLOR=#ff0000]10[/COLOR] 'justify and trim received byte +1 DWD
I'm not bothering to check the parity on the incoming messages. I have Mike's CRC algorithm checking the entire message so it's not a big deal if a bad byte gets by.
This is great to be able to modify the serial object like this. It's very "empowering". Tracy, I guess I did want you to spend your time teaching me basic serial communication after all.
Thank you again Tracy!
It does do the trick doesn't it.
LSB leading is named Little Endian. MSB leading is know as Big Endian. You'll notice them in various cases from time to time from now on. You might also recognise you'd missed it in the past as well.
On the subject of Modbus; Are you explicitly using binary mode? If not then 7E1 is the nicer fit.
Yes, I'm aware of Endianess. I've done enough LED projects that shift bytes around in memory to be very aware of what bytes go where in longs. I remember when I first learned the Prop was Little Endian, I thought Chip must be crazy for doing something so counter intuitive. Now that I've been programming the Prop (a lot) over the last six years I can see how Little Endian is really the only choice that makes sense for the Propeller.
Yes, it's RTU Modbus. Data being sent Big Endian for each register but least significant register first (for 32-bit values).
The programs Modbus Poll and Modbus Slave along with a logic analyzer finally made the makeup of a Modbus packet make sense. I was having trouble with the CRC until I found Mike Green's Spin code. Tracy Allen's guidance helped me finish finally figure out how to add even parity. This forum is great!
16 bit "registers" I presume? So, the byte significance order is 1, 0(LSB), 3(MSB), 2?
Yes, or in Modbus Poll's notation, "CD AB" where"A is MSB and D is LSB.
The strange byte order is only used when sending data. It's stored in the Propeller's "registers" as little endian longs. Having the (double) registers as longs makes using the values much easier in the program. The bytes get unscrambled with received and scrambled again when sent.
You noticed that the existing code sends two stop bits instead of one. A couple of people have complained to me about that in PMs, why 2 bits, slow down the tx, why 11 bits instead of 10? It is fairly easy to correct that, per the code segment below. The old way commences by shifting out a stop bit (huh?!), then the start bit, then 8 bits of data, then stop. So in operation, the two stop bits you see are coming from two ends of the candle, as it were. The code below commences right in with a start bit, then 8 data bits, and one stop bit. The txbitor1 $FFFFFE00 allows you to get up to 23 stop bits (interchar pacing) by patching the number of bits, because the extras will be stop bits. Your 8N1 would patch in pretty much like you have it.
The OBEX object is still the old way, with two stop bits on tx. That dates I think from the original fullDuplexSerial. Someday soon I'll update it.
[SIZE=1][FONT=courier new]' Transmit1 ------------------------------------------------------------------------------------- transmit1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return txcts1 test ctsmask1,ina wc 'if flow-controlled dont send rdlong t1,tx_head_ptr1 cmp t1,tx_tail1 wz ctsi1 if_z jmp #transmit1 'may be patched to if_z_or_c or if_z_or_nc (CTS handler, open vs driven, true vs inverted) rdbyte txdata1,txbuff_tail_ptr1 add tx_tail1,#1 cmpsub tx_tail1,txsize1 wz ' (TTA) for individually sized buffers, will zero at rollover wrlong tx_tail1,tx_tail_ptr1 if_z mov txbuff_tail_ptr1,txbuff_ptr1 'reset tail_ptr if we wrapped if_nz add txbuff_tail_ptr1,#1 'otherwise add 1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return shl txdata1,[COLOR=#ff0000]#1[/COLOR] ' shifting left only one for start bit (TTA) or txdata1,[COLOR=#ff0000]txbitor1[/COLOR] 'ready byte to transmit (TTA, txbitor1 instead of txbitor) mov txbits1,[COLOR=#ff0000]#10[/COLOR] ' 10 bits only, start,8 data, stop (TTA, 10 not 11, was stop,start,8data,stop) mov txcnt1,cnt txbit1 shr txdata1,#1 wc txout1 muxc outa,txmask1 'may be patched to muxnc dira,txmask to support open vs driven mode add txcnt1,bit_ticks1 'ready next cnt :wait1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return mov t1,txcnt1 'check if bit transmit period done sub t1,cnt cmps t1,#0 wc if_nc jmp #:wait1 djnz txbits1,#txbit1 'another bit to transmit? txjmp1 jmp ctsi1 'byte done, transmit next byte 'need also, [COLOR=#ff0000]txbitor1 long $FFFFFE00[/COLOR] ' start and stop bit(s)for tx [/FONT][/SIZE]
Two stops at 9600:
One stop at 9600:
I had wondered why we were shifting the byte by two places initially. Your new code (and your explanation) makes much more sense as I attempt to understand it than the previous code with a leading stop bit.
My modifications in post #10 left in this leading stop bit but took out the trailing one. Since the line idles high, the missing final stop bit didn't cause a problem. I will change my driver to match your new changes. Now I'm surprised the code in post #10 worked.
Thank you again for your help!
shl txdata1,#[COLOR=#ff0000]1 [/COLOR] or txdata1,[COLOR=#ff0000]txbitor1[/COLOR] wc 'ready byte to transmit [COLOR=#ff0000] if_nc or txdata1,parity[/COLOR] 'add even parity if needed? mov txbits1,#11
I think I neglected to mention, by removing the extra stop bit, I had to change the parity check from "if_c" to "if_nc".
Here are the current values of the added variables.
txbitor long $0401 'bits to or for transmitting, adding start and stop bits [COLOR=#ff0000]txbitor1 long $0400[/COLOR] 'bits to or for transmitting, adding start and stop bit [COLOR=#ff0000]parity long $0200[/COLOR] ' 1 << 9
I haven't removed the extra stop bit from the other ports yet.
I agree that it shouldn't be too hard to make the parity configurable but doing so would increase the size of the program. I've already had to make a two port version of your driver for a project that was running out of space. I'm not anxious to increase the size of the four port driver.
I think it would be nice to include instructions on how to modify the driver to add parity if desired. The instructions could even be a link to the forum where adding parity is shown.
How do devices which require parity respond to incorrect parity? Do they ignore just the character or do they ignore the whole message? As I type this I realize it's likely a "it depends" type answer. I can see a Modbus device initially ignoring a character with a bad parity bit but the ignored character would also cause the CRC to be incorrect for the message so the entire message would be ignored.
It sure seems like having parity and a CRC is kind of like having a belt and suspenders.
Thank you again for your help.
One thing I've added to my own version is another fast co-routine that implements a poke and peek function from spin to the cog registers. I originally wrote it to allow access to the cog counter registers, but it also allows patching stuff like the baud rate or pins used by a port, without stopping and restarting the cog. As always, poke is a dangerous function and requires knowing where everything is.
One question: It appears that no modifications are required to the "port receive" code to handle even parity? (Yes, I'm working on a Modbus project.) If I'm thinking correctly, when the parity bit is low, the "wait for stop bit" routine will just have to wait a bit longer for the stop bit. If the parity bit is high, "wait for stop" will assume it received the stop bit and exit. Almost like 2 stop bits?
P.S. Once again, the four-port serial driver is simply amazing!
This is the port #1(the port with even parity) PASM section of the serial driver I frequently use. The entire PASM code exceeded the character limit of the forum software.
' ' Receive1 ------------------------------------------------------------------------------------- ' receive1 jmpret rxcode1,txcode1 'run a chunk of transmit code, then return test rxmask1,ina wc start1 if_c jmp #norts1 'go check rts if no start bit mov rxbits1,#10 '** 9 'ready to receive byte mov rxcnt1,bit4_ticks1 '1/4 bits add rxcnt1,cnt :bit1 add rxcnt1,bit_ticks1 '1 bit period :wait1 jmpret rxcode1,txcode1 'run a chuck of transmit code, then return mov t1,rxcnt1 'check if bit receive period done sub t1,cnt cmps t1,#0 wc if_nc jmp #:wait1 test rxmask1,ina wc 'receive bit on rx pin rcr rxdata1,#1 djnz rxbits1,#:bit1 test rxtx_mode1,#INVERTRX wz 'find out if rx is inverted if_z_ne_c jmp #receive1 'abort if no stop bit (TTA) (from serialMirror) jmpret rxcode1,txcode1 'run a chunk of transmit code, then return shr rxdata1,#32-10 '** 9 'justify and trim received byte wrbyte rxdata1,rxbuff_head_ptr1 '7-22 add rx_head1,#1 cmpsub rx_head1,rxsize1 ' (TTA) allows non-binary buffer size wrlong rx_head1,rx_head_ptr1 mov rxbuff_head_ptr1,rxbuff_ptr1 'calculate next byte head_ptr add rxbuff_head_ptr1,rx_head1 norts1 rdlong rx_tail1,rx_tail_ptr1 '7-22 or 8 will be patched to jmp #r3 if no rts mov t1,rx_head1 sub t1,rx_tail1 wc if_c add t1,rxsize1 ' fix wrap, (TTA) change cmps t1,rtssize1 wc rts1 muxc outa,rtsmask1 rec1i jmp #receive1 'byte done, receive next byte ' ' TRANSMIT ======================================================================================= ' ' Transmit1 ------------------------------------------------------------------------------------- ' transmit1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return txcts1 test ctsmask1,ina wc 'if flow-controlled dont send rdlong t1,tx_head_ptr1 cmp t1,tx_tail1 wz ctsi1 if_z jmp #transmit1 'may be patched to if_z_or_c or if_z_or_nc rdbyte txdata1,txbuff_tail_ptr1 add tx_tail1,#1 cmpsub tx_tail1,txsize1 wz ' (TTA) for individually sized buffers, will zero at rollover wrlong tx_tail1,tx_tail_ptr1 if_z mov txbuff_tail_ptr1,txbuff_ptr1 'reset tail_ptr if we wrapped if_nz add txbuff_tail_ptr1,#1 'otherwise add 1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return ''** shl txdata1,#1 '** 2 '** changed for even parity or txdata1,txbitor1 wc '** txbitor if_nc or txdata1,parity 'add even parity if needed? '** added for parity mov txbits1,#11 mov txcnt1,cnt txbit1 shr txdata1,#1 wc txout1 muxc outa,txmask1 'maybe patched to muxnc dira,txmask add txcnt1,bit_ticks1 'ready next cnt :wait1 jmpret txcode1,rxcode2 'run a chunk of receive code, then return mov t1,txcnt1 'check if bit transmit period done sub t1,cnt cmps t1,#0 wc if_nc jmp #:wait1 djnz txbits1,#txbit1 'another bit to transmit? txjmp1 jmp ctsi1 'byte done, transmit next byte txbitor long $0401 'bits to or for transmitting, adding start and stop bits txbitor1 long $0400 'bits to or for transmitting, adding start and stop bit parity long $0200 ' 1 << 8 ' where stop bit had been for no parity
The receive code receives the extra bit but it doesn't check the parity.
I've attached the first version of a driver with parity I could find. The Spin portion of the code is different from the version Tracy originally posted. You should be able to use the PASM section from my object in a more traditional version of Tracy's driver.
I use the attached code mainly for debugging programs since it uses locks and can be used from multiple objects and multiple cogs.
Looked at your code--oh yes, you are completely correct with regards to that version! I'm actually using a four-port serial driver that has the following acknowledgements:
' Based in part upon full-duplex serial driver v1.01 ' Tracy Allen (TTA) (c) 22-Jan-2011 ' Copyright (c) 2006 Parallax, Inc. * ' ' Other attributions include, in alphabetic order: ' Juergen Buchmueller ' Duane Degn ' Tim Moore '____________________________________________________________________________________________ ' ' Copyright (c) 2015 by Wayne Meretsky ' ' This version of the serial port driver was rewritten entirely from scratch in early ' 2015 with three goals, in order: Correctness, Performance, Code Size. While it is ' an entirely new implementation, it draws heavily on the design and implementation of ' many original and derived works that preceed it.
And the receive code for one of the ports:
' Receiver for Port 0 ' P0_Rx mov P0_Rx_BC,#8 ' We need to read 8 data bits mov P0_Rx_Timer,P0_SO ' Get the sample offset :wait_for_start jmpret Rx0_PC,Tx0_PC ' Run some Transmit code and return here test P0_RxM,ina wc ' Move the RxD into Carry if_c jmp #:wait_for_start ' Wait for the start bit to arrive add P0_Rx_Timer,cnt ' Compute the sample time :next_bit add P0_Rx_Timer,P0_TPB ' Add one bit time :wait jmpret Rx0_PC,Tx0_PC ' Run some Transmit code and return here mov t1,P0_Rx_Timer ' Get the sample time sub t1,cnt ' Compare to the current time cmps t1,#0 wc if_nc jmp #:wait ' And wait until it's time to sample test P0_RxM,ina wc ' Get RxD into Carry rcr P0_RxD,#1 ' Rotate it into our byte; we receive LSB first djnz P0_Rx_BC,#:next_bit jmpret Rx0_PC,Tx0_PC ' Run some Transmit code and return here shr P0_RxD,#32-8 ' Position the byte into the LSBs mov t1,P0_RxB add t1,P0_Rx_II wrbyte P0_RxD,t1 add P0_Rx_II,#1 ' and maintain the buffer structure cmpsub P0_Rx_II,P0_RxS wrlong P0_Rx_II,P0_Rx_II_Ref :wait_for_stop jmpret Rx0_PC,Tx0_PC ' Run some Transmit code and return here test P0_RxM,ina wc ' Move the RxD into Carry if_nc jmp #:wait_for_stop ' Wait for the stop bit to arrive jmp #P0_Rx ' Go get the next byte
In MY case, then, it appears that the code does not need modified to receive even parity. Because the parity bit is immediately followed by the stop bit, and the code has a separate loop to wait for the stop bit. It seems to me that if the parity bit is high, it'll be seen as "two stop bits", otherwise identified by a longer delay before the next "start" bit can come. And if the parity bit is low, it'll be seen as an "extra long data bit", and the "wait for stop" loop will simply wait a little longer until the line goes high (for the actual stop bit.)
Didn't realize that we were talking about two entirely different versions of a four-port serial driver!
http://forums.parallax.com/discussion/160275/reduced-latency-1x-and-4x-serial/p1