DanielPHuber 16 Posted February 4, 2012 Share Posted February 4, 2012 I wanted to try out myself how well solar energy really works. Toward this aim I built a solar collector system for waterheating. A small boiler with a heat exchanger is used for heat storage and a ordinary heating pump for the water circulation. Temperature is measured at the collector, at the entrance and outlet of the heat exchanger and inside the heat storage boiler. As a controller for the pump I used an MSP430 launchpad. There were several problems I had to solve. First, temperature must be measured with a resolution of at least 1 degree. My preferred temperature sensor would have been a Pt100. This sensors are accurate and linear, but not very sensitive. With a 8 bit AD converter, a resolution of not more than 4 degree is possible, not good enough. Therefore I was forced to use NTC sensors. These are more sensitive, but not very accurate and the resistance changes with temperature not linearly, but exponentially. When I tried this out, I had to realize that the exponential function of C uses too much memory. Therefore, I had to find a replacement and I used a rational approximation that uses much less space and works satisfactorily accurate. Further, as the NTC sensors are not accurate enough, I had to measure the value of each and correct by software. To supervise the working of the controller I transmit the temperature values, the state of the pump and what action is taken to a PC that can be connected to the launchpad. This is actually not needed for the working of the controller, but it is nice to have a mean to see what is going on. Here is how the program works: After initialization we enter an infinite loop, that does the actual work. - First all 4 temperature values are measured. According to these values we decide what to do. - If the boiler temp is too high, we switch the pump of for security reasons. - If the pump is off and the collector temp is higher than the boiler temp by some margin and the collector temp is above some minimal value, we switch the pump on. - If the pump is on, we compare the entry and outlet temp of the heat exchanger. If these differ less than some value, that is, practically no heat is put into the boiler, we switch the pump off. - Finally we toggle a LED every 1 sec to indicate that the controller is working and wait 1 minute before we start the next cycle. To make the temperature measurement more reliable, I measure the temp 8 times and take the average. To minimize current consumption I put the CPU to sleep during wait. This controller now works for more than half an year to my full satisfaction. I get approx. 0.75 kW / m^2 heating power on a sunny day. I only regret, that we do not have more sunny days here in Switzerland. And here is the program that uses up nearly every bit of the 2K memory: ======================================================= /* Solar collector controller * Daniel Huber 1.7.2011 daniel_huber@sunrise.ch * * Measures Pin 1.3,1.4,1.5,1.7 with average * Calculates temperatures from measured values * If boiler temp too high -> do nothing * If collector temp > boiler+ TDelCol switch on pump, wait 1 min. * If temp of heatexchanger input < heat exchanger output+TDelEx switch off pump, repeat * send values to RS232 */ #include "msp430g2231.h" #include "stdbool.h" #include "stdlib.h" #include "math.h" #include "string.h" #define Tcm 110 // max temp at collector #define Tcmin 30 // min temp at collector #define Tbm 90 // max temp at boiler #define TDelCol 5 // min. temp between Collector and Boiler #define TDelEx 2 // min. temp between heat exchanger in and out #define TXD BIT1 // TXD on P1.1 #define Bit_time 104 // 9600 Baud, SMCLK=1MHz (1MHz/ 9600)=104 #define LEDR BIT0 // LED red #define LEDG BIT6 // LED green #define CHCOLLECT INCH_4 // measure channel collector #define CHHEATEXIN INCH_5 // measure channel heat exchanger #define CHHEATEXOUT INCH_3 // measure channel heat exchanger #define CHBOILER INCH_7 // measure channel boiler // pump on/off #define PumpOn {P1OUT |= BIT0;PumpOnFl=true; } #define PumpOff {P1OUT &= ~BIT0;PumpOnFl=false; } unsigned char BitCnt; // Bit count, used when transmitting byte unsigned int TXByte; // value sent over UART when Transmit() is called int i,j; // 'for' loop variable int TCol,TExIn,TExOut,TBoiler,channel; // Measured ADC Value bool PumpOnFl; // pump on flag unsigned int ADCValue; // aux. value to return value from interrupt short state; // state variable char msg[3]; float cor,R; // Function Definitions void Transmit(); //transmits a byte int Single_Measure(); //measures with avarage void Single_Measure0(); //measure single value void TransmitRecord(/*int TCol,int TExIn,int TExOut,int TBoiler,bool PumpOnFl*/); // transmit one record void delay1(); //delays 1 sec int P2T(int P); // ADC value to degree void main(void) { WDTCTL = WDTPW + WDTHOLD+WDTCNTCL; // Stop WDT: PWD,Hold, Counter Reset BCSCTL1 = CALBC1_1MHZ; // Set range DCOCTL = CALDCO_1MHZ; // SMCLK = DCO = 1MHz // BCSCTL3 |= LFXT1S1; // sets LFXT1Sx to 0b10, VLO mode, bit not set-> 32KHz BCSCTL3 |= XCAP_2; // set 10pF for cristal oscillator P1SEL |= TXD; // Connect TXD to timer pin P1DIR |= TXD | LEDR | LEDG; // use TX PumpOff; // Pump off state=1; __bis_SR_register(GIE); // interrupts enabled while(1){ msg[0]='\0'; // empty string cor=1.01*10.12;channel=CHCOLLECT; //correction factor: TCol=P2T(Single_Measure()); // collector temp cor=1*10.32;channel=CHHEATEXIN; TExIn=P2T(Single_Measure()); // heat exchanger in temp cor=0.89*10.21;channel=CHHEATEXOUT; // 0.937 takes care of R20 difference TExOut=3+P2T(Single_Measure()); // heat exchanger out temp, correct for NTC tolerance cor=1*10.32;channel=CHBOILER; TBoiler=P2T(Single_Measure());// boiler temp if (TBoiler > Tbm && state!=0) {PumpOff;state=0;strcat(msg,"st00");} switch(state){ case(0): if(TBoiler case(1): if((TCol>TBoiler+TDelCol) && TCol>=Tcmin) {PumpOn; state=2;strcat(msg,"12");} break; case(2): if(TExIn } TransmitRecord(); for(i=1;i<60;i++){ P1OUT ^= LEDG; // toggle LED at P1.6 delay1(); } } } // transmit one record void TransmitRecord(){ unsigned int k; i=0; while(i>=0){ switch(i){ case(0): k='SS'; break; case(1): k='tt'; break; case(2): k=TCol; break; case(3): k=TExIn; break; case(4): k=TExOut; break; case(5): k=TBoiler; break; case(6): if(PumpOnFl)k=1; else k=0; break; case(7): if (strlen(msg)>0){k=256*msg[1]+msg[0]; break;} else {i++;} case(8): k='EE'; break; case(9): {k='nn'; i=-2;} }; TXByte = (k & 0x00FF); // Set TXByte Transmit(); // Send TXByte = k >> 8; // Set TXByte to the upper 8 bits TXByte = TXByte & 0x00FF; Transmit(); i++; } } // averaged single measurement int Single_Measure(/*int channel*/){ int ADCAvg = 0; for (i = 0; i < 8; i++){ // add up values Single_Measure0(channel); ADCAvg += ADCValue; } ADCAvg >>= 3; // divide by 8 return ADCAvg; } /** * Reads ADC channel once, using AVCC as reference. **/ void Single_Measure0(/*int channel*/) { ADC10CTL0 &= ~ENC; // Disable ADC ADC10CTL0 = ADC10SHT_3 + ADC10ON + ADC10IE; // 64 clock ticks, ADC On, enable ADC interrupt ADC10CTL1 = ADC10SSEL_3 +channel; // Set 'chan', SMCLK __delay_cycles(1000); ADC10CTL0 |= ENC + ADC10SC; // Enable and start conversion _BIS_SR(CPUOFF + GIE); // sleep CPU } /** * Transmits the value currently in TXByte. The function waits till it is * finished transmiting before it returns. **/ void Transmit() { TXByte |= 0x100; // Add stop bit to TXByte (which is logical 1) TXByte = TXByte << 1; // Add start bit (which is logical 0) BitCnt = 0xA; // Load Bit counter, 8 bits + ST/SP CCTL0 = OUT; // TXD Idle as Mark TACTL = TASSEL_2 + MC_2; // SMCLK, continuous mode CCR0 = TAR; // Initialize compare register CCR0 += Bit_time; // Set time till first bit CCTL0 = CCIS0 + OUTMOD0 + CCIE; // Set signal, intial value, enable interrupts while ( CCTL0 & CCIE ); // Wait for previous TX completion } /** * ADC interrupt routine. Pulls CPU out of sleep mode. **/ #pragma vector=ADC10_VECTOR __interrupt void ADC10_ISR (void) { ADCValue = ADC10MEM; // Saves measured value. __bic_SR_register_on_exit(CPUOFF); // Enable CPU so the main while loop continues } /** * Timer interrupt routine. This handles transmitting and receiving bytes. **/ #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { CCR0 += Bit_time; // Add Offset to CCR0 if ( BitCnt == 0) // If all bits TXed { TACTL = TASSEL_2; // SMCLK, timer off (for power consumption) CCTL0 &= ~ CCIE ; // Disable interrupt } else { CCTL0 |= OUTMOD2; // Set TX bit to 0 if (TXByte & 0x01) CCTL0 &= ~ OUTMOD2; // If it should be 1, set it to 1 TXByte = TXByte >> 1; BitCnt --; } } /** * function to get temperature from measured ADC value **/ int P2T(int P){ #define P0 0x3FF R=cor*P/(P0-P); return( (159.444F+R*(29.4008F-0.21077F * R))/(1+R*(0.59504F+0.0155797F * R)) ); } //put CPU to sleep for 1 sec void delay1(){ IE1 |= WDTIE; // Watchdog Interrupt Enable WDTCTL = WDTPW+WDTTMSEL+WDTSSEL; // Passwd(WDTPW), Counter-Mode //Intervall(WDTTMSEL),Timer= "0"(WDTCNTCL), //SourceClk=ACLCK(WDTSSEL),Sel:00=Clk/32768 01=Clk/8192 10:Clk/512 11=Clk/64 _BIS_SR(LPM3_bits + GIE); // put CPU to sleep LPM1_bits } // delay interrupt routine, wakes up CPU #pragma vector=WDT_VECTOR __interrupt void WATCHDOG_ISR (void){ // interrupt routine for delay WDTCTL = WDTPW + WDTHOLD+WDTCNTCL; //Password(WDTPW), //Watchdog stop(WDTHOLD),Counter=0, this resets register(exeption: hold bit) // IE1 &= ~WDTIE; // Watchdog Interrupt Disable __bic_SR_register_on_exit(LPM3_bits); // clear LPM3 bits so the main while loop continues } ======================================================== la4o, oPossum, username and 4 others 7 Quote Link to post Share on other sites
bluehash 1,581 Posted February 4, 2012 Share Posted February 4, 2012 Welcome to the Forums and thanks for posting this up. Make sure you enter this into the next POTM contest. Added the pictures. Quote Link to post Share on other sites
kenemon 29 Posted February 16, 2012 Share Posted February 16, 2012 Welcome Dan, awsome project, couple of comments: 1) do you have any kind of storage tank? 2) Do you use the exchanger sensor as a cut off switch, or do you try to keep this constant? 3) is the demand signalled with temp alone? 4)did you do all that brazing and bending? Why copper ( not that i object) ? Quite impressive, share your tips please Quote Link to post Share on other sites
DanielPHuber 16 Posted February 23, 2012 Author Share Posted February 23, 2012 Hi Kenemon, the storage was a problem as I did not have the necessary space. Therefore, I needed another solution. The original installation had a small electrical boiler. I installed a second boiler in serie with the original one. Thereby, the electrical boiler receives water preheated by the solar collector. If there is not enough sunshine, the water temperature will be boosted by electricity during night time. If the sun shines long enough, no electricity is used. Of course I can not store heat for a long time, it is used on a daily basis. For cut off, I monitor the temperature difference over the heat exchanger. If this becomes too small, it means that no heat is delivered to the boiler or even drained and I switch off the pump. I heat the boiler as soon as the collector is warmer than the boiler. For security reasons, I also have a max. boiler temp. where the pump is switched off, but I have not yet reached it. I soldered where appropriate because it is easier to do. Only where I had steel I did braze. It is important to have copper sheet metal with a coating that absorbs visible light, but does not re- radiate infrared heat. The copper sheet metal is soldered to copper tubing because of heat flux. The auxiliary tubing was first done with PETX tubing (cross linked PET with a aluminium layer), but it turned out that with a temperature of 120 degree C, these started to leek at the connectors. Therefore, I did all the tubing inside the collector in copper. The tubing from the collector to the basement is done in PETX, isolated with PET. This seems to work well. The only problem I had was, that the water immediately at the outlet is so hot, that the PET isolation melted. Therefore, I isolated the first meter using glass wool. The isolation of the collector at the back side is done in rock wool. The front is covered with a polycarbonat sheet. This is much cheaper than glass and does not break. In addition it can be bent easily using a hot air gun. cheers, Daniel kenemon and nuetron 2 Quote Link to post Share on other sites
username 198 Posted February 25, 2012 Share Posted February 25, 2012 Very impressive project there! I wouldn't ever be able to do a project like which requires both embedded knowledge and physical mechanical hardware application. Way to combine mechanical skills and embedded skills and hope it works out for ya! Thanks for sharing! Hitefunning 1 Quote Link to post Share on other sites
kenemon 29 Posted February 26, 2012 Share Posted February 26, 2012 I am still taking it in... My gut brainless thought now is that you should recon a used or secondhand expansion tank to eliminate the potential disaster you have forseen with the proximity to the pump. It would be a shame to lose that pump (they aint cheap) or get juiced. I hope you have a GFI inline.... Otherwise, I want one, but they are tricky when it snows here I would need to cycle water at night, and on cold days, or use PEG, much like a hot tub.... Still admiring your copper work! How expensive was all that copper? Quote Link to post Share on other sites
DanielPHuber 16 Posted February 26, 2012 Author Share Posted February 26, 2012 Hi kenemon, I actually use an expansion tank, otherwise the system would crack (see pictures). At first thought it seems ridiculous that the tank consist of an upside down PET bottle. But if you dig in, you will find that these PET bottles are rated for an unbelievable 15bar. I have some experiences with these bottles, I used them to build "water rockets" for the kids. These consists of an upside down bottle, approx. half filled with water. You then pump them up with air (we used 7bar from a compressor or a bicycle pump) and then you let go. These "rockets" fly approx. 30m high and are fun for the whole neighborhood. An additional benefit is that the bottles are transparent, you can actually see how the water expands, what is not little (good lecture in physics!). One bottle works, but next time when I empty the system, I will add a second bottle, because the pressure changes still a bit too much for my taste. However, at this time the system is filled with antifreeze (car supply) what makes it a bit of a mess to empty. Therefore, I wait until I have to empty it anyway. In Switzerland you pay ridiculous prices for copper at this time. However, I am living not too far from Germany, where you can get it much cheaper. Most of the copper I could buy from surplus material and I payed something over SFr. 100.- for one collector. The tubing from the collector is done in PEX-Aluminium tubing, what is also much cheaper in Germany, I bought it for approx. 1 Euro/m. What do you mean by GFI? Ground Fault Interrupter? The main voltage part is separated from the low voltage part by an opto. Therefore, there is no direct connection between both sub-systems. By PEG, you main antifreeze? I need this dearly, two weeks ago we had -15 degree Celsius, but everything worked perfectly. As soon as the sun comes out, the boiler starts to heat. I do not think that circulating the water would prevent it from freezing with such low temperatures. cheers, Daniel kenemon 1 Quote Link to post Share on other sites
kenemon 29 Posted February 28, 2012 Share Posted February 28, 2012 No Worries Dan, you seem to have thought it through adequately. Things are a little more complicated here, with city inspectors snooping around from time to time. I saw your expansion device, but i did not realize they could handle that type of pressure. It is just an excuse for inspectors here to hastle you for something... They also are sticklers for GFI's, even when closer than 2 feet to a kitchen sink! I just saw that potential moisture near your pump condom. Seems like your system works great. Yes I was speaking of antifreeze (PEG), they use it here allot to prevent disasters with radiant heat systems.... I am jealous of your free hot water- Nice Job! KB Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.