📜 ⬆️ ⬇️

A simple digital thermometer / hygrometer on AM2302 (DHT22), ATtiny13 and MAX7219

The AM2302 (DHT22) digital temperature and humidity sensor is quite popular in the DIY segment, since at low cost (if you look at replicas made in China), it provides good measurement accuracy and is very simple to connect (three wires, including power). However, most of the examples of using this sensor are designed for Arduino and written in the C / C ++ programming language. This is perfect if you want to familiarize yourself with the functionality of the sensor or “quickly” screw the thermometer to an existing device. But if you want to assemble the thermometer / hygrometer and only it, the use of the whole Arduino board (or just a large MK with a couple of dozen of conclusions) may well seem superfluous.

This article will discuss a simple thermometer / hygrometer (hereinafter - just a thermometer), performed on one of the “smallest” microcontrollers - ATtiny13 with very modest characteristics - 1Kb of program memory, 64 bytes of RAM and 5th (6th if disable reset pin) interface pins. The article assumes that the reader is already a little familiar with AVR microcontrollers and their programming, but the article is mainly aimed at beginners in this field. By the way, about the programming language - the thermometer program is completely written in assembler.

So, let's begin. To display information on temperature and humidity, an 8-bit 7-segment LED indicator was selected, allowing you to display both parameters at once without having to switch between them. This indicator has 16 pins (8 segments + 8 digits), which is clearly “beyond the power” of the small ATtiny13 controller. Fortunately, Maxim manufactures the MAX7219 chip, specially designed for such cases - inside the chip contains all the functionality of the dynamic display for 8 bits plus a serial interface compatible with SPI. Thus, with this microcircuit, all of our indicator can be connected to an MK with just three wires (not counting ground and power). This is quite suitable for a controller with 5 interface outputs. By the way, the cost of one set of indicator, chip and PCB assembly was only $ 1.3 on aliexpress.
')
As mentioned above, AM2302 is used as a temperature and humidity sensor. It connects to the MK using only one wire. Thus, from the available 5 interface outputs of the MK, only 4 are used, and for the remaining 5th one can “hang” any additional function. Also, if you have an HVSP programmer available, you can disable the reset pin and use it as the 6th interface pin, but this will make the MK firmware update a bit difficult.

So, the whole scheme of the thermometer is shown in the figure below:
Schematic diagram

Since all interfaces for working with external devices of the MK are implemented programmatically, the choice of conclusions (pins) to which this or that signal is connected is purely arbitrary and made, most likely, according to the principle “where it was more convenient to insert this wiring on the breadboard”. So you can safely choose other conclusions, it will be necessary only to correct their number in the code. The only restriction - you should not connect the temperature sensor to one of the conclusions used for programming the MK via SPI - this can create a conflict, because the outputs of the two devices will be connected together, which is unacceptable from an electrical point of view.

Now that everything is clear with the connection of the sensor and the indicator, let's start writing the code directly. And here we are waiting for a new “challenge” - ATtiny13 does not have any serial interfaces on board, i.e. all their logic will have to be implemented programmatically. Fortunately, the implementation of the SPI for the MAX7219 is not difficult, since the synchronous protocol, the microcircuit operates at a frequency of up to 10 MHz, and the interface in our circuit works only for output. But communication with AM2302 will be a more difficult task, because it is connected only by one wire, the data on which are transmitted in both directions and the transmission speed is completely determined by the sensor itself. It should be said that most libraries for working with AM2302 follow a “simple path” - they prohibit interruptions and read all information from the sensor with a single function call. This is a simple and reliable solution, but it is hardly suitable if any other real-time functions are assigned to the MC (for example, dynamic display or continuous analysis of data from other sources), because the whole cycle of reading information about temperature and humidity takes from 4 up to 6 milliseconds (depending on the transmitted data). Despite the fact that there are no other real-time functions in this thermometer, it was decided to write a universal code that would read the information from the sensor “in the background”, i.e. on interrupts.

For maximum simplification of the circuit, the ATtiny13 is clocked from the built-in RC generator, issuing about 9.6 MHz. This allows, by causing an interrupt every 128 processor cycles, to obtain a sampling rate of AM2302 75KHz or 13.33 microseconds between adjacent polls. According to the AM2302 specification, the minimum pulse duration at its output is 26 microseconds, which is almost twice the polling interval and ensures stable data reading. Of course, 128 clocks between two interrupts are not very much to implement the polling algorithm, but AVR performs most of the commands per 1 clock, so it is quite possible to write a working program under such conditions, and there will be time to execute the main program.

AM2302 according to the specification can be interrogated no more than once every two seconds. However, practice shows that he is quite capable of giving the result, and more often - up to several times per second, provided that after power is turned on he will be given 1-2 seconds (according to the specification - 2) for initialization. In this thermometer, the sensor is polled once per second, but the polling interval is easily changed to any other value.

Unfortunately, AM2302 (perhaps its Chinese origin has an effect) has a rather large error of the result - two consecutive temperature queries can return a difference of 0.5 or even more degrees, so it was decided to programmatically average the data of the last 8 measurements so that the thermometer readings do not jump .

Now let's go directly to the code. The original asm and the resulting hex-file is located in the appendix at the end of the article, here I will explain the main points. It will be convenient to open the source code of the program in another window and look there while reading the article.

At the beginning of the program are two important definitions:

#define SKIPNEXT1W (PC + 2) #define DS(var) Y + var - _dataStart 

The first allows conditional transition through the next 16bit command (1 word, most AVR commands), i.e. skip it without introducing an additional label, for example:

  inc R16 cpi R16, 5 brne SKIPNEXT1W dec R16 ... 

The second allows you to access the first 64th bytes of RAM MK using 16-bit commands. Here I will tell you in more detail - usually to read or write to the RAM of the MK, the lds / sts commands are used, which take 2 words (32 bits) and are executed in 2 cycles. They allow you to address up to 64Kb (without extensions) of RAM. Unfortunately, the size of 32 bits (4 bytes) is already quite a lot for MKs with only 1KB of program memory. Therefore, to save program memory, the start address of the RAM (0x60 for ATtiny13) is placed in the Y register of the MK at startup, this register does not change any more, and access to the first 64 bytes of RAM is performed using indirect addressing with an offset in the Y register , eg:

  ldd R16, Y + 6 

The ldd / std commands are also executed in 2 clock cycles, but only occupy 16 bits (2 bytes), i.e. compared to the lds / sts commands, this type of addressing saves half the amount of program memory. In order not to calculate the offset of any variable manually in each command, at the very beginning of the data segment the mark _dataStart is put:

 .dseg _dataStart: ... testVar: .byte 1 

And the command uses the DS macro (short for Data Segment):

  ldd R16, DS (testVar) 

The compiler converts this to a string:

  ldd R16, Y + testVar - _dataStart 

Automatically calculating the desired offset. It should be noted that this type of addressing is limited by the capabilities of the ldd command itself, and these are the first 64 bytes relative to the base register. But, in the case of ATtiny13, which has just 64 bytes of RAM on board, it allows you to address all the memory. However, in other MCs with a larger amount of RAM, it is also possible to apply this method by placing the most frequently addressed variables in the first 64 bytes of the data segment. The price paid for this type of addressing is the Y register (two 8-bit registers R28 and R29), the value of which cannot be changed at any point of the program.

Further, the program determines the number of bits of port B (namely, the bits inside the byte, not the physical outputs of the chip) to which external devices are connected. Since all protocols of interaction with software devices are software, the bit numbers can be changed without any restrictions.

A special feature of AVR MK is that the first 16 registers R0 - R15 are “inferior”, commands that contain an operand - for example, ldi or subi - do not work with them. Those. to even load a value other than 0 into one of these registers, you must use an additional register:

  ldi R16, 32 mov R0, R16 

Therefore, such registers are often used as “fast access variables”. To do this, the compiler has a .def directive that allows you to assign an additional symbolic name to the register, for example:

 .def R_TS = R0 

In the thermometer program, the R0 register always stores the data receiver AM2302, the R1 register is used to calculate the signal reception time, R2 contains the received data, R3 is used as a counter of the timer, increasing with a frequency of 100 Hz, and R4 and R5 are counted as 75 KHz timer counting from 749 to 0.

The MK data segment is divided into 4 parts - a block of data received from AM2302 (5 bytes), a buffer for decimal printing of numbers (4 bytes), a buffer for averaging the readings of the thermometer and hygrometer by 8 values ​​(8 * 2 * 2 = 32 bytes) and stack MK (all remaining memory is separated to it, ie 23 bytes). In fact, of course, the stack takes up less, and in memory you can still find a few bytes for additional functions, but you should not get carried away.

Now let's go directly to the code segment. It traditionally begins with the interrupt table, for ATtiny13 it is 10 vectors, including the reset vector. Unused interrupts immediately contain the reti command, used (and there are two of them) - the transition command to the handler. The thermometer uses two interrupts serviced by one handler — this is the timer overflow interrupt and the timer equality interrupt with the OCRA value. It would be possible to do with one, but this method is 2 commands shorter (it is not necessary to change the timer operation mode from normal to ITS).

Immediately after the interrupt vectors, there is a table of translation of numbers into codes for ignition of 7-segment indicators. The decoding function built into MAX7219 could be used, but then it would be more difficult to display string messages on the indicator.
After the table, the program of the thermometer initialization begins, which is executed immediately after the reset of the MC It performs the initial installation of the MK stack pointer, the watchdog watchdog timer (set to 4 seconds), the entry of initial values ​​into the MK registers, as well as the initialization of I / O ports, MAX7219 and the main timer MK. After that, the program waits 2 seconds while AM2302 is initialized (demonstrating a simple animation of the dying minus signs on the display) and goes into its main loop.

The main loop begins with the initiation of a request to AM2302 by changing the state of the data receiver in the R_TS (R0) register. The closest timer interrupt will detect a change in state and begin a sensor polling cycle. Upon its completion, the value of TMS_NONE will be placed in the status bits of the R_TS register, and until this point, the main program can perform any actions. In this case, there is nothing to do, so the program simply puts the MK into sleep mode (sleep) and waits for the end of the polling cycle.

After polling is completed, bit 3 of the status register determines whether the data was received successfully (value 1) or an error occurred (value 0). In case of successful data acquisition, the program checks their checksum and, if necessary, transfers control to the error handler. The error handler counts the number of errors in a row, and as soon as this value becomes three, it displays the message “Sn Error”, indicating a sensor or connecting line malfunction. As soon as temperature and humidity data is received successfully, the error counter is reset. Such a mechanism allows ignoring single sensor errors that occur from time to time in real life.

In case of successful data acquisition, previous measurements in the data averaging buffer are shifted up, and new data is added to its beginning. In parallel, average values ​​are calculated, which will be shown on the display. It should be noted here that AM2302 produces a negative temperature not in the additional code, which is customary for processing by processors, but as an absolute value of temperature and a separate bit of its sign. In order to add such numbers and calculate their average values ​​using ordinary MK commands, the data must be translated into an additional code.

Since the initial averaging buffer is not initialized, the average values ​​of temperature and humidity are displayed only after eight successful measurements. Up to this point, the display shows the current values. In practice, this means that in the first 8 seconds after turning on the thermometer, the values ​​of temperature and humidity can jump within a degree, after which the readings stabilize. It should be said that the averaging of the last 8 values ​​has a very beneficial effect on the thermometer readings - now they mostly change by no more than 0.1 degrees per second.

The temperature is displayed in the “x.x”, “xx.x”, “xxxx.x”, “- x.x” or “-xxx” format, depending on its value. Humidity is displayed in the format "x.x" or "xx.x." To convert a binary number in the X register to decimal form (in accordance with the codes for the 7-segment indicator), use the printDecX function. Since the MC does not have a division command, the function is based on the successive subtraction from the original number of values ​​1000, 100 and 10. The maximum number that the function can output is 9999, if when it is called in the X register there is a greater number, the function will return an overflow error by setting the flag transfer.

To work with MAX7219, the maxWriteWord function is used, which writes the value from the XL register MK to the MAX register, the number of which is specified in the XH register. After displaying the values ​​of the current temperature and humidity on the display, the program makes a delay of 1 second and repeats the main loop again. To implement the delay, the function wait100Hz is used, which performs a delay for the time R16 * 0.01c using the counter R_TICK100, which is increased by interrupting the timer.

Data retrieval from the temperature sensor is performed using the function am2302proc, which is called from the timer interrupt handler. The function is a finite state machine whose state is stored in the R_TS (R0) MC register. Depending on the state, the function waits for a certain signal level from the sensor, initiating the transmission and consistently receiving all 40 bits of the transmitted information. Synchronization occurs at every change in the input signal level, so special accuracy from the timer interrupt frequency is not required (which allows the MC to work from the built-in generator). The function consists of a fast idle state handler (TMS_NONE), which allows minimizing the load on the MK processor when there is no communication with the sensor, a timeout handler designed to reset the machine to its original state, if the expected signal does not come for a long time (about 3 ms), and handlers for each individual state of the machine. It should be noted that this function does not have noise immunity - even if the impulse noise will change the level of the data line for a short period of time, but it is he who gets into the read operation from the port, the function will read the incorrect data. To compensate for this, the main program checks the checksum of the read data, so the display of incorrect information is almost impossible. However, such an implementation may not be the best if you want to take the sensor out of the thermometer and connect it to the MC with a long connecting line.

At the moment, the thermometer is assembled on a breadboard and looks like this:

The appearance of the thermometer

In the future, it is planned to place the thermometer inside the case of the existing electronic clock, organizing its power supply from the power supply of the clock.

The current program takes about 75% of the program memory of the MK. What can I add to the program? It may be useful for someone to change the brightness of the display (this is implemented directly in the driver MAX7219) by the external button or the light sensor (using the built-in ADC in the MC and free interface output), it may be useful for someone to memorize and display the minimum and maximum temperature. For small improvements the place is still there. Larger improvements may require changing the MC to another, having on board more software and RAM. As for the interface pins - at the moment, the MC has one completely unused output and one more can be obtained by turning off the RESET. Also two pins from the SPI interface (DATA and CLK) can be used for other functions, since until the CS pin is low (specifically for MAX7219 the transition from low level to high is important) the signals at these pins do not matter. That is, in principle, replacing the MK with a more powerful one, for example, ATtiny85, can be connected to a Real Time Clock (RTC) thermometer and up to four buttons.

My goal was to create a simple thermometer / hygrometer, so, most likely, I will keep it for myself in this form.

Text of the program
 // ********************************************* // *** Simple digital thermometer/hygrometer *** // ********************************************* // *** (c) SD, 14.03.2016 *** // ********************************************* // Based on ATtiny13, AM2303 and MAX7219 // ************** // *** Clocks *** // ************** // MCU clock frequency is 9.6MHz (internal oscillator) // Timer frequency is 75KHz = 9.6MHz/128 // (13.3 us between interrupts) #define SKIPNEXT1W (PC + 2) #define DS(var) Y + var - _dataStart // ************ // *** Pins *** // ************ // MAX7219 output pins .equ MAX_DIN = 0 .equ MAX_CS = 1 .equ MAX_CLK = 4 // AM2302 input pin .equ AM2302_PIN = 3 // MAX7219 registers .equ MAX_DECODE = 0x09 .equ MAX_INTENSITY = 0x0A .equ MAX_SCANLIMIT = 0x0B .equ MAX_SHUTDOWN = 0x0C .equ MAX_DISPTEST = 0x0F // Temperature measurement state register // Bits 0 - 2 define the byte number being received // Bit 3 is set when there are valid data received // Bits 4 - 7 define the current receiver state .def R_TS = R0 // Temperature measurement tick .def R_TT = R1 // Temperature data register .def R_TD = R2 // Temperature measurement states .equ TMS_NONE = 0x00 // TMS_NONE - do nothing an wait until // somebody changes the state .equ TMS_START = 0x10 // Start of the measurement cycle .equ TMS_ST_LOW = 0x20 // Initial low signal is being sent // (1 ms = 75 timer ticks) .equ TMS_WRSP_LOW = 0x30 // Initial low signal has been sent, // waiting for the response low signal .equ TMS_WRSP_HIGH = 0x40 // Response low signal has been received, // waiting for the response high signal .equ TMS_W1ST_BIT_LOW = 0x50 // Waiting for the first bit low signal .equ TMS_WBIT_HIGH = 0x60 // Waiting for the bit high signal .equ TMS_WBIT_LOW = 0x70 // Waiting for the bit low signal .equ TMS_WHIGH = 0x80 // Waiting for the final high signal // Timer 100Hz tick counter // (counts upwards from 0 to 255) .def R_TICK100 = R3 // Timer 16bit 75KHz tick counter // (counts downwords from 749 to 0) .def R_TICKL = R4 .def R_TICKH = R5 // ************ // *** Data *** // ************ .dseg _dataStart: // Data start label tempData: .byte 5 // Data, received from the AM2302 sensor displayData: .byte 4 // Decimal printing result .equ DATA_BUF_SIZE = 8 // AM2302 data buffer size in samples // (each sample is 4 bytes) dataBuffer: .byte DATA_BUF_SIZE*4 .cseg .org 0 // *** Interrupts *** // Reset Handler rjmp start // IRQ0 Handler reti // PCINT0 Handler reti // Timer0 Overflow Handler rjmp timerOvfl // EEPROM Ready Handler reti // Analog Comparator Handler reti // Timer0 CompareA Handler rjmp timerCompA // Timer0 CompareB Handler reti // Watchdog Interrupt Handler reti // ADC Conversion Handler reti // Table to convert decimal digit into 7-segment code hexTable: .db 0b01111110, 0b00110000, 0b01101101, 0b01111001 .db 0b00110011, 0b01011011, 0b01011111, 0b01110010 .db 0b01111111, 0b01111011 start: cli ldi R16, RAMEND out (SPL), R16 // Init watchdog (4s interval) wdr ldi R16, (1 << WDCE) | (1 << WDE) out (WDTCR), R16 ldi R16, (1 << WDE) | (1 << WDP3) out (WDTCR), R16 // Init registers ldi YL, low (_dataStart) ldi YH, high (_dataStart) clr R_TS clr R_TT clr R_TICKL clr R_TICKH clr R_TICK100 // Init ports out (PORTB), R_TS ldi R16, (1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK) out (DDRB), R16 // Init LED driver // Set all digits to "-" ldi XL, 0b00000001 ldi XH, 1 init1: rcall maxWriteWord cpi XH, 9 brne init1 // Set control registers ldi XL, 0 // Decode rcall maxWriteWord ldi XL, 4 // Intensity rcall maxWriteWord ldi XL, 7 // Scan limit rcall maxWriteWord ldi XL, 1 // Shutdown rcall maxWriteWord ldi XH, 0x0F ldi XL, 0 // Display test rcall maxWriteWord // Init timer for 1 interrupt each 128 CPU cycles ldi R16, 127 out (OCR0A), R16 ldi R16, 0b00000110 out (TIMSK0), R16 ldi R16, 0b00000001 out (TCCR0B), R16 // First part of the initialization is done. // Enable interrupts sei // Wait 2 sec (while AM2302 initialize itself) // with little animation ldi XH, 1 ldi XL, 0 init2: ldi R16, 25 rcall wait100Hz rcall maxWriteWord cpi XH, 9 brne init2 // R6 will contain the number of // measurement values received clr R6 // R7 will contain the number of // continious errors clr R7 loop: // Reset watchdog timer wdr // Initiate measurement ldi R16, TMS_START mov R_TS, R16 loop1: // Wait for the TMS_NONE state // which indicates that the measurement // is done sleep mov R16, R_TS andi R16, 0xF0 brne loop1 // Do we have the valid data? sbrs R_TS, 3 loop_error1: rjmp loop_error // Check control sum of the received data ldd R16, DS (tempData) ldd ZL, DS (tempData + 1) add R16, ZL ldd ZL, DS (tempData + 2) add R16, ZL ldd ZL, DS (tempData + 3) add R16, ZL ldd ZL, DS (tempData + 4) cp R16, ZL brne loop_error1 // We have valid new measurement data, // reset error count clr R7 // Move up data in the buffer // and count the sum at the same time. // R12:R13 will contain the humidity value and // R14:R15 the temperature value clr R12 clr R13 clr R14 clr R15 ldi ZL, low (dataBuffer + (DATA_BUF_SIZE - 2)*4) ldi ZH, 0 buf1: ldd R16, Z + 0 ldd R17, Z + 1 std Z + 4, R16 std Z + 5, R17 add R12, R16 adc R13, R17 ldd R16, Z + 2 ldd R17, Z + 3 std Z + 6, R16 std Z + 7, R17 add R14, R16 adc R15, R17 subi ZL, 4 cpi ZL, low (dataBuffer - 4) brne buf1 // Add new humidity value to the buffer // and to the sum ldd R16, DS (tempData + 1) ldd R17, DS (tempData) std DS (dataBuffer + 0), R16 std DS (dataBuffer + 1), R17 add R12, R16 adc R13, R17 // Add new temperature value to the buffer // and to the sum ldd R16, DS (tempData + 3) ldd R17, DS (tempData + 2) // Check for a negative value and R17, R17 brpl buf2 // Convert negative temperature to the 2's // complement form clr ZL andi R17, 0x7F neg R16 sbc ZL, R17 mov R17, ZL buf2: std DS (dataBuffer + 2), R16 std DS (dataBuffer + 3), R17 add R14, R16 adc R15, R17 // Divide the humidity and temperature // sum values by 8 (by shifting them right // three times) ldi R16, 3 buf3: asr R15 ror R14 asr R13 ror R12 dec R16 brne buf3 // Do we have 8 full measurements? mov R16, R6 cpi R16, 7 // If so, use the average values from // the buffer breq buf4 // Otherwise use the latest measurement ldd R12, DS (dataBuffer + 0) ldd R13, DS (dataBuffer + 1) ldd R14, DS (dataBuffer + 2) ldd R15, DS (dataBuffer + 3) inc R6 buf4: // Print out values // *** Humidity *** movw X, R12 rcall printDecX ldi XH, 1 ldd XL, DS (displayData + 3) rcall maxWriteWord ldd XL, DS (displayData + 2) ori XL, 0x80 rcall maxWriteWord ldd XL, DS (displayData + 1) rcall maxWriteWord ldd XL, DS (displayData) rcall maxWriteWord // *** Temperature *** movw X, R14 // Check for a negative value and XH, XH brpl buf5 // Calculate the absolute value clr ZL neg XL sbc ZL, XH mov XH, ZL buf5: rcall printDecX ldi XH, 5 ldd XL, DS (displayData + 3) rcall maxWriteWord ldd XL, DS (displayData + 2) ori XL, 0x80 rcall maxWriteWord ldd XL, DS (displayData + 1) rcall maxWriteWord // If temperature is negative // write the minus sign to the first digit // (temperatures of -100.0 and below // are not supported anyway) ldd XL, DS (displayData) and R15, R15 brpl SKIPNEXT1W ldi XL, 1 rcall maxWriteWord loop2: // Wait for 1 sec ldi R16, 100 rcall wait100Hz // And repeat rjmp loop loop_error: // An error had occured. // Increment error count inc R7 // Do we have 3 or more errors in a row? mov R16, R7 cpi R16, 3 // No? Just do nothing brne loop2 // Prevent error count from growing dec R7 // Display error ldi ZL, low (errText*2) ldi ZH, high (errText*2) rcall maxWrite8Bytes rjmp loop2 errText: // "Sn Error" .db 0b00000101, 0b00011101, 0b00000101, 0b00000101 .db 0b01001111, 0b00000000, 0b00010101, 0b01011011 // ********** // Waits given number (R16) of 100Hz ticks // Uses: Z wait100Hz: // Enable sleep ldi ZL, 0b00100000 out (MCUCR), ZL mov ZL, R_TICK100 w100: sleep mov ZH, R_TICK100 sub ZH, ZL cp ZH, R16 brcs w100 ret // Timer interrupt timerOvfl: timerCompA: push R16 in R16, (SREG) push R16 push ZL push ZH // Receive AM2303 data rcall am2302proc // Decrement current 75KHz tick ldi R16, 1 sub R_TICKL, R16 brcc timerRet sub R_TICKH, R16 brcc timerRet // Initialize 75KHz tick value ldi ZL, low (750 - 1) ldi ZH, high (750 - 1) movw R_TICKL, Z // Increment current 100Hz tick inc R_TICK100 timerRet: pop ZH pop ZL pop R16 out (SREG), R16 pop R16 reti // ************** // *** AM2302 *** // ************** amStart: // Send the start low signal. // Switch corresponding PORTB pin to output // (there is already 0 in the PORTB register) sbi (DDRB), AM2302_PIN ldi R16, TMS_ST_LOW rjmp amSetState amStartLow: // Initial start low signal is being sent. // Wait for 75 ticks cpi R16, 75 brne amNone // Switch PORTB pin back to input cbi (DDRB), AM2302_PIN ldi R16, TMS_WRSP_LOW // Do not check AM2303 input pin at this tick // since it's possible that it has not recovered // from the low state yet. rjmp amSetState amWRespLow: // Waiting for the response low signal sbrc ZH, AM2302_PIN ret ldi R16, TMS_WRSP_HIGH rjmp amSetState amWRespHigh: // Waiting for the response high signal sbrs ZH, AM2302_PIN ret ldi R16, TMS_W1ST_BIT_LOW rjmp amSetState amW1StBitLow: // Waiting for the first bit low signal sbrc ZH, AM2302_PIN ret // Get ready to receive the first bit ldi R16, 1 mov R_TD, R16 // Set new state and reset the byte counter ldi ZL, TMS_WBIT_HIGH rjmp amSetState2 amBitHigh: sbrs ZH, AM2302_PIN ret // If the bit low signal was there too long // (longer than 5 ticks (5*13.3 = 66.5us) // something went wrong) cpi R16, 6 brcc amResetState ldi R16, TMS_WBIT_LOW rjmp amSetState am2302proc: // First, check for the TMS_NONE state. // In this case just do nothing to // not waste MCU cycles. mov ZL, R_TS andi ZL, 0xF0 cpi ZL, TMS_NONE breq amNone // Increment receiver tick inc R_TT // If we are waiting for too long, // something went wrong, reset the state breq amResetState // Save the current tick into a more // convenient register mov R16, R_TT // Get input signal in ZH, (PINB) // Branch depending on the current state. // Check for TMS_WBIT_LOW first since it // has the longest service routine cpi ZL, TMS_WBIT_LOW breq amBitLow cpi ZL, TMS_START breq amStart cpi ZL, TMS_ST_LOW breq amStartLow cpi ZL, TMS_WRSP_LOW breq amWRespLow cpi ZL, TMS_WRSP_HIGH breq amWRespHigh cpi ZL, TMS_W1ST_BIT_LOW breq amW1StBitLow cpi ZL, TMS_WBIT_HIGH breq amBitHigh cpi ZL, TMS_WHIGH breq amWHigh amResetState: // In case of an error, reset state to // the default TMS_NONE ldi R16, TMS_NONE amSetState: // Preserve the current byte number mov ZL, R_TS andi ZL, 0x07 or ZL, R16 amSetState2: mov R_TS, ZL // Clear receiver tick counter clr R_TT amNone: ret amBitLow: sbrc ZH, AM2302_PIN ret // The high bit signal was too long? cpi R16, 8 brcc amResetState // Store input bit (inverted, since cpi produces // inverted result in the carry flag) cpi R16, 4 rol R_TD // Initally we set R_TD to 1, so when all 8 // bits are received, the carry flag will be set // indicating that a full byte has been received. // Otherwise, receive the next bit ldi R16, TMS_WBIT_HIGH brcc amSetState // We have the full byte. Invert it com R_TD // Save it mov ZL, R_TS andi ZL, 0x07 subi ZL, low (-tempData) ldi ZH, high (tempData) st Z+, R_TD // Did we receive all 5 bytes? cpi ZL, low (tempData + 5) ldi R16, TMS_WHIGH breq amSetState // OK, receive the next byte. // Increment the byte counter inc R_TS // Initialize R_TD ldi R16, 1 mov R_TD, R16 ldi R16, TMS_WBIT_HIGH rjmp amSetState amWHigh: sbrs ZH, AM2302_PIN ret cpi R16, 6 brcc amResetState // We received everything. Set // the state to TMS_NONE and set // the data validity bit ldi R16, 0x08 mov R_TS, R16 ret // ********* /* // Write data from Z // Uses R16 - R19, X, Z maxWriteData: lpm XH, Z+ tst XH brne SKIPNEXT1W ret lpm XL, Z+ rcall maxWriteWord rjmp maxWriteData maxInit: .db MAX_DECODE, 0 .db MAX_INTENSITY, 4 .db MAX_SCANLIMIT, 7 .db MAX_SHUTDOWN, 1 .db MAX_DISPTEST, 0 .db 0, 0 maxTest: .db 0, 0b00011101, 0b00010101, 0b00010000, 0b00011100, 0b00111101, 0b00000101, 0b01110111 */ // Writes 8 bytes from (Z) (program memory) // to MAX7219 // Uses R16 - R19, X, Z maxWrite8Bytes: ldi XH, 0x01 mw8b1: lpm XL, Z+ rcall maxWriteWord cpi XH, 9 brne mw8b1 ret // Write word X (XL = data, XH = address) to MAX2719 // Uses R16 - R19, X maxWriteWord: // Set all pins to zero in R17, (PORTB) andi R17, ~((1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK)) out (PORTB), R17 ldi R19, (1 << MAX_CLK) mov R16, XH rcall mww1 mov R16, XL rcall mww1 // Set LOAD(CS) to high thus writing all 16 bits into // MAX register sbi (PORTB), MAX_CS // Increment MAX register number inc XH ret mww1: ldi R18, 8 mww2: bst R16, 7 bld R17, MAX_DIN out (PORTB), R17 lsl R16 dec R18 // Create clock impulse by toggling clock output twice out (PINB), R19 out (PINB), R19 brne mww2 ret // ********* printDecX: ldi ZH, low (1000) ldi R16, high (1000) rcall pdx // Change zero digit to empty space cpi ZL, 0b01111110 brne SKIPNEXT1W ldi ZL, 0 std DS (displayData), ZL ldi ZH, 100 ldi R16, 0 rcall pdx // If this digit is zero and the first // digit is empty (ie it was zero too) // change this digit to empty space ldi R16, 0b01111110 eor R16, ZL ldd ZH, DS (displayData) or R16, ZH brne SKIPNEXT1W ldi ZL, 0 std DS (displayData + 1), ZL ldi ZH, 10 ldi R16, 0 rcall pdx std DS (displayData + 2), ZL mov ZL, XL rcall pdx3 std DS (displayData + 3), ZL // Clear carry flag to indicate that // no error occurred clc ret pdx: ldi ZL, 0 pdx1: sub XL, ZH sbc XH, R16 brcs pdx2 cpi ZL, 9 breq pdxOverflow inc ZL rjmp pdx1 pdx2: add XL, ZH adc XH, R16 pdx3: subi ZL, -low (hexTable << 1) ldi ZH, high (hexTable << 1) lpm ZL, Z ret pdxOverflow: // Set carry flag to indicate error sec // Pop return address out of the stack // so we can return to the caller of printDecX pop R16 pop R16 ret 


HEX- (fuses: H:FF, L:7A)
:020000020000FC
:100000000EC018951895C2C018951895BFC01895C0
:10001000189518957E306D79335B5F727F7BF8940D
:100020000FE90DBFA89508E101BD08E201BDC0E6DA
:10003000D0E00024112444245524332408BA03E1D9
:1000400007BBA1E0B1E015D1B930E9F7A0E011D1CB
:10005000A4E00FD1A7E00DD1A1E00BD1BFE0A0E05B
:1000600008D10FE706BF06E009BF01E003BF78949F
:10007000B1E0A0E009E181D0FCD0B930D9F7662425
:100080007724A89500E1002E8895002D007FE1F7E8
:1000900003FE66C00881E9810E0FEA810E0FEB8135
:1000A0000E0FEC810E17A9F77724CC24DD24EE2463
:1000B000FF24E1E8F0E00081118104831583C00E84
:1000C000D11E0281138106831783E00EF11EE450D6
:1000D000E53689F70981188109871A87C00ED11E74
:1000E0000B811A8111232AF4EE271F770195E10B6A
:1000F0001E2F0B871C87E00EF11E03E0F594E7949A
:10010000D594C7940A95D1F7062D073029F0C984F4
:10011000DA84EB84FC846394D601C0D0B1E0A88576
:10012000A8D0AF81A068A5D0AE81A3D0AD81A1D069
:10013000D701BB2322F4EE27A195EB0BBE2FAED047
:10014000B5E0A88596D0AF81A06893D0AE8191D05C
:10015000AD81FF200AF4A1E08CD004E60ED091CF4F
:100160007394072D0330C9F77A94E2E7F1E07BD06E
:10017000F4CF051D05054F00155BE0E2E5BFE32D5B
:100180008895F32DFE1BF017D8F308950F930FB742
:100190000F93EF93FF932BD001E0401A30F4501AE5
:1001A00020F4EDEEF2E02F013394FF91EF910F91E7
:1001B0000FBF0F911895BB9A00E232C00B34A9F51E
:1001C000BB9800E32DC0F3FD089500E429C0F3FFC0
:1001D000089500E525C0F3FD089501E0202EE0E636
:1001E00022C0F3FF08950630D0F400E719C0E02DD7
:1001F000E07FE030D1F0139491F0012DF6B3E037B9
:10020000A9F0E031C1F2E032C9F2E033E1F2E034CA
:10021000F1F2E03501F3E03621F3E038E9F000E0F7
:10022000E02DE770E02B0E2E11240895F3FD0895C4
:100230000830A8F70430221C00E690F72094E02D47
:10024000E770E05AF0E02192E53600E849F30394C4
:1002500001E0202E00E6E4CFF3FF08950630F8F623
:1002600008E0002E0895B1E0A59103D0B930E1F780
:10027000089518B31C7E18BB30E10B2F05D00A2F50
:1002800003D0C19AB395089528E007FB10F918BB75
:10029000000F2A9536BB36BBC1F70895F8EE03E090
:1002A00017D0EE3709F4E0E0ED83F4E600E010D07B
:1002B0000EE70E27FD810F2B09F4E0E0EE83FAE054
:1002C00000E006D0EF83EA2F0DD0E88788940895E8
:1002D000E0E0AF1BB00B20F0E93041F0E395F9CF3F
:1002E000AF0FB01FEC5EF0E0E491089508940F9119
:0402F0000F910895CD
:00000001FF

Source: https://habr.com/ru/post/371973/


All Articles