20
Dec 12

Reflow Toaster Oven Vid 6: Integer Division and Main State Machine

This will be a quick write-up about how I did my integer division by 10 and a brief discussion of the main state machine that runs the overall 140-170-215 thermal profile.

Division routine:


if(offset_temp_flag == 1){


			for(i=0;i<0xCD;i++){
				offset_temp += room_temp_avg;
			}

		offset_temp += 1;	// for rounding accuracey
		offset_temp = ((offset_temp >> 8) >> 3);	// shift 8 places to get decimal pont back, 3 places because we left shifted to get a 1
													// to the right of the decimal point as well.
		final_temp = (offset_temp + avg_temp);		// this is my final temp. which includes the room temp offset

		offset_temp_flag = 0;	// reset flag - this flag controls our division code.
		}// end if(offset_temp_flag == 1)


// end division

Here I divide the variable "room_temp_avg" by ten. Room_temp_avg is the variable that holds the number of samples from the microchip 9700A multiplied by two. I multiplied this by two because I was already coding up a division by 10 routine. As the video explains, I have 2mV per sample and the 9700A outputs 10mV/degrees C which yields a value in room_temp_avg of 5 samples/degrees C. I multiply this by 2 (add it to itself) which yields a value of 10 samples/degrees C. I then divide by ten above to get a room temp value (held in offset_temp) of degrees C.

To accomplish the division I multiply by 1/10 which is the same as dividing by ten. I found a website online that calculates the binary value of a decimal number so I plugged in 0.1 and it spit out 0.00011001100 etc... Effectively what we do is take the "real" binary number in non-deciaml form and multiply. We then right shift (divide) to get the decimal place back. With this said, next I shift the 0.00011001100 to the left three times to get the first one to the right of the decimal place, i.e 0.11001100. Next I left shift the number again 8 times to get a "whole" binary number i.e. 11001100. At this point, remember, you've shifted a total of 8 + 3 places to the left so after we multiply we need to shift that many times back to the right. I read that you should add 1 to this number to increase accuracy so I did that as well yeilding, 11001101. To multiply, I used a for loop to add room_temp_avg to itself 11001101 number of times (0xCD) which is the same thing as multiplying the two. Once the loop (multiplication) is done, right shift the 11 places back out to reclaim the decimal point which effectively truncates the result of room_temp_avg/10.

The next instruction,

final_temp = (offset_temp + avg_temp);

Adds the thermal couple temp to the room temp to give me a total temp in degrees C normalized to 0 degrees C. This is important because the thermal couple only measures the difference in temp between the tip inside the toaster and the two metal pins (a thermal couple is two pieces of metal welded together on one end and when heated produces a potential difference on the other) which means you don't get the real temp you get a difference in the toaster temp and room temp.

An example: If the room temp is 20 C and the toaster is at 100 C your thermal couple will produce a voltage representing a 80 C difference. Because of this you add the room temp to it.

Ok, so I hope this makes sense so far. Now, on to the main state machine.

The first state,

		switch(main_state){
			case 0:	// initial state
				if(START_THERMAL_CYCLE == 1){// if system is starting from a fresh power up
					TARGET_TEMP = SET_TEMP1;		// set first target temp
					THERMAL_RATE = 12000;			// This is the max duty cycle allowable d.c = THERMAL_RATE/TimerA0_CCR0
					main_state = 1;					// change state...main_state = state variable for main state machine
					PWM_DISPLACEMENT = 200;			// This value is how much is added every time the thermal timer interrupt is enters
					START_THERMAL_CYCLE = 0;		// Says we have begun a thermal run
				}
				break;

which is where we stay until the run or go command is received from the UART. The UARTRX interrupt sets START_THERMAL_CYCLE and allows me to setup my first temp target and associated parameters. Once I get a go command (0x77), I set the target temp, thermal rate (which is the max duty cycle in terms of TIMERA1 CCR0), change state variable to next state and set the PWM_DISPLACEMENT. The PWM_DISPLACEMENT = 200 means that when we increase the duty cycle we will increase by 200 every time. This controls the increments in which I increase and decrease the duty cycle.

case 1:	// ramp state
				if(final_temp == SET_TEMP1){	// checks to see if system as reached first target temp
					main_state = 2;	// go to next state
					mainstate_seconds = 0;	// reset seconds counter used in next state for hold time
					PWM_DISPLACEMENT = 1000;	// increase how much we add to PWM duty cycle every time we need to increase it
				}else{
					main_state = 1;	// if target temp hasn't been reached wait in this state
				}
				break;

In this state, I check to see if our real temp in the toaster has met my target temp or not. If it hasn't I just wait here. Once it has I set the next state, clear main_seconds (which is my stop watch...I use it to figure out how long I've been waiting in the next state since I increment this variable every second) and increase my displacement. I increase my displacement here because I am heading up to 170 from 140 so I want to allow the toaster to have more power.

case 2:	// hold state
				if(mainstate_seconds == 45){	// hold at this temp for 45 seconds
					main_state = 3;	// go to next state
					TARGET_TEMP = SET_TEMP2;	// set second target temp
					THERMAL_RATE = 18000;	// increase max PWM value allowing a more aggressive ramp rate
				}else{
					main_state = 2;	// stay in this state
				}
				break;

This is where mainstate_seconds comes in. I have a hold time of 45 seconds for 140 degrees C so I wait here for 45 counts of the mainstate_seconds variable. As I said above, this is simply a quick and easy stop watch. Once this expires, I set the state variable, the new target temp and the current thermal rate. The new target temp here is the 170. I increased the thermal rate in the last state to make it easier to service or hold the temp at 140. I now want to ramp up to 170 so I enable even more power delivery to the toaster. * Remember, a 100% duty cycle is 25000 because that will take up the entire period of the PWM signal as defined by timerA1 CCR0.

case 3:	// second ramp stage..ramp to target temp 2
				if(final_temp == SET_TEMP2){	//  if target temp has been met
					main_state = 4;		// change state
					mainstate_seconds = 0;	// reset seconds counter to prepare for another hold time
				}else{
					main_state = 3;	// stay in this state
				}
				break;

So, from here until I get to the last state you won't see anything new. I wait here until the toaster has ramped up to the new temp (170 C) and then go through all the same motions. I recommend you just grab the source code and check out the comments or look at the full state machine code at the bottom. I'll skip ahead and explain the last state since the next couple are the same as discussed above.

case 6:	// third hold state
				if(mainstate_seconds == 60){	// hold for 60 seconds
					main_state = 7;	// change state
					TARGET_TEMP = SET_TEMP3;	// keep target temp our final temp
				}else{
					main_state = 6;	// stay here
				}
				break;

			case 7:
				ShutdownPWM();	// shutdown PWM system and force output to 0
				break;

So, this is the last state. Here I've increased my hold time (at 215 C as seen in the demo video) so it is easier to see the small error in thermal regulation. As explained above, I use mainstate_seconds as a stopwatch to wait 60 seconds while holding 215 C. Once this time window expires the process is over and I change to state 7 where I power down the PWM module.

So, thats it! I hope this isn't too tough to read. I recommend downloading the code and reading the comments as well as watching the videos. I try to explain things in a lot more detail in the video and with the comments. Below is the whole state machine.

The only other thing I'd like to mention is that main consists of a while(1) statement which holds this state machine and the division routine. The system sets a flag to kick of division when a sample is done which allows the CPU to run through my division code and crank out a number real quick (remember the CPU runs at 8Mhz). If I'm not dividing and am waiting for a go command the while loop sits and spins checking each if statement to see if the appropriate flags got set to kick of one of the tasks.


/* Main state machine.  This state machine creates the desired
		 * thermal profile.  It doesn't not control the temperature directly
		 * but rather sets all main target temps.  The first timer interrupt
		 * handles ramping and holding to the tartget temp which is set here.
		 */
		switch(main_state){
			case 0:	// initial state
				if(START_THERMAL_CYCLE == 1){// if system is starting from a fresh power up
					TARGET_TEMP = SET_TEMP1;		// set first target temp
					THERMAL_RATE = 12000;			// This is the max duty cycle allowable d.c = THERMAL_RATE/TimerA0_CCR0
					main_state = 1;					// change state...main_state = state variable for main state machine
					PWM_DISPLACEMENT = 200;			// This value is how much is added every time the thermal timer interrupt is enters
					START_THERMAL_CYCLE = 0;		// Says we have begun a thermal run
				}
				break;

			case 1:	// ramp state
				if(final_temp == SET_TEMP1){	// checks to see if system as reached first target temp
					main_state = 2;	// go to next state
					mainstate_seconds = 0;	// reset seconds counter used in next state for hold time
					PWM_DISPLACEMENT = 1000;	// increase how much we add to PWM duty cycle every time we need to increase it
				}else{
					main_state = 1;	// if target temp hasn't been reached wait in this state
				}
				break;

			case 2:	// hold state
				if(mainstate_seconds == 45){	// hold at this temp for 45 seconds
					main_state = 3;	// go to next state
					TARGET_TEMP = SET_TEMP2;	// set second target temp
					THERMAL_RATE = 18000;	// increase max PWM value allowing a more aggressive ramp rate
				}else{
					main_state = 2;	// stay in this state
				}
				break;

			case 3:	// second ramp stage..ramp to target temp 2
				if(final_temp == SET_TEMP2){	//  if target temp has been met
					main_state = 4;		// change state
					mainstate_seconds = 0;	// reset seconds counter to prepare for another hold time
				}else{
					main_state = 3;	// stay in this state
				}
				break;

			case 4:	// second hold state
				if(mainstate_seconds == 45){	// hold for 45 seconds
					main_state = 5;	// change state
					TARGET_TEMP = SET_TEMP3;	// set new target temp
					THERMAL_RATE = 25000;	// increase max PWM value ~100%
				}else{
					main_state = 4;	// stay here if hold time hasn't been met
				}
				break;

			case 5:	// third ramp state
				if(final_temp == SET_TEMP3){	//  Target temp 3 met
					main_state = 6;	// change state
					mainstate_seconds = 0;	// reset seconds counter for another hold time
					PWM_DISPLACEMENT = 2000;	// increase displacement ... allows us to add larger values into PWM width register TA1 CCR1
				}else{
					main_state = 5;	// stay here
				}
				break;

			case 6:	// third hold state
				if(mainstate_seconds == 60){	// hold for 60 seconds
					main_state = 7;	// change state
					TARGET_TEMP = SET_TEMP3;	// keep target temp our final temp
				}else{
					main_state = 6;	// stay here
				}
				break;

			case 7:
				ShutdownPWM();	// shutdown PWM system and force output to 0
				break;


		}// End switch(main_state)

Data Package:
Note-I haven't been able to get the python app packed up as an exe yet because of some technical problems, but when I get this figured out I'll update the package. The source code (python GUI and embedded C) is packed up as the aptana project and CCS studion project respectively. It should be simple to import these and check out the code. If importing doesn't work just check out the source files or create your own project and add the source files only to it.

Download Data Package