/
Теги: программирование
Текст
Электроника
Source
text file
Binary
Machine
Language
ARDUINO HA ASSEMBLER
На распутье
Вряд ли программы промышленных автономных устройств составлены на платформе
Arduino IDE. Где все спрятано в громоздкие тяжеловесные библиотеки, а простые
коды (скетчи) занимают в редакторе несколько десятков строк, делая работу в
этой среде комфортной и не требующей особых усилий. Уточню сразу - дальше
речь о выборе языка программирования между Ардуино, Си или Ассемблером. "Язык
Ардуино"- это сленг для краткости. Нет такого языка программирования, это
"Arduino IDE — интегрированная среда разработки для Windows, MacOS и Linux,
разработанная на Си и С ++".
Почему многие не любят Arduino? Ниже, для наглядности, картинка оттуда с
кодом «мигалки».
'оз forGeekTimes2 | Arduino 1.6.7
©
Файл Правка Скетч Инструменты Помощь
©О ПЕЭЕЗ
-;р I ,1 ■
ъ (13,
о П
- il'J'J'JI ;
(1000);
|^' forGeekTimesl | Arduino 1,6.7
О I ОО ШНН
■аашвввв
<avr л.о . л:
<util ■■':-.■=_
DDR3 - (1 « 5}
FCRT3 i=
_delay_ras
FCR73 -
delav ms
1 <■: 5};
1000);
Пример слева написан в платформе Arduino IDE, а справа — работа непосредст-
венно с регистрами. Скетч выглядит несколько компактней, чем та же «мигалка»,
но с использованием регистров.
На изображении ниже — компиляция кода «мигалки» на Ассемблере. Как видно,
былая компактность испарилась - количество строк в 3 раза больше, чем в Ар-
дуино.
И
u;5er4t<<r*-E'E'K.stionl ■ uh-r>e<l>.4^,; |Лг)ттг:>'г*-.-
-3, *<-.:« *.'.:,л
-- у J-
^ г.'\ *>
*> к -.
ч *-,
-*~- w^v i '*■-•
1 nil! SPII, 'U»j + 1
1 LDI 416, LOH(RAMEND)
1 ом Г S?l , 416
1 MA IN:
1 LIU !(К., 0x H-
1 i:i.l| IJlbX f 41 о
1 '.ЧАС. < ;
1 OJI-' 416
1 OUT -'04R , 416
1 •: Д1 1 DM AY
1 4 :(•':' ;jAC <
1 DELAY;
1 1 IJ1 4 1 7 , 100
1 НИ !;•••■: МП R]H, /l-S
1 LOO "'2; LDI 419, 2У_>
1 LOU21: DK RV-*
1 --:*<:'if 100-4
1 f.Hi. ;U8
1 i:;4*H LOU P 2
1 и f ■: 417
1 -.-v-if- цнич
1 4L 1
?;Л.Г::;\г'\,Н..-":'.':':.::: :'.:t:':::V/:::::;::::";:;':::;: :\:"::t
Итак, с двух картинок выше видно - размер памяти, занимаемой в контроллере
кодом «мигалки» одним светодиодом, написанным в платформе Ардуино, составляет
1030 байт, на Си - 176 байт, на Ассемблере - 42 байта.
Теперь взглянем на более сложный код. Поскольку в своих проектах использую
модуль давления-температуры ВМР280, составил код барометра-термометра на Си,
чтобы заодно была какая-то польза.
h
На распутье - Ардуино, Си или Ассемблер?
7
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include ffbmpl80/bmpl80. c"
#include "uart.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nokia/nokia5110.h"
int main(void) {
serial_init () ;
DDRD |= (1 « 7); //pin 13, atmega328p
PORTD &= ~(1 « 7);
nokia_lcd_init () ;
while (1) {
init_sensor (bmpl80_mode_0) ;
calculate();
PORTD |= (1 « 7);
_delay_ms(100);
printf("Temperature:%.2fС,Pressure:%.2f Pa,\n",(float)bmp_180.temperature/
10,(float)bmp_180.pressure);
nokia_lcd_clear () ;
nokia_lcd_write_string("789",l);
nokia_lcd_set_cursor(0, 10);
nokia_lcd_write_string("22.2", 3);
nokia_lcd_render();
_delay_ms(2000);
PORTD &= -(1 « 7);
_delay_ms(100);
int a=54325;
char buffer[20];
itoa(a,buffer,2); // here 2 means binary
printf("Binary value = %s\n", buffer);
itoa(a,buffer,10); // here 10 means decimal
printf("Decimal value = %s\n", buffer);
itoa(a,buffer,16); // here 16 means Hexadecimal
printf("Hexadecimal value = %s\n", buffer);
}
return 0;
FiH u« v»«vk Ten Diuqr Ci*n-i Oteuq Library Т|гт^:Ыя V,4l»m Htip
di АПм^я
-► + <
н с
о
11
о
О)
л
ш
W
J. 1 оо
U.'TTCo.
nr-;
РЭ'ЛлХНРСКТ»?
РЭ1ТЛ" ЮГ»РС Г Л О
РО*ТО»СКРСГ/ГЯ]
РЭГ.«.ЫРСГ4Г23
p*i V3 аог^лрс tin
PB^CSC » ЛТП 1 *;?Я*
ТОТГ ЭОСаГГЛДРС Г Л7
РС1ЛСС«ХГЛЭ
PC:iAX:yPCJHTr
Ok
R?
ь
., •
-1 »
J
г
L
г
*
—^
—
ms ^^^^^^^^H
CTS> ншвша
вд
из
744 №,Ш
21.7С
§5SsseS§
¥"
JL
В проект входят следующие компоненты: контроллер ATMEGA328P, модуль давле-
ния-температуры ВМР180 и дисплей Nokia 3110. ATMEGA328P принимает инфу с дат-
чика ВМР180 и после преобразований отображает ее на дисплее Nokia 3110, затем
спит. Сон задается сторожевым таймером Watchdog. Проект собирается в Atmel
Studio 7 и эмулируется в Proteus 8 Pro. Этот проект Atmel Studio был создан
для отладки кода в Proteus*е. В библиотеке Proteus 8 Pro модуля ВМР280 нет,
поэтому пришлось составить код с включением ВМР180. Светодиод в коде — для
наглядности, чтобы придать динамику статичной картинке.
Ниже — электрическая схема устройства. При монтаже схемы обращайте внимание
на функциональное назначение выводов контроллера и модулей. Подключение квар-
ца — XTAL1, XTAL2 (ATMEGA328P) . Уточню, схему барометра-термометра на ВМР180
я **в железе** не собирал, поэтому тут могут проявиться проблемы, которые не
видны при эмуляции в Proteus * е.
i
г~~з
F-C O^'xD/pi": 1Г Л" 1R
F01/T.XD/PCIWT17
р-С'2''1ГчГГГ1.'РС1ГЛ"18
FO-У1Г-Л" 1.'ОС 2&РС IГ-Г19
pi:4,'Tr^xr;K,'Pcirrjo
FDST1/0C OB/PC INT21
PC tv.*! Ni>:jC0A.'"PC INT22
F07/AJN1.'P:iNT22
AREF
AVri'j
ATMEOA32tP
РВПЛСР'/С1.И0/РС NTO
-'BVCC'A/PC NT1
Pe2/S5.-vX*B/PCNT2
PB3,'Mos;i:'C2A;Fcir.fT3
P64y\1iS0.'PONT4
РБ5/ЗСК,РС1ГГ5
РВ£ЛС6С 1VXTAL1 'PC INT 6
PB7/TOSC 2-' X TAL2/FC INT7
-'COVADCCvPCir-rS
aC1'ADC1/PCIN"9
PC2,'AZ-C2.»PCINT,0
PC?..'ACC^PCINT'1
РС1'А1С.Д/50А/РС1Гч1Т12
FCfv"ADi^/5CUFCir4T13
PCn;RFsFT,'pLir-ru
19
NOKAi310
U2
Л
VJOIC
BMP 180
♦3 3V
Ниже — код этого же барометра-термометра в платформе Arduino IDE. Естест-
венно, с другими библиотеками.
/'
На распутье - Ардуино, Си или Ассемблер?
7
#include <SPI.h>
#include <LowPower.h>
#include <SimpleTimer.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_GFX.h>
//https:esp8266.rU/forum/threads/esp8266-5110-nokia-lcd.1143/#post-16942
#include <Adafruit_PCD8544.h>
//https:esp8266.rU/forum/threads/esp8266-5110-nokia-lcd.1143/#post-16942
Adafruit_BMP280 bmp280;
float Press, Tin; //давление, температура
Adafruit PCD8544 display = Adafruit PCD8544(5, 7, 6);
void setup() {
Serial.begin(9600);
di splay.begin() ;
// display. clearDisplay () ;
display.setContrast(60); // установка контраста
while (!bmp280.begin(BMP280_ADDRESS - 1)) {
Serial.println(F("Could not find a valid BMP280 sensor, check wiring!"));
delay(100);
}
}
void loop() {
// измерение температуры, давления
Tin = bmp280.readTemperature();
Press = bmp280.readPressure() / 133.3;
Serial.println("Temperature: " + String(Tin) + "*C");
Serial.println("Pressure: " + String(Press) + "mm Hq");
display.clearDisplay();
//давление, мм рт.ст.
{
display.setTextSize(1);
display.setCursor(20, 5);
display.println (Press, 0); // нет знаков после запятой
display.setCursor(41, 5);
display.println("mmHq");
}
//температура, *С
{
display.setTextSize(2);
display.setCursor(15, 20);
display.println (Tin, 1); // один знак после запятой
display.setCursor(66, 20) ;
display.println("С");
}
display.display();
LowPower.powerDown (SLEEP_8S , ADC_OFF, BOD_OFF) ;
Ресурсы, потребляемые программой барометра-термометра на Си и в Arduino IDE
наглядно показаны на картинке ниже.
Как видно, эти примеры потребляют 5954 байт (С) и 12956 байт (Arduino IDE)
в Flash. Соотношение изменилось с 6-ти раз для «мигалки» до 2-х с небольшим.
К сожалению, линейной зависимости нет - чем объемней код, тем меньше соотно-
шение размеров памяти Ардуино к Си. В идеале на этой картинке должен присут-
ствовать 3 столбец с кодом на Ассемблере, но такого кода в Интернете я не на-
шел, а составить код самому мне пока не под силу.
Попутно замечу, что использование компилируемых в Arduino IDE библиотек и
функций на C/C++ особо имеет смысл в тех случаях, когда размер занимаемой па-
мяти превышает или близок к размеру памяти контроллера. Мне, например, часто
удается уходить от предупреждения: Недостаточно памяти, программа может рабо-
тать нестабильно.
ul Г.-(I)
Теперь посмотрим еще один вариант - это цифровой термометр-гигрометр на
АМ2302 (DHT22), ATtinyl3 и МАХ7219, код которого составлен на Ассемблере.
Автор задался целью разработать простой термометр-гигрометр выполненном на
одном из самых «маленьких» микроконтроллеров — ATtiny13 с весьма скромными
характеристиками - 1Кб программной памяти, 64 байтами ОЗУ и 5 интерфейсными
выводами. Он решил эту непростую задачку, выбрав Ассемблер, заодно вспомнив
те далекие времена, когда код можно было составлять на низкоуровневых языках,
используя машины типа ZX-Spectrum.
// *********************************************
// *** simple digital thermometer/hygrometer ***
// *********************************************
// *** (с) SD, 14.03.2016 ***
// *********************************************
II Based on ATtinyl3, 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 = OxOA
.equ MAX_SCANLIMIT = OxOB
.equ MAX_SHUTDOWN = OxOC
.equ MAX_DISPTEST = OxOF
// 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 = Rl
// Temperature data register
.def R TD = R2
// Temperature measurement
.equ TMS_NONE =
.equ TMS_START =
.equ TMS_ST_LOW =
.equ TMS_WRSP_LOW =
.equ TMS_WRSP_HIGH =
signal
.equ TMS_W1ST_BIT_L0W =
.equ TMS_WBIT_HIGH =
.equ TMS_WBIT_LOW =
.equ TMS WHIGH =
states
0x00
0x20
0x30
0x40
//
//
их
//
//
//
//
//
TMS_NONE - do nothing an wait until
somebody changes the state
L0 // Start of the measurement cycle
Initial low signal is being sent
(1 ms = 75 timer ticks)
Initial low signal has been sent,
waiting for the response low signal
Response low signal has been received,
// waiting for the response
0x50 // Waiting for the first bit low signal
0x60 // Waiting for the bit high signal
0x70 // Waiting for the bit low signal
0x80 // Waiting for the final high signal
high
// 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:
displayData:
.byte 5
.byte 4
// Data, received from the AM2302 sensor
// 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
// IRQO Handler
reti
// PCINTO 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 ObOllllllO, ObOOllOOOO, ObOllOllOl, ObOllllOOl
.db ObOOllOOll, ObOlOllOll, ObOlOlllll, ObOlllOOlO
.db ObOlllllll, ObOllllOll
start:
cli
ldi
out
R16, RAMEND
(SPL), R16
// Init watchdog (4s interval)
wdr
ldi
out
ldi
out
R16, (1 « WDCE)
(WDTCR), R16
R16, (1 « WDE) |
(WDTCR), R16
| (1 « WDE)
(1 « WDP3)
// 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
initl:
// Init ports
out (PORTB), R_TS
ldi R16, (1 « MAX_DIN)
out (DDRB), R16
// Init LED driver
// Set all digits to "-"
ldi XL, ObOOOOOOOl
ldi XH, 1
rcall maxWriteWord
cpi XH, 9
brne initl
| (1 « MAX CS) | (1 « MAX CLK)
// Set control registers
ldi XL, 0
rcall maxWriteWord
ldi XL, 4
rcall maxWriteWord
ldi XL, 7
rcall maxWriteWord
ldi XL, 1
rcall maxWriteWord
ldi XH, OxOF
ldi XL, 0
rcall maxWriteWord
// Decode
// Intensity
// Scan limit
// Shutdown
// Display test
// Init timer for 1 interrupt each 128 CPU cycles
ldi R16, 127
out (OCROA), R16
ldi R16, ObOOOOOllO
out (TIMSKO), R16
ldi R16, ObOOOOOOOl
out (TCCROB), 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 waitlOOHz
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
loopl:
// Wait for the TMS_NONE state
// which indicates that the measurement
// is done
sleep
mov R16, R_TS
andi R16, OxFO
brne loopl
// Do we have the valid data?
sbrs R_TS, 3
loop_errorl:
rjmp loop_error
// Check control sum of the received data
ldd
ldd
add
ldd
add
ldd
add
ldd
cp
brne
loop
R16, DS (tempData)
ZL, DS (tempData +
R16, ZL
ZL, DS (tempData +
R16, ZL
ZL, DS (tempData +
R16, ZL
ZL, DS (tempData +
R16, ZL
errorl
1)
2)
3)
4)
// We have valid new measurement data,
// reset error count
clr R7
bufl:
// 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
clr
clr
clr
ldi
ldi
ldd
ldd
std
std
add
adc
ldd
ldd
std
std
add
adc
R12
R13
R14
R15
ZL, low (d
ZH, 0
R16, Z + 0
R17, Z + 1
Z + 4, R16
Z + 5, R17
R12, R16
R13, R17
R16, Z + 2
R17, Z + 3
Z + 6, R16
Z + 7, R17
R14, R16
R15, R17
(dataBuffer + (DATA BUF SIZE - 2)*4)
subi ZL, 4
cpi ZL, low (dataBuffer - 4)
brne bufl
// 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 2fs
// complement form
clr
andi
neg
sbc
mov
R17,
ZL
0x7F
R16
ZL, R17
R17, ZL
buf2:
buf3:
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
ldi
asr
ror
asr
ror
dec
brne
times)
R16,
R15
R14
R13
R12
R16
buf3
//Do we have 8 full measurements?
mov Rl6, 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 waitlOOHz
// 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 Rl б, 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 ObOOOOOlOl, ObOOOlllOl, ObOOOOOlOl, ObOOOOOlOl
.db ObOlOOllll, ObOOOOOOOO, ObOOOlOlOl, ObOlOllOll
// **********
// Waits given number (R16) of 100Hz ticks
// Uses: Z
waitlOOHz:
// Enable sleep
ldi ZL, ObOOlOOOOO
out (MCUCR) , ZL
mov
ZL, R TICK100
wlOO:
sleep
mov
sub
cp
brcs
ret
wlOO
ZH,
ZH,
ZH,
R_TICK100
ZL
R16
// Timer interrupt
timerOvf1:
timerCompA:
push Rl 6
in
push Rl 6
push ZL
push ZH
R16, (SREG)
// 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_T ICKL, Z
// Increment current 100Hz tick
inc R TICK100
timerRet:
pop
pop
pop
out
pop
reti
ZH
ZL
R16
(SREG),
R16
R16
// **************
// *** АМ2302 ***
/ / **************
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_L0W
rjmp amSetState
amWl S tBi tLow:
// 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 , Rl 6
// 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, OxFO
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 Rl6, 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_L0W
breq amWlStBitLow
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 , Rl 6
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
maxlnit:
.db
.db
.db
.db
.db
.db
MAX_DECODE, 0
MAX_INTENSITY, 4
MAX_SCANLIMIT, 7
MAX_SHUTDOWN, 1
MAX_DISPTEST, 0
0, 0
maxTest:
.db 0, ObOOOlllOl, ObOOOlOlOl, ObOOOlOOOO, ObOOOlllOO, ObOOllllOl,
ObOOOOOlOl, ObOlllOlll
*/
// Writes 8 bytes from (Z) (program memory)
// to MAX7219
// Uses R16 - R19, X, Z
maxWrite8Bytes:
ldi XH, 0x01
mw8bl:
lpm XL, Z+
rcall maxWriteWord
cpi XH, 9
brne mw8bl
ret
// Write word X (XL = data, XH = address) to MAX2719
// Uses R16 - R19, X
maxWri teWord:
// 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 mwwl
mov Rl6, XL
rcall mwwl
// Set LOAD(CS) to high thus writing all 16 bits into
// MAX register
sbi (PORTB), MAX_CS
// Increment MAX register number
inc XH
ret
mwwl:
mww2:
ldi R18, 8
bst
bid
out
lsl
dec
R16, 7
R17, MAX_DIN
(PORTB), R17
R16
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, ObOllllllO
brne SKIPNEXT1W
ldi ZL, 0
std DS (displayData), ZL
ldi
ZH, 100
ldi R16, О
rcall pdx
// If this digit is zero and the first
// digit is empty (i.e. it was zero too)
// change this digit to empty space
ldi R16, ObOllllllO
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
ldi
i .
±.
sub
sbc
brcs
cpi
breq
inc
rjmp
2:
add
adc
ZL, 0
XL, ZH
XH, R16
pdx2
ZL, 9
pdxOverflow
ZL
pdxl
XL, ZH
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 Rl6
pop Rl6
ret
Ниже скриншот со сборкой данного кода в Atmel Studio 7.
Код устройства на Ассемблере занимает 738 байт памяти в контроллере. Безус-
ловно, программа барометра-термометра, о котором шла речь выше, будь ее код
составлен на Ассемблере, заняла бы больше места. По нескольким причинам — в
схеме реализовано управление дисплеем Nokia3110 по интерфейсу SPI (это 5 ли-
ний связи, тут - 3) , связь с датчиком ВМР280 осуществляется по протоколу 12С
(2 линии, тут - 1) и дополнительные символы, которые позволяют не гадать -
температура это или другой параметр.
Из того, что я нашел в Интернете, можно утверждать, Ассемблер даст выигрыш
в размере кода для относительно больших проектов процентов 10-20 по сравнению
с Си. Но надо учитывать, что в больших проектах Си может уменьшить размер ко-
да за счёт лучшей оптимизации.
Код Ассемблера выполняется практически на машинном уровне: один цикл - одна
команда. В качестве аргумента приведу пример из справочника по командам ас-
семблера AVR. Установка бита в регистре ввода/вывода — SBI А, Ь. Эта команда
устанавливает заданный бит в регистре ввода-вывода. На выполнение этой опера-
ции контроллерами megaAVR потребуется 2 цикла и на tinyAVR, XMEGA — 1 цикл.
Для схемы с контроллером AT tiny 13 и резонатором 9,6 МГц выполнение команды
займет один цикл, то есть 1/9600000 Гц = 0,104 мксек.
Выполнение похожей операции на языке Си, например, задать состояние порта —
PORTB = 32; займет в этой же схеме не меньше времени. А о Ардуино и говорить
нечего - там придется выполнить объемную функцию
void digitalWrite(uint8_t pin, uint8_t val);.
Поэтому разработчики простых в управлении серийных продуктов (холодильник,
кофеварка без наворотов, другое — оглянитесь вокруг себя дома), как правило,
пишут коды на низкоуровневых языках. С тем, чтобы разместить программу в кон-
троллере с меньшей памятью. Тут работают законы экономики — контроллер с
меньшими ресурсами стоит дешевле, следовательно себестоимость изделия стано-
вится ниже.
Теперь о энергосбережении немножко издали. Вспомним, что код Ассемблера вы-
полняется на машинном уровне: один цикл - одна или несколько команд, в зави-
симости от типа контроллера. Это — десятые доли микросекунды. То есть, на вы-
полнение программы с размером несколько десятков байт уйдут единицы-десятки
микросекунд. Дальше контроллер бесконечно будет крутить этот набор «О» и «1»,
затрачивая энергию на перезаряд емкости затворов сотен полевых транзисторов,
на которых построен кристалл контроллера, а также чтение и записи данных в
его память. Длительность периода повтора будет зависеть только от размера ко-
да в памяти контроллера, неважно на каком языке он составлен. Просто на
Assembler'e он будет наименьшим, а в Arduino IDE - наибольшим. Соответствен-
но, период цикла для кода на Assembler'e - наименьший, в Arduino IDE - наи-
больший .
Уменьшить эти затраты можно остановив процессор или программно уменьшив
частоту его работы. В Ассемблере переход в "спящий" режим сна выполняет функ-
ция управления контроллером SLEEP. В других можно использовать функцию WDT
(WatchDog Timer), а в Ардуино еще и функцию LowPower. powerDown (SLEEP_1S,
ADC_OFF, BOD_OFF), заодно отключив все лишнее, что не используется в конкрет-
ной задаче. В эффективности этой функции сможет убедиться каждый, заменив в
скетче «мигалки» функцию отсчета времени delay(1000); этой функцией и включив
в разрыв питания контроллера амперметр. Да, не забудьте подключить библиотеку
LowPower.h. Ток в цепи питания attinyl3a с паузой — 1,5мА, со сном — 240мкА.
Потребление в 6(!) раз меньше.
Допустим, вы намерены собрать барометр-термометр и задумываетесь о энерго-
сбережении. Понятно, что давление/температура в заданной разрядности не изме-
нятся за несколько минут, которые для контроллера целая вечность. Ему можно
выделить это время для сна. После сна он снова выполнит свою работу: примет
информацию с датчика, преобразует в понятные для человека циферки и выведет
все это на дисплей. И в таком режиме «работа-сон» он будет крутиться, пока не
сядут батарейки. Объем «работы» контроллера, вернее время, которое контроллер
будет занят выполнением работы, зависит от того, на каком языке составлена
программа барометра-термометра. Если есть возможность загрузить в контроллер
код на выбор - ArduinoIDE, С, Assembler, с одинаковым временем «сна», то в
каком из трех предложенных вариантов батарейки сядут раньше (позже)? Мой от-
вет - ArduinoIDE (Assembler).
Так куда же идти? На мой взгляд, для любителей, как я, - это платформа
Arduino IDE с низкоуровневыми вставками. Тем же, кому тесно в Arduino IDE, —
в С. Хотя коды на С можно оптимизировать иногда до размеров не намного боль-
ше, чем в Assembler'е, все-таки для понимания работы контроллера стоит на-
прячься и освоить азы Assembler'а. Ведь полезность знаний - это аксиома.
Arduino на
ассемблере
Попробуем на простом примере рассмотреть, как можно ЛЛхакнуть" Arduino Uno и
начать писать программы в машинных кодах, т.е. на ассемблере для микрокон-
троллера ATmega328p. На данном микроконтроллере собственно и собрана большая
часть недорогих «классических» плат «duino». Данный код также будет работать
на практически любой demo плате на ATmega328p и после небольших возможных до-
работок на любой плате Arduino на Atmel AVR микроконтроллере. В примере я по-
старался подойти так близко к железу, как это только возможно. Для лучшего
понимания того, как работает микроконтроллер не будем использовать какие-либо
готовые библиотеки, а уж тем более Arduino IDE.
Arduino очень клевая штука, но многое из того что происходит с микрокон-
троллером специально спрятано в дебрях библиотек и среды Arduino для того
чтобы не пугать новичков. Поигравшись с мигающим светодиодом я захотел по-
нять , как микроконтроллер собственно работает. Помимо утоления чисто познава-
тельного зуда, знание того как работает микроконтроллер и стандартные средст-
ва общения микроконтроллера с внешним миром — это называется «периферия», да-
ет преимущество при написании кода как для Arduino так и при написания кода
на С/Assembler для микроконтроллеров а также помогает создавать более эффек-
тивные программы.
Микроконтроллер ATmega328P является 8 разрядным микроконтроллером, предна-
значенным для встраиваемых приложений. Он изготавливается по малопотребляющей
КМОП технологии, которая в сочетании с усовершенствованной RISC архитектурой
позволяет достичь наилучшего соотношения быстродействие/энергопотребление.
Микроконтроллер построен по двухшинной (гарвардской) архитектуре и имеет раз-
дельные шины памяти программ и памяти данных.
/-
Шина команд 16 бит
^
II
ч,>
Процессор
с
3JL
Паыять команд
Памятьданчык
1
Устройства
ввода'&ывода
л
Р
Шина данных 8 бит
Подсистема ввода-вывода:
■ 3 порта ввода-вывода (23 линии): В (8 линий), С (7 линий) и D (8 линий);
■ программное конфигурирование и выбор портов ввода/вывода;
■ выводы могут быть запрограммированы как входные или как выходные независи-
мо друг от друга;
■ входные буферы с триггером Шмитта на всех выводах;
■ возможность подключения ко всем входам внутренних подтягивающих резисторов
(сопротивление резисторов составляет 35...120 кОм).
Периферийные устройства:
■ 8 разрядные таймеры/счетчики (таймеры ТО и Т2);
■ 16 разрядный таймер/счетчик (таймер Т1);
■ сторожевой таймер WDT;
■ 6 каналов ШИМ (широтно-импульсная модуляция);
■ аналоговый компаратор;
■ 6-ти канальный 10 разрядный АЦП;
■ полнодуплексный универсальный синхронный/асинхронный приемопередатчик;
■ последовательный синхронный интерфейс SPI;
■ последовательный двухпроводный интерфейс TWI (аналог интерфейса I2C).
Арифметико-логическое устройство (АЛУ), выполняющее все вычисления,
подключено непосредственно к 32 рабочим регистрам, объединенным в регистровый
файл.
АЛУ выполняет одну операцию (чтение регистров, выполнение операции и запись
результата в регистр) за один машинный цикл. Практически каждая из команд (за
исключением команд, у которых одним из операндов является 16 разрядный адрес)
занимает одну ячейку памяти программ.
+ il
Instruction
Register
регистр команд
SPI
Последовательный
периферийный
интерфейс
Program
Counter
счетчик команд
РОН
32 регистра
общего назначения
\< Н
I/O Ports
Порты
ввода/вывода
Interrupts
Прерывания
Timer/Counters
Таймеры/счетчики
АС
Аналоговый
компаратор
A/D Converter
Аналого-цифровой I
преобразователь '
WDT
Сторожевой
таймер
Интерфейс
JTAG
Конвейеризация заключается в том, что во время исполнения текущей команды
производится выборка из памяти и дешифрация кода следующей команды.
В соответствии с Гарвардской архитектурой разделены не только адресные про-
странства памяти программ и памяти данных, но также и шины доступа к ним.
Способы адресации и доступа к этим областям памяти также различны. Такая
структура позволяет центральному процессору работать одновременно как с памя-
тью программ, так и с памятью данных, что существенно увеличивает производи-
тельность .
Каждая из областей памяти данных (ОЗУ и EEPROM) также расположена в своем
адресном пространстве.
Память программ предназначена для хранения команд, управляющих функциониро-
ванием микроконтроллера. Память программ представляет собой электрически сти-
раемое ППЗУ (FLASH ПЗУ).
Память программ имеет 16 разрядную организацию, поэтому для ATmega328P ее
длина равна 16 К (16x1024) 16-ти разрядных слов.
Логически память программ разделена на две неравные части — область при-
кладной программы и область загрузчика (2 КБ). В последней может располагать-
ся специальная программа (загрузчик), позволяющая микроконтроллеру самостоя-
тельно управлять загрузкой и выгрузкой прикладных программ.
Для адресации памяти программ используется 16-ти разрядный счетчик команд
(Program Counter).
По адресу $0000 памяти программ находится вектор сброса. После инициализа-
ции (сброса) микроконтроллера выполнение программы начинается с этого адреса
(по этому адресу должна размещаться команда перехода к инициализационной час-
ти программы). Начиная с адреса $0002 располагается таблица векторов прерыва-
ний.
Application Flash Section
0x0000
Boot Flash Section
0x3FFF
Память данных микроконтроллеров семейства Меда разделена на три части:
■ регистровая память,
■ оперативная память (статическое ОЗУ)
■ энергонезависимое ЭСППЗУ (EEPROM).
IN/OUT
0x0000-0x001 F
32 registers
64 I/O registers
160 Ext I/O registers
Internal SRAM
(2048x8)
Load/Store
0x0000-0x001 F
0x0020 - 0x005F
0x0060 - OxOOFF
0x0100
0x08FF
Регистровая память включает:
■ 32 регистра общего назначения (РОН), объединенных в файл,
■ Служебные регистры ввода/вывода (РВВ) и дополниельные регистры ввода-
вывода (ДРВВ). Под РВВ в памяти микроконтроллера отводится 64 байта, а
под ДРВВ - 160 байт.
Оперативная память (статическое ОЗУ) объемом 2 Кбайт служит для хранения
переменных программ помимо регистров общего назначения.
Энергонезависимая постоянная память служит для долговременного хранения
различной информации, которая может изменяться в процессе функционирования
готовой системы (калибровочные константы, серийные номера, ключи и т. п.). Ее
объем 1 Кбайт. Эта память расположена в отдельном адресном пространстве, а
доступ к ней осуществляется с помощью специальных регистров ввода-вывода
(РВВ) .
Используется линейная организация памяти. Первые 256 ячеек отведены под ре-
гистры: 32 штуки регистры общего назначения РОН, остальное - регистры ввода-
вывода (РВВ и ДРВВ).
Все регистры общего назначения объединены в регистровый файл быстрого дос-
тупа.
Все 32 РОН непосредственно доступны AJIY Любой РОН может использоваться
практически во всех командах и как операнд источник и как операнд приемник.
Это позволяет АЛУ выполнять одну операцию (извлечение операндов из регистро-
вого файла, выполнение команды и запись результата обратно в регистровый
файл) за один машинный цикл.
Каждый регистр файла имеет свой собственный адрес в пространстве памяти
данных. Поэтому к ним можно обращаться двумя способами (как к регистрам и как
к памяти).
7 о Адрес
Рабочие
регистры
общего
назначения
R0
R1
R2
R13
R14
R15
R16
R17
R26
R27
R28
R29
R30
R31
0x00
0x01
0x02
OxOD
ОхОЕ
OxOF
0x10
0x11
0ж1А
0x1 В
0х1С
0x1 D
0х1Е
0x1F
Х-регистр Младший байт
Х-регистр Старший боит
Y-регистр Младший байт
Y-регистр Старший байт
Z-регистр Младший байт
Z-регистр Старший байт
Последние 6 регистров файла (R26...R31) могут также объединяться в три 16
разрядных регистра X, Y и Z, используемых в качестве указателей при косвенной
адресации памяти данных.
15
X - register |7
R27($1B)
15
Y - register | 7
R29($1D)
15
Z-r
agister Г
R31 ($1F)
XH
YH
ZH
0 7
R26($1A)
0 7
R28($1C)
R30($1E)
XL
YL
ZL
Все регистры ввода/вывода (РВВ) условно можно разделить на две группы:
■ служебные регистры микроконтроллера
■ регистры, относящиеся к конкретным периферийным устройствам.
Регистры ввода/вывода располагаются в так называемом основном пространстве
ввода/вывода, в котором их адреса начинаются с 0x00 до 0x3F. При этом по ука-
занным адресам к ним можно обратиться с помощью команд IN и OUT.
Однако эти регистры также представлены в общем адресном пространстве стати-
ческой памяти как ячейки ОЗУ, где их адреса начинаются с 0x20 до 0x5F (т.е.
сдвинуты на константу 0x20). Как к ячейкам ОЗУ к регистрам можно обратиться с
помощью команд ST/SD/SDD и LD/LDS/LDD.
К дополнительным регистрам ввода-вывода (ДРВВ) можно обращаться только как
к ячейкам памяти.
Регистровый Адресное
файл пространств о
то
1 R'
1 к
1 R3'
S30CO
S0001
SG0CC
SOO'F
Регистр* мода вывода
S0O
I so'
I S3f
50020 Г
S0021 1
S305.* 1
Дог. регистры ввода вывода
S60
S6'
SFF
S0O60
sooei
S00FF
Регистр состояния SREG располагается по адресу $3F ($5F) и содержит набор
флагов, показывающих текущее состояние микроконтроллера. Большинство флагов
автоматически устанавливаются в «1» или сбрасываются в «О» при наступлении
определенных событий:
Bit
0x3F (0x5F)
Read/Write
Initial Value
с
7
1
R/W
0
6
T
R/W
0
5
H
R/W
0
4
S
R/W
0
3
V
R/W
0
2
N
R/W
0
1
Z
R/W
0
0
С
R/W
0
3
SREG
Раз-
ряд
7
6
5
4
3
2
1
0
Наз-
вание
I
Т
Н
S
V
N
Z
С
Описание
Общее разрешение прерываний. Для разрешения прерываний этот флаг
должен быть установлен в «1». Разрешение/запрещение отдельных
прерываний производится установкой или сбросом соответствующих
разрядов регистров масок прерываний (регистра управления прерыва-
ниями) . Если флаг сброшен, то прерывания запрещены независимо от
состояния разрядов этих регистров. Флаг сбрасывается аппаратно
после входа в прерывание и восстанавливается командой RETI для
разрешения обработки следующих прерываний
Хранение копируемого бита. Этот разряд регистра используется в
качестве источника или приемника команд копирования битов BLD
(Bit LoaD) и BST (Bit STore) . Заданный разряд любого РОН может
быть скопирован в этот разряд командой BST или установлен в соот-
ветствии с содержимым данного разряда командой BLD
Флаг половинного переноса. Этот флаг устанавливается в «1», если
произошел перенос из младшей половины байта (из 3-го разряда в 4-
й) или заем из старшей половины байта при выполнении некоторых
арифметических операций
Флаг знака. Этот флаг равен результату операции «Исключающее ИЛИ»
(XOR) между флагами N (отрицательный результат) и V (переполнение
числа в дополнительном коде). Соответственно этот флаг устанавли-
вается в «1», если результат выполнения арифметической операции
меньше нуля
Флаг переполнения дополнительного кода. Этот флаг устанавливается
в «1» при переполнении разрядной сетки знакового результата. Ис-
пользуется при работе со знаковыми числами (представленными в до-
полнительном коде). Более подробно — см. описание системы команд
Флаг отрицательного значения. Этот флаг устанавливается в «1»,
если старший (7-й) разряд результата операции равен «1». В про-
тивном случае флаг равен «0»
Флаг нуля. Этот флаг устанавливается в «1», если результат выпол-
нения операции равен нулю
Флаг переноса. Этот флаг устанавливается в «1», если в результате
выполнения операции произошел выход за границы байта
Каждый порт ввода-вывода микроконтроллеров состоит из определенного числа
выводов, через которые микроконтроллер может осуществлять прием и передачу
цифровых сигналов. Задание направления передачи данных через любой контакт
ввода/вывода может быть произведено программно в любой момент времени.
Микроконтроллер ATmega8x имеют три порта ввода/вывода:
■ порт В (8 разрядный),
■ порт С (7 разрядный),
■ порт D (8 разрядный).
Обращение к портам производится через регистры ввода/вывода. Под каждый
порт в адресном пространстве ввода/вывода зарезервировано по 3 адреса, по ко-
торым размещены следующие регистры:
■ регистр данных порта PORTx,
■ регистр направления данных DDRx,
■ регистр выводов порта PINx.
Регистр PINx доступен только для чтения, a PORTx и DDRx доступны как
для чтения, так и для записи.
В таблице приведены адреса регистров микроконтроллера ATmega328P.
Порт
В
С
D
Регистр
PORTB
DDRB
PINB
PORTC
DDRC
PINC
PORTD
DDRD
PIND
Адреса
$18 ($38)
$17 ($37)
$16 ($36)
$15 ($38)
$14 ($37)
$13 ($36)
$12 ($32)
$11 ($31)
$10 ($30)
&С1
[ RESET ]—[ РС6 ]
поч
RXD
PD0
GZH
TXD
{ PD1 )—т\
СЕН
СЕН
INTO
]—[ PD2 }
XTAL1
БС2 }—[ XTAL2
H Tl
СЕН
AINO
СЕН
AIN1
EM
CLKO
INT1 }-
-{ PD3 \
XCK j-
-[ PD4 }
1 1 PB6 }
] ( PB7 }
) ( PD5 }
PD6
PD7
)—[ pbo )—•!
•—[
•—[
—[
•—[
•-{
•—(
PC5 '
PC4}
PC3 j
PC2 j
PCI j
PCO^
PB5 И SCK
H
PB4 И MISO H
рвз и
OC2A
PB2 H
OC1B
•—{ PB1
OC1A
HED
Конфигурирование портов ввода-вывода:
Разряд DDxn регистра DDx определяет направление передачи данных через кон-
такт ввода/вывода. Если этот разряд установлен в «1», то n-й вывод порта
является выходом, если же сброшен в «0» — входом.
Разряд PORTxn регистра PORTx выполняет двойную функцию.
1. Если вывод функционирует как выход (DDxn = «1») , этот разряд определяет
со стояние вывода порта. Если разряд установлен в «1», на выводе уста-
навливается напряжение ВЫСОКОГО уровня. Если разряд сброшен в «О», на
выводе устанавливается напряжение НИЗКОГО уровня.
2. Если вывод функционирует как вход (DDxn = «0») , разряд PORTxn определяет
состояние внутреннего подтягивающего резистора для данного вывода. При
установке разряда PORTxn в «1» подтягивающий резистор подключается между
выводом микроконтроллера и проводом питания.
DDRxn
0
0
1
1
PORTxn
0
1
0
1
Состояние линии
I (Input) Вход
I (Input) Вход
0 (Output) Выход
0 (Output) Выход
Описание
Высокоимпендансный вход. (Не рекомендую ис-
пользовать , так как могут наводится наводки от
питания)
Подтянуто внутренне сопротивление
На выходе низкий уровень
На выходе высокий уровень
В качестве учебно-тренировочной задачи попробуем сделать самое простое, что
только возможно — правильно и полезно подергать одной ногой микроконтроллера,
ну то есть будем читать данные из датчика температуры и влажности DHT-11.
Итак, будем делать все наиболее близко к железу, у нас есть: плата совмес-
тимая с Arduino Uno, датчик DHT-11, три провода, Atmel Studio и машинные ко-
ды.
Для начала подготовим нужное оборудование.
Писать код будем в Atmel Studio 7 — бесплатно скачивается с сайта произво-
дителя микроконтроллера — Atmel1,2. Web-установщик есть в архиве:
ftp://homelab.homelinuxserver.org/pub/arhiv/2021-09-a3.rar
Весь код запускался на клоне Arduino Uno — у меня это DFRduino Uno от
DFRobot, на контроллере ATmega328p работающем на частоте 16 MHz — отличная
надежная плата. Каких-либо отличий от стандартного Uno в процессе эксплуата-
ции я не заметил. Похожая чорная плата от DFBobot, только ЛЛМеда" отлетала у
меня 2 года в качестве управляющего контроллера квадрокоптера — куда ее толь-
ко не заносило — проблем не было.
Для просмотра сигналов длительностью в микросекунды (а это на минутку 1
миллионная доля секунды) , я использовал штуку, которая называется ЛЛлогический
анализатор". Конкретно, я использовал клон восьмиканального USBEE AX Pro. Как
смотреть для отладки такие быстрые процессы без осциллографа или логического
анализатора — на самом деле даже не знаю, ничего посоветовать не могу.
Прежде всего я подключил свой клон Uno — как я говорил у меня это DFRduino
Uno к Atmel Studio 7 и решил попробовать помигать светодиодиком на ассембле-
ре. Код пишется прямо в студии, прошивать плату можно через USB порт исполь-
зуя привычные возможности загрузчика Arduino - через AVRDude. Можно шить и
через внешний программатор, я пробовал на китайском USBASP, по факту у меня
оба способа работали. В обоих случаях надо только правильно настроить проши-
вальщик AVRDude, пример моих настроек на картинке:
External Tools ? ШШ
Menu contents:
Delete
T,tle: DFRdumccnUSB
Command: C: AVRDUDE'a.rdude.exe
Arguments: -С С avrdude a.rdude.ccnf -p atmega^Sp -< ►
Initial directory: >
^ Use Output window Prompt for arguments
Treat output as Unicode ^
OK Cancel
1 http://studio.download.atmel.com/7.0.2397/as-installer-7.0.2397-web.exe
2 http://atmel-studio.s3-website-us-west-2.amazonaws.com/7.0.2397/as-installer-
7.0.2397-full.exe
Полная строка аргументов:
-С ЛЛС: \avrdude\avrdude. conf" -р atmega328p -с arduino -Р С0М7 115200 -U
flash:w:"$(ProjectDir)Debug\$(TargetName).hex:i
В итоге, для простоты я остановился на прошивке через USB порт — это стан-
дартный способ для Arduio. На моей UNO стоит чип ATmega 328Р, его и надо ука-
зать при создании проекта. Нужно также выбрать порт к которому подключаем
Arduino — на моем компьютере это был СОМ7.
Для того, чтобы просто помигать светодиодом никаких дополнительных подклю-
чений не нужно, будем использовать светодиод, размещенный на плате и подклю-
ченный к порту Arduino D13 — напомню, что это 5-ая ножка порта «PORTB» кон-
троллера .
Подключаем плату через USB кабель к компьютеру, пишем код в студии, проши-
ваем прямо из студии. Основная проблема здесь собственно увидеть это мигание,
поскольку контроллер фигачит на частоте 16 MHz и, если включать и выключать
светодиод такой же частотой мы увидим тускло горящий светодиод и собственно
все.
Для того чтобы увидеть, когда он светится и когда он потушен, мы зажжем
светодиод и займем процессор какой-либо бесполезной работой на примерно 1 се-
кунду. Саму задержку можно рассчитать вручную зная частоту — одна команда вы-
полняется за 1 такт. После установки задержки, код выполняющий примерно то же
что делает классический «Blink» Arduino может выглядеть примерно так:
1
з
г,
с
7
я
ч
1С
11
12
13
1-
15
1с
17
18
19
2£
21
22
loop:
L1:
cli
sbi
sbi
Idi
Idi
Idi
dec
Drne
dec
ere
dec
Ьте
nop
DDRB,
PORTB
ПБ,
П9,
Г23,
г 23
LI
rl9
LI
ПБ
LI
5
. 5
S2
43
0
in R16, PORTB
Idi
EOR
cut
rjrnp
R17,
R16,
PORTB
loop
; PORT В., Pin 5 - на бы кед
; выставили на Pin 5 гог единицу
; delay 1Э8Э ms
; перек.гкчи.ги XOR 5-ый бит в порту
3593180800
R17
, R16
Но на самом деле так писать не очень хорошо, поскольку мы полностью похери-
ли такие важные штуки, как стек и вектор прерываний (о них — позже).
Ок, светодиодиком помигали, теперь для того чтобы практика работа с GPIO
была более или менее осмысленной прочитаем значения с датчика DHT11 и сделаем
это также целиком на ассемблере.
Для того чтобы прочитать данные из датчика нужно в правильной последова-
тельность выставлять на рабочей линии датчика сигналы высокого и низкого
уровня — собственно это и называется дергать ногой микроконтроллера. С одной
стороны, ничего сложного, с другой стороны все какая-то осмысленная деятель-
ность — меряем температуру и влажность — можно сказать сделали первый шаг к
построению какой ни будь «Погодной станции» в будущем.
Забегая на один шаг вперед, хорошо бы понять, а что собственно с прочитан-
ными данными будем делать? Ну, хорошо прочитали мы значение датчика и устано-
вили значение переменной в памяти контроллера в 23 градуса по Цельсию, соот-
ветственно . Как посмотреть на эти цифры? Решение есть! Полученные данные я
буду смотреть на большом компьютере, выводя их через USART контроллера через
виртуальный СОМ порт по USB кабелю прямо в терминальную программу типа PuTTY.
Для того чтобы компьютер смог прочитать наши данные будем использовать преоб-
разователь USB-TTL — такая штука, которая и организует виртуальный СОМ порт в
Windows.
Сама схема подключения может выглядеть примерно так:
Сигнальный вывод датчика подключен к ноге 2 (PIN2) порта PORTD контролера
или (что то же самое) к выводу D2 Arduino. Он же через резистор 4.7 kOm
"подтянут" на ЛЛплюс" питания. Плюс и минус датчика подключены — к соответст-
вующим проводам питания. USB-TTL переходник подключен к выходу Тх USART порта
Arduino, что значит PIN1 порта PORTD контроллера.
Разбираемся с датчиком и смотрим datasheet. Сам по себе датчик несложный, и
использует всего один сигнальный провод, который надо подтянуть через рези-
стор к +5V — это будет базовый «высокий» уровень на линии. Если линия свобод-
на — т.е. ни контроллер, ни датчик ничего не передают, на линии как раз и бу-
дет базовый «высокий» уровень. Когда датчик или контроллер что-то передают,
то они занимают линию — устанавливают на линии «низкий» уровень на какое-то
время. Всего датчик передает 5 байт. Байты датчик передает по очереди, снача-
ла показатели влажности, потом температуры, завершает все контрольной суммой,
это выглядит как ЛЛННТТХХ", в общем, смотрим datasheet. Пять байт — это 40 бит
и каждый бит при передаче кодируется специальным образом.
Для упрощения, будет считать, что «высокий» уровень на линии — это «едини-
ца», а «низкий» соответственно «ноль». Согласно datasheet для начала работы с
датчиком надо положить контроллером сигнальную линию на землю, т.е. получить
«ноль» на линии и сделать это на период не менее чем 20 мсек (миллисекунд), а
потом резко отпустить линию. В ответ — датчик должен выдать на сигнальную ли-
нию свою посылку, из сигналов высокого и низкого уровня разной длительности,
которые кодируют нужные нам 40 бит. И, согласно datasheet, если мы удачно
прочитаем эту посылку контроллером, то мы сразу поймем что:
а) датчик собственно ответил,
б) передал данные по влажности и температуре,
с) передал контрольную сумму.
В конце передачи датчик отпускает линию. Ну и в datasheet написано, что
датчик можно опрашивать не чаще чем раз в секунду.
Итак, что должен сделать микроконтроллер, согласно datasheet, чтобы датчик
ему ответил — нужно прижать линию на 20 миллисекунд, отпустить и быстро смот-
реть , что на линии:
VDD у
GND
4 >lto" *
/
/ Release ihe bus
l / after the host down
г
Host signal Slave signal
Host sends a slart signal
Датчик должен ответить — положить линию в ноль на 80 микросекунд (мксек),
потом отпустить на те же 80 мксек — это можно считать подтверждением того,
что датчик на линии живой и откликается:
^
80ш
ч р
V /
1'
ш **** ъ
Щ W
/ V
\ Start sending
Host signal Slave signal
После этого, сразу же, по падению с высокого уровня на нижний датчик начи-
нает передавать 40 отдельных бит. Каждый бит кодируются специальной посылкой,
которая состоит из двух интервалов. Сначала датчик занимает линию (кладет ее
в ноль) на определенное время — своего рода первый «полубит». Потом датчик
отпускает линию (линия подтягивается к единице) тоже на определенное время —
это типа второй «полубит». Длительность этих интервалов — «полубитов» в мик-
росекундах кодирует что собственно пытается передать датчик: бит "ноль" или
бит ллединица".
Рассмотрим описание битовой посылки: первый «полубит» всегда низкого уровня
и фиксированной длительности — около 50 мксек. Длительность второго «полуби-
та» определят, что датчик собственно передает.
Для передачи нуля используется сигнал высокого уровня длительностью 26-28
мксек:
VDD
GND
50vs
26 ■«-28»»
\
К
Host signal
Slave signal
Bit data "0м bit format
Для передачи единицы, длительность сигнала высокого увеличивается до 70
микросекунд:
VDD
CND
V
\
\
Ж
9DU
*-
^ W
к
\
i
i
\
rff
Wat
w.
^ ^
i
h
V
'Host signal
Slave signal
Bit data МГ bit format
Мы не будет точно высчитывать длительность каждого интервала, нам вполне
достаточно понимания, что если длительность второго «полубита» меньше чем
первого — то закодирован ноль, если длительность второго «полубита» больше —
то закодирована единица. Всего у нас 40 бит, каждый бит кодируется двумя им-
пульсами, всего нам надо значит прочитать 80 интервалов. После того как про-
читали 80 интервалов будем сравнить их попарно, первый ЛЛполубит" со вторым.
Вроде все просто, что же требуется от микроконтроллера, для того чтобы про-
читать данные с датчика? Получается, нужно значит дернуть ногой в ноль, а по-
том просто считать всю длинную посылку с датчика на той же ноге. По ходу, бу-
дем разбирать посылку на «полу-биты», определяя, где передается бит ноль, где
единица. Потом соберем получившиеся биты, в байты, которые и будут ожидаемыми
данными о влажности и температуре.
Ок, мы начали писать код и для начала попробуем проверить, а работает ли
вообще датчик, для этого мы просто положим линию на 20 мсек и посмотрим на
линии, что из этого получится логическим анализатором.
Как я уже писал сам датчик подключен на 2 ногу порта D. В Arduino Uno это
цифровой выход D2 (смотрим для проверки Arduino Pinout).
Все делаем тупо: инициализировали порт на выход, выставили ноль, подождали
20 миллисекунд, освободили линию, переключили ногу в режим чтения и ждем по-
явление сигналов на ноге.
1
2
3
г
5
6
S
9
1С
11
12
13
К
15
16
17
IS
DEFINES
; определения для порта., к.
которому г
.EQU
.EQU
.EQU
.EQU
.EQU
.DEF
.DEF
.DEF
.DEF
.DEF
.DEF
.DEF
.DEF
.DEF
.DEF
подключем DHT11
DHT_Port=PORTD
DHT_InPort=PIND
DHT_Pin=P0RTD2
DHT_Direction=DDRD
DHT_Direction_Pin=DDD2
Tmpl=R16
USART_ByteR=R17
Tmp2=R18
USART_BytesN=R19
Tmp3=R20
Cycle_Count=R21
ERR_CODE=R22
N_Cycles=R23
ACCUM=R24
Tmp4=R25
; переменная для отправки байта ■
; переменная - сколько байт отпр.
; счетчик циклов в Expect_X
; возврат ошибок из подп
; счетчик в READJIYCLES
S
9
1С
11
12
13
15
16
17
18
19
22
21
22
23
2^
25
26
27
23
; после инициализации сразу !1!! надо считать ответ контроллера и собственно данные
DHT_INIT: С LI ; еде раз, на всякий случай - критичная ко времени секция
; сохранили X для использования в READ_CYCLES - там нет времени
LDI ХН, High(CYCLES) ; загрузили старший байт адреса Cycles
LDI XL, Low (CYCLES) ; загрузили младший байт адреса Cycles
LDI Tmpl, (l«DHT_Direction_Pin>
OUT DHT_Direction, Tmpl
LDI Tmpl, (8<<DHT_Pin)
OUT DHT_Port, Tmpl
RCALL DELAY_29MS
LDI Tmpl, <K<DHT_Pin)
OUT DHT_Port, Tmpl
RCALL DELAY 19US
; порт D, Пин 2 на выход
; выставили 0
; ждем 26 миллисекунд
; освободили линию - выставили 1
; ждем 18 микросекунд
LDI Tmpl, (9<<DHT_Direction_Pin) ; порт D, Pin 2
OUT DHT_Direction, Tmpl
LDI Tmpl,(K<DHT_Pin) ; подтянули pull-up вход на вмес
OUT DHT_Port, Tmpl
; ждем ответа от сенсора - он должен положить линик: в ноль на 80 us и отпустить на 80 us
Смотрим анализатором — а ответил ли датчик?
Да, ответ есть — вот те сигналы после нашего первого импульса в 20 милсек —
это и есть ответ датчика. Для просмотра посылки я использовал китайский клон
USBEE AX Pro который подключен к сигнальному проводу датчика.
4mi/dfo
л *Л9г-;
::s-ts
*;s"r'.
::s't'.
•*:ь*~:
*s.:s--5
::$'—,
Oigit.il 0
1IIIIB
Растянем масштаб так чтобы увидеть окончание нашего импульса в 20 мсек и
лучше увидеть начало посылки от датчика — смотрим все как в datasheet — сна-
чала датчик выставил низкий/высокий уровень по 80 мксек, потом начал переда-
вать биты — а данном случае во втором «полубите» передается «0».
300us/div
'9.53Г:5~1$
9.s3**:$~s
:;i3r:5-s
Digital 0
ffl
Значит, датчик работает и данные нам прислал, теперь надо эти данные пра-
вильно прочитать. Поскольку задача у нас учебная, то и решать ее будем тупо в
лоб. В момент ответа датчика, т.е. в момент перехода с высокого уровня в низ-
кий, мы запустим цикл с счетчиком числа повторов нашего цикла. Внутри цикла,
будем постоянно следить за уровнем сигнала на ноге. Итого, в цикле будем
ждать, когда сигнал на ноге перейдет обратно на высокий уровень — тем самым
определив длительность сигнала первого «полубита». Наш микроконтроллер рабо-
тает на частоте 16 MHz и за период, например, в 50 микросекунд контроллер ус-
пеет выполнить около 800 инструкций. Когда на линии появится высокий уровень
— то мы из цикла аккуратно выходим, а число повторов цикла, которые мы отсчи-
тали с использованием счетчика — запоминаем в переменную.
После перехода сигнальной линии уже на высокий уровень мы делаем такую же
операцию - считаем циклы, до момента когда датчик начнет передавать следующий
бит и положит линию в низкий уровень. К счастью, нам не надо знать точный
временной интервал наших импульсов, нам достаточно понимать, что один интер-
вал больше другого. Понятно, что если датчик передает бит «ноль» то длитель-
ность второго «полубита» и соответственно число циклов, которые мы отсчитали
будет меньше чем длительность первого «полубита». Если же датчик передал бит
«единица», то число циклов которые мы насчитаем во время второго полубита бу-
дет больше чем в первым.
И для того, что бы мы не висели вечно, если вдруг датчик не ответил или за-
сбоил, сам цикл мы будем запускать на какой-то временной период, но который
гарантированно больше самой длинной посылки, чтоб если датчик не ответил, то
мы смогли выйти по тайм-ауту.
В данном случае показан пример для ситуации, когда у нас на линии был ноль,
и мы считаем сколько раз мы в цикле мы считали состояние ноги контроллера,
пока датчик не переключил линию в единицу.
Аналогичная подпрограмма используется для того, чтобы посчитать сколько
циклов у нас должно прокрутиться, пока датчик из состояния ноль на линии пе-
реложил линию в состояние единицы.
1
-I
3
L
5
6
7
о
9
.с
.1
->
.0
.5
.6
. /
.3
.9
1С
[1
■2
:£
: с
EXPECT 1
; крутимся в цикле
; когда появилось
; сообщаем сколько
; или сообщение
ЕХРЕСТ_1:
EXP1L1:
ЕХ1Т_ЕХРЕСТ_1:
об
ждем нужного состояния на пине
- выходим
циклов ждали
ошибке тайм оута если не дождались
LDI Cycle_Count, 0 ; загрузили счетчик, циклов
LDI ERR_CODE, 2 ; Ошибка 2 - выход по тайм Out
ldi Trnpl., 2 ; Загрузили
ldi Tmp2^ 169 ; задержку 80 us
INC Cycle Count ; увеличили счетчик циклов
IN ТтрЗ,, DHT_InPort ; читаем порт
SBRC ТтрЗ., DHT_Pin ; Если 1
R3MP EXIT_EXPECT_1 ; To выходим
dec Tmp2 ; если нет то крутимся в задержке
brne EXP1L1
dec Trnpl
brne EXP1L1
NOP ; Здесь выход по тайм out
RET
LDI ERR_C0DEj 1 ; ошибка 1, все нормально, в Cycle_Count
RET
Для расчета временных задержек мы будет использовать тот же подход, который
мы использовали при мигании светодиодом — подберем параметры пустого цикла
для формирования нужной паузы. Я использовал специальный калькулятор. При же-
лании можно посчитать число рабочих инструкций и вручную.
Памяти в нашем контроллере довольно много — аж 2 килобайта, так что мы не
будем жлобствовать с памятью, и тупо сохраним данные счетчиков относительно
наших 80 ( 40 бит, 2 интервала на бит) интервалов в память.
Объявим переменную
CYCLES: .byte 80 ; буфер для хранения числа циклов
И сохраним все считанные циклы в память.
1
2
3
4
5
6
3
9
10
11
12
13
*
; читаем
READ_
READ:
биты
.CYCLES:
:_ KtAD CYLLtb =
контроллера и сохраняем в
LDI N_Cycles, 80
NOP
RCALL EXPECT_1
ST Х+, Cycles_Counter
RCALL EXPECT_0
ST Х-ь., Cycles_Counter
DEC N_Cycles
BRNE READ
RET
Cycles
»
У
У
у
у
У
читаем 80 циклов
Открутился 0
Сохранили число циклов
Сохранили число циклов
уменьшили счетчик
все циклы считали
Теперь, для отладки, попробуем посмотреть насколько удачно посчиталось дли-
тельность интервалов, и понять действительно ли мы считали данные из датчика.
Понятно, что число отсчитанных циклов первого «полубита» должно быть примерно
одинаково у всех битовых посылок, а вот число циклов при отсчете второго «по-
лубита» будет или существенно меньше, или наоборот существенно больше.
Для того чтобы передавать данные в большой компьютер будем использовать
USART контроллера, который через USB кабель будет передавать данные в про-
грамму — терминал, например PuTTY. Передаем опять же тупо в лоб — засовываем
байт в нужный регистр управления USART-a и ждем, когда он передастся. Для
удобства я также использовал пару подпрограмм, типа — передать несколько
байт, начиная с адреса в Y, ну и перевести каретку в терминале для красоты.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SEND.
SEND.
SEND.
»
SEND.
SBSJ
.BYTE:
_BYTE_
.CRLF:
- что
.BYTES
.1:
LI:
слать
:
QEMH 1 DVTC Х/ТЛ IICADT
itNL» 1 DT 1 С VIA UiAK 1
NOP
LDS Tmpl, UCSR0A
SBRS Tmpl, UDRE0
RJMP SEND_BYTE_L1
STS UDR0, USART_ByteR
NOP
RET
QPMri /"Dl P WTA IIQADT
->tNU LKLr VIA UbAK 1
LDI USART_ByteR, $0D
RCALL SEND_BYTE
LDI USART_ByteR, $0A
RCALL SEND_BYTE
RET
ССМГ» M RVTPC \/TA IICADT
^tl\JU N DT1lj VIM UbAK1
, USART_BytesN - сколько байт
NOP
LD (JSART_ByteR, Y+
RCALL SEND_BYTE
DEC USART_BytesN
BRNE SBS_L1
RET
; если регистр данных пустой
; то шлем байт из R17
Отправив в терминал число отсчётов для 80 интервалов, можно попробовать со-
брать собственно значащие биты. Делать будем, как написано в учебнике, т.е. в
datasheet — попарно сравним число циклов первого «полубита» с числом циклов
второго. Если вторые пол-бита короче — значит это закодировать ноль, если
длиннее — то единица. После сравнения биты накапливаем в аккумуляторе и со-
храняем в память по-байтово начиная с адреса BITS.
1
2
3
£
5
с
3
9
1С
11
12
13
1£
15
16
17
18
19
2С
21
22
23
2^
»
; Из Cycles
GET_BITS:
АСС:
ТО_АСС:
]_SHIFT:
лет
43 С 1
делаем
РТТС
Dl 1 2>
байты в
BITS
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LDI
LSL
LD "
LD '
CP "
Tmpl., 5
Tmp2., 3
ZH, High(CYCLES)
ZL, Loiv (CYCLES)
YH, High(BITS)
YL, Low (BITS)
ACCUM, 9
Tmp2, 8
ACCUM
ГглрЗ, Z+
Гглр4, Z+
ГглрЗ, Tmp4
BRPL ]_SHIFT
ORI
DEC
ACCUM, 1
Tmp2
BRNE TO_ACC
ST Y+, ACCUM
DEC
Tmpl
BRNE ACC
RET
; загру
; загру
; загру
; загру
; если
; для пяти байт - готовим счетчи
; для каждого бита
зили старший байт адреса Cycles
зили младший байт адреса Cycles
зили старший байт адреса BITS
зили младший байт адреса BITS
; акамулятор инициализировали
; для каждого бита
; сдвинули влево
; считали данные [i]
; о циклах и [i+1]
; сравнить первые пол бита с вто
положительно (9) то просто сдвиг
; если отрицательно (1) то добав
; повторить для 8 бит
; сохранили акамулятор
; для пяти байт
Итак, здесь мы собрали в памяти начиная с метки BITS те пять байт, которые
передал контроллер. Но работать с ними в таком формате не очень неудобно, по-
скольку в памяти это выглядит примерно, как:
34002100ХХ, где 34 — это влажность целая часть, 00 — данные после запятой
влажности, 21 — температура, 00 — опять данные после запятой температуры, XX
— контрольная сумма. А нам надо бы вывести в терминал красиво типа
«Temperature = 21.00». Так что для удобства, растащим данные по отдельным пе-
ременным .
1
2
3
4
Н1в:
HBI-
TIO
Т91
.byte l
.byte 1
.byte 1
.byte 1
; число •
; число ■
; число
; число
- целая часть влажность
- дробная часть влажность
- целая часть температура в С
- дробная часть температура
И сохраняем байты из BITS в нужные переменные
1
2
3
4
5
6
7
3
9
10
11
12
13
14
15
16
17
13
19
20
21
22
23
24
9 '
9
*
из
i i
GET_
BITS
i чуть
чл: i nm l'mim —
вытаскиваем цифры Н16
хаки
HnT_DATA:
ули,
NOP
потому что
Н10 и дальше... лежат
LDI ZH, HIGH(BITS)
LDI ZL, LOU'(BITS)
LDI XH, HIGH(H10)
LDI XL, LOW(H10)
LD Tmpl, Z+-
ST X+, Tmpl
LD Tmpl, Z-k
ST X+, Tmpl
LD Tmpl, Z*
ST X-»-, Tmpl
LD Tmpl, Z+-
ST X+, Tmpl
RET
последовательно в памяти
; Считали
; сохранили
; Считали
; сохранили
; Считали
; сохранили
; Считали
; сохранили
После этого преобразуем цифры в коды ASCII, чтобы данные можно было нор-
мально прочитать в терминале, добавляем названия данных, ну там «температура»
из флеша и шлем в СОМ порт в терминал.
Для того, чтобы это измерять температуру регулярно, добавляем вечный цикл с
задержкой порядка 1200 миллисекунд, поскольку datasheet DHT11 говорит, что не
рекомендуется опрашивать датчик чаще чем 1 раз в секунду.
Основной цикл после этого выглядит примерно так:
1 ;============ MAIN
2 ;1!1 Главный вход
3 RESET: NOP
4
5 ; Internal Hardware Init
6 СLI ; нам прерывания не нужны пока
7
8 ; stack init
9 LDI Tmpl, Low(RAMEND)
10 OUT SPL, Tmpl
11 LOI Tmpl, High(RAMEND)
12 OUT SPH, Tmpl
13
14 RCALL USART0_INIT
15
16 ; Init data
17 RCALL COPY_STRINGS ; скопировали данные в RATI
18 RCALL TEST_DATA ; подготовили тестовые данные
19
20 loop: NOP ; крутимся в веч!
21 ; External Hardware Init
22 RCALL DHT_INIT
23 ; получили здесь подтверждение контроллера и надо в темпе читать
24 RCALL READ_CYCLES
25 ; критичная ко времени секция завершилась...
26
27 ;Тест - отправить Cycles в USART
28 ;RCALL TEST_CYCLES
29
30 ; получаем из посыпки биты
31 RCALL <5ET_BITS
32
33 ;Тест - отправить BITS в USART
34 ;RCALL TEST_BITS
35
36 ; получаем из BITS цифровые данные
37 RCALL GET_HnT_DATA
38
39 ;Тест - отправить 4 байта начиная с Н10 в USART
40 ;RCALL TEST_H10_T01
41
42 ; подготовидли температуру и влажность в ASCII
43 RCALL HnT_ASCII_DATA_EX
44
45 ; Отправить готовую температуру (надпись и ASCII данные) в USART
46 RCALL PRINT_TEMPER
47 ; Отправить готовую влажность (надпись и ASCII данные) в USART
48 RCALL PRINT_HUMID
49 ; переведем строку дял красоты
50 RCALL SEND_CRLF
51
52 RCALL DELAY_1200MS ;повторяем кажды>
53 rjmp loop ; зациклились
Прошиваем, подключаем USB-TTL кабель (преобразователь)к компьютеру, запус-
каем терминал, выбираем правильный виртуальный СОМ порта и наслаждаемся нашим
новым цифровым термометром. Для проверки можно погреть датчик в руке — у меня
температура при этом растет, а влажность, как ни странно, уменьшается.
(ПРОДОЛЖЕНИЕ СЛЕДУЕТ)