Parts

The HC-05 and HC-06, honestly

An HC-05 or HC-06 is a serial port with a phone on the far end. Mokxi models the UART bit by bit, and the phone is the Bluetooth pane.

There is no radio between them: the phone is a pane in Mokxi, not a real radio (Bluetooth, simulated). What is modeled is the half of the module a circuit can see, the UART, and that turns out to be nearly all of what a sketch does with one.

The little blue board with the button on the end is sold as "Bluetooth for Arduino", and what it actually is could not be simpler: a wireless extension cable for a serial port. Bytes into RXD come out of the phone at the other end; bytes the phone sends come out of TXD. That is the whole of the device from the board's side, and a sketch cannot tell the difference between a paired HC-05 and a wire to a terminal.

Pins and properties

Pin What it does
VCC, GND Supply, 3.6 to 6 V, and ground. The logic is 3.3 V.
TXD The module's output: wire it to the board's RX pin.
RXD The module's input: wire it to the board's TX pin.
EN HC-05 only. High puts it in AT mode.
STATE HC-05 only. High while something is connected.

The HC-06 has the first four only. Both have baud (9600 by default), name (what the phone sees) and connected, whether a phone is linked when the run starts: on for the HC-05, off for the HC-06.

Where the far end is

The phone in the Bluetooth pane. Open the Bluetooth tab while the circuit runs: the module is listed under its name. Pair with its PIN (1234 until AT+PSWD or AT+PIN changes it; a wrong one fails), connect, and the pane is a terminal like the "Serial Bluetooth Terminal" app. What you type goes out of TXD; what the sketch sends into RXD appears under it.

The serial pane has the same far end, under the module's part id with its own input box beside the boards, so typing there is the phone sending too.

Click the module on the canvas to hang up and redial, the same as the pane's Connect and Disconnect. On the HC-05 STATE follows: high while there is somebody on the other end, low while there is not, which is how a sketch knows whether it is talking to itself.

What you cannot learn here is everything on the other side of the antenna: radio range, interference, and the master and slave roles. Those are real, and they are not in this model.

What is real, and it is the half that goes wrong

The bytes are not teleported. TXD and RXD carry 8N1 at the module's baud, bit by bit: a start bit low, eight data bits least significant first, a stop bit high.

So the failure everybody has actually had is reproducible here on purpose. Open the module's properties, set baud to 4800 while the sketch still samples at 9600, and the stop bit lands in the middle of a byte: the frame is thrown away and the log fills with framing errors. It is the commonest HC-05 problem in the world and it is worth causing once, deliberately, with the wires in front of you.

The module ships at 9600 baud in data mode, which is why every tutorial starts with Serial.begin(9600).

Three volts, not five

The board takes 3.6 to 6 V on VCC through its own regulator, and its logic is 3.3 V. TXD swings to 3.3 V, which an AVR reads as a high with room to spare.

RXD is the pin that matters on a real bench: it is not 5 V tolerant, which is why every tutorial puts a divider on the board's transmit line. Nothing in Mokxi can be damaged, so the gallery circuit does not carry one, but TXD really does only reach 3.3 V here, so a circuit that compares it against the 5 V rail behaves the way the real one does. Add the divider on hardware.

Two pins, and one hardware UART

An Uno has one hardware UART and it is the serial monitor's, so the module gets a software one, which is what SoftwareSerial is, and why it exists. The tutorials' sketch compiles and runs as written:

#include <SoftwareSerial.h>
SoftwareSerial BTSerial(2, 3);   // RX, TX: the module's TXD into 2
void setup() { Serial.begin(9600); BTSerial.begin(9600); }
void loop() {
  if (BTSerial.available()) Serial.write(BTSerial.read());
  if (Serial.available()) BTSerial.write(Serial.read());
}

SoftwareSerial.h is the runtime's own, written for Mokxi, with the Arduino library's calls and its mechanism: a pin-change interrupt catches the start bit, and counted loops time the other bits, with interrupts off for the byte. The ATmega328P model runs every instruction at its data sheet cycle count, so the timing is the chip's. It is tested against this module at 9600, 38400 and 57600 baud on a 16 MHz board, at 9600 and 38400 on the 8 MHz Pro Mini, and with the module 4% fast or slow. Three behaviors of the real library carry over, and all matter with this module:

  • Half duplex. While the sketch sends a byte, interrupts are off, so a byte arriving at the same moment is lost. Answer the phone after its line has ended, not in the middle of it.
  • One port listens at a time. With two SoftwareSerial ports, only the one that last called listen() receives. The receive buffer is 64 bytes and holds 63, and overflow() says when it filled.
  • 38400 baud is tight at 8 MHz. On the 8 MHz Pro Mini each byte at 38400 keeps interrupts off for about 95% of its time, so when the millis() timer interrupt lands on a start bit, that byte can come in wrong. In a test of 24 lines of 31 bytes, 23 arrived whole. Use 9600 or 19200 on an 8 MHz board when every byte matters.

The same header works on the Nano, the Pro Mini, the Mega (receive on 10 to 15, 50 to 53 and A8 to A15), the Leonardo and Pro Micro (receive on 8 to 11 and 14 to 16), the ESP8266, the ESP32 and the ESP32-S3. On the ESP boards it is the EspSoftwareSerial way: every edge is timed on an interrupt, so several ports receive at once.

The btlamp sketch counts a byte out by hand at 1200 baud instead, so the whole of it is visible in thirty lines.

AT mode

EN (marked KEY on some boards) high puts the module into command mode: bytes on RXD are commands for the module itself rather than traffic for the far end, and the answers come back on TXD. The subset answered here is the one a lesson uses:

command answer
AT OK
AT+VERSION? +VERSION:3.0-20170601 then OK
AT+NAME? +NAME:<name> then OK
AT+NAME=<text> OK, and the name changes
AT+UART? +UART:<baud>,0,0 then OK
AT+UART=<baud>,0,0 OK, and the rate changes
AT+ADDR? +ADDR:98d3:31:fb1234 then OK
AT+ROLE?, AT+ROLE=<0..2> +ROLE:<n> then OK; OK
AT+CMODE?, AT+CMODE=<0..1> +CMODE:<n> then OK; OK
AT+PSWD?, AT+PSWD=<pin> +PSWD:<pin> then OK; OK
anything else ERROR:(0)

AT mode talks at 38400 baud, whatever the data rate is set to, as the real module does in command mode. So the tutorials' AT sketch, with BTSerial.begin(38400) and EN held high from a pin, works as written. A sketch that stays at 9600 gets nothing sensible back, which is the commonest AT-mode mistake. AT+UART= changes the data rate, which takes effect when EN goes low again. The role and connect mode are stored and read back, and change nothing else, because there is no radio for them to change. The PIN is the one the phone in the Bluetooth pane has to type to pair.

The real module wants each command to end with CR LF ("Both NL & CR" in the Arduino IDE's serial monitor). This one also takes a bare LF, which is what Mokxi's serial monitor sends.

The HC-06

The HC-05's slave-only sibling, on the four-pin board: VCC, GND, TXD and RXD, with no EN and no STATE. It comes out of the bag with nothing connected, and while nothing is connected everything on RXD is an AT command, at the data rate, in the old "linvor" dialect: no line ending, and the module answers once the line has been quiet for a tenth of a second, with no line ending either. Pick "No line ending" for it.

command answer
AT OK
AT+VERSION OKlinvorV1.8
AT+NAME<name> OKsetname, and the pane shows the new name
AT+PIN<four digits> OKsetPIN, and pairing wants the new PIN
AT+BAUD<1 to C> OK<rate> at the old rate, then the new one: 4 is 9600, 8 is 115200
anything else nothing at all

Once the phone connects, the same bytes are traffic, as on the real one.

What is not modeled

The radio, and everything that hangs off it: what the role would do, AT+BIND, AT+INQ and the discovery they belong to. The difference between entering AT mode at power-up and after it: here EN high is AT mode at 38400 either way. The button on the end of the board, which on a real one is the other way into AT mode. Flow control, which the header does not bring out. Any framing but 8N1. And the delay between a byte reaching the module and leaving the far end. Here it is instant, where a real link is tens of milliseconds and jitters.

The circuits to open

Bluetooth chat: the tutorial sketch as written, SoftwareSerial on pins 2 and 3 at 9600. Type as the phone in the module's box; send 1 or 0 for the LED.

Bluetooth lamp: type on, off, blink or status at the module in the serial pane and the lamp answers, with STATE wired so the sketch can tell you when nobody is listening.

See it in action

The tutorial Arduino HC-05 Bluetooth walks through the wiring, the chat sketch and the AT commands.

Two projects take commands from the module's box the way a phone app sends them: the Bluetooth RC car drives a 2WD car on an L298N from F, B, L, R and S, and Bluetooth home automation toggles four relays from 1 to 4.