PWM for LED dimming unusual behavior - c

I made a function, where PWM signal is generated at the output (PORTD) without usage of PWM control registers inside PIC microcontroller (PIC18F452). In order to slowly dim LED connected at the output, I was trying to increase the time needed for pulse to advance from 0% of one period to 100% of one period of square wave, while having square wave frequency constant. Everything should go as planned, except that second parameter being passed into pwm function, somehow resets, when going from 655 to 666 (that is, when duty cycle is at 65%). After this event, value being passed to pwm function proceeds from 0. Where as it should not reset at transition from 655 to 656 but at transition from 1000 to 1001.
void main(void) {
TRISD = 0x00; //port D set as output
LATD = 0x00; //port D output set LOW
unsigned int width = 1000; // length of T_on + T_off
unsigned int j;
unsigned int res;
while(1){
for (j = 1; j <= width; j++){
res = (unsigned int)((j*100)/width);
pwm(&LATD, res);
}
}
return;
}
void pwm(volatile unsigned char *lat, unsigned int cycle){
if(cycle > 100){ // reset the "cycle"
cycle = 100;
}
unsigned int i = 1;
while(i<=(cycle)){ // T_on
*lat = 0x01;
i++;
}
unsigned int j = 100-cycle;
while(j){ // T_off
*lat = 0;
j--;
}
return;
}
As for the program itself, it should work like so:
second parameter passed into pwm function is the duty cycle (in %) which changes from 0 to 100
with variable "width" the time needed for duty cycle to advance from 0% to 100% is controlled (width = 100 represents fastest time and everything above that is considered gradually slower time from 0% to 100%)
expression ((j*100)/width) serves as step variable inside "while" loop inside pwm function:
if width = 100, step is increased every increment of "j"
if width = 1000, step is increased every 10 increments of "j",
etc.
PORTD is passed into function as its address, whereas in function pwm, this address is operated via pointer variable lat
As for the problem itself, I could only assume two possibilities: either data type of second parameter of function pwm is incorrect or there is some unknown limitation within PIC microprocessor.
Also, here are definitions of configuration bits (device specific registers) of PIC, located int header file included in this program: https://imgur.com/a/UDYifgN
This is, how the program should operate: https://vimeo.com/488207207
This is, how the program currently operates: https://vimeo.com/488207746

The problem is a 16 Bit overflow:
res = (unsigned int)((j*100)/width);
if j is greater then 655 the result of the calculation j*100 is greater 16 Bit. Switch this to 32 Bit. Or easier make your loop from 0...100 for res.
e.g.
for (res = 0; res <= 100; res++){
pwm(&LATD, res);
}

Related

Track Minimum Value of a Signal In Real Time

I'm currently tracking the analog value of a photodetector coming into my system. The signal itself is cleaned, filtered (low pass and high pass), and amplified in hardware before coming into my system. The signal has a small amount of DC walk to it, which is giving me some trouble. I've attempted to just move the min up by 1% every 50 reads of the ADC,but it adds more noise than I'd like to my signal. Here's a snapshot of what I'm pulling in below (blue = signal, max/min average = green, red = min) The spikes in the red signal can be ignored that's something I'm doing to say when a certain condition is met.
Right now my function for tracking min is this:
//Determine is value is outside max or min
if(data > max) max = data;
if(data < min) min = data;
//Reset function to bring the bounds in every 50 cycles
if(rstCntr>=50){
rstCntr=0;
max = max/1.01;
min = min*1.01;
if(min <= 1200) min = 1200;
if(max >= 1900) max = 1900;
}
That works fine except when I do that 1% correction to make sure we are still tracking the signal it throws other functions off which rely on the average value and the min value. My objective is to determine:
On the negative slope of the signal
Data coming in is less than the average
Data coming in is 5% above the minimum
It is really #3 that is driving everything else. There is enough slack in the other two that they aren't that affected.
Any suggestions for a better way to track the max and min in real-time than what I'm doing?
EDIT: Per comment by ryyker: here is additional information and reproducible example code
Need more clearly described: I'm reading an analog signal approximately once every 2ms and determining whether that signal has crossed a threshold just above the minimum value of the analog signal. The signal has some DC walk in it which doesn't allow me to simply set the lowest value seen since power-on as the minimum value.
The question: On a reading-by-reading basis, how can I track the min of a signal that doesn't have a consistent minimum value?
int main(void) {
while (1)
{
//******************************************************************************
//** Process analog sensor data, calculate HR, and trigger solenoids
//** At some point this should probably be moved to a function call in System.c,
//** but I don't want to mess with it right now since it works (Adam 11/23/2022)
//******************************************************************************
//Read Analog Data for Sensor
data = ADC1_ReadChannel(7);
//Buffer the sensor data for peak/valley detection
for(int buf=3;buf>0;buf--){
dataBuffer[buf] = dataBuffer[buf-1];
}
dataBuffer[0] = data;
//Look for a valley
//Considered a valley is the 3 most recent data points are increasing
//This helps avoid noise in the signal
uint8_t count = 0;
for(int buf=0;buf<3;buf++) {
if(dataBuffer[buf]>dataBuffer[buf+1]) count++;
}
if(count >= 3) currentSlope = true; //if the last 3 points are increasing, we just passed a valley
else currentSlope = false; //not a valley
// Track the data stream max and min to calculate a signal average
// The signal average is used to determine when we are on the bottom end of the waveform.
if(data > max) max = data;
if(data < min) min = data;
if(rstCntr>=50){ //Make sure we are tracking the signal by moving min and max in every 200 samples
rstCntr=0;
max = max/1.01;
min = min*1.01;
if(min <= 1200) min = 1200; //average*.5; //Probably finger was removed from sensor, move back up
if(max >= 1900) max = 1900; //Need to see if this really works consistently
}
rstCntr++;
average = ((uint16_t)min+(uint16_t)max)/2;
trigger = min; //Variable is only used for debug output, resetting each time around
if(data < average &&
currentSlope == false && //falling edge of signal
data <= (((average-min)*.03)+min) && //Threshold above the min
{
FireSolenoids();
}
}
return 1;
}
EDIT2:
Here is what I'm seeing using the code posted by ryyker below. The green line is what I'm using as my threshold, which works fairly well, but you can see max and min don't track the signal.
EDIT3:
Update with edited min/max code. Not seeing it ever reach the max. Might be the window size is too small (set to 40 in this image).
EDIT4:
Just for extra clarity, I'm restating my objectives once again, hopefully to make things as clear as possible. It might be helpful to provide a bit more context around what the information is used for, so I'm doing that also.
Description:
I have an analog sensor which measures a periodic signal in the range of 0.6Hz to 2Hz. The signal's periodicity is not consistent from pulsewave to pulsewave. It varies +/- 20%. The periodic signal is used to determine the timing of when a valve is opened and closed.
Objective:
The valve needs to be opened a constant number of ms after the signal peak is reached, but the time it physically takes the valve to move is much longer than this constant number. In other words, opening the valve when the peak is detected means the valve opens too late.
Similar to 1, using the valley of the signal is also not enough time for the valve to physically open.
The periodicity of the signal varies enough that it isn't possible to use the peak-to-peak time from the previous two pulsewaves to determine when to open the valve.
I need to consistently determine a point on the negative sloped portion of the pulsewave to use as the trigger for opening the valve.
Approach:
My approach is to measure the minimum and maximum of the signal and then set a threshold above the minimum which I can use to determine the time the open the valve.
My thought is that by setting some constant percentage above the minimum will get me to a consistent location on the negative sloped which can be used to open the valve.
"On a reading-by-reading basis, how can I track the min of a signal that doesn't have a consistent minimum value?"
By putting each discrete signal sample through a moving window filter, and performing statistical operations on the window as it moves, standard deviation can be extracted (following mean and variance) which can then be combined with mean to determine the minimum allowed value for each point of a particular waveform. This assumes noise contribution is known and consistent.
The following implementation is one way to consider.
in header file or top of .c
//support for stats() function
#define WND_SZ 10;
int wnd_sz = WND_SZ;
typedef struct stat_s{
double arr[10];
double min; //mean - std_dev
double max; //mean + std_dev
double mean; //running
double variance;//running
double std_dev; //running
} stat_s;
void stats(double in, stat_s *out);
in .c (edit to change max and min)
// void stats(double in, stat_s *out)
// Used to monitor a continuous stream of sensor values.
// Accepts series of measurement values from a sensor,
// Each new input value is stored in array element [i%wnd_sz]
// where wnd_sz is the width of the sample array.
// instantaneous values for max and min as well as
// moving values of mean, variance, and standard deviation
// are derived once per input
void ISL_UTIL stats(double in, stat_s *out)
{
double sum = 0, sum1 = 0;
int j = 0;
static int i = 0;
out->arr[i%wnd_sz] = in;//array index values cycle within window size
//sum all elements of moving window array
for(j = 0; j < wnd_sz; j++)
sum += out->arr[j];
//compute mean
out->mean = sum / (double)wnd_sz;
//sum squares of diff between each element and mean
for (j = 0; j < wnd_sz; j++)
sum1 += pow((out->arr[j] - out->mean), 2);
//compute variance
out->variance = sum1 / (double)wnd_sz;
//compute standard deviation
out->std_dev = sqrt(out->variance);
//EDIT here:
//mean +/- std_dev
out->max = out->mean + out->std_dev;
out->min = out->mean - out->std_dev;
//END EDIT
//prevent overflow for long running sessions.
i = (i == 1000) ? 0 : ++i;
}
int main(void)
{
stat_s s = {0};
bool running = true;
double val = 0.0;
while(running)
{
//read one sample from some sensor
val = someSensor();
stats(val, &s);
// collect instantaneous and running data from s
// into variables here
if(some exit condition) break
}
return 0;
}
Using this code with 1000 bounded pseudo random values, mean is surrounded with traces depicting mean + std_dev and mean - std_dev As std_dev becomes smaller over time, the traces converge toward the mean signal:
Note: I used the following in my test code to produce data arrays of a signal with constant amplitude added to injected noise that diminishes in amplitude over time.
void gen_data(int samples)
{
srand(clock());
int i = 0;
int plotHandle[6] = {0};
stat_s s = {0};
double arr[5][samples];
memset(arr, 0, sizeof arr);
for(i=0; i < samples; i++)//simulate ongoing sampling of sensor
{
s.arr[i%wnd_sz] = 50 + rand()%100;
if(i<.20*samples) s.arr[i%wnd_sz] = 50 + rand()%100;
else if(i<.40*samples) s.arr[i%wnd_sz] = 50 + rand()%50;
else if(i<.60*samples) s.arr[i%wnd_sz] = 50 + rand()%25;
else if(i<.80*samples) s.arr[i%wnd_sz] = 50 + rand()%12;
else s.arr[i%wnd_sz] = 50 + rand()%6;
stats(s.arr[i%wnd_sz], &s);
arr[0][i] = s.mean;
arr[1][i] = s.variance;
arr[2][i] = s.std_dev;
arr[3][i] = s.min;
arr[4][i] = s.max;
}
//
Plotting algorithms deleted for brevity.
}

Use Arduino to generate sin wave modulated by gold code

I am trying to use Arduino to generate sin wave and gold code is used to determine when the wave will have a phase shift. However, the output is not performed as I expected. Sometimes, it does not occur any phase shift for consequent ten cycles, which should not happen according to our definition of gold code array. Which part of the code could I try to fix the problem?
int gold_code[]={1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,1,-1,-1,1,1,-1,-1, 1,1,-1,1,1,1,-1,1,-1,1,1,-1,1,1,-1,-1,-1,-1,-1,1,1,-1,-1,1,1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,1,1,1,-1,1,1,-1, -1, 1, 1,1,-1,-1,1,-1,-1,1,1,1};
void loop()
{
int n = sizeof(gold_code)/sizeof(gold_code[0]);
byte bsin[128];
int it;
unsigned long tm0;
unsigned int tm;
for(int i=0;i<128;i++)
{
bsin[i] = 8 + (int)(0.5 + 7.*sin( (double)i*3.14159265/64.));
}
int count=0;
int count1=0;
Serial.println(n);
tm0 = micros();
while(true)
{
tm = micros() - tm0;
if(tm > 511)
{
tm0 = tm0+512;
tm -= 512;
count++;
//Serial.println(gold_code[count%n]);
}
tm = (tm >> 2) ;
if(gold_code[count%n]==0){
PORTB = bsin[tm];
}
else{
PORTB = 16-bsin[tm];
}
}
}
The variable count eventually overflows and becomes negative. This, in conjunction with the modulo operation is a sign (pun intended) of a disaster waiting to happen.
Use a different method for limiting the value of count to the bounds of your gold_codearray.
You should expect a significant increase in frequency after removing the modulo operation, so you may need to add some pacing to your loop.
The pacing in your loop is wrong. Variable count increments 4 times as fast as your phase counter.
Also, #Edward Karak raises a valid point. To do a proper phase shift, you should add (or subtract) from tm, not from the sin value.
[EDIT] I was not quite happy with the way the phase shift is handled. It just doesn't feel right to advance the gold counter at the same pace as the phase counter. So I added a separate timer for that. Advances in the gold_code array every 8 microseconds for now, but you can change it to whatever you're supposed to have.
as in:
unsigned char tm0 = 0;
unsigned char tm0_gold = 0;
const unsigned char N = sizeof(gold_code) / sizeof(gold_code[0]);
unsigned char phase = 0;
for(;;)
{
// pacing for a stable frequency
unsigned char mic = micros() & 0xFF;
if (mic - tm0_gold >= 8)
{
tm0_gold = mic;
// compute and do the phase shift
if (++count >= N)
count -= N;
if (gold_code[count] > 0) // you have == 0 in your code, but that doesn't make sense.
phase += 16; // I can't make any sense of what you are trying to do,
// so I'll just add 45° of phase for each positive value
// you'll probably want to make your own test here
}
if (mic - tm0 >= 4)
{
tm0 = mic;
// advance the phase. keep within the LUT bounds
if (++phase >= 128)
phase -= 128;
// output
PORTB = bsin[phase];
}
}
For frequency stability, you will want to move the sine generator to a timer interrupt, after debugging. This will free up your loop() to do some extra control.
I don't quite understand why count increments as fast as the phase counter.
You may want to increment count at a slower pace to reach your goal.

AVR interrupt's variable updated in main

Using a 8-bit AVR micro, I arrived to a simple situation which might not be that easy to solve.
Consider the following snippet:
static volatile uint8_t counter;
//fires often and I need all the values of the counter.
void isr(void) {
counter++;
}
int main (void) {
while(1) {
send_uart(counter);
counter = 0;
delay_ms(1000); //1 sec pause
}
return 0;
}
1.) It can happen that send_uart is followed by an isr which increases the counter, and then the next statement zeroes it out.
Therefore I'll miss one data from the counter.
2.) If I use ATOMIC_BLOCK(ATOMIC_RESTORESTATE) in the main fn, I can avoid the problems declared in (1), but it can happen that I miss an ISR because in this case INTs are disabled for a short time.
Is there a better way to pass information from the main fn to ISR?
If the counter is sampled rather than reset, there won't be any timing issues. Increments happening while sending will be accounted for in the next iteration. The unsigned data type of the counter variables will guarantee well-defined overflow behavior.
uint8_t cs = 0; // counter sample at time of sending
uint8_t n = 0; // counter as last reported
while (1) {
cs = counter; // sample the counter
send_uart((uint8_t)(cs - n)); // report difference between sample and last time
n = cs; // update last reported value
delay_ms(1000);
}

calculation in C for C2000 device

I'm having some trouble with my C code. I have an ADC which will be used to determine whether to shut down (trip zone) the PWM which I'm using. But my calculation seem to not work as intended, because the ADC shuts down the PWM at the wrong voltage levels. I initiate my variables as:
float32 current = 5;
Uint16 shutdown = 0;
and then I calculate as:
// Save the ADC input to variable
adc_info->adc_result0 = AdcRegs.ADCRESULT0>>4; //bit shift 4 steps because adcresult0 is effectively a 12-bit register not 16-bit, ADCRESULT0 defined as Uint16
current = -3.462*((adc_info->adc_result0/1365) - 2.8);
// Evaluate if too high or too low
if(current > 9 || current < 1)
{
shutdown = 1;
}
else
{
shutdown = 0;
}
after which I use this if statement:
if(shutdown == 1)
{
EALLOW; // EALLOW protected register
EPwm1Regs.TZFRC.bit.OST = 1; // Force a one-shot trip-zone interrupt
EDIS; // end write to EALLOW protected register
}
So I want to trip the PWM if current is above 9 or below 1 which should coincide with an adc result of <273 (0x111) and >3428 (0xD64) respectively. The ADC values correspond to the voltages 0.2V and 2.51V respectively. The ADC measure with a 12-bit accuracy between the voltages 0 and 3V.
However, this is not the case. Instead, the trip zone goes off at approximately 1V and 2.97V. So what am I doing wrong?
adc_info->adc_result0/1365
Did you do integer division here while assuming float?
Try this fix:
adc_info->adc_result0/1365.0
Also, the #pmg's suggestion is good. Why spending cycles on calculating the voltage, when you can compare the ADC value immediately against the known bounds?
if (adc_info->adc_result0 < 273 || adc_info->adc_result0 > 3428)
{
shutdown = 1;
}
else
{
shutdown = 0;
}
If you don't want to hardcode the calculated bounds (which is totally understandable), then define them as calculated from values which you'd want to hardcode literally:
#define VOLTAGE_MIN 0.2
#define VOLTAGE_MAX 2.51
#define AREF 3.0
#define ADC_PER_VOLT (4096 / AREF)
#define ADC_MIN (VOLTAGE_MIN * ADC_PER_VOLT) /* 273 */
#define ADC_MAX (VOLTAGE_MAX * ADC_PER_VOLT) /* 3427 */
/* ... */
shutdown = (adcresult < ADC_MIN || adcresult > ADC_MAX) ? 1 : 0;
/* ... */
When you've made sure to grasp the integer division rules in C, consider a little addition to your code style: to always write constant coefficients and divisors with a decimal (to make sure they get a floating point type), e.g. 10.0 instead of 10 — unless you specifically mean integer division with truncation. Sometimes it may also be a good idea to specify float literal precision with appropriate suffix.

Assign delays for 1 ms or 2 ms in C?

I'm using code to configure a simple robot. I'm using WinAVR, and the code used there is similar to C, but without stdio.h libraries and such, so code for simple stuff should be entered manually (for example, converting decimal numbers to hexadecimal numbers is a multiple-step procedure involving ASCII character manipulation).
Example of code used is (just to show you what I'm talking about :) )
.
.
.
DDRA = 0x00;
A = adc(0); // Right-hand sensor
u = A>>4;
l = A&0x0F;
TransmitByte(h[u]);
TransmitByte(h[l]);
TransmitByte(' ');
.
.
.
For some circumstances, I must use WinAVR and cannot external libraries (such as stdio.h). ANYWAY, I want to apply a signal with pulse width of 1 ms or 2 ms via a servo motor. I know what port to set and such; all I need to do is apply a delay to keep that port set before clearing it.
Now I know how to set delays, we should create empty for loops such as:
int value= **??**
for(i = 0; i<value; i++)
;
What value am I supposed to put in "value" for a 1 ms loop ?
Chances are you'll have to calculate a reasonable value, then look at the signal that's generated (e.g., with an oscilloscope) and adjust your value until you hit the right time range. Given that you apparently have a 2:1 margin, you might hit it reasonably close the first time, but I wouldn't be much on it.
For your first approximation, generate an empty loop and count the instruction cycles for one loop, and multiply that by the time for one clock cycle. That should give at least a reasonable approximation of time taken by a single execution of the loop, so dividing the time you need by that should get you into the ballpark for the right number of iterations.
Edit: I should also note, however, that (at least most) AVRs have on-board timers, so you might be able to use them instead. This can 1) let you do other processing and/or 2) reduce power consumption for the duration.
If you do use delay loops, you might want to use AVR-libc's delay loop utilities to handle the details.
If my program is simple enough there is not a need of explicit timer programming, but it should be portable. One of my choices for a defined delay would be AVR Libc's delay function:
#include <delay.h>
_delay_ms (2) // Sleeps 2 ms
Is this going to go to a real robot? All you have is a CPU, no other integrated circuits that can give a measure of time?
If both answers are 'yes', well... if you know the exact timing for the operations, you can use the loop to create precise delays. Output your code to assembly code, and see the exact sequence of instructions used. Then, check the manual of the processor, it'll have that information.
If you need a more precise time value you should employ an interrupt service routine based on an internal timer. Remember a For loop is a blocking instruction, so while it is iterating the rest of your program is blocked. You could set up a timer based ISR with a global variable that counts up by 1 every time the ISR runs. You could then use that variable in an "if statement" to set the width time. Also that core probably supports PWM for use with the RC type servos. So that may be a better route.
This is a really neat little tasker that I use sometimes. It's for an AVR.
************************Header File***********************************
// Scheduler data structure for storing task data
typedef struct
{
// Pointer to task
void (* pTask)(void);
// Initial delay in ticks
unsigned int Delay;
// Periodic interval in ticks
unsigned int Period;
// Runme flag (indicating when the task is due to run)
unsigned char RunMe;
} sTask;
// Function prototypes
//-------------------------------------------------------------------
void SCH_Init_T1(void);
void SCH_Start(void);
// Core scheduler functions
void SCH_Dispatch_Tasks(void);
unsigned char SCH_Add_Task(void (*)(void), const unsigned int, const unsigned int);
unsigned char SCH_Delete_Task(const unsigned char);
// Maximum number of tasks
// MUST BE ADJUSTED FOR EACH NEW PROJECT
#define SCH_MAX_TASKS (1)
************************Header File***********************************
************************C File***********************************
#include "SCH_AVR.h"
#include <avr/io.h>
#include <avr/interrupt.h>
// The array of tasks
sTask SCH_tasks_G[SCH_MAX_TASKS];
/*------------------------------------------------------------------*-
SCH_Dispatch_Tasks()
This is the 'dispatcher' function. When a task (function)
is due to run, SCH_Dispatch_Tasks() will run it.
This function must be called (repeatedly) from the main loop.
-*------------------------------------------------------------------*/
void SCH_Dispatch_Tasks(void)
{
unsigned char Index;
// Dispatches (runs) the next task (if one is ready)
for(Index = 0; Index < SCH_MAX_TASKS; Index++)
{
if((SCH_tasks_G[Index].RunMe > 0) && (SCH_tasks_G[Index].pTask != 0))
{
(*SCH_tasks_G[Index].pTask)(); // Run the task
SCH_tasks_G[Index].RunMe -= 1; // Reset / reduce RunMe flag
// Periodic tasks will automatically run again
// - if this is a 'one shot' task, remove it from the array
if(SCH_tasks_G[Index].Period == 0)
{
SCH_Delete_Task(Index);
}
}
}
}
/*------------------------------------------------------------------*-
SCH_Add_Task()
Causes a task (function) to be executed at regular intervals
or after a user-defined delay
pFunction - The name of the function which is to be scheduled.
NOTE: All scheduled functions must be 'void, void' -
that is, they must take no parameters, and have
a void return type.
DELAY - The interval (TICKS) before the task is first executed
PERIOD - If 'PERIOD' is 0, the function is only called once,
at the time determined by 'DELAY'. If PERIOD is non-zero,
then the function is called repeatedly at an interval
determined by the value of PERIOD (see below for examples
which should help clarify this).
RETURN VALUE:
Returns the position in the task array at which the task has been
added. If the return value is SCH_MAX_TASKS then the task could
not be added to the array (there was insufficient space). If the
return value is < SCH_MAX_TASKS, then the task was added
successfully.
Note: this return value may be required, if a task is
to be subsequently deleted - see SCH_Delete_Task().
EXAMPLES:
Task_ID = SCH_Add_Task(Do_X,1000,0);
Causes the function Do_X() to be executed once after 1000 sch ticks.
Task_ID = SCH_Add_Task(Do_X,0,1000);
Causes the function Do_X() to be executed regularly, every 1000 sch ticks.
Task_ID = SCH_Add_Task(Do_X,300,1000);
Causes the function Do_X() to be executed regularly, every 1000 ticks.
Task will be first executed at T = 300 ticks, then 1300, 2300, etc.
-*------------------------------------------------------------------*/
unsigned char SCH_Add_Task(void (*pFunction)(), const unsigned int DELAY, const unsigned int PERIOD)
{
unsigned char Index = 0;
// First find a gap in the array (if there is one)
while((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
{
Index++;
}
// Have we reached the end of the list?
if(Index == SCH_MAX_TASKS)
{
// Task list is full, return an error code
return SCH_MAX_TASKS;
}
// If we're here, there is a space in the task array
SCH_tasks_G[Index].pTask = pFunction;
SCH_tasks_G[Index].Delay =DELAY;
SCH_tasks_G[Index].Period = PERIOD;
SCH_tasks_G[Index].RunMe = 0;
// return position of task (to allow later deletion)
return Index;
}
/*------------------------------------------------------------------*-
SCH_Delete_Task()
Removes a task from the scheduler. Note that this does
*not* delete the associated function from memory:
it simply means that it is no longer called by the scheduler.
TASK_INDEX - The task index. Provided by SCH_Add_Task().
RETURN VALUE: RETURN_ERROR or RETURN_NORMAL
-*------------------------------------------------------------------*/
unsigned char SCH_Delete_Task(const unsigned char TASK_INDEX)
{
// Return_code can be used for error reporting, NOT USED HERE THOUGH!
unsigned char Return_code = 0;
SCH_tasks_G[TASK_INDEX].pTask = 0;
SCH_tasks_G[TASK_INDEX].Delay = 0;
SCH_tasks_G[TASK_INDEX].Period = 0;
SCH_tasks_G[TASK_INDEX].RunMe = 0;
return Return_code;
}
/*------------------------------------------------------------------*-
SCH_Init_T1()
Scheduler initialisation function. Prepares scheduler
data structures and sets up timer interrupts at required rate.
You must call this function before using the scheduler.
-*------------------------------------------------------------------*/
void SCH_Init_T1(void)
{
unsigned char i;
for(i = 0; i < SCH_MAX_TASKS; i++)
{
SCH_Delete_Task(i);
}
// Set up Timer 1
// Values for 1ms and 10ms ticks are provided for various crystals
OCR1A = 15000; // 10ms tick, Crystal 12 MHz
//OCR1A = 20000; // 10ms tick, Crystal 16 MHz
//OCR1A = 12500; // 10ms tick, Crystal 10 MHz
//OCR1A = 10000; // 10ms tick, Crystal 8 MHz
//OCR1A = 2000; // 1ms tick, Crystal 16 MHz
//OCR1A = 1500; // 1ms tick, Crystal 12 MHz
//OCR1A = 1250; // 1ms tick, Crystal 10 MHz
//OCR1A = 1000; // 1ms tick, Crystal 8 MHz
TCCR1B = (1 << CS11) | (1 << WGM12); // Timer clock = system clock/8
TIMSK |= 1 << OCIE1A; //Timer 1 Output Compare A Match Interrupt Enable
}
/*------------------------------------------------------------------*-
SCH_Start()
Starts the scheduler, by enabling interrupts.
NOTE: Usually called after all regular tasks are added,
to keep the tasks synchronised.
NOTE: ONLY THE SCHEDULER INTERRUPT SHOULD BE ENABLED!!!
-*------------------------------------------------------------------*/
void SCH_Start(void)
{
sei();
}
/*------------------------------------------------------------------*-
SCH_Update
This is the scheduler ISR. It is called at a rate
determined by the timer settings in SCH_Init_T1().
-*------------------------------------------------------------------*/
ISR(TIMER1_COMPA_vect)
{
unsigned char Index;
for(Index = 0; Index < SCH_MAX_TASKS; Index++)
{
// Check if there is a task at this location
if(SCH_tasks_G[Index].pTask)
{
if(SCH_tasks_G[Index].Delay == 0)
{
// The task is due to run, Inc. the 'RunMe' flag
SCH_tasks_G[Index].RunMe += 1;
if(SCH_tasks_G[Index].Period)
{
// Schedule periodic tasks to run again
SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Period;
SCH_tasks_G[Index].Delay -= 1;
}
}
else
{
// Not yet ready to run: just decrement the delay
SCH_tasks_G[Index].Delay -= 1;
}
}
}
}
// ------------------------------------------------------------------
************************C File***********************************
Most ATmega AVR chips, which are commonly used to make simple robots, have a feature known as pulse-width modulation (PWM) that can be used to control servos. This blog post might serve as a quick introduction to controlling servos using PWM. If you were to look at the Arduino platform's servo control library, you would find that it also uses PWM.
This might be a better choice than relying on running a loop a constant number of times as changes to compiler optimization flags and the chip's clock speed could potentially break such a simple delay function.
You should almost certainly have an interrupt configured to run code at a predictable interval. If you look in the example programs supplied with your CPU, you'll probably find an example of such.
Typically, one will use a word/longword of memory to hold a timer, which will be incremented each interrupt. If your timer interrupt runs 10,000 times/second and increments "interrupt_counter" by one each time, a 'wait 1 ms' routine could look like:
extern volatile unsigned long interrupt_counter;
unsigned long temp_value = interrupt_counter;
do {} while(10 > (interrupt_counter - temp_value));
/* Would reverse operands above and use less-than if this weren't HTML. */
Note that as written the code will wait between 900 µs and 1000 µs. If one changed the comparison to greater-or-equal, it would wait between 1000 and 1100. If one needs to do something five times at 1 ms intervals, waiting some arbitrary time up to 1 ms for the first time, one could write the code as:
extern volatile unsigned long interrupt_counter;
unsigned long temp_value = interrupt_counter;
for (int i=0; 5>i; i++)
{
do {} while(!((temp_value - interrupt_counter) & 0x80000000)); /* Wait for underflow */
temp_value += 10;
do_action_thing();
}
This should run the do_something()'s at precise intervals even if they take several hundred microseconds to complete. If they sometimes take over 1 ms, the system will try to run each one at the "proper" time (so if one call takes 1.3 ms and the next one finishes instantly, the following one will happen 700 µs later).

Resources