
DanielPHuber
-
Content Count
16 -
Joined
-
Last visited
-
Days Won
1
Reputation Activity
-
DanielPHuber got a reaction from zeke in Servo programming
Hi,
I tried my hand for the first time on a servo and my experience may be helpful to other people.
Servos are controlled by the length of a pulse. This pulse is repeated ca. every 20mS. The pulse length can vary coarsly between 1 and 2 mS. The pulse length determines the rotation angle. If the servo starts up, it goes to a middle position, corresponding to a ca. 1.5 mS pulse. For shorter pulses the servo turns in one direction, for longer pulses to the other. The range is limited, mostly to +/- 90 degree, the accurate values depend on the servo, as does the min/max widths. Mine had a range of a bit more than 180 degree for a min pulse width of 680 uS and a max width of 2.62 mS. However, you should not use these values but allow for some safety margin. Otherwise the motor may just not reach (e.g. due to slip or play) the position and the motor tries hard and gets hot.
Most servos have 3 wires that are color coded from bright to dark, the color may change. The brightest is the signal pin where the pulses are fed. The middle one, mostly red, goes to Plus and the darkest one (brown, black..) goes to ground.
Energia comes with a helpful server library. There an object: "Servo" is declared. You initiated the server by the method "attach". It comes in 2 varieties (assuming we declared an object "myservo"):
myservo.attach(pinNo);
myservo.attach(pinNo,minVal,maxVal);
where pinNo means the launchpad pin number of the pin used to control the servo. min/maxVal are the minimal/maximal pulse width you want to use. This is not clearly described in the library and confused me bit. In further commands you can confortably deal with degrees beween 0 and 180. The library will transform the degree values into pulse widths, where 0 degree corresponds to the minimal pulse width and 180 degree to the max you specified. If you do not specify min/max values, some default values are used. In may case the lower default value could not be reached and the motor got hot. Therefore, it is better to determine correct values and specify these, using the second form of "attach".
After initialization the servo goes to the 90 degree position. To send it to the 0 degree positio you would write:
myservo.write(0);
My servo did not respond quit linear. To send it to the 90 degree position I had to say 83 degree.
Here is a small test program:
// test for 9g Servo
// 1. Button press -> 0 degree
// 2. Button press -> 90 degree. Due due nonlinearity we need 83 degree
// 3. Button press -> 180 degree.
// 4. Button press -> 90 degree.
#define sig P1_4 // signal for servo
#define but P1_3 // button
#define minWidth 770 // min pulse width in uSec of servo (680 allowed)
#define maxWidth 2595 // max pulse width in uSec of servo (2620 allowed)
#include <Servo.h>
Servo myservo;
void setup()
{
myservo.attach(sig,minWidth,maxWidth);
pinMode(but, INPUT_PULLUP);
}
void loop()
{
while (digitalRead(but));
myservo.write(0); delay(500);
while (digitalRead(but));
myservo.write(83);delay(500);
while (digitalRead(but));
myservo.write(180);delay(500);
while (digitalRead(but));
myservo.write(83); delay(500);
}
Note that due to nonlinearity of the servo I need to specify 83 degree for a 90 degree rotation.
For test purposes you may power the servo directly from the launchpad (provided you do not have a monster servo with a hugh current consumption). In the enclosed picture I control the servo from launchpad pin P1_4 and power it directly from the launchpad.
Have fun, Daniel
-
DanielPHuber got a reaction from bluehash in Servo programming
Hi,
I tried my hand for the first time on a servo and my experience may be helpful to other people.
Servos are controlled by the length of a pulse. This pulse is repeated ca. every 20mS. The pulse length can vary coarsly between 1 and 2 mS. The pulse length determines the rotation angle. If the servo starts up, it goes to a middle position, corresponding to a ca. 1.5 mS pulse. For shorter pulses the servo turns in one direction, for longer pulses to the other. The range is limited, mostly to +/- 90 degree, the accurate values depend on the servo, as does the min/max widths. Mine had a range of a bit more than 180 degree for a min pulse width of 680 uS and a max width of 2.62 mS. However, you should not use these values but allow for some safety margin. Otherwise the motor may just not reach (e.g. due to slip or play) the position and the motor tries hard and gets hot.
Most servos have 3 wires that are color coded from bright to dark, the color may change. The brightest is the signal pin where the pulses are fed. The middle one, mostly red, goes to Plus and the darkest one (brown, black..) goes to ground.
Energia comes with a helpful server library. There an object: "Servo" is declared. You initiated the server by the method "attach". It comes in 2 varieties (assuming we declared an object "myservo"):
myservo.attach(pinNo);
myservo.attach(pinNo,minVal,maxVal);
where pinNo means the launchpad pin number of the pin used to control the servo. min/maxVal are the minimal/maximal pulse width you want to use. This is not clearly described in the library and confused me bit. In further commands you can confortably deal with degrees beween 0 and 180. The library will transform the degree values into pulse widths, where 0 degree corresponds to the minimal pulse width and 180 degree to the max you specified. If you do not specify min/max values, some default values are used. In may case the lower default value could not be reached and the motor got hot. Therefore, it is better to determine correct values and specify these, using the second form of "attach".
After initialization the servo goes to the 90 degree position. To send it to the 0 degree positio you would write:
myservo.write(0);
My servo did not respond quit linear. To send it to the 90 degree position I had to say 83 degree.
Here is a small test program:
// test for 9g Servo
// 1. Button press -> 0 degree
// 2. Button press -> 90 degree. Due due nonlinearity we need 83 degree
// 3. Button press -> 180 degree.
// 4. Button press -> 90 degree.
#define sig P1_4 // signal for servo
#define but P1_3 // button
#define minWidth 770 // min pulse width in uSec of servo (680 allowed)
#define maxWidth 2595 // max pulse width in uSec of servo (2620 allowed)
#include <Servo.h>
Servo myservo;
void setup()
{
myservo.attach(sig,minWidth,maxWidth);
pinMode(but, INPUT_PULLUP);
}
void loop()
{
while (digitalRead(but));
myservo.write(0); delay(500);
while (digitalRead(but));
myservo.write(83);delay(500);
while (digitalRead(but));
myservo.write(180);delay(500);
while (digitalRead(but));
myservo.write(83); delay(500);
}
Note that due to nonlinearity of the servo I need to specify 83 degree for a 90 degree rotation.
For test purposes you may power the servo directly from the launchpad (provided you do not have a monster servo with a hugh current consumption). In the enclosed picture I control the servo from launchpad pin P1_4 and power it directly from the launchpad.
Have fun, Daniel
-
DanielPHuber got a reaction from bluehash in [Solved]Energia not uploading to MSP432
Hello,
thanks for the response, it is greatly appreciated.
I happily arrived at a solution. It seemed that during the very first download some part of the memory was protected (I always suffer from too much security!)
What finally brought the solution was the unprotection of memory by the procedure described in the CodeComposer 6.1 manual: slau575B. On page 22, section 7 "Device Security" the procedure is described. The draw back is that you need CodeComposer to do this. Maybe it can also be done using the Cloud, but I am not sure.
There seems to be a further issue with firmware versions. Having used Energia and then CodeComposer, the CodeComposer complains about the firmware:
=====================
Error connecting to the target: (Error -1040 @ 0x0) A firmware update is required for the debug probe. Click the "Update" button to update the firmware and connect to the debug target. DO NOT UNPLUG THE DEBUG PROBE DURING THE UPDATE. (Emulation package 6.0.83.1) ============================ You are given the choice to update the firmware, what works and afterwards, you can download from CodeComposer. However, Energia seems to undo this update and next time you use CodeComposer we have the same issue again.
cheers, Daniel
-
DanielPHuber got a reaction from bluehash in 4 Digit display from Qifei
A display is a nice thing to have in many projects. A very cheap solution for only 2$ to 4$ is a 4 digit display from Qifei that can be found e.g. at www.Aliexpress.com. Of course, for this price you do not get a user manual! But what beats me is that I could not find a usable description on the internet. Therefore I had to rely on trial and error. Here is what I learned.
The device has 2 cascaded 74HC595 shift registers, one for the digit select, the other for segements select. By this the deviceu can only display one digit at a time. Therefore, you must time multiplex the for digits fast enough that there is no flickering visible.
Communication is serial, either by Bit banging or you may comfortably use the bult in SPI with the MSP430G2553 CPU. Energia is a very valuable tool for jobs like this. SPI will shift the data into the shift registers, but to output the data you have to shake (rising edge) the RCLK line, this will latch the output lines. Unfortunately this is not implemented in the Energia library for MSP430 (although the manual claims it) and you have to do it "by hand".
Negative logic is used to display a digit. That is, a 1 bit switches the corresponding segment off. The segments of a single digit are numbered like:
0
5 1
6
4 2
3 7
where 7 is the decimal point. E.g. to display a 0 with a decimal point you would send: B01000000
The device has 5 input pins:
Vcc: 3-5V
Gnd
DIO: the data signal
SCLK: the clock signal, data will be read on the rising edge
RCLK: on the rising edge of this signal the data and address will be latched to the output lines of the shift register
Details of the interface can be seen in the following example program. It is a counter that counts 1/10 seconds. The interface is in the subroutine "set":
// example for interfacing the 4 digit display from Qifei // 1/10 sec counter // The display can only show one single digit at a time. We // therefore need to time multiplex the display fast enough. #include <SPI.h> #define RCLK P2_0 // latch #define SCLK P1_5 // clock #define MOSI P1_7 // data to display //#define MISO P1_6 // data from peripherie, not used here const byte digit[] = //seven segment (+ 1 decimal point) bits { B11000000, //0 B11111001, //1 B10100100, //2 B10110000, //3 B10011001, //4 B10010010, //5 B10000010, //6 B11111000, //7 B10000000, //8 B10010000, //9 B10001000, //A B10000011, //b B11000110, //C B10100001, //d B10000110, //E B10001110 //F }; byte colDig[4] = // digits { B00000001, // digit 1 B00000010, // digit 2 B00000100, // digit 3 B00001000, // digit 4 }; byte dp= B01111111; //Decimal point. AND (&) with data byte empty= B11111111; // clear void setup() { pinMode (RCLK, OUTPUT); digitalWrite(RCLK,LOW); SPI.begin(); // SPI.setDataMode(SPI_MODE0); //default // SPI.setBitOrder(MSBFIRST); //default } void loop() { int del=1; long val=millis(); //decimal while(val>=1000000L) val-= 1000000L; int t1= val/100000L; val-= t1*100000L; int t2= val/10000L; val-= t2*10000L; int t3= val/1000L; val-= t3*1000L; int t4= val/100L; val-= t4*100L; // hexadecimal /* val/=100; int t1= val/4096L; val-= t1*4096L; int t2= val/256L; val-= t2*256L; int t3= val/16L; val-= t3*16L; int t4= val; */ for(int i1=0;i1<4;i1++){ set(digit[t1],colDig[3]);delay(del); set(digit[t2],colDig[2]);delay(del); set(digit[t3] & dp,colDig[1]);delay(del); set(digit[t4],colDig[0]);delay(del); set(empty,colDig[0]); } } // set one digit void set(byte val, byte dig){ digitalWrite(RCLK,LOW); SPI.transfer(val); // value SPI.transfer(dig); // which digit digitalWrite(RCLK,HIGH); }
Note that there is a similar device with the TM1637 chip that is different from what I described here.
-
DanielPHuber got a reaction from la4o in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from bluehash in LCD Game
Everybody here certainly loves the MSP430LaunchPad. But its is not easy to communicate info from the controller to the outside world. To switch some LED on and off is the most you can easily do.
Larsie (Lars Kristian Roland), a member of this forum made a wonderful project, a LCD booster back for the MSP430:
http://www.43oh.com/forum/viewtopic.php?f=9&t=2138
These are available in Larsies shop or in the 43oh shop. I lately bought one of thouse. To learn how to program the LCD controller, I first made some font demos and then the game here.
For an interactive game you need some buttons. On Larsies booster pack there is space for 4 small push buttons. I did not have buttons this small. Instead I made a small board with 4 larger buttons and connected them with a small ribbon cable.
The game displays a good guy icon (you) and several bad guys icons. You can cotrol the good guy using the for four buttons. The bad guys move around and you must try to avoid them. If a bad catches you, the game is finished.
At the beginning, you may choose 4 levels from easy to hard.
====> The code for the MSP430LaunchPad can be found here: <======
https://sites.google.com/site/msp430game/EscapeGame.zip
Here are some pictures:
The "key board"
The start screen
The game screen:
-
DanielPHuber got a reaction from RobG in LCD Game
Everybody here certainly loves the MSP430LaunchPad. But its is not easy to communicate info from the controller to the outside world. To switch some LED on and off is the most you can easily do.
Larsie (Lars Kristian Roland), a member of this forum made a wonderful project, a LCD booster back for the MSP430:
http://www.43oh.com/forum/viewtopic.php?f=9&t=2138
These are available in Larsies shop or in the 43oh shop. I lately bought one of thouse. To learn how to program the LCD controller, I first made some font demos and then the game here.
For an interactive game you need some buttons. On Larsies booster pack there is space for 4 small push buttons. I did not have buttons this small. Instead I made a small board with 4 larger buttons and connected them with a small ribbon cable.
The game displays a good guy icon (you) and several bad guys icons. You can cotrol the good guy using the for four buttons. The bad guys move around and you must try to avoid them. If a bad catches you, the game is finished.
At the beginning, you may choose 4 levels from easy to hard.
====> The code for the MSP430LaunchPad can be found here: <======
https://sites.google.com/site/msp430game/EscapeGame.zip
Here are some pictures:
The "key board"
The start screen
The game screen:
-
DanielPHuber got a reaction from Slartibartfast in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from kenemon in Solar water heating controller
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
-
DanielPHuber got a reaction from username in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from kenemon in Solar water heating controller
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
-
DanielPHuber got a reaction from nuetron in Solar water heating controller
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
-
DanielPHuber got a reaction from chopc in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from oPossum in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from Rickta59 in Solar water heating controller
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 }
========================================================
-
DanielPHuber got a reaction from turd in Solar water heating controller
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 }
========================================================