|
|
View previous topic :: View next topic |
Author |
Message |
cokakola
Joined: 23 Apr 2019 Posts: 3
|
How to convert a Arduino Code to CCS |
Posted: Tue Apr 23, 2019 1:26 pm |
|
|
Hello guys. I found this code in a design used to read a heart rate sensor called KY-039. And I'm having some difficulties converting it to CCS. I am doing the Microcontroller discipline this semester and until then I had seen only arduino.
Code: | #define samp_siz 4
#define rise_threshold 5
// Pulse Monitor Test Script
int sensorPin = 0;
void setup() {
Serial.begin(9600);
}
void loop ()
{
float reads[samp_siz], sum;
long int now, ptr;
float last, reader, start;
float first, second, third, before, print_value;
bool rising;
int rise_count;
int n;
long int last_beat;
for (int i = 0; i < samp_siz; i++)
reads[i] = 0;
sum = 0;
ptr = 0;
while(1)
{
// calculate an average of the sensor
// during a 20 ms period (this will eliminate
// the 50 Hz noise caused by electric light
n = 0;
start = millis();
reader = 0.;
do
{
reader += analogRead (sensorPin);
n++;
now = millis();
}
while (now < start + 20);
reader /= n; // we got an average
// Add the newest measurement to an array
// and subtract the oldest measurement from the array
// to maintain a sum of last measurements
sum -= reads[ptr];
sum += reader;
reads[ptr] = reader;
last = sum / samp_siz;
// now last holds the average of the values in the array
// check for a rising curve (= a heart beat)
if (last > before)
{
rise_count++;
if (!rising && rise_count > rise_threshold)
{
// Ok, we have detected a rising curve, which implies a heartbeat.
// Record the time since last beat, keep track of the two previous
// times (first, second, third) to get a weighed average.
// The rising flag prevents us from detecting the same rise
// more than once.
rising = true;
first = millis() - last_beat;
last_beat = millis();
// Calculate the weighed average of heartbeat rate
// according to the three last beats
print_value = 60000. / (0.4 * first + 0.3 * second + 0.3 * third);
Serial.print(print_value);
Serial.print('\n');
third = second;
second = first;
}
}
else
{
// Ok, the curve is falling
rising = false;
rise_count = 0;
}
before = last;
ptr++;
ptr %= samp_siz;
}
} |
Until then, the biggest difficulty is being in relation to the Arduino millis function, I have not seen any function that does the same thing as Arduino millis does. I've seen something about the get_ticks in CCS, but when I make use of it in my project, after a while the sensor is no longer read. The reading hangs and only comes back when restarting the PIC.
I'm trying to use the code from this site: https://www.hackster.io/Johan_Ha/from-ky-039-to-heart-rate-0abfca
Thank you all. Sorry for any grammatical error in English, I'm a foreigner.[/code] |
|
|
dluu13
Joined: 28 Sep 2018 Posts: 395 Location: Toronto, ON
|
|
Posted: Tue Apr 23, 2019 2:14 pm |
|
|
It would help if you tell us which processor you are using.
You also need to post your code, especially your timer and ADC setups.
If you have an oscilloscope you better test the output on that first to make sure your sensor hookups are correct as well. |
|
|
cokakola
Joined: 23 Apr 2019 Posts: 3
|
|
Posted: Tue Apr 23, 2019 3:06 pm |
|
|
dluu13 wrote: | It would help if you tell us which processor you are using.
You also need to post your code, especially your timer and ADC setups.
If you have an oscilloscope you better test the output on that first to make sure your sensor hookups are correct as well. |
Oh, yeah. Sorry.
Code: | //#include <sensor_projeto.h>
#include <16F877A.h>
#device ADC=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#use delay(crystal=20000000)
#USE TIMER(TIMER=1,TICK=1ms,BITS=16,NOISR)
#define LCD_ENABLE_PIN PIN_B2
#define LCD_RS_PIN PIN_B0
#define LCD_RW_PIN PIN_B1
#define LCD_DATA4 PIN_D4
#define LCD_DATA5 PIN_D5
#define LCD_DATA6 PIN_D6
#define LCD_DATA7 PIN_D7
#define samp_siz 4
#define rise_threshold 5
#include <LCD.C>
float reads[samp_siz], sum;
long int tick_now, ptr;
float last, reader, tick_start;
float first, second, third, before, print_value;
int rising = 0;
int rise_count;
int n;
long int last_beat;
void main()
{
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_INTERNAL);
lcd_init();
for (int i = 0; i < samp_siz; i++){
reads[i] = 0;
}
sum = 0;
ptr = 0;
while(TRUE)
{
n = 0;
tick_start = get_ticks();
reader = 0.;
do
{
reader += read_adc();
n++;
tick_now = get_ticks();
}
while (tick_now < tick_start + 20);
reader /= n;
sum -= reads[ptr];
sum += reader;
reads[ptr] = reader;
last = sum / samp_siz;
if (last > before)
{
rise_count++;
if (rising == 0 && rise_count > rise_threshold)
{
// Ok, we have detected a rising curve, which implies a heartbeat.
// Record the time since last beat, keep track of the two previous
// times (first, second, third) to get a weighed average.
// The rising flag prevents us from detecting the same rise
// more than once.
rising = 1;
first = get_ticks() - last_beat;
last_beat = get_ticks();
// Calculate the weighed average of heartbeat rate
// according to the three last beats
print_value = 60000. / (0.4 * first + 0.3 * second + 0.3 * third);
printf(lcd_putc, "\f%f", print_value);
printf(lcd_putc, "\n");
third = second;
second = first;
}
}
else
{
// Ok, the curve is falling
rising = 0;
rise_count = 0;
}
before = last;
ptr++;
ptr %= samp_siz;
}
} |
That's what I tried to do. |
|
|
dluu13
Joined: 28 Sep 2018 Posts: 395 Location: Toronto, ON
|
|
Posted: Tue Apr 23, 2019 7:16 pm |
|
|
My best guess is that the timer is overflowing when you use get_ticks(). tick_start could be some big number, while tick_now has overflowed into a small number. Then it will take a long time for your while (tick_now < tick_start+20) to be false. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19518
|
|
Posted: Wed Apr 24, 2019 12:23 am |
|
|
Also, don't use terms line 'long int'. This is bad practice on any processor,
since names like 'long' don't actually specify a size. Look up how large a
'long' is on the original processor, and specify the sizes of every type
explicitly.
int32 tick_now, ptr;
Similarly remember an 'int' on the PIC will only be an int8. You'll probably
need to change these to be int16's.
Get into the habit of always using explicit sizes, it'll save a lot of problems
when working with code....
The get_ticks function returns an int32.
setup_adc(ADC_CLOCK_INTERNAL);
Wrong. Read the data sheet. ADC_CLOCK_INTERNAL is not recommended
when the processor speed is above 1MHz, unless you put the processor
to sleep for the conversion. The data sheet has a table showing the
recommended divisors for different clock speeds.
Then you are wasting size and time on many variables. Think about it,
the value from the ADC is always an integer. So 'reader' only needs to
be an int16, not a 'float'. Everything that is fundamentally 'integer', should
be handled with integer variables. Only switch to 'float' when you need to.
Remember the Arduino has a hardware FP unit, so can handle float
numbers almost as easily as integers. In the PIC a FP operation takes
dozens of times longer than an integer operation, and a huge amount
of code space.... |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Wed Apr 24, 2019 4:53 am |
|
|
When I read the original code, I see that ADC is sampled for 20ms. Now if the PIC is running slow, you'll get a few readings, running it at 20MHz and you'll get a LOT of readings. As poited out ,it's best to stay with integers, floats are slow!
Better yet, read the sensor for say 32 or 64 times then do the /32 ( or /64) to get the average. That is light years faster and consistent.
You may want to use the 'Olympic' average as well. when used in groups of 10 samples, it gets rid of the highest, lowest and the 8 remaining are quickly averaged( /8 is FAST)
While I haven't specifically tested your program vs mine, I know that using integers and the above 'tricks' can easily be 100x faster.
Also be sure to filter the sensor output per the datasheet. It's an analog signal, subject to noise. As well, it's best to use a precision reference for the ADC and not VDD. If you use VDD, be sure to add good filtering on the power traces and bypass caps. For the PIC( any micro) to give proper results, the analog signal needs to be 'clean'.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19518
|
|
Posted: Wed Apr 24, 2019 5:50 am |
|
|
There is actually another issue I've just spotted:
Code: |
do
{
reader += read_adc();
n++;
tick_now = get_ticks();
}
while (tick_now < tick_start + 20);
|
This repeatedly reads the ADC, while it is waiting for 20 ticks to happen.
Problem is there is only a tiny delay between the reads. The ADC requires
acquisition time between each successive read. This is not being
given, which will mean the result after the last read will almost certainly
be well below the real voltage (the ADC capacitor slowly discharges
when it is not being refreshed).... |
|
|
cokakola
Joined: 23 Apr 2019 Posts: 3
|
|
Posted: Wed Apr 24, 2019 6:55 am |
|
|
Thank you all for help me with this code. I had no idea how different this was, but i understand a little now. |
|
|
dluu13
Joined: 28 Sep 2018 Posts: 395 Location: Toronto, ON
|
|
Posted: Wed Apr 24, 2019 7:16 am |
|
|
Regarding the variable type stuff, I recommend #include <stdint.h>. That one gives very nice abbreviated signed vs unsigned types like uint8_t vs int8_t for unsigned and signed int8. |
|
|
Ömer Faruk
Joined: 15 Nov 2018 Posts: 42 Location: Çanakkale
|
|
Posted: Mon May 06, 2019 7:10 pm |
|
|
temtronic wrote: | When I read the original code, I see that ADC is sampled for 20ms. Now if the PIC is running slow, you'll get a few readings, running it at 20MHz and you'll get a LOT of readings. As pointed out, it's best to stay with integers, floats are slow!
Better yet, read the sensor for say 32 or 64 times then do the /32 (or /64) to get the average. That is light years faster and consistent.
You may want to use the 'Olympic' average as well. When used in groups of 10 samples, it gets rid of the highest, lowest and the 8 remaining are quickly averaged ( /8 is FAST).
While I haven't specifically tested your program vs mine, I know that using integers and the above 'tricks' can easily be 100x faster.
Also be sure to filter the sensor output per the datasheet. It's an analog signal, subject to noise. As well, it's best to use a precision reference for the ADC and not VDD. If you use VDD, be sure to add good filtering on the power traces and bypass caps. For the PIC (any micro) to give proper results, the analog signal needs to be 'clean'.
Jay |
Hello Jay you talk about Olympic average. I have questions about it. according to my googling there is no big differences between Olympic and simple average. Is it worth it to implement ? And also for implementing we will make a burden for ucontroller to find the lowest and highest point. Could you share a code for this ? |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9229 Location: Greensville,Ontario
|
|
Posted: Mon May 06, 2019 7:58 pm |
|
|
One big advantage to using the Olympic Average is that it removes the highest and lowest readings. Say you're monitoring room temperature (22*C ) and for some unknown reason the sensor gives a bad reading (say 33*C). This bad reading could be caused by EMI, bad wiring, cell phone. The reason doesn't matter, what does matter is that the reading is much above (or below) the 'regular' readings. The Olympic method removes this 33*C reading from the 'group of 8 'good' readings, thus giving you a better, more accurate average.
If you always get readings within 2-3 bits, then it doesn't matter.However if there's a chance for a bad reading then the Olympic Method works very well.
The deadly 737Max8 crashes, sadly, are a great example as to WHY multiple readings from several sensors is critical.
I'm pretty sure the Olympic Average code is here, just use the search feature and see what comes back.
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19518
|
|
Posted: Mon May 06, 2019 11:45 pm |
|
|
The way to olympic average, is to combine it with a conventional
average. So domething like:
Code: |
unsigned int16 average; //current average reading
void update_avg(int16 val)
{
//call this with your readings. After every ten 'average' will update
static unsigned int32 sum;
static unsigned int16 max, min;
static unsigned int8 ctr=0;
if (ctr=0)
{ //first pass update temporary values
max=0;
min=65535;
sum=0;
}
sum+=vsl; //add the value to the sum
if (val>max)
max=val; //update max stored
if (val<min)
min=val; //update min stored
if (++ctr==10)
{ //on tenth pass
sum-=max; //subtract highest seen
sum-=min; //subtract lowest seen
average=(sum+3)/8; //Now average the 8 value sum (4/5
//rounding).
ctr=0; //reset
}
}
|
So you simply call this with each new reading, and after ten readings
'average' will be updated. It is the average of eight readings out of
ten, with the highest and lowest out of the ten being removed. This
is processor efficient (only addition, subtraction, and /8, which are all
quick operations on integers), and rejects any single 'unexpected' value. |
|
|
Ömer Faruk
Joined: 15 Nov 2018 Posts: 42 Location: Çanakkale
|
|
Posted: Sun May 19, 2019 11:00 pm |
|
|
Ttelmah wrote: |
average=(sum+3)/8; //Now average the 8 value sum (4/5 //rounding).
. |
Could you please explain this row in detail.I didnt get it. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19518
|
|
Posted: Sun May 19, 2019 11:29 pm |
|
|
The sum, is a total initially of ten values.
We then subtract the largest recorded and the smallest recorded, so now
have a total of 8 values.
So you could just take sum/8, to get an 'average'.
However, issue.
Imagine you get 2,2,3,3,3,3,3,3.
Total is 21. Divide by 8, gives (in integer), 2.
Yet it is plain that the 'average' would be better represented by 3 here.
After all, 6 of the numbers are 3....
Adding the '3', makes this give 3, rather than 2. It actually results
in what could be called 2/8 rounding. You can use 4 instead of 3, which
then gives 3/8 rounding.
Thing is that integer maths always results in the value 'below'. Rounds
down. To make the result round 'up' for values in the upper part of the range,
simply add an offset as I show. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|