HC-12 RF Transceiver
&
Raspberry Pi Pico (MicroPython)

Created:  
Updated:   13Aug2024   01:11:20   UTC 2024-08-13T01:11:20Z
Rating:   (0 reviewsThis article has not been rated yet)

The HC-12 wireless RF transceiver modules can be controlled by an Raspberry Pi (RPiRaspberry Pi) microcontroller to establish two-way half-duplex wireless communication. The microcontroller can configure the HC-12 and send/receive messages over 2-Wire (TXTransmitRXReceive) UARTUniversal Asynchronous Receiver-Transmitter.

In this tutorial, wireless text messages will be sent from a transmitting pair of Pico and HC-12 modules to a receiving pair using the UARTUniversal Asynchronous Receiver-Transmitter pins on the Pico. The transmitting Pico and HC-12 pair will be a stand-alone device connected to an external power source. The receiving Pico and HC-12 pair will be connected to a computer over USBUniversal Serial Bus, where a Python program on the computer will be used to read in the messages over the serial port and display them on the console.

HC-12 and RPi Pico Setup

The contents of this tutorial include the hardware wiring setup, software Python and Pyserial setup, Pico transmitter code, Pico receiver code, and Computer Python code.

Setup

RPi Pico Pinout

There are 5 different UARTUniversal Asynchronous Receiver-Transmitter (TXTransmitRXReceive) physical pin pairs on the Pico board, as shown in the figure below, that can be used for UARTUniversal Asynchronous Receiver-Transmitter serial communication: pins (1, 2), (6, 7), (11, 12), (16, 17), and (21, 22).

Powering the Pico can be done by the 5V Micro USBUniversal Serial Bus port, 5V VBUS pin 40, or 1.8V to 5.5V across the VSYS pin 39. The HC-12 has a voltage supply rating of 3.2V to 5.5V DC, so if the 5V Micro USBUniversal Serial Bus port is used to power the Pico, then the HC-12 can be powered by the Pico's VBUS pin 40, VSYS pin 39, or 3.3V pin 36.

RPi Pico UART Pinout

HC-12 Pinout

The pinout for the HC-12 module is given in the figure below. The setup in this tutorial uses the factory default firmware settings on the HC-12 module, so only the power and serial pins 1 through 4 will be used and there is no need to hook up the SET pin (connecting the SET pin to ground will put HC-12 in a configuration mode where settings can be changed).

HC-12 Pinout

Pico & HC-12 Wiring

The wiring between the Pico and HC-12 on both the receiver and transmitter in this tutorial will use the physical pins (21, 22), also labeled GPIO pins (GP16, GP17), on the Pico board for UART0 (TXTransmitRXReceive). These pins on the Pico are conveniently on the same side of the board as the power pins that can be used to power the HC-12 module for the transmitter. The (TXTransmitRXReceive) connections between the Pico and HC-12 need to be crossed, where the Pico TXTransmit pin is connected to the HC-12 RXDReceive Data pin and the Pico RXReceive pin is connected to the HC-12 TXDTransmit Data pin.

HC-12 and RPi Pico Wiring

Python & PySerial

Both Python and the PySerial package must first be installed on the computer to read in microcontroller data over USBUniversal Serial Bus. Also, when connecting the microcontroller to the computer with a USBUniversal Serial Bus cable, the USBUniversal Serial Bus device on the computer needs to be identified to establish a connection.

Python can be downloaded from python.org. A detailed tutorial on how to install Python on Windows, Mac, or Linux can be found at realpython.com.

The PySerial package is open source code written and maintained by Chris Liechti with the functionality to access and communicate with the serial port from Python. PySerial automatically selects the appropriate backend whether you're running on Windows, OSX (Mac), Linux, BSDBerkeley Software Distribution (BSD) operating system (possibly any POSIXPortable Operating System Interface (POSIX) is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems compliant system) or IronPython. The PySerial documentation is at pyserial.readthedocs.io and the source code can be found on GitHub.

Instructions for installing the PySerial package can be found at pyserial.readthedocs.io. It is recommended that you create a virtual environment to install PySerial in. A primer on Python virtual environments can be found at realpython.com. The command for creating a virtual environment is using the venv module is given below.

python -m venv venv

The -m flag stands for module-name. The first venv in the command is the venv module name and the second venv argument is the name of the virtual environment and the folder name it creates.

Then activate the virtual environment.

For Windows, the command is:

> .\venv\Scripts\activate

For Linux and macOS, the command is:

$ source venv/bin/activate

You can install PySerial using the following PIPPIP is a package installer for Python that allows users to install packages from the Python Package Index and other indexes. The name is a recursive acronym for "Pip Installs Packages" command.

python -m pip install pyserial

After PySerial is installed, you can verify the PySerial version with the following commands in Python:

>>> import serial
>>> print(serial.VERSION)


The computer device name is needed by PySerial to access the device. When a microcontroller is connected to the USBUniversal Serial Bus of a computer, it shows up as two devices:

HIDUSB Human Interface Device:
Human Interface Device which provides a generic interface that does not require drivers to be installed. It is limited to 64 Bytes/ms (64kB/sKilobytes Per Second) per endpoint for sending 64 byte "reports" back and forth.
CDCUSB Communication Device Class ACMAbstract Control Model:
Communication Device Class of sub-type Abstract Control Model that simulates a serial port and requires drivers (INF file) to be installed.

On a Linux machine, the USBUniversal Serial Bus CDCUSB Communication Device Class ACMAbstract Control Model is used with the device name /dev/ttyACM0. After plugging in the microcontroller into the USBUniversal Serial Bus you can see the USBUniversal Serial Bus devices listed by running the command dmesg. If a USBUniversal Serial Bus adapter is used, then these devices are usually named /dev/ttyUSB0, /dev/ttyUSB1, etc.

On a Windows PC, the Device Manager will show the microcontroller under the Ports (COMCommunication & LPTLine Print Terminal) as a USBUniversal Serial Bus Serial Device.

RPi Pico Transmitter Code

The code for the transmitting RPi Pico is in MicroPython. It starts out in 'standby' mode, where it waits for an incoming message to set it to 'transmit' mode. When the transmit mode is received, a "Hello" message is transmitted every 3 seconds, with the message count number appended to determine which message was received.

The UART serial object is used to send serial data from physical pin 21 on the Pico to the HC-12 at a baud rate of 9600bpsbits per second. The transmission delay time is setup to be non-blocking so the board can receive incoming messages to change the operation mode (from 'transmit' to 'standby') between transmissions.

Transmitter RPi Pico Code (transmitter.py)
            

RPi Pico Receiver Code

The code for the receiving RPiRaspberry Pi Pico is in MicroPython and acts as a passthrough between the computer and transmitter. It reads operation mode messages from the computer bpsbits per second serial buffer to relay to the transmitter and also reads messages from the transmitter to pass over bpsbits per second to the computer.

Receiver RPi Pico Code (receiver.py)
            

The Pico receiver code starts by importing the following libraries:

UARTUniversal Asynchronous Receiver-Transmitter:
is for the serial communication between the Pico and HC-12 (docs.micropython.org)
Pin:
used to set the GPIOGeneral Purpose Input Output pins for the UARTUniversal Asynchronous Receiver-Transmitter serial RXReceive and TXTransmit on the Pico (docs.micropython.org)
time:
is for delays to wait for a response or allow the serial buffer to fill up with the full message (docs.micropython.org)
select:
is for checking the USBUniversal Serial Bus standard input (stdin) for available data (docs.micropython.org)
sys
is used to read/write to the USBUniversal Serial Bus stdin/stdout (docs.micropython.org)

The serial connection from the Pico to HC-12 is done with a UART() object imported from the machine library, which implements a standard UARTUniversal Asynchronous Receiver-Transmitter/USARTUniversal Synchronous/Asynchronous Receiver-Transmitter duplex serial communications protocol. The UART() constructor arguments are

UART(id, baudrate, rx, tx, bits, parity, stop)

id:
is the UARTUniversal Asynchronous Receiver-Transmitter number for UART0, UART1, etc.
baudrate:
is the clock rate in bpsbits per second
rx:
is the RXReceive pin to use
tx:
is the TXTransmit pin to use
bits:
is the number of bits per character: 7, 8, or 9
parity:
is the error check bit at the end of the message: none, 0 (even), or 1 (odd)
stop:
is the number of stop bits: 1 or 2

More settings can be used in the UART() constructor, which can be found in the MicroPython documentation on UARTUniversal Asynchronous Receiver-Transmitter at docs.micropython.org.

The main part of of the code is contained within an infinite while loop that repeatedly checks and reads incoming messages from the computer, passes the these messages to the transmitter Pico, waits for an ACKAcknowledgment response message from the transmitter Pico, and when received delivers it back to the computer.

Computer Python Code

The computer Python code given below uses the PySerial library to communicate with the receiver over USBUniversal Serial Bus. You can also do this using the Serial Monitor in the Arduino IDEIntegrated Development Environment (IDE) is a software application that helps develop software code efficiently. or other software, but a custom program would offer more flexibility and capability if you want to do something with the incoming messages, such as trigger an alert notification or store the messages in a file or database.

The code below starts by sending an init message to the receiver to change the default operation mode from 'standby' to 'transmit', then runs in an infinite loop to constantly check for incoming messages that were transmitted wirelessly and prints them to the console. This program can be terminated at any time by hitting CTRL+C on the keyboard.

Computer Python Code (computer_hello_msg.py)
            

The Python code starts by importing the following libraries:

platform:
used to determine the operating system to set the serial USBUniversal Serial Bus port name accordingly
os:
used to get the filename of the code (filename = os.path.basename(__file__)) to print to the console.
serial:
the PySerial library used for serial communication between the computer and receiver (pyserial.readthedocs.io)
select:
for polling the serial buffer to detect incoming messages (docs.python.org)
time:
for delays to allow the serial buffer to fill up with the full message (docs.python.org)

The serial connection from the computer to receiver is done with a Serial() object imported from the PySerial library, which implements a standard UARTUniversal Asynchronous Receiver-Transmitter duplex serial communications protocol. The Serial() constructor arguments takes the USBUniversal Serial Bus device (e.g., port="/dev/ttyACM0") on the computer used to communicate with the receiver and the baud rate in bits per second (bpsbits per second) of this connection (e.g., baudrate=9600, which is 9600bpsbits per second, although it could be set higher if needed). This baud rate is for the USBUniversal Serial Bus connection to the receiver and does not need to match the RFRadio Frequency transceiver baud rate because they are on separate serial connections.

The next part of the initial setup is initializing the polling object. Instead of constantly checking the receiver for a response in a loop, it is more efficient to set a polling event interrupt that will hold the execution of the code (blocking) until that event occurs or a specified timeout interval has elapsed (if the timeout argument in milliseconds is provided). This can be accomplished from the select library with the method poll(). More details about the select library and poll can be found in the Python documentation at docs.python.org. A constant POLL_TIMEOUT_ACK parameter is set in milliseconds that specifies how long to wait for an acknowledgment (ACKAcknowledgment) after sending a message. The final part of the initial setup code flushes out the serial buffer with the flush() method, in case there is any remaining data in buffer from a previous run.

The main code starts by sending an init message to the receiver to change its default operation mode from 'standby' to 'transmit'. If the message was received, then the transmitter will start sending 'Hello' messages every 3 seconds to receiver to be relayed to the computer. The computer code waits for a transmitted messages from with a poll() event handler. When a message is starting to be received (i.e., characters are starting to occupy the serial buffer) from the receiver, the code moves onto a 1 second delay. This delay is needed to give enough time for the serial buffer to fill up with the complete message before reading it.

When serial data is transmitted, that information is transferred as raw bytes. A mapping between characters and bytes is needed in order to convert all the characters in a message to bytes (encode) and then convert those bytes back to characters (decode) at the receiving end. ASCIIAmerican Standard Code for Information Interchange is a simple mapping that consist of 128 characters, each uniquely represented by a different byte as integers from 0 - 127. The main limitation with ASCIIAmerican Standard Code for Information Interchange is that it does not have most of the characters in the world, which can not be represented by a single byte so we need a special representation for them. UTFUnicode Transformation Format is a universal standard for mapping Unicode encodings, defining how writing from anywhere in the world, stored in any Unicode encoding format, should be represented on different computer systems.-8 encoding has a lot of characters and this why it is the default used for Python (see docs on encode at docs.python.org). A more in-depth article on character encodings/decodings with Python can be found at realpython.com.

The serial data is read in by Python using the ser.readline() method, which returns a byte object that can be converted to a regular Python string using the .decode('utf-8', 'ignore') method where the 1st argument 'utf-8' specifies the format and the 2nd argument 'ignore' means that any errors in the message will be ignored. Any CRCarriage Return (Carriage Return) or LFLine Feed (Line Feed), \r\n, at the end of the string will be stripped off using Python's .rstrip() method. The Python code then prints the formatted string message to the console for viewing.

This Python script runs in an infinite loop that constantly checks for incoming messages, but the program can be stopped by the user hitting CTRL+C on the keyboard to trigger a keyboard interrupt exception. When this occurs the code sends a message to change the operation mode from from 'transmit' to 'standby' before closing the serial object with ser.close() and exiting the program.

An example of the output on the computer console is shown below. The transmitter sent 5 text messages that were received before the computer program was terminated by hitting CTRL+C on the keyboard.

Computer Console Output
            

Conclusion

There is a lot of room for improvement with the programs used for both the computer and RPiRaspberry Pi Pico boards. This tutorial was intended to be a starting point to understand how the computer and modules interact with each other, but there are some other things to consider if you plan on using this kind of setup.

One limitation with the software is no error message handling if the data packets were corrupted or not received. The HC-12 modules do not have error message handling built in like some other transceivers, such as the nRF24L01+ and RFM69, so you need to implement this in software.

The Python program on the computer can be improved by adding an interactive user input with a GUIGraphical User Interface to allow you to change the operation mode (standby/transmit) or any of the HC-12 configuration parameters without terminating the program. Ping tests could also be implemented to see if the transmitter/receiver pair are alive. If the transmitter is making measurements with a sensor, then the ability to control the measurements (sample rate, number of samples, duration, time of day, etc.) would be useful as well as recording the data to a file or database.

Related Content


HC-12 433 MHz Wireless Transceiver Module

Created:   02Jun2022   22:25:06   UTC 2022-06-02T22:25:06Z
Updated:   12Aug2024   05:23:30   UTC 2024-08-12T05:23:30Z
Rating:  (0 reviewsThis article has not been rated yet)

Overview of the HC-12 433MHz wireless transceiver module. Covers the HC-12 specs, board layout, and modes of operation.


Raspberry Pi Pico

Created:   09Apr2023   02:58:19   UTC 2023-04-09T02:58:19Z
Updated:   18Aug2024   08:09:36   UTC 2024-08-18T08:09:36Z
Rating:  (0 reviewsThis article has not been rated yet)

Overview of the Raspberry Pi Pico microcontroller board that covers specs, board layout, and power.


HC-12 RF Transceiver and Arduino Nano

Created:   30Jun2023   06:48:25   UTC 2023-06-30T06:48:25Z
Updated:   12Aug2024   21:18:26   UTC 2024-08-12T21:18:26Z
Rating:  (0 reviewsThis article has not been rated yet)

Tutorial on how to send wireless messages between two Arduino Nano and HC-12 module pairs.

  • Hardware and Software Setup
  • Arduino Code
  • Computer Python Code with PySerial
  • Computer Console Output

Products

HC-12 RF Module Products

Created:   18Jan2023   08:19:54   UTC 2023-01-18T08:19:54Z
Updated:   09May2024   03:12:24   UTC 2024-05-09T03:12:24Z

  • HC-12 433MHz RF Module
  • Pin Headers
  • 433MHz Antennas
RP2040 <span class="tooltip">RPi<span class="tooltiptext">Raspberry Pi</span></span> Pico Board Products

Created:   27Jul2023   23:50:32   UTC 2023-07-27T23:50:32Z
Updated:   04Sep2024   00:31:23   UTC 2024-09-04T00:31:23Z

Processor:
32-bit 133MHz Dual-Core ARMAdvanced Reduced Instruction Set Computer (RISC) Machines Cortex-M0+
Memory:
2MB QSPIQuad Serial Peripheral Interface (QSPI) is a serial communication interface designed for talking to flash chips by using 4 data lines. Flash and 264KB SRAMStatic Random Access Memory
Interface:
1x Micro-B USB, Up to 30x Digital I/OInput/Output with PWMPulse-Width Modulation, 4x Analog Inputs 12-bit ADCAnalog-to-Digital Converter (ADC, A/D, or A-to-D), 2x UARTUniversal Asynchronous Receiver-Transmitter, 2x I2CInter-Integrated Circuit. Also referred to as IIC or I2C., 2x SPISerial Peripheral Interface, and PIOProgrammable Input/Output (PIO) can be programmed to process data transmission, such as a non-standard serial interface, without using resources from the CPU.
Boards:
RPiRaspberry Pi Pico, Pico H, Pico W, and Pico WH

Article Rating

Sign in to rate this article

Sign In


(0) Comments

Sign in to leave a comment

Sign In