Connect STM32 Blue Pill to NB-IoT with Quectel BC95-G and Apache Mynewt
In the previous article we learnt the AT commands for sending sensor data to a CoAP Server via a Quectel NB-IoT module. Now let’s build an IoT sensor with a real microcontroller — STM32 Blue Pill — and a real NB-IoT module — Quectel BC95-G!
Why are we building it with Apache Mynewt realtime operating system?
Because handling the AT commands for the Quectel module is a little complicated… Imagine we’re polling the temperature sensor every 10 seconds and transmitting the sensor data via NB-IoT. But the Quectel module hasn’t responded to our AT command yet. Do we give up and crash the application?
Fortunately Mynewt is fully capable of multitasking — it will wait for the AT command to complete (or cancel it in case of timeout). It has a built-in CoAP library for composing CoAP messages. And drivers for many sensors. So Mynewt is perfect for building NB-IoT devices!
Here’s what we’ll be building today…
The complete source code is located here…
STM32 Blue Pill, ST-Link V2, Quectel BC95-G breakout board with antenna, NB-IoT SIM
We’ll need the following hardware…
1️⃣ STM32 Blue Pill
4️⃣ NB-IoT SIM from your local NB-IoT network operator
Many thanks to StarHub for sponsoring the NB-IoT SIM that I used for this tutorial!
Blue Pill connected to Quectel BC95-G and ST-Link
Connect Blue Pill to Quectel Module
Connect Blue Pill to Quectel BC95-G and ST-Link as follows…
Both yellow jumpers on Blue
Pill should be set to the
0 position, as shown in the above
Note that we are powering the Quectel module with 5V from ST-Link instead of 3.3V from Blue Pill. That’s because the module requires more power than Blue Pill can provide. (How did I find out? Because the module kept restarting when I powered it from Blue Pill.)
Check the documentation for your Quectel breakout board to confirm that it supports 5V. (Mine does)
SIM partially exposed to show the unusual orientation
Insert the NB-IoT SIM according to the orientation shown in the photo. (Yes the SIM notch faces outward, not inward)
Remember: Always connect the antenna before powering up the NB-IoT module!
Don’t connect ST-Link to your computer yet, we’ll need to install the ST-Link driver in a while.
Follow the instructions below to install the Mynewt build and application files on Windows…
The NB-IoT Program
We have just installed a simple program that reads Blue Pill’s internal temperature sensor every 10 seconds and sends the data to a CoAP Server (thethings.io) over the NB-IoT network.
is the function called upon device startup. Mynewt applications are required to call
sysinit() to start the system services and drivers, including the
drivers for the internal temperature sensor and the NB-IoT module.
start_sensor_listener() is called next to set up the polling
schedule for the temperature sensor. We’ll study this in a while.
is called to connect the NB-IoT module to the NB-IoT network. This may take a few seconds to complete,
start_server_transport() will perform the connection as a background task.
start_server_transport() is part of the Sensor Network Library.
The main event loop appears at
the end of the
main() function. This is required by Mynewt for
processing system events.
Functions start_network_sensor() and handle_sensor_data()
is called by
main() to set the polling schedule for the
What happens when Mynewt has polled the sensor data?
In the call to
Mynewt system function), we instruct Mynewt to call our function
handle_sensor_data() whenever it has polled for new sensor data.
Every 10 seconds after Mynewt
has obtained the raw sensor data from the temperature sensor, Mynewt calls
to work on the raw sensor data.
handle_sensor_data() wraps the raw sensor data into a
sensor_value. Here we specify that the raw sensor data should be
transmitted as an integer with field name
t. It then calls
send_sensor_data() to transmit the sensor value.
Why do we
transmit the raw sensor data as an integer value (like
1715) instead of a floating-point value (like
32.1 degrees Celsius)?
Remember that we are creating an embedded application for a constrained, low-power microcontroller with little ROM and RAM. On constrained devices, it takes a lot of ROM and RAM to convert temperature values from integer to floating-point.
Hence we conserve device resources when we transmit sensor values in their raw, integer forms and let the IoT cloud (thethings.io) convert the values into floating-point.
is called by
handle_sensor_data() to transmit sensor data. In
this program we’re transmitting sensor data to the CoAP server at thethings.io.
thethings.io requires our sensor data to be in this JSON format…
is called, the JSON message is encoded as the payload of a CoAP message. The CoAP message is
transmitted as a UDP packet to thethings.io over the NB-IoT network.
device in the JSON message? We’ll find out in a while.
Run The Program
Debug → Start Debugging
View → Output
Adapter Output to see the Blue Pill log
3️⃣ The debugger pauses at the
Continue or press
4️⃣ The debugger pauses next
Continue or press
The program should now poll the internal temperature sensor every 10 seconds and transmit to thethings.io. Let’s study the Blue Pill execution log…
Check The Log
The log from our Blue Pill should look like this. When we see this in the log…
It means that the program has sent this AT command to the NB-IoT module…
Followed by Carriage Return
0x0d and Line Feed
0x0a characters. Then
the NB-IoT module responded with…
AT+ is present in all AT commands, we won’t show the prefix
AT+ in the log. All the AT commands below are explained in my
When the program starts, it
disables NB-IoT module’s auto-connection (
NCONFIG=AUTOCONNECT,FALSE) and reboots the NB-IoT module (
It selects NB-IoT Frequency Band 8 (
enables the NB-IoT radio transceiver (
CFUN=1) and starts
attaching to the NB-IoT network (
The NB-IoT Frequency Band depends on your country and your NB-IoT network operator. Check with your NB-IoT network operator for the Frequency Band to use.
The NB-IoT Band is configured
The program queries the NB-IoT
registration status (
CEREG?). The response
+CEREG:0,2 means that the NB-IoT module is still registering with
the NB-IoT network.
The program continues to query
the registration status (
CEREG?). In a few seconds, we get the
+CEREG:0,1 which means that the NB-IoT module has
registered with the NB-IoT network.
CGATT? to check whether we have been attached to the NB-IoT network.
+CGATT:1 means that we have been successfully
attached to the NB-IoT network. We may start transmitting data to the network.
Before transmitting, we ask
the NB-IoT module to allocate a local UDP port (
NSOCR=DGRAM,17,0,1). The module returns local port
Next the program reads the
Blue Pill’s internal temperature sensor (every 10 seconds) and obtains the raw temperature value
The program composes the CoAP message with JSON payload (described earlier)…
…And transmits the CoAP
message via the AT command
NSOST=… (not shown in the log).
The hex numbers
58 02 00 01 ... are the bytes of the encoded
CoAP message. You may decode the CoAP message with Wireshark as explained in the previous
The CoAP message is
transmitted by the NB-IoT module to the CoAP server at thethings.io. Notice that the message includes
a device ID
ac913c... This is a random
number that’s transmitted in every CoAP message.
When we Ctrl-Click the URL in the log…
Web page with computed temperature
…We see a web page with the
computed temperature value in degrees Celsius. That’s because thethings.io has
converted the raw temperature into the actual temperature (in degrees Celsius). We have
installed a script at thethings.io that
pushes the computed temperature to
blue-pill-geolocate.appspot.com, so that we could see the computed
The URL (and the random number) changes each time we restart the program. More details about the setup for thethings.io may be found in the previous article.
In the next article we’ll create more NB-IoT devices… this time with Visual Rust!
Also we’ll be checking out these exciting NB-IoT developer kits with onboard low-power STM32 microcontrollers and Quectel NB-IoT modules. Stay tuned!
NB-IoT developer kit with onboard Quectel BC35-G Global NB-IoT module and STM32L431RCT6 microcontroller. From https://item.taobao.com/item.htm?spm=a230r.22.214.171.124a75d82e8brCe&id=577708190839&ns=1&abbucket=15#detail
The following Quectel
documents were very useful for understanding the AT commands. Download them from (free registration
- Quectel BC95-G Hardware Design V1.3: Details of the BC95-G pins
- Quectel BC95 & BC95-G & BC68 Application Design Guide V1.1: Designing applications for BC68
- Quectel BC95-G & BC68 AT Commands Manual V1.4: AT commands
- Quectel BC95-G & BC68 CoAP Application Note V1.0: AT commands for CoAP. Unfortunately I was not able to use the AT commands here to transmit the payload correctly (the transmitted payload was always empty). So I decided to encode the CoAP messages myself.
💎 Advanced Topic: Quectel BC95-G Driver for Mynewt
The Mynewt driver I have created for Quectel BC95-G is located here…
contains the main logic for the driver. It sends AT commands and handles responses and timeouts.
libraries (ported from mbed) are called by
driver.cpp to parse
the AT responses from the Quectel module. The dynamic heap memory allocation in the original mbed
version has been replaced by static memory buffers, to reduce RAM and ROM size.
contains the driver creation code required by Mynewt
provides the OIC (Open Interconnect Consortium) network transport required for transmitting CoAP
messages via Mynewt’s OIC framework
defines one configuration setting:
NBIOT_BAND, the NB-IoT band
Why do we use
mbufs when transmitting sensor data? Like in
(“memory buffer”) is a chain of
memory blocks that’s optimised for transmitting network messages. Recall the structure of our CoAP
message from the previous
article… Every CoAP message has a Preamble and an Options Header
that are usually fixed in length for the session. But the Payload of
the message may vary, depending on the sensor data.
Shall we keep reallocating and deallocating the memory blocks for the Preamble, Options and Payload every time we transmit a CoAP message?
No, we may actually reuse the same
mbuf to hold the fixed-length
Preamble and Options. But depending on the sensor data, we’ll attach one or more
mbufs to the chain to hold the entire Payload. This speeds up the
composition of CoAP messages.
That’s why in the driver code
we see the program walking through each
mbuf in the chain and transmitting
mbuf. This keeps the networking code highly efficient, just
like early versions of the Unix