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.
Related
please I have a problem with writing a code which will read a CAN message, edit it (limit to some maximum value) and then send back with same ID.
I´m using PCAN-Router Pro FD and will show you their example of such thing - basically same as mine but I have no idea what some of the numbers or operations are. [1]: https://i.stack.imgur.com/6ZDHn.jpg
My task is to: 1) Read CAN message with these parameters (ID = 0x120h, startbit 8, length 8 bit and factor 0,75)
2) Limit this value to 100 (because the message should have info about coolant temperature.)
3) If the value was below 100, dont change anything. If it was higher, change it to 100.
Thanks for any help !
Original code:
// catch ID 180h and limit a signal to a maximum
else if ( RxMsg.id == 0x180 && RxMsg.msgtype == CAN_MSGTYPE_STANDARD)
{
uint32_t speed;
// get the signal (intel format)
speed = ( RxMsg.data32[0] >> 12) & 0x1FFF;
// limit value
if ( speed > 6200)
{ speed = 6200;}
// replace the original value
RxMsg.data32[0] &= ~( 0x1FFF << 12);
RxMsg.data32[0] |= speed << 12;
}
After consulting the matter in person, we have found the answer.
The structure type of the RxMsg contains a union allowing the data to be accessed in 4-Byte chunks RxMsg.data32, 2-Byte chunks RxMsg.data16, or 1-Byte chunks RxMsg.data8. Since the temperature is located at the 8th bit and it is 1 Byte long, it can be accessed without using the binary masks, bit shifts and bitwise-logical-assignment operators at all.
// more if-else statements...
else if (RxMsg.id == 0x120 && RxMsg.msgtype == CAN_MSGTYPE_STANDARD)
{
uint8_t temperature = RxMsg.data8[1];
float factor = 0.75;
if (temperature * factor > 100.0)
{
temperature = (int)(100 / factor);
}
RxMsg.data8[1] = temperature;
}
The answer assumes that the startbit is the most significant bit in the message buffer and that the temperature value must be scaled down by the mentioned factor. Should the startbit mean the least significant bit, the [1] index could just be swapped out for [62], as the message buffer contains 64 Bytes in total.
The question author was not provided with a reference sheet for data format, so the answer is based purely on the information mentioned in the question. The temperature scaling factor is yet to be tested (will edit this after confirming it works).
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);
}
I am attempting to implement a temperature sensor on the MSP430G2553 that reads and outputs temp readings and also timestamps. The basic requirements are as follows:
• t: Show the current time of the system. Output to the serial terminal the current time of the system using the form hhmmss, where hh is the two digit hour in 24-hour format, mm is the two digit minute in the hour, and ss is the two digit second within the minute.
• s: Set the current time of the system. Set the current time of the system to the provided argument. There will be no space between the command and its argument. The argument will be on the form hhmmss, where hh is the two digit hour in 24-hour format, mm is the two digit minute in the hour, and ss is the two digit second within the minute.
• o: Show the oldest temperature reading and its timestamp. Output to the serial terminal the oldest temperature reading and its timestamp. The output must have the form hhmmss: T, where hh is the two digit hour in 24-hour format, mm is the two digit minute in the hour, ss is the two digit second within the minute, and T is the measured temperature. The displayed entry must be removed from the list. If no readings have been performed, the message “No temperatures recorded.” must be displayed.
• l: Show all the temperature readings and their timestamps. Output to the serial terminal all the temperature readings and their timestamps. The output should be in chronological order, oldest first. The output must have the form hhmmss: T, where hh is the two digit hour in 24-hour format, mm is the two digit minute in the hour, ss is the two digit second within the minute, and T is the measured temperature. All entries must be removed from the list. If no readings have been performed, the message “No temperatures recorded.” must be displayed.
Your system must also meet the following requirements:
• You must capture a new temperature reading every five (5) minutes and store 32 temperature readings at a minimum. If you have reached the maximum amount of temperature readings, discard the oldest one before storing a new one.
An error occurs after inputting 't' 2 times. The next time I input 't', It outputs 3 or 4 garbage values. My 't' case in the switch statement runs perfectly fine when I comment out the 's' case or change 's' to a different variable but that case for the assignment needs to be in the case 's' is input into the uart. What I've tried so far includes: changing the variable, commenting out the second case, changing the value of the S case to make sure the garbage values were resulting because of that case, playing with bracketing of certain parts of code, revamped the entire first part of the t case to be more efficient and still came out with the same error,etc. Any suggestions would be very helpful. Picture of Garbage Values
#include "msp430g2553.h"
volatile short unsigned int i=0,j=0,k=0;//This the integer that track the number of cycles
volatile short unsigned int timercount=0;//This the integer that track the number of cycles
volatile unsigned int GT=2600;//variable that sets the global time in seconds of the system
volatile char inputcharacters[16];//Input character storage array
volatile unsigned int tempature[32];//
volatile unsigned int tempaturetime[32];
volatile unsigned int d[6]={0,0,0,0,0,0},test[7];
volatile unsigned int h=0;
volatile unsigned int m=0;
volatile unsigned int ss=0;
int temp=0;
void main(void)
{
WDTCTL = WDTPW + WDTHOLD;//Disabling watchdogtimer
P1DIR |= BIT0;//setting p1 bit 1 to outy
P1OUT &= ~BIT0;//Shutting off the LED
/* UART config for 9600 baud with SMCLK*/
IE2 = 0x00;//interrupts off on UART
UCA0CTL1 = 0x81;//using SMCLK(1Mhz) and software reset enable
UCA0CTL0 = 0x00;//eight data bits, no parity, one stop bit
UCA0MCTL = 0x02;//Setting UCBRSx to 1 to set clock rate to 9600
UCA0BR0 = 0x68;//set clock rate to 9600 from 1Mhz
UCA0BR1 = 0x00;//set clock rate to 9600
UCA0STAT = 0x00;//error check and parity, and frame error check all off
TACTL = 0x1D0;// sets timer a to use the 32.768kh clock with 8 divisor and in up mode
TACCR0 = 0x1001;// setting the timer to count up to 4097 to get a 1hz(1s) oscillation
TACCTL0 &= 0xFFEF;// disable capture compare interrupts
P1SEL|=0X06;// According to slas735j both p1.1(0X02) and p1.2(0X04) need to be set on to receive and send on the UART
P1SEL2|=0X06;
ADC10CTL0 &= ENC;
ADC10CTL0 = SREF_1 | ADC10SHT_3 | REFON | ADC10ON;
ADC10CTL1 = INCH_10 | ADC10DIV_2 | ADC10SSEL_3;
//ADC10CTL0 = 0x3030;
//ADC10CTL1 = 0xA038;
ADC10CTL0 &= 0xFFFC;
/* Enable USCI logic and Enable interrupts */
UCA0CTL1 &= 0xFE;//turning on USCI logic
IE2 = 0x01;// Sets UART to receive interrupt
TACCTL0 = CCIE;// set timerA interupts on done last
__bis_SR_register(LPM3_bits + GIE);//found on TI wiki
}
#pragma vector=USCIAB0RX_VECTOR// This vector name was pulled from the webcources code
__interrupt void uartinput(void)//this form found on TI's wiki
{
inputcharacters[i] = UCA0RXBUF;
UCA0TXBUF = UCA0RXBUF;
if((i>14)||(inputcharacters[i]=='\r'))
{if(inputcharacters[i]=='\r'){inputcharacters[i]=0;}//to reduce i, the number of inputs so that we dont count the \r
for(j=0;j<i;j++){switch(inputcharacters[j]) {//switch statement that check the input characters and outputs the correct morse
case 't' :
h=GT/3600;
m=(GT-h*3600)/60;
ss=GT-h*3600-m*60;
d[0]=(h/10);
d[1]=(h-(d[0]*10));
d[2]=(m/10);
d[3]=(m-(d[2]*10));
d[4]=(ss/10);
d[5]=(ss-(d[4]*10));
for(k=0;k<6;k++)
{while((IFG2 & 0x02)==0)
{;}
UCA0TXBUF=d[k]+48;
test[k]=d[k]+48;}
while((IFG2 & 0x02)==0)
{;}
UCA0TXBUF='\r';
break;
case 'x' ://If this break case is not set to 's' it will not cause the error in the 't' case, no idea why
GT=0;
temp=(inputcharacters[j+1]-0x30)*10;//
temp+=(inputcharacters[j+2]-0x30);
GT+=(temp*3600);
temp=(inputcharacters[j+3]-0x30)*10;
temp+=(inputcharacters[j+4]-0x30);
GT+=(temp*60);
temp=(inputcharacters[j+5]-0x30)*10;
temp+=(inputcharacters[j+6]-0x30);
GT+=temp;
j+=7;
break;
case 'o' :
{;}
break;
case 'l' :
{;}
break;
case '\r' :
{;}
break;
default :
{i++;}
}}
i=0;
j=0;
}
else{i++;}}
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_interupt(void)
{
if(GT<86459)//24:00:59 in seconds
{GT++;}//add a second
else
{GT=60;}//00:01:00
if(timercount<299)//300s=5min
{timercount++;}
else{timercount=0;
ADC10CTL0 |= 0x3;// turn on ADC
while(ADC10CTL1 & ADC10BUSY)//loop to alow for ADC to finish sampeling
{;;}
ADC10CTL0 & 0xFFFE;//Shuting off the ADC after sampeling
tempature[i]=ADC10MEM;//Storing the tempature
tempaturetime[i]=GT;// storing the time
if(i<31)
{i++;}
else
{i=0;}
}
}
one very obvious problem with the posted code is that when the currently being checked input char is found to be x is the new date stamp must have been entered (as 6 digits) after the user entered x but before the user entered a \r this could be made to work, but there is no code enforcing that sequence of input.
in general, the uart input function should contain several 'states and sub states' where a command char causes the code to switch to a 'state' and that would have sub states for the actual time stamp. so when the user inputs a command, like x a mode is set, where the next 6 digits entered are the new timestamp.
suggest designing a 'context sensitive' state diagram then implementing that diagram.
amongst other things, the size of the input buffer would reduced to 1 byte
amongst the many sequencing problems there is the fact that when accumulating some number, saying things like
int temp = 0;
temp += inputchar[x] - 0x30;
temp += inputchar[x+1] - 0x30;
...
will not generate the expected results.
what would generate the expected results would be:
int temp = 0;
temp += inputchar[x] - 0x30;
temp = temp*10 + (inputchar[x+1] - 0x30);
....
temp = temp*10 + (inputchar[x+5] - 0x30);
GT = temp;
and of course, test each inputchar using if( isdigit( inputchar[x] ) before trying to apply that input to the calculation of temp
for ease of development, strongly suggest only try to implement one command at a time and when that command is working properly (make extensive use of a debugger to make the testing easy. such as a jtag debugger for that chip.) Then move on to the next command to implement.
the variable i is being modified in both interrupt handlers. So when one interrupt handler modifies i, the value is corrupted for the other interrupt.
one full day (24 hours) is 878400 seconds so the value 86459 is not correct. Also, this is a 'magic' number in the code. magic numbers have no basis. 'magic' numbers make the code much more difficult to understand, debug, maintain. Suggest using a enum statement or a #define statement to give that 'magic' number a meaningful name, then use that meaningful name throughout the code. Note: resetting the GT counter to 60 is not correct, in the posted code it should be reset to 0.
Similar considerations exist for the 'magic' number 300.
the posted code has no action when the input command is l so we cannot analyze such (non existent) code to determine what is wrong with it.
in the handling of the t command:
the GT variable is subject to modification between access. Suggest saving to a 'new' variable and do all calculations from that new variable.
the calculation of h, m, and s is performing a lot of re-calculation of the same intermediate variables. Suggest:
int myGT = GT;
h = myGT / 3600;
myGT %= 3600;
m = myGT / 60;
s = myGT % 60;
The array test[] is set but not used. This is a waste of resources in production code. Suggest its' existence and setting be determined by some -D compiler parameter.
I have an ADC interrupt that I'd like to sample the channel (ADCBUF0) 8 times, then take the average of the samples. My code utilizes flags to jump out of the if statement. The code compiles and my variables are initialized elsewhere. Could someone please tell me why I am not receiving a value for SpeedADC???
///////Global////////////
int SpeedADCcount=0;
/////////////////////////
SpeedADCflag=1;
if(SpeedADCflag==1) //The following is meant to take a an average of the incoming ADC voltages
{
SpeedADCcount++;
for(i = SpeedADCcount; i < 16; i++)
{
while(!ADCON1bits.SAMP); //Sample Done?
ADCON1bits.SAMP=0; //Start Converting
while(!ADCON1bits.DONE); //Conversion Done? Should be on next Tcy cycle
SpeedADCarray[i] = ADCBUF0;
SpeedADCflag=0;
}
}
if(SpeedADCcount==15)
{
SpeedADC=SpeedADCarray[i]>>4;
SpeedADCcount=0;
// Re-enable the motor if it was turned off previous
if((SpeedADC>246) && Flags.RunMotor==0){RunMotor();}
/*Go through another stage of "filtering" for any analog input voltage below 1.25volts
You need to get the right downshift amount (to avoid dividing), such that 8 -> 3, 16 -> 4, etc. For 8 samples, you only need to downshift 3 (3 bits).
And you need to sum all of the values in a single value, not put them in separate array entries.
SpeedADCarray += ADCBUF0; /* accumulate in a single integer, not an array */
Im trying to make a simple RPM meter using an ATMega328.
I have an encoder on the motor which has 306 interrupts per rotation (as the motor encoder has 3 spokes which interrupt on rising and falling edge, the motor is geared 51:1 and so 6 transitions * 51 = 306 interrupts per wheel rotation ) , and I am using a timer interrupting every 1ms, however in the interrupt it set to recalculate every 1 second.
There seems to be 2 problems.
1) RPM never goes below 60, instead its either 0 or RPM >= 60
2) Reducing the time interval causes it to always be 0 (as far as I can tell)
Here is the code
int main(void){
while(1){
int temprpm = leftRPM;
printf("Revs: %d \n",temprpm);
_delay_ms(50);
};
return 0;
}
ISR (INT0_vect){
ticksM1++;
}
ISR(TIMER0_COMPA_vect){
counter++;
if(counter == 1000){
int tempticks = ticksM1;
leftRPM = ((tempticks - lastM1)/306)*1*60;
lastM1 = tempticks;
counter = 0;
}
}
Anything that is not declared in that code is declared globally and as an int, ticksM1 is also volatile.
The macros are AVR macros for the interrupts.
The purpose of the multiplying by 1 for leftRPM represents time, ideally I want to use 1ms without the if statement so the 1 would then be 1000
For a speed between 60 and 120 RPM the result of ((tempticks - lastM1)/306) will be 1 and below 60 RPM it will be zero. Your output will always be a multiple of 60
The first improvement I would suggest is not to perform expensive arithmetic in the ISR. It is unnecessary - store the speed in raw counts-per-second, and convert to RPM only for display.
Second, perform the multiply before the divide to avoid unnecessarily discarding information. Then for example at 60RPM (306CPS) you have (306 * 60) / 306 == 60. Even as low as 1RPM you get (6 * 60) / 306 == 1. In fact it gives you a potential resolution of approximately 0.2RPM as opposed to 60RPM! To allow the parameters to be easily maintained; I recommend using symbolic constants rather than magic numbers.
#define ENCODER_COUNTS_PER_REV 306
#define MILLISEC_PER_SAMPLE 1000
#define SAMPLES_PER_MINUTE ((60 * 1000) / MILLISEC_PER_SAMPLE)
ISR(TIMER0_COMPA_vect){
counter++;
if(counter == MILLISEC_PER_SAMPLE)
{
int tempticks = ticksM1;
leftCPS = tempticks - lastM1 ;
lastM1 = tempticks;
counter = 0;
}
}
Then in main():
int temprpm = (leftCPS * SAMPLES_PER_MINUTE) / ENCODER_COUNTS_PER_REV ;
If you want better that 1RPM resolution you might consider
int temprpm_x10 = (leftCPS * SAMPLES_PER_MINUTE) / (ENCODER_COUNTS_PER_REV / 10) ;
then displaying:
printf( "%d.%d", temprpm / 10, temprpm % 10 ) ;
Given the potential resolution of 0.2 rpm by this method, higher resolution display is unnecessary, though you could use a moving-average to improve resolution at the expense of some "display-lag".
Alternatively now that the calculation of RPM is no longer in the ISR you might afford a floating point operation:
float temprpm = ((float)leftCPS * (float)SAMPLES_PER_MINUTE ) / (float)ENCODER_COUNTS_PER_REV ;
printf( "%f", temprpm ) ;
Another potential issue is that ticksM1++ and tempticks = ticksM1, and the reading of leftRPM (or leftCPS in my solution) are not atomic operations, and can result in an incorrect value being read if interrupt nesting is supported (and even if it is not in the case of the access from outside the interrupt context). If the maximum rate will be less that 256 cps (42RPM) then you might get away with an atomic 8 bit counter; you cal alternatively reduce your sampling period to ensure the count is always less that 256. Failing that the simplest solution is to disable interrupts while reading or updating non-atomic variables shared across interrupt and thread contexts.
It's integer division. You would probably get better results with something like this:
leftRPM = ((tempticks - lastM1)/6);