STM32F030. Всё есть число

Линукс говорит: «Всё есть файл». Микроконтроллер видит мир со своей точки зрения и тоже говорит: «Всё есть число».

Все остальное от Лукавого с единственной целью — отнять ваши деньги более-менее законным способом.

Помните файл blinky.S из нашей последней версии проекта?

На всякий случай я его еще раз приведу тут целиком:

/* blinky.S */

/* Инструкции Thumb-2 поддерживаются только в режиме синтаксиса unified */
	.syntax unified

/* Все следующие команды должны попасть в секцию .text */
	.section ".text"

@-----------------------------------------------------------

/* Объявим main() как глобальное имя. Иначе Линковщик не сможет его найти */
	.global main

/* Объявим функцию как thumb_func. Иначе линковка не будет успешной */
	.thumb_func

/* Это еще не аналог С-шной функции main(), но механизм вызова примерно такой же */
main:
	PUSH	{LR}             /* Сохраним в стеке адрес возврата */

	LDR     R0, =init_gpio   /* В регистр R0 поместим адрес подпрограммы */
	BLX     R0               /* Вызовем эту подпрограмму */
loop:
	LDR     R0, =clear_leds  /* Далее -- аналогично */
	BLX     R0
	LDR     R0, =delay
	BLX     R0
	LDR     R0, =set_leds
	BLX     R0
	LDR     R0, =delay
	BLX     R0
	B       loop               /* Безусловный переход. Это бесконечный цикл */

/* Сюда мы не должны попасть. Но для безопасности допишем следующий код */
/* Если все-таки мы сюда попали, то вернемся в то место, откуда функция main() была вызвана */
   	POP	{PC}

@-----------------------------------------------------------
/* Произведем инициализацию портов  */
	.thumb_func
init_gpio:
/* Поскольку в этой функции нет и не будут содержаться вызовы других функций, то */
/* сохранять адрес возврата (который сейчас находится в регистре LR) в стеке не имеет */
/* смысла. Поэтому следующая команда закомментирована. */
@	PUSH	{LR}             /* Сохраним в стеке адрес возврата */

	/* Включим тактирования порта B */
	LDR	R0, =0x40021014  /* адрес регистра RCC.AHBENR */
	LDR	R1, =0x00020000  /* бит IOPAEN */
	STR	R1, [R0]

	/* Сконфигурируем вывод порта B на вывод */
	LDR	R0, =0x48000000  /* адрес регистра GPIOA.MODER */
	LDR	R1, =0x00000010  /* bit2 на вывод */
	STR	R1, [R0]

	/* Другие регистры порта не трогаем. */

	BX      LR	/* Возвращаемся */

@--------------------------------------------------------------------
/* Зажжем светодиоды */
	.thumb_func
set_leds:
	LDR	R0, =0x48000018  /* адрес регистра GPIO.BSRR */
	LDR	R1, =0x00000004  /* бит 2 */
	STR	R1, [R0]
	BX      LR

@---------------------------------------------------------------------
/* Погасим светодиоды */
	.thumb_func
clear_leds:
	LDR	R0, =0x48000028  /* адрес регистра GPIO.BRR */
	LDR	R1, =0x00000004  /* бит 2 */
	STR	R1, [R0]
	BX      LR

@----------------------------------------------------------
/* Пока мы ничего делать не умеем, поэтому тупо потратим процессорное время на */
/* большой цикл.  */
	.thumb_func
delay:
	LDR	R3, =0x00080000  /* Загружаем в регистр R3 количество циклов */

delay_loop:
	SUBS	R3, #1       /* Декремент регистра R3 */
	BEQ	delay_exit   /* Если R3 окажется равен нулю, то перейдем на метку */
	B       delay_loop   /* В противном случае -- безусловный переход на другую метку */

delay_exit:
                             /* В регистре LR содержится адрес возврата */
	BX      LR           /* Переход по адресу возврата */

Вспомнили. Теперь вспоминаем, что мы говорили про адреса регистров.

Мы говорили, что пространство памяти (все 4 ГБайта) поделено на области. У каждой области определены чёткие границы — диапазон адресов.
Например, для флешь-памяти отведена область первых 512 Мбайт — диапазон адресов от 0x00000000 по 0x1FFFFFFF, для SRAM-а отведена второго участка в 512 Мбайт — диапазон адресов с 0x20000000 по 0x3FFFFFFF. Следующие пол-мегабайта зарезервированы за периферийными устройствами (диапазон адресов с 0x40000000 по 0x5FFFFFFF). Ну и так далее. Все это хорошо описано в документации на микроконтроллер PM215 «Programming Manual».

Для работы с микроконтроллерами фирма ST-Microelectronics) создала хэдерный файл stm32f0xx.h, где имеются следующие строки (номера строк 692-694):

#define FLASH_BASE            ((uint32_t)0x08000000) /*!< FLASH base address in the alias region */
#define SRAM_BASE             ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

Это ни что иное, как начальные (базовые) адреса этих трех областей. Области Флешь-памяти и SRAM нам сейчас не очень интересны, а вот область периферийных устройств давайте рассмотрим более подробно.

Согласно другой книге — RM0360 «Reference manual», область периферийных устройств также разбивается на более мелкие подобласти, принадлежащие разным шинам — APB, AHB1 и AHB2
(См. раздел 2 «System and memory overview», с.29 и далее).

Тоже самое мы видим в файле stm32f0xx.h на строках 697-699:

#define APBPERIPH_BASE        PERIPH_BASE
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x00020000)
#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x08000000)

Это тоже базовые адреса. Базовые адреса для шин, к которым подключены периферийные устройства.

В документе RM0360 «Reference manual» на странице 31 имеется пункт 2.2.2 «Memory map and register boundary addresses», в таблице которого указаны (снова-таки!) базовые адреса уже конкретных для периферийных устройств.

Снимок-STM32F030x4-x6-x8 advanced ARM-based 32-bit MCUs

Снимок-STM32F030x4-x6-x8 advanced ARM-based 32-bit MCUs-1

Снимок-STM32F030x4-x6-x8 advanced ARM-based 32-bit MCUs-2

Теперь снова смотрим в файл stm32f0xx.h на строки 701-745:

#define TIM2_BASE             (APBPERIPH_BASE + 0x00000000)
#define TIM3_BASE             (APBPERIPH_BASE + 0x00000400)
#define TIM6_BASE             (APBPERIPH_BASE + 0x00001000)
#define TIM14_BASE            (APBPERIPH_BASE + 0x00002000)
#define RTC_BASE              (APBPERIPH_BASE + 0x00002800)
#define WWDG_BASE             (APBPERIPH_BASE + 0x00002C00)
#define IWDG_BASE             (APBPERIPH_BASE + 0x00003000)
#define SPI2_BASE             (APBPERIPH_BASE + 0x00003800)
#define USART2_BASE           (APBPERIPH_BASE + 0x00004400)
#define I2C1_BASE             (APBPERIPH_BASE + 0x00005400)
#define I2C2_BASE             (APBPERIPH_BASE + 0x00005800)
#define PWR_BASE              (APBPERIPH_BASE + 0x00007000)
#define DAC_BASE              (APBPERIPH_BASE + 0x00007400)
#define CEC_BASE              (APBPERIPH_BASE + 0x00007800)

#define SYSCFG_BASE           (APBPERIPH_BASE + 0x00010000)
#define COMP_BASE             (APBPERIPH_BASE + 0x0001001C)
#define EXTI_BASE             (APBPERIPH_BASE + 0x00010400)
#define ADC1_BASE             (APBPERIPH_BASE + 0x00012400)
#define ADC_BASE              (APBPERIPH_BASE + 0x00012708)
#define TIM1_BASE             (APBPERIPH_BASE + 0x00012C00)
#define SPI1_BASE             (APBPERIPH_BASE + 0x00013000)
#define USART1_BASE           (APBPERIPH_BASE + 0x00013800)
#define TIM15_BASE            (APBPERIPH_BASE + 0x00014000)
#define TIM16_BASE            (APBPERIPH_BASE + 0x00014400)
#define TIM17_BASE            (APBPERIPH_BASE + 0x00014800)
#define DBGMCU_BASE           (APBPERIPH_BASE + 0x00015800)

#define DMA1_BASE             (AHBPERIPH_BASE + 0x00000000)
#define DMA1_Channel1_BASE    (DMA1_BASE + 0x00000008)
#define DMA1_Channel2_BASE    (DMA1_BASE + 0x0000001C)
#define DMA1_Channel3_BASE    (DMA1_BASE + 0x00000030)
#define DMA1_Channel4_BASE    (DMA1_BASE + 0x00000044)
#define DMA1_Channel5_BASE    (DMA1_BASE + 0x00000058)
#define RCC_BASE              (AHBPERIPH_BASE + 0x00001000)
#define FLASH_R_BASE          (AHBPERIPH_BASE + 0x00002000) /*!< FLASH registers base address */
#define OB_BASE               ((uint32_t)0x1FFFF800)        /*!< FLASH Option Bytes base address */
#define CRC_BASE              (AHBPERIPH_BASE + 0x00003000)
#define TSC_BASE              (AHBPERIPH_BASE + 0x00004000)

#define GPIOA_BASE            (AHB2PERIPH_BASE + 0x00000000)
#define GPIOB_BASE            (AHB2PERIPH_BASE + 0x00000400)
#define GPIOC_BASE            (AHB2PERIPH_BASE + 0x00000800)
#define GPIOD_BASE            (AHB2PERIPH_BASE + 0x00000C00)
#define GPIOF_BASE            (AHB2PERIPH_BASE + 0x00001400)

Таким образом, чтобы получить базовый адрес порта GPIOA мы должны произвести следующие вычисления:

#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
...
#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x08000000)
...
#define GPIOA_BASE            (AHB2PERIPH_BASE + 0x00000000)

Я думаю, «механика» вам понятна. К сожалению, ST-Microelectronics не предоставляет аналогичного С-шному хэдерному файлу (*.h) инклюд-файла (*.inc) для ассемблера. (Ну, я его не нашел. Хотя, признаться, и не очень сильно искал.) Поскольку работы тут не так уж и много, мы можем его сформировать самостоятельно.

Вот его часть, по ходу освоения мы будем его дописывать. Называется файл stm320xx.inc:

/* stm32f0xx.inc */


/* Базовые адреса регионов (областей) адресного пространства */
.equiv	FLASH_BASE,	0x08000000	/* Базовый адрес флешь-пямяти */
.equiv	SRAM_BASE,	0x20000000	/* Базовый адрес SRAM */
.equiv	PERIPH_BASE,	0x40000000	/* Базовый адрес региона периферийных устроств */

/* Базовые адреса для шин */
.equiv	APBPERIPH_BASE,	PERIPH_BASE                  /* APB */
.equiv	AHBPERIPH_BASE,	(PERIPH_BASE + 0x00020000)   /* AHB1 */
.equiv	AHB2PERIPH_BASE,(PERIPH_BASE + 0x08000000)   /* AHB2 */


/* Базовые адреса периферийных устройств шины APB */
.equiv	TIM2_BASE,             (APBPERIPH_BASE + 0x00000000)
.equiv	TIM3_BASE,             (APBPERIPH_BASE + 0x00000400)
.equiv	TIM6_BASE,             (APBPERIPH_BASE + 0x00001000)
.equiv	TIM14_BASE,            (APBPERIPH_BASE + 0x00002000)
.equiv	RTC_BASE,              (APBPERIPH_BASE + 0x00002800)
.equiv	WWDG_BASE,             (APBPERIPH_BASE + 0x00002C00)
.equiv	IWDG_BASE,             (APBPERIPH_BASE + 0x00003000)
.equiv	SPI2_BASE,             (APBPERIPH_BASE + 0x00003800)
.equiv	USART2_BASE,           (APBPERIPH_BASE + 0x00004400)
.equiv	I2C1_BASE,             (APBPERIPH_BASE + 0x00005400)
.equiv	I2C2_BASE,             (APBPERIPH_BASE + 0x00005800)
.equiv	PWR_BASE,              (APBPERIPH_BASE + 0x00007000)
.equiv	DAC_BASE,              (APBPERIPH_BASE + 0x00007400)
.equiv	CEC_BASE,              (APBPERIPH_BASE + 0x00007800)

.equiv	SYSCFG_BASE,           (APBPERIPH_BASE + 0x00010000)
.equiv	COMP_BASE,             (APBPERIPH_BASE + 0x0001001C)
.equiv	EXTI_BASE,             (APBPERIPH_BASE + 0x00010400)
.equiv	ADC1_BASE,             (APBPERIPH_BASE + 0x00012400)
.equiv	ADC_BASE,              (APBPERIPH_BASE + 0x00012708)
.equiv	TIM1_BASE,             (APBPERIPH_BASE + 0x00012C00)
.equiv	SPI1_BASE,             (APBPERIPH_BASE + 0x00013000)
.equiv	USART1_BASE,           (APBPERIPH_BASE + 0x00013800)
.equiv	TIM15_BASE,            (APBPERIPH_BASE + 0x00014000)
.equiv	TIM16_BASE,            (APBPERIPH_BASE + 0x00014400)
.equiv	TIM17_BASE,            (APBPERIPH_BASE + 0x00014800)
.equiv	DBGMCU_BASE,           (APBPERIPH_BASE + 0x00015800)

/* Базовые адреса периферийных устройств шины AHB1 */
.equiv	DMA1_BASE,              (AHBPERIPH_BASE + 0x00000000)
.equiv	DMA1_Channel1_BASE,     (DMA1_BASE + 0x00000008)
.equiv	DMA1_Channel2_BASE,     (DMA1_BASE + 0x0000001C)
.equiv	DMA1_Channel3_BASE,     (DMA1_BASE + 0x00000030)
.equiv	DMA1_Channel4_BASE,     (DMA1_BASE + 0x00000044)
.equiv	DMA1_Channel5_BASE,	(DMA1_BASE + 0x00000058)
.equiv	RCC_BASE,		(AHBPERIPH_BASE + 0x00001000)
.equiv	FLASH_R_BASE,           (AHBPERIPH_BASE + 0x00002000) /* Базовый адрес регистров FLASH  */
.equiv	OB_BASE,                (0x1FFFF800)                  /* Базовый адрес option-байтов FLASH */
.equiv	CRC_BASE,               (AHBPERIPH_BASE + 0x00003000)
.equiv	TSC_BASE,               (AHBPERIPH_BASE + 0x00004000)

/* Базовые адреса периферийных устройств шины AHB1 */
.equiv	GPIOA_BASE,	(AHB2PERIPH_BASE + 0x00000000)
.equiv	GPIOB_BASE,	(AHB2PERIPH_BASE + 0x00000400)
.equiv	GPIOC_BASE,	(AHB2PERIPH_BASE + 0x00000800)
.equiv	GPIOD_BASE,	(AHB2PERIPH_BASE + 0x00000C00)
.equiv	GPIOF_BASE,	(AHB2PERIPH_BASE + 0x00001400)



/* Адреса регистров RCC */
.equiv	RCC_CR,		(RCC_BASE)		/* RCC clock control register */
.equiv	RCC_CFGR,	(RCC_BASE + 0x04)	/* RCC clock configuration register */
.equiv	RCC_CIR,	(RCC_BASE + 0x08)	/* RCC clock interrupt register */
.equiv	RCC_APB2RSTR,	(RCC_BASE + 0x0C)	/* RCC APB2 peripheral reset register */
.equiv	RCC_APB1RSTR,	(RCC_BASE + 0x10)	/* RCC APB1 peripheral reset register */
.equiv	RCC_AHBENR,	(RCC_BASE + 0x14)	/* RCC AHB peripheral clock register */
.equiv	RCC_APB2ENR,	(RCC_BASE + 0x18)	/* RCC APB2 peripheral clock enable register */
.equiv	RCC_APB1ENR,	(RCC_BASE + 0x1C)	/* RCC APB1 peripheral clock enable register */
.equiv	RCC_BDCR,	(RCC_BASE + 0x20)	/* RCC Backup domain control register */
.equiv	RCC_CSR,	(RCC_BASE + 0x24)	/* RCC clock control & status register */
.equiv	RCC_AHBRSTR,	(RCC_BASE + 0x28)	/* RCC AHB peripheral reset register */
.equiv	RCC_CFGR2,	(RCC_BASE + 0x2C)	/* RCC clock configuration register 2 */
.equiv	RCC_CFGR3,	(RCC_BASE + 0x30)	/* RCC clock configuration register 3 */
.equiv	RCC_CR2,	(RCC_BASE + 0x34)	/* RCC clock control register 2 */


/* Смещения регистров GPIO относительно базоавого адреса порта */
.equiv	MODER,		0x00    /* GPIO port mode register */
.equiv	OTYPER,		0x04    /* GPIO port output type register */
.equiv	OSPEEDR,	0x08    /* GPIO port output speed register */
.equiv	PUPDR,		0x0C    /* GPIO port pull-up/pull-down register */
.equiv	IDR,		0x10    /* GPIO port input data register */
.equiv	ODR,		0x14    /* GPIO port output data register */
.equiv	BSRR,		0x18    /* GPIO port bit set/reset register */
.equiv	LCKR,		0x1C    /* GPIO port configuration lock register */
.equiv	AFRL,		0x20    /* GPIO alternate function low register */
.equiv	AFRH,		0x24    /* GPIO alternate function low register */
.equiv	BRR,		0x28    /* GPIO bit reset register */

/* Адреса регистров порта GPIOA */
.equiv	GPIOA_MODER,	(GPIOA_BASE + MODER)
.equiv	GPIOA_OTYPEER,	(GPIOA_BASE + OTYPER)
.equiv	GPIOA_OSPEEDR,	(GPIOA_BASE + OSPEEDR)
.equiv	GPIOA_PUPDR,	(GPIOA_BASE + PUPDR)
.equiv	GPIOA_IDR,	(GPIOA_BASE + IDR)
.equiv	GPIOA_ODR,	(GPIOA_BASE + ODR)
.equiv	GPIOA_BSRR,	(GPIOA_BASE + BSRR)
.equiv	GPIOA_LCKR,	(GPIOA_BASE + LCKR)
.equiv	GPIOA_AFRL,	(GPIOA_BASE + AFRL)
.equiv	GPIOA_AFRH,	(GPIOA_BASE + AFRH)
.equiv	GPIOA_BRR,	(GPIOA_BASE + BRR)

/* Адреса регистров порта GPIOB */
.equiv	GPIOB_MODER,	(GPIOB_BASE + MODER)
.equiv	GPIOB_OTYPEER,	(GPIOB_BASE + OTYPER)
.equiv	GPIOB_OSPEEDR,	(GPIOB_BASE + OSPEEDR)
.equiv	GPIOB_PUPDR,	(GPIOB_BASE + PUPDR)
.equiv	GPIOB_IDR,	(GPIOB_BASE + IDR)
.equiv	GPIOB_ODR,	(GPIOB_BASE + ODR)
.equiv	GPIOB_BSRR,	(GPIOB_BASE + BSRR)
.equiv	GPIOB_LCKR,	(GPIOB_BASE + LCKR)
.equiv	GPIOB_AFRL,	(GPIOB_BASE + AFRL)
.equiv	GPIOB_AFRH,	(GPIOB_BASE + AFRH)
.equiv	GPIOB_BRR,	(GPIOB_BASE + BRR)

/* Адреса регистров порта GPIOBC */
.equiv	GPIOC_MODER,	(GPIOC_BASE + MODER)
.equiv	GPIOC_OTYPEER,	(GPIOC_BASE + OTYPER)
.equiv	GPIOC_OSPEEDR,	(GPIOC_BASE + OSPEEDR)
.equiv	GPIOC_PUPDR,	(GPIOC_BASE + PUPDR)
.equiv	GPIOC_IDR,	(GPIOC_BASE + IDR)
.equiv	GPIOC_ODR,	(GPIOC_BASE + ODR)
.equiv	GPIOC_BSRR,	(GPIOC_BASE + BSRR)
.equiv	GPIOC_LCKR,	(GPIOC_BASE + LCKR)
.equiv	GPIOC_AFRL,	(GPIOC_BASE + AFRL)
.equiv	GPIOC_AFRH,	(GPIOC_BASE + AFRH)
.equiv	GPIOC_BRR,	(GPIOC_BASE + BRR)

/* Адреса регистров порта GPIOD */
.equiv	GPIOD_MODER,	(GPIOD_BASE + MODER)
.equiv	GPIOD_OTYPEER,	(GPIOD_BASE + OTYPER)
.equiv	GPIOD_OSPEEDR,	(GPIOD_BASE + OSPEEDR)
.equiv	GPIOD_PUPDR,	(GPIOD_BASE + PUPDR)
.equiv	GPIOD_IDR,	(GPIOD_BASE + IDR)
.equiv	GPIOD_ODR,	(GPIOD_BASE + ODR)
.equiv	GPIOD_BSRR,	(GPIOD_BASE + BSRR)
.equiv	GPIOD_LCKR,	(GPIOD_BASE + LCKR)
.equiv	GPIOD_AFRL,	(GPIOD_BASE + AFRL)
.equiv	GPIOD_AFRH,	(GPIOD_BASE + AFRH)
.equiv	GPIOD_BRR,	(GPIOD_BASE + BRR)

/* Адреса регистров порта GPIOF */
.equiv	GPIOF_MODER,	(GPIOF_BASE + MODER)
.equiv	GPIOF_OTYPEER,	(GPIOF_BASE + OTYPER)
.equiv	GPIOF_OSPEEDR,	(GPIOF_BASE + OSPEEDR)
.equiv	GPIOF_PUPDR,	(GPIOF_BASE + PUPDR)
.equiv	GPIOF_IDR,	(GPIOF_BASE + IDR)
.equiv	GPIOF_ODR,	(GPIOF_BASE + ODR)
.equiv	GPIOF_BSRR,	(GPIOF_BASE + BSRR)
.equiv	GPIOF_LCKR,	(GPIOF_BASE + LCKR)
.equiv	GPIOF_AFRL,	(GPIOF_BASE + AFRL)
.equiv	GPIOF_AFRH,	(GPIOF_BASE + AFRH)
.equiv	GPIOF_BRR,	(GPIOF_BASE + BRR)


/****************** Bit definition for RCC_AHBENR register ******************/
.equiv	RCC_DMAEN,	0x00000001	/* DMA1 clock enable */
.equiv	RCC_SRAMEN,	0x00000004	/* SRAM interface clock enable */
.equiv	RCC_FLITFEN,	0x00000010	/* FLITF clock enable */
.equiv	RCC_CRCEN,	0x00000040	/* CRC clock enable */
.equiv	RCC_IOPAEN,	0x00020000	/* GPIOA clock enable */
.equiv	RCC_IOPBEN,	0x00040000	/* GPIOB clock enable */
.equiv	RCC_IOPCEN,	0x00080000	/* GPIOC clock enable */
.equiv	RCC_IOPDEN,	0x00100000	/* GPIOD clock enable */
.equiv	RCC_IOPFEN,	0x00400000	/* GPIOF clock enable */

Подключим этот файл определений в наш ассемблерный blinky.S:

/* blinky.S */

.include "stm32f0xx.inc"

...

Теперь вместо страшных чисел можно писать названия регистров и названия битов:

	/* Включим тактирования порта B */
	LDR	R0, =RCC_AHBENR  /* адрес регистра RCC.AHBENR */
	LDR	R1, =RCC_IOPAEN  /* бит IOPAEN */
	STR	R1, [R0]

	/* Сконфигурируем вывод порта B на вывод */
	LDR	R0, =GPIOA_MODER  /* адрес регистра GPIOA.MODER */

Согласитесь, это куда более надежно, чем самостоятельно заниматься арифметикой адресов.

Однако, давайте пойдем дальше. Давайте создадим еще один файл — файл определения битов.
Назовем его bits.inc. Вот его содержимое:

/* bits.inc */

.equiv	BIT0,	(0x00000001)
.equiv	BIT1,	(0x00000002)
.equiv	BIT2,	(0x00000004)
.equiv	BIT3,	(0x00000008)

.equiv	BIT4,	(0x00000010)
.equiv	BIT5,	(0x00000020)
.equiv	BIT6,	(0x00000040)
.equiv	BIT7,	(0x00000080)

.equiv	BIT8,	(0x00000100)
.equiv	BIT9,	(0x00000200)
.equiv	BIT10,	(0x00000400)
.equiv	BIT11,	(0x00000800)

.equiv	BIT12,	(0x00001000)
.equiv	BIT13,	(0x00002000)
.equiv	BIT14,	(0x00004000)
.equiv	BIT15,	(0x00008000)

.equiv	BIT16,	(0x00010000)
.equiv	BIT17,	(0x00020000)
.equiv	BIT18,	(0x00040000)
.equiv	BIT19,	(0x00080000)

.equiv	BIT20,	(0x00100000)
.equiv	BIT21,	(0x00200000)
.equiv	BIT22,	(0x00400000)
.equiv	BIT23,	(0x00800000)

.equiv	BIT24,	(0x01000000)
.equiv	BIT25,	(0x02000000)
.equiv	BIT26,	(0x04000000)
.equiv	BIT27,	(0x08000000)

.equiv	BIT28,	(0x10000000)
.equiv	BIT29,	(0x20000000)
.equiv	BIT30,	(0x40000000)
.equiv	BIT31,	(0x80000000)

И тоже подключим его к нашему файлу blinky.S.
А после этого в нашем файле напишем табличку распределения кнопочек и светодиодов:

/* blinky.S */

.include "bits.inc"
.include "stm32f0xx.inc"


/* Распределим биты порта GPIOA по кнопкам и светодиодам*/
.equ	BTN1,	(BIT0)
.equ	BTN2,	(BIT1)
.equ	LED2,	(BIT2)
.equ	LED3,	(BIT3)
.equ	LED4,	(BIT4)
.equ	LED5,	(BIT5)
.equ	LED6,	(BIT6)
.equ	LED7,	(BIT7)
.equ	LED8,	(BIT8)

...

Теперь мы можем писать код следующим образом:

@--------------------------------------------------------------------
/* Зажжем светодиоды */
	.thumb_func
set_leds:
	LDR	R0, =GPIOA_BSRR  /* адрес регистра GPIO.BSRR */
	LDR	R1, =LED2        /* Бит светодиода LED2 */
	STR	R1, [R0]
	BX      LR

@---------------------------------------------------------------------
/* Погасим светодиоды */
	.thumb_func
clear_leds:
	LDR	R0, =GPIOA_BRR  /* адрес регистра GPIO.BRR */
	LDR	R1, =LED2       /* Бит светодиода LED2 */
	STR	R1, [R0]
	BX      LR

Удобнее? — О тож!

Ну и наконец, давайте здесь же в начале файла также определим значение счетчика циклов для подпрограммы задержки:

/* Зададим количество циклов здержки моргания */
.equ	DELAY,	(0x00080000)

После этих «штукатурных» изменений наша программа стала выглядеть значительно изящнее. По сути, мы в ней ничего не изменили. Мы только заменили «холодные» числа на более «разговорчивые» имена переменных.

Наша программа должна выглядеть вот так:

/* blinky.S */

.include "bits.inc"
.include "stm32f0xx.inc"


/* Распределим биты порта GPIOA по кнопкам и светодиодам*/
.equ	BTN1,	(BIT0)
.equ	BTN2,	(BIT1)
.equ	LED2,	(BIT2)
.equ	LED3,	(BIT3)
.equ	LED4,	(BIT4)
.equ	LED5,	(BIT5)
.equ	LED6,	(BIT6)
.equ	LED7,	(BIT7)
.equ	LED8,	(BIT8)

/* Зададим количество циклов здержки моргания */
.equ	DELAY,	(0x00080000)


/* Инструкции Thumb-2 поддерживаются только в режиме синтаксиса unified */
	.syntax unified

/* Все следующие команды должны попасть в секцию .text */
	.section ".text"

@-----------------------------------------------------------

/* Объявим main() как глобальное имя. Иначе Линковщик не сможет его найти */
	.global main

/* Объявим функцию как thumb_func. Иначе линковка не будет успешной */
	.thumb_func

/* Это еще не аналог С-шной функции main(), но механизм вызова примерно такой же */
main:
	PUSH	{LR}             /* Сохраним в стеке адрес возврата */

	LDR     R0, =init_gpio   /* В регистр R0 поместим адрес подпрограммы */
	BLX     R0               /* Вызовем эту подпрограмму */
loop:
	LDR     R0, =clear_leds  /* Далее -- аналогично */
	BLX     R0
	LDR     R0, =delay
	BLX     R0
	LDR     R0, =set_leds
	BLX     R0
	LDR     R0, =delay
	BLX     R0
	B       loop               /* Безусловный переход. Это бесконечный цикл */

/* Сюда мы не должны попасть. Но для безопасности допишем следующий код */
/* Если все-таки мы сюда попали, то вернемся в то место, откуда функция main() была вызвана */
   	POP	{PC}

@-----------------------------------------------------------
/* Произведем инициализацию портов  */
	.thumb_func
init_gpio:
/* Поскольку в этой функции нет и не будут содержаться вызовы других функций, то */
/* сохранять адрес возврата (который сейчас находится в регистре LR) в стеке не имеет */
/* смысла. Поэтому следующая команда закомментирована. */
@	PUSH	{LR}             /* Сохраним в стеке адрес возврата */

	/* Включим тактирования порта B */
	LDR	R0, =RCC_AHBENR  /* адрес регистра RCC.AHBENR */
	LDR	R1, =RCC_IOPAEN  /* бит IOPAEN */
	STR	R1, [R0]

	/* Сконфигурируем вывод порта B на вывод */
	LDR	R0, =GPIOA_MODER  /* адрес регистра GPIOA.MODER */
	LDR	R1, =0x00000010  /* bit2 на вывод */
	STR	R1, [R0]

	/* Другие регистры порта не трогаем. */

	BX      LR	/* Возвращаемся */

@--------------------------------------------------------------------
/* Зажжем светодиоды */
	.thumb_func
set_leds:
	LDR	R0, =GPIOA_BSRR  /* адрес регистра GPIO.BSRR */
	LDR	R1, =LED2        /* Бит светодиода LED2 */
	STR	R1, [R0]
	BX      LR

@---------------------------------------------------------------------
/* Погасим светодиоды */
	.thumb_func
clear_leds:
	LDR	R0, =GPIOA_BRR  /* адрес регистра GPIO.BRR */
	LDR	R1, =LED2       /* Бит светодиода LED2 */
	STR	R1, [R0]
	BX      LR

@----------------------------------------------------------
/* Пока мы ничего делать не умеем, поэтому тупо потратим процессорное время на */
/* большой цикл.  */
	.thumb_func
delay:
	LDR	R3, =DELAY  /* Загружаем в регистр R3 количество циклов */

delay_loop:
	SUBS	R3, #1       /* Декремент регистра R3 */
	BEQ	delay_exit   /* Если R3 окажется равен нулю, то перейдем на метку */
	B       delay_loop   /* В противном случае -- безусловный переход на другую метку */

delay_exit:
                             /* В регистре LR содержится адрес возврата */
	BX      LR           /* Переход по адресу возврата */

Пару слов скажу о том, как я изменяю программу, компилирую и заливаю код в микроконтроллер.

Мой рабочий стол выглядит так (картинка кликабельна):

desktop

На первый взгляд вглядит как какая-то IDE. На самом деле тут четыре независимых окна, плотно пристыкованных друг к другу. Верхнее левое окно — редактор gedit. Под ним находится окно псевдоконсоли, где я набираю руками команды. Часть этих команд предназначена для утилиты make. Другие команды могут быть могут быть адресованы оболочке bash.

С правой стороны размещаются два окна Файлового менеджера Nautilus. В одном из них находятся файлы рабочего директория, другое — содержит файлы документации.

Все под руками. На другом рабочем столе открыт даташит RM0360
«Reference manual»
, а на третьем открыт браузер в котором я сейчас набираю этот текст для блога.

Переключение между рабочими столами осуществляется через Ctrl-Alt-стрелка_влево и Ctrl-Alt-стрелка_вправо. То есть вообще не напряжно! Находясь, допустим на рабочем столе, где открыт мой проект (четыре окна — текстовый редактор, консоль и два окна файлововго менеджера) я перепрыгиваю из окна в окно через Alt-Tab.

Я что-то изменяю в исходном тексте программы, затем нажимаю Ctrl-S для сохранения и сразу же нажимаю Alt-Tab для перехода в псевдоконсоль. И, если в текстовый редактор я переходил из консоли, то консоль становится снова активной. Если нет, то мне придется нажать еще несколько раз на Alt-Tab.

Находясь в консоли, мне нужно собрать, откомпилировать и залить новую версию кода в микроконтроллер. Делаю я это одним движением — я даже не набираю заветную последовательность make load, я тупо нажимаю клавишу «стрелка вверх», и иду по истории команд. (Это еще одна тема. Не будем ее здесь развивать!)

Почему я сразу даю команду на заливку? Почему, я сначала не компилирую и только потом заливаю? Дело в том, что утилита make отслеживает зависимости (мы об этом говорили!) и сама пересобирает проект перед заливкой.

Таким образом мне очень сложно что-либо накосячить. Я пишу программу, я занимаюсь своим делом, а Линукс как нельзя лучше помогает мне в этом деле — берет на себя столько рутины, что я готов поставить ему памятник при жизни. Хочу особо подчеркнуть отличие Линукса от Виндовса — в Линуксе я контролирую всё полностью, и если мне что-нибудь покажется неудобным, то я всегда смогу это изменить.

Однако, давайте опустимся на землю.

Помните, я говорил про перебор при чрезмерной автоматизации ассеблирования исходных файлов? Пришла пора вернуться и востановить make-файл. Изменения нужно сделать только в этом месте:

# Компиляция (ассемблирование) исходных кодов
# На выходе получаем объектные файлы *.o
start-up.o: start-up.S
	@$(AS) $(ASFLAGS) -o $*.o $<

blinky.o: blinky.S bits.inc stm32f0xx.inc
	@$(AS) $(ASFLAGS) -o $*.o $<

Я добавил две зависимости в цель blinky.o. Теперь blinky.o будет перекомпилироваться заново при изменении в любом из трех файлов.

Вообще файлы bits.inc stm32f0xx.inc должны лежать не в директории проекта, а в общедоступном месте для всех проектов. Но об этом мы поговорим как-нибудь в другой раз. А на сегодня это всё! Тем кому хочется еще повозиться, могу предложить поиграться со включением других светодиодов или написать прогу для «бегущих огней».

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s