Difference between revisions of "Nordic SoC"

From Organic Design wiki
m (Async)
m (DNS)
 
(28 intermediate revisions by the same user not shown)
Line 29: Line 29:
  
 
== Networking ==
 
== Networking ==
 +
 +
Docs: https://docs.nordicsemi.com/bundle/ref_at_commands_nrf91x1/page/REF/at_commands/intro_nrf91x1.html
 +
 +
=== Enable Serial AT Commands ===
 +
<source lang="env">
 +
CONFIG_AT_HOST_LIBRARY=y
 +
CONFIG_UART_INTERRUPT_DRIVEN=y
 +
</source>
  
 
=== Modem Init ===
 
=== Modem Init ===
Line 134: Line 142:
 
char resp[128];
 
char resp[128];
 
// AT%XBANDLOCK=<Mode 0=remove-lock,1=persistant-lock,2=runtime-lock>,<Mask>
 
// AT%XBANDLOCK=<Mode 0=remove-lock,1=persistant-lock,2=runtime-lock>,<Mask>
err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XBANDLOCK=1,\"%s\"", BAND_BIT_MASK);
+
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XBANDLOCK=1,\"%s\"", BAND_BIT_MASK);
 
if (!err) {
 
if (!err) {
 
   printk("Assigned bit mask: %s\n", resp);
 
   printk("Assigned bit mask: %s\n", resp);
Line 151: Line 159:
 
#include <modem/lte_lc.h>
 
#include <modem/lte_lc.h>
  
err = lte_lc_connect();
+
int err = lte_lc_connect();
  
 
if (err) {
 
if (err) {
Line 193: Line 201:
 
} else {
 
} else {
 
   printk("connect started\n");
 
   printk("connect started\n");
 +
}
 +
</source>
 +
 +
=== Get SIM Status===
 +
The SIM will return error until it lte_lc_connect gets called.
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[128];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CPIN?");
 +
 +
if (err) {
 +
  printk("Failed to get SIM status, err %d\n", err);
 +
} else {
 +
  printk("SIM status: %s\n", resp);
 +
}
 +
</source>
 +
 +
=== Get Connection Status ===
 +
< -100 dBm is a poor connection.
 +
 +
<source lang="c">
 +
// CONFIG_LTE_LC_CONN_EVAL_MODULE=y
 +
#include <modem/lte_lc.h>
 +
#include <include/modem/modem_info.h>
 +
 +
struct lte_lc_conn_eval_params params;
 +
int err = lte_lc_conn_eval_params_get(&params);
 +
 +
if (err == 0) {
 +
  printk("RSRP: %d dBm\n", RSRP_IDX_TO_DBM(params.rsrp));
 +
  printk("SNR: %d dB\n", params.snr);
 +
  printk("Cell ID: %d\n", params.cell_id);
 +
  printk("Band: %d\n", params.band);
 +
} else {
 +
  printk("Connection evaluation failed: %d\n", err);
 +
}
 +
</source>
 +
 +
=== APN/PDP ===
 +
 +
Access Point Name (APN) is the name of the access point that the device should connect to, it defines the destination network and connection rules.
 +
Packet Data Protocol (PDP) is the protocol that sets up a logical connection between the device and that network and provides PDP contexts which is essentially a tunnel over the connection.
 +
If you configure the Packet Data Network (PDN) to PDN_FAM_NONIP you are configuring it so that it sends packets of raw data and the network will need to be configured for Non IP Data Delivery (NIDD) and you specify where it should go. Given that it doesn't have the overhead of IP it saves some data but can have higher latency.
 +
 +
You can configure the default APN in the config or use the one already configured on the sim if omitted:
 +
<source lang="env">
 +
CONFIG_PDN_DEFAULTS_OVERRIDE=y
 +
CONFIG_PDN_DEFAULT_APN="apn.iot.example"
 +
CONFIG_PDN_DEFAULT_FAM_IPV4V6=y
 +
</source>
 +
 +
Or you can manually configure it:
 +
<source lang="c">
 +
// CONFIG_PDN=y
 +
#include <modem/pdn.h>
 +
uint8_t cid;
 +
int err;
 +
 +
err = pdn_ctx_create(&cid, NULL);
 +
 +
if (err && err != -EALREADY) {
 +
  printk("Failed to get CID: %d\n", err);
 +
  return err;
 +
}
 +
 +
printk("pdn_ctx_create returned %d, cid: %d\n", ret, cid);
 +
 +
// Can PDN_FAM_IPV4V6, PDN_FAM_IPV4, PDN_FAM_IPV6 or PDN_FAM_NONIP (NIDD)
 +
err = pdn_ctx_configure(cid, "apn.iot.example", PDN_FAM_IPV4V6, NULL);
 +
if (err) {
 +
  printk("Failed to configure APN: %d\n", err);
 +
  return err;
 +
}
 +
</source>
 +
 +
=== Link Connection Power Saving Mode ===
 +
 +
Tracking Area Update (TAU) is how long it can sleep before needing to update the network with it's position, you request this with Requested Periodic TAU (RPTAU).
 +
Requested Active Time (RAT) is how long it should be active for during it's wake up period after TAU or sending data.
 +
If you are sending data frequently you can set RPTAU to be a longer time (assuming that the device isn't physically moving) and let your data sending update the network in the meantime.
 +
RAT should be set long enough to download updates, if you are sending data you should set this to be long enough that you can get your response back.
 +
 +
<source lang="c">
 +
// # Enable PSM (REQUIRED):
 +
// CONFIG_LTE_LC_PSM_MODULE=y
 +
 +
// # Automatic PSM:
 +
// CONFIG_LTE_PSM_REQ=y
 +
 +
// # PSM configuration:
 +
// CONFIG_LTE_PSM_REQ_RPTAU_SECONDS=86400
 +
// CONFIG_LTE_PSM_REQ_RAT_SECONDS=30
 +
#include <modem/lte_lc.h>
 +
 +
// Equivalent to:
 +
// CONFIG_LTE_PSM_REQ=y
 +
lte_lc_psm_req(true);
 +
 +
// Equivalent to:
 +
// CONFIG_LTE_PSM_REQ_RPTAU_SECONDS=rptau
 +
// CONFIG_LTE_PSM_REQ_RAT_SECONDS=rat
 +
lte_lc_psm_param_set_seconds(rptau, rat);
 +
</source>
 +
 +
=== Get Modem Firmware ===
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[64];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGMR");
 +
if (!err) {
 +
  printk("Modem firmware: %s\n", resp);
 +
} else {
 +
  printk("Failed to get modem firmware: %d\n", err);
 +
}
 +
</source>
 +
 +
=== Get Signal Quality ===
 +
The CSQ command always returns 99,99 which means error despite having a connect, need to use the connection evaluation in the other example.
 +
 +
==== WiFi? ====
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[64];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CSQ");
 +
if (!err) {
 +
  printk("Signal quality: %s\n", resp);
 +
} else {
 +
  printk("Failed to get signal quality: %d\n", err);
 +
}
 +
</source>
 +
 +
==== LTE ====
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[64];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CESQ");
 +
if (!err) {
 +
  printk("Signal quality: %s\n", resp);
 +
} else {
 +
  printk("Failed to get signal quality: %d\n", err);
 +
}
 +
</source>
 +
 +
=== Get Network Registration ===
 +
 +
<source>
 +
+CEREG: <n>,<stat>,<...other-details>
 +
 +
n = event verbosity, higher = more event sensitivity
 +
stat = stats:
 +
0 – Not registered. User Equipment (UE) is not currently searching for an operator to register to.
 +
1 – Registered, home network
 +
2 – Not registered, but UE is currently trying to attach or searching an operator to register to
 +
3 – Registration denied
 +
4 – Unknown (for example, out of Evolved Terrestrial Radio Access Network (E-UTRAN) coverage)
 +
5 – Registered, roaming
 +
90 – Not registered due to Universal Integrated Circuit Card (UICC) failure
 +
 +
Standard flow of <stat> in response: 4 -> 2 -> 1
 +
</source>
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[64];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CEREG?");
 +
if (!err) {
 +
  printk("Network registration: %s\n", resp);
 +
} else {
 +
  printk("Failed to get network registration: %d\n", err);
 +
}
 +
</source>
 +
 +
=== Get Connection Details ===
 +
 +
See here for response details: https://docs.nordicsemi.com/bundle/ref_at_commands_nrf91x1/page/REF/at_commands/nw_service/xmonitor_set.html
 +
 +
<source lang="c">
 +
#include <nrf_modem_at.h>
 +
 +
char resp[64];
 +
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XMONITOR");
 +
if (!err) {
 +
  printk("Connection details: %s\n", resp);
 +
} else {
 +
  printk("Failed to get connection details: %d\n", err);
 +
}
 +
</source>
 +
 +
=== UDP ===
 +
 +
<source lang="c">
 +
// CONFIG_NRF_MODEM_LIB=y
 +
// CONFIG_LTE_LINK_CONTROL=y
 +
#include <nrf_socket.h>
 +
 +
#define SERVER_IP  "123.456.789.123"  // UDP server IP
 +
#define SERVER_PORT 12345
 +
#define MESSAGE    "Hello world!"
 +
 +
int sock = nrf_socket(NRF_AF_INET, NRF_SOCK_DGRAM, NRF_IPPROTO_UDP);
 +
if (sock < 0) {
 +
  printk("Failed to create socket: %d\n", sock);
 +
  return;
 +
}
 +
 +
struct nrf_sockaddr_in remote_addr = {
 +
  .sin_family = NRF_AF_INET,
 +
  .sin_port = nrf_htons(SERVER_PORT),
 +
};
 +
 +
// Convert IP to binary
 +
int err = nrf_inet_pton(NRF_AF_INET, SERVER_IP, &remote_addr.sin_addr);
 +
if (err != 1) {
 +
  printk("Invalid IP address format: %d\n", err);
 +
  nrf_close(sock);
 +
  return;
 +
}
 +
 +
// Send message
 +
err = nrf_sendto(sock, MESSAGE, strlen(MESSAGE), 0, (struct nrf_sockaddr *)&remote_addr, sizeof(remote_addr));
 +
if (err < 0) {
 +
  printk("Failed to send UDP message: %d\n", err);
 +
  nrf_close(sock);
 +
  return;
 +
}
 +
 +
printk("Message sent!\n");
 +
 +
// Optional: receive response
 +
char recv_buf[128];
 +
struct nrf_sockaddr_in from_addr;
 +
nrf_socklen_t from_len = sizeof(from_addr);
 +
err = nrf_recvfrom(sock, recv_buf, sizeof(recv_buf) - 1, 0, (struct nrf_sockaddr *)&from_addr, &from_len);
 +
if (err > 0) {
 +
  recv_buf[err] = '\0';
 +
  printk("Received: %s\n", recv_buf);
 +
} else {
 +
  printk("No data received or recv error: %d\n", err);
 +
}
 +
 +
nrf_close(sock);
 +
</source>
 +
 +
=== DNS ===
 +
 +
'''prj.conf'''
 +
<source lang="sh">
 +
CONFIG_DNS_RESOLVER=y
 +
 +
# If not using default DNS (I.e. not the network provided one.).
 +
CONFIG_DNS_SERVER_IP_ADDRESSES=y
 +
CONFIG_DNS_SERVER1="8.8.8.8"
 +
</source>
 +
 +
'''dns.c'''
 +
<source lang="c">
 +
#include <modem/at_parser.h>
 +
#include <nrf_modem_at.h>
 +
#include <zephyr/net/dns_resolve.h>
 +
 +
#define DNS_TIMEOUT (5 * MSEC_PER_SEC)
 +
#define DNS_BACKUP "8.8.8.8"
 +
 +
struct dns_resolved_data {
 +
  bool resolved;
 +
  uint8_t *address;
 +
};
 +
 +
static void dns_result_cb(enum dns_resolve_status status, struct dns_addrinfo *info, void *user_data) {
 +
  struct dns_resolved_data *data = (struct dns_resolved_data *)user_data;
 +
 +
  // Handle different DNS resolve statuses
 +
  switch (status) {
 +
  case DNS_EAI_CANCELED:
 +
    printk("DNS query was canceled\n");
 +
    data->resolved = true;
 +
    return;
 +
  case DNS_EAI_FAIL:
 +
    printk("DNS resolve failed\n");
 +
    data->resolved = true;
 +
    return;
 +
  case DNS_EAI_NODATA:
 +
    printk("Cannot resolve address\n");
 +
    data->resolved = true;
 +
    return;
 +
  case DNS_EAI_ALLDONE:
 +
    printk("DNS resolving finished\n");
 +
    data->resolved = true;
 +
    return;
 +
  case DNS_EAI_INPROGRESS:
 +
    break;
 +
  default:
 +
    printk("DNS resolving error (%d)\n", status);
 +
    data->resolved = true;
 +
    return;
 +
  }
 +
 +
  if (!info) {
 +
    return;
 +
  }
 +
 +
  // If the address family is IPv4, copy the binary IP address
 +
  if (info->ai_family == AF_INET) {
 +
    // Copy the binary address directly into the user_data buffer
 +
    memcpy(data->address, &net_sin(&info->ai_addr)->sin_addr, NET_IPV4_ADDR_SIZE);
 +
  } else {
 +
    printk("Invalid IP address family %d\n", info->ai_family);
 +
  }
 +
 +
  data->resolved = true;
 +
}
 +
 +
int dns_query_ipv4(const char *hostname, uint8_t *ip_addr) {
 +
  static uint16_t dns_id;
 +
 +
  struct dns_resolved_data user_data = {.resolved = false, .address = ip_addr};
 +
 +
  memset(user_data.address, 0, NET_IPV4_ADDR_SIZE);
 +
 +
  // Initialize DNS query
 +
  int ret = dns_get_addr_info(hostname, DNS_QUERY_TYPE_A, &dns_id, dns_result_cb, &user_data, DNS_TIMEOUT);
 +
 +
  if (ret < 0) {
 +
    return -1;
 +
  }
 +
 +
  // Block until DNS resolution is done
 +
  while (!user_data.resolved) {
 +
    k_msleep(100);
 +
  }
 +
 +
  uint8_t zeroed_addr[NET_IPV4_ADDR_SIZE];
 +
  memset(zeroed_addr, 0, NET_IPV4_ADDR_SIZE);
 +
 +
  return (memcmp(user_data.address, zeroed_addr, NET_IPV4_ADDR_SIZE) != 0) ? 0 : -1;
 +
}
 +
 +
int extract_dns_addresses(char *dns_primary, char *dns_secondary) {
 +
  struct at_parser parser;
 +
  char resp[256];
 +
  size_t len = NET_IPV4_ADDR_LEN;
 +
 +
  memset(dns_primary, 0, NET_IPV4_ADDR_LEN);
 +
  memset(dns_secondary, 0, NET_IPV4_ADDR_LEN);
 +
 +
  if (nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGDCONT?")) {
 +
    return -1;
 +
  }
 +
 +
  if (at_parser_init(&parser, resp)) {
 +
    return -1;
 +
  }
 +
 +
  uint16_t num;
 +
 +
  if (at_parser_num_get(&parser, 1, &num)) {
 +
    return -1;
 +
  }
 +
 +
  if (nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGCONTRDP=%d", num)) {
 +
    return -1;
 +
  }
 +
 +
  if (at_parser_init(&parser, resp)) {
 +
    return -1;
 +
  }
 +
 +
  if (at_parser_string_get(&parser, 6, dns_primary, &len)) {
 +
    return -1;
 +
  }
 +
 +
  len = NET_IPV4_ADDR_LEN;
 +
 +
  if (at_parser_string_get(&parser, 7, dns_secondary, &len)) {
 +
    return 1;
 +
  }
 +
 +
  return 2;
 +
}
 +
 +
int init_dns() {
 +
  const char *dns_addrs[4];
 +
  char dns_primary[NET_IPV4_ADDR_LEN];
 +
  char dns_secondary[NET_IPV4_ADDR_LEN];
 +
 +
  int index = 0;
 +
  int ret = extract_dns_addresses(dns_primary, dns_secondary);
 +
 +
  if (ret >= 1) {
 +
    dns_addrs[index] = dns_primary;
 +
    index++;
 +
  }
 +
 +
  if (ret >= 2) {
 +
    dns_addrs[index] = dns_secondary;
 +
    index++;
 +
  }
 +
 +
  dns_addrs[index] = DNS_BACKUP;
 +
  index++;
 +
 +
  dns_addrs[index] = NULL;
 +
 +
  return dns_resolve_init(dns_resolve_get_default(), dns_addrs, NULL);
 +
}
 +
</source>
 +
 +
'''Usage'''
 +
<source lang="c">
 +
#include "dns.c"
 +
 +
// ...After connected to network...
 +
 +
// Use network provided dns.
 +
int ret = init_dns();
 +
 +
// Use prj.conf network instead:
 +
// int ret = dns_resolve_init_default(dns_resolve_get_default()); // #include <zephyr/net/dns_resolve.h>
 +
 +
if (ret < 0) {
 +
  printk("Failed to init dns: %d\n", err);
 +
}
 +
 +
uint8_t ip_addr[NET_IPV4_ADDR_SIZE];
 +
int ret = dns_query_ipv4(SERVER_DOMAIN, ip_addr, NET_IPV4_ADDR_SIZE);
 +
if (ret == 0) {
 +
  printk("Resolved IP: %d.%d.%d.%d\n", ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
 +
} else {
 +
  printk("DNS resolution failed\n");
 +
}
 +
</source>
 +
 +
== GPIO ==
 +
 +
GPIO although can be done directly is setup through references in a device tree, you can add your own references to circuits and reference them with with nice references that make sense in the code.
 +
 +
<source lang="c">
 +
// CONFIG_GPIO=y
 +
#include <zephyr/kernel.h>
 +
#include <zephyr/drivers/gpio.h>
 +
 +
#define SLEEP_TIME_MS 100
 +
#define LED0_NODE DT_ALIAS(led0)
 +
 +
int err;
 +
bool led_state = true;
 +
 +
if (!gpio_is_ready_dt(&led)) {
 +
  printf("error: GPIO is not ready");
 +
  return 0;
 +
}
 +
 +
err = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
 +
 +
if (err < 0) {
 +
  printf("could not configure pin");
 +
  return 0;
 +
}
 +
 +
while (1) {
 +
  reterr= gpio_pin_toggle_dt(&led);
 +
 +
  if (err < 0) {
 +
    return 0;
 +
  }
 +
 +
  led_state = !led_state;
 +
  printf("LED state: %s\n", led_state ? "ON" : "OFF");
 +
  k_msleep(SLEEP_TIME_MS);
 
}
 
}
 
</source>
 
</source>

Latest revision as of 01:34, 18 September 2025

VS Code is the only IDE that has extensions for Nordic Connect, so you will want to use that and download the Nordic Connect extensions.

Installing nrfutil

Download nrfutil, make it executable and move it somewhere where the path can pick it up, e.g.: ~/.local/bin/. You will also need to install the device module with it:

nrfutil search
nrfutil install device

Installing the SDK

I recommend installing version 3.0.2 because it just works whereas 3.1.0 runs into many issues.

3.0.2

Follow the walkthrough for VS Code: vscode:/nordic-semiconductor.nrf-connect-extension-pack/quickstart and select 3.0.2.

3.1.0

Note: I could not get my project to build with this version.

It's easiest to follow the walkthrough for VS Code: vscode:/nordic-semiconductor.nrf-connect-extension-pack/quickstart. I had an issue where installing the SDK would fail, checking the logs it threw on unpacking the SDK archive. I attempting to manually extract this and found that it failed beacause it contained some ridiculously long filenames in there, specifically:

v3.1.0/modules/lib/matter/examples/chef/devices/rootnode_contactsensor_lightsensor_occupancysensor_temperaturesensor_pressuresensor_flowsensor_humiditysensor_airqualitysensor_powersource_367e7cea91

That wasn't the only long file, so I extracted everything but v3.1.0/modules/lib/matter/examples/chef/devices and then extracted file that wasn't too long in that. You will want it extracted to ~/ncs/v3.1.0 but you want to rename it to something else temporarily - click install in VS Code, select the SDK/Version and when it asks for the install path don't hit enter just yet, first rename the directory back to v3.1.0 then hit enter in VS Code and it will detect that it is already installed and work fine. If you don't rename it first VS Code will complain that the directory already exists and if you don't have it named in the right place VS Code will attempt to install it normally and fail again.

Networking

Docs: https://docs.nordicsemi.com/bundle/ref_at_commands_nrf91x1/page/REF/at_commands/intro_nrf91x1.html

Enable Serial AT Commands

CONFIG_AT_HOST_LIBRARY=y
CONFIG_UART_INTERRUPT_DRIVEN=y

Modem Init

// CONFIG_NRF_MODEM_LIB=y
#include <modem/nrf_modem_lib.h>

// Init modem manually (auto init is disabled)
int err = nrf_modem_lib_init();

if (err < 0) {
  printk("Modem init failed: %d\n", err);
}

RAT Selection

Radio Access Technology (RAT). You can enable LTE-M, NB-IoT and GNSS or a combination of these RATs. If you have more than one selected you can configure what one is prefered.


Option 1, force by config.

CONFIG_LTE_NETWORK_MODE_NBIOT=y

Option 2, use lte link control.

// CONFIG_LTE_LINK_CONTROL=y ?
#include <modem/lte_lc.h>

int err = lte_lc_system_mode_set(LTE_LC_SYSTEM_MODE_NBIOT, LTE_LC_SYSTEM_MODE_PREFER_NBIOT);

if (!err) {
  printk("RAT Set: %d\n", err);
} else {
  printk("Failed to set RAT: %d\n", err);
}

Option 3, use AT commands:

#include <nrf_modem_at.h>
char resp[64];

// AT%XSYSTEMMODE=<LTE-M 1/0>,<NB-IoT 1/0>,<GNSS 1/0>,<Preference 0=none,1=LTE-M,2=NB-IoT,3=SIM-preference-or-LTE-M,4=SIM-preference-or-NB-IoT>
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XSYSTEMMODE=0,1,0,2");
if (!err) {
  printk("Set system mode: %s\n", resp);
} else {
  printk("Failed to set system mode: %d\n", err);
}

Public Land Mobile Network (PLMN)

The PLMN is a combination of Mobile Country Code (MCC) and Mobile Network Code (MNC). MCC is a 3 digit number assigned to the country, MNC is a 2 digit code assigned to the network operator. PLMN is MCC concatenated with MNC.

You can find the MCC and MNC here: https://www.mcc-mnc.com


Reading Current

#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+COPS?");
if (!err){
  printk(resp);
  /* E.g.
  +COPS: 0,2,"26201",7
  OK
  */
} else {
  printk("Failed to assign operator: %d\n", err);
}

Manual Selection

This example uses NZ MCC: 530 with One's MNC 01:

#include <nrf_modem_at.h>

const char *PLMN = "53001";
char resp[64];

// AT+COPS=<Mode 0=automatic,1=manual>,<Format=0=Long-Alphanumeric,1=Short-Alphanumeric,2=Numeric>
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+COPS=1,2,\"%s\"", PLMN);
if (!err){
  printk("Assigned operator: %s\n", resp);
} else {
  printk("Failed to assign operator: %d\n", err);
}

Manual Band Selection

You can get or set the bitmask for what bands are permitted, this looks like a string of 1s & 0s where the position (reading from the right) of the 1 is the band that is enabled.

You should check the band on NVM and only set it if it's not already set in order to save NVM write cycles and power.

This example selects bands 3 and 28.

#include <nrf_modem_at.h>

const char *BAND_BIT_MASK = "1000000000000000000000000100";
char resp[128];
// AT%XBANDLOCK=<Mode 0=remove-lock,1=persistant-lock,2=runtime-lock>,<Mask>
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XBANDLOCK=1,\"%s\"", BAND_BIT_MASK);
if (!err) {
  printk("Assigned bit mask: %s\n", resp);
} else {
  printk("Failed to assign bit mask: %d\n", err);
}

Connecting

Sync

You can do a synchronous request which will hang your code until it connects or throws.

// CONFIG_LTE_LINK_CONTROL=y
#include <modem/lte_lc.h>

int err = lte_lc_connect();

if (err) {
  printk("LTE connect failed: %d\n", err);
}

Async

// CONFIG_LTE_LINK_CONTROL=y
#include <modem/lte_lc.h>

static void lte_handler(const struct lte_lc_evt *const evt) {
  if (evt->type == LTE_LC_EVT_NW_REG_STATUS) {
    switch (evt->nw_reg_status) {
    case LTE_LC_NW_REG_REGISTERED_HOME:
      printk("Registered to home network\n");
      break;
    case LTE_LC_NW_REG_REGISTERED_ROAMING:
      printk("Registered to roaming network\n");
      break;
    case LTE_LC_NW_REG_SEARCHING:
      printk("Searching for network\n");
      break;
    case LTE_LC_NW_REG_REGISTRATION_DENIED:
      printk("Registration denied\n");
      break;
    case LTE_LC_NW_REG_UNKNOWN:
      printk("Network status unknown\n");
      break;
    default:
      printk("Network registration failed or unknown status: %d\n", evt->nw_reg_status);
      break;
    }
  }
}

int err = lte_lc_connect_async(lte_handler);
if (err) {
  printk("start connect failed, err %d\n", err);
} else {
  printk("connect started\n");
}

Get SIM Status

The SIM will return error until it lte_lc_connect gets called.

#include <nrf_modem_at.h>

char resp[128];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CPIN?");

if (err) {
  printk("Failed to get SIM status, err %d\n", err);
} else {
  printk("SIM status: %s\n", resp);
}

Get Connection Status

< -100 dBm is a poor connection.

// CONFIG_LTE_LC_CONN_EVAL_MODULE=y
#include <modem/lte_lc.h>
#include <include/modem/modem_info.h>

struct lte_lc_conn_eval_params params;
int err = lte_lc_conn_eval_params_get(&params);

if (err == 0) {
  printk("RSRP: %d dBm\n", RSRP_IDX_TO_DBM(params.rsrp));
  printk("SNR: %d dB\n", params.snr);
  printk("Cell ID: %d\n", params.cell_id);
  printk("Band: %d\n", params.band);
} else {
  printk("Connection evaluation failed: %d\n", err);
}

APN/PDP

Access Point Name (APN) is the name of the access point that the device should connect to, it defines the destination network and connection rules. Packet Data Protocol (PDP) is the protocol that sets up a logical connection between the device and that network and provides PDP contexts which is essentially a tunnel over the connection. If you configure the Packet Data Network (PDN) to PDN_FAM_NONIP you are configuring it so that it sends packets of raw data and the network will need to be configured for Non IP Data Delivery (NIDD) and you specify where it should go. Given that it doesn't have the overhead of IP it saves some data but can have higher latency.

You can configure the default APN in the config or use the one already configured on the sim if omitted:

CONFIG_PDN_DEFAULTS_OVERRIDE=y
CONFIG_PDN_DEFAULT_APN="apn.iot.example"
CONFIG_PDN_DEFAULT_FAM_IPV4V6=y

Or you can manually configure it:

// CONFIG_PDN=y
#include <modem/pdn.h>
uint8_t cid;
int err;

err = pdn_ctx_create(&cid, NULL);

if (err && err != -EALREADY) {
  printk("Failed to get CID: %d\n", err);
  return err;
}

printk("pdn_ctx_create returned %d, cid: %d\n", ret, cid);

// Can PDN_FAM_IPV4V6, PDN_FAM_IPV4, PDN_FAM_IPV6 or PDN_FAM_NONIP (NIDD)
err = pdn_ctx_configure(cid, "apn.iot.example", PDN_FAM_IPV4V6, NULL);
if (err) {
  printk("Failed to configure APN: %d\n", err);
  return err;
}

Link Connection Power Saving Mode

Tracking Area Update (TAU) is how long it can sleep before needing to update the network with it's position, you request this with Requested Periodic TAU (RPTAU). Requested Active Time (RAT) is how long it should be active for during it's wake up period after TAU or sending data. If you are sending data frequently you can set RPTAU to be a longer time (assuming that the device isn't physically moving) and let your data sending update the network in the meantime. RAT should be set long enough to download updates, if you are sending data you should set this to be long enough that you can get your response back.

// # Enable PSM (REQUIRED):
// CONFIG_LTE_LC_PSM_MODULE=y

// # Automatic PSM:
// CONFIG_LTE_PSM_REQ=y

// # PSM configuration:
// CONFIG_LTE_PSM_REQ_RPTAU_SECONDS=86400
// CONFIG_LTE_PSM_REQ_RAT_SECONDS=30
#include <modem/lte_lc.h>

// Equivalent to:
// CONFIG_LTE_PSM_REQ=y
lte_lc_psm_req(true);

// Equivalent to:
// CONFIG_LTE_PSM_REQ_RPTAU_SECONDS=rptau
// CONFIG_LTE_PSM_REQ_RAT_SECONDS=rat
lte_lc_psm_param_set_seconds(rptau, rat);

Get Modem Firmware

#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGMR");
if (!err) {
  printk("Modem firmware: %s\n", resp);
} else {
  printk("Failed to get modem firmware: %d\n", err);
}

Get Signal Quality

The CSQ command always returns 99,99 which means error despite having a connect, need to use the connection evaluation in the other example.

WiFi?

#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CSQ");
if (!err) {
  printk("Signal quality: %s\n", resp);
} else {
  printk("Failed to get signal quality: %d\n", err);
}

LTE

#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CESQ");
if (!err) {
  printk("Signal quality: %s\n", resp);
} else {
  printk("Failed to get signal quality: %d\n", err);
}

Get Network Registration

+CEREG: <n>,<stat>,<...other-details>

n = event verbosity, higher = more event sensitivity
stat = stats:
0 – Not registered. User Equipment (UE) is not currently searching for an operator to register to.
1 – Registered, home network
2 – Not registered, but UE is currently trying to attach or searching an operator to register to
3 – Registration denied
4 – Unknown (for example, out of Evolved Terrestrial Radio Access Network (E-UTRAN) coverage)
5 – Registered, roaming
90 – Not registered due to Universal Integrated Circuit Card (UICC) failure

Standard flow of <stat> in response: 4 -> 2 -> 1
#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT+CEREG?");
if (!err) {
  printk("Network registration: %s\n", resp);
} else {
  printk("Failed to get network registration: %d\n", err);
}

Get Connection Details

See here for response details: https://docs.nordicsemi.com/bundle/ref_at_commands_nrf91x1/page/REF/at_commands/nw_service/xmonitor_set.html

#include <nrf_modem_at.h>

char resp[64];
int err = nrf_modem_at_cmd(resp, sizeof(resp), "AT%%XMONITOR");
if (!err) {
  printk("Connection details: %s\n", resp);
} else {
  printk("Failed to get connection details: %d\n", err);
}

UDP

// CONFIG_NRF_MODEM_LIB=y
// CONFIG_LTE_LINK_CONTROL=y
#include <nrf_socket.h>

#define SERVER_IP   "123.456.789.123"  // UDP server IP
#define SERVER_PORT 12345
#define MESSAGE     "Hello world!"

int sock = nrf_socket(NRF_AF_INET, NRF_SOCK_DGRAM, NRF_IPPROTO_UDP);
if (sock < 0) {
  printk("Failed to create socket: %d\n", sock);
  return;
}

struct nrf_sockaddr_in remote_addr = {
  .sin_family = NRF_AF_INET,
  .sin_port = nrf_htons(SERVER_PORT),
};

// Convert IP to binary
int err = nrf_inet_pton(NRF_AF_INET, SERVER_IP, &remote_addr.sin_addr);
if (err != 1) {
  printk("Invalid IP address format: %d\n", err);
  nrf_close(sock);
  return;
}

// Send message
err = nrf_sendto(sock, MESSAGE, strlen(MESSAGE), 0, (struct nrf_sockaddr *)&remote_addr, sizeof(remote_addr));
if (err < 0) {
  printk("Failed to send UDP message: %d\n", err);
  nrf_close(sock);
  return;
}

printk("Message sent!\n");

// Optional: receive response
char recv_buf[128];
struct nrf_sockaddr_in from_addr;
nrf_socklen_t from_len = sizeof(from_addr);
err = nrf_recvfrom(sock, recv_buf, sizeof(recv_buf) - 1, 0, (struct nrf_sockaddr *)&from_addr, &from_len);
if (err > 0) {
  recv_buf[err] = '\0';
  printk("Received: %s\n", recv_buf);
} else {
  printk("No data received or recv error: %d\n", err);
}

nrf_close(sock);

DNS

prj.conf

CONFIG_DNS_RESOLVER=y

# If not using default DNS (I.e. not the network provided one.).
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="8.8.8.8"

dns.c

#include <modem/at_parser.h>
#include <nrf_modem_at.h>
#include <zephyr/net/dns_resolve.h>

#define DNS_TIMEOUT (5 * MSEC_PER_SEC)
#define DNS_BACKUP "8.8.8.8"

struct dns_resolved_data {
  bool resolved;
  uint8_t *address;
};

static void dns_result_cb(enum dns_resolve_status status, struct dns_addrinfo *info, void *user_data) {
  struct dns_resolved_data *data = (struct dns_resolved_data *)user_data;

  // Handle different DNS resolve statuses
  switch (status) {
  case DNS_EAI_CANCELED:
    printk("DNS query was canceled\n");
    data->resolved = true;
    return;
  case DNS_EAI_FAIL:
    printk("DNS resolve failed\n");
    data->resolved = true;
    return;
  case DNS_EAI_NODATA:
    printk("Cannot resolve address\n");
    data->resolved = true;
    return;
  case DNS_EAI_ALLDONE:
    printk("DNS resolving finished\n");
    data->resolved = true;
    return;
  case DNS_EAI_INPROGRESS:
    break;
  default:
    printk("DNS resolving error (%d)\n", status);
    data->resolved = true;
    return;
  }

  if (!info) {
    return;
  }

  // If the address family is IPv4, copy the binary IP address
  if (info->ai_family == AF_INET) {
    // Copy the binary address directly into the user_data buffer
    memcpy(data->address, &net_sin(&info->ai_addr)->sin_addr, NET_IPV4_ADDR_SIZE);
  } else {
    printk("Invalid IP address family %d\n", info->ai_family);
  }

  data->resolved = true;
}

int dns_query_ipv4(const char *hostname, uint8_t *ip_addr) {
  static uint16_t dns_id;

  struct dns_resolved_data user_data = {.resolved = false, .address = ip_addr};

  memset(user_data.address, 0, NET_IPV4_ADDR_SIZE);

  // Initialize DNS query
  int ret = dns_get_addr_info(hostname, DNS_QUERY_TYPE_A, &dns_id, dns_result_cb, &user_data, DNS_TIMEOUT);

  if (ret < 0) {
    return -1;
  }

  // Block until DNS resolution is done
  while (!user_data.resolved) {
    k_msleep(100);
  }

  uint8_t zeroed_addr[NET_IPV4_ADDR_SIZE];
  memset(zeroed_addr, 0, NET_IPV4_ADDR_SIZE);

  return (memcmp(user_data.address, zeroed_addr, NET_IPV4_ADDR_SIZE) != 0) ? 0 : -1;
}

int extract_dns_addresses(char *dns_primary, char *dns_secondary) {
  struct at_parser parser;
  char resp[256];
  size_t len = NET_IPV4_ADDR_LEN;

  memset(dns_primary, 0, NET_IPV4_ADDR_LEN);
  memset(dns_secondary, 0, NET_IPV4_ADDR_LEN);

  if (nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGDCONT?")) {
    return -1;
  }

  if (at_parser_init(&parser, resp)) {
    return -1;
  }

  uint16_t num;

  if (at_parser_num_get(&parser, 1, &num)) {
    return -1;
  }

  if (nrf_modem_at_cmd(resp, sizeof(resp), "AT+CGCONTRDP=%d", num)) {
    return -1;
  }

  if (at_parser_init(&parser, resp)) {
    return -1;
  }

  if (at_parser_string_get(&parser, 6, dns_primary, &len)) {
    return -1;
  }

  len = NET_IPV4_ADDR_LEN;

  if (at_parser_string_get(&parser, 7, dns_secondary, &len)) {
    return 1;
  }

  return 2;
}

int init_dns() {
  const char *dns_addrs[4];
  char dns_primary[NET_IPV4_ADDR_LEN];
  char dns_secondary[NET_IPV4_ADDR_LEN];

  int index = 0;
  int ret = extract_dns_addresses(dns_primary, dns_secondary);

  if (ret >= 1) {
    dns_addrs[index] = dns_primary;
    index++;
  }

  if (ret >= 2) {
    dns_addrs[index] = dns_secondary;
    index++;
  }

  dns_addrs[index] = DNS_BACKUP;
  index++;

  dns_addrs[index] = NULL;

  return dns_resolve_init(dns_resolve_get_default(), dns_addrs, NULL);
}

Usage

#include "dns.c"

// ...After connected to network...

// Use network provided dns.
int ret = init_dns();

// Use prj.conf network instead:
// int ret = dns_resolve_init_default(dns_resolve_get_default()); // #include <zephyr/net/dns_resolve.h>

if (ret < 0) {
  printk("Failed to init dns: %d\n", err);
}

uint8_t ip_addr[NET_IPV4_ADDR_SIZE];
int ret = dns_query_ipv4(SERVER_DOMAIN, ip_addr, NET_IPV4_ADDR_SIZE);
if (ret == 0) {
  printk("Resolved IP: %d.%d.%d.%d\n", ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
} else {
  printk("DNS resolution failed\n");
}

GPIO

GPIO although can be done directly is setup through references in a device tree, you can add your own references to circuits and reference them with with nice references that make sense in the code.

// CONFIG_GPIO=y
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

#define SLEEP_TIME_MS 100
#define LED0_NODE DT_ALIAS(led0)

int err;
bool led_state = true;

if (!gpio_is_ready_dt(&led)) {
  printf("error: GPIO is not ready");
  return 0;
}

err = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);

if (err < 0) {
  printf("could not configure pin");
  return 0;
}

while (1) {
  reterr= gpio_pin_toggle_dt(&led);

  if (err < 0) {
    return 0;
  }

  led_state = !led_state;
  printf("LED state: %s\n", led_state ? "ON" : "OFF");
  k_msleep(SLEEP_TIME_MS);
}