RS232 New PIC programmer just learning C  - Page 1
Forum section: electronics / general

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 08, 2016, 10:05:27 pm
content: Hi allI have just built as a hobby project a PIC16F690 based DC load using the usual PWM control arrangement. The circuit is powered from the USB bus and uses a DC-DC converter and opto isolation and FTDI chip for communication with the PC via USB.I think the DC load works great and I have an encoder connected for adjustment of the current from 1ma to 2 amps in 1ma steps. I have the encoder set up for turn speed sensing so I can make the adjustment quite quickly. I have a bleeper and do some thermal overload calculation to sound a warning and back down the power if I think the power dissipation of FET is getting to high. All works great so far.I have managed to figure out the software and PIC datasheet enough to send the Current and Voltage measurement back to the PC via RS232 and the FTDI chip. And using hyper terminal to display the current and load voltage. I will hopefully develop a nice GUI for it later.I want to use the PC to control the PIC/DC load and just today I have had the success of receiving my first single characters back from the computer. So I can no receive single characters.Please could I have some advice on the best approach for how the PIC can decode the command strings I plan to send from the PC and perhaps what format the command strings should be.My first thought is to send ASCII that will be human readable for ease of debugging and I dont think the overhead would be a issue compared with a binary control format. I'm not asking for a complete answer I am just asking whats best practice and what is normally the way of doing this.I will want to send the output current, switch on switch off, modulation on, off, modulation speed, etc1) Should I make all the commands I send the same length ?2) Should I terminate them in a special character to help framing the data3) Do I copy the incoming string one byte at a time into an array and then parse it for the information later4) Do I try and decode the incoming string on the fly one byte at a time like some type of state machine.5) Any other tips tricks etc to get me started.6) Saturdays winning lottery numbers.Any advice welcome- especial those lottery numbers.Many ThanksBest Regards Chris

author: rstofer
author_rank: Super Contributor
timestamp: November 09, 2016, 01:00:22 am
content: All good questions!Quote from: Ginettag20 on November 08, 2016, 10:05:27 pmNo need, just bring in sentences that end in an ASCII newline (LF character). I usually have the char input routine dump any CR chars so I only quit assembling the sentence when I see the LFQuoteSee above. I just use LFQuoteThis is tricky! I don't know what resources you have available (I didn't RTFM) but I like to bring incoming chars into a circular buffer (longer than longest sentence) using interrupts. When I get the LF, I might send an X-Off char or drop CTS. Something to enforce flow control. I would also set a flag indicating a sentence is available in the buffer. Later, I would pull the data out of the queue and either place it in an array (and raise CTS or send X-On char) or analyze the complete sentence as I grab it from the buffer.QuoteIf you already know you have a sentence, you can just start scanning. Maybe your sentences are all VERB NOUN PARAM1 PARAM2 ... PARAMnQuoteWrite the scanner code on a PC first!QuoteNo help on the Lottery.

author: rstofer
author_rank: Super Contributor
timestamp: November 09, 2016, 01:03:53 am
content: On reflection, if you achieve flow control, you can insert the incoming chars into a linear array and interpret/execute the command before re-enabling communications.Maybe you can do something with a prompt char. If the PC hasn't seen a prompt, it doesn't send a line. That's a low dollar way of enforcing flow control.

author: JPortici
author_rank: Super Contributor
timestamp: November 09, 2016, 05:42:11 am
content: Three things you MUST do:- handle UART RX during interrupt. the RX interrupt will only have to put the character into the buffer and increase the received counter. if you do it by polling youwilllose characters.- process the data (and transmission back from the load) in the main loop.- no need to do it now, can be added later but you have also to handle transmission errors, like spurs or connection lost (a timeout from the last character that resets the buffer, for example)

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 09, 2016, 10:37:39 am
content: Correction line feed not carriage returnMany thanks for those replies.That helps get me started. I had not considered using any form of flow control I know there are some hardware pins within the rs232 specification but not sure how to implement them. I would assume though that I'm unlikely to get many issues with data corruption as I'm not using a real rs232 connection as its implemented through the ftdi and USB. I need to give these comment more thought and have another look at the ftdi data sheet.Ok good idea regarding carriage return terminator that will help with debugging in terminal.Ok so I assume that I can set the pic to enter a interrupt on receipt of a new byte. I will check to see if the byte is a carriage return. If it is a carriage return raise a flag to say that a new message has been received. If the new byte was not a carriage return copy the byte into a char array and add one to the array index pointer. Drop out the interupt and wait for next new char.I'm still a bit confused by the purpose of those error flags associated with the uart control registers. The data sheet a bit above my pay grade.Many thanksBest regards Chris

author: JPortici
author_rank: Super Contributor
timestamp: November 09, 2016, 11:54:39 am
content: well one i like is the framing error: stop bit not detected. it may be sign of two different things:if received data is different than zero the set baud rate is probably wrong.if received data is zero it was most probably a break character. i like to use this one to reset communication if i'm sending raw bytes and can't wait for a timeout to reset the busregarding checking for LF in the interrupt.. i suppose you can spare that couple of instructions.. but this will work because you want to use verbose commands so you send numbers one digit at a time and so on. if you are sending raw data that's not possible of course so you have to come up with a protocol that uses frames, like a fixed size frame with known start and stop codes (just an example) and the interrupt routine will only put the data in the buffer, incement the index pointer and go out

author: John Coloccia
author_rank: Super Contributor
timestamp: November 09, 2016, 12:00:54 pm
content: If I can afford the performance hit, and 99% of the time I can, I like to set up embedded RS232 like so:1) interrupt echos back every character sent, sticks in circular buffer2) command set/status is simple space delimitted (actually, usually a configurable delimeter) commands, and ended with a carraige return, line feed or whatever else is convenient.This way, you can hook up a terminal and see what you're typing, also being sure that the data's not corrupted. A controlling program can use the same interface, also checking for corruption. It can also start the command with a special character that indicates it's an automated interface, and the interface can change to a more complicated protocol with sequence numbers, checksums, name/value pairs....whatever.I've used this technique over and over and over and over again, and it always works extremely well when all you need is low bandwidth commands, health and status.

author: spudboy488
author_rank: Regular Contributor
timestamp: November 09, 2016, 12:39:02 pm
content: I do this kind of thing all the time. You don't need to make the commands the same length but it does make a command interpreter a bit easier to implement. For example, the commands I send are 2 bytes minimum. If the command requires more data, the subroutine waits for the missing bytes before doing anything.This is my simplified routine.Code:void main(void) {Init_Fixture();while(1) {while (BuffPtr < CMND_SIZE)// Wait for a commandHeartbeat();// Blink the LED// Command interpreterswitch (Buff) {case 'F':switch (Buff) {case '?':Send_RS232Str(FIXTURE_ID);// Fixture IDbreak;default:SendError();// Unknown commandbreak;} break;case 'S':switch (Buff) {case '?':Send_RS232Str(SW_ID);// Software versionbreak;default:SendError();// Unknown commandbreak;} break;case '?':switch (Buff) {case '?':Help();// Display the help screenbreak;default:SendError();// Unknown commandbreak;} break;default:SendError();// Unknown commandbreak;}ClearRX();}}I don't use hardware flow control but every command sent needs to return something; either data, "OK", or "ERROR!". This helps the program flow on the PC side. The 3 commands that are defined here are "F?", "S?", and "??". I just add more case statements for new commands.I also use a very simple RX routine. I set up the buffer and the interrupt on receive code. I set BuffPtr to 0x00. As the commands are received, they are stuffed into the buffer until there are at least 2 bytes then it falls into the command interpreter which acts on the 2 bytes or wait for any remaining bytes. Once the command has been executed and a response sent, the buffer is "cleared" by setting the BuffPtr back to 0x00. I've never had the need for a ring buffer since each command sent to the PIC requires a response back to the PC before another command is set. It greatly simplifies things, IMHO.

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 09, 2016, 12:42:18 pm
content: Many thanks all makes sense. I only write pic code every few years to its going to take me a little while to figure out things again like how to setup the interrupts and how to declare an array in C. So I will probably back with more question later.Thanks again best regards Chris.PS - sorry original posted in wrong forum section thanks for moving it Simon.

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 09, 2016, 12:46:16 pm
content: Are your decoding using the switch statement, that how I was thinking about it.Thanks

author: obiwanjacobi
author_rank: Super Contributor
timestamp: November 09, 2016, 12:48:41 pm
content: I have done something similarfor control of a homebrew Z80 computer.I basically have short abbreviated commands of 2 or 3 chars. Each command can have specific additions.Like said before I wait for the line-feed and then parse till the first space: that is the command.Then I hand it of to a specific command handlerthat knows how to interpret the rest.

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 09, 2016, 06:08:27 pm
content: Many Thanks everyone for small victories Had one of my best tech bench days ever ! I have managed to get in my serial data via a interrupt and echo it back to the keyboard. Tomorrow will be serial decode dayor house cleaningBest Regards ChrisThis is my working codeTuneOscillator(){OSCTUNE = 0x09; // 09 = 104.9 uS In order for the serial port timing to be correct the oscillator needs tuning} // Use 09 for PIC on low pin count demo board - adjust for other devicesvoid ConfigUART(void) // Setup Uart Baud 9600, Data bits 8, Stop Bits 1, Parity none, Handshaking none.{ANSEL = 00000000; // JUST IN CASE DISABLE SHARED ANOLOG PINS - These may be shared with TX and RXANSELH = 00000000; // JUST IN CASE DISABLE SHARED ANOLOG PINSSPBRGH=0; // Board Rate SelectionSPBRG=25; // Set Baud rate to 9600TXSTA=0x24; // 00100100 Transmit Enabled=1 High Baud rate Selected=1RCSTA=10010000; // SPEN ENABLED, RX9 DISABLED, SCREN DISABLED, CREN ENABLE RECIEVERBAUDCTL=0; // 8-Bit Generator used and Auto baud rate disabledRCREG = 0x00; // Clear contents of the receive bufferRCREG = 0x00; // Clear contents of the receive buffer}void ConfigPortC(void) // Configure Port C as Outputs for LEDs on Low Pin Count Demo Board.{PORTC = 0x00; // PORTC OutputsTRISC = 0x00;PORTC = 0x00;}void ConfigInterrupts(void) // Interrupt Service Routine{GIE=0; // Cleared to disable global interrupt while doing configurationPIE1=0x00; // Just for good measure disable all peripheral interruptsPIE2=0x00; // Just for good measure disable all peripheral interruptsRCIE=1; // Enable interrupt on serial data receivedPEIE=1; // Enable peripheral interruptsGIE=1; // Finished interrupt configuration re-enable global interrupts}void interrupt isr(void) // Interrupt Service Routine{GIE=Clear; // Cleared to disable global interrupt while executing the ISRif(PIR1bits.RCIF) // Test receive flag for new Rx data{TXREG = RCREG; //Echo back the received char - TX the RXPORTC++; // Increment LEDs to show how many bytes received}GIE=Set; // Finished interrupt routine re-enable global interrupts}int main(){TuneOscillator(); // Correct the PICs defaults oscillatorConfigPortC(); //Configure Port C for outputs - Test LED indicators on the low pin count demo boardConfigUART(); // Setup Uart Baud 9600, Data bits 8, Stop Bits 1, Parity none, Handshaking none.ConfigInterrupts(); // Setup Serial interruptswhile(1){// Do nothing yet}} //End Main

author: rstofer
author_rank: Super Contributor
timestamp: November 09, 2016, 10:37:09 pm
content: Left to itself, *nix only sends a LF with you do something like printf("hello world\n"); If I really want a CR, I need to add \r. Now, that's no big deal but it isn't necessary, just look for LF.When you use a terminal, set the options to send both CR and LF. Your input code skips over CR and stops on LF whether it comes ahead of or behind the LF. It is simply ignored.The reason I suggested VERB NOUN PARAM1 ... PARAMn is because you may have a bunch of SET or GET commands and the verb, obviously, is the same for all, SET or GET. The NOUN differentiates what is being SET or GETed. PARAMs are optional, of course, and totally dependent on the sentence.If you want to be able to abbreviate VERBs, no problem, just find a command that matches in the first chars, whatever the VERB is. If only S is sent and the first VERB in the table is SET, S will match. OTOH, if the next command in the table is STORE, the minimum abbreviation would be ST. You can get into a pile of trouble trying to do this, particularly with single letter abbreviations.Think about sentences like:SET REGISTER R0 123 -- would registers be predefined literals?GET REGISTER R0SET MEMORY <first address> <value> <count> -- <count would be omitted if all you want to set was one value. That's why it is the 3rd parameter.GET MEMORY <first address> <count> -- either <count> or <last address>, your choice.I realize these sentences may have little or no application for your project. I toss them out just for consideration of the sentence structure.

author: John Coloccia
author_rank: Super Contributor
timestamp: November 09, 2016, 11:54:54 pm
content: My favorite method of comm protocol, if you can deal with some inefficiency (and that's usually OK) goes something like this:-Everything is ascii-define a standard command packet, i.e. SeqNo Command Params...- Params are all name/value pairsThen one simple command handler can grab the incoming coming command, parse it, shove it into some sort of command structure and dispatch it without knowing anything at all about the command itself. It's one of those design patterns that I keep reusing project after project because it works so well.

author: spudboy488
author_rank: Regular Contributor
timestamp: November 10, 2016, 12:27:42 pm
content: Quote from: Ginettag20 on November 09, 2016, 12:46:16 pmIf this was directed to my post then yes. The first layer looks at the first character in Buff then the next layer looks at the second character in Buff

author: rstofer
author_rank: Super Contributor
timestamp: November 10, 2016, 07:28:44 pm
content: You can also build a transition matrix and simplify the switch() statement considerably. You basically lay out the entire FSM in an NxM array of 'next-states'. At any point, if an invalid character is received for a given state, branch to a state that eats chars until the LF.Columns are chars, rows are states. At any given state, there is only one possible 'next state' for an input character. For example, you might want to accept "SEt Memory" where I capitalized the REQUIRED chars. If the command is abbreviated, the detection of a <space> will accept the word and you can move on to the next token.The matrix will be pretty sparse because a) not all letters are used and b) most sequences are invalid. The nice thing is that the logic is quite simple and the matrix is extensible.Whether these ideas are useful depends entirely on the number of possible commands. If there are only a few, just use strtok() to grab the token, set it to upper (or lower) case and compare against a table of strings. You do want to make the input case-insensitive.

author: Ginettag20
author_rank: Regular Contributor
timestamp: November 10, 2016, 09:19:13 pm
content: Thanks for all your help and advise I used a switch statement in the end and terminated the string with a carriage returnI was just about to post a video of a bug that made the test LED flash a error code when sending a GI command when I saw the stupid mistake anyway here is the link to the video but it works now without any errors. I am amazed how well it works given that im not doing doing any proper error checking. I think it will work well enough for my application without adding any error handling.I have include my sample code for anyone interest, dont assume to much i am just a beginnerThanks againhttps://youtu.be/D4y0OrgCEZwComStatus=0x00; // I wanted to set ComStatus to zeroComStatus==0x00; // what I had was this* SIXXXX+CR Set Current XXXX = mA PortC 1* SVXXXX+CR Set Voltage XXXX = mV PortC 2* SOXXXX+CR Switch on the LOAD - XXXX don't care PortC 3* SFXXXX+CR Switch off the load - XXXX don't care PortC 4* SMXXXX+CR Set Modulation 0000 = OFF PortC 5** GIXXXX+CR Get Current - TX Send current in mA PortC 6* GVXXXX+CR Get Voltage - TX Send Volts in mV PortC 7* GSXXXX+CR Get Status PortC 8* GTXXXX+CR Get time load has be switched on - Seconds PortC 9void interrupt isr(void) // Interrupt Service Routine{GIE=Clear; // Cleared to disable global interrupt while executing the ISRif(PIR1bits.RCIF){TXREG = RCREG; //Echo back the received dataRXstring[RXpointer++]=RCREG; // Copy the received character into an array and increment the array pointer to the next locationif (RXstring[RXpointer-1] == 0x0D ) // Check to see if the received string is completed by looking for carriage return terminator{RXpointer=0x00; // set string pointer to zero ready for next stringComStatus=0x01; // A new command string is available for decode}}GIE=Set; // Finished interrupt configuration re-enable global interrupts}Code in Main --------------if(ComStatus==0x01) // check if new string has been received{ComStatus=0x00; // Clear bit to show last incoming string is decodingMY ERROR LOOK HERE ComStatus==0x00; I had == instead of =switch (RXstring){case 'S' :switch (RXstring){case'I' : // Got I set current next 4 bytes are current in mAPORTC= 0x00;PORTC= 0x01;break;case'V' : // Got V next 4 bytes are Volts in mVPORTC= 0x00;PORTC= 0x02;break;case'O' : // Switch on DC loadPORTC= 0x00;PORTC= 0x03;break;case'F' : // Switch OFF DC loadPORTC= 0x00;PORTC= 0x04;break;case'M' : // SET Modulation - Next 4 char are mod frequencyPORTC= 0x00;PORTC= 0x05;break;default: // The second SET parameter was unknown charPORTC= 0x00;PORTC= 0xFF;break;}break;case 'G':switch (RXstring){case'I' : // Send current measurement in mAPORTC= 0x00;PORTC= 0x06;break;case'V' : // Send Volts measurement in mV -PORTC= 0x00;PORTC= 0x07;break;case'S' : // Send Load Status InformationPORTC= 0x00;PORTC= 0x08;break;case'T' : // Send Load Status InformationPORTC= 0x00;PORTC= 0x09;break;default: // The second Get parameter was unknown charPORTC= 0x00;PORTC= 0xFF;break;}break;default: // The first character in the command string was not S or GPORTC= 0x00;PORTC= 0xFF;break;}}