On 21/04/17 18:19, Rodolfo Giometti wrote: > On 04/21/17 00:14, Stuart Longland wrote: >> On 21/04/17 02:17, Rodolfo Giometti wrote: >>> AFAIK the multidrop mode is supported also by Freescale (IMX6 and maybe >>> IMX7) even if by using directly 9 bits transmission. The SENDA trick >>> (that is autogically sending the next first byte as an address while the >>> following ones as data) is Atmel specific. Atmel solution is really >>> smart (even if limited to 1 byte addresses long) because it allows >>> developers to send a message without any delay between the address and >>> the data bytes. >> >> I've seen similar features on the NXP LPC81x series of microcontrollers. >> >> http://www.nxp.com/documents/user_manual/UM10601.pdf page 187 onwards >> covers the USART for these chips. The data register is natively 32-bits >> wide, so can accommodate the 9th bit easily. >> >> On Atmel AVR, I've also seen it, there the 9th bit is held in a separate >> register from what I recall, don't have a datasheet in front of me to >> look it up. > > Yes, Atmel CPUs can handle multidrop in two manners: the first one is by > using the 9bits transmission while the second is by using the SENDA bit. > The latter is obviously less versatile and less general then the former > but it really better fits into actual Linux TTY subsystem. Indeed, and if someone were to later figure out CS9 support, I doubt the two are mutually exclusive. >> The 9-bit mode sounded like a good idea, and I thought of how to >> possibly implement it, but then I had to contend with where would I put >> the extra bit in an 8-bit byte? > > This is exactly the problem I notice in adding multidrop support in > Linux trying to avoid the two calls of the tcsetattr() function which > introduce a delay between the address and data bytes. Fully agreed there… lots of protocols are sensitive to that kind of timing. Modbus/RTU, although it is purely 8-bit, is one that comes to mind where it comes to timing between adjacent bytes. > IMHO the best solution would be using 9bits transmission (CS9 define): > > term.c_cflag &= ~CSIZE; > term.c_cflag |= CS9; > ret = tcsetattr(fd, TCSADRAIN, &term); > ... > > /* Write data */ > n = 0; > buf[n++] = 0x100; > buf[n++] = 0x01c; > buf[n++] = 0x001; > buf[n++] = 0x001; > buf[n++] = 0x000; > buf[n++] = 0x001; > buf[n++] = 0x0de; > buf[n++] = 0x0d4; > ret = writen(fd, buf, 2*n); > ... Okay, this is obviously not pretty, but is it possible to cast the stream of bytes into 16-bit words (host native byte ordering)? Userspace is responsible from the first byte written after CS9 is set, until the last word before CS9 is cleared, in ensuring that all writes are an even number of bytes, or -EMSGSIZE is returned ("too big" in the sense you've sent one more than is valid). This can be checked before we start handling the actual data. If we ensure that the buffer is always allocated on a 16-bit alignment (or better; native word size alignment, so 32 or 64-bits), then you can treat the buffer as an array of uint16_t instead of uint8_t without issue. The cleaner way around is to make the tty layer natively deal with uint16_t… technically superior, but as you point out, that's a lot of work. > Then my solution tries to keep it simple just still > using 8bits transmission but adding a system call to define how many > address bytes we have into next message: > > /* Transmission: enable parity multidrop */ > term.c_cflag |= PARENB | CMSPAR | PARMD; > ret = tcsetattr(fd, TCSADRAIN, &term); > ... > > mdrop.addr_len = 1; > ret = ioctl(fd, TCSSENDA, &mdrop); > ... > > /* Write data */ > n = 0; > buf[n++] = 0x00; > buf[n++] = 0x1c; > buf[n++] = 0x01; > buf[n++] = 0x01; > buf[n++] = 0x00; > buf[n++] = 0x01; > buf[n++] = 0xde; > buf[n++] = 0xd4; > ret = writen(fd, buf, n); > ... > > [Note that Atmel CPUs support only 1 byte for address length] That looks clean and simple enough… I used the example of two address bytes to depict a "generic" solution, with the thought of that `mdrop` struct perhaps having a couple of members: struct mdrop_t { int addr_len; uint8_t* addr; } mdrop; uint8_t addr[1] = {0x00}; mdrop.addr_len = 1; mdrop.addr = addr; ret = ioctl(fd, TCSSETADDR, &mdrop); Then each frame, you'd call ret = ioctl(fd, TCSSENDA); before writing a frame. That would automatically prepend that buffer. Want to send another frame? Call ioctl(fd, TCSSENDA) again then start writing. Your example there though would be better if you're sending to lots of addresses… in my example, you'd have to call TCSSETADDR again to set a different address. In that regard, your solution is much simpler (and requires fewer ioctls to boot). The thorny issue IMO is on receive. Userspace really needs to know where the address starts and ends. You can assume a fixed size, but if the protocol uses a variable address length, you're screwed. Whilst not essential, an in-kernel frame address filter could be useful for that purpose. I'm taking the idea from how CANbus filters work. (Thus, LinuxCAN might be worth looking at for ideas here.) http://www.cse.dmu.ac.uk/~eg/tele/CanbusIDandMask.html The idea being that the kernel starts to buffer when it sees the "address" bit go high but delays notifying userspace until it has the full address. If a mismatch is discovered, it clears the receive buffer and waits for the next frame. Otherwise, it sets a counter to say how many bytes are address bytes (accessed via ioctl) and makes the entire frame (with address) available to userspace. This stops when the address bit goes high again, indicating a new frame. Doing without the filter is doable too; we just start buffering when address goes high and notify userspace when we receive the first data byte, the userspace application can then ask how many of the incoming bytes are address bytes and start reading as normal. A further ioctl might be used to manually reset the state machine from userspace ready for the start of another frame. If a new frame comes in before the previous one is read… I guess we either ignore the new frame or we throw away the not-yet read one, unless we want to do something fancy like copying entire frames to specially allocated buffers. > Since multidrop is used with RS485 we can think to add all these stuff > into TIOCSRS485 ioctl() as below: > > rs485conf.flags |= SER_MULTIDROP_ENABLED; > rs485conf.delay_rts_after_send = 0; > > ret = ioctl(fd, TIOCSRS485, &rs485conf); > ... Conceivably you could use multi-drop with a number of scenarios, not just RS-485. e.g. DebugWire on AVR uses a UART-style link on the reset pin where the line is effectively open-collector and the Tx pulls the line low. The same style of signalling could be used for multi-drop communications over short distances. I am splitting hairs here though. :-) > However we still need and ioctl() command to state when new address > bytes should be sent in the data stream... > > We can use different solutions but the real problem is avoiding a delay > between the address and data bytes and to do so we must find a way to > completely support 9 bits transmission or tricks as the SENDA bit. Indeed, whilst thinking about transmission though, we should not forget the need to be able to receive properly either. :-) I have a pet project where I'm coding up a LPC810 MCU to use as an I²C UART chip since the chip lacks the RAM/flash to do much useful (but *does* have hardware UARTs) and there's a distinct lack of I²C UART chips out there in a DIP package, target being for adding extra UARTs to hobby single-board computers like the Raspberry Pi. Whatever solution gets decided here, I'll probably be coding the firmware (which will be GPLv2 licensed) in that chip to suit the interface in the Linux kernel. -- Stuart Longland (aka Redhatter, VK4MSL) I haven't lost my mind... ...it's backed up on a tape somewhere. -- To unsubscribe from this list: send the line "unsubscribe linux-serial" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html