Погружаемся

Вчера придумал себе такую задачу, чтобы меньше работы с макеткой. Итак, периодически заставляем DMA пересылать два байта с ADC, настроенного на датчик температуры, в USART. Итого: один проводок от макетки к преобразователю уровня на RS232 (+ земля, конечно).
Контроллер разгоним до 72MHz (что мы уже делали), а DMA будем запускать таймером tim2 (с которым мы уже работали). Что осталось? USART. О нем нечего было бы говорить, если бы ни дурацкое вычисление бодрейта. Рецепт такой: берем частоту (в данном случае 72Мгц) на юсарте и делим на нужную скорость (например 9600), умноженную на 16. В получившемся результате умножаем целую часть на 16 и остаток на 16 поотдельности. Остаток округляем до ближайшего целого. Теперь складываем что получилось и забрасываем в регистер usart_brr. Далее DMA. Вообще нечего обсуждать — самый простой блок. Достаточно 20 мин поиграться с ним в эмуляторе и все становится ясно. А вот с ADC я игрался гораздо дольше, т.к. не все мне было очевидно. Ну и теперь вся прога с подробными коментами:

  	AREA |header code|,CODE,READONLY
	ENTRY
	GET stm32equ.s
	EXPORT __main
__main
	macro	;r10=0, r11=1
	setbit $word, $offset, $bit
	ldr r0,=((($word - 0x40000000) +$offset)*32)+($bit *4) +0x42000000
	str r11,[r0]
	mend

	macro	;r10=0, r11=1
	resbit $word, $offset, $bit
	ldr r0,=((($word - 0x40000000) +$offset)*32)+($bit *4) +0x42000000
	str r10,[r0]
	mend

	macro	;r10=0, r11=1
	getbit $word, $offset, $bit
	ldr r0,=((($word - 0x40000000) +$offset)*32)+($bit *4) +0x42000000
	ldr r0,[r0]
	tst r0,r0
	mend

	macro
	setbits $base, $reg, $lbs, $width, $field
	ldr r0,=$base
	ldr r1,[r0,#$reg]
	movs r2,#$field
	bfi r1,r2,#$lbs,#$width
	str r1,[r0,#$reg]
	mend

	macro
	savec $const, $reg
	movs r1,#$const
	str r1,[r0,#$reg]
	mend

	macro
	savecw $const, $reg
	ldr r1,=$const
	str r1,[r0,#$reg]
	mend

	movs r10,#0
	movs r11,#1

;rcc config----------------------------
;set sysclk=72, apb1=36, apb2=72, adcclk=12
;--------------------------------------
	setbit rcc, rcc_cr, hse_bit	    ;hse on
hse_not_ready
	getbit rcc,	rcc_cr, hserdy_bit  ;hse ready ?
	beq hse_not_ready                   ;hse ready
	setbits rcc, rcc_cfgr, sw_lbs, sw_width, sw_hse ;selected hse as system clock
	setbit flash_acr, 0, prftbs_bit			;prefetch is enabled
	setbit flash_acr, 0, latency_two_bit	;latency2

	ldr r0,=rcc
	ldr r1,[r0,#rcc_cfgr]

	movs r2,#hpre_d1
	bfi r1,r2,#hpre_lsb,#hpre_width	;hclk=sysclk

	movs r2,#ppre2_d1
	bfi r1,r2,#ppre2_lsb,#ppre2_width	;pclk2=hclk (72MHz max)

	movs r2,#ppre1_d2
	bfi r1,r2,#ppre1_lsb,#ppre1_width	;pclk1=hclk/2 (36MHz max)

	orr r1,#pllsrc_on			;clock from prediv1 as pll input clock

	and r1,#pllxtpre_off		;hse clock not divided

	movs r2,#pllmul_x9
	bfi r1,r2,#pllmul_lsb,#pllmul_width	;set pll mul x9

	movs r2,#adcpre_d6
	bfi r1,r2,#adcpre_lsb,#adcpre_width	;pclk2 divided by 6 for ADC 72/6=12

	str r1,[r0,#rcc_cfgr]


	setbit rcc, rcc_cr, pll_bit		;pll on
rcc_config1
	getbit rcc, rcc_cr, pllrdy_bit	;pll ready ?
	beq rcc_config1			;not ready

	setbits rcc, rcc_cfgr, sw_lbs, sw_width, sw_pll	;pll selected as system clock

rcc_config2
	ldr r1,[r0,#rcc_cfgr]
	and r1,#2_1100
	cmp r1,#2_1000			;pll selected ?
	bne rcc_config2			;not

	setbit 	rcc, rcc_apb2enr, iopa_bit	;porta clock enabled
	setbit 	rcc, rcc_apb2enr, afio_bit	;alternate function io clock enabled
	setbit 	rcc, rcc_apb2enr, usart1_bit	;usart1 clock enabled
	setbit  rcc, rcc_apb2enr, adc1_bit	;adc1 clock enabled
	setbit  rcc, rcc_ahbenr, dma1_bit	;dma1 clock enabled
;end rcc comfig------------------------

;gpio config---------------------------
	ldr r0,=gpioa
	ldr r1,[r0,#pcrh]		;read pins8-15 config
	movs r2,#cnf2m2		;двухтактный выход альтернативный 2MHz
	bfi r1,r2,#cnfpin9,#4	;pina9 - usart1 TX
	str r1,[r0,#pcrh]		;write pins8-15 config
;end gpio config-----------------------

;usart1 config-------------------------
;9600 baud, 1 Start Bit, 8 Data Bits, 1.0 Stop Bits, No Parity
;--------------------------------------
	setbit usart1, usart_cr1, usart_cr1_te_bit	;transmitter enable
	setbit usart1, usart_cr1, usart_cr1_re_bit	;receiver enable
;div=fck/(baud*16)=72M/(9600*16)=468,75 (div_mantissa=468, div_fraction=16*0,75=12)
;usart_gtpr <- (468<<4) + 12 = 7500
	ldr r0,=usart1
	savecw 7500, usart_brr
	setbit usart1, usart_cr3, usart_cr3_dmar_bit	;DMA enable receiver
	setbit usart1, usart_cr1, usart_cr1_ue_bit	;usart enable
;end usart1 config---------------------

;adc config----------------------------
	setbits adc1, adc_sqr1, adc_sqr1_l_lbs, adc_sqr1_l_width, adc_sqr_l1		;1 conversion
	setbits adc1, adc_sqr3, adc_sqr3_sq1_lbs, adc_sqr3_sq_width, adc_cr1_awdch_16	;select the ADCx_IN16 input channel
	setbits adc1, adc_smpr2, adc_smpr2_smp0_lbs, adc_smpr1_smp_width, adc_smp_55_5	;sample time selection
	setbit adc1, adc_cr2, adc_cr2_tsvrefe_bit	;temperature sensor and Vrefint channel enabled
	resbit adc1, adc_cr2, adc_cr2_align_bit		;right alignment
	setbit adc1, adc_cr2, adc_cr2_cont_bit		;continuous conversion mode
	setbit adc1, adc_cr2, adc_cr2_exttrig_bit	;conversion on external event enabled
	setbits adc1, adc_cr2, adc_cr2_extsel_lbs, adc_cr2_extsel_width, adc_cr2_extsel_swstart ;swstart
	setbit adc1, adc_cr2, adc_cr2_adon_bit		;enable ADC and to start conversion
;end adc config------------------------

;DMA config----------------------------
	ldr r0,=dma1
	savecw adc1 +adc_dr, dma_cmar2	;adc_dr -> 
	savecw usart1 +usart_dr , dma_cpar2	;usart_dr <-
	savec 2, dma_cndtr2		;2 bytes transfered

	setbits dma1, dma_ccr2, dma_ccr_pl_lbs, dma_ccr_pl_width, dma_ccr_pl_lo	;channel priority level- low
	setbits dma1, dma_ccr2, dma_ccr_msize_lbs, dma_ccr_msize_width, dma_ccr_msize_8	;memory size - 8 bits
	setbits dma1, dma_ccr2, dma_ccr_psize_lbs, dma_ccr_psize_width, dma_ccr_psize_8	;peripheral size - 8 bits
	setbit dma1, dma_ccr2, dma_ccr_minc_bit	;memory increment mode enabled
	resbit dma1, dma_ccr2, dma_ccr_pinc_bit	;peripheral increment mode disabled
	setbit dma1, dma_ccr2, dma_ccr_circ_bit	;circular mode enabled
	setbit dma1, dma_ccr2, dma_ccr_dir_bit	;read from memory
	setbit dma1, dma_ccr2, dma_ccr_en_bit	;channel enabled						 
;end DMA config------------------------

;tim2 config---------------------------
	setbit rcc, rcc_apb1enr, tim2_bit		;tim2 interface clock enable  72M
	ldr r0,=tim2
	savecw 10000, tim25_psc			;set prescaler
	savec 720, tim25_arr			;set reload
	setbit tim2, tim25_cr1, tim25_cr1_dir_bit	;downcounter
	setbit tim2, tim25_cr1, tim25_cr1_cen_bit	;counter enabled
	setbit tim2, tim25_dier, tim25_dier_ude_bit	;update dma request enabled
;end tim2 config-----------------------
	setbit adc1, adc_cr2, adc_cr2_swstart_bit	;starts conversion of regular channels
m1	b m1

	AREA |header data|,DATA,READWRITE

	END

В окне терминала компа (или у кого что) мы наблюдаем постоянное появление двух циферок — это и есть оцифровка напряжения на нашем датчике температуры. Если контроллер нагревать (а я мучал его феном), то значение уменьшается. А чтобы пересчитать все в реальные значения температуры, нужна поверка + хитрая формула из мануала. Мануал же рекомендует применять сей датчик лишь для относительного сравнения температур, а для реальных замеров ставить внешний.
Что понравилось — программы как таковой и нет, одни настройки. Все действия происходят в обход ядра, а бедное ядро нервно тупит 72Мгц в зацикливании.
Что не понравилось — вечно попадаюсь на одно и то же — в симуляторе работает, а в железе — нет. А что ж такое? А это DMA забыл затактировать или таймер или еще какую хрень. Достаёт!

PS. Хотелось бы узнать, читает ли хоть кто-нибудь мою писанину или пора прекращать. А то, чувствую, прийдется как в давние времена в нудных курсовых и дипломах вставлять где-то в середину — «кто дочитает до этого места, получит бутылку коньяку».

Дополнение.

Все крутят на АЦП переменником и я хочу. Немного подправим программу и прицепим переменный резистор. Выберем сперва канал для АЦП. Пусть будет канал 1 на pinA1, настроим его на аналоговый вход:

	ldr r1,[r0,#pcrl]	;read pins0-7 config
	movs r2,#cnf0m0		;аналоговый вход
	bfi r1,r2,#cnfpin1,#4	;pina1 - ADC12_IN1
	str r1,[r0,#pcrl]	;write pins0-7 config

И запихнем в настройки gpio сразу после настройки pinA9 на TX.
Теперь подправим настройки ADC:
— убираем подключение температурного сенсора;
— выберем для конвертации канал 1;
— выровняем результат по левому краю, чтобы передавать один значащий байт (будем строить график в терминале).
Подправим и DMA:
— отправлять будем один байт;
— отключим увеличение адреса источника;
— изменим на 1 сам адрес источника.

Итак, без макросов и разгона программа выглядит теперь так:

;gpio config---------------------------
	ldr r0,=gpioa
	ldr r1,[r0,#pcrh]	;read pins8-15 config
	movs r2,#cnf2m2		;двухтактный выход альтернативный 2MHz
	bfi r1,r2,#cnfpin9,#4	;pina9 - usart1 TX
	str r1,[r0,#pcrh]	;write pins8-15 config

	ldr r1,[r0,#pcrl]	;read pins0-7 config
	movs r2,#cnf0m0		;аналоговый вход
	bfi r1,r2,#cnfpin1,#4	;pina1 - ADC12_IN1
	str r1,[r0,#pcrl]	;write pins0-7 config
;end gpio config-----------------------

;usart1 config-------------------------
;9600 baud, 1 Start Bit, 8 Data Bits, 1.0 Stop Bits, No Parity
;--------------------------------------
	setbit usart1, usart_cr1, usart_cr1_te_bit ;transmitter enable
	setbit usart1, usart_cr1, usart_cr1_re_bit	;receiver enable
	ldr r0,=usart1
	savecw 7500, usart_brr      ;set baudrate
	setbit usart1, usart_cr3, usart_cr3_dmar_bit ;DMA enable receiver
	setbit usart1, usart_cr1, usart_cr1_ue_bit ;usart enable
;end usart1 config---------------------

;adc config----------------------------
	setbits adc1, adc_sqr1, adc_sqr1_l_lbs, adc_sqr1_l_width, adc_sqr_l1		;1 conversion
	setbits adc1, adc_sqr3, adc_sqr3_sq1_lbs, adc_sqr3_sq_width, adc_cr1_awdch_1	;select the ADCx_IN1 input channel
	setbits adc1, adc_smpr2, adc_smpr2_smp0_lbs, adc_smpr1_smp_width, adc_smp_55_5	;sample time selection
	setbit adc1, adc_cr2, adc_cr2_align_bit		;right alignment
	setbit adc1, adc_cr2, adc_cr2_cont_bit		;continuous conversion mode
	setbit adc1, adc_cr2, adc_cr2_exttrig_bit	;conversion on external event enabled
	setbits adc1, adc_cr2, adc_cr2_extsel_lbs, adc_cr2_extsel_width, adc_cr2_extsel_swstart ;swstart
	setbit adc1, adc_cr2, adc_cr2_adon_bit		;enable ADC and to start conversion
;end adc config------------------------

;DMA config----------------------------
	ldr r0,=dma1
	savecw adc1 +adc_dr +1, dma_cmar2	;adc_dr -> 
	savecw usart1 +usart_dr , dma_cpar2	;usart_dr <-
	savec 1, dma_cndtr2		;1 bytes transfered

	setbits dma1, dma_ccr2, dma_ccr_pl_lbs, dma_ccr_pl_width, dma_ccr_pl_lo	;channel priority level- low
	setbits dma1, dma_ccr2, dma_ccr_msize_lbs, dma_ccr_msize_width, dma_ccr_msize_8	;memory size - 8 bits
	setbits dma1, dma_ccr2, dma_ccr_psize_lbs, dma_ccr_psize_width, dma_ccr_psize_8	;peripheral size - 8 bits
	resbit dma1, dma_ccr2, dma_ccr_minc_bit	;memory increment mode disabled
	resbit dma1, dma_ccr2, dma_ccr_pinc_bit	;peripheral increment mode disabled
	setbit dma1, dma_ccr2, dma_ccr_circ_bit	;circular mode enabled
	setbit dma1, dma_ccr2, dma_ccr_dir_bit	;read from memory
	setbit dma1, dma_ccr2, dma_ccr_en_bit	;channel enabled						 
;end DMA config------------------------

;tim2 config---------------------------
	setbit rcc, rcc_apb1enr, tim2_bit	;tim2 interface clock enable  72M
	ldr r0,=tim2
	savecw 10000, tim25_psc			;set prescaler
	savec 720, tim25_arr			;set reload
	setbit tim2, tim25_cr1, tim25_cr1_dir_bit	;downcounter
	setbit tim2, tim25_cr1, tim25_cr1_cen_bit	;counter enabled
	setbit tim2, tim25_dier, tim25_dier_ude_bit	;update dma request enabled
;end tim2 config-----------------------
	setbit adc1, adc_cr2, adc_cr2_swstart_bit	;starts conversion of regular channels
m1	b m1
                END


Подключили переменник GND---PINA1---Vcc, подключили PINA9 через max232 к COM-порту, включили терминал на вывод графика и понеслась (взираем на дикую синусоиду, судорожно вращая переменник в разные стороны). Ура!

PS. А где же наш страшный, плохо читаемый ассемблер? Куда он подевался то? Ну может в следующий раз.
  • +2
  • 17 апреля 2011, 14:53
  • psv
  • 1
Файлы в топике: equ32F100C4.txt

Комментарии (42)

RSS свернуть / развернуть
давай бутылку.
0
вторую
0
  • avatar
  • Puff
  • 17 апреля 2011, 15:22
Хитрые, может вы середину не читали.
0
  • avatar
  • psv
  • 17 апреля 2011, 15:44
прочитал. давай :)
0
Я тоже на бутылку рассчитываю… А какой коньяк кстати?
0
Я уже плохо в них разбираюсь, когда-то ценился закарпатский.
0
  • avatar
  • psv
  • 17 апреля 2011, 22:39
А мне две, я два раза прочитал.
Когда-то «Белый аист» мы пивали…
0
+ дополнение
0
  • avatar
  • psv
  • 19 апреля 2011, 11:37
Дополнение, блин
0
  • avatar
  • psv
  • 19 апреля 2011, 11:49
А где б взять файлик stm32equ.s? Что же в нем?
0
А файлик этот — результат самой нудной работы на свете. В нем прописаны адреса регистров и битов периферии арма, коих дохрена и немного больше. В зависимости от стиля, каждый формирует его сам ну или подстраивается под стиль другого (использует чужой файл).
0
придется цистерну коньяка брать оптом, а потом бонусами отправлять…
0
Касательно файла эквивалентов — он постоянно дополняется, переделывается, исправляется. То что-то не удобно, то — не красиво. Я свой конечно выложу, но предупреждаю что ему далеко до идеала.
0
Я вчера сие уже понял и начал ваять свой. Единственное с чем столкнулся — не найду конкретики что в данный проц(STM32F100RB)встроено, все так обтекаемо. Гапример с таймерами непонятка, точнее с их количеством. Где бы это внтно почитать. Например так, как в Атмега16 (таймеров такх то стоко, УАРТов стоко, и т. д.)
0
Хм, идея — пойду посмотрю в симуляторе кайла — может чо найду))
0
Самому писать трудно — но полезно. 1.Сразу делаешь как самому удобно 2.Расписывая биты периферии понимаешь как это работает. Что касается внутренностей конкретного проца — тут да производители выдали ребус. Я обычно смотрю к какому семейству МК относится (low den,high) и разбираюсь с его block diagram в даташите.
0
Не совсем понял, можно подробнее?
0
Не совсем понял, можно подробнее? Где эту блок диаграму взять, то что я находил — общее для всех, м в Кайле тоже нет толкового списка, правда может я и не нашел
0
Есть даташит для семейства например STM32F100x4-x6-x8.pdf, где только распиновка, наличие периферии и эл. характеристики.
0
Да, я кажется нашел 11 стр, " STM32F100xx features and peripheral count" в DS6517, видимо все таки спать надо больше по ночам))
0
Спать нужно однозначно больше — это закрепляет долговременную память.
0
читаю!
сегодня дискавери пришла на почту, так что скоро буду и пробовать…

тоже люблю асм, но буду на бесплатной среде работать (не люблю пиратствовать)
0
  • avatar
  • WitGo
  • 08 февраля 2012, 10:40
а приведите код обработчика прерывания!
как правильно завершать прерывание?
0
  • avatar
  • WitGo
  • 25 февраля 2012, 19:15
Обработчик прерывания ничем не отличается от подпрограммы, поэтому возврат: bx lr (если возврат в lr, конечно)
0
Гм. Не знаю как в Cortex-M, а вот в ARM7 из каждого режима (IRQ/FIQ/SWI, и пара-тройка исключений ядра — типа неправильной инструкции) нужно было выходить своей командой.
0
Вопрос возник потому что:
в документации написано что при генерации прерывания процессор сохраняет на стеке регистры R0-R3, R12, LR и приведено 3 способа выхода из прерывания
BX LR
POP {SP}
LDR SP
Я так понимаю при выходе из прерывания процессор сам со стека возьмет значения регистров (это удобно что не нужно писать самому) НО ЕСЛИ вызвать из прерывания подпрограмму BL subProg
из subProg, соответственно возвращаться будем командой BX LR — процессор вдруг не решит что это выход из прерывания (см. выше 3 варианта) и не восстановит регистры ???

поэтому и спрашиваю — есть ли здесь какие то особенности?
0
  • avatar
  • WitGo
  • 26 февраля 2012, 19:34
ой… не POP SP и LDR SP!!! а POP {PC} и LDR PC
(опечатался)
0
Хороший вопрос. Документального объяснения не видел. Очевидно, прерывание любит возвращаться только по своему адресу. Одно могу сказать точно — до десятка прерываний с несколькими выходами и вложенными подпрограммами нормально работают с выходом по bx lr.
0
Спасибо за ответ. а то я уже все перерыл, примеров на асме для M3 практически нет, и с прерываниями полных примеров еще меньше :-(

Еще один вопрос, сразу извиняюсь за тупизм вопроса, сам в асме (правда не АРМ) не первый день, и представляю насколько вопрос глупый, но бился весь вечер над следующей конструкцией

R0 — фиксированное значение 50
R1 — значение счетчика, меняется от 0 до 255 и дальше по кругу

мне по достижении R1 значения R0 нужно сделать какое то действие (зарегистрировать нажатие кнопки) — я пишу условие
CMP R1, R0
BCS ничего_не делаем_рано
а_здесь_делаем

наивно полагаю что BCS это переход когда C=1 (так написано в документации), далее наивно полагаю что CMP это R1-R0

но такое ощущение что CMP это R0-R1 !!!!
потому что программа неработала пока я наобум (просто уже все проверил!!) не поменял BCS на BCC…

расскажите пожалуйста — это на самом деле так или я все таки не все проверил в программе?
у меня уже после 3х часов думания голова болит… но иного объяснения поведения программы кроме как обратный порядок вычитания я не нашел…

так как же работает CMP?
0
ООО!!! правильно работает с конструкцией не BCS а BMI
BMI по описанию переход когда " меньше нуля" флаг N=1

вот такие дела,
почему не работает флаг переноса (как то привык к нему после i8080, z80, avr) не понимаю…
наверное нужно что нить почитать еще раз… если кто подскажет что именно почитать буду очень благодарен!
0
Такой же был шок с этим cmp. С тех пор любую инструкцию проверяю в отладчике.
0
А с bcs да применял обратный порядок, работает вроде правильно, НО! кто знает как на самом деле?
0
гм… вот так приколы у STM…

а еще тонкости каких команд вы выявили ?
кстати, а автосохранение регистров на стеке при прерываниии действительно есть или это тоже только в доке написано но на деле не работает?

p.s. вчера я был уверен что потихоньку крыша едет от меня :-))) а оказывается cmp — это давно известная фича…
0
Автосохранение работает. Тонкостей не документированных не припомню. Пока с r12 не разобрался, забросил его (не использую). Для перекодировки скан-кодов (кнопок и др.) в двоичный рекомендую clz. IT блоки не использую — условные команды удобнее (нужно будет глянуть в чем разница, если она есть). А так — старый добрый ассемблер.
0
еще маленький вопрос:
а если асм код будет более чем на 32 килобайта — армасм — откажется его компилировать?
какой нить есть способ преодаления предела?
или этот предел только для СИ программ?
0
Там по моему не компилятор лимитирует, а линкер. А линкеру глубоко параллельно, какие объектники линковать — все равно больше 32кб линковать откажется.
0
и что делать? можно эти файлы другому линкеру подсунуть? 32 кб это мало :-(
0
Кто-то здесь описывал способ — загонял данные отдельно во флеш.
0
Ну кроме линкера там еще и отладчик ограничен (а иногда — только отладчик). Так что разумнее найти теблетку или перейти на GCC.
Алсо angel5a писал статьи о том, как задействовать флеш за пределами бесплатно доступного.
0
Дело только в том, что в ARM при вычитании Carry flag получает значение обратное по сравнению с общепринятым: 0 – был заём (borrow), 1 – не было (видимо разработчица решила сэкономить пару инверторов). Нужно просто использовать специальные коды условий:
LO – lower (синоним для CC)
LS – lower or same
HI – higher
HS – higher or same (синоним для CS).
Кстати для знаковых сравнений лучше использовать коды условий, учитывающие переполнение: LT, LE, GT, GE.

Прошу прощения, не удержался, даже специально зарегистрировался.
0
Во время прерывания в LR сохраняется специальное значение: 0xFFFFFFxy (x=E,F; y=1,9,D). При попытке загрузить это значение в PC ядро понимает, что происходит выход из прерывания и производит необходимые действия. При вызове подпрограмм LR сохраняется как обычно, так что всё будет работать вполне прозрачно. (ARM®v7-M Architecture
Reference Manual, B1.5.8 Exception return behavior)
+1
Огненно плюсую за продолжение цикла статей.
Допилю проекты на 8-разрядниках, начну гораздо усиленней мучать асм для стм32 с целью максимально понять логику работы контроллера. Очень интересно было бы почитать про использование привелигированых режимов в M3/M4, про работу ДМА с внешней памятью (вроде такая возможность существует), про распределение ресурсов, про правильную работу с DSP-инструкциями и FPU в M4, например.
Но вообще любая информация по асму для stm32 интересна.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.