lab7 -- interrupts

In this lab, we'll implement a not-very useful voltmeter, with a range of of 0-3.3V and a maximum accuracy of one part in 1000, using the same hardware we used for lab6. The voltmeter will continuously measure the voltage on pin 26 of the PIC32MX110F016B, and display the voltage as three decimal digits, with the units digit separated from the tenths by an underscore. So if the measured voltage is 1.82V, the display will read
1 1_ _8 82 2 1 1_ etc.
You worked out display code for lab6. For this lab, we need two additional components, the setup and interrupt routines for the analog-to-digital converter built into the the microcontroller, and the arithmetic routines to convert from the internal representation into three-digit decimal numbers.

ADC operation

The PIC ADC is a successive-approximation converter. It uses a built in digital to analog converter and a comparator to do a binary search for the analog value. Each step of the search takes a fixed amount of time, and there are twelve steps involved in obtaining a 10-bit value.

To use the ADC, you set up various parameters, as described in The ADC manual and, to a lesser extent, in the The PIC32MX110/220 Family manual. You wait until the conversion finishes, and then you can read out the result. You can either wait with a polling loop, checking the DONE bit until the ADC turns it on, or you can set up an interrupt routine to execute when that happens. Once the DONE bit comes on, you can read the result and restart the conversion. If you use interrupts, you may also need The interrupt controller manual.

In our case, we don't have a lot of other things to do, but that polling loop is a waste of CPU time. If you had a lot of computation to do, you would want to avoid using extra cycles on it.

Here is some sample code, based on the ADC manual, which uses an interrupt:

# the following code is based on Example 17-4 in the PIC32 family datasheet section 17: # Analog to Digital Converter. The input routine just picks up the value of the ADC # by reading from memory; so the whole consists of three parts: # astart174 sets up the interrupt, and starts the ADC # ainput174 just reads the current value in the memory location # inte23 is the interrupt routine, which copies the completed value from the # ADC result buffer in the SFRs into RAM LatestADC = 0xA0000000 # a location in RAM # start things up. AD1 is interrupt 28, vector 23 # assumes that CPU is already initialized to multi-vector mode astart174: add sp,sp,-8 sw ra,0(sp) # first value is a bogus value addi v0,zero,0b01010000000 # save bogus value in RAM sw v0,LatestADC ori t0,zero,0x8000 sw t0,AD1CON1CLR # turn off AD1. leave bits as set up lui t0,1<<(28-16) # set bit 28 in t0 for INT setup sw t0,IFS0CLR # clear interrupt flag sw t0,IEC0SET # enable interrupt lui t0,0x1e00 #this is bits 28:25 (of 28:24) sw t0,IPC5SET # set AD1IP=7,AD1IS=2 // setup ADC, based on assignments from Example 17-4 in PIC32 Family datasheet // section 17: ADC ori t0,zero,0xE0 # SSRC = 0b111 sw t0,AD1CON1 # store lui t0,0x9 # input from AN9 sw t0,AD1CHS sw zero,AD1CSSL # no scanning addi t0,zero,0x1f02 # is 0x0f00 in example 17-4 sw t0,AD1CON3 # differs from example 17-4 -- see above sw zero,AD1CON2 # differs form example 17-4 -- there 4, here zero // now start ADC running ei # enable interrupts ori t0,zero,0x8000 sw t0,AD1CON1SET # turn on ADC ori t0,zero,4 # ASAM starts sampling sw t0,AD1CON1SET lw ra,0(sp) jr ra addiu sp,sp,8 # pop stack ainput174: lw v0,LatestADC #pick up most recent value jr ra # and return nop # jump here on interrupt inte23: addiu sp,sp,-8 sw k0,4(sp) sw k1,0(sp) la k0,ADC1BUF0 # address of buffer lw k0,0(k0) # content of buffer lui k1,LatestADC>>16 sw k0,0xFFFF&LatestADC(k1) # save for pickup lui k0,1<<(28-16) la k1,IFS0CLR # get address sw k0,0(k1) # clear AD1 interrupt flag in IFS0 lw k0,4(sp) lw k1,0(sp) addiu sp,sp,8 eret # return from interrupt # load the interrupt vector with a reference to the interrupt routine .section .vector_23,code j inte23 nop

Fixed-point arithmetic

The ADC can format the ten data bits in a variety of ways, according to the three FORM bits in AD1CON1. In the example above, the FORM bits are set to 000, so the data is returned as a ten-bit positive binary integer with a value between 0000000000 and 1111111111 (or zero and 1023.) A value of zero corresponds to a voltage of zero, and a value of 1023 corresponds to a voltage of just under 3.3 V. One way of thinking about this integer is as a binary fraction, with the binary point in front of the first bit, so that the fraction ranges from .0000000000 to .1111111111 (0.0 to .999)

If you were to multiply that fraction by 330, you'd get an integer value corresponding to the three digits you need to display (but of course followed by ten bits of fraction.) You could mask and shift out the integer, then do appropriate divides to figure out the hundreds, tens, and units places to display.

A simpler scheme would be to multiply the fraction by 3.3; then the integer part would be the integer part of the final answer, and dropping the integer part of the result and multiplying the resulting intermediate fraction by ten would give then next digit, and you can get the third digit by repeating the process another time. In pseudo-code:

t = ADCvalue * 0x34CD // 0x34CD is 3.3 with the hex point between the 3 and 4 // binary point in result will give 22 bit fraction d1 = t>>22 // discard fraction for first decimal digit t &= 0x3FFFFF // discard integer part t *= 10 d2 = t>>22 // discard 22-bit fraction to get 2nd digit t &= 0x3FFFFF // discard integer part; t *= 10 // multiply by ten again. d3 = t>>22 // discard fraction to get third digit
Once you have digit values, you need to convert them to seven-segment display values. A convenient way to do this is by indexing a table of bytes, each one containing the bits to drive a single digit.

You may want to start with my sample code, which initializes the PIC for I/O and includes the analog input code above.

What you'll turn in

As usual, I'd like a word document containing