Вместо светодиода (часы)

Разрабатывая это простое устройство, мы пощупаем кое-что из периферии и освоим принципы доступа к ней. Итак, что мы берем для часов. Естественно, rtc, который считает только секунды. Секунды нужно конвертировать во время и выводить на 7ми сегментный 4х разрядный индикатор в динамической индикации. Вывод времени мы поручим прерыванию по переполнению таймера2, а конвертацию секунд — прерыванию rtc_second, срабатывающее каждую секунду при обнулении предделителя rtc_div. Для атомарного доступа к битам периферии воспользуемся услугами бит-бэнда.
Все применяемые блоки периферии достаточно просты и хорошо описаны. Много чего есть на С, за что авторам спасибо. Для нас их примеры — прекрасные алгоритмы. Нас же интересует лишь то, как удобней и эффективней сделать это на асме. Ну и как обычно: макросы — наше всё. Пишем макросы для атомарного доступа к битам периферии.

	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

где word — базовый адрес начала блока периферии, offset — адрес регистра в блоке (смещение), bit — расположение бита в регистре (адрес бита).
Да, сразу договоримся, что константы 0 и 1 у нас уже будут в регистрах r10, r11 соответственно. Это и удобней и код сократит. Зачем вычитаем 0х40000000 — опять же для удобства, чтобы брать базовые адреса блоков периферии прямо из мануала, а компиллер пусть себе пересчитывает. При чтении бита сразу проверяем его (tst выставляет флаги).
Поскольку нам часто прийдется оперировать с группой битов (т.н. битовыми полями), напишем макрос и под них.

	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

Смысл макроса прост: читаем слово, меняем битовое поле, пишем слово на место. Инструкция bfi забрасывает битовое поле длиной width с адресом 0 из регистра r2 на позицию с адресом lbs регистра r1.
Ну и еще один макрос, который будет полезен. Он забрасывает константу по адресу с базой r0 и константой-смещением.

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

А теперь займемся настройками периферии:

	movs r10,#0  ;не забудем о наших константах
	movs r11,#1
	ldr r0,=shift ;текущий разряд при динамической индикации
	strb r10,[r0]
;rcc config--------
	setbit 	rcc, rcc_apb2enr, iopa_bit ;porta clock enable
	setbit 	rcc, rcc_apb2enr, iopb_bit ;portb clock enable
;rtc config--------
	setbit rcc, rcc_apb1enr, pwr_bit   ;power interface clock enable
	setbit rcc, rcc_apb1enr, bkp_bit   ;backup interface clock enable
	setbit pwr_cr, 0, dbp_bit	   ;access to rtc and backup registers enable

	setbit rcc, rcc_bdcr, lseon_bit	   ;lse on
lse_not_ready
	getbit rcc, rcc_bdcr, lserdy_bit   ;lse ready ?
	beq lse_not_ready		   ;not ready

	setbits rcc, rcc_bdcr, rtcsel_lbs, rtcsel_width, rtcsel_lse ;lse oscillator clock used as rtc clock 
	setbit rcc, rcc_bdcr, rtcen_bit	   ;rtc clock enabled
	setbit rtc, rtc_crh, secie_bit	   ;second interrupt enable
;tim2 config-------
	setbit rcc, rcc_apb1enr, tim2_bit  ;tim2 interface clock enable
	ldr r0,=tim2
	savec 1000, tim25_psc		   ;set prescaler
	savec 4, 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_uie_bit ;update interrupt enabled
;gpio config-------
	ldr r0,=gpioa
	ldr r1,[r0,#pcrl]	;read pins0-7 config
	movs r2,#cnf0m2		;push-pull 2MHz
	bfi r1,r2,#cnfpin0,#4	;pina0 - push-pull
	bfi r1,r2,#cnfpin1,#4	;pina1 - push-pull
	bfi r1,r2,#cnfpin2,#4	;pina2 - push-pull
	bfi r1,r2,#cnfpin3,#4	;pina3 - push-pull
	bfi r1,r2,#cnfpin4,#4	;pina4 - push-pull
	bfi r1,r2,#cnfpin5,#4	;pina5 - push-pull
	bfi r1,r2,#cnfpin6,#4	;pina6 - push-pull
	bfi r1,r2,#cnfpin7,#4	;pina7 - push-pull
	str r1,[r0,#pcrl]	;write pins0-7 config
;	savec 0x22222222, pcrl	;set pins a0-a7 push-pull

	ldr r0,=gpiob
	ldr r1,[r0,#pcrh]	;read pins8-15 config
	movs r2,#cnf0m2		;push-pull 2MHz
	bfi r1,r2,#cnfpin10,#4	;pinb10 - push-pull
	bfi r1,r2,#cnfpin11,#4	;pinb11 - push-pull
	bfi r1,r2,#cnfpin12,#4	;pinb12 - push-pull
	bfi r1,r2,#cnfpin13,#4	;pinb13 - push-pull
	str r1,[r0,#pcrh]	;write pins8-15 config (set pins b10-b13 push-pull)
;nvic config-------
	ldr r0,=nvic
	ldr r1,[r0,#nvic_iser0]
	orr r1,#irq_rtc_ena	;rtc interrupt enable
	orr r1,#irq_tim2_ena	;tim2 global interrupt
	str r1,[r0,#nvic_iser0]
m2	b m2 

С настройкой gpio есть разные варианты: для наглядности можно настраивать каждый бит порта макросом в ущерб эффективности, можно битовыми полями, а можно и константу кинуть. Для вывода сегментов воспользуемся PA0-PA7, разрядов — PB10-PB13. Ну вот все настроили, больше в основной программе нечего делать — зациклим ее.

Прерывания.
Поскольку у нас событийная система прерываний, мы вынуждены устранять причину прерывания, сбрасывая соответствующие флаги. Далее все просто: в секундном прерывании конвертируем секунды в часы и минуты и переводим их в десятичную систему для удобства вывода на индикатор. В прерывании от таймера организуем динамическую индикацию на case структуре.

        ALIGN 4
RCC_IRQ	PROC

	push {lr}

	resbit rtc, rtc_crl, secf_bit	;res second flag

;----time calculation--------------------------------------
	push {r4,r5}
	ldr r0,=rtc
	ldrh r1,[r0,#rtc_cnth]
	lsl r1,#16
	ldrh r2,[r0,#rtc_cntl]
	mov r3,#0xFFFF
	ands r2,r3
	orrs r1,r2		;full cnt

	ldr r3,=3600*24
	udiv r2,r1,r3		;days
	movs r3,#24
	mul r4,r2,r3		;days *24
	movs r3,#3600
	udiv r2,r1,r3		;hours
	subs r2,r4		;hours(day)=hours - days*24

	ldr r0,=hours
	strb r2,[r0]
	movs r3,#60
	mul r5,r2,r3		;hours *60

	mul r4,r3		;days *24 *60
	udiv r2,r1,r3		;minutes
	subs r2,r4
	subs r2,r5		;minutes(hour)=minutes - day*24*60 - hours*60

	ldr r0,=minutes
	strb r2,[r0]
	mul r2,r3		;minutes *60
	adds r5,r2		;hours*60 +	minutes*60

	mul r4,r3		;day *24 *60 *60
	subs r1,r4
	subs r1,r5		;seconds(min)=seconds - day*24*60*60 - hours*60 - minutes*60

	ldr r0,=seconds
	strb r1,[r0]
;----------------------------------------------------------
;----decimal convert---------------------------------------
	ldr r0,=seconds
	ldrb r1,[r0]
	bl convert
	ldr r0,=dec_sec
	strb r3,[r0]

	ldr r0,=minutes
	ldrb r1,[r0]
	bl convert
	ldr r0,=dec_min
	strb r3,[r0]

	ldr r0,=hours
	ldrb r1,[r0]
	bl convert
	ldr r0,=dec_hou
	strb r3,[r0]
;----------------------------------------------------------
	pop {r4,r5}
;----------------------------------------------------------
	pop {pc}
	ENDP

convert proc
	push {lr}
	movs r2,r1
	movs r0,#10
	udiv r3,r1,r0
	mul r1,r3,r0
	subs r2,r1
	lsl r3,#4
	orr r3,r2
	pop {pc}
	endp

        ALIGN 4
TIM2_IRQ PROC

	resbit tim2, tim25_sr, tim25_sr_uif_bit	;update interrupt flag off

	ldr r0,=shift
	ldrb r1,[r0]
	adr.w r0,case_table0
	tbb [r0,r1]
case1		;shift=0
	ldr r0,=shift
	movs r1,#1
	strb r1,[r0]		;shift=1

	movs r3,#resp13
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;res pin B13 выключаем предыдущий разряд

	ldr r0,=dec_min
	ldrb r1,[r0]
	ands r1,#0x0f

	ldr r0,=datatab
	ldrb r1,[r0,r1]	         ;конвертируем число в образ для индикации

	ldr r0,=gpioa
	ldr r2,[r0,#podr]
	bfi r2,r1,#0,#8
	str r2,[r0,#podr]        ;выводим образ в порт

	movs r3,#setp10
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;set pin B10 включаем разряд 1
	bx lr
case2		;shift=1
	ldr r0,=shift
	movs r1,#2
	strb r1,[r0]		;shift=2

	movs r3,#resp10
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;res pin B10 выключаем предыдущий разряд

	ldr r0,=dec_min
	ldrb r1,[r0]
	ands r1,#0xf0
	lsr r1,#4

	ldr r0,=datatab
	ldrb r1,[r0,r1]		;конвертируем число в образ для индикации

	ldr r0,=gpioa
	ldr r2,[r0,#podr]
	bfi r2,r1,#0,#8
	str r2,[r0,#podr]	;выводим образ в порт

	movs r3,#setp11
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;set pin B11 включаем разряд 2
	bx lr
case3		;shift=2
	ldr r0,=shift
	movs r1,#3
	strb r1,[r0]		;shift=3

	movs r3,#resp11
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;res pin B11 выключаем предыдущий разряд

	ldr r0,=dec_hou
	ldrb r1,[r0]
	ands r1,#0x0f

	ldr r0,=datatab
	ldrb r1,[r0,r1]		;конвертируем число в образ для индикации

	ldr r0,=rtc
	ldrh r2,[r0,#rtc_cntl]
	ands r2,#1
	ite eq
	movseq r3,#0
	movsne r3,#(1<<segh)
	orr r1,r3		;добавляем мигание секунд

	ldr r0,=gpioa
	ldr r2,[r0,#podr]
	bfi r2,r1,#0,#8
	str r2,[r0,#podr]	;выводим образ в порт

	movs r3,#setp12
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;set pin B12 включаем разряд 3
	bx lr
case4		;shift=3
	ldr r0,=shift
	movs r1,#0
	strb r1,[r0]		;shift=4

	movs r3,#resp12
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;res pin B12 выключаем предыдущий разряд

	ldr r0,=dec_hou
	ldrb r1,[r0]
	ands r1,#0xf0
	lsr r1,#4

	ldr r0,=datatab
	ldrb r1,[r0,r1]		;конвертируем число в образ для индикации

	ldr r0,=gpioa
	ldr r2,[r0,#podr]
	bfi r2,r1,#0,#8
	str r2,[r0,#podr]	;выводим образ в порт

	movs r3,#setp13
	ldr r0,=gpiob
	str r3,[r0,#pbsrr]	;set pin B13 включаем разряд 4
	bx lr

case_table0
	dcb 0
	dcb ((case2 - case1)/2)
	dcb ((case3 - case1)/2)
	dcb ((case4 - case1)/2)

	ENDP

С пересчетами и конвертацией сильно не заморачивался, наверняка можно зделать все более элегантно и/или эффективно. У кого есть — предлагайте. Да, забыли про таблицу перекодировки и срам. Таблицу перекодировки копируем из предыдущей статьи.

datatab dcb char0,char1,char2,char3,char4,char5,char6,char7,char8,char9

		AREA |header data|,DATA,READWRITE
seconds	dcb 0
minutes	dcb 0
hours	dcb 0

dec_sec	dcb 0
dec_min	dcb 0
dec_hou	dcb 0

shift	dcb 0
		END

Чтобы прерывания работали их нужно выровнять по слову, а ссылки на них экспортировать в startup.
EXPORT RCC_IRQ
EXPORT TIM2_IRQ
В самом же стартапе их нужно импортировать
import RCC_IRQ
import TIM2_IRQ и подставить соответствующим векторам
RTC_IRQHandler b RCC_IRQ
TIM2_IRQHandler b TIM2_IRQ
(Вообще-то надо было давно его переписать под себя)

Ну и осталось самое простое — расписать все эквиваленты :)

PS. Возможно что-то не эффективно или я что-то не правильно понял — но все работает. Приму любую помощь и пожелания (ибо ученье — свет)
PSS. Отлично работающая программа в симуляторе не всегда сразу запускается в реальном железе :)
  • +2
  • 10 апреля 2011, 23:16
  • psv

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

RSS свернуть / развернуть
маленький вопрос а дефайны сишные подтягиваются? или вы отдельно прописываете их в отдельном файле?
0
  • avatar
  • WitGo
  • 08 февраля 2012, 10:47
отдельно свои
0
можем как то обменяться\договориться о едином формате заполнения?
а то я тоже сегодня для f100rb начал делать :-) может сложить усилия?
0
  • avatar
  • WitGo
  • 08 февраля 2012, 21:32
Где-то я уже выкладывал свой equ файл под 100С4. А договориться тяжело т.к. формат может зависеть как и от IDE так и от стиля программирования.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.