afz писал(а):Вообще, создается впечатление, что авторы АВР Студии ничего, кроме писюка не видели, все богатство идей, реализованных в ассемблерах до-писюшных времен осталось за бортом...
ММдяааа... а у меня впечатление, что Вам не слишком хорошо сам предмет обсуждения (ассемблер АВР) известен весьма... посредственно. Все необходимое для работы там имеется. Некоторые "заковыки" вызваны структурными особенностями ядра семейства, но не более того.
ARV писал(а):вы же слыхали про магические числа и их вред в программировании? так вот, фиксированные адреса - это магические числа.
При чем тут "магические числа" ? Смешались в кучу кони, люди... Еще немного, и МК начнет по прерыванию выскакивать не на прописанный ему "железом" адрес, а на заботливо предложенное ему символьное имя. Но это уже будет следующее поколение МК со встроенным телепатором. Кажется, я начинаю спорить . А ведь я никогда не спорю И напоследок. Может, кто-то изучал МК по сказкам и слухам, а я так все больше по даташитам. И десяток успешных немаленьких проектов подтвердят, что читал ДШ я внимательно. Break. Game over. Kondec .
вы жестко привязываете команду jmp vector0 к адресу, равному 0+sizeof(jmp) (если, конечно, так можно выразиться). размер команды jmp вам, условно говоря, неизвестен, так как зависит от типа МК. то есть вы получаете код, который "гуляет" по адресному пространству, а вы об этом не подозреваете даже. в один прекрасный момент вы пишите то же самое для другого МК и вдруг внезапно оказывается, то вся стройная таблица разрушена, потому что размер jmp стал не таким, как был ранее, то есть у вас обработчики прерываний будут хрен знает куда попадать, а компилятор вам даже предупреждения не выдаст.
в то же самое время в каждом описании МК, данном вам в соответствующем inc-файле, определены константы символьные, обозначающие адрес вектора прерывания (по идее, для однотипных прерываний разных МК эти символьные константы должны быть одинаковыми, хоть это и не всегда так) то есть разработчик МК и ассемблера как бы вам намекает - используй это!
в этом случае вы получаете таблицу, не зависящую от размера команды jmp, и можете ее таскать из проекта в проект. как минимум, если в таблице будет непорядок, компилятор вам об этом сможет сказать.
ну и, что лично мне всегда больше нравилось, вы получаете возможность работать только с нужными вам векторами, а не со всеми подряд, т.е. можете написать
и все будет правильно. более того, вы можете вдруг "вспомнить" о нужном вам векторе, и написать в любом месте перед меткой start переход на этот вектор - и снова все будет отлично!!!
вы получаете вариант, за которым следит компилятор, давая вам больше свободы. пользоваться этим или нет - дело ваше, но в наставлениях по masm и tasm было строго-настрого указано, что программист обязан определять вектора прерываний исключительно при помощи директивы ORG - не надеясь на фиксированные адреса в адресном пространстве! я к этому привык еще в ту пору, и пока что ничего лучше не видел (для ассемблера).
наконец, при предлагаемом подходе вы имеете полное право делать заглушки "лишних" прерываний, например так:
надеюсь, вы понимаете и согласитесь, что в варианте "табличных джампов" замена jmp на reti может быть настолько черевата, что рискнувшего это сделать можно только пожалеть?
что касается самого ассемблера AVR, то из-под пера Атмела он вышел на редкость кривеньким и убогоньким, сравнивать его даже с avr-as из комплекта avr-gcc просто нельзя! макросы даже в древнейшем ассемблере для 51-ых микроконтроллеров были во много раз более эффективными!
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...
возьмет 1, сдвинет её влево на столько раз, чему равен адрес метки speed_tabl, а потом возьмет старший байт получившегося числа и занесет его в рег. ZH.
сделает все то же самое, но в рег.ZL будет помещен младший байт получившегося числа. Как Вы думаете, какое значение в итоге занесется в рег.пару Z, если учесть, что адрес наверняка будет больше 10h?
ldi ZH, high(speed_tabl*2); ldi ZL, low(speed_tabl*2); загрузили в Z адрес таблицы add ZL, SV; прибавили смещение [0...255] adc ZH, zeroreg; прибавили к старшему байту указателя бит переноса С если есть lpm r16, Z+; загрузили первый байт с пост-инкрементом указателя lpm r17, Z; загрузили следующий байт из таблицы
[ Всё дело не столько в вашей глупости, сколько в моей гениальности ] [ Правильно заданный вопрос содержит в себе половину ответа ]
Gudd-Head, там, в таблице, вроде, два байта на число. Смещение подкоректировть надо будет. Ну да ладно, пусть автор вопроса додумает хоть что-то сам...
Дима_Медвед, настоятельно рекомендую ознакомиться со следующим документом: Atmel AVR 8-bit Instruction Set. Если "нихт парле инглиш" , то вот. Но это не первоисточник.
Когда уже ничего не помогает - прочтите, наконец, инструкцию.
Лучший оптимизатор находится у вас между ушей. (Майкл Абраш, программист Quake и QuakeII)
Избыток информации ведёт к оскудению души - Леонтьев А. (сказано в 1965 г.)
Здравствуйте, коты! Прошу вас сразу не ругаться, я понимаю что код ну очень кривой. Я раньше с асм не общаялся. Я написал прошивку для ATmega8A-PU, которая должна управлять серво-приводами с ПК. Не использовал готовые библиотеки, чтоб как бы посмотреть на все изнутри. Все вроде работает, но через какое-то время серво-приводы пересатют работать. Они все также удерживают свою позицию(рукой не крутятся, сопротивляются), но перестают реагировать на команды с ПК. Код под спойлером. Также приложил архив с проектом в AtmelStudio, т.к. код под спойлером получается не структурированным. Не много про то как это работает. С компьютера посылаются 2 байта: 1) Первый байт это номер команды. Но до команд я не дошёл, по этому это попросту номер сервы. 2 ) Это как бы угол, отклонения сервы. После этого, команда посылается обратно в компьютер, для проверки, того что принял МК. Вот когда зависат МК, то всеравно ответ от МК приходит. Код писал, давненько, уже сам по забывал откуда некоторые числа. Ну там некоторые числа подобранны опытным путем.
Вы меня подтолкните, что может быть, я сам подправлю код.
;---------- функция EXT_MAIN: ;Инициализируем стек LDI Temp, low(RAMEND) OUT SPL, Temp LDI Temp, high(RAMEND) OUT SPH, Temp
;Настраиваем наш МК RCALL UART_INIT
;Настраиваем порт С на выход ldi Temp1, 0b11111111 out DDRC, Temp1 ldi Temp1, 0b00000000 out PortC, Temp1
;Сбрасываеп переменные, устанавливаем начальный угол серв LDI Data1, 0 LDI Data2, 0 LDI Servo1, 0x94 ; как бы "угол сервы" в диапозоне: 41-254 LDI Servo2, 0x94
Begin: ;---------- серва ldi Temp1, 0b000101 out PortC, Temp1
MOV Temp1, Servo1 Loop_1: dec Temp1 brne Loop_1 MOV Temp1, Servo1 Loop2_1: dec Temp1 brne Loop2_1 MOV Temp1, Servo1 Loop3_1: dec Temp1 brne Loop3_1
ldi Temp1, 0b00000000 out PortC, Temp1
SEI
ldi Temp1,60 ldi Temp2,11 Loop4_1: dec Temp1 brne Loop4_1
dec Temp2 brne Loop4_1
MOV Temp1, Servo1 COM Temp1 SUBI Temp1, SubAngle Loop_2: dec Temp1 brne Loop_2 MOV Temp1, Servo1 COM Temp1 SUBI Temp1, SubAngle Loop2_2: dec Temp1 brne Loop2_2 MOV Temp1, Servo1 COM Temp1 SUBI Temp1, SubAngle Loop3_2: dec Temp1 brne Loop3_2
CLI
;---------- серва ldi Temp1, 0b000110 out PortC, Temp1
MOV Temp1, Servo2 Loop_3: dec Temp1 brne Loop_3 MOV Temp1, Servo2 Loop2_3: dec Temp1 brne Loop2_3 MOV Temp1, Servo2 Loop3_3: dec Temp1 brne Loop3_3
ldi Temp1, 0b00000100
SEI
out PortC, Temp1
ldi Temp1,60 ldi Temp2,11 Loop4_2: dec Temp1 brne Loop4_2
dec Temp2 brne Loop4_2
MOV Temp1, Servo2 COM Temp1 SUBI Temp1, SubAngle Loop_4: dec Temp1 brne Loop_4 MOV Temp1, Servo2 COM Temp1 SUBI Temp1, SubAngle Loop2_4: dec Temp1 brne Loop2_4 MOV Temp1, Servo1 COM Temp1 SUBI Temp1, SubAngle Loop3_4: dec Temp1 brne Loop3_4 ; Начинаем все с начала
CLI
rjmp Begin
LDI Temp1, 0x00 ;---------- EXT_INT0: EXT_INT1: EXT_TIMER2_COMP: EXT_TIMER2_OVF: EXT_TIMER1_CAPT: EXT_TIMER1_COMPA: EXT_TIMER1_COMPB: EXT_TIMER1_OVF: EXT_TIMER0_OVF: EXT_SPI_STC: ;---------- по приему байта EXT_USART_RXC: CLI CPI Data1, 0 BRNE Set_Data_2 Set_Data_1: IN Data1, UDR SEI RET Set_Data_2: IN Data2, UDR RCALL Read_COMMAND RET ;---------- EXT_USART_UDRE: EXT_USART_TXC: EXT_ADC: EXT_EE_RDY: EXT_ANA_COMP: EXT_TWI: EXT_SPM_RDY:
;Вспомогательные функции ;---------- UART_INIT: LDI R16, low(Baud_Divider) OUT UBRRL,R16 LDI R16, high(Baud_Divider) OUT UBRRH,R16 LDI R16,0 OUT UCSRA, R16 LDI R16, (1 << RXEN)|(1 << TXEN)|(1 << RXCIE)|(0 << TXCIE) ; Прерывание по приему разрешенно, по передаче запрещены, прием-передача разрешен. OUT UCSRB, R16 LDI R16, (1 << URSEL)|(1 << UCSZ0)|(1 << UCSZ1) ; Формат кадра - 8 бит, пишем в регистр UCSRC, за это отвечает бит селектор OUT UCSRC, R16 ldi R16, 1 RET ;----------
;---------- READ_COMMAND: uart_snt: SBIS UCSRA,UDRE RJMP uart_snt OUT UDR, Data1 uart_snt2: SBIS UCSRA,UDRE RJMP uart_snt2 OUT UDR, Data2
Programmer86 писал(а): Код под спойлером. Также приложил архив с проектом в AtmelStudio, т.к. код под спойлером получается не структурированным.
Он и в проекте ни разу не структурированный...
Programmer86 писал(а): Вы меня подтолкните, что может быть, я сам подправлю код.
Первая грубая ошибка - из прерываний выходят по RETI, а не по RET'у. В результате код, который выполнялся при открытых прерываниях после выполнения программы прерывания продолжит исполнение при закрытых прерываниях и следующего прерывания может не произойти. То есть здесь, скорее всего, до этого успеет выполниться SEI в другом месте и до зависания дело не дойдет, но, тем не менее, в общем случае так делать нельзя.
Вторая, и еще более грубая ошибка - это то, что прерывающая программа обязана сохранять и восстанавливать регистры, которыми она пользуется. Здесь ничего подобного нет. В частности, здесь в любой момент исполнения основной программы (от SEI до CLI) могут внезапно измениться регистры Data1, Data2, Servo1 и Servo2 и, самое главное, регистр статуса SREG. Не вникал, может быть изменение регистров данных и предусмотрено, но забытый SREG приведет к тому, что, допустим, цикл Loop4_1 досчитал до конца, выполнилась команда dec TEMP2, результат получился ноль, тут прерывание, которое испортит признак Z, и вперед, цикл будет исполняться еще 256 раз. Совпадение, конечно, достаточно редкое, но рано или поздно оно произойдет и, поскольку оно редкое, будет похоже на случайный сбой.
Третья ошибка - это как раз, в тему того, о чем я вел дискуссию страницей раньше. Если полностью заполнить таблицу векторов командами (r)jmp, то нужна и вторая таблица с объявлением меток, на которые ссылаются эти RJMP. Та самая:
Только она должна быть сплошной, в нее должны входить все метки, на которые ссылаются RJMP из таблицы векторов, они указывают на один и тот же адрес, а по нему должна находиться ловушка для незапланированных прерываний, которую следует написать после этой таблицы, типа rjmp .-1 или, на худой конец, RETI. А, собравшись использовать какое-либо прерывание, надо выдернуть его строчку из таблицы и вставить в нужное место программы. Ну, или скопипастить, а в таблице закомментировать. А так, как сделано у тебя, если вдруг произойдет незапланированное прерывание от INT0 до SPI_STC, запустится программа приема с UART, а от USART_UDRE до SPM_RDY - программа USART_INIT.
Это то, что бросилось в глаза. А про структурирование... На асме принято метки писать с первой позиции, а коды операций от меток через один или несколько tab'ов, чтобы они шли в колонку. Ну и комментариев в программе на асме надо писать раз в 10 больше, чем у тебя, причем структурировать в C-стиле следует именно комментарии...
Кто мешает тебе выдумать порох непромокаемый? (К. Прутков, мысль № 133)
Спасибо, afz, что откликнулись. Я понял, что все работает. Это все ошибка в коде программы на ПК, она перестает посылать байты к МК. Но я все же хочу исправить ошибки про которые вы писали. Заменил RET на RETI. По второй ошибки я вас не понял. Мне надо сохранить регистр SREG? Не могли бы вы написать примерчик? На счет третьей ошибки, вы имели сделать нечто подобное? EXT_INT0: RETI EXT_INT1: RETI EXT_TIMER2_COMP: RETI EXT_TIMER2_OVF: RETI EXT_TIMER1_CAPT: RETI
Programmer86 писал(а): По второй ошибки я вас не понял. Мне надо сохранить регистр SREG? Не могли бы вы написать примерчик?
Регистры общего назначения (R0 - R31) положено сохранять в стеке. Сохраняем командой PUSH. восстанавливаем командой POP. Естественно, необязательно сохранять и восстанавливать все 32 РОНа, если какие-то регистры в этом конкретном прерывании не используются, или используются для передачи параметров из прерывания в основную программу, их сохранять и потом восстанавливать не надо. А так, все сохранения и восстановления должны быть согласованы, какие регистры сохранялись, те же надо и восстанавливать. Восстанавливаем в порядке обратном тому, в котором сохраняли. Это о РОНах.
А вот SREG - это регистр, целиком доступный только по командам IN/OUT. для его сохранения надо сначала освободить какой-нибудь РОН, сохранив в стеке его значение, затем прочитать в него SREG командой IN и сохранить это значение в стеке. То есть в начале программы обслуживания прерывания ставим:
Далее добавляем сохранение тех регистров, которые используются в этом прерывании в качестве рабочих (чтобы их изменение в прерывании не привело к их изменению в основной задаче) и только потом пишем собственно программу обслуживания прерывания. В ее конце восстанавливаем все сохраненные при входе регистры, завершаем это восстановление командами
Rx здесь - любой РОН, поскольку он сохраняется, его тоже можно использовать в качестве рабочего.
Programmer86 писал(а): На счет третьей ошибки, вы имели сделать нечто подобное? EXT_INT0: RETI EXT_INT1: RETI EXT_TIMER2_COMP: RETI EXT_TIMER2_OVF: RETI EXT_TIMER1_CAPT: RETI
Так следует делать только в том случае, если незапланированное прерывание таки обнаружится и начинаешь его поиск под отладчиком, только тогда надо писать не RETI, а, допустим, зацикленный сам на себя RJMP. тогда смотрим в отладчике, где она повисла, и находим, кто это нас прервал и по какой причине. А так вполне достаточно перечислить все эти метки по одной в строку, а после них написать RETI или зацикленный на себя RJMP. Все метки этой таблицы будут ссылаться на один и тот же адрес - адрес этой команды RETI/RJMP, в отлаженной программе это вполне допустимо. Если же оно таки улетит на эту команду, тогда уже предпринимаем действия по выявлению причины путем прописывания зацикленных RJMP по каждой метке.
А программы обслуживания используемых прерываний располагаем где-нибудь в другом месте, из расчета, чтобы на нее не "наехало" управление в естественном порядке.
Кто мешает тебе выдумать порох непромокаемый? (К. Прутков, мысль № 133)
Gudd-Head писал(а):У кого-нибудь есть код перевода 16-битного числа в формате DS18B20 во что-нибудь удобоваримое для вывода со знаком с точностью 0,5 градуса?
; Сначала очистим ZFRX ZLDA x,zfrx clr r2 ldi r16,zfex-zfrx L10: st x+,r2 dec r16 brne L10 nop
ldi R17,32 ; Количество битов в преобразуемом числе
L11: ; Возьмем очередной бит исходного числа и в соответствии с его значением ; установим бит переноса для ZFAdd10 (младший бит R2)
clr r2 lsl r3 rol r4 rol r5 rol r6 brcc L12 inc R2 L12: ldi R18,zfex-zfrx
; Сложим содержимое ZFRX само с собой (* 2) ; и добавим из переноса (R2) значение очередного бита.
ZLDA x,zfex L13: ld r0,-X mov r1,r0 rcall ZFAdd10 st X,r0 dec r18 ; Повторим для всех байтов ZFRX brne L13 dec r17 ; и для всех битов исходного числа brne L11 ret
ZFAdd10: ; Сложение двух однобайтовых десятичных чисео в BCD.
; R0 - первое слагаемое, результат ; R1 - второе слагаемое ; R2 - входной перенос - выходной перенос ; R16 - тоже рабочий mov r16,r2 clr r2 sbrc r16,0 sec adc r0,r1 brhs L4 brcs L6 ldi r16,0x66 add r0,r16 brhs L1 brcs L2 sub r0,r16
ret L1: brcs L3 ldi R16,0x60 sub r0,r16 ret L2: ldi r16,6 sub r0,r16 L3: inc r2 ret L4: brcs l7 ldi r16,0x66 add r0,r16 brcs L5 ldi r16,0x60 sub r0,r16 ret L5: inc r2 ret L6: ldi r16,0x66 add r0,r16 brhs l8 ldi r16,6 sub r0,r16 L8: inc r2 ret L7: ldi r16,0x66 add r0,r16 inc r2 ret
Нужно, во-первых, уменьшить число битов до 16 (ИМХО - легко), а с числом из 1820 разобраться так: во-первых, запоминаем знак, если минус - взять дополнение до 2 от числа, затем к нему прибавить 0x04 и сдвинуть на 3 разряда вправо (это округление), затем умножить его на 10 (оно уже сдвинутое на 1 разряд влево, т.е. умноженное на 2, его надо скопировать в другую пару паре регистров, сдвинуть еще на 2 разряда, т.е. итого умножить на 8 и сложить эти две пары оегистров), после чего отдать моей программе, модифицированной до 16 бит.
Я тут поначалу ошибся, сейчас поправил
Кто мешает тебе выдумать порох непромокаемый? (К. Прутков, мысль № 133)
brne X1 swap SHIFT_REG // Значения собираются в старшей тетраде , готовим их к дальнейшему расчёту ;/ *********************** out GPIOR0,SHIFT_REG ;= а пока запоминаем их .................................... ldwi Z,Accuracy // В Accuracy значение с которой вычисляется температура после запятой in count,GPIOR0 // Вот и потребовалось значение которое мы запомнили cpse count,zero // Если оно вдруг 0х00 то пропускаем вычисление rjmp Add_Thousandths // Иначе вычисляем st Y+,zero // Но и записываем 0х00 для вывода на дисплей NEW_FIND_or_OUT:
Так вот , значения температуры ( самой) укладываются в подпрограмму преобразования в BCD от 1-100 и такая как у меня тебе не нужна, у меня в проекте преобразования больше чем 1000.
in count,GPIOR0 // Вот и потребовалось значение которое мы запомнили
В этом регистре собственно само значение после запятой - т.е смещение от начала таблицы до нужного тебе значения . Только надо просто переделать таблицу - сразу занести в нее значения в BCD и все. Будет типа