// timer8b.c #include "timer8b.h" #include // save the old vector, set up new vector, zero out counter // set up timer rate int set_up_new_timer(double freq) { unsigned ms,ls; double divideby; if(freq < (1193180.0/65536.0)) return 0; // can't go below this if(freq > 1193180.0) return 0; // or above this divideby = 1193180.0/freq; divideby+=0.5; // causes a round above .5 ms = (unsigned)divideby >> 8; // get upper 8 for ms ls = (unsigned)divideby & 0xff; // mask off lower 8 for ls frequency = 1193180.0/(double)((ms << 8) + ls); oldfreq = 1193180.0/65536.0; MinTime = 1.0/frequency; MaxTime = (pow(2.0, 32.0)/2.0)/frequency; minpulse = 1e-2; // minimum pulse is 10ms - change if desired // Reeder's H-Bridge and 754410 run well at 2.5ms timer_counter = 0L; disable(); // turn off interrupts outp(0x40, ls); // least significant byte of timer count outp(0x40, ms); // most significant byte of timer count old_timer = getvect(0x1c); setvect(0x1c, new_timer); enable(); // turn interrupts back on return 1; } // restore former table entry and rate void restore_old_timer() { disable(); outp(0x40, 0); // least significant byte of timer count outp(0x40, 0); // most significant byte of timer count setvect(0x1c, old_timer); enable(); } // the timer interrupt handler // pwm type: // 0 = unidirctional, no brake // 1 = unidirectional with brake // 2 = pwm line, directional line, no brake // 3 = pwm line, directional line, with brake // 4 = dual pwm lines -- both high = brake // 5 = pwm line and two direction lines as for L298 // 255 = last slot -- leave interrupt new_timer() { int x; disable(); timer_counter++; if(DA_Enabled) // is DA section enabled? { // look for start conversion or data ready status while(!AnnalogChannel[CurrentChannel].status) { CurrentChannel++; if(CurrentChannel > 7) CurrentChannel = 0; } switch(AnnalogChannel[CurrentChannel].status) { case START_CONVERSION: // will be ready at next interrupt, so say so AnnalogChannel[CurrentChannel].status = DATA_READY; break; case DATA_READY: // check eoc even though it's probably already ready while(!(inp(eoc) & 0x80)); // load data into structure AnnalogChannel[CurrentChannel].data = inp(base); // set up for next AnnalogChannel[CurrentChannel].status = START_CONVERSION; CurrentChannel++; // bump up to next channel if(CurrentChannel > 7) CurrentChannel = 0; // this will find a channel that needs // a start even if it's just the above while(AnnalogChannel[CurrentChannel].status != START_CONVERSION) { CurrentChannel++; // get away from above channel if(CurrentChannel > 7) CurrentChannel = 0; } break; } // end switch(AnnalogChannel[CurrentChannel].status) // start a conversion - either the one designated // or the next one found after a data load outp(base + CurrentChannel, 0); } // end if(DA_Enabled) if(OutputControlActive) { for(x=0; x<24; x++) { if(OutputControl[x] == NULL // not set up or off || !OutputControl[x]->direction) continue; if(OutputControl[x]->type == 255) // last slot break; if(OutputControl[x]->direction == 1 || OutputControl[x]->type < 2) { if(OutputControl[x]->ForwardOnCount > 0L) { OutputControl[x]->ForwardOnCount--; if(!OutputControl[x]->ForwardOnCount) { // keep existing bits but remove this one *OutputControl[x]->ForwardPortData &= OutputControl[x]->ForwardOffMask; // put the result in this node's port register outp(OutputControl[x]->ForwardPortAddress, *OutputControl[x]->ForwardPortData); OutputControl[x]->ForwardOffCount = OutputControl[x]->ForwardSetOff; } } // note that this will not decrement as soon as set // above, but will wait until the next interrupt else if(OutputControl[x]->ForwardOffCount > 0L) { OutputControl[x]->ForwardOffCount--; if(!OutputControl[x]->ForwardOffCount) { // keep existing bits and OR this one in *OutputControl[x]->ForwardPortData |= OutputControl[x]->ForwardOnMask; // put the result in this node's port register outp(OutputControl[x]->ForwardPortAddress, *OutputControl[x]->ForwardPortData); OutputControl[x]->ForwardOnCount = OutputControl[x]->ForwardSetOn; } } } // end if(OutputControl[x]->direction == 1 // || OutputControl[x]->type < 2) else if(OutputControl[x]->direction == 2 && OutputControl[x]->type > 1) // types 2,3 and 4 can have reverse { if(OutputControl[x]->ReverseOnCount > 0L) { OutputControl[x]->ReverseOnCount--; if(!OutputControl[x]->ReverseOnCount) { // keep existing bits but remove this one *OutputControl[x]->ReversePortData &= OutputControl[x]->ReverseOffMask; // put the result in this node's port register outp(OutputControl[x]->ReversePortAddress, *OutputControl[x]->ReversePortData); OutputControl[x]->ReverseOffCount = OutputControl[x]->ReverseSetOff; } } // end if(OutputControl[x]->ReverseOnCount > 0L) // note that this will not decrement as soon as set // above, but will wait until the next interrupt else if(OutputControl[x]->ReverseOffCount > 0L) { OutputControl[x]->ReverseOffCount--; if(!OutputControl[x]->ReverseOffCount) { // keep existing bits and OR this one in *OutputControl[x]->ReversePortData |= OutputControl[x]->ReverseOnMask; // put the result in this node's port register outp(OutputControl[x]->ReversePortAddress, *OutputControl[x]->ReversePortData); OutputControl[x]->ReverseOnCount = OutputControl[x]->ReverseSetOn; } } // end else if(OutputControl[x]->ReverseOffCount > 0L) } // end else if(OutputControl[x]->direction == 2 // && OutputControl[x]->type > 1) } // end for(x=0; x<24; x++) } // end if(OutputControlActive) enable(); } // Set up Pulse Width Modulation for an output // // arraynumber is the position in the output control array // // type: // 0 = unidirectional, no brake // 1 = unidirectional with brake // 2 = pwm line, directional line, no brake // 3 = pwm line, directional line, with brake // 4 = dual pwm lines -- both high = brake // 5 = pwm line and two direction lines as for L298 // 255 = last slot -- leave // // Forward and Reverse port numbers are pwm lines for each // // The Direction port number is provided for bridges that have a reverse line // Set to anything if not used // // The Brake port number is provided for circuits that have a brake line // Set to anything if not used // int pwm(int arraynumber, int type, int ForwardPortNumber, int ReversePortNumber, int DirectionPortNumber, int BrakePortNumber, double ForwardOnTime, double ForwardOffTime, double ReverseOnTime, double ReverseOffTime, int StartDirection) { if(StartDirection < 0 || StartDirection > 2) return 0; if(ForwardOnTime <= MinTime || ForwardOnTime >= MaxTime || ForwardOffTime <= MinTime || ForwardOffTime >= MaxTime) return 0; disable(); // no interrupts while setting up if(!ConfigureOutput(arraynumber, type, ForwardPortNumber, ReversePortNumber, DirectionPortNumber, BrakePortNumber)) { enable(); return 0; } OutputControlActive = 1; OutputControl[arraynumber]->ForwardSetOn = (long)((frequency * ForwardOnTime) + 0.5); // round up at .5 OutputControl[arraynumber]->ForwardSetOff = (long)((frequency * ForwardOffTime) + 0.5); OutputControl[arraynumber]->ForwardOnCount = OutputControl[arraynumber]->ForwardSetOn; OutputControl[arraynumber]->ForwardOffCount = OutputControl[arraynumber]->ForwardSetOff; OutputControl[arraynumber]->direction = StartDirection; OutputControl[arraynumber]->type = type; if(!type) // uni directional { enable(); return 1; } if(type == 1 || type == 3) // 1 and 3 have a brake { *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); } if(type > 1) // 2,3,4,5 use reverse pwm line, 2,3,5 use direction line { if(ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime || ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime) { free(OutputControl[arraynumber]); OutputControl[arraynumber] = NULL; enable(); return 0; } OutputControl[arraynumber]->ReverseSetOn = (long)((frequency * ReverseOnTime) + 0.5); // round up at .5 OutputControl[arraynumber]->ReverseOnCount = OutputControl[arraynumber]->ReverseSetOn; OutputControl[arraynumber]->ReverseSetOff = (long)((frequency * ReverseOffTime) + 0.5); OutputControl[arraynumber]->ReverseOffCount = OutputControl[arraynumber]->ReverseSetOff; if(type == 2 || type == 3 || type == 5) // 2,3,5 use a direction line { if(StartDirection == 1) *OutputControl[arraynumber]->DirectionPortData != OutputControl[arraynumber]->DirectionOnMask; // set for forward else if(StartDirection == 2) *OutputControl[arraynumber]->DirectionPortData &= OutputControl[arraynumber]->DirectionOffMask; // clear for reverse outp(OutputControl[arraynumber]->DirectionPortAddress, *OutputControl[arraynumber]->DirectionPortData); if(type == 5) { if(StartDirection == 1) *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake else if(StartDirection == 2) *OutputControl[arraynumber]->BrakePortData |= OutputControl[arraynumber]->BrakeOnMask; // turn on the brake outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); } } } // end if(type > 1) enable(); return 1; } // end int pwm(..) // go forward int Forward(arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; if(OutputControl[arraynumber]->type < 2) return 0; // < 2 is unidirectional if(OutputControl[arraynumber]->type == 255) return 0; // 255 is last node disable(); OutputControl[arraynumber]->direction = 1; // 2,3 and 5 use the direction line if(OutputControl[arraynumber]->type == 2 || OutputControl[arraynumber]->type == 3 || OutputControl[arraynumber]->type == 5) { *OutputControl[arraynumber]->DirectionPortData |= OutputControl[arraynumber]->DirectionOnMask; // set for forward outp(OutputControl[arraynumber]->DirectionPortAddress, *OutputControl[arraynumber]->DirectionPortData); if(OutputControl[arraynumber]->type == 5) { *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake line outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); } } // keep existing bits but remove this one *OutputControl[arraynumber]->ReversePortData &= OutputControl[arraynumber]->ReverseOffMask; // put the result in this node's port register outp(OutputControl[arraynumber]->ReversePortAddress, *OutputControl[arraynumber]->ReversePortData); enable(); return 1; } // go in reverse int Reverse(arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; if(OutputControl[arraynumber]->type < 2) return 0; // < 2 is unidirectional if(OutputControl[arraynumber]->type == 255) return 0; // 255 is last node disable(); OutputControl[arraynumber]->direction = 2; // 2,3 and 5 use the direction line if(OutputControl[arraynumber]->type == 2 || OutputControl[arraynumber]->type == 3 || OutputControl[arraynumber]->type == 5) { *OutputControl[arraynumber]->DirectionPortData &= OutputControl[arraynumber]->DirectionOffMask; // clear for reverse outp(OutputControl[arraynumber]->DirectionPortAddress, *OutputControl[arraynumber]->DirectionPortData); if(OutputControl[arraynumber]->type == 5) { *OutputControl[arraynumber]->BrakePortData |= OutputControl[arraynumber]->BrakeOnMask; // turn on the brake line outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); } } // keep existing bits but remove this one *OutputControl[arraynumber]->ForwardPortData &= OutputControl[arraynumber]->ForwardOffMask; // put the result in this node's port register outp(OutputControl[arraynumber]->ForwardPortAddress, *OutputControl[arraynumber]->ForwardPortData); enable(); return 1; } // stop a pwm channel int Stop(int arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; disable(); OutputControl[arraynumber]->direction = 0; // keep existing bits but remove this one for forward // for L289, this takes its enable line low *OutputControl[arraynumber]->ForwardPortData &= OutputControl[arraynumber]->ForwardOffMask; // put the result in this node's port register outp(OutputControl[arraynumber]->ForwardPortAddress, *OutputControl[arraynumber]->ForwardPortData); // keep existing bits but remove this one for reverse *OutputControl[arraynumber]->ReversePortData &= OutputControl[arraynumber]->ReverseOffMask; // put the result in this node's port register reverse outp(OutputControl[arraynumber]->ReversePortAddress, *OutputControl[arraynumber]->ReversePortData); enable(); return 1; } // apply brake for an L298 int BrakeL298(int arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; disable(); OutputControl[arraynumber]->direction = 0; // make enable line high *OutputControl[arraynumber]->ForwardPortData |= OutputControl[arraynumber]->ForwardOnMask; // put the result in this node's port register outp(OutputControl[arraynumber]->ForwardPortAddress, *OutputControl[arraynumber]->ForwardPortData); // make direction and brake lines low for brake on bottom switches *OutputControl[arraynumber]->DirectionPortData &= OutputControl[arraynumber]->DirectionOffMask; outp(OutputControl[arraynumber]->DirectionPortAddress, *OutputControl[arraynumber]->DirectionPortData); // make brake line low *OutputControl[arraynumber]->BrakePortData &= OutputControl[arraynumber]->BrakeOffMask; outp(OutputControl[arraynumber]->BrakePortAddress, *OutputControl[arraynumber]->BrakePortData); enable(); wait(0.1); // brake .1 sec // make enable line low *OutputControl[arraynumber]->ForwardPortData &= OutputControl[arraynumber]->ForwardOffMask; return 1; } // brake a dual pwm channel // taking both lines high brakes int BrakeDualPwm(int arraynumber) { if(OutputControl[arraynumber] == NULL) return 0; disable(); OutputControl[arraynumber]->direction = 0; // keep existing bits but remove this one for forward *OutputControl[arraynumber]->ForwardPortData |= OutputControl[arraynumber]->ForwardOnMask; // put the result in this node's port register outp(OutputControl[arraynumber]->ForwardPortAddress, *OutputControl[arraynumber]->ForwardPortData); // keep existing bits but remove this one *OutputControl[arraynumber]->ReversePortData |= OutputControl[arraynumber]->ReverseOnMask; // put the result in this node's port register reverse outp(OutputControl[arraynumber]->ReversePortAddress, *OutputControl[arraynumber]->ReversePortData); enable(); wait(0.1); // brake .1 sec Stop(arraynumber); // release lines return 1; } // ========================== Short Cut PWM Routines ========================== // set up an L289 int L289PwmDuty(int arraynumber, int StartDirection, int PwmPortNumber, double ForwardDutycycle, double ReverseDutycycle, int Direction1Port, int Direction2Port) { double ForwardOnTime,ForwardOffTime; double ReverseOnTime,ReverseOffTime; if(ForwardDutycycle > 1.0) ForwardDutycycle/=100.0; if(ForwardDutycycle > 1.0) ForwardDutycycle = 1.0; if(ForwardDutycycle <= 0.5) { ForwardOnTime = minpulse; ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime; } else { ForwardDutycycle = 1.0 - ForwardDutycycle; ForwardOffTime = minpulse; ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime; } if(ReverseDutycycle > 1.0) ReverseDutycycle/=100.0; if(ReverseDutycycle > 1.0) ReverseDutycycle = 1.0; if(ReverseDutycycle <= 0.5) { ReverseOnTime = minpulse; ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime; } else { ReverseDutycycle = 1.0 - ReverseDutycycle; ReverseOffTime = minpulse; ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime; } return pwm(arraynumber, 5, PwmPortNumber, PwmPortNumber, // frwrd & rev same = pwm/enable line Direction1Port, Direction2Port, // direction gets dir1, brake dir2 ForwardOnTime, ForwardOffTime, ReverseOnTime, ReverseOffTime, StartDirection); } // start a blinker Blink(int arraynumber, int BlinkPort, double BlinkOnTime, double BlinkOffTime) { return pwm(arraynumber, 0, // arraynum, type 0 BlinkPort, 0, // forward port, no rev port 0, 0, BlinkOnTime, // no direction, no brake, forward on time BlinkOffTime, 0, 0, 1); // fwd off time, no rev on/off, fwd start } // set up a uni-directioanl pwm channel int UniPwm(int arraynumber, int ForwardPortNumber, double ForwardOnTime, double ForwardOffTime) { return pwm(arraynumber, 0, // arraynum, type 0 ForwardPortNumber, 0, // forward port, no rev port 0, 0, ForwardOnTime, // no direction, no brake, forward on time ForwardOffTime, 0, 0, 1); // fwd off time, no rev on/off, fwd start } // set up a uni-directioanl pwm channel using duty cycle int UniPwmDuty(int arraynumber, int ForwardPortNumber, double ForwardDutycycle) { int x; double ForwardOnTime,ForwardOffTime; if(ForwardDutycycle > 1.0) ForwardDutycycle/=100.0; if(ForwardDutycycle > 1.0) ForwardDutycycle = 1.0; if(ForwardDutycycle <= 0.5) { ForwardOnTime = minpulse; ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime; } else { ForwardDutycycle = 1.0 - ForwardDutycycle; ForwardOffTime = minpulse; ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime; } return UniPwm(arraynumber, ForwardPortNumber, ForwardOnTime, ForwardOffTime); } // set up a dual pwm channel using duty cycle int DualPwmDuty(int arraynumber, int StartDirection, int ForwardPortNumber, double ForwardDutycycle, int ReversePortNumber, double ReverseDutycycle) { double ForwardOnTime,ForwardOffTime; double ReverseOnTime,ReverseOffTime; if(ForwardDutycycle > 1.0) ForwardDutycycle/=100.0; if(ForwardDutycycle > 1.0) ForwardDutycycle = 1.0; if(ForwardDutycycle <= 0.5) { ForwardOnTime = minpulse; ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime; } else { ForwardDutycycle = 1.0 - ForwardDutycycle; ForwardOffTime = minpulse; ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime; } if(ReverseDutycycle > 1.0) ReverseDutycycle/=100.0; if(ReverseDutycycle > 1.0) ReverseDutycycle = 1.0; if(ReverseDutycycle <= 0.5) { ReverseOnTime = minpulse; ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime; } else { ReverseDutycycle = 1.0 - ReverseDutycycle; ReverseOffTime = minpulse; ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime; } return pwm(arraynumber, 4, // type 4 is dual pwm ForwardPortNumber, ReversePortNumber, 0,0, // no direction or brake port numbers ForwardOnTime, ForwardOffTime, ReverseOnTime, ReverseOffTime, StartDirection); } // set up a channel with a pwm line and a direction line -- no brake // using duty cycle int PwmAndDirectionDuty(int arraynumber, int StartDirection, int PwmPortNumber, int DirectionPortNumber, double ForwardDutycycle, double ReverseDutycycle) { double ForwardOnTime,ForwardOffTime; double ReverseOnTime,ReverseOffTime; if(ForwardDutycycle > 1.0) ForwardDutycycle/=100.0; if(ForwardDutycycle > 1.0) ForwardDutycycle = 1.0; if(ForwardDutycycle <= 0.5) { ForwardOnTime = minpulse; ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime; } else { ForwardDutycycle = 1.0 - ForwardDutycycle; ForwardOffTime = minpulse; ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime; } if(ReverseDutycycle > 1.0) ReverseDutycycle/=100.0; if(ReverseDutycycle > 1.0) ReverseDutycycle = 1.0; if(ReverseDutycycle <= 0.5) { ReverseOnTime = minpulse; ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime; } else { ReverseDutycycle = 1.0 - ReverseDutycycle; ReverseOffTime = minpulse; ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime; } return pwm(arraynumber, 2, // type 2 = pwm, direction, no brake PwmPortNumber, PwmPortNumber, // forward and reverse are same DirectionPortNumber, 0, // no brake ForwardOnTime, ForwardOffTime, ReverseOnTime, ReverseOffTime, StartDirection); } // wait for seconds and/or fractions of a second void wait(double seconds) { long wait_count; if(seconds <= 0.0) return; if(timer_counter < 0L) return; wait_count = (long)((seconds * frequency) + 0.5); // round at .5 timer_counter = 0L; while(timer_counter < wait_count); } // ================= Routines to Get Timer Information ================= // return the frequency to the caller double get_frequency(void) { return frequency; } // return the frequency to the caller double get_mintime(void) { return MinTime; } // return the frequency to the caller double get_maxtime(void) { return MaxTime; } // return the value of the counter to the caller long get_timer_counter(void) { return timer_counter; } // ======================= DA Routines ======================= unsigned InitializeAnalog(void) { base = get_port(); eoc = base + 0x18; if(!base) return 0; memset(&AnnalogChannel, 0, sizeof(AnnalogChannel)); CurrentChannel = 0; return base; } int TurnOnAnalog(int channel) { if(channel < 0 || channel > 7) return -1; AnnalogChannel[channel].status = START_CONVERSION; DA_Enabled = 1; return channel; } int TurnOffAnalog(int channel) { int x; if(channel < 0 || channel > 7) return -1; AnnalogChannel[channel].status = INACTIVE; for(x=0; x<8; x++) { if(AnnalogChannel[channel].status != INACTIVE) break; } if(x == 8) // all channels are inactive DA_Enabled = 0; return channel; } int GetChannelValue(int channel) { if(channel < 0 || channel > 7) return -1; if(AnnalogChannel[channel].status == INACTIVE) return -1; return AnnalogChannel[channel].data; return channel; } // ======================= Test Routines ======================= void showall(void) { int x; for(x=0; x<24; x++) { show(x); if(OutputControl[x]->type == 255) break; } } void show(int location) { if(OutputControl[location] != NULL) { printf("Location %02d:\ntype=%d direction=%d " ,location,OutputControl[location]->type,OutputControl[location]->direction); if(OutputControl[location]->type != 255) { printf("Frwrd Port = %X Mask = %X Rev Port = %X Mask = %X\n" ,OutputControl[location]->ForwardPortAddress ,OutputControl[location]->ForwardOnMask ,OutputControl[location]->ReversePortAddress ,OutputControl[location]->ReverseOnMask); printf("Brk Port = %X Mask = %X Dir Port = %X Mask = %X\n" ,OutputControl[location]->BrakePortAddress ,OutputControl[location]->BrakeOnMask ,OutputControl[location]->DirectionPortAddress ,OutputControl[location]->DirectionOnMask); printf("Forward Set On=%ld Forward Set Off=%ld\n" ,OutputControl[location]->ForwardSetOn ,OutputControl[location]->ForwardSetOff); printf("Reverse Set On=%ld Reverse Set Off=%ld" ,OutputControl[location]->ReverseSetOn ,OutputControl[location]->ReverseSetOff); } printf("\n\n"); } } // end timer8b.c