<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>imsolidstate &#187; C</title>
	<atom:link href="http://www.imsolidstate.com/archives/tag/c/feed" rel="self" type="application/rss+xml" />
	<link>http://www.imsolidstate.com</link>
	<description>Always improving things...</description>
	<lastBuildDate>Mon, 30 Aug 2010 17:54:03 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>SMS remote control</title>
		<link>http://www.imsolidstate.com/archives/708</link>
		<comments>http://www.imsolidstate.com/archives/708#comments</comments>
		<pubDate>Sat, 17 Jul 2010 02:42:23 +0000</pubDate>
		<dc:creator>imsolidstate</dc:creator>
				<category><![CDATA[Automation]]></category>
		<category><![CDATA[Electronics]]></category>
		<category><![CDATA[ATMega]]></category>
		<category><![CDATA[AVR]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[PCB]]></category>

		<guid isPermaLink="false">http://www.imsolidstate.com/?p=708</guid>
		<description><![CDATA[I&#8217;ve been expirimenting with cheap GSM cell phones as remote control devices. I wanted to be able to control stuff at my house just by sending a text from my phone. I also wanted to use an AVR since the options are pretty limitless as to what you can control. I started off simple with relay [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been expirimenting with cheap GSM cell phones as remote control devices. I wanted to be able to control stuff at my house just by sending a text from my phone. I also wanted to use an AVR since the options are pretty limitless as to what you can control. I started off simple with relay outputs for stuff like the garage door, outside lights, etc. It could also be useful for the AVR to send texts based on events, but I haven&#8217;t messed with this yet.</p>
<p><img class="alignnone size-large wp-image-729" title="Cell phone and AVR" src="http://www.imsolidstate.com/wp-content/uploads/2010/07/Stuff-039-1024x768.jpg" alt="Cell phone and AVR" width="645" height="484" /></p>
<p>I planned on just having the AVR recognize a particular text string, like &#8220;open garage&#8221; if I wanted to let someone in my house when I&#8217;m not there without giving them a key for example. It&#8217;s not overly secure, but you could add a number sequence as a prefix to the command that would be like a password. Then you could periodically change your password if you wanted.<span id="more-708"></span></p>
<p>I&#8217;m using cell phones from Siemens. They are older phones with true serial comms. You can score one for $20 or so from ebay. The only thing that sucks about these phones is they use PDU mode for text messages. It&#8217;s a really inconvenient way to send text strings. There&#8217;s a ton of setup to each packet and each character is encoded in seven bits. I haven&#8217;t been able to get my code working with the PDU method yet. It gets stuck trying to decode the received message. I&#8217;m going to post it here to see if anyone has any suggestions.  I&#8217;m all self-taught in C. I&#8217;ve never tried comparing strings before, so that bit is a little cumbersome. I don&#8217;t like using the built-in libraries to do that kind of stuff until I figure out how it works. The only thing I think turned out nice in the code is the unpacking function.</p>
<p>Obviously, you&#8217;d be ahead of the game if you found a serial-interface phone that used the regular text mode instead of PDU. I couldn&#8217;t find any phones that did text mode and didn&#8217;t have a USB interface. (at least not for cheap) Programming a USB host interface is beyond my capabilities on the AVR.</p>
<p>References: <a href="http://www.developershome.com/sms/">Developer&#8217;s Home SMS Tutorial</a></p>
<pre>#include &lt;avr/io.h&gt;
#include &lt;avr/interrupt.h&gt;

#define unpack_state1 0
#define unpack_state2 1
#define unpack_state3 2
#define unpack_state4 3
#define unpack_state5 4
#define unpack_state6 5
#define unpack_state7 6
#define unpack_state8 7
#define F_CPU       8000000
#define BAUD   9600
#define MYUBRR  (F_CPU/(BAUD*(unsigned long)16))-1

void init_USART(void);
void init_timer0(void);
void turnoff_timer0(void);
void init_timer1(void);
void check_sms(void);
void decode_sms(void);
char unpack(unsigned char octet);
void USART_write(unsigned char *buf);

volatile unsigned char rcv_buf[128];
volatile unsigned char rcv_buf_idx;
volatile unsigned char check_message, check_online;

void main(void)
{
 DDRD |= 0x03;
 PORTD &amp;= ~0x03;
 DDRC |= 0x3F;
 PORTC &amp;= ~0x3F;
 DDRB |= 0x3F;
 PORTB &amp;= ~0x3F;
 init_USART();
 init_timer0();
 sei();

 while(check_online&lt;0xF7)
 {
  rcv_buf_idx= 0x00;
  USART_write("at");
  while((rcv_buf_idx&lt;0x08)&amp;(check_online&lt;0x30)) {}
  if( rcv_buf[0x05]==0x4F &amp;
   rcv_buf[0x06]==0x4B)
  {
   PORTB &amp;= ~(1&lt;&lt;PORTB0);   //diagnostic leds
   PORTB &amp;= ~(1&lt;&lt;PORTB5);   //diagnostic leds
   check_online= 0xFA;
  }
  else
  {
   if(rcv_buf_idx&lt;0x01) PORTB |= (1&lt;&lt;PORTB5);
   PORTB |= (1&lt;&lt;PORTB0);
   while(check_online&lt;0xF4){} //8s
   check_online= 0x00;
  }
 }
 turnoff_timer0();
 init_timer1();

while(1)
 {
  if(check_message) check_sms();
 }
}

ISR(USART_RX_vect)
{
 rcv_buf[rcv_buf_idx]= UDR0;
 rcv_buf_idx++;
}

ISR(TIMER0_OVF_vect)
{
 check_online++;
}

ISR(TIMER1_OVF_vect)
{
 check_message= 0x01;
}

void init_USART(void)
{
 UCSR0B |= (1&lt;&lt;RXCIE0)|(1&lt;&lt;RXEN0)|(1&lt;&lt;TXEN0);
 UCSR0C |= (1&lt;&lt;UCSZ00)|(1&lt;&lt;UCSZ01);

 UBRR0L = MYUBRR;
 UBRR0H = (MYUBRR &gt;&gt; 8);
}

void init_timer0(void)
{
 TCCR0B |= (1&lt;&lt;CS00)|(1&lt;&lt;CS02);  //clk/1024
 TIMSK0 |= (1&lt;&lt;TOIE0);
}

void turnoff_timer0(void)
{
 TCCR0B &amp;= ~((1&lt;&lt;CS00)|(1&lt;&lt;CS02));  //clk/1024
 TIMSK0 &amp;= ~(1&lt;&lt;TOIE0);
}

void init_timer1(void)
{
 TCCR1B |= (1&lt;&lt;CS10)|(1&lt;&lt;CS12);  //clk/1024
 TIMSK1 |= (1&lt;&lt;TOIE1);
}

void check_sms(void)
{
 rcv_buf_idx= 0x00;
 USART_write("at+cmgl");
 while(TCNT1&lt;0x90) {}
 if( rcv_buf[0x0A]==0x4F &amp;
  rcv_buf[0x0B]==0x4B) {PORTB &amp;= ~(1&lt;&lt;PORTB1); PORTB &amp;= ~(1&lt;&lt;PORTB5);}
 else if(rcv_buf[0x0A]=='+' &amp;
   rcv_buf[0x0B]=='C' &amp;
   rcv_buf[0x0C]=='M' &amp;
   rcv_buf[0x0D]=='G' &amp;
   rcv_buf[0x0E]=='L' &amp;
   rcv_buf[0x0F]==':') {PORTB &amp;= ~(1&lt;&lt;PORTB1); PORTB &amp;= ~(1&lt;&lt;PORTB5); decode_sms();}
 else
 {
  PORTB |= (1&lt;&lt;PORTB1);
  if(rcv_buf_idx&lt;0x01) PORTB |= (1&lt;&lt;PORTB5);
 }
 check_message= 0x00;
}

void decode_sms(void)
{
 unsigned char offset;
 while(rcv_buf_idx&lt;0x17){}
 unsigned char lenTPDU= (((rcv_buf[0x16] &amp;= ~0x30)*10) + (rcv_buf[0x17] &amp;= ~0x30));
 lenTPDU*= 2;
 while(rcv_buf_idx&lt;0x1B){}
 unsigned char lenSMSC= (((rcv_buf[0x1A] &amp;= ~0x30)*10) + (rcv_buf[0x1B] &amp;= ~0x30));
 lenSMSC*= 2;
 offset= 0x1B;
 while(rcv_buf_idx&lt;(lenSMSC+offset+lenTPDU)){}
 //ignore SMSC part
 //ignore first byte
 offset= 0x1E;
 unsigned char len_sender_number= ((rcv_buf[offset+lenSMSC] &amp;= ~0x30)*10);
 offset= 0x1F;
 if((rcv_buf[offset+lenSMSC]&gt;0x2F) &amp; (rcv_buf[offset+lenSMSC]&lt;0x3A))
  len_sender_number+= (rcv_buf[offset+lenSMSC] &amp;= ~0x30);
 else if(rcv_buf[offset+lenSMSC]=='A') len_sender_number+= 10;
 else if(rcv_buf[offset+lenSMSC]=='B') len_sender_number+= 11;
 else if(rcv_buf[offset+lenSMSC]=='C') len_sender_number+= 12;
 else if(rcv_buf[offset+lenSMSC]=='D') len_sender_number+= 13;
 else if(rcv_buf[offset+lenSMSC]=='E') len_sender_number+= 14;
 else if(rcv_buf[offset+lenSMSC]=='F') len_sender_number+= 15;
 if(len_sender_number%2) len_sender_number++;
 rcv_buf_idx= (0x1B+lenSMSC);
 unsigned char i= len_sender_number;
 while(i)
 {
  unsigned char *sender_number= rcv_buf[++rcv_buf_idx];
  sender_number++;
  *sender_number= rcv_buf[--rcv_buf_idx];
  sender_number++;
  rcv_buf_idx += 0x02;
  i-= 0x02;
 }
 offset= 0x35;
 unsigned char num_septets= ((rcv_buf[offset+lenSMSC+len_sender_number] &amp;= ~0x30)*10);
 offset= 0x36;
 if((rcv_buf[offset+lenSMSC+len_sender_number]&gt;0x2F) &amp; (rcv_buf[offset+lenSMSC+len_sender_number]&lt;0x3A))
  num_septets+= (rcv_buf[offset+lenSMSC+len_sender_number] &amp;= ~0x30);
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='A') num_septets+= 10;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='B') num_septets+= 11;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='C') num_septets+= 12;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='D') num_septets+= 13;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='E') num_septets+= 14;
 else if(rcv_buf[offset+lenSMSC+len_sender_number]=='F') num_septets+= 15;
 offset= 0x37;
 rcv_buf_idx= (offset+lenSMSC+len_sender_number);
 unsigned char loop= 0;
 unsigned char message[16];
 unsigned char message_idx= 0x00;
 offset= 0x1C;
 while(rcv_buf_idx&lt;=(offset+lenTPDU+lenSMSC))
 {
  unsigned char octet= 0;
  if(rcv_buf[rcv_buf_idx]=='A') octet |= 0xA0;
  else if(rcv_buf[rcv_buf_idx]=='B') octet |= 0xB0;
  else if(rcv_buf[rcv_buf_idx]=='C') octet |= 0xC0;
  else if(rcv_buf[rcv_buf_idx]=='D') octet |= 0xD0;
  else if(rcv_buf[rcv_buf_idx]=='E') octet |= 0xE0;
  else if(rcv_buf[rcv_buf_idx]=='F') octet |= 0xF0;
  else octet= ((rcv_buf[rcv_buf_idx] &amp;= ~0x30)*10);
  rcv_buf_idx++;
  if(rcv_buf[rcv_buf_idx]=='A') octet |= 0x0A;
  else if(rcv_buf[rcv_buf_idx]=='B') octet |= 0x0B;
  else if(rcv_buf[rcv_buf_idx]=='C') octet |= 0x0C;
  else if(rcv_buf[rcv_buf_idx]=='D') octet |= 0x0D;
  else if(rcv_buf[rcv_buf_idx]=='E') octet |= 0x0E;
  else if(rcv_buf[rcv_buf_idx]=='F') octet |= 0x0F;
  else octet+= (rcv_buf[rcv_buf_idx] &amp;= ~0x30);
  rcv_buf_idx++;
  message[message_idx]= unpack(octet);
  message_idx++;
  if(++loop==7)
  {
   message[message_idx]= unpack(0x00);
   message_idx++;
   loop=0;
  }
 }
 if( message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='1' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC0);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='1' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC0);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='2' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC1);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='2' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC1);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='3' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC2);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='3' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC2);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='4' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC3);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='4' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC3);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='5' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC4);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='5' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC4);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='6' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='n') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC |= (1&lt;&lt;PORTC5);}
 else if(message[0x00]=='r' &amp;
  message[0x01]=='e' &amp;
  message[0x02]=='l' &amp;
  message[0x03]=='a' &amp;
  message[0x04]=='y' &amp;
  message[0x05]==' ' &amp;
  message[0x06]=='6' &amp;
  message[0x07]==' ' &amp;
  message[0x08]=='o' &amp;
  message[0x09]=='f' &amp;
  message[0x0A]=='f') {PORTB &amp;= ~(1&lt;&lt;PORTB2); PORTC &amp;= ~(1&lt;&lt;PORTC5);}
 else PORTB |= (1&lt;&lt;PORTB2);
}

char unpack(unsigned char octet)
{
 static unsigned char unpack_state, remain_val;
 unsigned char septet, masked_val;

 switch(unpack_state)
 {
  case unpack_state1:
   septet= (octet &amp; 0x7F);
   remain_val= (octet &amp; 0x80);
   unpack_state= unpack_state2;
   break;
  case unpack_state2:
   masked_val= (octet &amp; 0x3F);
   septet= (masked_val&lt;&lt;1) | (remain_val&gt;&gt;7);
   remain_val= (octet &amp; 0xC0);
   unpack_state= unpack_state3;
   break;
  case unpack_state3:
   masked_val= (octet &amp; 0x1F);
   septet= (masked_val&lt;&lt;2) | (remain_val&gt;&gt;6);
   remain_val= (octet &amp; 0xE0);
   unpack_state= unpack_state4;
   break;
  case unpack_state4:
   masked_val= (octet &amp; 0x0F);
   septet= (masked_val&lt;&lt;3) | (remain_val&gt;&gt;5);
   remain_val= (octet &amp; 0xF0);
   unpack_state= unpack_state5;
   break;
  case unpack_state5:
   masked_val= (octet &amp; 0x07);
   septet= (masked_val&lt;&lt;4) | (remain_val&gt;&gt;4);
   remain_val= (octet &amp; 0xF8);
   unpack_state= unpack_state6;
   break;
  case unpack_state6:
   masked_val= (octet &amp; 0x03);
   septet= (masked_val&lt;&lt;5) | (remain_val&gt;&gt;3);
   remain_val= (octet &amp; 0xFC);
   unpack_state= unpack_state7;
   break;
  case unpack_state7:
   masked_val= (octet &amp; 0x01);
   septet= (masked_val&lt;&lt;6) | (remain_val&gt;&gt;2);
   remain_val= (octet &amp; 0xFE);
   unpack_state= unpack_state8;
   break;
  case unpack_state8:
   septet= (remain_val&gt;&gt;1);
   unpack_state= unpack_state1;
   break;
 }
 return septet;
}

void USART_write(unsigned char *buf)
{
 while(*buf)
    {
       while ( !( UCSR0A &amp; (1&lt;&lt;UDRE0)) ){}
       UDR0= *buf;
       buf++;
    }
 while ( !( UCSR0A &amp; (1&lt;&lt;UDRE0)) ){}
 UDR0= 0x0D;
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.imsolidstate.com/archives/708/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Build a spot welder from a battery charger</title>
		<link>http://www.imsolidstate.com/archives/590</link>
		<comments>http://www.imsolidstate.com/archives/590#comments</comments>
		<pubDate>Thu, 11 Mar 2010 04:53:39 +0000</pubDate>
		<dc:creator>imsolidstate</dc:creator>
				<category><![CDATA[Electronics]]></category>
		<category><![CDATA[ATMega]]></category>
		<category><![CDATA[AVR]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Current sense]]></category>
		<category><![CDATA[LCD]]></category>
		<category><![CDATA[PCB]]></category>

		<guid isPermaLink="false">http://www.imsolidstate.com/?p=590</guid>
		<description><![CDATA[I ran across a battery charger a while ago that was collecting dust. I looked inside and saw the transformer, heatsinks, high current bridge rectifier and SCR and knew I could do something with it. So I turned it into a spot welder.

I originally intended this project to weld thin sheetmetal tabs to stuff to [...]]]></description>
			<content:encoded><![CDATA[<p>I ran across a battery charger a while ago that was collecting dust. I looked inside and saw the transformer, heatsinks, high current bridge rectifier and SCR and knew I could do something with it. So I turned it into a spot welder.</p>
<p><img class="size-large wp-image-598 alignnone" title="Spot Welder" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelder-028-1024x768.jpg" alt="SpotWelder 028" width="645" height="484" /></p>
<p>I originally intended this project to weld thin sheetmetal tabs to stuff to act as solder tabs. The project has not been as easy as I originally thought though. (It also suffered some scope creep) It&#8217;s my first crack at 5V logic mixed with line AC voltage, and for rolling my own power supply. I used a step-down transformer, bridge regulator and a capacitor to feed an LDO regulator for the control circuit. With the low current draw of the controller, the voltage input to the regulator was relatively free from any ripple thanks to the capacitor.</p>
<p>I ended up frying a processor, LCD, and a couple other components due to a dumb move while troubleshooting the circuit, and overlooking a capacitor&#8217;s voltage rating. 120VAC will eat 5V stuff for lunch.</p>
<p><img class="alignnone size-large wp-image-599" title="Spot Welder guts" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelder-030-1024x768.jpg" alt="Spot Welder guts" width="645" height="484" /></p>
<p>The control circuit basically modulates the SCR, which is hooked up to the output of the bridge rectifier after a step-down transformer. The controller allows for adjustment of duration of the weld and amount of the rectified AC phase that is delivered to the workpiece. The controller holds off the SCR until a pre-determined time of each half phase to control power delivery. An analog comparator detects the zero point of the phase for timing purposes, via a seperate bridge rectifier that has it&#8217;s ouput fed through a large resistor to the comparator. A zener clamps the current-limited voltage at 4.8V so as not to damage the micro&#8217;s input. A high-to-low transition on the comparator triggers the zero crossing timer. The threshhold voltage is adjustable by an on-board pot.</p>
<p>I also added an Allegro hall effect current sensor that I had lying around from my <a href="http://www.imsolidstate.com/archives/9">alternator current sense project</a>. It&#8217;s overkill, but it measures the amount of peak current being delivered and displays it on the LCD.</p>
<p><img class="size-medium wp-image-603 alignleft" title="SpotWelder 034" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelder-0341-300x225.jpg" alt="SpotWelder 034" width="300" height="225" /></p>
<p>The controller is an ATmega88PA running at 8Mhz. Firmware is written in C with AVRStudio and AVR-GCC. The micro reads the power and duration settings, displays that on the LCD, along with the max current for the last weld cycle and the temperature of the mega&#8217;s on-chip sensor. The controller also handles timing duties, zero crossing detection, and control of the SCR gate. The gate is fired by a P-channel MOSFET, with the FET&#8217;s gate driven by an NPN BJT on one of the micro&#8217;s pins. A footswitch is used as input to the micro to trigger a weld cycle. Both the footswitch input and the zero crossings are buffered by a simple three-sample debouncing routine to prevent erroneous triggers. The system also checks for the footswitch input on power up and after the weld cycle is complete, and waits if the footswitch is down with a message on the LCD to release the footswitch. This allows for safety as well as eliminating any unintended re-triggers at very short durations. Duration is adjustable from roughly one ac cycle to 60 cycles (1 sec). Power control allows from 5% to 95% of each half phase to be delivered to the workpiece.</p>
<p>The SCR&#8217;s cathode voltage is available at PORTC2 as a 10:1 voltage divider, and clamped with a zener to prevent damage to the micro. I didn&#8217;t need it, so it&#8217;s not used in the code.</p>
<p>I&#8217;ve also added a power resistor to the output to limit current. I used carbon-carbon as a power resistor (I work in a carbon plant) since it&#8217;s free and power resistors are expensive. You only need a few tenths of an ohm to limit the current to a level that won&#8217;t destroy the diodes and SCR. I&#8217;m overdriving mine at about 130A maximum. It seems to handle it fine for the short bursts.  [Edit: 130A isn't enough though. I may rewire so the diodes/SCR are on the input side and push the current higher by removing or modifying the resistor. Pressure of the electrodes on the joint is also important, still figuring that out.]</p>
<p>Here&#8217;s some drive waveforms: yellow is the output voltage (it&#8217;s at 50V/div so it looks small), purple is the output current measured by the hall sensor, blue is the FET&#8217;s gate that turns on the SCR, and green is the bridge voltage.</p>
<p><img class="alignnone size-medium wp-image-604" title="Low drive" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelder-031-300x225.jpg" alt="Low drive" width="300" height="225" /><img class="alignnone size-medium wp-image-605" title="Medium drive" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelder-032-300x225.jpg" alt="Medium drive" width="300" height="225" /></p>
<p>This project has got me thinking about modifying my old &#8221;buzzbox&#8221; AC welder. I&#8217;ve got some big capacitors and IGBTs from a couple old motor drives that could give me a really nice TIG welding power supply. I think I&#8217;ve read you can weld high frequency (1-2kHz?) square-wave without needing any HF section. If I remember right square-wave with a positive DC offset is sort of the ultimate TIG welder. Anybody with comments or information about that feel free to drop me a line.</p>
<p>Continue reading for the schematic, PCB layout, and code.</p>
<p>References: <a href="http://www.millerwelds.com/pdf/Resistance.pdf">Miller Resistance Spot Welding</a></p>
<p><span id="more-590"></span></p>
<p>The schematic: (Click on the picture for full size)</p>
<p><a href="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelderSchem3.png"><img class="alignnone size-large wp-image-659" title="SpotWelder Schematic" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelderSchem3-1024x528.png" alt="SpotWelder Schematic" width="645" height="333" /></a></p>
<p>The board: (Click for full-size image)</p>
<p><a href="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelderLayout3.png"><img class="alignnone size-large wp-image-661" title="SpotWelder Layout" src="http://www.imsolidstate.com/wp-content/uploads/2010/03/SpotWelderLayout3-1024x822.png" alt="SpotWelder Layout" width="645" height="518" /></a></p>
<p>And the code. I think it&#8217;s all correct but I had to rewire a couple things on mine so you might do well to doublecheck the I/O is all correct.</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<p>#include &lt;avr/io.h&gt;<br />
#include &lt;util/delay.h&gt;<br />
#include &lt;avr/interrupt.h&gt;<br />
//#include &lt;avr/wdt.h&gt; // can&#8217;t get the watchdog to work yet</p>
<p>#define F_CPU 8000000; //8MHz</p>
<p>//function declarations<br />
void lcd_write_byte(unsigned char CONTROL, unsigned char DATA);<br />
void initLCD(void);<br />
void updateLCD(void);<br />
void switch_up(void);<br />
void weld_message(void);</p>
<p>//global variables<br />
volatile unsigned int power, duration, temperature;<br />
volatile unsigned char current, max_current;</p>
<p>void main(void)<br />
{<br />
 unsigned int old_power, old_duration= 0; //variables for comparison</p>
<p> PRR &amp;= ~(1&lt;&lt;PRADC);  //disable ADC power reduction<br />
 ADMUX |= (1&lt;&lt;REFS0); //setup VCC as reference<br />
 ADMUX |= (1&lt;&lt;ADLAR); //left adjust result for 8 bit ADC<br />
 ADCSRA |= (1&lt;&lt;ADPS0)|(1&lt;&lt;ADPS1)|(1&lt;&lt;ADPS2); //prescaler clk/64 125kHz @ 8MHz clock<br />
 ADCSRA |= (1&lt;&lt;ADEN); //enable ADC</p>
<p> TCCR1B |= (1&lt;&lt;CS10); //enable timer1, no prescale (clk/1)</p>
<p> ACSR |= (1&lt;&lt;ACIS1); //enable analog comparator falling edge interrupt<br />
 ACSR |= (1&lt;&lt;ACIE); //enable analog comparator interrupt</p>
<p> DDRC |= (1&lt;&lt;PORTC3); //SCR gate drive<br />
 DDRC &amp;= ~((1&lt;&lt;PORTC0)|(1&lt;&lt;PORTC1)|(1&lt;&lt;PORTC2)); //set as input<br />
 PORTC &amp;= ~((1&lt;&lt;PORTC0)|(1&lt;&lt;PORTC1)|(1&lt;&lt;PORTC2)); //PUD</p>
<p> DDRD |= (1&lt;&lt;PORTD0)|(1&lt;&lt;PORTD1)|(1&lt;&lt;PORTD2)|(1&lt;&lt;PORTD3)|(1&lt;&lt;PORTD4)|(1&lt;&lt;PORTD5);<br />
 DDRD &amp;= ~((1&lt;&lt;PORTD6)|(1&lt;&lt;PORTD7)); //set as input<br />
 PORTD &amp;= ~((1&lt;&lt;PORTD6)|(1&lt;&lt;PORTD7)); //PUD</p>
<p> DDRB &amp;= ~(1&lt;&lt;PORTB0);  //footswitch input<br />
 PORTB &amp;= ~(1&lt;&lt;PORTB0);  //PUD</p>
<p> initLCD(); //set up LCD</p>
<p> switch_up(); //check footswitch</p>
<p> max_current= 0&#215;7F;<br />
 updateLCD(); //display settings</p>
<p>// wdt_reset(); //reset the watchdog timer<br />
// WDTCSR |= (1&lt;&lt;WDCE)|(1&lt;&lt;WDE); //clear the system reset/ change enable bits<br />
// WDTCSR |= (1&lt;&lt;WDE)|(1&lt;&lt;WDP0)|(1&lt;&lt;WDP1)|(1&lt;&lt;WDP2); //set new prescaler, 2 seconds</p>
<p> <br />
 while(1)<br />
 {<br />
  old_power= power;  //set up the comparsison value before getting new value<br />
  ADMUX &amp;= ~((1&lt;&lt;MUX0)|(1&lt;&lt;MUX1)|(1&lt;&lt;MUX2)|(1&lt;&lt;MUX3)); //sample ADC0 (power setting)<br />
  ADCSRA |= (1&lt;&lt;ADSC); //start conversion<br />
  while(ADCSRA &amp; (1&lt;&lt;ADSC)){} //wait for conversion<br />
  power= ADCH;<br />
  power*= 234; //scale power for use later: ((2^8)*234)= 59904 max value<br />
      //59904 clk cycles= (1/8000000)*59904= 7.5ms<br />
      //7.5ms= (.0075/(1/120))*100= 90% of one half wave (60Hz)<br />
      //with these values power can be up to 90% of each half wave,<br />
      //which with the 5% coded into the ISR, yields a range of 5%-95%.</p>
<p>  old_duration= duration;  //set up the comparsison value before getting new value<br />
  ADMUX |= (1&lt;&lt;MUX0);  //sample ADC1 (duration setting)<br />
  ADCSRA |= (1&lt;&lt;ADSC); //start conversion<br />
  while(ADCSRA &amp; (1&lt;&lt;ADSC)){} //wait for conversion<br />
  duration= ADCH; <br />
  duration= (duration&gt;&gt;1); //convert to 7-bit number, limits duration to ~1 second<br />
  if(duration&lt;=0&#215;0007) duration= 0&#215;0000;<br />
   else duration-= 0&#215;0007; //subtract 7 so duration can&#8217;t exceed 3 digits (999ms)</p>
<p>  ADMUX &amp;= ~((1&lt;&lt;MUX0)|(1&lt;&lt;MUX1)|(1&lt;&lt;MUX2));  //set up for ADC8 (temp)<br />
  ADMUX |= (1&lt;&lt;MUX3);<br />
  ADMUX |= (1&lt;&lt;REFS0)|(1&lt;&lt;REFS1);  //1.1V ADC reference<br />
  ADMUX &amp;= ~(1&lt;&lt;ADLAR); //undo left adjust result for 8 bit ADC <br />
  ADCSRA |= (1&lt;&lt;ADSC); //start conversion<br />
  while(ADCSRA &amp; (1&lt;&lt;ADSC)){} //wait for conversion<br />
  temperature= ADC;<br />
  temperature/= 12;  //scale temp value to degrees C <br />
  ADMUX |= (1&lt;&lt;ADLAR);  //restore left adjust result <br />
  ADMUX &amp;= ~(1&lt;&lt;REFS1);  //restore VCC ADC reference</p>
<p>  if(power!=old_power) updateLCD(); //update LCD if values changed<br />
  else if(duration!=old_duration) updateLCD();<br />
  if((!(PINB &amp; (1&lt;&lt;PINB0))) &amp; (duration&gt;0)) //check for footswitch input<br />
  {           <br />
   char j= 0;</p>
<p>      for(j= 0;j&lt; 3;)  //three sample noise filter<br />
   {<br />
    if(!(PINB &amp; (1&lt;&lt;PINB0))) j++;  //increment loop value if PORTC2 (fsw) is low<br />
    else j= 4;  //break out of the loop if high<br />
      }  <br />
                        <br />
      if(j==3)  //should only get here if we got three low samples<br />
   {<br />
    weld_message();<br />
    ADMUX |= (1&lt;&lt;MUX0)|(1&lt;&lt;MUX1)|(1&lt;&lt;MUX2); //sample ADC7 (current sensor)<br />
    ADMUX &amp;= ~(1&lt;&lt;MUX3);<br />
    ADCSRA |= (1&lt;&lt;ADATE); //ADC free runnnng mode<br />
    ADCSRA |= (1&lt;&lt;ADSC); //start conversion<br />
    _delay_us(5); //wait for ADC&#8217;s first reading<br />
    max_current= 0&#215;0000;  //reset max current from last cycle<br />
    sei();<br />
    while((!(PINB &amp; (1&lt;&lt;PINB0))) &amp; (duration&gt;0)){}  //wait for zero cross<br />
    cli();<br />
    ADCSRA &amp;= ~(1&lt;&lt;ADATE);  //turn off free running<br />
    switch_up(); //wait for footswitch release<br />
    updateLCD();<br />
   }<br />
  } <br />
 }<br />
}</p>
<p> <br />
ISR (ANALOG_COMP_vect)<br />
{ <br />
 char i= 0;</p>
<p>    for(i= 0;i&lt; 3;)  //three sample noise filter<br />
 {<br />
  if(!(ACSR &amp; (1&lt;&lt;ACO))) i++;  //increment loop value if ACO is low<br />
  else i= 4;  //break out of the loop if high<br />
    }  <br />
                        <br />
    if(i==3)  //should only get here if we got three low samples,  <br />
 {    //which indicates a zero crossing.  <br />
  TCNT1= 0;<br />
  while(TCNT1&lt; power){} //wait for phase rotation<br />
  PORTC |= (1&lt;&lt;PORTC3); //fire SCR<br />
  while(TCNT1&lt; 63333)  //wait for 7.9ms, 95% of one half cycle (60Hz)<br />
  {<br />
   current= ADCH;<br />
   if(current&gt;max_current) max_current= current;  //record the highest value<br />
  } <br />
  PORTC &amp;= ~(1&lt;&lt;PORTC3);  //turn off SCR gate<br />
  duration&#8211;;<br />
 }<br />
}</p>
<p> </p>
<p>void initLCD(void)<br />
{<br />
 _delay_ms(250);  // Wait for HD44780<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD4);<br />
 PORTD |= (1&lt;&lt;PORTD1);<br />
 PORTD |= (1&lt;&lt;PORTD0);  <br />
 PORTD |= (1&lt;&lt;PORTD5); // function set<br />
 _delay_ms(2);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);<br />
 _delay_ms(20);  <br />
 PORTD |= (1&lt;&lt;PORTD5); // function set<br />
 _delay_ms(2);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);<br />
 _delay_ms(10);  <br />
 PORTD |= (1&lt;&lt;PORTD5); // function set<br />
 _delay_ms(2);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);<br />
 _delay_ms(10);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD0);<br />
 PORTD |= (1&lt;&lt;PORTD5); // initialize to 4 bit<br />
 _delay_ms(2);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);<br />
 _delay_ms(10);</p>
<p> lcd_write_byte(0,0&#215;28);  //set interface width, # of lines, and font size<br />
 lcd_write_byte(0,0&#215;0C);  //display on<br />
 lcd_write_byte(0,0&#215;01);  //clear display<br />
 lcd_write_byte(0,0&#215;06);  //increment address by one, shift cursor at write<br />
}</p>
<p> </p>
<p>void lcd_write_byte(unsigned char CONTROL, unsigned char DATA)<br />
{<br />
 if(CONTROL == 1) PORTD |= (1&lt;&lt;PORTD4); else PORTD &amp;= ~(1&lt;&lt;PORTD4);<br />
 if((DATA &amp; 0&#215;80) == 0&#215;80) PORTD |= (1&lt;&lt;PORTD3); else PORTD &amp;= ~(1&lt;&lt;PORTD3);<br />
 if((DATA &amp; 0&#215;40) == 0&#215;40) PORTD |= (1&lt;&lt;PORTD2); else PORTD &amp;= ~(1&lt;&lt;PORTD2);<br />
 if((DATA &amp; 0&#215;20) == 0&#215;20) PORTD |= (1&lt;&lt;PORTD1); else PORTD &amp;= ~(1&lt;&lt;PORTD1);<br />
 if((DATA &amp; 0&#215;10) == 0&#215;10) PORTD |= (1&lt;&lt;PORTD0); else PORTB &amp;= ~(1&lt;&lt;PORTD0);<br />
 PORTD |= (1&lt;&lt;PORTD5);<br />
 _delay_ms(1);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);</p>
<p> if((DATA &amp; 0&#215;08) == 0&#215;08) PORTD |= (1&lt;&lt;PORTD3); else PORTD &amp;= ~(1&lt;&lt;PORTD3);<br />
 if((DATA &amp; 0&#215;04) == 0&#215;04) PORTD |= (1&lt;&lt;PORTD2); else PORTD &amp;= ~(1&lt;&lt;PORTD2);<br />
 if((DATA &amp; 0&#215;02) == 0&#215;02) PORTD |= (1&lt;&lt;PORTD1); else PORTD &amp;= ~(1&lt;&lt;PORTD1);<br />
 if((DATA &amp; 0&#215;01) == 0&#215;01) PORTD |= (1&lt;&lt;PORTD0); else PORTD &amp;= ~(1&lt;&lt;PORTD0);<br />
 PORTD |= (1&lt;&lt;PORTD5);<br />
 _delay_ms(2);<br />
 PORTD &amp;= ~(1&lt;&lt;PORTD5);<br />
 _delay_ms(10);<br />
}</p>
<p> </p>
<p>void updateLCD(void)<br />
{<br />
 unsigned int temp_duration, temp_power, temp_max_current;<br />
 <br />
 temp_duration= (duration*8); //scale duration for BCD conversion to milliseconds          //<br />
 unsigned int duration_BCD= ((((temp_duration/10)+((temp_duration/100)*6))*16)+(temp_duration%10));<br />
 <br />
 unsigned char ONES= 0&#215;00;<br />
 unsigned char TENS= 0&#215;00;<br />
 unsigned char HUND= 0&#215;00;</p>
<p> if((duration_BCD &amp; 0&#215;0800) == 0&#215;0800) HUND |= 0&#215;08; else HUND &amp;= ~0&#215;08;<br />
 if((duration_BCD &amp; 0&#215;0400) == 0&#215;0400) HUND |= 0&#215;04; else HUND &amp;= ~0&#215;04;<br />
 if((duration_BCD &amp; 0&#215;0200) == 0&#215;0200) HUND |= 0&#215;02; else HUND &amp;= ~0&#215;02;<br />
 if((duration_BCD &amp; 0&#215;0100) == 0&#215;0100) HUND |= 0&#215;01; else HUND &amp;= ~0&#215;01;</p>
<p> if((duration_BCD &amp; 0&#215;0080) == 0&#215;0080) TENS |= 0&#215;08; else TENS &amp;= ~0&#215;08;<br />
 if((duration_BCD &amp; 0&#215;0040) == 0&#215;0040) TENS |= 0&#215;04; else TENS &amp;= ~0&#215;04;<br />
 if((duration_BCD &amp; 0&#215;0020) == 0&#215;0020) TENS |= 0&#215;02; else TENS &amp;= ~0&#215;02;<br />
 if((duration_BCD &amp; 0&#215;0010) == 0&#215;0010) TENS |= 0&#215;01; else TENS &amp;= ~0&#215;01;</p>
<p> if((duration_BCD &amp; 0&#215;0008) == 0&#215;0008) ONES |= 0&#215;08; else ONES &amp;= ~0&#215;08;<br />
 if((duration_BCD &amp; 0&#215;0004) == 0&#215;0004) ONES |= 0&#215;04; else ONES &amp;= ~0&#215;04;<br />
 if((duration_BCD &amp; 0&#215;0002) == 0&#215;0002) ONES |= 0&#215;02; else ONES &amp;= ~0&#215;02;<br />
 if((duration_BCD &amp; 0&#215;0001) == 0&#215;0001) ONES |= 0&#215;01; else ONES &amp;= ~0&#215;01;</p>
<p> ONES |= 0&#215;30;<br />
 TENS |= 0&#215;30;<br />
 HUND |= 0&#215;30;</p>
<p> lcd_write_byte(0&#215;00, 0&#215;01); //clear screen<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, HUND);<br />
 lcd_write_byte(0&#215;01, TENS);<br />
 lcd_write_byte(0&#215;01, ONES);<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;6D); //&#8217;m&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;69); //&#8217;i&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6C); //&#8217;l&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6C); //&#8217;l&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;69); //&#8217;i&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;63); //&#8217;c&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6F); //&#8217;o&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6E); //&#8217;n&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;64); //&#8217;d&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
 temp_power= (power/665);  //scale power for BCD conversion to percent<br />
 temp_power=(95-temp_power);<br />
 unsigned int power_BCD= ((temp_power/10)*16)+(temp_power%10);</p>
<p> ONES= 0&#215;00;<br />
 TENS= 0&#215;00;</p>
<p> if((power_BCD &amp; 0&#215;0080) == 0&#215;0080) TENS |= 0&#215;08; else TENS &amp;= ~0&#215;08;<br />
 if((power_BCD &amp; 0&#215;0040) == 0&#215;0040) TENS |= 0&#215;04; else TENS &amp;= ~0&#215;04;<br />
 if((power_BCD &amp; 0&#215;0020) == 0&#215;0020) TENS |= 0&#215;02; else TENS &amp;= ~0&#215;02;<br />
 if((power_BCD &amp; 0&#215;0010) == 0&#215;0010) TENS |= 0&#215;01; else TENS &amp;= ~0&#215;01;</p>
<p> if((power_BCD &amp; 0&#215;0008) == 0&#215;0008) ONES |= 0&#215;08; else ONES &amp;= ~0&#215;08;<br />
 if((power_BCD &amp; 0&#215;0004) == 0&#215;0004) ONES |= 0&#215;04; else ONES &amp;= ~0&#215;04;<br />
 if((power_BCD &amp; 0&#215;0002) == 0&#215;0002) ONES |= 0&#215;02; else ONES &amp;= ~0&#215;02;<br />
 if((power_BCD &amp; 0&#215;0001) == 0&#215;0001) ONES |= 0&#215;01; else ONES &amp;= ~0&#215;01;</p>
<p> ONES |= 0&#215;30;<br />
 TENS |= 0&#215;30;</p>
<p> lcd_write_byte(0&#215;00, 0xC0); //go to second line<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, TENS);<br />
 lcd_write_byte(0&#215;01, ONES);<br />
 lcd_write_byte(0&#215;01, 0&#215;25); //&#8217;%&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;6F); //&#8217;o&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;66); //&#8217;f&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;70); //&#8217;p&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;68); //&#8217;h&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;61); //&#8217;a&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
 temp_max_current= max_current;<br />
 temp_max_current-= 0&#215;7F;  //remove 2.5V sensor offset<br />
 temp_max_current*= 3; //scaling<br />
 temp_max_current/= 2; //scaling</p>
<p> unsigned int max_current_BCD= ((((temp_max_current/10)+((temp_max_current/100)*6))*16)+(temp_max_current%10));<br />
 <br />
 ONES= 0&#215;00;<br />
 TENS= 0&#215;00;<br />
 HUND= 0&#215;00;</p>
<p> if((max_current_BCD &amp; 0&#215;0800) == 0&#215;0800) HUND |= 0&#215;08; else HUND &amp;= ~0&#215;08;<br />
 if((max_current_BCD &amp; 0&#215;0400) == 0&#215;0400) HUND |= 0&#215;04; else HUND &amp;= ~0&#215;04;<br />
 if((max_current_BCD &amp; 0&#215;0200) == 0&#215;0200) HUND |= 0&#215;02; else HUND &amp;= ~0&#215;02;<br />
 if((max_current_BCD &amp; 0&#215;0100) == 0&#215;0100) HUND |= 0&#215;01; else HUND &amp;= ~0&#215;01;</p>
<p> if((max_current_BCD &amp; 0&#215;0080) == 0&#215;0080) TENS |= 0&#215;08; else TENS &amp;= ~0&#215;08;<br />
 if((max_current_BCD &amp; 0&#215;0040) == 0&#215;0040) TENS |= 0&#215;04; else TENS &amp;= ~0&#215;04;<br />
 if((max_current_BCD &amp; 0&#215;0020) == 0&#215;0020) TENS |= 0&#215;02; else TENS &amp;= ~0&#215;02;<br />
 if((max_current_BCD &amp; 0&#215;0010) == 0&#215;0010) TENS |= 0&#215;01; else TENS &amp;= ~0&#215;01;</p>
<p> if((max_current_BCD &amp; 0&#215;0008) == 0&#215;0008) ONES |= 0&#215;08; else ONES &amp;= ~0&#215;08;<br />
 if((max_current_BCD &amp; 0&#215;0004) == 0&#215;0004) ONES |= 0&#215;04; else ONES &amp;= ~0&#215;04;<br />
 if((max_current_BCD &amp; 0&#215;0002) == 0&#215;0002) ONES |= 0&#215;02; else ONES &amp;= ~0&#215;02;<br />
 if((max_current_BCD &amp; 0&#215;0001) == 0&#215;0001) ONES |= 0&#215;01; else ONES &amp;= ~0&#215;01;</p>
<p> ONES |= 0&#215;30;<br />
 TENS |= 0&#215;30;<br />
 HUND |= 0&#215;30;</p>
<p> lcd_write_byte(0&#215;00, 0&#215;95); //go to third line (DDRAM address 0&#215;15)<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, HUND);<br />
 lcd_write_byte(0&#215;01, TENS);<br />
 lcd_write_byte(0&#215;01, ONES);<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;61); //&#8217;a&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6D); //&#8217;m&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;70); //&#8217;p&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;28); //&#8217;(&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6D); //&#8217;m&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;61); //&#8217;a&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;78); //&#8217;x&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;29); //&#8217;)&#8217;</p>
<p> unsigned int temperature_BCD= ((temperature/10)*16)+(temperature%10);<br />
 <br />
 ONES= 0&#215;00;<br />
 TENS= 0&#215;00;</p>
<p> if((temperature_BCD &amp; 0&#215;0080) == 0&#215;0080) TENS |= 0&#215;08; else TENS &amp;= ~0&#215;08;<br />
 if((temperature_BCD &amp; 0&#215;0040) == 0&#215;0040) TENS |= 0&#215;04; else TENS &amp;= ~0&#215;04;<br />
 if((temperature_BCD &amp; 0&#215;0020) == 0&#215;0020) TENS |= 0&#215;02; else TENS &amp;= ~0&#215;02;<br />
 if((temperature_BCD &amp; 0&#215;0010) == 0&#215;0010) TENS |= 0&#215;01; else TENS &amp;= ~0&#215;01;</p>
<p> if((temperature_BCD &amp; 0&#215;0008) == 0&#215;0008) ONES |= 0&#215;08; else ONES &amp;= ~0&#215;08;<br />
 if((temperature_BCD &amp; 0&#215;0004) == 0&#215;0004) ONES |= 0&#215;04; else ONES &amp;= ~0&#215;04;<br />
 if((temperature_BCD &amp; 0&#215;0002) == 0&#215;0002) ONES |= 0&#215;02; else ONES &amp;= ~0&#215;02;<br />
 if((temperature_BCD &amp; 0&#215;0001) == 0&#215;0001) ONES |= 0&#215;01; else ONES &amp;= ~0&#215;01;</p>
<p> ONES |= 0&#215;30;<br />
 TENS |= 0&#215;30;</p>
<p> lcd_write_byte(0&#215;00, 0xD5); //go to fourth line (DDRAM address 0&#215;55)<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, TENS);<br />
 lcd_write_byte(0&#215;01, ONES);<br />
 lcd_write_byte(0&#215;01, 0xDF); //degree symbol<br />
 lcd_write_byte(0&#215;01, 0&#215;43); //&#8217;C&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;63); //&#8217;c&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;61); //&#8217;a&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
 lcd_write_byte(0&#215;01, 0&#215;74); //&#8217;t&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;6D); //&#8217;m&#8217;<br />
 lcd_write_byte(0&#215;01, 0&#215;70); //&#8217;p&#8217;<br />
}</p>
<p> </p>
<p>void switch_up(void)<br />
{<br />
 if(!(PINB &amp; (1&lt;&lt;PINB0)))<br />
 {<br />
  lcd_write_byte(0&#215;00, 0&#215;01); //clear screen<br />
  lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
  lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
  lcd_write_byte(0&#215;01, 0&#215;72); //&#8217;r&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;6C); //&#8217;l&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;61); //&#8217;a&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
  lcd_write_byte(0&#215;01, 0&#215;66); //&#8217;f&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;6F); //&#8217;o&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;6F); //&#8217;o&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;74); //&#8217;t&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;73); //&#8217;s&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;77); //&#8217;w&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;69); //&#8217;i&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;74); //&#8217;t&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;63); //&#8217;c&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;68); //&#8217;h&#8217;<br />
  while(!(PINB &amp; (1&lt;&lt;PINB0))){};<br />
 }<br />
}</p>
<p> </p>
<p>void weld_message(void)<br />
{<br />
  lcd_write_byte(0&#215;00, 0&#215;01); //clear screen<br />
  lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
  lcd_write_byte(0&#215;01, 0&#215;20); //space<br />
  lcd_write_byte(0&#215;01, 0&#215;77); //&#8217;w&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;65); //&#8217;e&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;6C); //&#8217;l&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;64); //&#8217;d&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;69); //&#8217;i&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;6E); //&#8217;n&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;67); //&#8217;g&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;2E); //&#8217;.&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;2E); //&#8217;.&#8217;<br />
  lcd_write_byte(0&#215;01, 0&#215;2E); //&#8217;.&#8217;<br />
}</p>
]]></content:encoded>
			<wfw:commentRss>http://www.imsolidstate.com/archives/590/feed</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Digital RPM indicator</title>
		<link>http://www.imsolidstate.com/archives/104</link>
		<comments>http://www.imsolidstate.com/archives/104#comments</comments>
		<pubDate>Wed, 26 Aug 2009 17:40:42 +0000</pubDate>
		<dc:creator>imsolidstate</dc:creator>
				<category><![CDATA[Electronics]]></category>
		<category><![CDATA[ATtiny]]></category>
		<category><![CDATA[AVR]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[LCD]]></category>
		<category><![CDATA[RPM]]></category>

		<guid isPermaLink="false">http://www.imsolidstate.com/?p=104</guid>
		<description><![CDATA[
I made a circuit for RPM measurement based on an Atmel ATtiny24 to drive a Hitachi 44780 parallel interface character LCD. It uses an external interrupt request and INT0 to calculate the elapsed time between high/low transitions on the INT0 pin. The elapsed time is then converted to RPM and sent to the LCD. The [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone size-large wp-image-110" title="LCD RPM indicator" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/IMAG0008-1024x768.jpg" alt="IMAG0008" width="614" height="461" /></p>
<p>I made a circuit for RPM measurement based on an Atmel ATtiny24 to drive a Hitachi 44780 parallel interface character LCD. It uses an external interrupt request and INT0 to calculate the elapsed time between high/low transitions on the INT0 pin. The elapsed time is then converted to RPM and sent to the LCD. The code will work from RPM values up to 999, and a variable timeout is used for a low cutoff to display 0RPM. The RPM range could be extended to 9,999 or higher by modifying the decimal to BCD routine at the end of the program.</p>
<p><img class="alignnone size-large wp-image-111" title="ATtiny24 interface PCB" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/Sony-016-1024x768.jpg" alt="Sony 016" width="614" height="461" /></p>
<p>Here is the source code, it compiles with AVR Studio and AVR-GCC. <span id="more-104"></span><br />
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
<pre>#include &lt;avr/io.h&gt;
#include &lt;avr/interrupt.h&gt;
#include &lt;util/delay.h&gt;

#define F_CPU 	1000000;	// 1MHz

volatile unsigned long RPM=0;	// Global RPM variable

void initLCD(void);
void lcd_write_byte(unsigned int CONTROL, unsigned int DATA);
void updateLCD(unsigned long RPM_OLD);
void check_BF(void);

void main(void)
{
TCCR1B=(1&lt;&lt;CS10)|(1&lt;&lt;CS12);	// Set up prescaler for CLK/1024
GIMSK=(1&lt;&lt;INT0);		// Enable external interrupt INT0
MCUCR=(1&lt;&lt;ISC00)|(1&lt;&lt;ISC01);    // Setup INT0 for rising edge detection

unsigned long RPM_OLD=1;	// Internal loop RPM variable

DDRB=0x00;			// Set PB2 for input

initLCD();			// Initialize the LCD

sei();				// Enable interrupts

while(1)
{
if((RPM != RPM_OLD) &amp; (TCNT1 &lt; 4000))
{
RPM_OLD=RPM;		// If new updated RPM value is not the same
updateLCD(RPM_OLD);	// as the one inside the loop, update the LCD.
}

if(TCNT1 &gt;= 4000)	// If it's been longer than 4 seconds, RPM=0.
{
RPM=0;
if (RPM != RPM_OLD)
{
RPM_OLD=RPM;		// If new updated RPM value is not the same
updateLCD(RPM_OLD);	// as the one inside the loop, update the LCD.
}
}
}
}

ISR(EXT_INT0_vect)	// External interrupt on PB2
{
unsigned long ELAPSED;

if(TCNT1 &lt; 4000)	// If Timer1 value is valid:
{
ELAPSED=TCNT1;		// Get Timer1 value before it changes much
TCNT1=0;		// Reset Timer1
RPM=(58594/ELAPSED);	// Calculate RPM based on Timer1 elapsed time
}

if(TCNT1 &gt;= 4000)	// If Timer1 overflowed, value is no good.
{
TCNT1=0;		// Reset Timer1
}
}

void initLCD()
{
DDRA=0xFF;			// Set PortA for output
_delay_ms(250);			// Wait for HD44780
PORTA=0x04;			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &amp;= ~0x10;
_delay_ms(10);			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &amp;= ~0x10;
_delay_ms(10);			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &amp;= ~0x10;
_delay_ms(10);
lcd_write_byte(0,0x0C);		// Turn on display
lcd_write_byte(0,0x06);		// Increment by one after write

}

void lcd_write_byte(unsigned int CONTROL, unsigned int DATA)
{
// check_BF();

if(CONTROL == 1) PORTA |= 0x20; else PORTA &amp;= ~0x20;
if((DATA &amp; 0x80) == 0x80) PORTA |= 0x01; else PORTA &amp;= ~0x01;
if((DATA &amp; 0x40) == 0x40) PORTA |= 0x02; else PORTA &amp;= ~0x02;
if((DATA &amp; 0x20) == 0x20) PORTA |= 0x04; else PORTA &amp;= ~0x04;
if((DATA &amp; 0x10) == 0x10) PORTA |= 0x08; else PORTA &amp;= ~0x08;
PORTA |= 0x10;
_delay_us(1);
PORTA &amp;= ~0x10;

if((DATA &amp; 0x08) == 0x08) PORTA |= 0x01; else PORTA &amp;= ~0x01;
if((DATA &amp; 0x04) == 0x04) PORTA |= 0x02; else PORTA &amp;= ~0x02;
if((DATA &amp; 0x02) == 0x02) PORTA |= 0x04; else PORTA &amp;= ~0x04;
if((DATA &amp; 0x01) == 0x01) PORTA |= 0x08; else PORTA &amp;= ~0x08;
PORTA |= 0x10;
_delay_us(1);
PORTA &amp;= ~0x10;
_delay_ms(4);
}

void updateLCD(unsigned long RPM_OLD)
{
unsigned long RPM_BCD=((((RPM_OLD/10)+((RPM_OLD/100)*6))*16)+(RPM_OLD%10));

unsigned int ONES = 0x00;
unsigned int TENS = 0x00;
unsigned int HUND = 0x00;

if((RPM_BCD &amp; 0x0800) == 0x0800) HUND |= 0x08; else HUND &amp;= ~0x08;
if((RPM_BCD &amp; 0x0400) == 0x0400) HUND |= 0x04; else HUND &amp;= ~0x04;
if((RPM_BCD &amp; 0x0200) == 0x0200) HUND |= 0x02; else HUND &amp;= ~0x02;
if((RPM_BCD &amp; 0x0100) == 0x0100) HUND |= 0x01; else HUND &amp;= ~0x01;

if((RPM_BCD &amp; 0x0080) == 0x0080) TENS |= 0x08; else TENS &amp;= ~0x08;
if((RPM_BCD &amp; 0x0040) == 0x0040) TENS |= 0x04; else TENS &amp;= ~0x04;
if((RPM_BCD &amp; 0x0020) == 0x0020) TENS |= 0x02; else TENS &amp;= ~0x02;
if((RPM_BCD &amp; 0x0010) == 0x0010) TENS |= 0x01; else TENS &amp;= ~0x01;

if((RPM_BCD &amp; 0x0008) == 0x0008) ONES |= 0x08; else ONES &amp;= ~0x08;
if((RPM_BCD &amp; 0x0004) == 0x0004) ONES |= 0x04; else ONES &amp;= ~0x04;
if((RPM_BCD &amp; 0x0002) == 0x0002) ONES |= 0x02; else ONES &amp;= ~0x02;
if((RPM_BCD &amp; 0x0001) == 0x0001) ONES |= 0x01; else ONES &amp;= ~0x01;

ONES |= 0x30;
TENS |= 0x30;
HUND |= 0x30;

lcd_write_byte(0x00, 0x01);
lcd_write_byte(0x01, 0x20);
lcd_write_byte(0x01, HUND);
lcd_write_byte(0x01, TENS);
lcd_write_byte(0x01, ONES);
lcd_write_byte(0x01, 0x20);
lcd_write_byte(0x01, 0x52);
lcd_write_byte(0x01, 0x50);
lcd_write_byte(0x01, 0x4D);

}

void check_BF(void)
{
DDRA = 0xF0;  // Set PortA 4-7 (R/W, RS, E) to output, 0-3 (DBx) for input
PORTA &amp;= ~0x20;			// Clear register select
PORTA |= 0x40;			// Set read
PORTA |= 0x10;			// Set enable
_delay_us(1);

while((PA0 &amp; 0x01) == 0x01)	// Read DB7 of LCD (busy flag)
{
PORTA &amp;= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
PORTA &amp;= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
}

PORTA &amp;= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
PORTA &amp;= ~0x10;			// Clear enable
PORTA &amp;= ~0x40;			// Clear read
DDRA = 0xFF;			// Set for output

}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.imsolidstate.com/archives/104/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>How much electric current does a truck really use?</title>
		<link>http://www.imsolidstate.com/archives/9</link>
		<comments>http://www.imsolidstate.com/archives/9#comments</comments>
		<pubDate>Tue, 25 Aug 2009 03:08:41 +0000</pubDate>
		<dc:creator>imsolidstate</dc:creator>
				<category><![CDATA[6.0L Ford Super Duty]]></category>
		<category><![CDATA[Electronics]]></category>
		<category><![CDATA[6.0]]></category>
		<category><![CDATA[Alternator]]></category>
		<category><![CDATA[ATMega]]></category>
		<category><![CDATA[AVR]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Current sense]]></category>
		<category><![CDATA[Powerstroke]]></category>

		<guid isPermaLink="false">http://www.imsolidstate.com/?p=9</guid>
		<description><![CDATA[So, a while back my truck was getting slow to start. I checked the battery voltage with the truck running, and it was only 11 volts or something. I started the troubleshooting process by replacing the alternator with one from the local parts store, but it didn&#8217;t fix the problem. I changed both batteries. Still [...]]]></description>
			<content:encoded><![CDATA[<p>So, a while back my truck was getting slow to start. I checked the battery voltage with the truck running, and it was only 11 volts or something. I started the troubleshooting process by replacing the alternator with one from the local parts store, but it didn&#8217;t fix the problem. I changed both batteries. Still didn&#8217;t fix the problem. So I did some diagnostics with an ammeter and a voltmeter and figured out that my brand-new alternator was bad. I took it back to the parts store, where they gave me another one. I had them test it, and it failed on their bench tester. So did the next one. They finally gave me my money back and I bought one from Ford. It worked just fine.</p>
<p>While I was looking for alternators, I found some high output models. This sounds cool, but do you really need it? I pull a trailer pretty regularly, and I imagined that the trailer lights and brakes would be a pretty good additional load on the electrical system. I had also read that people buying these &#8220;high output&#8221; alternators had been disappointed with their actual output, so I thought it might be good to find out.</p>
<p>I wasn&#8217;t sure how dirty the output of the alternator would be, or how quickly the output might fluctuate which ruled out the use of an inductive current clamp. So I looked around and found a hall-effect current sensor from Allegro Microsystems. The manufacturer&#8217;s part number is <a href="http://www.allegromicro.com/en/Products/Part_Numbers/0758/0758.pdf">ACS758KCB-150B-PFF-T</a>. This sensor has a maximum current rating of 150 Amps, and outputs a linear 0-5V signal proportional to the current that passes through the device. It&#8217;s fast enough to record transients and will faithfully reproduce both AC and DC currents. The output of the sensor was fed to an Atmel ATMega8, which did ADC duties and sent the data out it&#8217;s UART to a MAX232 level converter. I just picked up the data stream with hyperterminal on my laptop. Excel let me manipulate the raw data and make some pretty graphs. I made the circuit with my CNC machine. Here&#8217;s what it looks like.</p>
<p><img class="alignnone size-large wp-image-6" title="Current sense PCB" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/CurrentSense-1024x768.jpg" alt="CurrentSense" width="614" height="461" /></p>
<p>The output of the alternator was alot cleaner than I had expected. I thought there would be more of a rectified three phase look due to the phases generated inside the alternator. This picture of the scope shows the trace of the output of the sensor at idle.</p>
<p><img class="size-large wp-image-5" title="OScope trace" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/OScope-1024x768.jpg" alt="Oscilloscope Display" width="614" height="461" /></p>
<p>Here is a graph of the data from an engine start up. The Y-axis values are actual current draw in Amps. Time is shown on the X-axis, but the numbers represent the conversion events of the ADC, which happen at approximately 15Hz. This equates to about 40 seconds. The noise is real as far as I can tell. I didn&#8217;t use a ground plane, but the trace from the sensor output to the Mega&#8217;s ADC input is only about a quarter of an inch. I added a large filter capacitor to the sensor&#8217;s output and the waveform didn&#8217;t change at all.</p>
<p><img class="alignnone size-full wp-image-7" title="StartUp current" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/StartUp.jpg" alt="StartUp" width="620" height="428" /></p>
<p>Then I took the truck for about a ten minute drive. I had the lights on, but not the radio or anything extra.</p>
<p><img class="alignnone size-full wp-image-8" title="Driving test current" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/DrivingTest.jpg" alt="DrivingTest" width="604" height="417" /></p>
<p>It&#8217;s interesting to note how much power the transmission consumes when it&#8217;s in gear. The first plot is idling in park, the second plot again shows the truck idling in park at the end of the plot. It&#8217;s a clear 20 Amp drop from when the truck was in drive.</p>
<p>I hooked up my horse trailer, but even with all the lights on and everything it only shifted the curve up 10 Amps. The trailer brakes (which I thought would be a significant load) didn&#8217;t even show up. I&#8217;m still curious about this, as the trailer brake control wiring is usually about 10-12 guage, which is almost the same gauge wire the alternator output has to connect it to the battery. Why bother to wire trailer brake wiring with wire that has an ampacity of 100 Amps or so if it only uses a few amps? There must be more to the story.</p>
<p>Here is the schematic. Sorry it&#8217;s not all labelled but I didn&#8217;t expect to be posting it at the time. Right click and open the image to view full size.</p>
<p><img class="alignnone size-full wp-image-345" title="Schematic" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/Schematic1.jpg" alt="Schematic" width="641" height="194" /></p>
<p>And here&#8217;s the layout.</p>
<p><img class="alignnone size-full wp-image-346" title="Layout" src="http://www.imsolidstate.com/wp-content/uploads/2009/08/Layout.jpg" alt="Layout" width="599" height="481" /></p>
<p>I neglected to add a header for ISP. I was in a hurry to get it done and forgot. The target supply voltage in this application is 11-14V, but supply voltage could be extended to +45V with the appropriate version of the 7805.</p>
<p>Here&#8217;s the source code for the Mega8. Compiles with AVR Studio and AVR GCC. It&#8217;s a timer-driven interrupt, that starts an ADC conversion of the sensor output and then sends the result to the UART. It uses standard 9600 8N1. The result is left-adjusted so it&#8217;s only 8 bit. If you don&#8217;t need both positive and negative current measurements, then it would be best to remove the offset of the sensor and use the internal 2.5V ADC reference for better accuracy. The decimal to BCD routine at the end is something I figured out so I can just do a file capture in hyperterminal and import it directly into Excel.   <span id="more-9"></span></p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<pre>#include &lt;avr/io.h&gt;
#include &lt;avr/interrupt.h&gt;

#define F_CPU 8000000

#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void main(void)
{

UCSRB |= (1 &lt;&lt; RXEN) | (1 &lt;&lt; TXEN);  // Turn on the UART
UCSRC |= (1 &lt;&lt; URSEL) | (1 &lt;&lt; UCSZ0) | (1 &lt;&lt; UCSZ1);  // Use standard 8N1
UBRRL = BAUD_PRESCALE; 	       // Load lower 8-bits of the baud rate value
                               // into the low byte of the UBRR register
UBRRH = (BAUD_PRESCALE &gt;&gt; 8);  // Load upper 8-bits of the baud rate value
                               // into the high byte of the UBRR register

TIMSK |= (1 &lt;&lt; TOIE1);	       // Enable Timer1 overflow interrupt
TCCR1B |= (1 &lt;&lt; CS11);	       // Turn on Timer1, CLK/8 prescale

ADMUX |= (1 &lt;&lt; REFS0);	       // Select VCC reference
ADMUX |= (1 &lt;&lt; ADLAR); 	       // Left adjust ADC result
ADCSRA |= (1 &lt;&lt; ADEN) | (1 &lt;&lt; ADIE);    // Turn on ADC and enable
                                        // conversion complete interrupt
ADCSRA |= (1 &lt;&lt; ADPS1) | (1 &lt;&lt; ADPS2);	// ADC prescale of CLK/64

sei();

while(1); {}

}

ISR(TIMER1_OVF_vect)
{

ADCSRA |= (1 &lt;&lt; ADSC);	       // Start ADC conversion

}

ISR(ADC_vect)
{
unsigned long VOLTAGE = ((((ADCH/10)+((ADCH/100)*6))*16)+(ADCH%10));

unsigned int ONES = 0x00;
unsigned int TENS = 0x00;
unsigned int HUND = 0x00;

if((VOLTAGE &amp; 0x0800) == 0x0800) HUND |= 0x08; else HUND &amp;= ~0x08;
if((VOLTAGE &amp; 0x0400) == 0x0400) HUND |= 0x04; else HUND &amp;= ~0x04;
if((VOLTAGE &amp; 0x0200) == 0x0200) HUND |= 0x02; else HUND &amp;= ~0x02;
if((VOLTAGE &amp; 0x0100) == 0x0100) HUND |= 0x01; else HUND &amp;= ~0x01;

if((VOLTAGE &amp; 0x0080) == 0x0080) TENS |= 0x08; else TENS &amp;= ~0x08;
if((VOLTAGE &amp; 0x0040) == 0x0040) TENS |= 0x04; else TENS &amp;= ~0x04;
if((VOLTAGE &amp; 0x0020) == 0x0020) TENS |= 0x02; else TENS &amp;= ~0x02;
if((VOLTAGE &amp; 0x0010) == 0x0010) TENS |= 0x01; else TENS &amp;= ~0x01;

if((VOLTAGE &amp; 0x0008) == 0x0008) ONES |= 0x08; else ONES &amp;= ~0x08;
if((VOLTAGE &amp; 0x0004) == 0x0004) ONES |= 0x04; else ONES &amp;= ~0x04;
if((VOLTAGE &amp; 0x0002) == 0x0002) ONES |= 0x02; else ONES &amp;= ~0x02;
if((VOLTAGE &amp; 0x0001) == 0x0001) ONES |= 0x01; else ONES &amp;= ~0x01;

ONES |= 0x30;
TENS |= 0x30;
HUND |= 0x30;

UDR = HUND;				// Send the ADC results to the UART
while ((UCSRA &amp; (1 &lt;&lt; UDRE)) == 0) {}; 	// Wait for UDR to clear
UDR = TENS;
while ((UCSRA &amp; (1 &lt;&lt; UDRE)) == 0) {};
UDR = ONES;
while ((UCSRA &amp; (1 &lt;&lt; UDRE)) == 0) {};
UDR = 0x0D;				// Send new line
while ((UCSRA &amp; (1 &lt;&lt; UDRE)) == 0) {};
UDR = 0x0A;

}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.imsolidstate.com/archives/9/feed</wfw:commentRss>
		<slash:comments>30</slash:comments>
		</item>
	</channel>
</rss>
