Introduction
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.
Source code
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[2] at div_wfactor
16:
17: TXSTA_BRGH = true
18: BAUDCON_BRG16 = true
19:
20: SPBRGH = div_btfactor[1]
21: SPBRG = div_btfactor[0]
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.