;*************************************************************************** ;* THERMOMETER.ASM ;* ;* Read temperature from DS1621 and send it to RS232 ;* AVR MINI THREADS v.1.02 ;* ---> nanoKernel for the AVR AT90S1200 ;* ;* Copyright (c) 1998 by Claudio Lanconelli ;* e-mail: lanconel@cs.unibo.it ;* WWW: http://www.cs.unibo.it/~lanconel ;* ;/ This program is free software; you can redistribute it and/or // ;/ modify it under the terms of the GNU General Public License // ;/ as published by the Free Software Foundation; either version2 of // ;/ the License, or (at your option) any later version. // ;/ // ;/ This program is distributed in the hope that it will be useful, // ;/ but WITHOUT ANY WARRANTY; without even the implied warranty of // ;/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // ;/ General Public License for more details. // ;/ // ;/ You should have received a copy of the GNU General Public License // ;/ along with this program (see COPYING); if not, write to the // ;/ Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // ;*************************************************************************** .DEVICE AT90S1200 .include "macro.inc" .include "1200def.inc" ;In questo esempio utilizzo solo 2 thread. ;-il primo (principale) controlla in polling lo stato di un pulsante (PinB-0), e ; quando viene premuto accende un Led (PortD-0) e inizia una conversione di ; temperatura del DS1621, attende che finisca e trasmette il risultato sulla ; seriale settata a 1200,n,8,1 (divisore timer calcolato per quarzo 3.6864 MHz) ;-il terzo (serial driver) attende richieste di output dal thread1 e quando ne ha trasmette ; un bit alla volta su interrupt del timer. ;N.B. il DS1621 comunica tramite bus seriale I2C, i cui segnali vengono implementati sulla ; portaB (PortB-7 --> SDA, PortB-6 --> SCL). Le routine I2Cbus sono tratte dalle app.note ; dell'Atmel (avr300). ; I segnali sono gli stessi utilizzati per la programmazione in-circuit tramite PonyProg, ; la programmazione non da` alcun problema con resistenze di pull-up I2C di 4K7. ;In this example I use only two threads. ;-the first (main thread) polls the status of the key (PinB-0). When the key is ; pressed switch on the Led (PortD-0) and start temperature conversion within ; the DS1621. Then it wait for the end of the conversion, read the temperature, ; and transmit it over the serial line (1200,n,8,1) with a crystal of 3.6864MHz. ;-the third (serial driver) wait for a request from thread1, and transmit ; a bit at a time (a timer interrupt for each bit) over serial line ;The DS1621 communicate over the I2C Bus. I2C Bus routines are taken from Atmel ; app.note (avr300). PortB-7 --> SDA, PortB-6 --> SCL. ; The same pins are used for the ISP programming with PonyProg. .CSEG ;*************************************************************************** ;* VARIABLE ASSIGNEMENTS ;*************************************************************************** .def SaveThr1Status = r0 ;status copy for thread 1 .def SaveThr2Status = r1 ;status copy for thread 2 .def SaveThr3Status = r2 ;status copy for thread 3 .def SaveStatus = r3 ;status copy for interrupts .def Temperatura = r4 .def SerNbit = r16 ;serial bit to transmit .def SerData = r17 ;serial data byte to transmit .def TmrDivisor = r18 .def i2cdelay = r19 ; Delay loop variable .def i2cdata = r20 ; I2C data transfer register .def i2cadr = r21 ; I2C address and direction register .def i2cstat = r22 ; I2C bus status register .def Main1 = r23 ;Temp variable used by main program .def MiscFlags = r31 ;threads status flags + signal flags ;flags bit definition .equ Thread1Status = 0 ; 1 --> ready, 0 --> waiting .equ Thread2Status = 1 .equ Thread3Status = 2 .equ Thread1Signal = 3 .equ Thread2Signal = 4 .equ Thread3Signal = 5 .equ SerRequest = 6 .equ THRSTATUSMASK = 7 .equ SIGNALMASK = 112 ;*************************************************************************** ;* Port Pin Assignements ;*************************************************************************** ;port D bit definitions .equ Led1 = 0 ;out .equ Led2 = 1 ;out ;port B bit definitions .equ Key1 = 0 ;in (pullup) .equ SerTx = 4 ;out .equ SCLP = 6 ; SCL Pin number (port D) .equ SDAP = 7 ; SDA Pin number (port D) ;************************ ;* Constants (not change) ;************************ .equ b_dir = 0 ; transfer direction bit in i2cadr .equ i2crd = 1 .equ i2cwr = 0 ;*************************************************************************** ;* VECTORS ;*************************************************************************** rjmp RESET ;Reset Handle rjmp INT0DRV ;Ext. interrupt request 0 rjmp TIMERDRV ;Timer ;*************************************************************************** ;* ;* MAIN PROGRAM ;* ;*************************************************************************** ;* INITIALIZATION ;*************************************************************************** RESET: ldi Main1, 0b01111011 ;set port D bits to outputs (INT0 as an input) out DDRD, Main1 ldi Main1, 0b00000111 ;preset output state (activate INT0 pull-up) out PortD, Main1 ldi Main1, 0b00010000 ;set port B to inputs (SCL e SDA = 0), SerTx to output out DDRB, Main1 ldi Main1, 0b00101111 ;turn on pullups on inputs (SCL e SDA = 0) out PortB, Main1 ldi Main1, 5 out TCCR0, Main1 ;set prescaler to CK/1024 ldi Main1, 32+2 out MCUCR, Main1 ;enable sleep idle mode (ext int on falling edge) ldi Main1, 2 out TIMSK, Main1 ;enable timer interrupt ldi Main1, 64 out GIMSK, Main1 ;enable external interrupt ldi Main1, 256-3 ;load Timer Counter with 3 count to interrupt (1200bps) out TCNT0, Main1 ldi MiscFlags,THRSTATUSMASK sei ;enable interrupts in SaveThr1Status,SREG ;initialize status (interrupts enabled in all thread) mov SaveThr2Status,SaveThr1Status mov SaveThr3Status,SaveThr1Status ;===================================== ;THREAD 1 ; main loop THR1LOOP: ;test if the key is pressed sbic PinB,Key1 rjmp L001 cbi PortD,Led1 ;switch the led on ;start I2C communication ;the periferal is located at 0x90 + offset, where offset is given by A0,A1,A2 (address pin) ;for this example the address is 0x9E (A0 = +5V, A1 = +5V, A2 = +5V) ;**** Write data => Adr(9E) = 0xEE **** ;EE --> Start conversion ldi i2cadr,$9E+i2cwr ; Set device address and write rcall i2c_start ; Send start condition and address ldi i2cdata,$EE ; Write command byte rcall i2c_do_transfer ; Execute transfer brcs ERRNOACK rcall i2c_stop ;========= end conversion cycle READCONFIG: ;permit other thread to exececute sbr MiscFlags, EXP2(Thread1Signal) rcall WAIT1 ;**** Write data => Adr(9E) = 0xAC **** ;AC --> Read status/configuration register ldi i2cadr,$9E+i2cwr ; Set device address and write rcall i2c_start ; Send start condition and address ldi i2cdata,$AC ; Write command byte rcall i2c_do_transfer ; Execute transfer brcs ERRNOACK ;**** Read data => i2cdata = Adr(9E) **** ldi i2cadr,$9E+i2crd ; Set device address and read rcall i2c_rep_start ; Send repeated start condition and address sec ; Set no acknowledge (read is followed by a stop condition) rcall i2c_do_transfer ; Execute transfer (read) rcall i2c_stop ; Send stop condition - releases bus sbrs i2cdata,7 ; test DONE bit in status/configuration register rjmp READCONFIG ;====== Read temperature command ;**** Write data => Adr(9E) = 0xAA **** ;AA --> Read temperature ldi i2cadr,$9E+i2cwr ; Set device address and write rcall i2c_start ; Send start condition and address ldi i2cdata,$AA ; Write command byte rcall i2c_do_transfer ; Execute transfer brcs ERRNOACK ;**** Read data => i2cdata = Adr(9E) **** ldi i2cadr,$9E+i2crd ; Set device address and read rcall i2c_rep_start ; Send repeated start condition and address clc rcall i2c_do_transfer mov Temperatura,i2cdata ;temperature MSByte, we ignore the other byte sec ; Set no acknowledge (read is followed by a stop condition) rcall i2c_do_transfer ; Execute transfer (read) rcall i2c_stop ; Send stop condition - releases bus ;===== Transmit the temperature over the serial line mov SerData,Temperatura sec ;transmit most significant digit rcall BIN2ASCII sbr MiscFlags, EXP2(SerRequest) sbr MiscFlags, EXP2(Thread3Signal) ;send the request to RS232 thread rcall WAIT1 mov SerData,Temperatura clc ;transmit least significant digit rcall BIN2ASCII sbr MiscFlags, EXP2(SerRequest) sbr MiscFlags, EXP2(Thread3Signal) rcall WAIT1 ;====== done rjmp THR1LOOP L001: sbi PortD,Led1 ;switch off the led sbr MiscFlags, EXP2(Thread1Signal) rcall WAIT1 rjmp THR1LOOP ERRNOACK: ;transmit "err" over the serial line ldi SerData,101 ;'e' sbr MiscFlags, EXP2(SerRequest) sbr MiscFlags, EXP2(Thread3Signal) rcall WAIT1 ldi SerData,114 ;'r' sbr MiscFlags, EXP2(SerRequest) sbr MiscFlags, EXP2(Thread3Signal) rcall WAIT1 ldi SerData,114 ;'r' sbr MiscFlags, EXP2(SerRequest) sbr MiscFlags, EXP2(Thread3Signal) rcall WAIT1 rjmp THR1LOOP ;----------------- ;converts a binary byte to ASCII char ; ;if Carry == 1, most significant nibble ;otherwise less significant nibble ;----------------- BIN2ASCII: brcc DIGIT0 swap SerData DIGIT0: andi SerData,15 cpi SerData,10 brlo DIGDEC addi SerData,(65-10) ;'A' ret DIGDEC: addi SerData,48 ;'0' ret ;----------------- WAIT1: ;save status before switch to another thread in SaveThr1Status,SREG sbrs MiscFlags, Thread1Signal ;check for reeceived signal cbr MiscFlags, EXP2(Thread1Status) ;thread1 in wait state cbr MiscFlags, EXP2(Thread1Signal) ;reset signal rjmp SCHEDULER2 THR1WAKEUP: ret ;================================= ;THREAD 2 (do nothing) ; main loop THR2LOOP: ;thread 2 WAIT ;we can't do a separate Wait subroutine for every thread. Note that ;the stack is hardware, and not accessible. ;save status before switch to another thread in SaveThr2Status,SREG sbrs MiscFlags, Thread2Signal ;check for received signal, if we have already ;received the signal don't go sleep cbr MiscFlags, EXP2(Thread2Status) ;thread2 in wait state cbr MiscFlags, EXP2(Thread2Signal) rjmp SCHEDULER3 THR2WAKEUP: rjmp THR2LOOP ;=================================== ;THREAD 3: SERIAL OUTPUT DRIVER ; main loop THR3LOOP: ;thread 3 WAIT ;save status before switch to another thread in SaveThr3Status,SREG sbrs MiscFlags, Thread3Signal ;check for received signal cbr MiscFlags, EXP2(Thread3Status) ;thread3 in wait state cbr MiscFlags, EXP2(Thread3Signal) rjmp SCHEDULER1 THR3WAKEUP: ;testa SerRequest per capire se siamo stati risvegliati dal Thr1 o dal Timer sbrc MiscFlags, SerRequest rjmp THR3_L1 ;siamo stati risvegliati dal timer, controlliamo se ci sono dati da trasmettere tst SerNBit ;se zero non fa niente breq THR3LOOP dec SerNBit breq THR3_TXEND ;se zero tx stop e FINITO cpi SerNBit,9 ;tx start breq THR3_TX0 THR3_TXDATA: lsr SerData ;trasmette il prossimo bit (nel carry) brcc THR3_TX0 THR3_TX1: cbi PortB,SerTx rjmp THR3LOOP THR3_TX0: sbi PortB,SerTx rjmp THR3LOOP THR3_L1: ;nuova richiesta (dal Thr1) cbr MiscFlags, EXP2(SerRequest) ldi SerNBit,10 ;tx 10 bits: 1start + 8dati + 1stop rjmp THR3LOOP THR3_TXEND: sbr MiscFlags, EXP2(Thread1Signal) ;wakeup thread 1 rjmp THR3_TX1 ;*************************************************************************** ;* ;* FUNCTION ;* i2c_hp_delay ;* i2c_qp_delay ;* ;* DESCRIPTION ;* hp - half i2c clock period delay (normal: 5.0us / fast: 1.3us) ;* qp - quarter i2c clock period delay (normal: 2.5us / fast: 0.6us) ;* ;* SEE DOCUMENTATION !!! ;* ;* USAGE ;* no parameters ;* ;* RETURN ;* none ;* ;*************************************************************************** i2c_hp_delay: ldi i2cdelay,6 i2c_hp_delay_loop: dec i2cdelay brne i2c_hp_delay_loop ret i2c_qp_delay: ldi i2cdelay,3 ;2.333 i2c_qp_delay_loop: dec i2cdelay brne i2c_qp_delay_loop ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_rep_start ;* ;* DESCRIPTION ;* Assert repeated start condition and sends slave address. ;* ;* USAGE ;* i2cadr - Contains the slave address and transfer direction. ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to the address. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_start. ;* ;*************************************************************************** i2c_rep_start: sbi DDRB,SCLP ; force SCL low cbi DDRB,SDAP ; release SDA rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL rcall i2c_qp_delay ; quarter period delay ;*************************************************************************** ;* ;* FUNCTION ;* i2c_start ;* ;* DESCRIPTION ;* Generates start condition and sends slave address. ;* ;* USAGE ;* i2cadr - Contains the slave address and transfer direction. ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to the address. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_write. ;* ;*************************************************************************** i2c_start: mov i2cdata,i2cadr ; copy address to transmitt register sbi DDRB,SDAP ; force SDA low rcall i2c_qp_delay ; quarter period delay ;*************************************************************************** ;* ;* FUNCTION ;* i2c_write ;* ;* DESCRIPTION ;* Writes data (one byte) to the I2C bus. Also used for sending ;* the address. ;* ;* USAGE ;* i2cdata - Contains data to be transmitted. ;* ;* RETURN ;* Carry flag - Set if the slave respond transfer. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_get_ack. ;* ;*************************************************************************** i2c_write: sec ; set carry flag rol i2cdata ; shift in carry and out bit one rjmp i2c_write_first i2c_write_bit: lsl i2cdata ; if transmit register empty i2c_write_first: breq i2c_get_ack ; goto get acknowledge sbi DDRB,SCLP ; force SCL low brcc i2c_write_low ; if bit high nop ; (equalize number of cycles) cbi DDRB,SDAP ; release SDA rjmp i2c_write_high i2c_write_low: ; else sbi DDRB,SDAP ; force SDA low rjmp i2c_write_high ; (equalize number of cycles) i2c_write_high: rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL rcall i2c_hp_delay ; half period delay rjmp i2c_write_bit ;*************************************************************************** ;* ;* FUNCTION ;* i2c_get_ack ;* ;* DESCRIPTION ;* Get slave acknowledge response. ;* ;* USAGE ;* (used only by i2c_write in this version) ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to a request. ;* ;*************************************************************************** i2c_get_ack: sbi DDRB,SCLP ; force SCL low cbi DDRB,SDAP ; release SDA rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL i2c_get_ack_wait: sbis PINB,SCLP ; wait SCL high ;(In case wait states are inserted) rjmp i2c_get_ack_wait clc ; clear carry flag sbic PINB,SDAP ; if SDA is high sec ; set carry flag rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_do_transfer ;* ;* DESCRIPTION ;* Executes a transfer on bus. This is only a combination of i2c_read ;* and i2c_write for convenience. ;* ;* USAGE ;* i2cadr - Must have the same direction as when i2c_start was called. ;* see i2c_read and i2c_write for more information. ;* ;* RETURN ;* (depends on type of transfer, read or write) ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_read. ;* ;*************************************************************************** i2c_do_transfer: sbrs i2cadr,b_dir ; if dir = write rjmp i2c_write ; goto write data ;*************************************************************************** ;* ;* FUNCTION ;* i2c_read ;* ;* DESCRIPTION ;* Reads data (one byte) from the I2C bus. ;* ;* USAGE ;* Carry flag - If set no acknowledge is given to the slave ;* indicating last read operation before a STOP. ;* If cleared acknowledge is given to the slave ;* indicating more data. ;* ;* RETURN ;* i2cdata - Contains received data. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_put_ack. ;* ;*************************************************************************** i2c_read: rol i2cstat ; store acknowledge ; (used by i2c_put_ack) ldi i2cdata,0x01 ; data = 0x01 i2c_read_bit: ; do sbi DDRB,SCLP ; force SCL low rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL rcall i2c_hp_delay ; half period delay clc ; clear carry flag sbic PINB,SDAP ; if SDA is high sec ; set carry flag rol i2cdata ; store data bit brcc i2c_read_bit ; while receive register not full ;*************************************************************************** ;* ;* FUNCTION ;* i2c_put_ack ;* ;* DESCRIPTION ;* Put acknowledge. ;* ;* USAGE ;* (used only by i2c_read in this version) ;* ;* RETURN ;* none ;* ;*************************************************************************** i2c_put_ack: sbi DDRB,SCLP ; force SCL low ror i2cstat ; get status bit brcc i2c_put_ack_low ; if bit low goto assert low cbi DDRB,SDAP ; release SDA rjmp i2c_put_ack_high i2c_put_ack_low: ; else sbi DDRB,SDAP ; force SDA low i2c_put_ack_high: rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL i2c_put_ack_wait: sbis PINB,SCLP ; wait SCL high rjmp i2c_put_ack_wait rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_stop ;* ;* DESCRIPTION ;* Assert stop condition. ;* ;* USAGE ;* No parameters. ;* ;* RETURN ;* None. ;* ;*************************************************************************** i2c_stop: sbi DDRB,SCLP ; force SCL low sbi DDRB,SDAP ; force SDA low rcall i2c_hp_delay ; half period delay cbi DDRB,SCLP ; release SCL rcall i2c_qp_delay ; quarter period delay cbi DDRB,SDAP ; release SDA rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_init ;* ;* DESCRIPTION ;* Initialization of the I2C bus interface. ;* ;* USAGE ;* Call this function once to initialize the I2C bus. No parameters ;* are required. ;* ;* RETURN ;* None ;* ;* NOTE ;* PORTD and DDRD pins not used by the I2C bus interface will be ;* set to Hi-Z (!). ;* ;* COMMENT ;* This function can be combined with other PORTD initializations. ;* ;*************************************************************************** ; ;i2c_init: ; clr i2cstat ; clear I2C status register (used ; ; as a temporary register) ; out PORTB,i2cstat ; set I2C pins to open colector ; out DDRB,i2cstat ; ret ;--------------- ;Interrupts routine just send a signal to a thread. If you need a VERY short interrupt ;response you can put some instructions here, then send the signal to the thread ;--------------- TIMERDRV: in SaveStatus,SREG ldi Main1, 256-3 out TCNT0, Main1 sbr MiscFlags, EXP2(Thread3Signal) ;wakeup thread 3 out SREG,SaveStatus reti ;--------------- INT0DRV: in SaveStatus,SREG sbr MiscFlags, EXP2(Thread2Signal) ;wakeup thread 2 out SREG,SaveStatus reti ;----------------- ;This is the scheduler, the core of the nanoKernel. You can decide to go sleep ; when there's nothing to do. You can also put there the watchdog reset instruction. ;----------------- SCHEDULER: sei ;be sure interrupts on here ;if you don't want to go sleep remove next 3 instr tst MiscFlags brne SCHEDULER1 sleep SCHEDULER1: sbrc MiscFlags, Thread1Status ;test if thread1 is ready rjmp SCHEDRDY1 ;check for wakeup sbrs MiscFlags, Thread1Signal ;test if thread1 received a signal rjmp SCHEDULER2 cbr MiscFlags, EXP2(Thread1Signal) ;clear signal SCHEDRDY1: ;switch to thread 1 sbr MiscFlags, EXP2(Thread1Status) out SREG,SaveThr1Status rjmp THR1WAKEUP SCHEDULER2: sbrc MiscFlags, Thread2Status ;test if thread2 is ready rjmp SCHEDRDY2 ;check for wakeup sbrs MiscFlags, Thread2Signal ;test if thread1 received a signal rjmp SCHEDULER3 cbr MiscFlags, EXP2(Thread2Signal) ;clear signal SCHEDRDY2: ;switch to thread 2 sbr MiscFlags, EXP2(Thread2Status) out SREG,SaveThr2Status rjmp THR2WAKEUP SCHEDULER3: sbrc MiscFlags, Thread3Status ;test if thread3 is ready rjmp SCHEDRDY3 ;check for wakeup sbrs MiscFlags, Thread3Signal ;test if thread1 received a signal rjmp SCHEDULER cbr MiscFlags, EXP2(Thread3Signal) ;clear signal SCHEDRDY3: ;switch to thread 3 sbr MiscFlags, EXP2(Thread3Status) out SREG,SaveThr3Status rjmp THR3WAKEUP