ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 14.06.2025
Просмотров: 3122
Скачиваний: 0
12
Digital Voltmeter with RS232 Interface
12.1 In this Chapter
This chapter contains a didactic example on how to use the AVR development board described in Chap. 10 as an 8-channel digital voltmeter that can be interrogated over the RS232 interface. The characters received from the serial line are interpreted as the address of the analog channel to be read and reported.
12.2 The Hardware
The voltage levels expected by the A/D converter are in the range [0,Vref] and Vref is connected to Vcc. The analog inputs are presented as positive voltage signals to the connector X100, pins 1–8, referred to the ground line X100-9, then filtered by the RC filters implemented by R11–R26 and C11–C19, and applied to the analog inputs ADC0–ADC7 of the MCU.
The output of the ADC system, corresponding to Vin is a 10-bit binary value:
ADC_value = 1023 × Vin/Vref. |
(12.1) |
Since Vref = Vcc = +5 V, an input voltage Vin = 3.1 V, for example, is converted to decimal 634, which corresponds to the hexadecimal value $27A.
The example presented in this chapter uses only the serial communication interface RS232 to report the values read by the ADC, but optionally a local display, like the one presented in Appendix A14, can be connected to the board on the synchronous peripheral interface SPI.
12.3 The Software
The software application is structured according to the principles described in Chap. 10. After the initialization sequence, the program enters an infinite loop, where
158 12 Digital Voltmeter with RS232 Interface
it reads all the analog inputs of the ADC, and stores the results in a RAM buffer. If an ASCII character in the range ‘0’ to ‘7’ is received on the asynchronous serial interface, the two bytes needed to represent the 10-bit value of the corresponding analog input are converted to four ASCII digits and transmitted on the serial communication line.
Here is the listing of the main module VMMAIN.ASM:
.include |
‘‘8535def.inc’’ |
|
.include |
‘‘map.asm’’ |
;local variables |
.cseg |
||
.org |
0 |
|
reset: |
||
rjmp |
init |
|
.org |
$10 |
|
init: |
||
.include |
‘‘init.asm’’ |
;calls to init routines |
sei |
||
main_loop: |
||
rcall |
get_adc |
;read analog inputs |
rcall |
get_cmd |
;check for data from UART |
brcc |
main_loop |
|
rcall |
exec_cmd |
;answer query over |
;serial line |
||
rjmp |
main_loop |
|
.include |
‘‘uart.asm’’ |
;UART specific routines |
.include |
‘‘lib.asm’’ |
;misc routines |
.include |
‘‘adc.asm’’ |
;ADC specific routines |
Note that the main program loop only contains calls to three subroutines. Get_adc reads all the analog inputs, get_cmd checks the UART receiver data register for an ASCII character between ‘0’ and ‘7’, and returns the CARRY flag set if one of these characters has been received. Finally, exec_cmd extracts the value of the analog line indicated by the command character, converts it to four ASCII digits, and sends them to the serial line.
All other modules contain either variable definitions (MAP.ASM), calls to the initialization routines (INIT.ASM), peripheral specific routines (UART.ASM, SDC.ASM) or miscellaneous data conversion or execution routines (LIB.ASM).
MAP.ASM contains a number of definitions for constants, and RAM variables:
.def |
rint=r1 |
;some aliases for regs |
.def |
rsav=r2 |
|
.def |
tmp1=r16 |
|
.def |
tmp2=r17 |
|
.def |
tmp3=r18 |
|
.def |
op1l=r19 |
12.3 The Software |
159 |
||
.equ |
prescaler_adc=7 |
;ADC clock=CK/128 |
|
.equ |
adc_max_ch=7 |
;max # of ADC channels |
|
.equ |
f_baud=51 |
;9600 baud |
|
.dseg |
;RAM variables |
||
buf_adc: .byte |
16 |
;buffer for ADC data |
|
.equ |
f_baud=51 |
;9600 baud |
INIT.ASM is a block of code containing the initialization of the stack pointer SP, and calls to the initialization routines associated with the peripheral interfaces used by the application. The resource-specific initialization routines are located in the corresponding module: init_uart in UART.ASM and init_adc in ADC.ASM. The whole block of code contained in INIT.ASM is inserted in the main program at the point specified by the .include directive.
init_sp:
ldi tmp1,high(ramend) out sph,tmp1
ldi tmp1,low(ramend) out spl,tmp1
rcall init_uart rcall init_adc
end_init:
The UART initialization routine, init_uart. has been described in the previous chapters. Below is the listing of the subroutine for the initialization of the ADC subsystem:
init_adc: |
||
ldi |
tmp1,0 |
;channel 0 first |
out |
admux,tmp1 |
|
ldi |
tmp1,prescaler_adc |
;ADC clock=CK/128 |
out |
adcsr,tmp1 |
|
sbi |
adcsr,aden |
;ADC enabled |
sbi |
adcsr,adsc |
;start first |
;conversion |
||
ret |
This code selects channel number 0 by writing to admux, then selects the ADC clock by programming the prescaler to divide the system clock by 128, and enables the ADC system by setting the bit ADEN (A/D converter enable) in ADCSR. Finally, it starts the conversion on the selected channel, by setting the bit ADSC (Start A/D conversion) in ADCSR.
The subroutine get_adc checks the bit ADIF in ADCSR to determine if the last conversion is completed. If ADIF = 1, then the ADC result value (read as two bytes) is stored in a RAM buffer buf_adc, using the channel number as index in this buffer, then a new conversion is started on the next channel:
160 |
12 Digital Voltmeter with RS232 Interface |
||
get_adc: |
|||
sbis |
adcsr,adif |
;exit if ADIF=0 |
|
ret |
|||
in |
tmp1,admux |
;else, get channel number |
|
andi |
tmp1,0b00000111 |
||
ldi |
xl,low(buf_adc) |
;x points to start |
|
;of buffer |
|||
ldi |
xh,high(buf_adc) |
||
clr |
tmp2 |
||
push |
tmp1 |
;save channel number |
|
add |
tmp1,tmp1 |
;two bytes foe each channel |
|
add |
xl,tmp1 |
;add offset to x |
|
adc |
xh,tmp2 |
||
in |
tmp1,adcl |
;get ADC conversion result |
|
in |
tmp2,adch |
||
st |
x+,tmp1 |
;and store it in buffer |
|
st |
x,tmp2 |
||
pop |
tmp1 |
;restore channel number |
|
inc |
tmp1 |
;increment |
|
cpi |
tmp1,adc_max_ch+1 ;check if last channel |
||
brlo |
getadc1 |
||
clr |
tmp1 |
;if so, return to channel 0 |
|
getadc1: |
|||
out |
admux,tmp1 |
;select new channel |
|
sbi |
adcsr,adsc |
;start a new conversion |
|
ret |
|||
The RS232 communication is performed by means of two subroutines called in the main program loop: get_cmd and exec_cmd, both located in the module LIB.ASM.
Get_cmd checks the UART receiver data register for an ASCII code between ‘0’ and ‘7’. If no character is received, or the ASCII code is outside the expected range, get_cmd returns CARRY = 0, otherwise it returns the valid code in the register r16 (tmp1) and CARRY = 1.
get_cmd: |
||
rcall |
get_uart |
;check if character received |
brcs |
getcmd1 |
;exit if none |
ret |
||
getcmd1: |
||
clc |
||
cpi |
tmp1,$30 |
;$30 is the ASCII code for ‘0’ |
brlo |
exit_gcmd |
|
cpi |
tmp1,$38 |
;$38 is ASCII ‘8’ |
brsh |
exit_gcmd |
|
sec |
;set carry |
|
exit_gcmd: |
||
ret |
12.3 The Software |
161 |
Exec_cmd sends on the serial line a data packet, having the following structure:
N : AAAA CR LF
where N is the ASCII code of the channel number, ‘:’ is used as delimiter, AAAA is the last converted value in hexadecimal of the selected analog channel, and CR, LF are the ASCII codes for ‘carriage return’ ($0D) and ‘line feed’ ($0A). This is required to allow the use of any ASCII terminal emulator, such as Windows™ HyperTerminal, to test the program.
Note that exec_cmd calls hex_asc – a subroutine that converts the content of tmp1 to two ASCII characters, returned in tmp1 and tmp2 corresponding to the nibbles of the input value.
Therefore, the string output to the terminal by exec_cmd in case of reading on channel 3 the binary value 1100100111b is: 3:0C27
Here is the listing of exec_cmd:
exec_cmd: |
|
rcall |
put_uartw |
rcall |
asc_hex |
ldi |
xl,low(buf_adc) |
ldi |
xh,high(buf_adc) |
clr |
tmp2 |
add |
tmp1,tmp1 |
add |
xl,tmp1 |
adc |
xh,tmp2 |
adiw |
xh:xl,2 |
ldi |
tmp1,’:’ |
rcall |
put_uartw |
ld |
tmp1,-x |
rcall |
hex_asc |
rcall |
put_uartw |
mov |
tmp1,tmp2 |
rcall |
put_uartw |
ld |
tmp1,-x |
rcall |
hex_asc |
rcall |
put_uartw |
mov |
tmp1,tmp2 |
rcall |
put_uartw |
ldi |
tmp1,$0d |
rcall |
put_uartw |
ldi |
tmp1,$0a |
rcall |
put_uartw |
ret |
;display channel number ;convert it to hex
;x points to start of buf
;compute offset in buf_adc
;send ‘:’ as separator
;get high byte from buffer ;convert to ascii
;send first character
;send second chr ;get low byte ;convert it ;send it
;CR
;LF
Note that the input for exec_cmd is the channel number in ASCII, as received from the serial line. Exec_cmd starts by echoing this value. Then, the ASCII value of the channel number is converted to its corresponding hexadecimal value by the subroutine asc_hex. The resulting binary value is used to compute the offset in the
162 12 Digital Voltmeter with RS232 Interface
buffer buf_adc, to retrieve the result of the last conversion performed on the selected analog channel.
The accompanying CD contains a terminal emulator, Voltm.exe, which automatically interrogates the voltmeter for the values of all eight analog channels, and displays the values on the screen. The default Comm port is COM2, but this can be changed by the user. The Start/Stop button initiates and interrupts the communication process. The program runs under Windows™ 9x.
12.4 Exercises
X 12.1
Write a subroutine CBCD16 that converts a 16-bit binary number, lower than 9999, received as input in the registers tmp1:tmp2, to four BCD digits placed in the same registers.
X12.2
Modify the software so that the data sent on the serial line contains the decimal representation of the analog value read from the requested channel.
X12.3
Modify the software so that the decimal value of channel 0 is also displayed on the SPI display described in Appendix A14.