This is a small follow up regarding the Jallib USB Serial library. Today’s blog I’m showing how to make a more serious application with the PIC 18f14k50 USB Interface Board, namely a USB-RS-232 converter. I will go trough the source and explain the JAL code step by step.
The beginning is the same as Part 2, namely defining the target clockand including the device file.
1: -- chip setup 2: include 18f14k50 3: 4: -- even though the external crystal is 20 MHz, the configuration is such that 5: -- the CPU clock is derived from the 96 Mhz PLL clock (div2), therefore set 6: -- target frequency to 48 MHz 7: pragma target clock 48_000_000 8:
Serial interrupt handling
Then we need to declare a forward procedure in order to receive the line setting change events, which will be discussed a bit later on. In addition the usb_serial library has to be included as well as the print library so for now we have to add the following code (line 1-7)
1: -- forward procedure declaration 2: procedure usb_cdc_line_coding_changed_callback() 3: 4: -- include standard libraries 5: include delay 6: include usb_serial 7: include print 8: 9: procedure _serial_receive_interrupt_handler() is 10: pragma interrupt 11: 12: 13: if (PIR1_RCIF == TRUE) then -- UART receive interrupt 14: 15: if ((RCSTA_OERR == TRUE) | (RCSTA_FERR == TRUE)) then -- frame/overr error 16: var byte x = RCREG -- flush hardware buffer 17: while RCSTA_OERR == TRUE loop -- overrun state 18: RCSTA_CREN = FALSE -- disable UART 19: RCSTA_CREN = TRUE -- re-enable UART 20: x = RCREG -- \ flush hardware buffers 21: x = RCREG -- / 22: end loop -- until no more overrun 23: 24: else -- data without errors 25: var byte ch = RCREG 26: -- usb_serial_data = RCREG 27: usb_cdc_putc( ch ) 28: end if 29: end if 30: end procedure 31: 32:
From line 9-30, the interrupt routine to handle the receiving of the UART characters. As discussed in the previous blog, the USB library is not interrupt driven, therefore the USB service procedure must be called on a regular base to service the USB hardware. Servicing can be time consuming therefore I’ve decided to make the RS-232 serial interface (UART) interrupt driven, so were sure that were not lossing incoming data on the UART. The incoming data will be placed directly in the USB transmit ring buffer by calling the usb_cdc_putc( ch ) procedure. The USB-Serial serving procedure checks if there is data present in the ring buffer.
Handling baudrate settings
Next code section is the handling of the baudrate settings. The USB serial library has a callback function usb_cdc_line_coding_changed_callback() , when defined, it will be called each time the USB Host will request the line coding setting (which contains the baudrate, parity, stopbits etc). The procedure entry is on line 45 in the code below, the procedure checks/limits the baudrate settings and will change the baudrate (line 2-23) and by re-initializing the UART hardware (line 27-42).
1: -- procedure to change the baudrate settings of the UART 2: procedure change_baudrate( dword in baud_rate) is 3: 4: -- compiler issue with -const-detect 5: if true then 6: var dword fosc_div4 = dword( target_clock ) / 4 7: else 8: var dword fosc_div4 9: fosc_div4 = dword( target_clock ) 10: fosc_div4 = fosc_div4 / 4 11: end if 12: 13: var dword div_factor = fosc_div4 / baud_rate - 1 14: var word div_wfactor = word( div_factor ) 15: var byte div_btfactor at div_wfactor 16: 17: TXSTA_BRGH = true 18: BAUDCON_BRG16 = true 19: 20: SPBRGH = div_btfactor 21: SPBRG = div_btfactor 22: 23: end procedure 24: 25: 26: -- Initializes the serial port, calculates baudrate registers. 27: procedure serial_hw_init() is 28: RCSTA = 0b0000_0000 -- reset 29: RCSTA_SPEN = enabled -- serial port enable 30: RCSTA_CREN = enabled -- continuous receive enable 31: 32: TXSTA = 0b0000_0100 -- reset (16 bit, asyn) 33: TXSTA_TXEN = enabled -- UART transmit enabled 34: -- TXSTA_SYNC = true 35: TXSTA_BRGH = true 36: BAUDCON_BRG16 = true 37: 38: PIE1_RCIE = enabled -- UART receive int. enable 39: -- (PIE1_TXIE dynamically) 40: INTCON_PEIE = enabled -- periferal 41: INTCON_GIE = enabled -- general 42: end procedure 43: 44: -- callback procedure, is called if the USB Host changes the line settings 45: procedure usb_cdc_line_coding_changed_callback() is 46: 47: if ( cdc_line_coding_dte_rate > 115200 ) then 48: cdc_line_coding_dte_rate = 115200 49: end if 50: change_baudrate( cdc_line_coding_dte_rate ) 51: serial_hw_init() 52: end procedure 53:
The main loop
Finally we need to initalize the hardware and define the main loop. First all IO ports are set to digital (line 3) and need to initialize the USB serial library (line 6). Next a character is defined which holds the receiving character.
1: 2: -- disable analog 3: enable_digital_io() 4: 5: -- setup the USB serial library 6: usb_serial_init() 7: 8: var byte ch 9: 10: -- main loop 11: forever loop 12: -- poll the usb ISR function on a regular base, in order to 13: -- serve the USB requests 14: usb_serial_flush() 15: 16: -- check if USB device has been configured by the HOST 17: if ( usb_cdc_line_status() != 0x00 ) then 18: 19: -- check for input character 20: while usb_serial_read( ch ) loop 21: -- echo input character 22: TXREG = ch 23: while ! PIR1_TXIF loop end loop 24: end loop 25: 26: end if 27: end loop
Finally the main loop, as said before, the usb_serial_flush() procedure has to be called on a regular base. This function takes care of transmitting the character to the USB Host that have been received by the UART, and it also takes care of putting the character that are sent from USB Host into a receive buffer.
A rough calculation on the maximum interval time between two USB Serial flush:
By default the USB-Serial library will create a ring buffer of 32 bytes, which is for this application more than sufficient (max UART speed is ~10.000 characters/second, so USB Serial flush must be called at least every 10.000 / 32 = 3.2 ms. So there is enough time left to perform additional taks
On line 17 it checks if the USB Host is connected, if so, it will put all character that were sent by the USB Host in the UART Transmit register (line 19-24).
That’s about it, this blog showed how to create a simple USB-RS-232 convert, using the JALLB USB serial librray and the PIC 18f14k50 USB Interface Board. Possible extentions are to include RS-232 handshaking and to take other RS-232 line setting into account.