Про дефайны там и прочитать, в этом файле. Для этого надо немного глубже знать язык Си, в частности, структуры и указатели.
С дефайнами битов там всё просто.
#define DMA_SxCR_HTIE ((uint32_t)0x00000008 )
присваивает тексту DMA_SxCR_HTIE значение 0x00000008 с конкретным указанием типа uint32_t. И далее текст DMA_SxCR_HTIE будет соответствовать тексту ((uint32_t)0x00000008 ). Тут всё просто.
HAL-оподобный заголовочник (в отличие от кейло-подобного чистого CMSIS) может иметь дефайны битов в виде сумасшедшего громоздкого будерброда через определение сначала позиции бита:
#define DMA_SxCR_HTIE_Pos (3U) ,
затем сдвига 1 на число позиций:
#define DMA_SxCR_HTIE_Msk (0x1U ‹‹ DMA_SxCR_HTIE_Pos)
в результате которой как раз и получается число 0x00000008.
и затем определяется непосредственно
#define DMA_SxCR_HTIE DMA_SxCR_HTIE_Msk
который и есть то, что я писал на абзац выше. То есть, через жопу дошли, написав лишние строки.
..._Pos и равен численно номеру позиции бита. _Msk - битовая маска. А без суффикса - непосредственное значение.
По большому счету, нас будет интересовать только конечный дефайн DMA_SxCR_HTIE. В некоторых выражениях пригодятся значения позиции бита или битовой маски.
Типа
GPIOA-›MODER |= (0x02 ‹‹ 2 * 3), где числа можно заменить текстом
GPIOA-›MODER |= GPIO_MODER_MODE3_1
Это, пожалуй, единственная польза от раздутой конструкции. Ну это лично мое мнение.
Касательно структур с названиями регистров дело обстоит куда сложнее.
Изначально, так же в виде текстовых дефайнов определены численные значения сперва базовых адресов шин согласно карте памяти, вот так:
#define FLASH_BASE ((uint32_t)0x08000000) /*!‹ FLASH(up to 1 MB) base address
....
#define PERIPH_BASE ((uint32_t)0x40000000) /*!‹ Peripheral base address
...
это базовые адреса, от которых все ноги и растут, а затем постепенно друг через друга начинают определяться адреса шин и периферии, сидящей на шинах. Определяются через приращение адреса.
Вначале адрес шины через определенный ранее базовый адрес периферии, плюс смещение адреса шины относительно начала адресов периферии:
/*!‹ Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
...
затем адрес первого регистра (базовый адрес) периферии на шине, так же через предыдущее определение адреса шины и плюс смещение адреса конкретной периферии относительно адреса шины:
/*!‹ APB1 peripherals */
#define TIM2_BASE (APB1PERIPH_BASE + 0x0000)
#define TIM3_BASE (APB1PERIPH_BASE + 0x0400)
...
После прописывания базовых адресов у всей периферии определяются дефайнами указатели, имеющие тип структуры (о которой позже):
/** @addtogroup Peripheral_declaration */
#define TIM2 ((TIM_TypeDef *) TIM2_BASE)
#define TIM3 ((TIM_TypeDef *) TIM3_BASE)
...
по этим указателям как раз и происходит обращение к структуре, когда вы пишете
TIM2-›CR1.
Затем для каждой периферии пишутся структуры, то есть тупо порядок и названия регистров, начиная от первого:
typedef struct
{
__IO uint16_t CR1; /*!‹ TIM control register 1, Address offset: 0x00 */
uint16_t RESERVED0; /*!‹ Reserved, 0x02 */
__IO uint16_t CR2; /*!‹ TIM control register 2, Address offset: 0x04 */
uint16_t RESERVED1; /*!‹ Reserved, 0x06 */
__IO uint16_t SMCR; /*!‹ TIM slave mode control register, Address offset: 0x08 */
....
} TIM_TypeDef;
вот оно и есть, имя типа TIM_TypeDef для регистров таймеров.
Суть структур в том, что например у всех таймеров или у всех портов порядок и состав регистров - в принципе то одинаковый. Значит, можно существенно сократить описательный текст, создав как бы один шаблон с названиями, а конкретный таймер или порт выбирать через обращение к адресу структуры. Конечно не у всех таймеров в железе будут все регистры (TIM6 отличается по составу от TIM1), но это не такая уж беда для чисто текстового описания.
Таким образом, запись
TIM2-›CR1 = TIM_CR1_CEN;
если её расшифровать полностью до самого начала, вычислив конкретный адрес и убрав структуру, получится
*(uint16_t *)0x40000000 = 0x001;
а запись
GPIOA-›ODR = 2;
обращается в
*(uint16_t *)0x40020014 = 0x0002;
Такие вот дела. По большому счету, в качестве особо изощренного извращения, вы можете игнорировать текстовые имена в заголовочнике и прямо так и писать цифрами:
*(uint16_t *)0x40020014 = 0x0002; результат будет тот же
)))) Языку Си это не противоречит, ибо это есть "обращение по адресу и присвоение значения".
Ну и еще там в заголовочнике примерно аналогично прописаны номера прерываний, через энумератор enum. Аналогично типу структур регистров, только вместо порядка регистров идут текстовые имена и численные номера прерываний. Так же, аналогично, через тип IRQn_Type, который используется в другом файле, для функций подобных NVIC_EnableIRQ(DMA1_Stream0_IRQn). То есть, в эту функцию передается просто число 11, соответствующее значению DMA1_Stream0_IRQn в структуре. Энумераторы отличаются от структуры тем, что тексту можно присвоить численное значение. Близко к понятию #define, но немного отличается.
Вот вобщем то и всё, весь заголовочник и разобран.