Unused port adds a PWM/analog channel to a microcontroller
Vishwas Vaidya, Tata Motors, Pune, India -- EDN Europe, 01 Nov 2009
Low-cost, 8-bit, single-chip microcontrollers often have limited on-chip PWM (pulse-widthmodulation) resources. The use of a PWM resource often forces a designer to sacrifice a capture/compare or timer channel because the PWM channel shares the same on-chip hardware. This Design Idea describes how you can use an on-chip unused synchronous serial port to generate PWM signals and convert them to a slowmoving analog signal (Figure 1). Many microcontroller-based standalone electronic units don’t employ the synchronous serial port. Thus, you can use the microcontroller’s baudrate generator and parallel-to-serialconverter blocks to generate bit patterns to form a 256-bit PWM pattern. You can then filter the PWM output with an RC filter to extract an analog signal (Reference 1). The synchronous communication is devoid of the start and stop bits of asynchronous mode, so the bit pattern can generate long periods of high or low level.
You can generate raw data with a decimal value of 165 using this concept (Figure 2). A PWM-conversion cycle consists of generating 256 bits— that is, 32 bytes. The number of “on” bits corresponds to the value of the raw data to convert into PWM. Hence, for 165 bits as the raw data, 165 bits are on and 91 bits are off. To generate a 165-bit on-period, the first 20 bytes —that is, 160 bits—transmit as 0xff onstate bytes. The trick lies in judiciously composing the 21st, or transition, byte. This byte has some of its LSBs (least significant bits) as ones and the rest as zeros to form the required length of the on-period. In this case, the circuit needs five more on bits: 160+5=165. Hence, the transition byte should have a 00011111b pattern (byte=0x1f).
Figure 3 illustrates the process in flow-chart form. You can tailor the PWM frequency to your application by selecting a crystal, PLL (phaselocked loop), and baud rate. A simple RC filter can convert the PWM into a slow-moving analog value. Although this Design Idea describes an 8-bit PWM, you can increase or decrease resolution by changing the total bits per PWM cycle. You correspondingly increase or decrease the conversion time.
Listing 1, which is available at www. edn-europe.com/article.asp?articleid=3492, provides a sample code for illustrating the concept. The code uses the Microchip (www.microchip.com) PIC18F4525, which has a 4-MHz crystal and 10-kHz baud rate for the synchronous serial communication, yielding 10,000/25639.31 Hz of PWM frequency. You can filter it with a 0.1-sec RC filter, which is sufficient for slow-moving analog signals, such as speed setpoints for motion-control applications. By using a 20-MHz crystal, you can achieve synchronous serial baud rates greater than 1.5 MHz and PWM frequencies of a few kilohertz.
Some instrumentation amplifiers use external resistors to set their gain. Unfortunately, the lack of temperature-coefficient matching between the external and the internal resistors results in a high gain drift. If, however, another on-chip resistor is available, you can use it to compensate for gain drift as a result of temperature.
As an example, Analog Devices’ (www.analog.com) AD8295 has a drift of as much as -50 ppm/°C, even if you use a zero-drift gain-setting resistor. In this Design Idea, you can compensate this drift with an extra zero-drift resistor in combination with an internal chip resistor.
The gain-set equation from the data sheet (Reference 1) is From this gain-set equation, you can assume that the chip uses two 24.7-kΩ resistors with the external gain resistor, RG, to set the amplifier’s gain. The chip has two more 20-kΩ resistors. Because all of these chip resistors are of the same magnitude, they probably will have good temperature-coefficient matching, and you can use this matching for compensation. If the amplifier resistance, RA, and the gain resistor are zero-drift resistors (Figure 1), then where Δ is the drift of the internal matched resistors. If then the first-order drift of the gain cancels, and the gain splits equally between the instrumentation amplifier and A1. Solving for RG and RA yields.
For gain greater than 100, the amplifier resistance becomes greater than 90 kΩ, which can be problematic. In this case, you can use A1 in an inverting configuration with a gain of -1 (Figure 2). With an amplifier resistance of 10 kΩ, This case sizes RG using a value from the data-sheet formula. If the gain is 50, the internal matching and the negative drift compensate the “49” part of the gain, and the “one” part is just the drift divided by 50 in the total gain, resulting in a typical figure of -1 ppm/°C. In both cases, the resulting gain temperature coefficient can be less than 5 ppm/°C, which is 10 times better than the original outcome.
| Listing 1: |
************************************************************* * Unused port adds a PWM/analog channel to a microcontroller * ************************************************************* This is a demo program from illustrating PWM generation using Synchronous Serial Port Micro-controller for this code is 18F4525 Controller. however the code can be ported to any equivalent CPU Date= 15/05/2009 Author: vishwas Vaidya Version 1.00 contains the code for outputting a byte on synchronous serial transmitter Variable Raw_Data is output to synchronous transmitter ************************************************************* Initialisations relevant for Synchronous Serial Communication 1.0 TRISC<6:7> = 1; PORT C FOR SERIAL COMMUNICATION. 2.0 SPEN = 1; .....RSCTA<7> 3.0 SPBRG = 0x64 ; For 4MHZ crystal Fosc/4x100 = 10 KHZ 4.0 TXSTA =0xBF ; CSRC = 1; TX9 = 0;TXEN = 1;SYNC = 1; b3,b2 = x;TRMT = i/p;TX9D = x 5.0 RCSTA =0x80 ; SPEN = 1; all other bits beind Rx related reset to zero. Initialisations for Transmit Interrupt 1.0 Bit PEIE ..(INTCON <6>) = 1 FOR ENABLING PERIPHERAL INTERRUPTS 2.0 Bit TXIE .. (PIE1 <4> ) = 1 3.0 Ensure that GIE is set */ //Project includes //#include <pic18.h> #include <p18f4525.h> //Project defi nes #defi ne tmr1_reload_L 0x0A //Timer 1 reload values for 1ms timing, therotical = 0xFBE7 #defi ne tmr1_reload_H 0xFC //fi ne tuned for RPM accuracy, practical = 0xFC0A //Global variables unsigned char timer_100ms; //system timer variables unsigned char timer_500ms; unsigned char timer_1000ms; int timer_100ms_fl ag; //fl ag for 100ms timer unsigned char Tx_Byte; // Byte to be Transmitted in Tx interrupt //------Variables for DAC house Keeping and Start conversion Start unsigned char RawData; //Input digital data to be converted into analog unsigned char Next_On_Byte_Count; // Number of 0xFF bytes in on duty period of PWM for next DAC cycle unsigned char Next_Off_Byte_Count;// Number of 0x00 bytes to be transmitted in off state unsigned char Next_Transition_Byte; // Last byte in On duty period beffore off duty starts(for Next DAC cycle) unsigned char Remainder; // Remainder of division of RawData by 32 //Look-up table for mapping remainder of RawData/32 for generation of “Transition byte” //into number of ‘1’s. e.g. remainder of 02 is mapped as 0x03 where number of ones are two. const unsigned char Transition_Byte_Array [8] = {0X00,0x01, 0x03, 0x7, 0x0F, 0x1F, 0x3F, 0x7F}; unsigned char On_Byte_Count; // Number of 0xFF bytes in on duty period of PWM for current DAC cycle unsigned char Transition_Byte; // Last byte in On duty period beffore off duty starts for current DAC cycle //----Variables for DAC House Keeping end. 0xFF; //PortB confi gured as input port TRISC = 0b11010010; //PortC confi gured for Serial Communication, RC2 as output pin for EGR output TRISD = 0b11110000; //PortD lower nibble confi gured as output TRISEbits.PSPMODE = 0; //Parallel slave port mode disabled for PortD digital I/O operation //Debugging code for division DivisionData = 0x08; DivisionData = DivisionData * 0x15; // DivisionData = 0xff/ DivisionData ; // Debug division algo //ADC module confi guration ADCON0 = 0b00000001; //ADC clock as Fosc/8 and CH0 selected ADCON1 = 0b00000000; //PortA confi gured as analog input pin ADCON2 = 0b00000001; //oscilator frequency focs/8 PIE1bits.ADIE = 0; //Disable ADC interrupt //SPI module confi guration PIE1bits.SSPIE = 0; //SPI interrupt disabled p;TX9D = x RCSTA =0x80 ; //SPEN = 1; all other bits beind Rx related reset to zero. //Interrupts confi guration INTCONbits.GIE = 1; //Global interrupt enable bit INTCONbits.PEIE = 1; //Peripheral interrupt enable bit PIE1bits.TMR1IE = 1; //enable timer 1 interrupt INTCONbits.INT0IE = 1; //Enable INT0 external interrupt INTCON2bits.INTEDG0 = 0; //INT interrupt confi gured for rising edge PIE1bits.TXIE = 1; // Enable Transmit Interrupt INTCONbits.RBIF = 0; //disable interrupt on portB pin change Tx_Byte = 0x0F; //Load Transmit Byte for fi rst interrupt } //void interrupt isr (void) void isr (void) { //Timer 1 used to generate 1ms periodic interrupt. if (PIR1bits.TMR1IF) { PIR1bits.TMR1IF = 0; //clear interrupt fl ag TMR1L = tmr1_reload_L; //Reload timer for next cycle PIR1bits.TXIF) {// Load transmit register with the byte to be transmitted TXREG = Tx_Byte ; Get_Next_Byte_Flag = 1; //Get next value of Tx_byte } } void system_init (void) { timer_100ms = 0; //initialise system timers timer_500ms = 0; timer_1000ms = 0; On_Period_Flag =1; //Initial period in PWM is on state Eoc_Flag =1; // Assume conversion is over at the start Tx_Byte = 0x3F ; // First byte is mostly all bits on Get_Next_Byte_Flag =1; } void system_timers_update (void) { timer_500ms++; //increment timer if (timer_500ms == 5) //reset timer every 0.5 second timer_500ms = 0; timer_1000ms++; //increment timer if (timer_1000ms == 10) //reset timer every 1 second timer_1000ms = 0; } //Function for DAC House Keeping /* This routine takes RawData as input from the calling routine and works out PWM on duty cycle period for the same. This period is specifi ed as number of 0xFF bytes (NextOnByteCount) followed by last byte (known as NextTransitionByte) to complete the own “duty-cycle period”. For example for Rawdata = 0x86 = 134 decimal We should have 134 bits on and 122 bits off in 256 bit interval. To generate 134 bit on interval we can generate 16 bytes with value 0xff to give 128 bit on time giving NextOnByteCount = 0x28 (40 Decimal). To complete remaining 6 bit interval out of 134 bit on time we should ouput 0x00111111b = 0x3F which gives fi rst 6 bits as “1”. Remember that shifting sequence is from lsb to msb. */ void DAC_House_Keeping (void) { Next_On_Byte_Count = RawData >> 3 ; //RawDat/8 = number of on bytes with 0xff value Remainder = RawData - (Next_On_Byte_Count << 3) ; //Remainder indicates last byte in on period Next_Transition_Byte = Transition_Byte_Array[Remainder]; //Map remainder into on bits. Next_Off_Byte_Count = 31 - Next_On_Byte_Count ; //OffByte +OnByte +TransitionByte = 32 } //Function for Start of conversion /*This routine is invoked when End_Of_Conversion is true(Eocfl ag) and new RawData is available for conversion. The new Raw data is processed by the DAC_House_Keeping routine to generate OnByteCount and TransitionByte for next cycle. These values are updated at the start of every new conversion cycle. EocFlag is rest to mark “conversion in progress” condition */ void Start_DA_Conversion (void) { On_Period_Flag = 1; //Initial perid of of PWM is on state Tx_Byte = 0xff ;//First Byte will be 0x ff in most cases. On_Byte_Count = Next_On_Byte_Count; // Set number of 0xff bytes during on duty Off_Byte_Count = Next_Off_Byte_Count;//Set number of 0x00 bytes Transition_Byte = Next_Transition_Byte; //Set the last byte at the transition from on to off Eoc_Flag = 0 ; // Conversion is in progress now. } //-----------------Comments for Transmit_Next_Byte Start--------------------------------------------- // Function for Byte Transmition on synchronous communication channel /* This routine actually implements PWM on serial synchronous channel. It is invoked every time the transmit interrupt submits a byte to the synchronous transmitter. Get_Next_Byte_Flag triggers this routine The PWM cycle is logically divided into three segments, viz: On duty, transition and off duty. The byte to be transmitted (TxByte)takes on three possible values depending on which segement of the PWM cycle is currently in progress: 1.0 If on duty is in progress then TxByte = 0x ff (THIS CONDITION is defi ned when OnBytecount >0 and On_Period_fl ag is set) 2.0 If “on to off” transition is in progress then TxByte = Transition_Byte (This condition is defi ned when Onbyte count = 0 and On_Period_fl ag is set. Once this condition is detected, transition byte is submitted for transmission and On_Period_fl ag is reset. ) 3.0 If off duty is in progress then TxByte = 0x00 */ //--------------------------------Code for Transmit_Next_Byte Start---------------- void Transmit_Next_Byte (void) { Get_Next_Byte_Flag = 0;//Reset the fl ag set by Transmit ISR if (On_Period_Flag) { // Onduty is in progress if (On_Byte_Count > 0) { On_Byte_Count --; } else { //On_Byte_count = 0 implies no more 0x ff to be transmitted if (!(Tx_Byte == Transition_Byte)) { Tx_Byte = Transition_Byte; } // Transmit Transition_Byte if not already transmitted. //However if Transition_Byte is transmitted last time // then start off duty cycle else { Tx_Byte = 0x00; // off state byte On_Period_Flag = 0;//On period ends Off_Byte_Count--;// Start tracking off_bytes transmitted } } } else { if (Off_Byte_Count > 0) { Off_Byte_Count --; } else { Eoc_Flag =1; } //If off_bytes have been all transmitted, } } //end the conversion cycle. //Main program // this program illustrates how the “Software DAC chip” formed by sync. serial port can be used to convert digital raw-data // into PWM/analog voltage. Actual application can be built by expanding this concept. void main (void) { controller_init (); //initialise controller system_init (); //initialise global variables, timers & counters New_RawData = 0xA5; DivisionData = 0x05; DivisionData = 0x01/ DivisionData ; // Debug division algo while (1) { New_RawData = 0xA5; //RawData = 165 Say. 5 On Bytes, TransitionByte=0x1f,Off Bytes=2. Actual application can contain code for generating the raw data corrosponding to the application. system_timers_update (); //system timer function if (timer_100ms_fl ag) { timer_100ms_fl ag = 0; RawData = New_RawData; //update Raw Data DAC_House_Keeping (); //Prepare for next conversion cycle } // Keep converting the RawData from digital to analog if (Eoc_Flag) Start_DA_Conversion ();//Start at EOC rising edge converting if (Get_Next_Byte_Flag) Transmit_Next_Byte();//Keep transmitting on serial line } } |