[En-Nut-Discussion] Using USART to Rx from PC Keyboard

Dave Smart SmartFamily at mchsi.com
Mon Dec 16 14:16:50 CET 2002


Hello EtherNut developers,

This turned into a pretty long note, so if you're not into connecting PC
keyboards or some improvements to the standard UART, you may want to move on
to another message...

I'm trying to use the _Synchronous_ portion of USART #1 on the Atmega 128 to
receive data from a PC keyboard. Unfortunately, I can't seem to get the sync
mode setup quite right, so I get junk back.

Has anyone connected Ethernut to a PC keyboard? Can you share your driver?



Here's where I'm at, what I did, and you'll see I've posted almost every
piece of code below -

1) Assuming I would be ultimately successful, I set about revising the
methods in uartavr.c to add a few options and fill in the missing
functionality.

2) In my thread, I Register and Open the uart normally - per samples.

3) Replace the standard ISR handler with a modified version (deals with
scancodes).

4) Wired my keyboard in and began testing.

At this stage, I was getting unique junk back. By that I mean for a <F1>
press and release, I always got the same sequence of data back, but it was
wrong when compared to what it should be. Then, for <F1> I received
different and repeatable, but still wrong sequence of data. And so on.

Since PC keyboards are "data rate predictable", I scoped it, computed the
baud rate, and then set the uart for async mode at that rate. It works fine.
*** But, the big problem with this solution is that it only works for one
keyboard (12,500 baud). Another keyboard outputs a different data rate
(8,333 baud), and another a 3rd rate (14,500 baud). So, I suppose I could've
tackled auto-baud... but PC keyboards have a strobe output to clock the data
so this shouldn't be necessary.

5) Added console port commands to change Sync, Polarity, Stop bits, Data
bits.

Confusingly, the changes didn't appear to take. Is there some subtle
documentation on the USART that I missed?


Must the UART be disabled to change these parameters?
Can you see any obvious error in the code below?

thanks for all your help,
Dave Smart



===========================================================

Reference Info:

The PC keyboard outputs 1 start, 8 data, Odd parity, and 1 stop bits in this
synchronous stream and here is an excellent web reference -
http://www.beyondlogic.org/keyboard/keybrd.htm if you want to learn more.

===========================================================
Here's lots more details for those that might help, ordered the same as the
section above.

1) Assuming I would be ultimately successful, I set about revising the
methods in uartavr.c to add a few options and fill in the missing
functionality -

include\sys\Device.h Added:
	//DS+ Additions by DSmart
	#define UART_SETSYNC        0x010f  /*!< \brief Set Uart Sync/Async Mode.
*/
	#define UART_GETSYNC        0x0110  /*!< \brief Get Uart Sync/Async Mode.
*/
	#define UART_SETPOLARITY    0x0111  /*!< \brief Set Uart Sync-Mode XCK
Polarity. */
	#define UART_GETPOLARITY    0x0112  /*!< \brief Get Uart Sync-Mode XCK
Polarity. */
	//DS-

dev\uartavr.c
	Patterned after UartAvrSetSpeed(...), I set about creating
UartAvrSetSync(...) and others.

	static int UartAvrSetSync(UARTDCB *dcb, u_long parm, u_char devnum)
	{
		u_char bits = (parm & 1) << 6;	// Position to 0x40
	#ifdef UCSR1C
		if (devnum)
			UCSR1C = (UCSR1C & ~0x40) | bits;
		else
	#endif
		UCSR0C = (UCSR0C & ~0x40) | bits;
		return 0;
	}

	static int UartAvrSetPolarity(UARTDCB *dcb, u_long parm, u_char devnum)
	{
		u_char bits = (parm & 0x01);
	#ifdef UCSR1C
		if (devnum)
			UCSR1C = (UCSR1C & ~0x01) | bits;
		else
	#endif
			UCSR0C = (UCSR0C & ~0x01) | bits;
		return 0;
	}

	static int UartAvrSetDataBits(UARTDCB *dcb, u_long parm, u_char devnum)
	{
		u_char bits;
		if (parm < 5 || parm > 8)
			return -1;				// Error, can only support 5 through 8 bits
		bits = (parm - 5) << 1;		// align to register
	#ifdef UCSR1C
		if(devnum)
			UCSR1C = (UCSR1C & ~0x06) | bits;
		else
	#endif
			UCSR0C = (UCSR0C & ~0x06) | bits;
		return 0;
	}

	static int UartAvrSetParity(UARTDCB *dcb, u_long parm, u_char devnum)
	{
		u_char bits;
		if (parm > 2)
			return -1;				// Error in parm
		bits = parm;
		if (bits)
			bits++;				// fixup for register values 0=>0, 1-2=>2-3
		bits <<= 4;				// align to register
	#ifdef UCSR1C
		if(devnum)
			UCSR1C = (UCSR1C & ~0x30) | bits;
		else
	#endif
			UCSR0C = (UCSR0C & ~0x30) | bits;
		return 0;
	}

	static int UartAvrSetStopBits(UARTDCB *dcb, u_long parm, u_char devnum)
	{
		u_char bits = parm;
		if (bits < 1 || bits > 2)
			return -1;				// Error, can only support 1 or 2 stop bits
		bits = (bits - 1) << 3;		// align to register
	#ifdef UCSR1C
		if(devnum)
			UCSR1C = (UCSR1C & ~0x08) | bits;
		else
	#endif
			UCSR0C = (UCSR0C & ~0x08) | bits;
		return 0;
	}

and in UartAvrIOCtl(...) I added and revised the fixed coding -

	...
		//DS+ Additions by DSmart
	    case UART_SETSYNC:
	        rc = UartAvrSetSync(dcb, *((u_long *)conf), dev->dev_base);
	        break;
	    case UART_GETSYNC:
	        rc = UartAvrGetSync(dcb, (u_long *)conf, dev->dev_base);
	        break;
	    case UART_SETPOLARITY:
	        rc = UartAvrSetPolarity(dcb, *((u_long *)conf), dev->dev_base);
	        break;
	    case UART_GETPOLARITY:
	        rc = UartAvrGetPolarity(dcb, (u_long *)conf, dev->dev_base);
	        break;
		//DS-
	    case UART_SETDATABITS:
	        rc = UartAvrSetDataBits(dcb, *((u_long *)conf), dev->dev_base);
//DS+
	        break;
	    case UART_GETDATABITS:
	        rc = UartAvrGetDataBits(dcb, (u_long *)conf, dev->dev_base);	//DS+
	        break;
	    case UART_SETPARITY:
	        rc = UartAvrSetParity(dcb, *((u_long *)conf), dev->dev_base);	//DS+
	        break;
	    case UART_GETPARITY:
	        rc = UartAvrGetParity(dcb, (u_long *)conf, dev->dev_base);		//DS+
	        break;
	    case UART_SETSTOPBITS:
	        rc = UartAvrSetStopBits(dcb, *((u_long *)conf), dev->dev_base);
//DS+
	        break;
	    case UART_GETSTOPBITS:
	        rc = UartAvrGetStopBits(dcb, (u_long *)conf, dev->dev_base);	//DS+
	        break;
	...


----------------------------------------------------------------------

2) In my thread, I Register and Open the uart normally - per samples.

	// Sync UART Interface to PC Keyboard, which will retrieve Scan Codes
	NutRegisterDevice(&devUart1, 0, 0);  // Get the UART as a stream
	uartkybd = NutDeviceOpen("uart1");

	// Replace the standard Rx ISR with our custom version
	NutRegisterInterrupt(IRQ_UART1_RX, CustomRxComplete, uartkybd);
	//NutDeviceIOCtl(uartkybd, UART_SETSYNC, &_Sync);

	NutDeviceIOCtl(uartkybd, UART_SETSPEED, &_Baud);  // Test
	NutDeviceIOCtl(uartkybd, UART_SETPOLARITY, &_Polarity);
	NutDeviceIOCtl(uartkybd, UART_SETPARITY, &_Parity);
	NutDeviceIOCtl(uartkybd, UART_SETSTOPBITS, &_StopBits);
	NutDeviceIOCtl(uartkybd, UART_SETDATABITS, &_DataBits);

I should be checking all the return values, but haven't done that yet...


----------------------------------------------------------------------

3) Replace the standard ISR handler with a modified version (deals with
scancodes).

See above where I've done a NutRegisterInterrupt(...CustomRxComplete...).
The unique characteristic of PC keyboards is that they return ScanCodes,
which must then be decoded. Not decoding them will allow the full scancode
to be inserted into the stream, which is harder to parse later. Also, the
keyboard doesn't output a \r\n, instead it outputs a scancode. So, my ISR
does a little fixup on these. This is just the standard RxComplete with a
few small mods.

	static void CustomRxComplete(void *arg)
	{
	    NUTDEVICE *dev = (NUTDEVICE *)arg;
	    IFSTREAM *ifs = dev->dev_icb;
	    UARTDCB *dcb;
		u_char data;
		int kdecode;

	#ifdef UDR1
	    if(dev->dev_base)
	        data = inp(UDR1);
	    else
	#endif
	        data = inp(UDR);

		kdecode = ScanCodeDecode(data); // {-1 for pending, 0..FF for decoded}
		if (kdecode >= 0)
		{
			ifs->if_rx_buf[ifs->if_rx_idx] = (char)kdecode;
			if(ifs->if_rd_idx == ifs->if_rx_idx++)
			{
				dcb = dev->dev_dcb;
				NutEventPostAsync(&dcb->dcb_rx_rdy);
			}
		}
	}

----------------------------------------------------------------------

4) Wired my keyboard in and began testing.

Since I'm using UART1, my connections are as follows:

	PortD.5	XCK	Clock signal from keyboard to EtherNut
	PortD.2	RXD	Data signal from keyboard to EtherNut
	PortD.3	TXD	Data signal to keyboard from EtherNut (but not wired yet)



----------------------------------------------------------------------

5) Added console port commands to change Sync, Polarity, Stop bits, Data
bits.


So, next I added some console port command (on UART0) to change, on the fly,
some of the parameters of UART1.

Confusingly, the changes didn't appear to take. By that, when I change the
number of data bits, I still see the same thing for a given keystroke.





More information about the En-Nut-Discussion mailing list