LTC Timecode Reader using Arduino - arduino

I posted a little while ago on here and you were really helpful. And i've managed to adapt some code to get it working on an lcd display.
What I would like some extra help with is firstly is there a way of defining the one_time_max etc different depending on the high low state of a pin. This way i could use a switch to change the times so it could switch between NTSC and PAL.
Secondly would anyone be kind enough to explain what is happening in the middle part of this code. I understand that there is a volatile boolean which can be true of false. But later in the code you make an IF statement against it and I don't fully understand how that works. Any help would be appreciated.
Here is a picture of how it's going so far. I'll keep you guys up to date as the project continues :)
// Code from forum post Dec 12, 2007
//
//
// include the library code:
#include <LiquidCrystal.h>
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
#define one_time_max 600 // these values are setup for PA video
#define one_time_min 400 // It's the durstion of a one and zero with a little bit of room for error.
#define zero_time_max 1050 //
#define zero_time_min 950 //
#define icpPin 8 // ICP input pin on arduino
//#define one_time_max 475 // these values are setup for NTSC video
//#define one_time_min 300 // PAL would be around 1000 for 0 and 500 for 1
//#define zero_time_max 875 // 80bits times 29.97 frames per sec
//#define zero_time_min 700 // equals 833 (divide by 8 clock pulses)
#define end_data_position 63
#define end_sync_position 77
#define end_smpte_position 80
volatile unsigned int pin = 13;
volatile unsigned int bit_time; // volatile instructs the variable to be stored in RAM
volatile boolean valid_tc_word; // booleon can be either of two values true or false
volatile boolean ones_bit_count; // booleon can be either of two values true or false
volatile boolean tc_sync; // booleon can be either of two values true or false
volatile boolean write_tc_out; // booleon can be either of two values true or false
volatile boolean drop_frame_flag; // booleon can be either of two values true or false
volatile byte total_bits; //this stores a an 8-bit unsigned number
volatile byte current_bit; //this stores a an 8-bit unsigned number
volatile byte sync_count; //this stores a an 8-bit unsigned number
volatile byte tc[8]; //this stores a an 8-bit unsigned number
volatile char timeCode[11]; //this stores a an 8-bit unsigned number
/* ICR interrupt vector */
ISR(TIMER1_CAPT_vect) //ISR=Interrupt Service Routine, and timer1 capture event
{
//toggleCaptureEdge
TCCR1B ^= _BV(ICES1); //toggles the edge that triggers the handler so that the duration of both high and low pulses is measured.
bit_time = ICR1; //this is the value the timer generates
//resetTimer1
TCNT1 = 0;
if ((bit_time < one_time_min) || (bit_time > zero_time_max)) // this gets rid of anything that's not what we're looking for
{
total_bits = 0;
}
else
{
if (ones_bit_count == true) // only count the second ones pluse
ones_bit_count = false;
else
{
if (bit_time > zero_time_min)
{
current_bit = 0;
sync_count = 0;
}
else //if (bit_time < one_time_max)
{
ones_bit_count = true;
current_bit = 1;
sync_count++;
if (sync_count == 12) // part of the last two bytes of a timecode word
{
sync_count = 0;
tc_sync = true;
total_bits = end_sync_position;
}
}
if (total_bits <= end_data_position) // timecode runs least to most so we need
{ // to shift things around
tc[0] = tc[0] >> 1;
for(int n=1;n<8;n++) //creates tc[1-8]
{
if(tc[n] & 1)
tc[n-1] |= 0x80;
tc[n] = tc[n] >> 1;
}
if(current_bit == 1)
tc[7] |= 0x80;
}
total_bits++;
}
if (total_bits == end_smpte_position) // we have the 80th bit
{
total_bits = 0;
if (tc_sync)
{
tc_sync = false;
valid_tc_word = true;
}
}
if (valid_tc_word)
{
valid_tc_word = false;
timeCode[10] = (tc[0]&0x0F)+0x30; // frames this converst from binary to decimal giving us the last digit
timeCode[9] = (tc[1]&0x03)+0x30; // 10's of frames this converst from binary to decimal giving us the first digit
timeCode[8] = ':';
timeCode[7] = (tc[2]&0x0F)+0x30; // seconds
timeCode[6] = (tc[3]&0x07)+0x30; // 10's of seconds
timeCode[5] = ':';
timeCode[4] = (tc[4]&0x0F)+0x30; // minutes
timeCode[3] = (tc[5]&0x07)+0x30; // 10's of minutes
timeCode[2] = ':';
timeCode[1] = (tc[6]&0x0F)+0x30; // hours
timeCode[0] = (tc[7]&0x03)+0x30; // 10's of hours
drop_frame_flag = bit_is_set(tc[1], 2); //detects whether theree is the drop frame bit.
write_tc_out = true;
}
}
}
void setup()
{
lcd.begin (16, 2);
pinMode(icpPin, INPUT); // ICP pin (digital pin 8 on arduino) as input
bit_time = 0;
valid_tc_word = false;
ones_bit_count = false;
tc_sync = false;
write_tc_out = false;
drop_frame_flag = false;
total_bits = 0;
current_bit = 0;
sync_count = 0;
lcd.print("Finished setup");
delay (1000);
TCCR1A = B00000000; // clear all
TCCR1B = B11000010; // ICNC1 noise reduction + ICES1 start on rising edge + CS11 divide by 8
TCCR1C = B00000000; // clear all
TIMSK1 = B00100000; // ICIE1 enable the icp
TCNT1 = 0; // clear timer1
}
void loop()
{
if (write_tc_out)
{
write_tc_out = false;
if (drop_frame_flag)
lcd.print("TC-[df] ");
else
lcd.print("TC-NO DROP FRAME");
lcd.setCursor(0, 1);
lcd.print((char*)timeCode);
lcd.print("\r");
lcd.setCursor(11, 1);
lcd.print("......");
delay (40);
lcd.clear(); } }

an if removed from the type declaration should not affect the «volatile» keyword as shown in the code ▬ compiler-writers have a trade-off where folks want to do systems ( stuff like here ) and it jams the machine so they tell you volatileis some sort of exotic thing but is far from that when used here
it is the way it should be here ~ that is not a business machine

Related

Send Arduino serial commands while plotting

I have a simple PD Arduino controller to spin a motor. I want to use it to demonstrate system responses graphically. I have it working so I can give a target position using the serial monitor, but I want to be able to see the serial plot output at the same time. There seems to be a similar dialogue box in the Serial Plotter, but commands sent from there don't seem to be recognized. Is there a way to plot incoming serial data while also sending commands as described above? I don't mind if I need additional libraries, but I can't see why it shouldn't work natively since I can already send commands while receiving info using the Serial Monitor. Maybe I'm misunderstanding that process.
Any help would be very appreciated. See full code below:
// Clockwise rotation direction.
#define CW 1
// Counter clockwise rotation direction.
#define CCW 0
// Frequency of output PWM signal.
#define PWM_FREQ 25000
// Update rate in microseconds.
#define CYCLE_TIME 1000
// Rate of sending position data to PC.
#define PLOT_RATE 200
#define PLOT_COUNTER CYCLE_TIME/PLOT_RATE
// IO pins. //
// The pin connected to ENBble A on the driver.
const int ENB = 14;
// Pins connected to IN3 and IN4 on the driver (for controlling the rotation direction).
const int IN4 = 15;
const int IN3 = 16;
// Signal A wire of the encoder.
const int ENCA = 17;
// Signal B wire of the encoder.
const int ENCB = 18;
// Value of ENCA.
int enca = 0;
// Value of ENCB.
int encb = 0;
// Value of IN3.
int in3 = 0;
// Value of IN4.
int in4 = 0;
// Motors position measure by encoder.
volatile long int motorPos = 0;
// Communication variables. //
// The byte sent over serial to Teensy.
int incomingByte = 0;
// Input buffer for receiving user input over serial.
char inputBuffer[8];
int bufferCnt = 0;
// Counter for sending position over serial for plotting purposes.
int pltCounter = 0;
// Controller variables./ /
// Last motor position.
long int lastPos = 0;
// Target motor position.
int targetPos = 0;
// Position at the start of the control loop.
int currentPos = 0;
// Position at the start of the previous control loop.
int prevPos = 0;
// Change in position (for approximating the derivative).
int dP = 0;
// Position error.
int pError = 0;
// P term of the controller.
int pTerm = 0;
// D term of the controller.
int dTerm = 0;
// Speed (= voltage = duty cycle). Controller output mapped to duty cycle range.
int spd = 0;
// Controller output.
int contOut = 0;
// Ratio for transforming counts to degrees (1920 count / 360 deg)
float ratio = static_cast<float>(360)/static_cast<float>(1920);
// Controller tunable parameters. //
// P gain.
const int kP = 10;
// D gain.
const int kD = 0;
// Error in encoder pulses correponding to the minimum duty cycle.
const int minErr = 0;
// Error in encoder pulses corresponding to the maximum duty cycle.
const int maxErr = 1024;
// minDutyCycle and maxDutyCycle depend on PWM frequency and can be determined in dc_motor_speed_control . For example for frequency of 25k,
// minDutyCycle = 120 (Motor starts to move),
// maxDutyCycle = 190 (Motor speed reaches its maximum given the supplied voltage).
const int minDutyCycle = 120;
const int maxDutyCycle = 190;
// Controller update rate variables. //
// Difference in time between desired cycle period and its execution time (without any delay()s).
int cycleDiff;
// Control loop start time.
long int startTime;
// Control loop end time.
long int endTime;
// Plotting
float motorPosDeg = 0;
//Plotter p;
void setup() {
Serial.begin(9600);
// Initialize the pins.
pinMode(IN3,OUTPUT);
pinMode(IN4,OUTPUT);
pinMode(ENB,OUTPUT);
pinMode(ENCA,INPUT);
pinMode(ENCB,INPUT);
analogWriteFrequency(ENB, PWM_FREQ);
// Set the initial rotation direction.
setDirection(CCW);
// Start with the motor at rest.
analogWrite(ENB,0);
// Encoder interrupt.
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
//p.Begin();
//p.AddTimeGraph("Position v Time", 1000, "Position", motorPosDeg);
}
// *** Encoder interrupt routines. See "Understanding Quadrature Encoded Signals" here: https://www.pjrc.com/teensy/td_libs_Encoder.html" *** //
void encoderAISRising(){
if(digitalRead(ENCB) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISFalling, FALLING);
}
void encoderAISFalling(){
if(digitalRead(ENCB) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCA), encoderAISRising, RISING);
}
void encoderBISRising(){
if(digitalRead(ENCA) == LOW)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISFalling, FALLING);
}
void encoderBISFalling(){
if(digitalRead(ENCA) == HIGH)
motorPos++;
else
motorPos--;
attachInterrupt(digitalPinToInterrupt(ENCB), encoderBISRising, RISING);
}
// *** ***//
// Default rotation direction is CCW.
void setDirection(bool dir){
// CCW
if (dir == CCW){
digitalWrite(IN3,HIGH);
digitalWrite(IN4,LOW);
}else{
digitalWrite(IN3,LOW);
digitalWrite(IN4,HIGH);
}
}
void loop() {
if (Serial.available() > 0) {
// Read the incoming bytes, until a next line character (Enter) is encountered.
while (1){
incomingByte = Serial.read();
// We have read all the bytes.
if (incomingByte == '\n' || incomingByte == '\r'){
Serial.read();
break;
}else{
// Store the byte in the buffer and move on to the next.
inputBuffer[bufferCnt] = incomingByte;
bufferCnt++;
}
}
// Add a NULL character to the end of the array. Required for using atoi.
inputBuffer[bufferCnt] = '\0';
bufferCnt = 0;
// Convert string to integer.
targetPos = atoi(inputBuffer);
targetPos = targetPos / ratio;
}
// int i = 0;
// if (i % 2 == 0){
// targetPos = 360;
// } else {
// targetPos = 0;
// }
startTime = micros();
// Get the latest motor position.
currentPos = motorPos;
// Position error.
//pError = targetPos - motorPos;
pError = targetPos - currentPos;
// P term of the controller.
pTerm = kP * pError;
dP = currentPos - prevPos;
// D term of the controller. CYCLE_TIME/1000 normalizes the denominator, otherwise dTerm would always be zero (integer division).
dTerm = kD * (dP/(CYCLE_TIME/1000));
contOut = pTerm + dTerm;
// Set the target duty cycle (i.e. speed (i.e. voltage)).
// Error (in terms of encoder pulses) in the range minErr-maxErr is mapped to speed range corresponding to minDutyCycle-maxDutyCycle.
// 4 parameters to tune here.
spd = map(abs(contOut),minErr,maxErr,minDutyCycle,maxDutyCycle);
// Set the direction according to sign of position error (CCW is positive), and then speed.
// One optimization would be calling analogWrite(ENB,abs(spd)) at the start or end of the loop instead
// (at the expense of readibility).
if (pError > 0){
setDirection(CCW);
analogWrite(ENB,abs(spd));
}else if (pError < 0){
setDirection(CW);
analogWrite(ENB,abs(spd));
}
if (pltCounter == PLOT_COUNTER){
float mtrPos = static_cast<float>(motorPos);
motorPosDeg = mtrPos * ratio;
Serial.print(int(motorPosDeg));
Serial.println();
pltCounter = 0;
}
pltCounter++;
prevPos = currentPos;
cycleDiff = micros() - startTime;
// Adjust the update rate.
if (cycleDiff < CYCLE_TIME){
delayMicroseconds(CYCLE_TIME - cycleDiff);
}
//i++;
}
From what i understand of the plot function it utilizes the main arduino connexion to work. Based on how the arduino uart work you can only have 1 com port connexion per com port. This means you can either have the plot or command line open for each uart connexion. It is possible with different version of arduino to have multiple com ports. On the arduino uno there is only one com port "Serial". On the mega i think there are 3 uart ports. If you use a external FTDI UART board you can have the plot window open for serial0 and have the FTDI board connected on Serial1 to have the command line window open. You will have to change your code a little to send commands to serial1.
Here are a couple links to help you.
https://docs.arduino.cc/tutorials/communication/TwoPortReceive
https://docs.arduino.cc/built-in-examples/communication/MultiSerialMega
https://www.amazon.fr/AZDelivery-Adaptateur-FT232RL-s%C3%A9rie-book/dp/B01N9RZK6I?th=1

How to downsize code that uses DHT11 for AVR (Attiny45)

I'm trying to build a small hygrometer based on the DHT11 and I'm having a bit of an "issue" with the code size. I want to run it on an Attiny45 and it's a wee bit too big (352 bytes too big to be exact). I am aware that I could just use an Attiny85 and have space to spare or don't use a bootloader and barely fit it in (94%) but I kind of want to make my life harder than it needs to be and figure out how to reduce size since it'll probably come in handy in the future. Treat it as a learning experience if you will.
What it's supposed to do:
Read DHT11 input
Display results on 2 two-digit 7-segment displays (so I guess 4 7-segments in total)
Go to sleep most of the time to preserve battery
Wake up every 8 seconds to update sensor values (without turning on the display)
Wake up on a button press to display the values for humidity and temperature
Side note: 7-segments are adressed via two 74HC595s of which I am using 7 outputs each for the displays and 1 each for a transistor that connects the display in question to GND. There's a schematic at the bottom if you're interested.
As pointed out, my main issue is code size so if anyone has any tips on how to reduce that (or any other tips how to improve the code) please let me know.
I hope I'm asking the question properly, if not please let me know.
Compiler output:
Sketch uses 3872 bytes (110%) of program storage space. Maximum is 3520 bytes.text section exceeds available space in board
Global variables use 107 bytes (41%) of dynamic memory, leaving 149 bytes for local variables. Maximum is 256 bytes.
Sketch too big; see https://support.arduino.cc/hc/en-us/articles/360013825179 for tips on reducing it.
Error compiling for board ATtiny45/85 (Optiboot).
Code:
/*
Humidity/Temperature sensor setup with DHT-11
Two digits for humidity
Two digits for temperature
Button to wake up from sleep
NPNs activated via 8th bit in 74HC595s, always alternating
Author: ElectroBadger
Date: 2021-11-02
Version: 1.0
*/
/*
Reduce power consumption:
- Run at 1 MHz internal clock
- Turn off ADC
- Use SLEEP_MODE_PWR_DOWN
*/
#include "DHT.h" //DHT-11 sensor
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> //Doggy stuff
//define attiny pins
#define INT_PIN PB4
#define DATA PB1
#define SENSOR PB3
#define LATCH PB2
#define CLK PB0
//define other stuff
#define SENSOR_TYPE DHT11
#define LED_DELAY 50
//changing variables
short ones_data; //16-bits for display of ones
short tens_data; //16-bits for display of tens
byte sevSeg, measurements; //7-segment bit pattern / wait time between LEDs [ms] / # of measurements taken
bool firstPair, btnPress; //tracks which pair of 7-segments is on; tracks button presses
uint32_t oldMillis, sleepTimer; //tracks the last acquisition time and wakeup time
//Initialize sensor
DHT dht(SENSOR, SENSOR_TYPE);
//Shifts 16 bits out MSB first, on the rising edge of the clock.
void shiftOut(int dataPin, int clockPin, short toBeSent){
int i=0;
int pinState = 0;
//Clear everything out just in case
digitalWrite(dataPin, 0);
digitalWrite(clockPin, 0);
//Loop through bits in the data bytes, COUNTING DOWN in the for loop so that
//0b00000000 00000001 or "1" will go through such that it will be pin Q0 that lights.
for(i=0; i<=15; i++){
digitalWrite(clockPin, 0);
//if the value passed to myDataOut AND a bitmask result
//is true then set pinState to 1
if(toBeSent & (1<<i)){
pinState = 1;
}
else{
pinState = 0;
}
digitalWrite(dataPin, pinState); //Sets the pin to HIGH or LOW depending on pinState
digitalWrite(clockPin, 1); //Shifts bits on upstroke of clock pin
digitalWrite(dataPin, 0); //Zero the data pin after shift to prevent bleed through
}
digitalWrite(clockPin, 0); //Stop shifting
}
//Converts an int <10 to a bit pattern for 7-segment displays
short toSegments(int value){
byte pattern = 0b00000000; //create empty pattern
//Using a switch...case (3878 bytes) if...else if...else uses 3946 bytes
switch(value){
case 0:
pattern = 0b01111110;
break;
case 1:
pattern = 0b00110000;
break;
case 2:
pattern = 0b01101101;
break;
case 3:
pattern = 0b01111001;
break;
case 4:
pattern = 0b00110011;
break;
case 5:
pattern = 0b01011011;
break;
case 6:
pattern = 0b01011111;
break;
case 7:
pattern = 0b01110000;
break;
case 8:
pattern = 0b01111111;
break;
case 9:
pattern = 0b01111011;
break;
default:
pattern = 0b00000000;
break;
}
return pattern;
}
void goToSleep(){
//Turn off 7-segments and NPNs
digitalWrite(LATCH, 0);
shiftOut(DATA, CLK, 0b0000000000000000);
digitalWrite(LATCH, 1);
//Set deep sleep mode
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
cli(); // timed sequence coming up, so disable interrupts
btnPress = false;
measurements = 0;
resetWatchdog (); // get watchdog ready
sleep_enable (); // ready to sleep
sei(); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
power_all_enable (); // power everything back on
}
ISR(PCINT_VECTOR){
btnPress = true;
sleepTimer = millis();
}
// watchdog interrupt
ISR(WDT_vect){
wdt_disable(); //disable watchdog
}
void resetWatchdog(){
MCUSR = 0; //clear various "reset" flags
WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF); //allow changes, disable reset, clear existing interrupt
//set interrupt mode and an interval (WDE must be changed from 1 to 0 here)
WDTCR = bit (WDIE) | bit (WDP3) | bit (WDP0); //set WDIE, and 8 seconds delay
wdt_reset(); //pat the dog
}
void setup(){
resetWatchdog(); // do this first in case WDT fires
cli(); //Disable interrupts during setup
pinMode(INT_PIN, INPUT_PULLUP); //Set interrupt pin as input w/ internal pullup
pinMode(DATA, OUTPUT); //Set serial data as output
pinMode(CLK, OUTPUT); //Set shift register clock as output
pinMode(LATCH, OUTPUT); //Set output register (latch) clock as output
// Interrupts
PCMSK = bit(INT_PIN); //Enable interrupt handler (ISR)
GIFR |= bit(PCIF); // clear any outstanding interrupts
GIMSK |= bit(PCIE); //Enable PCINT interrupt in the general interrupt mask
//default conditions
/* bit 0-6: ones digits
bit 7: NPN for units digits
bit 8-14: ones digits
bit 15: NPN for tens digits
*/
ones_data = 0b0000000000000000;
tens_data = 0b0000000000000000;
measurements = 0;
firstPair = true;
btnPress = false;
oldMillis = 0;
sleepTimer = 0;
//Start sensor
dht.begin();
delay(1000); //wait 1s for sensor to stabilize
sei(); //Enable interrupts after setup
}
void loop(){
if((millis()-oldMillis) > 1000){
//Slow sensor, so readings may be up to 2 seconds old
byte hum = dht.readHumidity(); //Read humidity
byte temp = dht.readTemperature(); //Read temperatuer in °C
//update tens bit string
tens_data = 0b0000000000000000; //reset to all 0s
sevSeg = toSegments(hum/10); //convert tens of humidity to 7-segment logic
tens_data |= sevSeg; // bitwise OR the result with the output short
tens_data = tens_data << 8; //shift by 8 so it's almost in the right place (see below)
sevSeg = toSegments(temp/10); //convert tens of temperature to 7-segment logic
tens_data |= sevSeg; // bitwise OR the result with the output short
tens_data = tens_data << 1; //shift by 1 so everything is in the right place
tens_data |= 0b0000000100000000; //set NPN for tens pair to active and ones NPN to inactive
//update ones bit string
ones_data = 0b0000000000000000; //reset to all 0s
sevSeg = toSegments(hum%10); //convert ones of humidity to 7-segment logic
ones_data |= sevSeg; // bitwise OR the result with the output short
ones_data = ones_data << 8; //shift by 8 so it's almost in the right place (see below)
sevSeg = toSegments(temp%10); //convert ones of temperature to 7-segment logic
ones_data |= sevSeg; // bitwise OR the result with the output short
ones_data = ones_data << 1; //shift by 1 so everything is in the right place
ones_data |= 0b0000000000000001; //set NPN for ones pair to active and tens NPN to inactive
oldMillis = millis(); //I don't much care about the few ms lost
} //during data acquisition
if(btnPress){
//shift out the next batch of data to the display
digitalWrite(LATCH, 0); //Set latch pin LOW so nothing gets shifted out
if(firstPair){
shiftOut(DATA, CLK, tens_data); //Shift out LED states for 7-segments of tens
firstPair = false;
}
else{
shiftOut(DATA, CLK, ones_data); //Shift out LED states for 7-segments of ones
firstPair = true;
}
digitalWrite(LATCH, 1); //sent everything out in parallel
delay(LED_DELAY); //wait for some time until switching to the other displays
if((millis()-sleepTimer) > 6000){ //Sleep after 6s display time
goToSleep();
}
}
else{
if(measurements > 5){
goToSleep();
}
}
}
DHT11 hygrometer schematic
Okay so thanks to the input of Mat I tried substituting the DHT11 library with something more sleek, which took me a while to get up and running. I ended up using this as a base, edited around a bit and commented heavily for my benefit.
I added my updated code below for anyone interested (thanks for pointing out the correct highlighting issue), there's also a github with the rest of the design files.
Seems the library is really heavy, as the compiler output shows:
Compiler output:
Sketch uses 2354 bytes (66%) of program storage space. Maximum is 3520 bytes.
Global variables use 104 bytes (40%) of dynamic memory, leaving 152 bytes for local variables. Maximum is 256 bytes.
Code:
/*
Humidity/Temperature sensor setup with DHT-11
Two digits for humidity
Two digits for temperature
Button to wake up from sleep
NPNs activated via 8th bit in 74HC595s, always alternating
Author: ElectroBadger
Date: 2021-11-09
Version: 2.0
*/
/*
Reduce power consumption:
- Run at 1 MHz internal clock
- Turn off ADC
- Use SLEEP_MODE_PWR_DOWN
*/
//#include "DHT.h" // DHT-11 sensor
#include <avr/sleep.h> // Sleep Modes
#include <avr/power.h> // Power management
#include <avr/wdt.h> // Doggy stuff
// define attiny pins
#define INT_PIN PB4
#define DATA PB1
#define SENSOR PB3
#define LATCH PB2
#define CLK PB0
// define other stuff
//#define SENSOR_TYPE DHT11
#define LED_DELAY 50
//fixed variables
//array lookup for number display; ascending order: 0, 1, 2, ...
const byte numLookup[] = {
0b01111110, //0
0b00110000, //1
0b01101101, //2
0b01111001, //3
0b00110011, //4
0b01011011, //5
0b01011111, //6
0b01110000, //7
0b01111111, //8
0b01111011 //9
};
// changing variables
short ones_data; // 16-bits for display of ones
short tens_data; // 16-bits for display of tens
byte sevSeg, measurements; // 7-segment bit pattern / wait time between LEDs [ms] / # of measurements taken
bool firstPair, btnPress; // tracks which pair of 7-segments is on; tracks button presses
uint32_t oldMillis, sleepTimer; // tracks the last acquisition time and wakeup time
byte humI, humD, tempI, tempD; // values of humidity and temperature (we're only gonna need integral parts but I need all for the checksum)
// Initialize sensor
//DHT dht(SENSOR, SENSOR_TYPE);
// Shifts 16 bits out MSB first, on the rising edge of the clock.
void shiftOut(int dataPin, int clockPin, short toBeSent) {
int i = 0;
int pinState = 0;
// Clear everything out just in case
digitalWrite(dataPin, 0);
digitalWrite(clockPin, 0);
// Loop through bits in the data bytes
for (i = 0; i <= 15; i++) {
digitalWrite(clockPin, 0);
// if the value AND a bitmask result is true then set pinState to 1
if (toBeSent & (1 << i)) {
pinState = 1;
}
else {
pinState = 0;
}
digitalWrite(dataPin, pinState); // sets the pin to HIGH or LOW depending on pinState
digitalWrite(clockPin, 1); // shifts bits on upstroke of clock pin
digitalWrite(dataPin, 0); // zero the data pin after shift to prevent bleed through
}
digitalWrite(clockPin, 0); // Stop shifting
}
void start_signal(byte SENSOR_PIN) {
pinMode(SENSOR_PIN, OUTPUT); // set pin as output
digitalWrite(SENSOR_PIN, LOW); // set pin LOW
delay(18); // wait 18 ms
digitalWrite(SENSOR_PIN, HIGH); // set pin HIGH
pinMode(SENSOR_PIN, INPUT_PULLUP); // set pin as input and pull to VCC (10k)
}
boolean read_dht11(byte SENSOR_PIN) {
uint16_t rawHumidity = 0;
uint16_t rawTemperature = 0;
uint8_t checkSum = 0;
uint16_t data = 0;
unsigned long startTime;
for (int8_t i = -3; i < 80; i++) { // loop 80 iterations, representing 40 bits * 2 (HIGH + LOW)
byte high_time; // stores the HIGH time of the signal
startTime = micros(); // stores the time the data transfer started
// sensor should pull line LOW and keep for 80µs (while SENSOR_PIN == HIGH)
// then pull HIGH and keep for 80µs (while SENSOR_PIN == LOW)
// then pull LOW again, aka send data (while SENSOR_PIN == HIGH)
do { // waits for sensor to respond
high_time = (unsigned long)(micros() - startTime); // update HIGH time
if (high_time > 90) { // times out after 90 microseconds
Serial.println("ERROR_TIMEOUT");
return;
}
}
while (digitalRead(SENSOR_PIN) == (i & 1) ? HIGH : LOW);
// actual data starts at iteration 0
if (i >= 0 && (i & 1)) { // if counter is odd, do this (only counts t_on time and ignores t_off)
data <<= 1; // left shift data stream by 1 since we are at a the next bit
// TON of bit 0 is maximum 30µs and of bit 1 is at least 68µs
if (high_time > 30) {
data |= 1; // we got a one
}
}
switch ( i ) {
case 31: // bit 0-16 is humidity
rawHumidity = data;
break;
case 63: // bit 17-32 is temperature
rawTemperature = data;
case 79: // bit 33-40 is checksum
checkSum = data;
data = 0;
break;
}
}
// Humidity
humI = rawHumidity >> 8;
rawHumidity = rawHumidity << 8;
humD = rawHumidity >> 8;
// Temperature
tempI = rawTemperature >> 8;
rawTemperature = rawTemperature << 8;
tempD = rawTemperature >> 8;
if ((byte)checkSum == (byte)(tempI + tempD + humI + humD)) {
return true;
}
else {
return false;
}
}
void goToSleep() {
// Turn off 7-segments and NPNs
digitalWrite(LATCH, 0);
shiftOut(DATA, CLK, 0b0000000000000000);
digitalWrite(LATCH, 1);
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // Set deep sleep mode
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
cli(); // timed sequence coming up, so disable interrupts
btnPress = false; // reset button flag
measurements = 0; // reset measurement counter
resetWatchdog (); // get watchdog ready
sleep_enable (); // ready to sleep
sei(); // interrupts are required now
sleep_cpu (); // sleep
sleep_disable (); // precaution
power_all_enable (); // power everything back on
}
ISR(PCINT0_vect) {
btnPress = true;
sleepTimer = millis();
}
// watchdog interrupt
ISR(WDT_vect) {
wdt_disable(); // disable watchdog
}
void resetWatchdog() {
MCUSR = 0; //clear various "reset" flags
WDTCR = bit (WDCE) | bit (WDE) | bit (WDIF); //allow changes, disable reset, clear existing interrupt
WDTCR = bit (WDIE) | bit (WDP3) | bit (WDP0); //set WDIE, and 8 seconds delay
wdt_reset();
}
void setup() {
resetWatchdog(); // do this first in case WDT fires
cli(); // disable interrupts during setup
pinMode(INT_PIN, INPUT_PULLUP); // set interrupt pin as input w/ internal pullup
pinMode(DATA, OUTPUT); //set serial data as output
pinMode(CLK, OUTPUT); //set shift register clock as output
pinMode(LATCH, OUTPUT); //set output register (latch) clock as output
pinMode(SENSOR, INPUT); //set DHT11 pin as input
// Interrupts
PCMSK = bit(INT_PIN); // enable interrupt handler (ISR)
GIFR |= bit(PCIF); // clear any outstanding interrupts
GIMSK |= bit(PCIE); // enable PCINT interrupt in the general interrupt mask
//default conditions
/* bit 0-6: ones digits
bit 7: NPN for units digits
bit 8-14: ones digits
bit 15: NPN for tens digits
*/
ones_data = 0b0000000000000000;
tens_data = 0b0000000000000000;
measurements = 0;
firstPair = true;
btnPress = false;
oldMillis = 0;
sleepTimer = 0;
humI = 0;
humD = 0;
tempI = 0;
tempD = 0;
// Start sensor
//dht.begin();
sei(); // enable interrupts after setup
}
void loop() {
if ((millis() - oldMillis) > 1000) {
// slow sensor, so readings may be up to 2 seconds old
//byte hum = dht.readHumidity(); //Read humidity
//byte temp = dht.readTemperature(); //Read temperatuer in °C
delay(2000); // wait for DHT11 to start up
start_signal(SENSOR); // send start sequence
if(read_dht11(SENSOR)){
// update tens bit string
tens_data = 0b0000000000000000; // reset to all 0s
tens_data |= numLookup[humI / 10]; // bitwise OR the result with the output short
tens_data = tens_data << 8; // shift by 8 so it's almost in the right place (see below)
tens_data |= numLookup[tempI / 10]; // bitwise OR the result with the output short
tens_data = tens_data << 1; // shift by 1 so everything is in the right place
tens_data |= 0b0000000100000000; // set NPN for tens pair to active and ones NPN to inactive
// update ones bit string
ones_data = 0b0000000000000000; // reset to all 0s
ones_data |= numLookup[humI % 10]; // bitwise OR the result with the output short
ones_data = ones_data << 8; // shift by 8 so it's almost in the right place (see below)
ones_data |= numLookup[tempI % 10]; // bitwise OR the result with the output short
ones_data = ones_data << 1; // shift by 1 so everything is in the right place
ones_data |= 0b0000000000000001; // set NPN for ones pair to active and tens NPN to inactive
}
else{
tens_data = 0b1001111110011100;
ones_data = 0b0000101000001011;
}
oldMillis = millis(); // I don't much care about the few ms lost during data acquisition
}
if (btnPress) {
// shift out the next batch of data to the display
digitalWrite(LATCH, 0); // Set latch pin LOW so nothing gets shifted out
if (firstPair) {
shiftOut(DATA, CLK, tens_data); // shift out LED states for 7-segments of tens
firstPair = false; // reset first digit flag
}
else {
shiftOut(DATA, CLK, ones_data); //Shift out LED states for 7-segments of ones
firstPair = true; //set first digit flag
}
digitalWrite(LATCH, 1); //sent everything out in parallel
delay(LED_DELAY); //wait for some time until switching to the other displays
if ((millis() - sleepTimer) > 6000) { //Sleep after 6s display time
goToSleep();
}
}
else {
if (measurements > 3) {
goToSleep();
}
}
}

pulse sensor + arduino mkr1000 to calculate BPM

tldr; what is an easy/logical way (for a beginner) to calculate BPM using pulse sensor and mkr1000? I don't want any visualizations or processing sketch, but just print BPM values
Please bear with me, I am a newbie at this and i've tried my best to understand this and fix this issue, but in vain.
I am using the pulse sensor (SEN-11574) with Arduino mkr1000 to calculate the BPM and print it in serial monitor. I was able to get raw readings using their starter code
// Variables
int PulseSensorPurplePin = 0; // Pulse Sensor PURPLE WIRE connected to ANALOG PIN 0
int LED13 = 13; // The on-board Arduion LED
int Signal; // holds the incoming raw data. Signal value can range from 0-1024
int Threshold = 550; // Determine which Signal to "count as a beat", and which to ingore.
// The SetUp Function:
void setup() {
pinMode(LED13,OUTPUT); // pin that will blink to your heartbeat!
Serial.begin(9600); // Set's up Serial Communication at certain speed.
}
// The Main Loop Function
void loop() {
Signal = analogRead(PulseSensorPurplePin); // Read the PulseSensor's value.
// Assign this value to the "Signal" variable.
Serial.println(Signal); // Send the Signal value to Serial Plotter.
if(Signal > Threshold){ // If the signal is above "550", then "turn-on" Arduino's on-Board LED.
digitalWrite(LED13,HIGH);
} else {
digitalWrite(LED13,LOW); // Else, the sigal must be below "550", so "turn-off" this LED.
}
delay(10);
}
However the real problem is that I am unable to calculate the BPM using their example code available on their website here
From what I understand, the interrupt timer function in the Interrupt.ino file is not compatible with mkr1000. Attached is this code for your reference.
// THIS IS THE TIMER 2 INTERRUPT SERVICE ROUTINE.
// Timer 2 makes sure that we take a reading every 2 miliseconds
ISR(TIMER2_COMPA_vect){ // triggered when Timer2 counts to 124
cli(); // disable interrupts while we do this
Signal = analogRead(pulsePin); // read the Pulse Sensor
sampleCounter += 2; // keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
// find the peak and trough of the pulse wave
if(Signal < thresh && N > (IBI/5)*3){ // avoid dichrotic noise by waiting 3/5 of last IBI
if (Signal < T){ // T is the trough
T = Signal; // keep track of lowest point in pulse wave
}
}
if(Signal > thresh && Signal > P){ // thresh condition helps avoid noise
P = Signal; // P is the peak
} // keep track of highest point in pulse wave
// NOW IT'S TIME TO LOOK FOR THE HEART BEAT
// signal surges up in value every time there is a pulse
if (N > 250){ // avoid high frequency noise
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
Pulse = true; // set the Pulse flag when we think there is a pulse
digitalWrite(blinkPin,HIGH); // turn on pin 13 LED
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
lastBeatTime = sampleCounter; // keep track of time for next pulse
if(secondBeat){ // if this is the second beat, if secondBeat == TRUE
secondBeat = false; // clear secondBeat flag
for(int i=0; i<=9; i++){ // seed the running total to get a realisitic BPM at startup
rate[i] = IBI;
}
}
if(firstBeat){ // if it's the first time we found a beat, if firstBeat == TRUE
firstBeat = false; // clear firstBeat flag
secondBeat = true; // set the second beat flag
sei(); // enable interrupts again
return; // IBI value is unreliable so discard it
}
// keep a running total of the last 10 IBI values
word runningTotal = 0; // clear the runningTotal variable
for(int i=0; i<=8; i++){ // shift data in the rate array
rate[i] = rate[i+1]; // and drop the oldest IBI value
runningTotal += rate[i]; // add up the 9 oldest IBI values
}
rate[9] = IBI; // add the latest IBI to the rate array
runningTotal += rate[9]; // add the latest IBI to runningTotal
runningTotal /= 10; // average the last 10 IBI values
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
QS = true; // set Quantified Self flag
// QS FLAG IS NOT CLEARED INSIDE THIS ISR
}
}
if (Signal < thresh && Pulse == true){ // when the values are going down, the beat is over
digitalWrite(blinkPin,LOW); // turn off pin 13 LED
Pulse = false; // reset the Pulse flag so we can do it again
amp = P - T; // get amplitude of the pulse wave
thresh = amp/2 + T; // set thresh at 50% of the amplitude
P = thresh; // reset these for next time
T = thresh;
}
if (N > 2500){ // if 2.5 seconds go by without a beat
thresh = 530; // set thresh default
P = 512; // set P default
T = 512; // set T default
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
firstBeat = true; // set these to avoid noise
secondBeat = false; // when we get the heartbeat back
}
sei(); // enable interrupts when youre done!
}// end isr
On the interrupt-notes file they mention another work-around for processors that are not compatible with this code, but even after hours of following the intructions, the code didn't work, again with errors with timer interrupt functions.
Next, I used this guide but again, it didn't work either and just prints raw signal value that constantly changes (S1023). The code is attached (2 tabs):
/* Pulse Sensor Amped 1.4 by Joel Murphy and Yury Gitman http://www.pulsesensor.com
Adapted by sdizdarevic
---------------------- Notes ---------------------- ----------------------
This code:
1) Blinks an LED to User's Live Heartbeat PIN 6
2) Fades an LED to User's Live HeartBeat
3) Determines BPM
4) Prints All of the Above to Serial
Read Me:
https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino/blob/master/README.md
---------------------- ---------------------- ----------------------
*/
// Variables
int pulsePin = 0; // Pulse Sensor purple wire connected to analog pin 0
int blinkPin = 6; // pin to blink led at each beat
//int fadePin = 5; // pin to do fancy classy fading blink at each beat
//int fadeRate = 0; // used to fade LED on with PWM on fadePin
// Volatile Variables, used in the interrupt service routine!
volatile int BPM; // int that holds raw Analog in 0. updated every 2mS
volatile int Signal; // holds the incoming raw data
volatile int IBI = 600; // int that holds the time interval between beats! Must be seeded!
volatile boolean Pulse = false; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile boolean QS = false; // becomes true when Arduoino finds a beat.
volatile int rate[10]; // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P =512; // used to find peak in pulse wave, seeded
volatile int T = 512; // used to find trough in pulse wave, seeded
volatile int thresh = 525; // used to find instant moment of heart beat, seeded
volatile int amp = 100; // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true; // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false; // used to seed rate array so we startup with reasonable BPM
// Regards Serial OutPut -- Set This Up to your needs
static boolean serialVisual = false; // Set to 'false' by Default. Re-set to 'true' to see Arduino Serial Monitor ASCII Visual Pulse
void setup(){
pinMode(blinkPin,OUTPUT); // pin that will blink to your heartbeat!
//pinMode(fadePin,OUTPUT); // pin that will fade to your heartbeat!
Serial.begin(115200); // we agree to talk fast!
//interruptSetup(); // sets up to read Pulse Sensor signal every 2mS
// IF YOU ARE POWERING The Pulse Sensor AT VOLTAGE LESS THAN THE BOARD VOLTAGE,
// UN-COMMENT THE NEXT LINE AND APPLY THAT VOLTAGE TO THE A-REF PIN
// analogReference(EXTERNAL);
}
// Where the Magic Happens
void loop(){
//
//
Signal = analogRead(pulsePin); // read the Pulse Sensor
sampleCounter += 2; // keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
// find the peak and trough of the pulse wave
if(Signal < thresh && N > (IBI/5)*3){ // avoid dichrotic noise by waiting 3/5 of last IBI
if (Signal < T){ // T is the trough
T = Signal; // keep track of lowest point in pulse wave
}
}
if(Signal > thresh && Signal > P){ // thresh condition helps avoid noise
P = Signal; // P is the peak
} // keep track of highest point in pulse wave
// NOW IT'S TIME TO LOOK FOR THE HEART BEAT
// signal surges up in value every time there is a pulse
if (N > 250){ // avoid high frequency noise
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
Pulse = true; // set the Pulse flag when we think there is a pulse
digitalWrite(blinkPin,HIGH); // turn on pin 13 LED
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
lastBeatTime = sampleCounter; // keep track of time for next pulse
if(secondBeat){ // if this is the second beat, if secondBeat == TRUE
secondBeat = false; // clear secondBeat flag
for(int i=0; i<=9; i++){ // seed the running total to get a realisitic BPM at startup
rate[i] = IBI;
}
}
if(firstBeat){ // if it's the first time we found a beat, if firstBeat == TRUE
firstBeat = false; // clear firstBeat flag
secondBeat = true; // set the second beat flag
return; // IBI value is unreliable so discard it
}
// keep a running total of the last 10 IBI values
word runningTotal = 0; // clear the runningTotal variable
for(int i=0; i<=8; i++){ // shift data in the rate array
rate[i] = rate[i+1]; // and drop the oldest IBI value
runningTotal += rate[i]; // add up the 9 oldest IBI values
}
rate[9] = IBI; // add the latest IBI to the rate array
runningTotal += rate[9]; // add the latest IBI to runningTotal
runningTotal /= 10; // average the last 10 IBI values
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
QS = true; // set Quantified Self flag
// QS FLAG IS NOT CLEARED INSIDE THIS ISR
}
}
if (Signal < thresh && Pulse == true){ // when the values are going down, the beat is over
digitalWrite(blinkPin,LOW); // turn off pin 13 LED
Pulse = false; // reset the Pulse flag so we can do it again
amp = P - T; // get amplitude of the pulse wave
thresh = amp/2 + T; // set thresh at 50% of the amplitude
P = thresh; // reset these for next time
T = thresh;
}
if (N > 2500){ // if 2.5 seconds go by without a beat
thresh = 512; // set thresh default
P = 512; // set P default
T = 512; // set T default
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
firstBeat = true; // set these to avoid noise
secondBeat = false; // when we get the heartbeat back
}
serialOutput() ;
if (QS == true){ // A Heartbeat Was Found
// BPM and IBI have been Determined
// Quantified Self "QS" true when arduino finds a heartbeat
// fadeRate = 255; // Makes the LED Fade Effect Happen
// Set 'fadeRate' Variable to 255 to fade LED with pulse
serialOutputWhenBeatHappens(); // A Beat Happened, Output that to serial.
QS = false; // reset the Quantified Self flag for next time
}
// ledFadeToBeat(); // Makes the LED Fade Effect Happen
delay(20); // take a break
}
/*void ledFadeToBeat(){
fadeRate -= 15; // set LED fade value
fadeRate = constrain(fadeRate,0,255); // keep LED fade value from going into negative numbers!
//analogWrite(fadePin,fadeRate); // fade LED
}
*/
SerialHandling file:
//////////
///////// All Serial Handling Code,
///////// It's Changeable with the 'serialVisual' variable
///////// Set it to 'true' or 'false' when it's declared at start of code.
/////////
void serialOutput(){ // Decide How To Output Serial.
if (serialVisual == true){
arduinoSerialMonitorVisual('-', Signal); // goes to function that makes Serial Monitor Visualizer
} else{
sendDataToSerial('S', Signal); // goes to sendDataToSerial function
}
}
// Decides How To OutPut BPM and IBI Data
void serialOutputWhenBeatHappens(){
if (serialVisual == true){ // Code to Make the Serial Monitor Visualizer Work
Serial.print("*** Heart-Beat Happened *** "); //ASCII Art Madness
Serial.print("BPM: ");
Serial.print(BPM);
Serial.print(" ");
} else{
sendDataToSerial('B',BPM); // send heart rate with a 'B' prefix
sendDataToSerial('Q',IBI); // send time between beats with a 'Q' prefix
}
}
// Sends Data to Pulse Sensor Processing App, Native Mac App, or Third-party Serial Readers.
void sendDataToSerial(char symbol, int data ){
Serial.print(symbol);
Serial.println(data);
}
// Code to Make the Serial Monitor Visualizer Work
void arduinoSerialMonitorVisual(char symbol, int data ){
const int sensorMin = 0; // sensor minimum, discovered through experiment
const int sensorMax = 1024; // sensor maximum, discovered through experiment
int sensorReading = data;
// map the sensor range to a range of 12 options:
int range = map(sensorReading, sensorMin, sensorMax, 0, 11);
// do something different depending on the
// range value:
switch (range) {
case 0:
Serial.println(""); /////ASCII Art Madness
break;
case 1:
Serial.println("---");
break;
case 2:
Serial.println("------");
break;
case 3:
Serial.println("---------");
break;
case 4:
Serial.println("------------");
break;
case 5:
Serial.println("--------------|-");
break;
case 6:
Serial.println("--------------|---");
break;
case 7:
Serial.println("--------------|-------");
break;
case 8:
Serial.println("--------------|----------");
break;
case 9:
Serial.println("--------------|----------------");
break;
case 10:
Serial.println("--------------|-------------------");
break;
case 11:
Serial.println("--------------|-----------------------");
break;
}
}
Serial monitor only displays these numbers that are constantly changing:
S797
S813
S798
S811
S822
S802
S821
S819
S818
S806
S797
S797
S812
S816
S794
S820
S821
S808
S816
S820
S803
S810
S811
S806
S822
S817
S811
S822
S800
S820
S799
S800
S815
S809
S820
S822
S821
S809
S796
S821
S816
S798
S820
All in all, I was hoping if someone could help me with the code to calculate BPM in a more basic/ easy manner without having to deal with visualization of the BPM.
Sorry for the long post, thanks!
This is how i did it, to overpass the absence of interrupt on my board:
#define pulsePin A0
// VARIABLES
int rate[10];
unsigned long sampleCounter = 0;
unsigned long lastBeatTime = 0;
unsigned long lastTime = 0, N;
int BPM = 0;
int IBI = 0;
int P = 512;
int T = 512;
int thresh = 512;
int amp = 100;
int Signal;
boolean Pulse = false;
boolean firstBeat = true;
boolean secondBeat = true;
boolean QS = false;
void setup() {
Serial.begin(9600);
}
void loop() {
if (QS == true) {
Serial.println("BPM: "+ String(BPM));
QS = false;
} else if (millis() >= (lastTime + 2)) {
readPulse();
lastTime = millis();
}
}
void readPulse() {
Signal = analogRead(pulsePin);
sampleCounter += 2;
int N = sampleCounter - lastBeatTime;
detectSetHighLow();
if (N > 250) {
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) )
pulseDetected();
}
if (Signal < thresh && Pulse == true) {
Pulse = false;
amp = P - T;
thresh = amp / 2 + T;
P = thresh;
T = thresh;
}
if (N > 2500) {
thresh = 512;
P = 512;
T = 512;
lastBeatTime = sampleCounter;
firstBeat = true;
secondBeat = true;
}
}
void detectSetHighLow() {
if (Signal < thresh && N > (IBI / 5) * 3) {
if (Signal < T) {
T = Signal;
}
}
if (Signal > thresh && Signal > P) {
P = Signal;
}
}
void pulseDetected() {
Pulse = true;
IBI = sampleCounter - lastBeatTime;
lastBeatTime = sampleCounter;
if (firstBeat) {
firstBeat = false;
return;
}
if (secondBeat) {
secondBeat = false;
for (int i = 0; i <= 9; i++) {
rate[i] = IBI;
}
}
word runningTotal = 0;
for (int i = 0; i <= 8; i++) {
rate[i] = rate[i + 1];
runningTotal += rate[i];
}
rate[9] = IBI;
runningTotal += rate[9];
runningTotal /= 10;
BPM = 60000 / runningTotal;
QS = true;
}
The sensor I used is a DFRobot Piezo Disc Vibration Sensor Module.
void setup() {
Serial.begin(57600);
}
void loop() {
int avg = 0;
for(int i=0;i<64;i++){
avg+=analogRead(A2);
}
Serial.println(avg/64,DEC);
delay(5);
}
void setup() {
Serial.begin(57600);
}
void loop() {
int avg = 0;
for(int i=0;i<64;i++){
avg+=analogRead(A2);
}
Serial.println(avg/64,DEC);
delay(5);
}
When defining an arbitrary threshold (e.g. half of the maximum measured value), the rising edge of the signal will pass the threshold once per heartbeat, making measuring it as simple as measuring the time between two successive beats. For less jitter, I chose to calculate the heart rate using the average of the last 16 time differences between the beats.
code that calculates the heart rate and outputs the average heart rate over the last 16 beats at every beat:
int threshold = 60;
int oldvalue = 0;
int newvalue = 0;
unsigned long oldmillis = 0;
unsigned long newmillis = 0;
int cnt = 0;
int timings[16];
void setup() {
Serial.begin(57600);
}
void loop() {
oldvalue = newvalue;
newvalue = 0;
for(int i=0; i<64; i++){ // Average over 16 measurements
newvalue += analogRead(A2);
}
newvalue = newvalue/64;
// find triggering edge
if(oldvalue<threshold && newvalue>=threshold){
oldmillis = newmillis;
newmillis = millis();
// fill in the current time difference in ringbuffer
timings[cnt%16]= (int)(newmillis-oldmillis);
int totalmillis = 0;
// calculate average of the last 16 time differences
for(int i=0;i<16;i++){
totalmillis += timings[i];
}
// calculate heart rate
int heartrate = 60000/(totalmillis/16);
Serial.println(heartrate,DEC);
cnt++;
}
delay(5);
}
int threshold = 60;
int oldvalue = 0;
int newvalue = 0;
unsigned long oldmillis = 0;
unsigned long newmillis = 0;
int cnt = 0;
int timings[16];
void setup() {
Serial.begin(57600);
}
void loop() {
oldvalue = newvalue;
newvalue = 0;
for(int i=0; i<64; i++){ // Average over 16 measurements
newvalue += analogRead(A2);
}
newvalue = newvalue/64;
// find triggering edge
if(oldvalue<threshold && newvalue>=threshold){
oldmillis = newmillis;
newmillis = millis();
// fill in the current time difference in ringbuffer
timings[cnt%16]= (int)(newmillis-oldmillis);
int totalmillis = 0;
// calculate average of the last 16 time differences
for(int i=0;i<16;i++){
totalmillis += timings[i];
}
// calculate heart rate
int heartrate = 60000/(totalmillis/16);
Serial.println(heartrate,DEC);
cnt++;
}
delay(5);
}
If you would like to try this at home, just connect the analog output of the sensor to A2 (or change the code) and connect the 5V and GND lines of the sensor.

How to know Arduino Sampling Rate

/*
fft_adc_serial.pde
guest openmusiclabs.com 7.7.14
example sketch for testing the fft library.
it takes in data on ADC0 (Analog0) and processes them
with the fft. the data is sent out over the serial
port at 115.2kb.
*/
#define LOG_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#include <FFT.h> // include the library
unsigned long time;
void setup() {
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}
void loop() {
while(1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while(!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fft_input[i] = k; // put real data into even bins
Serial.print("input ");
Serial.print(i);
Seirla.print(" = ");
Serial.println(k);
fft_input[i+1] = 0; // set odd bins to 0
}
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_log(); // take the output of the fft
sei();
Serial.println("start");
for (byte i = 0 ; i < FFT_N/2 ; i++) {
Serial.print("\t output");
Serial.print(i);
Serial.println(fft_log_out[i]); // send out the data
}
}
}
Im using this FFT example code for FFT
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while(!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fft_input[i] = k; // put real data into even bins
Serial.print("input ");
Serial.print(i);
Seirla.print(" = ");
Serial.println(k);
fft_input[i+1] = 0; // set odd bins to 0
}
in this input part How much input time period?
Theres no delay(); function in this example
while(!(ADCSRA & 0x10)); // wait for adc to be ready
this line work like delay() function? and this function how long wait for Analog0?
The sample rate is set in wiring.c:
https://code.google.com/p/arduino/source/browse/trunk/hardware/cores/arduino/wiring.c?r=565#210
So on an 16mHz arduino has a maximum sample rate of at 9600hz, but the real sample rate highly depends on on the delay you have between conversions.
As your baud rate is pretty high and you don't do a lot of calculation it should somehow be next to 9600hz.
update:
there's a more accurate answer here: https://arduino.stackexchange.com/a/701

Last chunk of data truncated on arduino serial data

I have an arduino taking serial input and will turn on the leds. The code is below.
I have a strange problem that when I send multiples of 120x bytes e.g., 240, 480 the last 120 bytes never get read completely.
I see on the serial monitor 120 120 120 81 if I send 480 bytes of data. Could anyone point out the mistake?
#include "FastLED.h"
#define DATA_PIN 6
#define NUM_LEDS 40
byte colors[120];
CRGB leds[NUM_LEDS];
void setup(){
FastLED.addLeds<NEOPIXEL, DATA_PIN, RGB>(leds, NUM_LEDS);
Serial.begin(115200);
}
void loop(){
if (Serial.available()){
int i =0;
char incomingByte;
while(1) {
incomingByte = Serial.readBytes((char *)colors,120);
break;
}
Serial.print(incomingByte);
for(i=0;i<NUM_LEDS ;i++){
leds[i].green = colors[i];
leds[i].red = colors[i+1];
leds[i].blue = colors[i+2];
}
if(incomingByte==0x78){
FastLED.show();
}
}
}
your code is flawed in different ways.
First, please remove the useless use of while(1) {…; break;}, it's just adding an overhead and adds nothing to your algorithm.
Otherwise, your code is not working well because, I guess, at some point there's a lag happening in the serial communication that causes the read to timeout. Let's have a look at source code.
First, you take the readBytes() function. All it does is:
size_t Stream::readBytes(char *buffer, size_t length)
{
size_t count = 0;
while (count < length) {
int c = timedRead();
if (c < 0) break;
*buffer++ = (char)c;
count++;
}
return count;
}
i.e. it iterates length times over the blocking read function. But it breaks if that function's return value is less than zero, returning less than length bytes. So that what's happening to get less than length, so let's have a look at timedRead():
int Stream::timedRead()
{
int c;
_startMillis = millis();
do {
c = read();
if (c >= 0) return c;
} while(millis() - _startMillis < _timeout);
return -1; // -1 indicates timeout
}
what happens here, is that if the read succeeds, it returns the read value, otherwise it loops until timeout has passed, and returns -1, which will end readBytes immediately. The default value for the timeout is 1000ms, though you can make that value higher by using Serial.setTimeout(5000); in your setup() function.
Though you have nothing to earn by using the blocking readBytes() function. So you'd better instead write your loop so you read the values, and trigger an event only once all values have been read:
#define NB_COLORS 120
void loop() {
static byte colors[NB_COLORS];
static int colors_index=0;
if (colors_index < NB_COLORS) {
// let's read only one byte
colors[colors_index] = Serial.read();
// if a byte has been read, increment the index
if (colors[colors_index] != -1)
++colors_index;
} else {
// reset the index to start over
colors_index = 0;
// should'nt you iterate 3 by 3, i.e. having i=i+3 instead of i++ ?
for(i=0;i<NUM_LEDS ;i++){
leds[i].green = colors[i];
leds[i].red = colors[i+1];
leds[i].blue = colors[i+2];
}
FastLED.show();
}
}
HTH

Resources