0% found this document useful (0 votes)
12 views

Week3 Arduino

Uploaded by

ramkumar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

Week3 Arduino

Uploaded by

ramkumar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 92

Programming Arduino

and Nano 33 BLE


(nRF52840)

IE 5995: IoT and Edge AI Programming


Yanchao Liu
Industrial & Systems Engineering
College of Engineering
Wayne State University
What is Arduino
• Physical Arduino boards
• Uno
• Nano
• Leonardo
• Mega
• Pro Mini
• etc.
• Arduino IDE
• Installed on a PC (Windows/Mac/Linux)
Arduino Uno • To develop, install and debug programs on Arduino boards
• Communicates with Arduino board over USB
• Third-party Arduino compatible boards
Arduino’s purpose is to control things by • STM32 Nucleo / Discovery / Feather, etc.
interfacing with sensors and actuators. • Adafruit
• No keyboard, mouse and screen* • SparkFun
• No operating system, limited memory • etc.
• A single program enjoys 100% of CPU time

* Can be attached via “shields”

Winter 2022 IE5995: IoT & Edge AI Programming 2


A generic AVR microcontroller block diagram

CPU
Internal Memory:
• Flash (stores program code)
• SRAM (holds data and variable during execution)
• EEPROM (holds persistent data generated by program)
Peripherals:
• A/D converter (ADC)
• Timers
• UART
• SPI
• DMA
• GPIO
• TWI
• Comparator
• RTC
• WDT
• RNG
• etc.

Winter 2022 IE5995: IoT & Edge AI Programming 3


Interfacing with Arduino
• Temperature sensor
• Pressure sensor
Sensors
• Switches
• Variable resistor Reed Switch, Tilt sensor, Flame sensor, Hall
effect sensor, Shock (impact) sensor, IR
• Range finder Proximity sensor, IR line following sensor,
• PIR (person-in-room) sensor Conductive contact sensor, Microphone
• Relay sensor, LED heartbeat sensor, Rotary
• Motor control encoder, Ultrasonic distance sensor, Water
sensor, Touch sensor, Soil moisture sensor,
• LED
Barometric sensor, Light sensor, PIR sensor,
• 16x2 display Laser transmitter/receiver, RTC module
• Graphic display
• Bluetooth shield
• WiFi shield
• Ethernet shield
Arduino Libraries:
https://www.arduinolibraries.info/libraries
Winter 2022 IE5995: IoT & Edge AI Programming 4
An Arduino-based Intrusion Detector
Arduino Sketch:
https://github.com/ArdNut/Miscellaneous/blob/master/Simpl
eAlarm/SimpleAlarm.ino

Magnetic Reed Switch

Connecting
switches in series

Winter 2022 IE5995: IoT & Edge AI Programming 5


An Arduino-based Thermostat Design

Winter 2022 IE5995: IoT & Edge AI Programming 6


Breadboard

+ Power rails
-

Winter 2022 IE5995: IoT & Edge AI Programming 7


Build a circuit
or 3.3V

Winter 2022 IE5995: IoT & Edge AI Programming 8


Uno vs Nano 33 BLE Sense
Uno R3 Nano 33 BLE Sense Arduino Nano 33 BLE only supports 3.3V
Chip ATmega328P nRF52840 I/Os and is NOT 5V tolerant so please
Clock 16 MHz 64 MHz make sure you are not directly connecting
Flash 32 KB 1 MB 5V signals to this board or it will be
SRAM 2 KB 256 KB damaged.
EEPROM 1 KB none
Input Voltage 6 - 20 V 4.5 - 21 V
I/O Voltage 5V 3.3 V
Pinout 14 digital, 6 PWM, 6 AnalogIn 14 digital (PWM), 8 AnalogIn DC current per IO pin is 15 mA (max)
Interfaces USB, SPI, I2C, UART USB, SPI, I2C, I2S, UART
Connectivity via shields BLE 5.0
Weight 25 g 5g

Features of Nano 33 Sense


• 8 Analog Input Pins can provide 12-bit ADC at about 30 kHz
• Integrated sensors (IMU, Mic, Light, Pressure, Temperature, Humidity)
• All digital pins can trigger interrupts

Winter 2022 IE5995: IoT & Edge AI Programming 9


Winter 2022 IE5995: IoT & Edge AI Programming 10
Winter 2022 IE5995: IoT & Edge AI Programming 11
Arduino Nano comparisons
Arduino Nano 33 BLE
Property Arduino Nano Arduino Nano Every Arduino Nano 33 IoT Arduino Nano 33 BLE
Sense
SAMD21 Cortex®-
nRF52840 (ARM nRF52840 (ARM
Microcontroller ATmega328 ATMega4809 M0+ 32bit low power
Cortex M4) Cortex M4)
ARM MCU
Operating voltage 5V 5V 3.3 V 3.3 V 3.3 V
Input voltage (VIN) 6-20 V 7-21 V 5-21 V 5-21 V 5-21 V
Clock speed 16 Mhz 20 MHz 48 MHz 64 MHz 64 MHz
Flash 32 KB 48 KB 256 KB 1 MB 1 MB
RAM 2 KB 6 KB 32 KB 256 KB 256 KB
Current per pin 40 mA 40 mA 7 mA 15 mA 15 mA
PWM pins 6 5 11 All All
LSM6DS3 LSM9DS1 LSM9DS1
IMU No No
(6-axis) (9-axis) (9-axis)
Other sensors No No No No Several
WiFi No No Yes No No
Bluetooth No No Yes Yes Yes
USB type Mini Micro Micro Micro Micro
$20 $12.50 $20 $23 $33
Price*
Amazon Amazon Amazon Amazon Amazon
Winter 2022 IE5995: IoT & Edge AI Programming 12
Pinout

Pins A4 and A5 have an internal pull


up and default to be used
as an I2C Bus so usage as analog
inputs is not recommended.

Ground the RESET


pin to reset

https://forum.arduino.cc/t/fully-understand-
pins_arduino-h-for-the-nano-33-ble/628994
Winter 2022 IE5995: IoT & Edge AI Programming 13
Nano 33 BLE Power Tree

Winter 2022 IE5995: IoT & Edge AI Programming 14


nRF52840
• Arduino Nano BLE (and BLE Sense) is based on the nRF52840 microprocessor made by Nordic Semiconductor.
• nRF52840 has the ARM® Cortex-M4 processor with single-precision floating-point unit (FPU).
• The nRF52840 contains 1 MB of flash and 256 kB of RAM that can be used for code and data storage.
• The flash can be read an unlimited number of times by the CPU, but it has restrictions on the number of times it can
be written and erased (minimum 10,000 times) and also on how it can be written.
• The flash is divided into 256 pages of 4 kB each that can be accessed by the CPU via both the ICODE and DCODE buses.

Winter 2022 IE5995: IoT & Edge AI Programming 15


Get started with the Arduino IDE
• Install Arduino IDE
• Open the IDE
• Connect the Arduino
Nano 33 BLE board to
PC using USB
• Select the board (port
number may be
different on different
computers)
• Click “Yes” to install
the suggested add-on

Winter 2022 IE5995: IoT & Edge AI Programming 16


Arduino Sketch Structure
void setup() {
// put your setup code here, to run once:

}
void loop() {
// put your main code here, to run repeatedly:

Winter 2022 IE5995: IoT & Edge AI Programming 17


Programming the Arduino (exercise)
• Connect Nano 33 to PC using USB
• Open Arduino IDE
• Select Board
• Select Port
• Try and modify the blink sketch in the example
• pinMode()
• digitalWrite()
• digitalRead()
• delay()
• Serial.begin(), .println(), .print(), .available()
• random()
• analogRead()
• analogWrite() (actually, PWM)
• Use Serial Monitor
Winter 2022 IE5995: IoT & Edge AI Programming 18
Try these examples Example 2 (use pin 4 to drive a LED)

Example 1

0x7FFFFFFF
= 0b01111111111111111111111111111111
= 2,147,483,647
Note: standard C does not have syntax for binary literal, the “0b” prefix is a non-standard compiler extension.
Maximum value of long in 32-bit system. In Arduino, the prefix “B” can also be used to denote binary literals (see Binary.h)
Winter 2022 IE5995: IoT & Edge AI Programming 19
Read from Serial
void setup(){ void setup(){
Serial.begin(9600); Serial.begin(9600);
} }
void loop(){ void loop(){
if(Serial.available() > 0){ if(Serial.available() > 0){
byte val = Serial.read(); int val = Serial.parseInt();
Serial.print("Received: "); Serial.print("Received: ");
Serial.println(val); Serial.println(val);
} }
} }

Exercise: Create a program that generates ten, one at a time, two-number addition questions, e.g., 4+7=? After
printing out each question, wait for the user (e.g., a kindergartener) to input an answer, then present the next
question. After 10 questions are answered, print out the number of correct answers and the total time taken to
complete the 10-question test.

Arduino functions: https://www.arduino.cc/reference/en/

Winter 2022 IE5995: IoT & Edge AI Programming 20


Length of data type may be different on different CPUs
Arduino Data Types (on Arduino Uno) On Arduino Nano 33 BLE (Sense)
• 1 byte: byte is 1 byte(s)
• boolean (true or false, 0 or 1) short is 2 byte(s)
• char (-128 to +127) int is 4 byte(s)
• byte (0 to 255) unsigned int is 4 byte(s)
• 2 bytes: long is 4 byte(s)
• int (-32768 to +32768) unsigned long is 4 byte(s)
• unsigned int (0 to 65536) long long is 8 byte(s)
• 4 bytes: unsigned long long is 8 byte(s)
• long (around -2.1 billion to +2.1 billion, integer) float is 4 byte(s)
• unsigned long (0 to ~4.2 billion, integer) double is 8 byte(s)
• float (-3.4E+38 to 3.4E+38) uint8_t is 1 byte(s)
• double (the same as float) uint16_t is 2 byte(s)
uint32_t is 4 byte(s)

Winter 2022 IE5995: IoT & Edge AI Programming 21


Arduino Pin Names
// LEDs
// ----
#define PIN_LED (13u)
#define LED_BUILTIN PIN_LED • Pin name aliases are defined
in the pins_arduino.h
#define LEDR (22u)
#define LEDG (23u)
#define LEDB (24u)
#define LED_PWR (25u)
• As a convention,
// Analog pins
// -----------
• Digital pins can be referred by
#define PIN_A0 (14u) the number (e.g., 3) or
#define PIN_A1 (15u)
#define PIN_A2 (16u)
D+number (e.g., D3)
#define PIN_A3 (17u) • Analog pins must be referred
#define PIN_A4 (18u)
#define PIN_A5 (19u) by A+number, e.g., A3
#define PIN_A6 (20u)
#define PIN_A7 (21u)
static const uint8_t A0 = PIN_A0; // set pin D3 to ON
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2; digitalWrite(3, HIGH);
static const uint8_t A3 = PIN_A3; digitalWrite(D3, HIGH); // both works
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
static const uint8_t A6 = PIN_A6; // read voltage from pin A3, and convert to a
static const uint8_t A7 = PIN_A7;
#define ADC_RESOLUTION 12 number between 0 and 4095 (12-bit ADC)
analogRead(A3);
C:\Users\gn0061\AppData\Local\Arduino15\packages\arduino\hardware\mbed_nano\2.6.1\va
riants\ARDUINO_NANO33BLE\pins_arduino.h
Winter 2022 IE5995: IoT & Edge AI Programming 22
Try the examples

To test, connect a jumper cable to A2, and try


connecting the other end to 3.3 V or GND, and • How much time (in microseconds) does each
observe the output in the Serial Monitor. invocation of analogRead() take?
• Take 30 samples (of duration) and construct a
Also, try changing the expression val*3.3/1023 confidence interval for the mean time of the
to val/1023*3.3, see what happens. Why? analogRead() invocation.

Winter 2022 IE5995: IoT & Edge AI Programming 23


Integer vs Floating-point arithmetic
#define BUFLEN 8192 #define BUFLEN 8192
int val; int val;
float buf[BUFLEN]; unsigned long buf[BUFLEN];
unsigned long start_time, duration; unsigned long start_time, duration;
void setup() { void setup() {
Serial.begin(9600); Serial.begin(9600);
} }
void loop() { void loop() {
start_time = micros(); start_time = micros();
for(int i=0; i<BUFLEN; i++){ for(int i=0; i<BUFLEN; i++){
val = analogRead(A3); val = analogRead(A3);
buf[i] = val*3.3/1023; // volt buf[i] = val*3300000/1023; // micro-volt
} }
duration = micros() - start_time; duration = micros() - start_time;
Serial.println(duration); Serial.println(duration);
delay(500); delay(500);
} }

Winter 2022 IE5995: IoT & Edge AI Programming 24


Bitwise operation
Decimal Binary Hexadecimal
A = 60 111100 3C
B = 15 1111 F
~A = 195 11000011 C3
A<<1 = 120 1111000 78
A<<2 = 240 11110000 F0
A<<3 (no type cast) = 480 111100000 1E0
A<<3 (cast to uint8_t) = 224 11100000 E0
A>>1 = 30 11110 1E
A>>2 = 15 1111 F
A>>3 = 7 111 7
A>>4 = 3 11 3
A & B = 12 1100 C
A | B = 63 111111 3F
A ^ B = 51 110011 33
Set the 7th bit of A to 1 by A | (1<<6) Result: 1111100
Set the 3rd bit of A to 0 by A & ~((uint8_t)1<<2) Result: 111000

Winter 2022 IE5995: IoT & Edge AI Programming 25


Endianness and byte addressing
• Beware of the endianness when you need to manipulate or
transmit data by bytes, or when memory contents are
transmitted between computers with different endianness.
• Casting an uint32_t integer to uint8_t takes the low 8-bit of
the integer

#include <stdio.h>
#include <stdint.h>
int main()
{
uint32_t a = 0xF86A81F3;
uint8_t * p = (uint8_t*)&a;
printf("%x %u\n", a, a);
printf("%x %x %x %x\n", *p, *(p+1), *(p+2), *(p+3));
printf("%x\n", (uint8_t)a);
a >>= 8;
printf("%x %u\n", a, a);
printf("%x %x %x %x\n", *p, *(p+1), *(p+2), *(p+3));
}

Winter 2022 IE5995: IoT & Edge AI Programming 26


Pulse Width Modulation (PWM)

• A technique for getting analog results with


digital means
• Digital control is used to create a square wave,
a signal switched between ON and OFF.
• The simulated analog voltage is the portion of
the time the signal spends ON versus the time
that the signal spends OFF.

https://docs.arduino.cc/learn/microcontrollers/analog-output

Winter 2022 IE5995: IoT & Edge AI Programming 27


PWM Frequency and Pins

Winter 2022 IE5995: IoT & Edge AI Programming 28


Try the example (use PWM to fade LED)
• Examples -> 01. Basics -> Fade

D9

- 330 ohm
+

GND

Winter 2022 IE5995: IoT & Edge AI Programming 29


Two-wire serial communication toy example
• Serial interface is common in device
communications
• Send / Receive data one bit at time
• Need a clock signal to synchronize
timing between sending and receiving
ends
• Sender does this to send a byte:
1. Set the data pin according to the first
bit (MSB) of the byte to be sent
2. Wait for the signal to be stable
3. Pulse the clock pin
Timeline
4. Repeat from step 1 for the next bit until
all 8 bits are sent

Winter 2022 IE5995: IoT & Edge AI Programming 30


Two-wire serial send (Tx) example
#define dataPin 2
#define clockPin 3
byte data = 0;
void sendByte(byte b){ Sending number 42, 8-bit binary representation: 00101010
for(int i = 0; i < 8; i++){
digitalWrite(dataPin, bitRead(b, 7-i));
delay(1);
digitalWrite(clockPin, HIGH);
delay(1);
digitalWrite(clockPin, LOW);
delay(1);
}
}
void setup() {
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
}
void loop() {
//data = data > 20? 0 : data+1;
data = 42;
sendByte(data);
delay(1000);
}

Winter 2022 IE5995: IoT & Edge AI Programming 31


Two-wire serial receive (Rx) example
• Upload the Tx code to the sender board,
#define dataPin 2
#define clockPin 3
upload Rx code to the receiver board
void setup() { • Make sure two boards have the same I/O
pinMode(dataPin, INPUT_PULLUP);
pinMode(clockPin, INPUT_PULLUP);
voltage
Serial.begin(9600); • e.g., don’t connect Uno (5v) with Nano (3.3v)
}
void loop() { • Connect the sender and receiver
byte x = 0;
for(int i = 0; i < 8; i++){ • sender’s dataPin -> receiver’s dataPin
// wait for clock to go HIGH • sender’s clockPin -> receiver’s clockPin
while(digitalRead(clockPin) == LOW);
x = x << 1; // shift all bits left one place
x += digitalRead(dataPin); // add the new bit
• Open the Serial Monitor on the receiver
// wait for the clock to go LOW end to see data received.
while(digitalRead(clockPin) == HIGH);
} • CAUTION
Serial.println(x);
} • Don’t upload the sender code to both boards
and connect them. Can damage boards
when two digital output pins connect

Winter 2022 IE5995: IoT & Edge AI Programming 32


Exercise
• Design and implement a one-wire communication protocol, i.e., the two
devices are connected by only one line
• Write both the sender and receiver programs and demonstrate the how it
works
• Encrypt / decrypt the messages being sent / received, e.g., using the AES
algorithm

Winter 2022 IE5995: IoT & Edge AI Programming 33


I2C Interface
• I2C is a serial communication protocol, commonly used for connecting
MCU and sensors.
• It is a “bus”, meaning multiple devices can use the same two wires (one
for timing, one for data). In other words, the MCU can use only two wires
to communicate with multiple (up to 127) devices (i.e., sensors).
• The device that provides the clock signal is the “master”, all others are
“slaves”. Typically, the MCU is the master, sensor units are slaves.

• Each slave connected on the bus should have a unique address,


which is a number between 1 and 127. The value 0 is the broadcast
address.
• The master identifies the slave by the slave’s address.
• For a sensor unit, its I2C address is typically hardcoded, and is
explained in the datasheet.

Winter 2022 IE5995: IoT & Edge AI Programming 34


I2C Hardware
• I2C uses two wires (thus, also called two wire interface, or TWI): Serial
Clock (SCL) line and Serial Data (SDA) line.
• When no data is transmitted, SCL and SDA lines are in a tri-state or free
state, i.e., neither HIGH nor LOW, a floating value.
• When there is data to be transmitted, the sender (master or slave) takes
the SDA line out of tri-state and sends data as logic highs and lows in time
with the clock signal.
• When transmission is complete, the clock signal can stop, and the SDA line
is returned to tri-state.
• Usually, the slave only sends data upon the master’s request, so the clock
signal is guaranteed available.

Winter 2022 IE5995: IoT & Edge AI Programming 35


I2C Library
• On Arduino, the Wire library provides functions for I2C communication.
Functions
begin()
end()
requestFrom()
beginTransmission()
endTransmission()
write()
available()
read()
setClock()
onReceive()
onRequest()
setWireTimeout()
clearWireTimeoutFlag()
getWireTimeoutFlag()

Winter 2022 IE5995: IoT & Edge AI Programming 36


I2C experiments
• Connect two Arduino Nano 33 BLE boards via I2C. One acts as master
and the other acts as slave.
// Arduino Nano 33 BLE acts as I2C slave
#include <Wire.h>
// Arduino Nano 33 BLE acts as I2C master
int my_addr = 4;
#include <Wire.h>
volatile int led_state = LOW;
void setup() {
void setup() {
Wire.begin(); // Initialize I2C as master
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
digitalWrite(LED_BUILTIN, led_state);
}
Wire.begin(my_addr); // Initialize I2C as master
Wire.onReceive(recv_handler);
void loop() {
Wire.onRequest(request_handler);
// start talk to device at address 4
Serial.begin(9600);
Wire.beginTransmission(4);
}
// write a byte (value 123) to the SDA line
void recv_handler(int nbytes){
Wire.write(123);
led_state = !led_state;
delay(10);
}
// request 1 byte from device at address 4
void request_handler(){
Wire.requestFrom(4,1);
Wire.write(my_addr);
// read the requested byte from the SDA line
}
int val = Wire.read();
Wire.endTransmission();
void loop() {
Serial.println(val);
digitalWrite(LED_BUILTIN, led_state);
delay(1000);
delay(50);
}
}
Winter 2022 IE5995: IoT & Edge AI Programming 37
UART Protocol
• UART (Universal asynchronous receiver-transmitter) is
• A device-to-device communication protocol.
• A block of circuitry responsible for implementing serial communication (hardware UART)
• The interface can be bit-banged, i.e., directly controlled by the processor,
called Software UART
• Processor-intensive, not preferred but does the job when hardware UART is not available

• Baud rate must be set the same on both


RX buffer RX buffer ends of the communication channel.
• Most typical Baud Rates: 9600, 19200,
TX buffer TX buffer 38400, 57600, 115200, 230400, 460800,
921600, 1000000, 1500000

Device 1 Device 2

https://www.analog.com/en/analog-dialogue/articles/uart-a-hardware-communication-protocol.html
https://learn.sparkfun.com/tutorials/serial-communication/rules-of-serial
Winter 2022 IE5995: IoT & Edge AI Programming 38
Serial Bridge
void setup() {
• Serial is the USB Serial Port through which the
Serial.begin(115200);
Arduino board connects to the PC
Serial1.begin(115200);
• Serial1 works through the TX / RX pins on the board
}
• Connect two boards and message each other
• Board1’s TX connects to Board2’s RX
void loop() {
• Board1’s RX connects to Board2’s TX
while(Serial.available()){
• Board1’s GND connects to Board2’s GND
Serial1.write(Serial.read());
• Caution: Do not connect a 5 V board with a 3.3 V
}
board
while(Serial1.available()){
• e.g., Do not connect Arduino Uno / Mega (5 V)
Serial.write(Serial1.read());
with Arduino Nano 33 BLE or any Adafruit
}
board (3.3 V)
}

Experiment: use the serial bridge to read data from a GPS


module and dump it to the computer’s serial monitor

Winter 2022 IE5995: IoT & Edge AI Programming 39


Wireless Serial
• In class experiments
• Two boards talk to each other wirelessly through the HC-12 transceivers
• Connect Serial1 (TX / RX pins on board) to HC-12
• Set Serial1’s baud rate to 6900, the default setting on HC-12
• The Serial Bridge code (with modified baud rate on Serial1) should work
• Now two (or multiple) MCUs can talk wirelessly, think of some project ideas
leveraging this capability.

Winter 2022 IE5995: IoT & Edge AI Programming 40


BLESerial on Nano 33 BLE
#include <HardwareBLESerial.h> • Install the HardwareBLESerial library
#define NCHAR 64 • Upload the code to Nano 33 BLE
HardwareBLESerial &bleSerial = HardwareBLESerial::getInstance(); • Modify “IE5995” to something else before
char line[NCHAR];
void setup() { uploading, to prevent conflicts
Serial.begin(9600); • Download the phone app “Bluefruit Connect”
while(!bleSerial.beginAndSetupBLE("IE5995")); • Via App Store or Google Play
}
void loop() {
• Connect to the device via Bluefruit Connect
bleSerial.poll(); • Open UART in Bluefruit Connect
while (bleSerial.availableLines() > 0) { • Open Serial Monitor of the PC that’s connected to
bleSerial.readLine(line, NCHAR); Nano 33 BLE
Serial.println(line);
} • You can exchange text messages between the
int i=0; Serial Monitor on PC and the UART console in
while(Serial.available() > 0 && i < NCHAR){ Bluefruit Connect on the cellphone
line[i++] = Serial.read();
}
if(i){ Note: if you use an Adafruit board (feather, playground,
Serial.println(line);
bleSerial.println(line); etc.) having the nRF52840 MCU, you should use the
} BLESerial library instead of the HardwareBLESerial library.
delay(50); The usage is slightly different but similar.
}

Winter 2022 IE5995: IoT & Edge AI Programming 41


What’s happening
Program on Nano 33 BLE
Wireless
bleSerial.write() connection
bleSerial.print()
BLE Serial
Wired
connection TX buffer
Serial Monitor Nano 33 Bluefruit Display on
on PC BLE RX buffer Connect App screen

TX buffer RX buffer
bleSerial.available()
RX buffer bleSerial.read() TX buffer Type & send
Serial via keypad
Type some text, TX buffer Serial.write()
hit Ctrl + Enter Display on screen Serial.print()
RX buffer

Serial.available()
Serial.read()

Winter 2022 IE5995: IoT & Edge AI Programming 42


Exercise (homework)
1. Make BLE Serial work between your Nano 33 BLE board (or other nRF52 board) and your
smart phone’s Bluefruit Connect App (or similar app)
2. Turn on/off an LED connected to the board by the phone app
3. Bring a project idea leveraging what’s learned so far

Winter 2022 IE5995: IoT & Edge AI Programming 43


Interrupts
• Interrupts allow microcontrollers to respond to events without having to
repeatedly poll to see if the event has occurred.
• Interrupts can be triggered by a Pin or by a Timer
Polling method: Interrupt method:

void loop{ void setup{


if (digitalRead(inputPin) == LOW){ attachInterrupt(digitalPinToInterrupt(interruptPin), myISR, FALLING);
// do something }
} void loop(){}
} void myISR(){
// do something to respond to the event
}

Exercises:
Toggle the LED (connected to D13) whenever pin D3 goes from HIGH to LOW
• Try both the Polling and the Interrupt methods

Winter 2022 IE5995: IoT & Edge AI Programming 44


Interrupt Handling Process

Winter 2022 IE5995: IoT & Edge AI Programming 45


Interrupt Example
const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
digitalWrite(ledPin, state);
}

void blink() {
state = !state;
}

Winter 2022 IE5995: IoT & Edge AI Programming 46


Pins available for interrupts

Winter 2022 IE5995: IoT & Edge AI Programming 47


Interrupt Service Routines (ISR)
• ISR is also called callback function
• Cannot take parameters and cannot return a value
• Use volatile global variables to pass data between ISR and the rest of the program
• Keep ISR short and fast
• Don’t perform heavy-duty computations in ISR
• Interrupts are automatically turned off while inside an ISR*
• delay() and millis() will not work in an ISR; delayMicroseconds() will work
• Serial.print() will not work because Serial communication uses interrupts
• While ISR is running, nothing happens in the main loop until the ISR is finished
• In program code, we can explicitly turn interrupts on/off using
• interrupts(); and noInterrupts();
• Useful when we do not wish an area of code to be interrupted
• e.g., when using delayMicroseconds() to generate pulses with accurate timing

This is just for Arduino. It is configured this way to keep things simple. Many processors (e.g., STM32) allow interrupt to occur within an ISR.
Winter 2022 IE5995: IoT & Edge AI Programming 48
Timer Interrupt
• The interrupt is triggered by a hardware timer event
• Whenever a given period of time has elapsed, an interrupt is triggered and the
associated ISR is run, regardless of what the CPU is doing at the moment
• Why use timer interrupt?
• To have the mission-critical task (which is implemented in the ISR) not blocked by ill-
behaving functions / tasks in the main loop

Read water level

Control sump pump


based on water level What if the WIFI module encounter a problem
connecting to the server?
Send the water level
value to server via WIFI

Winter 2022 IE5995: IoT & Edge AI Programming 49


Use Timer Interrupts
• Use the attachInterrupt(function, period) function from the TimerOne library
• This library only works for the AVR architecture (i.e., Atmega MCUs)
• This library is not available for nRF52840
• User needs to write lower-level code to realize Timer-triggered Interrupts

#include <TimerOne.h>
Exercise: finish this code to
void setup(){
• blink the LED at 2 Hz
Timer1.initialize(500); // 500 microseconds per trigger
• generate a 1000 Hz square wave
Timer1.attachInterrupt(callback);
• generate a 1000 Hz PWM signal at
}
10% duty cycle to drive the LED
void callback(){
// Implement the ISR
}

Winter 2022 IE5995: IoT & Edge AI Programming 50


Other useful methods of the Timer1 library
void initialize(long microseconds=1000000);
void start();
void stop();
void restart();
unsigned long read();
void setPeriod(long microseconds);
void pwm(char pin, int duty, long microseconds=-1);
void setPwmDuty(char pin, int duty);
void disablePwm(char pin);
void attachInterrupt(void (*isr)(), long microseconds=-1);
void detachInterrupt();

See detailed documentation here: https://playground.arduino.cc/Code/Timer1/

Winter 2022 IE5995: IoT & Edge AI Programming 51


Timer Interrupt on Nano 33 BLE
• Install the library NRF52_MBED_TimerInterrupt
• Basic usage:

#include <NRF52_MBED_TimerInterrupt.h>
#include <NRF52_MBED_ISR_Timer.h>

NRF52_MBED_Timer ITimer(NRF_TIMER_3);
volatile byte led_val;
void callback(){
led_val = !led_val;
}
void setup(){
ITimer.attachInterruptInterval(500000, callback); Exercise: finish this code to
}
void loop(){ • blink the LED at 2 Hz
digitalWrite(13, led_val); • generate a 1000 Hz square wave
} • generate a 1000 Hz PWM signal at
10% duty cycle to drive the LED

https://github.com/khoih-prog/NRF52_MBED_TimerInterrupt
Winter 2022 IE5995: IoT & Edge AI Programming 52
volatile int flag = 0;
extern "C" {
void TIMER4_IRQHandler_v(){ nRF52840 Timer Interrupt Example
if (NRF_TIMER4->EVENTS_COMPARE[0] == 1){ • Uses nRF MDK
flag++;
NRF_TIMER4->EVENTS_COMPARE[0] = 0;
}
} C:\Users\gn0061\AppData\Local\Arduino15\packages\a
} rduino\hardware\mbed_nano\2.6.1\cores\arduino\mbe
d\targets\TARGET_NORDIC\TARGET_NRF5x\TARGET_SD
void setup() { K_15_0\modules\nrfx\mdk\nrf52840.h
Serial.begin(115200);
Serial.println("Configuring timer"); C:\Users\gn0061\AppData\Local\Arduino15\packages\a
// Refer to definitions in nrf52840.h and nrf52840_bitfield.h rduino\hardware\mbed_nano\2.6.1\cores\arduino\mbe
// 16-bit timer, max count is 65535 d\targets\TARGET_NORDIC\TARGET_NRF5x\TARGET_SD
NRF_TIMER4->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos;
K_15_0\modules\nrfx\mdk\ nrf52840_bitfield.h
// Timer frequency = 16 MHz/2^PRESCALER (pp 460 of nRF52840 Product Specification v1.1)
// So this sets the timer frequency to 16 MHz/2^4 = 1 MHz
NRF_TIMER4->PRESCALER = 4 << TIMER_PRESCALER_PRESCALER_Pos; C:\Users\gn0061\AppData\Local\Arduino15\packages\a
// Sets the capture compare value, cannot exceed 65536 rduino\hardware\mbed_nano\2.6.1\cores\arduino\mbe
// 20000 counts will take 20 ms d\cmsis\CMSIS_5\CMSIS\TARGET_CORTEX_M\Include\c
NRF_TIMER4->CC[0] = 20000; ore_cm4.h
// Enable interrupt for event Compare[0], see pp 464
NRF_TIMER4->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos; https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v
// Sets the shortcuts between event Compare[0] and task CLEAR (i.e., clear time) 1.1.pdf
// That is reset the timer count to 0 when the event occurs
NRF_TIMER4->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos;
// Function is defined in core_cm4.h
// TIMER4_IRQn is a nrf52840 Specific Interrupt Number, defined in nrf52840.h
// Enables a device specific interrupt in the NVIC interrupt controller.
NVIC_EnableIRQ(TIMER4_IRQn);
// Start the timer, pp 461
NRF_TIMER4->TASKS_START = 1;
This example: Directly access the
} NRF API and implement your
void loop() { own timer interrupt.
Serial.println(flag);
delay(500);
Winter 2022 } IE5995: IoT & Edge AI Programming 53
Arduino code optimization for speed
• Avoid using float
• Floating-point arithmetic is slow on hardware that does not support it
• Use long instead, which can retain more digits of precision, and faster
• Lookup rather than calculate
• Use const byte instead of int for small constants such as Pin names
• Move looping code into the setup function
• the loop() function checks for Serial communication which takes time
• Speeding up Digital IO*
• Directly manipulate the pin bit in the port register * Involves bypassing Arduino functions
• Speeding up Analog Inputs*
• Reducing the prescaler of the Timer that triggers ADC conversions

Winter 2022 IE5995: IoT & Edge AI Programming 54


Calculate vs Lookup

Winter 2022 IE5995: IoT & Edge AI Programming 55


Minimize power usage
• If the product is battery operated, reducing power usage is important
• Ways to reduce power consumption
• Put the microcontroller in sleep when it is not doing anything
• Turn off (disable) unused peripherals
• Use lowest clock frequency sufficient
• Use lower input voltage if allowable
• Provide power to sensor only when taking a reading

Winter 2022 IE5995: IoT & Edge AI Programming 56


CONNECTIONS make a ‘POWERFUL’ difference

Power-saving modification: supply


power to sensor (red line) from a
digital pin, which is set to HIGH only
when taking a sensor reading.
Winter 2022 IE5995: IoT & Edge AI Programming 57
Minimize RAM usage
• Reduce the amount of memory used by data and variables
• Choose data type based on need, especially for large arrays
• int is at least 2-byte, values range in [-32,768, 32767]
• If the value range of [0, 255] suffices, use byte, which is 1-byte.
• Store string constants in Flash Memory
• Serial.println(“Some message to print”); // Stored both in flash memory and in RAM
• Serial.println(F(“Some message to print”)); // Fetched from flash memory, no RAM use
• Use PROGMEM directive to store constant arrays in Flash
• #include <avr/pgmspace.h>
• Use const if the variable value do not change while the sketch is running
• “const int v = 5000;” uses less memory than “int v = 5000;” and runs faster when v is used in
computation
• Bypass the bootloader
• The Nano 33 BLE bootloader takes 35 KB of Flash Memory
• There is a way to program the chip without using a bootloader, thus saving RAM and
reducing start-up time
• Length of variable names does not help save memory

Winter 2022 IE5995: IoT & Edge AI Programming 58


Integrated sensors on Nano 33 BLE Sense

Winter 2022 IE5995: IoT & Edge AI Programming 59


LSM9DS1
• The LSM9DS1 inertial
measurement unit (IMU)
• accelerometer
• gyroscope
• magnetometer
• Useful for detecting orientation,
motion or vibrations
• Datasheet
• Library “Arduino_LSM9DS1”

https://github.com/kriswiner/LSM9DS1/blob/master/LSM9DS1_MS5611_BasicAHRS_t3.ino
https://axodyne.com/2020/06/arduino-nano-33-ble-ahrs/
https://github.com/kriswiner/MPU6050/wiki/Simple-and-Effective-Magnetometer-Calibration

Winter 2022 IE5995: IoT & Edge AI Programming 60


LSM9DS1 Acceleration in G: x, y, z

#include <Arduino_LSM9DS1.h>
#include <HardwareBLESerial.h>
HardwareBLESerial &bleSerial = HardwareBLESerial::getInstance();
void setup() {
Serial.begin(9600);
while(!bleSerial.beginAndSetupBLE("IE5995"));
while(!IMU.begin());
}
void loop() {
float x, y, z;
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(x, y, z);
bleSerial.poll(); Exercise:
Serial.print(x); bleSerial.print(x); • Write programs to read the gyroscope
Serial.print('\t'); bleSerial.print('\t'); and magnetometer values
Serial.print(y); bleSerial.print(y);
Serial.print('\t'); bleSerial.print('\t'); • Study the source code LSM9DS1.cpp
Serial.println(z); bleSerial.println(z); • Use Wire.h to communicate with
} sensor device via I2C
delay(10); // to avoid flooding bleSerial
}
• The same method can be used to
read other onboard sensors that
has I2C bus

Winter 2022 IE5995: IoT & Edge AI Programming 61


PDM MEMS Microphone
• MP34DT06JTR (Datasheet)
• Use the PDM library to sample
audio
• Implemented using a DoubleBuffer
• To enable simultaneous sampling
and processing

Buffer Buffer

PDM.setBufferSize(1024) // bytes

C:\Users\gn0061\AppData\Local\Arduino15\packages\arduino\hardware\mbed_nano\2.6.1\libraries\PDM

Winter 2022 IE5995: IoT & Edge AI Programming 62


Audio sampling code
#include <PDM.h>
#define SAMPBUFSIZE 256
• Exercise – modify the code to:
static const char channels = 1; • Verify that data processing (the
printout) is faster than sampling
static const int frequency = 16000;
unsigned int sampleBuffer[SAMPBUFSIZE]; // buffer for 16-bit samples
volatile int samplesRead; // Number of audio samples read (the speed at which the buffer is
filled), thus no data is lost
void setup(){
Serial.begin(115200);
while (!Serial);
PDM.setBufferSize(SAMPBUFSIZE*sizeof(unsigned int)); • Project ideas:
PDM.onReceive(onPDMdata);
PDM.setGain(0x01); • Save the audio data to SD card
PDM.begin(channels, frequency);
} • Stream the audio to PC via WIFI
void loop(){
if (samplesRead){
for (int i = 0; i < samplesRead; i++){
Serial.println(sampleBuffer[i]);
}
samplesRead = 0;
}
}
void onPDMdata(){
int bytesAvailable = PDM.available();
PDM.read(sampleBuffer, bytesAvailable);
samplesRead = bytesAvailable / sizeof(unsigned int);
}

Winter 2022 IE5995: IoT & Edge AI Programming 63


Interact with sensors on the I2C bus
• There are ready-made libraries to read sensor data from the integrated
sensors on the BLE Sense board.
• For the sake of learning, let us implement our own “device driver” to
obtain sensor data.
• We will start with the barometric pressure sensor LPS22HB.

Winter 2022 IE5995: IoT & Edge AI Programming 64


Write your own device driver for LPS22HB
• First, find out how LPS22HB is physically connected to the nrf52840 MCU on
the BLE Sense Rev2 board.
Note: SCL1 and SDA1 = Wire1 object in the Wire library;
whereas SCL and SDA = Wire.

We can see that it is connected to the I2C bus


through nrf52840’s SCL1 and SDA1 pins. Also, note
that the SDO/SA0 pin on LPS22HB is connected to
GND. This information will be useful for determining
the I2C address of the LPS22HB, according to its
dataset.
Winter 2022 IE5995: IoT & Edge AI Programming 65
Write your own device driver for LPS22HB
• Next, find the datasheet of LPS22HB and go to the I2C operation section.

Given that SDO/SA0 is connected


to GND, we know that the device’s
I2C address should be 1011100b,
which is 0x5C.

#define LPS22HB_ADDRESS 0x5C

We will use Arduino’s Wire library


to perform I2C communication
with the sensor.

#include <Wire.h>

On Nano 33 BLE Sense, integrated


sensors are connected to Wire1.

Winter 2022 IE5995: IoT & Edge AI Programming 66


• When we need the sensor to
acquire a new reading, we need
to set the least significant bit (bit
0) of the CTRL_REG2 register.

#define CTRL_REG2 0x11

void trigger(){
Wire1.beginTransmission(LPS22HB_ADDRESS);
Wire1.write(CTRL_REG2);
Wire1.write(0x01);
Wire1.endTransmission();
}

Winter 2022 IE5995: IoT & Edge AI Programming 67


• The pressure data is 24-bit; thus we need to read the three 8-bit registers
(at addresses 0x28, 0x29 and 0x2A).
• If IF_ADD_INC bit is set in CTRL_REG2, then register address will be
automatically incremented in repeated read of multi-byte data.

#define PRESS_OUT_XL 0x28


#define PRESS_OUT_L 0x29
#define PRESS_OUT_H 0x2A

Winter 2022 IE5995: IoT & Edge AI Programming 68


• The temperature is 16-bit value, contained in two registers.

#define TEMP_OUT_L 0x2B


#define TEMP_OUT_H 0x2C

Winter 2022 IE5995: IoT & Edge AI Programming 69


Trigger a conversion

Write to the CTRL_REG2 register – set bit 0


of this register.

#define CTRL_REG2 0x11

void trigger(){
Wire1.beginTransmission(LPS22HB_ADDRESS);
Wire1.write(CTRL_REG2);
Wire1.write(0x01);
Wire1.endTransmission();
}

Winter 2022 IE5995: IoT & Edge AI Programming 70


Check if data is ready, before reading.

#define LPS22HB_STATUS 0x27

bool presReady(){
uint8_t status = read1(LPS22HB_STATUS);
return(status & 0x1);
}

bool tempReady(){
uint8_t status = read1(LPS22HB_STATUS);
return(status & 0x2);
}

Winter 2022 IE5995: IoT & Edge AI Programming 71


Read data uint32_t getPressure(){
return read1(PRESS_OUT_XL) |
For pressure, we have to perform the below sequence three read1(PRESS_OUT_L) << 8 |
time, one for each byte of the pressure data. read1(PRESS_OUT_H) << 16;
}

uint16_t getTemperature(){
return read1(TEMP_OUT_L) |
read1(TEMP_OUT_H) << 8;
}
uint8_t read1(uint8_t reg){
uint8_t val;
Wire1.beginTransmission(LPS22HB_ADDRESS);
Wire1.write(reg);
Wire1.endTransmission(false); // send restart message
Wire1.requestFrom(LPS22HB_ADDRESS, 1);
val = Wire1.read();
Wire1.endTransmission(true); // send stop message
return(val);
}
Winter 2022 IE5995: IoT & Edge AI Programming 72
trigger();
uint32_t raw_pres;
uint16_t raw_temp;
while(!presReady());
raw_pres = getPressure();
while(!tempReady());
raw_temp = getTemperature();
float pres_hPa = raw_pres / 4096.0;
float temp_F = raw_temp / 100.0;

Winter 2022 IE5995: IoT & Edge AI Programming 73


bool presReady(){
uint8_t status = read1(LPS22HB_STATUS);
return(status & 0x1);
Complete code: }

bool tempReady(){
uint8_t status = read1(LPS22HB_STATUS);
#include <Wire.h> return(status & 0x2);
#define LPS22HB_ADDRESS 0x5C }
#define CTRL_REG2 0x11
#define LPS22HB_STATUS 0x27 uint32_t getPressure(){
#define PRESS_OUT_XL 0x28 return read1(PRESS_OUT_XL) | read1(PRESS_OUT_L) << 8 | read1(PRESS_OUT_H) << 16;
#define PRESS_OUT_L 0x29 }
#define PRESS_OUT_H 0x2A
#define TEMP_OUT_L 0x2B uint16_t getTemperature(){
#define TEMP_OUT_H 0x2C return read1(TEMP_OUT_L) | read1(TEMP_OUT_H) << 8;
char msg[40]; }

void trigger(){ void setup() {


Wire1.beginTransmission(LPS22HB_ADDRESS); Wire1.begin();
Wire1.write(CTRL_REG2); Serial.begin(9600);
Wire1.write(0x01); }
Wire1.endTransmission();
} void loop() {
trigger();
// Perform single-byte read outlined in Table 14 of uint32_t raw_pres;
LPS22HB datasheet uint16_t raw_temp;
uint8_t read1(uint8_t reg){ while(!presReady());
uint8_t val; raw_pres = getPressure();
Wire1.beginTransmission(LPS22HB_ADDRESS); while(!tempReady());
Wire1.write(reg); raw_temp = getTemperature();
Wire1.endTransmission(false); // send restart message float pres_hPa = raw_pres / 4096.0;
Wire1.requestFrom(LPS22HB_ADDRESS, 1); float temp_F = raw_temp / 100.0;
val = Wire1.read(); sprintf(msg, "Pressure %0.1f, Temperature %0.2f\n", pres_hPa, temp_F);
Wire1.endTransmission(true); // send stop message Serial.println(msg);
return(val); delay(1000);
} }

Winter 2022 IE5995: IoT & Edge AI Programming 74


Exercise
• Write your own device driver for the integrated IMU sensor LSM9DS1

Winter 2022 IE5995: IoT & Edge AI Programming 75


Advanced Programming Techniques
• The Nano 33 BLE is based on the nRF52840 microprocessor, which has lots
of powerful features
• But most of the advanced features are not available through the
standard Arduino functions
• How to learn / use these features and develop serious products?
• My experience is:
• Read the nRF52840 Product Specification, again and again
• Find sample code that does something close to what you need, digest it, line by line
• For unfamiliar functions and symbols, dig into the source code directory and find the
definitions

Winter 2022 IE5995: IoT & Edge AI Programming 76


Access registers by // nrf52840: get device information from

address // 4.4 FICR - Factory information configuration registers


// Page 31 on PS ver 1.7
uint32_t *INFO_RAM_addr = (uint32_t *)(0x10000000 + 0x10C);
char msg[30];
void setup() {
// read the RAM information
uint32_t ram = *INFO_RAM_addr;
switch(ram){
case 0x10:
strcpy(msg, "16kB RAM"); break;
case 0x20:
strcpy(msg, "32kB RAM"); break;
case 0x40:
strcpy(msg, "64kB RAM"); break;
case 0x80:
strcpy(msg, "128kB RAM"); break;
case 0x100:
strcpy(msg, "256kB RAM"); break;
default:
strcpy(msg, "Unspecified RAM");
}
Serial.begin(9600);
}
void loop() {
Serial.println(msg);
delay(2000);
}
Winter 2022 IE5995: IoT & Edge AI Programming 77
Exercises
• Use the same approach (i.e., access registers by address) to
• Get the total flash size on the MCU
• Turning on and off some GPIO pin (e.g., the one connected to the LED_BUILTIN)
• These exercises force you to read the product specification

Winter 2022 IE5995: IoT & Edge AI Programming 78


nRF52840 SAADC
Continuously read analog data at a given sampling rate.
Section 6.23 SAADC – Successive approximation analog-to-digital converter

Winter 2022 IE5995: IoT & Edge AI Programming 79


nRF52840’s SAADC continuous sampling
Length Length
• Read Section 6.23 SAADC of “nRF52840 Product Specification”
• Below is a design diagram
Buffer Buffer
Configure SAADC
• Channel(s), RESOLUTION, • Ready = 0
0 1
SAMPLERATE, Enable Ready? • Process data in Start
• Enable & register Interrupts ready buffer address
• Calibrate

Specify buffer start


Trigger START task Trigger SAMPLE task
address and length

STARTED event END event


ISR
ISR
Orange: setup function
Red: loop function
Purple: Event flow Ready = 1
Green: ISR flow
Winter 2022 IE5995: IoT & Edge AI Programming 80
// Disable the SAADC during configuration
nrf_saadc_disable();
// Configure A2 and A3 pins in differential mode SAADC Configuration
const nrf_saadc_channel_config_t channel_config = {
.resistor_p = NRF_SAADC_RESISTOR_DISABLED,
.resistor_n = NRF_SAADC_RESISTOR_DISABLED, • Used both the HAL (Hardware
.gain = NRF_SAADC_GAIN1_6,
.reference = NRF_SAADC_REFERENCE_INTERNAL, Abstraction Layer) functions and
.acq_time = NRF_SAADC_ACQTIME_3US, the MDK (Microprocessor
.mode = NRF_SAADC_MODE_DIFFERENTIAL,
.burst = NRF_SAADC_BURST_DISABLED, Development Kit) functions by nRF
.pin_p = NRF_SAADC_INPUT_AIN2, // Pin A2 • Header file locations:
.pin_n = NRF_SAADC_INPUT_AIN3 // Pin A3 • C:\Users\gn0061\AppData\Local\Arduino15\pack
}; ages\arduino\hardware\mbed_nano\2.6.1\cores\
// initialize the SAADC channel by calling the hal function, declared in nrf_saadc.h arduino\mbed\targets\TARGET_NORDIC\TARGET
nrf_saadc_channel_init(1, &channel_config); // use channel 1 _NRF5x\TARGET_SDK_15_0\modules\nrfx\mdk
nrf_saadc_resolution_set(NRF_SAADC_RESOLUTION_12BIT); // Configure the resolution
nrf_saadc_oversample_set(NRF_SAADC_OVERSAMPLE_DISABLED); // Disable oversampling • C:\Users\gn0061\AppData\Local\Arduino15\pack
// Enable Continuous Mode & set to 16MHz / 1000 (CC) = 16 kHz ages\arduino\hardware\mbed_nano\2.6.1\cores\
NRF_SAADC->SAMPLERATE = (SAADC_SAMPLERATE_MODE_Timers << SAADC_SAMPLERATE_MODE_Pos) arduino\mbed\targets\TARGET_NORDIC\TARGET
_NRF5x\TARGET_SDK_15_0\modules\nrfx\hal
| ((uint32_t)1000 << SAADC_SAMPLERATE_CC_Pos);
// Configure RESULT Buffer and MAXCNT
NRF_SAADC->RESULT.PTR = (uint32_t)SAADC_RESULT_BUFFER;
NRF_SAADC->RESULT.MAXCNT = SAADC_RESULT_BUFFER_SIZE;
Global variables
// Set the END mask to an interrupt: when the result buffer is filled, trigger an interrupt
nrf_saadc_int_enable(NRF_SAADC_INT_END);
// Enable the STARTED event interrupt, to trigger an interrupt each time the STARTED event happens
nrf_saadc_int_enable(NRF_SAADC_INT_STARTED);
// Register the interrupts in NVIC, these functions are declared in core_cm4.h
NVIC_SetPriority(SAADC_IRQn, 3UL);
NVIC_EnableIRQ(SAADC_IRQn);
nrf_saadc_enable(); // Enable the SAADC

Winter 2022 IE5995: IoT & Edge AI Programming 81


Notes on the SAADC Configuration
“.acq_time = NRF_SAADC_ACQTIME_3US,” should be set according to
the source resistance

Winter 2022 IE5995: IoT & Edge AI Programming 82


.gain = NRF_SAADC_GAIN1_6,
.reference = NRF_SAADC_REFERENCE_INTERNAL,

Winter 2022 IE5995: IoT & Edge AI Programming 83


Winter 2022 IE5995: IoT & Edge AI Programming 84
Calibration code:
// Calibrate the SAADC by finding its offset
NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0);
NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
Serial.println(F("Finished SAADC Configuration"));

Winter 2022 IE5995: IoT & Edge AI Programming 85


Trigger START task Trigger SAMPLE task

// Trigger the START task


nrf_saadc_task_trigger(NRF_SAADC_TASK_START);
delay(5); // Allow some time for the START task to trigger
// Trigger the SAMPLE task
nrf_saadc_task_trigger(NRF_SAADC_TASK_SAMPLE);

This completes the void setup() function!

Winter 2022 IE5995: IoT & Edge AI Programming 86


The Global Variables – set up the buffer array and indicator variables
const uint16_t NUM_SAADC_RESULT_BUFFERS = 2;
const uint16_t SAADC_RESULT_BUFFER_SIZE = 8192;
// Contains the (16-bit) results for the SAADC - volatile buffer
volatile nrf_saadc_value_t SAADC_RESULT_BUFFER[NUM_SAADC_RESULT_BUFFERS * SAADC_RESULT_BUFFER_SIZE];
volatile uint8_t READY_INDEX = 0; // the segment index that is filled and ready to move
volatile uint32_t BUFFER_INDEX = 0; // Buffer index (For the SAADC Buffers)

The Interrupt Service Routine (ISR)


extern "C" {
void SAADC_IRQHandler_v() {
// Check to see if the ADC has filled up the result buffer
if (nrf_saadc_event_check(NRF_SAADC_EVENT_END)){
nrf_saadc_event_clear(NRF_SAADC_EVENT_END); // Clear the END event
READY_INDEX = BUFFER_INDEX == 0 ? NUM_SAADC_RESULT_BUFFERS - 1 : BUFFER_INDEX - 1;
nrf_saadc_task_trigger(NRF_SAADC_TASK_START); // Trigger the START task
} else if (nrf_saadc_event_check(NRF_SAADC_EVENT_STARTED)){
nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED); // Clear the STARTED event
// Move the RESULT.PTR to the next segment of the ping-pong buffer
BUFFER_INDEX = BUFFER_INDEX >= NUM_SAADC_RESULT_BUFFERS - 1 ? 0 : BUFFER_INDEX + 1;
NRF_SAADC->RESULT.PTR = (uint32_t)(SAADC_RESULT_BUFFER + (BUFFER_INDEX * SAADC_RESULT_BUFFER_SIZE));
}
}
}

Winter 2022 IE5995: IoT & Edge AI Programming 87


• Project ideas:
The loop
• Currently does nothing • Sample audio at 16 KHz (or higher) and
• Just flashes the LED to verify that things work stream data continuously to PC or
Cellphone via Bluetooth or WIFI
• Kind of a remote microphone
void loop() { • Take a short audio clip (e.g., 1 sec) and
if(READY_INDEX){ perform inference based on some
digitalWrite(13, HIGH); TensorFlow Lite model
} else { • Wake words recognition
digitalWrite(13, LOW);
• Record and send short audio clips to PC for
}
training data collection for the TF Lite
}
model
• Bioelectric sensing -> smart health
The LED should blink at a round 1 Hz frequency.
• Vibration sensing -> machine diagnosis
• ADC -> DSP -> DAC tasks

Winter 2022 IE5995: IoT & Edge AI Programming 88


If we want to set pin D2 in sense mode, and sense for high level:

NRF_P1->PIN_CNF[11] &= ~((uint32_t)GPIO_PIN_CNF_SENSE_Msk);


NRF_P1->PIN_CNF[11] |= ((uint32_t)GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos);

nrf52840.h

Winter 2022 IE5995: IoT & Edge AI Programming 89


DETECTMODE

Winter 2022 IE5995: IoT & Edge AI Programming 90


Follow the directions in the PS

Read:
Section 5.3.1 Power – Power supply
For implementation, refer to nrf52840.h and nrf52840_bitfield.h in the directory: Section 6.9 GPIO
C:\Users\gn0061\AppData\Local\Arduino15\packages\arduino\hardware\mbed_nano\2.6.1\co
Section 6.10 GPIOTE
res\arduino\mbed\targets\TARGET_NORDIC\TARGET_NRF5x\TARGET_SDK_15_0\modules\nrfx
\mdk

Winter 2022 IE5995: IoT & Edge AI Programming 91


// System is turned off after a few seconds. Connect Pin D2 to V3.3 to wake up.
const uint8_t P1_pin = 11; // P1.11 is D2
void setup() {
// Startup Wake up from SYSTEMOFF
for(uint8_t i=0; i<5; i++){ power-saving mode.
digitalWrite(13, HIGH); delay(500); digitalWrite(13, LOW); delay(500);
}
// Start Preparing for the wake-up mechanism
// Disable port interrupt
NRF_GPIOTE->INTENCLR |= ((uint32_t)GPIOTE_INTENCLR_PORT_Clear << GPIOTE_INTENCLR_PORT_Pos);
// Configure wake-up mechanism
NRF_P1->DETECTMODE = 0; // default detect mode
NRF_P1->LATCH &= ~((uint32_t)GPIO_LATCH_PIN11_Msk); // Clear latch status of PIN11
// Set direction to input mode
NRF_P1->PIN_CNF[P1_pin] &= ~((uint32_t)GPIO_PIN_CNF_DIR_Msk);
NRF_P1->PIN_CNF[P1_pin] |= ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos);
// Connect input buffer
NRF_P1->PIN_CNF[P1_pin] &= ~((uint32_t)GPIO_PIN_CNF_INPUT_Msk);
NRF_P1->PIN_CNF[P1_pin] |= ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos);
// Set pulldown
NRF_P1->PIN_CNF[P1_pin] &= ~((uint32_t)GPIO_PIN_CNF_PULL_Msk);
NRF_P1->PIN_CNF[P1_pin] |= ((uint32_t)GPIO_PIN_CNF_PULL_Pulldown << GPIO_PIN_CNF_PULL_Pos);
// Generate Sense signal when pin is HIGH
NRF_P1->PIN_CNF[P1_pin] &= ~((uint32_t)GPIO_PIN_CNF_SENSE_Msk);
NRF_P1->PIN_CNF[P1_pin] |= ((uint32_t)GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos);
// Clear any port event that might have occurred during the configuration
NRF_GPIOTE->EVENTS_PORT = 0;
// Enable port interrupt
NRF_GPIOTE->INTENSET |= ((uint32_t)GPIOTE_INTENSET_PORT_Set << GPIOTE_INTENSET_PORT_Pos);
// End preparing for the wake-up mechanism
// Now put device into System Off mode
NRF_POWER->SYSTEMOFF = 1;
}
void loop() {}
Winter 2022 IE5995: IoT & Edge AI Programming 92

You might also like