Learn about the HC-12 transceiver module and how to use it to transmit and receive digital data.

The HC-12 is a half-duplex wireless serial communication module with 100 channels in the 433.4-473.0 MHz range that is capable of transmitting up to 1 km. This project will begin by using the HC-12 to create a wireless link between two computers and end with a second article that creates a simple wireless GPS tracker.

### Parts Required

HC-12 transceiver (x2)$4Datasheet IPEX-to-SMA adapter (optional)$3Specification
Arduino Uno R3 (or compatible)$10Reference GPS Receiver (x2)$16Datasheet
or Adafruit GPS Logger Shield (x2)$45Project Guide 433MHz antenna$2-$5Datasheet Search ### About the HC-12 The HC-12 is a half-duplex 20 dBm (100 mW) transmitter paired with a receiver that has -117 dBm (2×10-15 W) sensitivity at 5000 bps. Paired with an external antenna, these transceivers are capable of communicating up to and possibly slightly beyond 1 km in the open and are more than adequate for providing coverage throughout a typical house. ##### The HC-12 circuit board. Image courtesy of Seeed. This image has been digitally manipulated to enhance chip markings. The HC-12 circuit board is built around the STM8S003F3 microcontroller and the Si4463 transceiver. #### The Si4463 Transceiver The Si4463 provides the wireless communication in this circuit. It has a maximum transmit power of 20 dBm (100 mW) and receive sensitivity of -129 dBm. Two 64-byte Rx and Tx FIFO memories are built into the chip along with a great many advanced features that are not implemented in the HC-12 design. See the datasheet for more information on multiband operation, frequency hopping, etc. #### The STM8S003FS Microcontroller This is an 8-bit microcontroller with 8 kB of flash memory, 128 bytes of EEPROM, and a 10-bit ADC. It supports UART, SPI, and I²C and has multiple I/O pins. It offers many of the same capabilities as its ATMega and XMC counterparts. It is programmed to control the Si4463 as well as handle the UART communication between the HC-12 and whatever it is connected to on the other end. #### The HC-12 Transceiver Module Combined with other components, the Si4463 and STM8S003 create the HC-12 transceiver, which provides a 4-pin TTL-level UART interface (Vcc, Gnd, Tx, Rx), with a 5th pin that is used to enter "command" mode for changing the module's configuration. The HC-12 has 100 supported channels spaced 400 kHz apart, eight transmit levels, eight supported baud rates, and three different working modes. The 5th pin on the HC-12 is labeled "Set" and, when driven to logic low, allows various settings to be selected on the HC-12 using AT commands sent to the "RXD" pin. The default configuration of the HC-12 is FU3—on Channel 1, FU3 is a fully automatic and transparent (to other devices) setting that adapts to the transmission rate of the connected device (although 9600 baud is still required to program it in Command mode). Note that as the transmission rate increases, the sensitivity of the receiver decreases. You can return to the default state by sending AT+DEFAULT once in command mode. Serial Port Baud RateOver-the-Air Baud RateReceiver Sensitivity 1200 bps5000 bps-117 dBm 2400 bps5000 bps-117 dBm 4800 bps15000 bps-112 dBm 9600 bps15000 bps-112 dBm 19200 bps58000 bps-107 dBm 38400 bps58000 bps-107 dBm 57600 bps236000 bps-100 dBm 115200 bps236000 bps-100 dBm In "HC12 Send/Recieve Example Program 1," the HC-12s are used in their default state (FU3: 20mW transmit, 9600 bps, Channel 001) to create a wireless bridge between the serial ports of two computers. The transceivers must be physically separated by at least 1.5 meters to function. This program will allow messages to be sent between two computers via the HC-12 transmitters. Text typed on one computer will be displayed on the serial monitor of the second computer. Begin by connecting the HC-12 transceivers to each Arduino: • Connect the HC-12 "Set" pin to Arduino pin 10 • Connect the HC-12 "RXD" pin to Arduino pin 11 • Connect the HC-12 "TXD" pin to Arduino pin 12 • Per the datasheet, connect a 22 µF to 1 mF reservoir capacitor in parallel with the HC-12 "Gnd" and "Vcc" pins • Connect HC-12 "Gnd" and "Vcc" to a 3.2 V to 5.5V 200mA source. Per the datasheet, if powering the HC-12 with more than 4.5V, place a 1N4007 diode in series with the HC-12 "Vcc" pin Upload the following code and open the serial port monitor in the Arduino IDE to send/receive messages. The functions "*.available()" reads the number of bytes stored in the Arduino's Serial or SoftwareSerial FIFO buffers. As long as there is a non-zero value returned by "*.available()", the "*.read()" function pulls one byte out of the buffer and the "*.write()" function sends that byte along to the other serial port to be read by either the computer or the HC-12. The Arduino UART has a 64-byte receive buffer built into the hardware so any bytes of data that exceed the 64-byte limit will be discarded. The software serial also has a 64-byte buffer; however, the SoftwareSerial library can be modified to increase that, if required. /* HC12 Send/Receive Example Program 1 By Mark J. Hughes for AllAboutCircuits.com Connect HC12 "RXD" pin to Arduino Digital Pin 4 Connect HC12 "TXD" pin to Arduino Digital Pin 5 Connect HC12 "Set" pin to Arduino Digital Pin 6 Do not power over USB. Per datasheet, power HC12 with a supply of at least 100 mA with a 22 uF - 1000 uF reservoir capacitor. Upload code to two Arduinos connected to two computers. Transceivers must be at least several meters apart to work. */ #include <SoftwareSerial.h> const byte HC12RxdPin = 4; // Recieve Pin on HC12 const byte HC12TxdPin = 5; // Transmit Pin on HC12 SoftwareSerial HC12(HC12TxdPin,HC12RxdPin); // Create Software Serial Port void setup() { Serial.begin(9600); // Open serial port to computer HC12.begin(9600); // Open serial port to HC12 } void loop() { if(HC12.available()){ // If Arduino's HC12 rx buffer has data Serial.write(HC12.read()); // Send the data to the computer } if(Serial.available()){ // If Arduino's computer rx buffer has data HC12.write(Serial.read()); // Send that data to serial } } "HC12 Send/Recieve Example Program 2" remains backward-compatible with "HC12 Send/Recieve Example Program 1." However, two significant changes have been made in the way data is handled by the Arduino. In the first program, single bytes were detected and read from the serial buffer and software serial buffer and immediately written to the other serial port. The following program will read entire strings of data from the serial buffers and store it until a newline character is detected at which point it will write out the entire buffer. The first program had no way to enter command mode and change the settings of the HC-12 transceiver. The following program can detect command sequences and change the settings of remote and local transceivers (provided they are running the newer version of the code). Note that it is important to change the settings of the remote transceiver first and then change the settings of the local transceiver to match, as any initial changes to the local transceiver will interrupt communication between the two. The Arduino will collect strings from the serial port and software serial port. Once the new line has been detected, there is a check to confirm if the string is a command sequence (commands begin with AT+). If a command sequence is present, the local chip executes the command and transmits the command to the remote chip for execution there. The code keeps the HC12s on the same channel at same power levels during testing. Use caution as it is possible to accidentally lower the remote power level to a point that the remote transceiver can no longer communicate with the local transceiver. This is quickly fixed by broadcasting a new power level. Changing the baud rate can disable communication between the HC-12 and the Arduino unless the Arduino baud rate is changed at the same time. /* HC12 Send/Receive Example Program 2 By Mark J. Hughes for AllAboutCircuits.com This code will automatically detect commands as sentences that begin with AT and both write them and broadcast them to remote receivers to be written. Changing settings on a local transceiver will change settings on a remote receiver. Connect HC12 "RXD" pin to Arduino Digital Pin 4 Connect HC12 "TXD" pin to Arduino Digital Pin 5 Connect HC12 "Set" pin to Arduino Digital Pin 6 Do not power HC12 via Arduino over USB. Per the data sheet, power the HC12 with a supply of at least 100 mA with a 22 uF to 1000 uF reservoir capacitor and connect a 1N4007 diode in series with the positive supply line if the potential difference exceeds 4.5 V. Upload code to two Arduinos connected to two computers that are separated by at least several meters. */ #include <SoftwareSerial.h> const byte HC12RxdPin = 4; // "RXD" Pin on HC12 const byte HC12TxdPin = 5; // "TXD" Pin on HC12 const byte HC12SetPin = 6; // "SET" Pin on HC12 unsigned long timer = millis(); // Delay Timer char SerialByteIn; // Temporary variable char HC12ByteIn; // Temporary variable String HC12ReadBuffer = ""; // Read/Write Buffer 1 for HC12 String SerialReadBuffer = ""; // Read/Write Buffer 2 for Serial boolean SerialEnd = false; // Flag to indicate End of Serial String boolean HC12End = false; // Flag to indiacte End of HC12 String boolean commandMode = false; // Send AT commands // Software Serial ports Rx and Tx are opposite the HC12 Rx and Tx // Create Software Serial Port for HC12 SoftwareSerial HC12(HC12TxdPin, HC12RxdPin); void setup() { HC12ReadBuffer.reserve(64); // Reserve 64 bytes for Serial message input SerialReadBuffer.reserve(64); // Reserve 64 bytes for HC12 message input pinMode(HC12SetPin, OUTPUT); // Output High for Transparent / Low for Command digitalWrite(HC12SetPin, HIGH); // Enter Transparent mode delay(80); // 80 ms delay before operation per datasheet Serial.begin(9600); // Open serial port to computer HC12.begin(9600); // Open software serial port to HC12 } void loop() { while (HC12.available()) { // While Arduino's HC12 soft serial rx buffer has data HC12ByteIn = HC12.read(); // Store each character from rx buffer in byteIn HC12ReadBuffer += char(HC12ByteIn); // Write each character of byteIn to HC12ReadBuffer if (HC12ByteIn == '\n') { // At the end of the line HC12End = true; // Set HC12End flag to true } } while (Serial.available()) { // If Arduino's computer rx buffer has data SerialByteIn = Serial.read(); // Store each character in byteIn SerialReadBuffer += char(SerialByteIn); // Write each character of byteIn to SerialReadBuffer if (SerialByteIn == '\n') { // Check to see if at the end of the line SerialEnd = true; // Set SerialEnd flag to indicate end of line } } if (SerialEnd) { // Check to see if SerialEnd flag is true if (SerialReadBuffer.startsWith("AT")) { // Has a command been sent from local computer HC12.print(SerialReadBuffer); // Send local command to remote HC12 before changing settings delay(100); // digitalWrite(HC12SetPin, LOW); // Enter command mode delay(100); // Allow chip time to enter command mode Serial.print(SerialReadBuffer); // Echo command to serial HC12.print(SerialReadBuffer); // Send command to local HC12 delay(500); // Wait 0.5s for a response digitalWrite(HC12SetPin, HIGH); // Exit command / enter transparent mode delay(100); // Delay before proceeding } else { HC12.print(SerialReadBuffer); // Transmit non-command message } SerialReadBuffer = ""; // Clear SerialReadBuffer SerialEnd = false; // Reset serial end of line flag } if (HC12End) { // If HC12End flag is true if (HC12ReadBuffer.startsWith("AT")) { // Check to see if a command is received from remote digitalWrite(HC12SetPin, LOW); // Enter command mode delay(100); // Delay before sending command Serial.print(SerialReadBuffer); // Echo command to serial. HC12.print(HC12ReadBuffer); // Write command to local HC12 delay(500); // Wait 0.5 s for reply digitalWrite(HC12SetPin, HIGH); // Exit command / enter transparent mode delay(100); // Delay before proceeding HC12.println("Remote Command Executed"); // Acknowledge execution } else { Serial.print(HC12ReadBuffer); // Send message to screen } HC12ReadBuffer = ""; // Empty buffer HC12End = false; // Reset flag } } ##### Data transmitted by a remote HC-12 transmitter is received and decoded as "AllAboutCircuits.com" by a local HC-12 receiver. Note the delay before the .com was transmitted, which might be due to the data handling of the Si4463 (see page 39 of the Si4463 datasheet) or to some aspect of the program running on the STM8S003FS. The previous code, if uploaded to both boards, allows you to program both the local and remote transceiver at the same time. Here are some other commands for your reference: Valid Baud Rate Commands: AT+B1200, AT+B2400, AT+B4800, AT+9600, AT+19200, AT+38400, AT+57600, AT+115200 Valid Channel Commands: AT+C001, AT+C002, AT+C0xx, ... , AT+C099, AT+C100. Per its datasheet, if using multiple HC-12s on different channels, stagger adjacent channels at least five apart $$\frac{40\;\text{kHz}}{\text{1 channel}}\times\text{5 channels}=200\;\text{kHz}$$ Valid Transmit Power Commands: AT+P1 (-1 dBm), AT+P2 (2 dBm), AT+P3 (5 dBm), AT+P4 (8 dBm), AT+P5 (11 dBm), AT+P6 (14 dBm), AT+P7 (17 dBm), AT+P8 (20 dBm) Return to default settings: AT+DEFAULT Additional commands are available; refer to the user manual for more information. ### Conclusion The HC-12 is a capable transceiver with an impressive range (up to 1 km). It is satisfactory for most hobby and even some industrial applications. It is an important alternative to the very inexpensive, low-power, but short-range nRF24L01. Though a bit more expensive than the nRF24L01, its range and simplicity of use make the HC-12 an excellent choice for projects involving tracking. In the next article we will explore using the HC-12 in a GPS (Global Positioning System) application. #### Tags: ### Comments #### 13 CommentsLogin • Wiky5 2016-11-03 Hi, Is it possible to program the microcontroller on the HC12 board to directly read simple inputs such as switches or a voltage and send a signal by itself? Or maybe pull up or down a pin when certain command is received? Great article! Regards, Willy • Mark Hughes 2016-11-03 Hi @Wiky5 (Willy), The short answer is possibly, but it’s not worth the time or energy required to attempt it. The microcontroller on the HC12 board is the Si4463 (datasheet is in the article), which has five GPIO pins, a couple of which might work as inputs / switches for your purpose—but there’s no real way to access them—this chip is the size of the nub on the end of an AA battery, and the manufacturers didn’t provide access to the extra pins on the circuitboard. Even if you manage to solder some wires to them, there’s a decent chance they have been tied to Vdd or Gnd. It would be much simpler, and much quicker to add a microcontroller (for example, the ATTIny 85. If you’d like to post your project idea in the Forums, I’m sure there are some site users that can help advise you. And if you do choose to do that, ping me and I’ll try to help too. Thanks, Mark • Wiky5 2016-11-04 Thank you for your answer, Mark. The thing is I’m not comfortable with solving problems by just adding more microcontrollers, even though they are dirt cheap. I feel like I’m wasting processing power which could be better used elsewhere. Anyway, I guess I’ll just have to come to good terms with the idea and go for the easy way. After all, the time used to solve that problem would cost way more than the uC and the power it’ll use during its whole life. Regards, Willy • Mark Hughes 2016-11-04 @Wiky5 I know how you feel. Over the course of many years, I’ve developed the extraordinary ability to find$100 solutions to $20 problems. • wesbflyer 2016-11-04 Just a slight corretion Mark. The micro is the STM8. I agree with your recommendation for using another micro in this application . Wes • Mark Hughes 2016-11-04 @wesbflyer Right you are sir. I must’ve been passing a brain-stone. • john p 2016-11-07 This is a useful article for me right now, as I’ve just invested a magnificent$21 in buying 5 HC-12 boards from a Chinese vendor via eBay, and they arrived 2 days ago. I set up 2 units and so far I’ve demonstrated that they work right out of the package, and then I tried changing the baud rate from 9.6K to 115.2K, and they worked that way too. Nobody told me that “The transceivers must be physically separated by at least 1.5 meters to function” and I started with them side by side on a breadboard and they were fine! Then I moved them apart and they certainly work from downstairs to upstairs in a wooden house. I’ve been thinking of ways to test them over longer range.

Something I’ve noticed looking at the output of the receiving unit with a scope is that the operation seems to be time-sliced. That is, you send data in at one end, and the bytes come out at the other end, but it’s as if everything that’s collected in an interval of time gets sent in a burst, then there’s a pause and if anything didn’t make it into the first burst it comes in the next one. It seemed to me (very roughly) that the interval was 18msec at 9.6KB, and something like 4msec at 115.2KB. Does this make sense? It does appear that there’s a significant (but not consistent) delay between input at one end, and output at the other. That might be a problem in some applications.

Now I’m wondering if it’s possible to use multiple HC-12 units in a common network, instead of just two. It would have to be set up so that only one unit transmitted at a time, and I could imagine doing that by using a master-slave protocol. The master would send a packet addressing a single slave, and although all would receive it, only one would respond. Or could it be that two units automatically link up even if nothing is being sent, and the presence of a third unit would somehow disrupt communication? I’ll have to try it.

• Mark Hughes 2016-11-07

Hello @John P, glad you find the article useful.  I’m glad you’re having such early success with the HC12s.  I’m also glad you didn’t have any trouble getting them working side by side on a breadboard.  That was an early trouble I had.  If you get a chance, take a look at What is happening in an antenna wire and you’ll see that the electric field close to a transmitting antenna is exceedingly complex.  It doesn’t begin to behave as one might expect an antenna to until around 1-2 wavelengths away—which is around 0.7 m away.  So if you’re ever playing with any transceivers in the future, and you aren’t getting success, separate them.

Now to your second issue—you’ve scoped the output and noticed the same pause in the transmission I did.  I believe it has to do either with the Si4464 packet handling (look at page 39) or the programming of the microcontroller that runs the show.
Why it’s happening is out of our control.  But if it’s a problem for your application, throw a fifo buffer into your circuit using a microcontroller.

Your master-slave protocol should be possible through programming.  If you do find that the slave units compete with one another, a second option would be to use multiple channels, with each slave assigned their own separate channel and the master changing channels to communicate with them (obviously not ideal—but potentially workable).

Mark

• Mark Hughes 2016-11-10

@john p,
What a cool project.  I’m glad that you’re enjoying yourself.  While it certainly doesn’t sound like you need any help, perhaps I could encourage you to post your in-progress or final project in the forums.  I’m sure other enthusiasts could benefit from your expertise.  And who knows—it might motivate me to reclaim my collection of trains from a friend who “borrowed” them about 15 years ago
Mark

• john p 2016-11-09

I think I just proved that the HC-12 can work in a multi-unit network, if you can tolerate fairly slow timing. What I’ve done is connect my computer via a USB-to-serial converter to an HC-12. Mostly using software I wrote for an earlier project, I made the computer send a packet of 5 data bytes out of the port at 115.2KB, sending the packets 4 times a second. Let’s call that setup unit A.

Then upstairs, I put 2 HC-12’s side by side on a breadboard; let’s call them units B and C. Unit B is wired with RX and TX connected together, so it re-transmits anything it receives. Unit C isn’t wired to transmit anything; it’s just an “observer”. First of all, the packets that the computer sends via unit A are received back again from unit B, so I clearly have bidirectional communication. I connected a scope probe to the TX of unit C, and it shows the packet twice, exactly as one would expect: once transmitted by unit A downstairs, and then again by unit B. The lag between the two was quite consistent but quite lengthy, at 10msec.

What I think this shows is that the HC-12 devices don’t suffer any problems from multiple units in the same waveband, i.e. three of them can coexist. Also, although we’re told that they are half-duplex, that isn’t true as far as RX and TX are concerned; you can feed data into RX at the same time as receiving it on TX. (RX and TX here are in the frame of reference of the HC-12, and that’s the way they’re labeled on the circuit board.) I think it’s obvious that my unit C could transmit equally well as unit B, and its data would be received by unit A in the same way. I didn’t try making unit C re-transmit also, because unit B would receive the same data and would re-transmit it, and I’d get a single message bouncing between them forever! However, that 10msec lag between data being received, and the same unit being able to transmit, could well be a problem in actual use. It would depend on what the system requirements were.

• Mark Hughes 2016-11-09

That’s some mighty fine work John!  If you don’t mind me asking—what are you making with yours?  Sounds like you’re having a ton of fun!
Mark

• john p 2016-11-10

I’m in a model railroad club, and we use “cabs”, which are devices with which to control a train. Ours need 2-way communication, with a speed pot and a keypad for user input, and a display, a beeper and signal lights for feedback to the user. At present you designate one cab to run your train, and then you can plug it in at various points around the room, and your train keeps moving when you unplug your cab, and you can resume control when you reconnect. It would be more convenient, and safer, if the cabs could be radio-linked, so a train would never be out of the operator’s control. The reason I’ve been interested in master-slave operation is that I’m seeing this as a way to get all the cabs connected to one serial line, which is possible because the actual amount of data that’s involved isn’t very large. If each cab ended up connected to its own serial line, it might still work, but then we’d have to figure out a way to interface with each serial line separately, which is a little awkward. Yes, it’s definitely fun!

• Mark Hughes 2016-11-30

So, @john p, where are you with your project?  Are we going to take the model train community by storm?