CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

How to convert a Arduino Code to CCS

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
cokakola



Joined: 23 Apr 2019
Posts: 3

View user's profile Send private message

How to convert a Arduino Code to CCS
PostPosted: Tue Apr 23, 2019 1:26 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Tue Apr 23, 2019 2:14 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Tue Apr 23, 2019 3:06 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Tue Apr 23, 2019 7:16 pm     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Wed Apr 24, 2019 12:23 am     Reply with quote

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: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Wed Apr 24, 2019 4:53 am     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Wed Apr 24, 2019 5:50 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Apr 24, 2019 6:55 am     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Wed Apr 24, 2019 7:16 am     Reply with quote

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

View user's profile Send private message Send e-mail

PostPosted: Mon May 06, 2019 7:10 pm     Reply with quote

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: 9081
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Mon May 06, 2019 7:58 pm     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Mon May 06, 2019 11:45 pm     Reply with quote

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

View user's profile Send private message Send e-mail

PostPosted: Sun May 19, 2019 11:00 pm     Reply with quote

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: 19195

View user's profile Send private message

PostPosted: Sun May 19, 2019 11:29 pm     Reply with quote

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.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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