Photon operating a simple RS-232 port (TX, RX, Gnd) via USB

I need to have a Photon send/receive over an RS-232 port. I was going to connect the USB port to a USB/RS-232 converter and just use Serial.print() commands to send output to the RS-232 port. But thinking about it further, those USB/RS-232 converters come with drivers for Windows, Linux, and other operating systems and no instructions on what is necessary to drive them if I wanted the Photon to do it.

Can anyone offer a solution for operating an RS-232 port out of the Photon, whether it be via its USB port or using pins on the Photon and special software to drive them. The application I am running requires 38,400 baud, 8 bits and no parity. I am trying to have some cloud applications drive some special RS-232 driven equipment by posting messages to the Photon.

As long as you only need simple RS-232 without hardware flow control, I’d use a RS232 level shifter attached to the Serial1 port of the Photon (RX and TX pins). Here’s one from SparkFun; you can probably find one under US$4 on eBay, search for MAX3232.

1 Like

Perfect. Found some from the US (instead of China or Hong Kong for faster delivery), on eBay - two for $5. I noted that writing to Serial1 will result in TTL level output on the TX.RX pins and this device will convert that to RS-232. I imagine that the device will take power and ground off the Photon to drive it. Certainly less expensive than a USB/RS-232 converter and then driver software would still need to be created.

1 Like

@rickkas7 advice is the way to go, since there might be a misconception with Serial over USB.

The Photon connected via USB does not actually feature an RX/TX communication via D+/D- lines but it gets enumerated as a USB CDC device which still communicates via USB packets which will be “unwrapped” and interpreted as if they came via a COM line (virtual COM port).
Hence you need a driver (just as you need a driver for the FTDI USB adapters) to get the virtual COM port.

BTW: Even if you set Serial.begin(9600) communication will still go with USB speed (whatever baudrate you set does not matter).

Based upon what @ScruffR is saying, the solution of using the MAX3232, the TX/RX/Ground pins on the Photon, and Serial1.print() is the better solution if we need to insure we are writing to an external RS-232 port at 38,400 baud. That using a USB/Serial adapter would be far more difficult to properly achieve RS-232 communications at the speed required.

I use this board from Digikey connected to Serial1 to talk to commercial RS-232 equipment for prototype development. I use the USB port to a PC for debugging and also have a telnet “console” (aka passthrough) implemented to access the serial port remotely. The final product with use a MAX RS232 chip (specific one TBD) onboard for the interface.

1 Like

Well I received a couple of the MAX3232 devices ordered from eBay from China for $5. The ad said it works at 3.3.- 5 volts but I was unable to see any data being received on the PC from the Photon Serial1 TX pin. I connected TX, RX, 3.3 v from the Photon and ground on the Photon. Although the on board LED lights up, no data is received. Even tried swapping TX and RX and no difference. But it was strange that the eBay ad said +5v in one place and the voltage range in another.

So I am waiting for a SparkFun level converter to arrive instead and see if that works. Although much more costly than those on eBay, (nearly $20 with shipping) I like that it has an on board TX and RX LED so you can see when character are going in different directions. But paying the same price for a level converter that you pay for the Photon seems out of wack.

Back to the original question, I have an application where there is no access to the Serial1 port, so the USB might be my only option. Can a serial communication RS-232 be implemented using the USB port?

Using the FDI USB HOST to UART module: VDIP1

and then using a simple UART to RS-232 chip for the last conversion?

That sounds like hard work. It would require the FTDI device to know how to enumerate the Photon as a serial connection in much the same way the windows driver tells windows how to do the same.

You can get I2C or SPI UART devices if you have used RX/TX for other things.

Another (crazy) option is to plug the USB into a Raspberry Pi and use it as a relay.

Hi @bacichetti

I think that @Viscacha has a good point here and you might be better served with a UART bridge device like MAX3107. This part talks over i2c/spi and has full UART with some extra GPIOs for any protocol or LEDs you might want.

https://www.maximintegrated.com/en/products/interface/controllers-expanders/MAX3107.html

A ready made eBay doodad https://www.ebay.co.uk/itm/I2C-to-UART-5V-GPIO-DAC-ADC-for-Raspberry-Pi-Arduino-microcontroller/312023144206?hash=item48a607570e:g:0c4AAOSw3ZtaL8kA

Another IC, Sparkfun used to make a module with https://www.nxp.com/docs/en/data-sheet/SC16IS740_750_760.pdf?

A chinese eBay module with the same chip https://www.ebay.co.uk/itm/SC16I750-I2C-or-SPI-to-UART-Bridge-Module-for-Arduino/121292250289?epid=1858514003&hash=item1c3d94dcb1:g:y2IAAOxyUI1THeIk

Hi @bko, thanks for the suggestion, it really looks a good suggestion since they also implement RS-485 into the same chip and that would be an interesting addition.

@Viscacha, the use of a Raspberry Pi is not that bad for just a prototype, but in my case I’m looking for something more scalable.

The problem is that I really need to use the USB port, so I’ll buy and test the FTDI dev board and see if it can support the connection with my P1. I’ll post here the result using the dev board.

Hi - I’ve been using the MAX3232 breakout board from Sparkfun with a decent bit of success to poll an RS-232 serial peripheral using my Electron. However, I want to be able to de-power the chip in between polling to save a little juice so I put the 3.3 – 5 V line on the Electron’s A1 GPIO and if I digitalWrite(thatA1Pin, LOW) after polling and then back to HIGH again before polling the next time, I can only poll the instrument once. No subsequent loops give me a response. If I comment out that very last digitalWrite (as is currently shown below), the polling continues properly. But if I uncomment it, polling only works on the first loop and not again.

When I hook up the RS232 side of the MAX3232’s output directly to a PC’s RS232 input, I’m able to see that the command is still sent properly over RS232 regardless of whether that line is commented or uncommented. I’ve tried adding delays all over the place before and after powering up and down to see if I’m trying to do things too quickly, but the MAX3232 datasheet makes it look like it only needs nanoseconds of power on to work properly. I’ve also tried a Serial1.flush() at the end of my loop.

So it seems like the program is still sending the polling command properly but not receiving the response if I try to toggle power to the MAX3232 in between polls. For what it’s worth, the MAX3232 power down time won’t save much juice in this example but in the field I want to power it down for ~ 1 hour which will add up over months. Am I missing something silly in the digitalWrite command?

int max3232_switch = A1; // DIO to turn on/off MAX3232 chip
SYSTEM_MODE(MANUAL); // Turn cell modem off for now
const unsigned long SEND_INTERVAL_MS = 30000; // How often we poll SeapHOx in millis
unsigned long lastSend = 0; // Use for wait timing
char* new_var;

void setup() {
	Cellular.off();

	// Sets switch pin to output and defaults to off
	pinMode(max3232_switch, OUTPUT);
	digitalWrite(max3232_switch, LOW);

  // To print to screen over USB-Serial
	Serial.begin(19200);

  // To communicate with peripheral (SeapHOx is peripheral to Particle)
	Serial1.begin(115200);

	// Wait for a line to arrive, max this out for now
	Serial1.setTimeout(SEND_INTERVAL_MS);

  // Wait a few seconds then let us know program is running
  delay(5000);
  Serial.println("Beginning to poll");
}

// Give SeapHOx arbitrary wake character then "ts" command. Then listen for response.
void loop() {
	// When we hit timing interval, send wake then "ts" and listen
	if (millis() - lastSend >= SEND_INTERVAL_MS) {
		lastSend = millis(); // don't put anything above this to keep loop timing tight

		digitalWrite(max3232_switch, HIGH); // turn on MAX3232
		// delay(1500);									// this is superstition; I don't know how long that thing needs to "warm up"
	  Serial1.print("a");         	// arbitrary wake char
	  delay(500);                   // let SeapHOx wake up
	  Serial1.println("ts");        // take sample
		Serial.println("Taking sample now...");

		String s = Serial1.readString();
		const char* s_args = s.c_str();
		// Serial.printlnf("got %s", s.c_str()); // s.c_str() gives args of s

		// Parse out SeapHOx response; TODO: clean up malloc issues from using strdup by using free()
		new_var = strtok(strdup(s_args), "\t");
		while (new_var != NULL) {
			Serial.println(new_var);
			new_var = strtok(NULL, " \t");
		}

		Serial.println("REPEAT"); // just for kicks

		// digitalWrite(max3232_switch, LOW); // turn off MAX3232
  }

	delay(100); // in case it matters that otherwise it'll just spin at clock speed
}

@rickkas7, @ScruffR, @peekay123 - If I may be so bold as to solicit your help directly, I thought at least one of you might have some insight into my question above since it looks like you all have experience with this chip based on your other contributions to this thread.

I am certain that I’m sending the command from the Particle every single time and it’s properly converted to RS232 voltages because my peripheral is doing what it is supposed to every single time. However, I am not getting any serial response when I uncomment the digitalWrite(pin, LOW) at the end of the loop, even though that command is below all of the serial read commands.

I added a bunch of print statements to debug along the way, just to ensure that everything is happening in the order that I think it is and I can confirm to the best of my ability that it is, so I’m very confused why uncommenting that digitalWrite(pin, LOW) line prevents me from being able to hear the peripheral’s response. Thanks for any insight!

Just to clarify one thing you might be missing.
Serial.write(), Serial.read() and the likes are not blocking calls till the actual transfer has finished.
Read/Write action go via internal buffers to decoulple the code flow from the actual transfer. The actual transfer happens asynchronous to your code.

Serial.write() places the data into the buffer and then your code moves on while the HW interface will pull the data out of that buffer and byte by byter and send it over the line, which will take some time - depending on baudrate.

Serial.read() does actually only ever read data that has been transmitted and stored into the buffer some time before.

So when you depower the transmitter and keep it powered down for big portions of the time, then power it up and expect to read anything immediately, you’ll just find an empty buffer as there was no time for any data to be commited to the buffer.

BTW, your use of strdup() will enevitably cause a memory leaks as you are never freeing the allocated memory for the string duplicate.

2 Likes

Hi @supscientist

I looks like you are hooking up the power pin for the MAX3232 directly to a Photon GPIO output. I don’t think that the GPIO can provide enough current to really power this device when it is hooked up. The datasheet is a bit confusing in that they spec a 1.0mA current but with no load. Elsewhere they spec the max power dissipation at 533-1000mW so you know the device can draw at least 100mA from a 5V supply.

Other parts in that chip family have a shutdown input that allows you to power down the device gracefully. I would look for one of those.

1 Like

Thanks, @ScruffR, this makes sense and is very helpful insight. I was definitely missing portions of that understanding. I tried to simplify things inside that loop() such that now all I have is the following. I thought that adding the flushes would force the program to wait until all characters were written before moving on and that this could solve the problem. But I’m still not able to see the peripheral’s response on the Electron’s serial monitor. If I change that final digitalWrite(pin, LOW) to System.sleep(SLEEP_MODE_DEEP, 30);, then it works but I can’t get the timing as precise as I’d like yet. And, confusingly, I default that same pin to LOW in my setup() so the pin shouldn’t be HIGH for any longer whether it is using deep sleep or not.

if(millis()-lastSend >= SEND_INTERVAL_MS){
  lastSend = millis();

  digitalWrite(max3232_switch, HIGH); // turn on MAX3232
  delay(500);
  Serial1.print("a");         	// arbitrary wake char
  delay(500);                   // let SeapHOx wake up
  Serial1.println("ts");        // take sample
  Serial1.flush();

  Serial.println("Taking sample now...");
  Serial.flush();

  Serial.println(Serial1.readString());
  Serial.flush();

  Serial.printlnf("Loop %i; REPEAT\n", counter);
  Serial.flush();
  counter++;
}

digitalWrite(max3232_switch, LOW);

Great to know, @bko! I'll have to go looking for one of those.

In the meantime, though, I don't think it's a power issue as my peripheral is still receiving the "take sample" command from the Electron and logging internally as it should, verifying that the MAX3232 is sufficiently powered. Moreover, if I comment out the digitalWrite(pin, LOW) but keep everything else the same, it works as expected, reinforcing my belief that the MAX3232 is powered.

I guess I must be having trouble understanding how the buffers are working with respect to when I'm depowering the chip and that's why I can't receive the peripheral's response.

One extra thing you might see happening when depowering the chip (after it has been on once - that’s the difference between LOW in setup() and in loop()) you might have some “residue” in your buffer or even worse half finished transmits in the interface stage due to noise during power-off/power-up.

You could try adding a Serial1.end() before depowering the chip and a new Serial1.begin() after power-up.

2 Likes

That pretty much seems to have done the trick—thanks, @ScruffR! I had to move around the relative placements of the Serial1.end() and Serial1.begin() calls as well as remove my “wake” character that I previously had to send to the instrument to get it to listen for the next characters (perhaps the Serial1.begin is doing something like that implicitly). As you can see below, I actually put that Serial1.end() after the power down and subsequent power up.

It’s worked for a few different interval timings now, so it looks much more robust. Thanks again!

if(millis()-lastSend >= SEND_INTERVAL_MS){
  lastSend = millis();

  digitalWrite(max3232_switch, HIGH); // turn on MAX3232
  Serial1.end();	// clean out any residual junk in buffer
  delay(500);
  Serial1.begin(115200);

  // delay(500);
  // Serial1.print("a");         	// arbitrary wake char
  delay(500);
  Serial1.println("ts");        // take sample

  Serial.println("Taking sample now...");
  Serial.println(Serial1.readString());
  Serial.printlnf("Loop %i; REPEAT\n", counter);
  delay(500);

  counter++;
}

digitalWrite(max3232_switch, LOW);
1 Like