Welcome to the Parallax Discussion Forums, sign-up to participate.
var
long cog ' cog flag/id
long rxp ' rx smart pin
long txp ' tx smart pin
long rxhub ' hub address of rxbuf
long txhub ' hub address of txbuf
long rxhead ' rx head index
long rxtail ' rx tail index
long txhead ' tx head index
long txtail ' tx tail index
long txdelay ' ticks to transmit one byte
byte rxbuf[BUF_SIZE] ' buffers
byte txbuf[BUF_SIZE]
byte pbuf[80] ' padded strings
long rxhead ' rx head index
long rxtail ' rx tail index
long txhead ' tx head index
long txtail ' tx tail index
long rxhead ' rx head index
long rxtail ' rx tail index
long txhead ' tx head index
long txtail ' tx tail index
long rxsize ' rx buffer size
long txsize ' tx buffer size
byte rxbuf[BUF_SIZE] ' buffers
byte txbuf[BUF_SIZE]
long rxhead1 ' rx head index
long rxtail1 ' rx tail index
long txhead1 ' tx head index
long txtail1 ' tx tail index
long rxsize1 ' rx buffer size
long txsize1 ' tx buffer size
long rxhead2 ' rx head index
long rxtail2 ' rx tail index
long txhead2 ' tx head index
long txtail2 ' tx tail index
long rxsize2 ' rx buffer size
long txsize2 ' tx buffer size
long rxhead3 ' rx head index
long rxtail3 ' rx tail index
long txhead3 ' tx head index
long txtail3 ' tx tail index
long rxsize3 ' rx buffer size
long txsize3 ' tx buffer size
long rxhead4 ' rx head index
long rxtail4 ' rx tail index
long txhead4 ' tx head index
long txtail4 ' tx tail index
long rxsize4 ' rx buffer size
long txsize4 ' tx buffer size
byte rxbuf1[BUF_SIZE1] ' buffers
byte txbuf1[BUF_SIZE1]
byte rxbuf2[BUF_SIZE2] ' buffers
byte txbuf2[BUF_SIZE2]
byte rxbuf3[BUF_SIZE3] ' buffers
byte txbuf3[BUF_SIZE3]
byte rxbuf4[BUF_SIZE4] ' buffers
byte txbuf4[BUF_SIZE4]
Comments
The UART manager cog could check a mailbox that would have a long broken into a word and two bytes
word[1] is the hub address of the port structure
byte[1] is add or remove
byte[0] is the port (0 to 7) to add or remove
If there are not port changes a value of 0 in the mailbox would indicate there is nothing to do but check on the buffers.
If a buffer is added, a bit flag is added to available ports and the cog reads the configuration data it needs (mostly pointers) from the hub. If a port is removed the bit is cleared so that those pins are no longer checked on scans.
The foreground Spin can configure the smart pins for serial to take that burden off of the cog -- this keeps things simple (I think).
Thanks for commenting so quick Jon.
I think we would need a table of bits for the currently in-use ports so a byte for 8 ports. I do this for mapping used stay-resident cogs in my P1 OS and it woks nicely.
I think the hub address of the port structure should be at least 20 bits but we could combine the port number and add/remove bit within the long, so your mailbox would work.
BUT, the problem comes with multiple cogs trying to get a port concurrently. We would need to use a lock or we have to have separate longs for each port and continuously read them all by the driver cog.
Now if we used 2 longs (ie 8 bytes) as just a flag for each port, the driver could read these 2 longs using a setq #2-1 & rdlong and compare with zero, OR the driver could read each long using the wz flag. Neither of these would take too long to interfere with the rx/tx scanning. A further set of 8 longs would follow with one dedicated to each port. This would be quicker than using locks.
Smartpins may as well be set/cleared by the driver cog as there is plenty of cog space and it's only a couple of instructions. Might just need to be interleaved within the port scanning. Thinking more, yes, could be better for the calling cog to set the smartpins as the bitper needs to be calculated from baud and clkfreq .
Do you see that the data buffers for each port should be in a contiguous area in hub? Or do you think separate areas for each port would be better?
IMHO it would be nice to have separate hub areas but it's more complex and might be even more confusing to newcomers.
And if they are contiguous then there is no need for the hub pointers for each port.
And INCMOD allows sizes to not be a binary multiple.
Yes, I stole it from you. It just needs to be a variable
If rx and tx buffers are all contiguous...
Or if we allowed tx/rx buffer pairs to be anywhere in hub, then this...
I've not looked at this code/design in detail but one (probably helpful) reason to separate port control into a byte per port is that it allows the independent port control settings to be setup/changed independently from different COG contexts without extra code needed to preserve any other port state during the change. i.e. it potentially avoids the read-modify-write cycle needed to change the value for the port control byte when the byte containing it in hub memory spans two actual port control nibbles, and you can then just do a single byte write atomically instead and simply clobber its prior value as you change it. There's no additional nibble shifting needed either. In this case sure it might burn an extra long of hub memory, but with 512kB I'd say it would be well worth it.
In my embedded/middleware work over the years I've found it's useful to do little things like that for assisting multi-threaded code, particularly when state can change dynamically after initialization. If you try to pack things a bit too tight you can come unstuck in some areas so it's a tradeoff as usual.
Maybe make two versions, one supporting eight ports with a lower top speed, and the other with four ports for higher speeds.
This has led me to the following thoughts and I'd like to know if this makes sense...
* Sixteen uni-directional ports each with its' own control byte in an array (solves read-modify-write or lock problems)
* Each port is for 1 pin only and can be either a receive or a transmit pin (ie you can have 16 receivers if you want)
* Each port will have a p_head and p_tail which are pointers to hub addresses (ie not offsets)
* At initialisation the p_head and p_tail will point to the first and last+1 hub address of the buffer
* The calling code will setup and disable the smart pin (ie the driver will not program the smart pin associated with the port)
' Main hub interface (allows up to 16 uni-directional ports)
port_control 'byte 0[16] '\ control (16 ports) %atpppppp where...
'| a: 1 = port active, t: 1 = tx, 0 = rx, pppppp: = port pin
byte 1<<7 | 0<<6 | 63 '| port[0]: active, rx, P63
byte 1<<7 | 1<<6 | 62 '| port[1]: active, tx, P62
byte 0[14] '| port[2]-[15]: = inactive/idle
port_params 'long 0[2][16] '| max (16 ports) of 2 longs... (note: only need to reserve as many ports as max required)
long @xbuf_0 '| port[0]: p_head (initially set to start of buffer)
long @xbuf_0_end '| p_tail (initially set to end of buffer)
long @xbuf_1 '| port[1]: p_head (initially set to start of buffer)
long @xbuf_1_end '| p_tail (initially set to end of buffer)
' long 0[2][14] '/ port[2]-[15]: (no need to fill/reserve unused ports)
' buffers (no need to follow port_params)
xbuf_0 long 0[128] ' port[0]: buffer
xbuf_0_end
xbuf_1 long 0[128] ' port[1]: buffer
xbuf_1_end
This minimises the hub when only a small number of uni-directional ports will be used. Just leave space for the maximum number of supported pins (ie uni-directional ports).
This simplifies the number of instructions needed to be executed in the pasm driver, and therefore maximises the number of ports and speed that can be supported.
This has led me to the following thoughts and I'd like to know if this makes sense...
* Sixteen uni-directional ports each with its' own control byte in an array (solves read-modify-write or lock problems)
* Each port is for 1 pin only and can be either a receive or a transmit pin (ie you can have 16 receivers if you want)
* Each port will have 4 long parameters...
- p_head pointer to current head hub address of the buffer (ie not an offset)
- p_tail pointer to current tail hub address of the buffer (ie not an offset)
- p_first pointer to first hub address of the buffer (ie not an offset)
- p_end pointer to last+1 hub address of the buffer (ie not an offset)
* The calling code will setup and disable the smart pin (ie the driver will not program the smart pin associated with the port)
This minimises the hub when only a small number of uni-directional ports will be used. Just leave space for the maximum number of supported pins (ie uni-directional ports).
This simplifies the number of instructions needed to be executed in the pasm driver, and therefore maximises the number of ports and speed that can be supported.
By having p_first and p_last available it reduces the number of instructions necessary to calculate the start and end and can be accessed easily by both spin and pasm objects.
It’s probably advisable for the setup code to walk the table to look for pin conflicts before adding new entries. That wouldn’t be particularly difficult to achieve.
Same applies to configure the smart pins. The caller setting up the smartpins also lets the caller configure baud and number of bits.
I’m almost ready to test it out but i will not have time tomorrow due to day job. Mondays are always hectic.
While it’s unusual, having half-duplex (ie single pin uni-directional) ports seems like it will work nicely. You can have any mix of receive and transmit pins (ports) to a maximum of 16 pins. So you can have 1 transmit and 15 receive, or any other mix to a total of 16.
Using SETQ and rdlong minimise the number of clocks when getting parameter blocks from hub. P2 has some really neat instructions.
Yes, it does.
Yes, it's easy enough for the caller to walk the table and check the pin's not in use. Of course it cannot know if the pin is being used by a different driver eg I2C.
True.
Code looks ready for testing to me; Seems to be striking a good balance between clarity and speed.
Yes. Just ran out of time to test it with a new front-end. I will build at least two front-ends. One will just be a full duplex (ie 2 port pins) object that will be pre-configured for the masses. The other will be an extendable version and it will probably undergo a few revisions to get it easy to use.
Only tested with FlexProp v5.0.7
pnut and PropTool testing to come soon. pnut working.
mpx_multiportserialdriver.spin2
This is the pasm driver that is loaded into it's own cog. The code is working and AFAIK with no bugs. It will support up to 16 half duplex ports - any mix of receive ports (pins) and transmit ports (pins) up to a total of 16. Smartpins are setup in the calling program, as are the port control and parameters.
mpx.demo.spin2
This is a demo program and just calls mpx_fullduplexserial.spin2. This is a simple demo program that uses a few methods to display and receive some serial data. Nothing major here.
mpx_fullduplexserial.spin2
This program is based on @JonnyMac 's jm_fullduplexserial.spin2 code. This is great code which includes methods to display strings with embedded hex/decimal/binary etc. A really great object.
I have added some additional methods to display such things as hub dumps which includes both hex and ascii representation.
Also, this is now using my new mpx_multiportserialdriver.spin2 driver.
Warning: I know there are some problems in the receive section because I haven't completed the process to use the new style receive buffers. I have just released it so you can get a preview. AFAIK the transmit methods should work correctly.
If you find any bugs please let me know here, aside from the receive methods (rxcheck, rxavailable, etc) which haven't been converted yet.
See next post for latest code
txflush, rxcheck, available, etc should now be working
Please report bugs here.
Glad to hear you have this running. I do hope that diverbob is following this thread as I know he needs at least 6 serial ports for his robot.
Jim