Acesso seguro para registrar campos em C ++ sem sacrificar a eficiência (usando o CortexM como exemplo)

imagem
Fig. extraído de www.extremetech.com/wp-content/uploads/2016/07/MegaProcessor-Feature.jpg

Boa saúde a todos!

Em um artigo anterior, examinei a questão do acesso a registros de um microcontrolador com um núcleo CortexM em C ++ e mostrei soluções simples para alguns dos problemas.

Hoje, quero mostrar a idéia de como tornar seguro o acesso ao registrador e seus campos sem sacrificar a eficiência, usando classes C ++ geradas a partir de arquivos SVD.

Todos que você está interessado, bem-vindo ao gato. Haverá muito código.

1. Introdução


Em um artigo do C ++ Hardware Register Access Redux , Ken Smith mostrou como trabalhar com segurança e eficiência com os registradores, e até mostrou com github.com/kensmith/cppmmio como exemplo.
Então, várias pessoas desenvolveram essa idéia, por exemplo, Niklas Hauser fez uma revisão maravilhosa e sugeriu várias outras maneiras de acessar registros com segurança.

Algumas dessas idéias já foram implementadas em várias bibliotecas, em particular no modm . Para sempre, você pode usar todas essas frases na vida real. Porém, no momento do desenvolvimento dessas bibliotecas, as descrições de periféricos e registros começaram a ser padronizadas e, portanto, algumas coisas foram feitas com o objetivo de que o principal trabalho de descrição de registros fosse com o programador. Além disso, algumas soluções não são eficazes em termos de recursos de código e microcontrolador.

Hoje, todo fabricante de um microcontrolador ARM fornece uma descrição de todos os registros no formato SVD. Os arquivos de cabeçalho podem ser gerados a partir dessas descrições, portanto, é possível criar não apenas uma descrição simples dos registros, mas uma descrição mais complexa, mas ao mesmo tempo, que aumentará a confiabilidade do seu código. E é ótimo que o arquivo de saída possa estar em qualquer idioma, C, C ++ ou até D

Mas vamos entender o que geralmente é um acesso seguro aos registros e por que é necessário. A explicação pode ser mostrada em exemplos sintéticos simples, provavelmente improváveis, mas bem possíveis:

int main(void) { //      GPIOA //,    AHB1ENR RCC->APB1ENR |= RCC_AHB1ENR_GPIOAEN ; //    ,      RCC->AHB1ENR = RCC_AHB1ENR_GPIOAEN; //,  TIM1    APB2 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB2ENR_TIM1EN; // - ,        . auto result = GPIOA->BSRR ; if (result & GPIO_BSRR_BS1) { //do something } //-     .   ... GPIOA->IDR = GPIO_IDR_ID5 ; } 

Todos esses casos são possíveis na prática, e eu definitivamente assisti algo assim dos meus alunos. Seria ótimo se você pudesse proibir cometer tais erros.

Também me parece que é muito mais agradável quando o código parece limpo e não precisa de comentários. Por exemplo, mesmo se você conhece bem o microcontrolador STM32F411, nem sempre é possível entender o que está acontecendo neste código:

 int main() { uint32 temp = GPIOA->OSPEEDR ; temp &=~ GPIO_OSPEEDR_OSPEED0_Msk ; temp = (GPIO_OSPEEDR_OSPEED0_0 | GPIO_OSPEEDR_OSPEED0_1) ; GPIOA->OSPEEDR = temp; } 

Nenhum comentário aqui não pode fazer. O código define a frequência de operação da porta GPIOA.0 para o máximo (esclarecimento do mctMaks : de fato, esse parâmetro afeta o tempo de subida da frente (ou seja, sua inclinação)) e significa que a porta pode processar um sinal digital normalmente em um determinado (VeryLow \ Low \ Medium \ High) frequência).

Vamos tentar nos livrar dessas deficiências.

Registrar abstração


Primeiro de tudo, você precisa descobrir qual é o registro do ponto de vista do programador e do programa.

O registro possui um endereço, comprimento ou tamanho, modo de acesso: alguns registros podem ser gravados, outros podem ser lidos e a maioria pode ser lida e gravada.

Além disso, o registro pode ser representado como um conjunto de campos. Um campo pode consistir em um ou vários bits e está localizado em qualquer lugar do registro.

Portanto, as seguintes características de campo são importantes para nós: comprimento ou tamanho ( largura ou tamanho ), deslocamento desde o início do registro ( deslocamento ) e valor.

Os valores do campo são o espaço de todas as quantidades possíveis que o campo pode ocupar e depende do comprimento do campo. I.e. se o campo tiver um comprimento de 2, haverá 4 valores possíveis do campo (0,1,2,3). Assim como o registro, os campos e os valores dos campos têm um modo de acesso (leitura, gravação, leitura e gravação)

Para tornar mais claro, vamos usar o registro TIM1 CR1 do microcontrolador STM32F411. Esquematicamente, fica assim:

imagem

  • Bit 0 CEN: Ativar Contador
    0: Contador ativado: Desativado
    1: Contador desativado: Ativar
  • UDIS Bit 1: Ativar / Desativar Evento UEV
    0: Evento UEV Ativado: Ativar
    1: evento UEV desativado: desativar
  • URS Bit 2: Selecionar Fontes de Geração de Eventos UEV
    0: UEV é gerado ao transbordar ou ao definir o UG: Qualquer bit
    1: UEV é gerado apenas no estouro: Estouro
  • Bit 3 OPM: Operação Única
    0: O cronômetro continua a contar mais após o evento UEV: ContinueAfterUEV
    1: O cronômetro para após o evento UEV : StopAfterUEV
  • Bit 4 DIR: Direção da Contagem
    0: Conta direta: Upcounter
    1: Contagem regressiva : Downcounter
  • Bit 6: 5 CMS: modo de alinhamento
    0: Modo de alinhamento 0: CenterAlignedMode0
    1: Modo de alinhamento 1: CenterAlignedMode1
    2: Modo de alinhamento 2: CenterAlignedMode2
    3: Modo de alinhamento 3: CenterAlignedMode3
  • Bit 7 APRE: modo de pré-carregamento para registro de ARR
    0: O registro TIMx_ARR não é armazenado em buffer: ARRNotBuffered
    1: O registro TIMx_ARR não é armazenado em buffer: ARRBuffered
  • Bit 8: 9 CKD: Divisor de Relógio
    0: tDTS = tCK_INT: ClockDevidedBy1
    1: tDTS = 2 * tCK_INT: ClockDevidedBy2
    2: tDTS = 4 * tCK_INT: ClockDevidedBy4
    3: Reservado: Reservado

Aqui, por exemplo, CEN é um campo de 1 bit com um deslocamento de 0 em relação ao início do registro. E Ativar (1) e Desativar (0) são seus valores possíveis.

Não vamos nos concentrar no que cada campo deste registro é especificamente responsável; é importante para nós que cada campo e valor de campo possua um nome que carregue uma carga semântica e da qual possamos entender em princípio o que ele faz.

Nós devemos ter acesso ao registro e ao campo e seu valor. Portanto, de uma forma muito aproximada, a abstração do registro pode ser representada pelas seguintes classes:

imagem

Além das classes, também é importante para nós que os registros e campos individuais tenham determinadas propriedades, o registro tenha um endereço, tamanho, modo de acesso (somente leitura, somente gravação ou ambos).
O campo possui tamanho, deslocamento e também modo de acesso. Além disso, o campo deve conter um link para o registro ao qual ele pertence.

O valor do campo deve ter um link para o campo e um atributo adicional - o valor.

Portanto, em uma versão mais detalhada, nossa abstração ficará assim:



Além dos atributos, nossa abstração deve ter métodos de modificação e acesso. Por simplicidade, nos restringimos aos métodos de instalação / gravação e leitura.

imagem

Quando decidimos a abstração do caso, precisamos verificar como essa abstração corresponde ao que é descrito no arquivo SVD.

Arquivo Descrição da Visualização do Sistema (SVD)


O formato de descrição de apresentação do sistema CMSIS (CMSIS-SVD) é uma descrição formal dos registros do microcontrolador com base no processador ARM Cortex-M. As informações contidas nas descrições da representação do sistema praticamente correspondem aos dados nos manuais de referência para dispositivos. A descrição dos registradores nesse arquivo pode conter informações de alto nível e a finalidade de um único bit do campo no registrador.

Esquematicamente, os níveis de detalhes das informações em um arquivo desse tipo podem ser descritos pelo seguinte esquema, obtido no site da Keil :

imagem

Descrição Os arquivos SVD são fornecidos pelos fabricantes e são usados ​​durante a depuração para exibir informações sobre o microcontrolador e os registradores. Por exemplo, o IAR os utiliza para exibir informações no painel Exibir-> Registros. Os arquivos estão na pasta Arquivos de Programas (x86) \ IAR Systems \ Embedded Workbench 8.3 \ arm \ config \ debugger.

O Clion do JetBrains também usa arquivos svd para exibir informações de registro durante a depuração.

Você sempre pode fazer o download de descrições dos sites do fabricante. Aqui você pode pegar o arquivo SVD para o microcontrolador STM32F411

Em geral, o formato SVD é um padrão que os fabricantes suportam. Vamos ver quais são os níveis de descrição em SVD.

No total, são distinguidos 5 níveis, nível de dispositivo, nível de microcontrolador, nível de registro, nível de campo, nível de valores enumerados.

  • Nível do dispositivo : a descrição do nível superior da visualização do sistema é o dispositivo. Nesse nível, são descritas as propriedades relacionadas ao dispositivo como um todo. Por exemplo, um nome, descrição ou versão de dispositivo. A unidade endereçável mínima, bem como a profundidade de bits do barramento de dados. Os valores padrão para atributos de registro, como tamanho do registro, valor de redefinição e permissões de acesso, podem ser configurados para todo o dispositivo nesse nível e são implicitamente herdados por níveis mais baixos de descrição.
  • Nível do microcontrolador: A seção CPU descreve o núcleo do microcontrolador e seus recursos. Esta seção é necessária se o arquivo SVD for usado para criar o arquivo de cabeçalho do dispositivo.
  • Camada Periférica : Um dispositivo periférico é uma coleção nomeada de registros. Um dispositivo periférico é mapeado para um endereço base específico no espaço de endereço do dispositivo.
  • Nível de registro : um registro é um recurso programável nomeado que pertence a um dispositivo periférico. Os registros são mapeados para um endereço específico no espaço de endereço do dispositivo. O endereço é relativo ao endereço periférico de base. Além disso, para o registro, o modo de acesso (leitura / gravação) é indicado.
  • Nível do campo : como mencionado acima, os registros podem ser divididos em pedaços de bits de diferentes funcionalidades - campos. Este nível contém os nomes dos campos que são únicos no mesmo registro, seu tamanho, as compensações em relação ao início do registro, bem como o modo de acesso.
  • O nível de valores de campo enumerados : na verdade, esses são denominados valores de campo que podem ser usados ​​por conveniência em C, C ++, D e assim por diante.

De fato, os arquivos SVD são arquivos xml comuns com uma descrição completa do sistema. Existem conversores de arquivos svd para código C, por exemplo, aqui , que geram estruturas e cabeçalhos compatíveis com C para cada periferia e registro.

Há também um analisador de arquivo SVD cmsis-svd, escrito em Phyton, que faz algo como desserializar dados de um arquivo para objetos de classe Phython, que são convenientemente usados ​​em seu programa de geração de código.

Um exemplo da descrição do registro do microcontrolador STM32F411 pode ser visto sob o spoiler:

Temporizador CR1 de registro de exemplo TIM1
 <peripheral> <name>TIM1</name> <description>Advanced-timers</description> <groupName>TIM</groupName> <baseAddress>0x40010000</baseAddress> <addressBlock> <offset>0x0</offset> <size>0x400</size> <usage>registers</usage> </addressBlock> <registers> <register> <name>CR1</name> <displayName>CR1</displayName> <description>control register 1</description> <addressOffset>0x0</addressOffset> <size>0x20</size> <access>read-write</access> <resetValue>0x0000</resetValue> <fields> <field> <name>CKD</name> <description>Clock division</description> <bitOffset>8</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>ARPE</name> <description>Auto-reload preload enable</description> <bitOffset>7</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CMS</name> <description>Center-aligned mode selection</description> <bitOffset>5</bitOffset> <bitWidth>2</bitWidth> </field> <field> <name>DIR</name> <description>Direction</description> <bitOffset>4</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>OPM</name> <description>One-pulse mode</description> <bitOffset>3</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>URS</name> <description>Update request source</description> <bitOffset>2</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>UDIS</name> <description>Update disable</description> <bitOffset>1</bitOffset> <bitWidth>1</bitWidth> </field> <field> <name>CEN</name> <description>Counter enable</description> <bitOffset>0</bitOffset> <bitWidth>1</bitWidth> </field> </fields> </register> <register> 


Como você pode ver, há todas as informações necessárias para nossa abstração, exceto a descrição dos valores de bits específicos dos campos.

Nem todos os fabricantes desejam dedicar tempo a uma descrição completa de seu sistema; portanto, como você pode ver, a ST não queria descrever os valores de campo e transferiu esse ônus aos programadores de clientes. Mas a TI cuida de seus clientes e descreve o sistema completamente, incluindo descrições de valores de campo.

O texto acima mostra que o formato da descrição do SVD é muito consistente com a abstração de nosso caso. O arquivo contém todas as informações necessárias para descrever completamente o registro.

Implementação


Registre-se


Agora que fizemos uma abstração do registro e temos uma descrição dos registros na forma de svd dos fabricantes ideal para essa abstração, podemos ir diretamente para a implementação.

Nossa implementação deve ser tão eficaz quanto o código C e fácil de usar. Eu gostaria que o acesso aos registros parecesse o mais claro possível, por exemplo, assim:

  if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(10_ms) ; TIM1::CR1::CEN::Enable::Set() ; } 

Lembre-se de que, para acessar o endereço de registro inteiro, você precisa usar reinterpret_cast:

 *reinterpret_cast<volatile uint32_t *>(0x40010000) = (1U << 5U) ; 

A classe de registro já foi descrita acima, deve ter um endereço, tamanho e modo de acesso, além de dois métodos Get() e Set() :

 //      template<uint32_t address, size_t size, typename AccessMode> struct RegisterBase { static constexpr auto Addr = address ; using Type = typename RegisterType<size>::Type ; // Set     , //     __forceinline template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<WriteMode, T>::value>> inline static void Set(Type value) { *reinterpret_cast<volatile Type *>(address) = value ; } // Get    , //    ,    __forceinline template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<ReadMode, T>::value>> inline static Type Get() { return *reinterpret_cast<volatile Type *>(address) ; } } ; 

Passamos o endereço, o comprimento do registro e o modo de acesso aos parâmetros do modelo (isso também é uma classe). Usando o mecanismo SFINAE , ou seja, a meta-função enable_if , "jogaremos fora" as funções de acesso Set() ou Get() para registros que não devem suportá-los. Por exemplo, se o registro for somente leitura, ReadMode tipo ReadMode para o parâmetro do modelo, enable_if verificará se o acesso é o sucessor do ReadMode e, caso contrário, criará um erro controlado (o tipo T não pode ser exibido) e o compilador não incluirá o método Set() para esse registro. O mesmo vale para um registro destinado apenas à gravação.

Para controle de acesso, usaremos as classes:

 //    struct WriteMode {}; struct ReadMode {}; struct ReadWriteMode: public WriteMode, public ReadMode {}; 

Os registros vêm em tamanhos diferentes: 8, 16, 32, 64 bits. Para cada um deles, definimos nosso tipo:

Tipo de registros, dependendo do tamanho
 template <uint32_t size> struct RegisterType {} ; template<> struct RegisterType<8> { using Type = uint8_t ; } ; template<> struct RegisterType<16> { using Type = uint16_t ; } ; template<> struct RegisterType<32> { using Type = uint32_t ; } ; template<> struct RegisterType<64> { using Type = uint64_t ; } ; 


Depois disso, para o timer TIM1, você pode definir o registro CR1 e, por exemplo, o registro EGR desta maneira:

 struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { } struct EGR : public RegisterBase<0x40010014, 32, WriteMode> { } } int main() { TIM1::CR1::Set(10) ; auto reg = TIM1::CR1::Get() ; // ,     reg = TIM1::EGR::Get() } 

Como o compilador exibe o método Get() apenas para registradores nos quais o modo de acesso é herdado do ReadMode e os métodos Set() para registradores nos quais o modo de acesso é herdado do WriteMode , no caso de uso incorreto dos métodos de acesso, você receberá um erro no estágio de compilação. E se você usar ferramentas de desenvolvimento modernas, como o Clion, mesmo no estágio de codificação, você verá um aviso do analisador de código:

imagem

Bem, o acesso aos registradores agora se tornou mais seguro, nosso código não permite que você faça coisas inaceitáveis ​​para esse registro, mas queremos ir além e nos referir não ao registro inteiro, mas a seus campos.

Fields


O campo, em vez do endereço, tem um valor de deslocamento relativo ao início do registro. Além disso, para saber o endereço ou tipo para o qual o valor do campo deve ser trazido, ele deve ter um link para o registro:

 //        template<typename Reg, size_t offset, size_t size, typename AccessMode> struct RegisterField { using RegType = typename Reg::Type ; using Register = Reg ; static constexpr RegType Offset = offset ; static constexpr RegType Size = size ; using Access = AccessMode ; template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<WriteMode, T>::value>> static void Set(RegType value) { assert(value < (1U << size)) ; //CriticalSection cs ; //    RegType newRegValue = *reinterpret_cast<RegType *>(Register::Address) ; //       newRegValue &= ~ (((1U << size) - 1U) << offset); //    newRegValue |= (value << offset) ; //      *reinterpret_cast<RegType *>(Reg::Address) = newRegValue ; } __forceinline template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<ReadMode, T>::value>> inline static RegType Get() { return ((*reinterpret_cast<RegType *>(Reg::Address)) & (((1U << size) - 1U) << offset)) >> offset ; } }; 

Depois disso, já é possível fazer o seguinte:

 struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = RegisterField<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = RegisterField<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = RegisterField<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = RegisterField<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = RegisterField<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = RegisterField<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = RegisterField<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = RegisterField<TIM1::CR1, 0, 1, ReadWriteMode> ; } } int main() { //   CR1  9   1,  8  0 TIM1::CR1::CKD::Set(2U) ; auto reg = TIM1::CR1::CEN::Get() ; } 

Embora tudo pareça muito bom no geral, ainda não está totalmente claro o que significa TIM1::CR1::CKD::Set(2) , o que significam os dois mágicos passados ​​para a função Set() ? E o que o número retornado pelo TIM1::CR1::CEN::Get() ?

Vá diretamente para os valores do campo.

Valor do campo


A abstração de um valor de campo também é essencialmente um campo, mas capaz de aceitar apenas um estado. Atributos são adicionados à abstração do campo - o valor real e um link para o campo. O método Set() para definir o valor do campo é idêntico ao método Set() para definir o campo, exceto que o valor em si não precisa ser passado para o método, é conhecido antecipadamente, apenas precisa ser definido. Mas o método Get() não faz nenhum sentido; em vez disso, é melhor verificar se esse valor está definido ou não, substitua esse método pelo método IsSet() .

 //        template<typename Field, typename Field::Register::Type value> struct FieldValueBase { using RegType = typename Field::Register::Type ; template<typename T = typename Field::Access, class = typename std::enable_if_t<std::is_base_of<WriteMode, T>::value>> static void Set() { RegType newRegValue = *reinterpret_cast<RegType *>(Field::Register::Address) ; newRegValue &= ~ (((1U << Field::Size) - 1U) << Field::Offset); newRegValue |= (value << Field::Offset) ; *reinterpret_cast<RegType *>(Field::Register::Address) = newRegValue ; } __forceinline template<typename T = typename Field::Access, class = typename std::enable_if_t<std::is_base_of<ReadMode, T>::value>> inline static bool IsSet() { return ((*reinterpret_cast<RegType *>(Field::Register::Address)) & static_cast<RegType>(((1U << Field::Size) - 1U) << Field::Offset)) == (value << Field::Offset) ; } }; 

O campo de registro agora pode ser descrito por um conjunto de seus valores:

Valores dos campos de registro CR1 do timer TIM1
 template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CKD_Values: public RegisterField<Reg, offset, size, AccessMode> { using DividedBy1 = FieldValue<TIM_CR_CKD_Values, 0U> ; using DividedBy2 = FieldValue<TIM_CR_CKD_Values, 1U> ; using DividedBy4 = FieldValue<TIM_CR_CKD_Values, 2U> ; using Reserved = FieldValue<TIM_CR_CKD_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_ARPE_Values: public RegisterField<Reg, offset, size, AccessMode> { using ARRNotBuffered = FieldValue<TIM_CR_ARPE_Values, 0U> ; using ARRBuffered = FieldValue<TIM_CR_ARPE_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CMS_Values: public RegisterField<Reg, offset, size, AccessMode> { using CenterAlignedMode0 = FieldValue<TIM_CR_CMS_Values, 0U> ; using CenterAlignedMode1 = FieldValue<TIM_CR_CMS_Values, 1U> ; using CenterAlignedMode2 = FieldValue<TIM_CR_CMS_Values, 2U> ; using CenterAlignedMode3 = FieldValue<TIM_CR_CMS_Values, 3U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_DIR_Values: public RegisterField<Reg, offset, size, AccessMode> { using Upcounter = FieldValue<TIM_CR_DIR_Values, 0U> ; using Downcounter = FieldValue<TIM_CR_DIR_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_OPM_Values: public RegisterField<Reg, offset, size, AccessMode> { using ContinueAfterUEV = FieldValue<TIM_CR_OPM_Values, 0U> ; using StopAfterUEV = FieldValue<TIM_CR_OPM_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_URS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Any = FieldValue<TIM_CR_URS_Values, 0U> ; using Overflow = FieldValue<TIM_CR_URS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_UDIS_Values: public RegisterField<Reg, offset, size, AccessMode> { using Enable = FieldValue<TIM_CR_UDIS_Values, 0U> ; using Disable = FieldValue<TIM_CR_UDIS_Values, 1U> ; } ; template <typename Reg, size_t offset, size_t size, typename AccessMode> struct TIM_CR_CEN_Values: public RegisterField<Reg, offset, size, AccessMode> { using Disable = FieldValue<TIM_CR_CEN_Values, 0U> ; using Enable = FieldValue<TIM_CR_CEN_Values, 1U> ; } ; 


Em seguida, o próprio registro CR1 já será descrito da seguinte maneira:

 struct TIM1 { struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR1_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode> ; using ARPE = TIM_CR1_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode> ; using CMS = TIM_CR1_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode> ; using DIR = TIM_CR1_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode> ; using OPM = TIM_CR1_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode> ; using URS = TIM_CR1_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode> ; using UDIS = TIM_CR1_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode> ; using CEN = TIM_CR1_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode> ; } ; } 

Agora você pode definir e ler diretamente o valor do campo de registro: Por exemplo, se desejar habilitar o cronômetro na conta, basta chamar o método Set() no valor Enable do campo CEN do registro CR1 do cronômetro CR1 do cronômetro TIM1: TIM1::CR1::CEN::Enable::Set() ; . No código, ficará assim:

 int main() { if (TIM1::CR1::CKD::DividedBy2::IsSet()) { TIM1::ARR::Set(100U) ; TIM1::CR1::CEN::Enable::Set() ; } } 

Para comparação, a mesma coisa usando o cabeçalho C:
 int main() { if((TIM1->CR1 & TIM_CR1_CKD_Msk) == TIM_CR1_CKD_0) { TIM1->ARR = 100U ; regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CEN_Msk) ; regValue |= TIM_CR1_CEN ; TIM1->CR1 = regValue ; } } 


Assim, as principais melhorias são feitas, podemos ter acesso simples e compreensível ao registro, seus campos e valores. , , , , .

, . , :

 int main() { uint32_t regValue = TIM1->CR1 ; regValue &=~(TIM_CR1_CKD_Msk | TIM_CR1_DIR) ; regValue |= (TIM_CR1_CEN | TIM_CR1_CKD_0 | TIM_CR1_CKD_0) ; TIM1->CR1 = regValue ; } 

Set(...) , , . I.e. :

 int main() { // 1,      Set() TIM1::CR1::Set(TIM1::CR1::DIR::Upcounter, TIM1::CR1::CKD::DividedBy4, TIM1::CR1::CEN::Enable) ; // 2,     TIM1::CR1<TIM1::CR1::DIR::Upcounter, TIM1::CR1::CKD::DividedBy4, TIM1::CR1::CEN::Enable>::Set() ; } 

, , , , .

. :

 //    ,          template<uint32_t address, size_t size, typename AccessMode, typename ...Args> class Register { private: ... 

, :

  1. , .
  2. .

constexpr , :

 //    ,          template<uint32_t address, size_t size, typename AccessMode, typename ...Args> class Register { private: // ,    //     . __forceinline template<typename T> static constexpr auto GetIndividualMask() { Type result = T::Mask << T::Offset ; return result ; } // ,    //       . static constexpr auto GetMask() { //       const auto values = {GetIndividualMask<Args>()...} ; Type result = 0UL; for (auto const v: values) { //       result |= v ; } return result ; } //    __forceinline template<typename T> static constexpr auto GetIndividualValue() { Type result = T::Value << T::Offset ; return result ; } static constexpr auto GetValue() { const auto values = {GetIndividualValue<Args>()...}; Type result = 0UL; for (const auto v: values) { result |= v ; } return result ; } }; 

Set() IsSet() :

 //    ,          template<uint32_t address, size_t size, typename AccessMode, typename ...Args> class Register { public: using Type = typename RegisterType<size>::Type; template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<WriteMode, T>::value>> static void Set() { Type newRegValue = *reinterpret_cast<Type *>(address) ; //GetMask()    ,     newRegValue &= ~GetMask() ; //GetValue()    ,     newRegValue |= GetValue() ; //     *reinterpret_cast<Type *>(address) = newRegValue ; } template<typename T = AccessMode, class = typename std::enable_if_t<std::is_base_of<ReadMode, T>::value>> static bool IsSet() { Type newRegValue = *reinterpret_cast<Type *>(address) ; return ((newRegValue & GetMask()) == GetValue()) ; } private: ... 

, :

 int main() { // ,     TIM1::CR1 TIM1::CR1<TIM2::CR1::Enabled, TIM1::CR2::OIS1::OC1OutputIs0>::Set() ; } 

, - , , , , FieldValueBaseType . , FieldValueBaseType :

 template<uint32_t address, size_t size, typename AccessMode, typename FieldValueBaseType, typename ...Args> class Register { private: //     BaseType     FieldValueBaseType,    . __forceinline template<typename T, class = typename std::enable_if_t<std::is_same<FieldValueBaseType, typename T::BaseType>::value>> static constexpr auto GetIndividualMask() { Type result = T::Mask << T::Offset ; return result ; } static constexpr auto GetMask() { const auto values = {GetIndividualMask<Args>()...} ; Type result = 0UL; for (auto const v: values) { result |= v ; } return result ; } //     BaseType     FieldValueBaseType,    . __forceinline template<typename T, class = typename std::enable_if_t<std::is_same<FieldValueBaseType, typename T::BaseType>::value>> static constexpr auto GetIndividualValue() { Type result = T::Value << T::Offset ; return result ; } static constexpr auto GetValue() { const auto values = {GetIndividualValue<Args>()...}; Type result = 0UL; for (const auto v: values) { result |= v ; } return result ; } }; 


, SFINAE , , , , , .

CR1 TIM1, :

 struct TIM1 { struct TIM1CR1Base {} ; struct CR1 : public RegisterBase<0x40010000, 32, ReadWriteMode> { using CKD = TIM_CR_CKD_Values<TIM1::CR1, 8, 2, ReadWriteMode, TIM1CR1Base> ; using ARPE = TIM_CR_ARPE_Values<TIM1::CR1, 7, 1, ReadWriteMode, TIM1CR1Base> ; using CMS = TIM_CR_CMS_Values<TIM1::CR1, 5, 2, ReadWriteMode, TIM1CR1Base> ; using DIR = TIM_CR_DIR_Values<TIM1::CR1, 4, 1, ReadWriteMode, TIM1CR1Base> ; using OPM = TIM_CR_OPM_Values<TIM1::CR1, 3, 1, ReadWriteMode, TIM1CR1Base> ; using URS = TIM_CR_URS_Values<TIM1::CR1, 2, 1, ReadWriteMode, TIM1CR1Base> ; using UDIS = TIM_CR_UDIS_Values<TIM1::CR1, 1, 1, ReadWriteMode, TIM1CR1Base> ; using CEN = TIM_CR_CEN_Values<TIM1::CR1, 0, 1, ReadWriteMode, TIM1CR1Base> ; } ; } 

, , . , , , .

, :

 int main(void) { //     GPIOA //,    AHB1ENR RCC->APB1ENR |= RCC_AHB1ENR_GPIOAEN ; //    ,      RCC->AHB1ENR = RCC_AHB1ENR_GPIOAEN; //,  TIM1    APB2 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB2ENR_TIM1EN; // - ,        . auto result = GPIOA->BSRR ; if (result & GPIO_BSRR_BS1) { //do something } //-     .   ... GPIOA->IDR = GPIO_IDR_ID5 ; 

:

 int main(void) { //     GPIOA // ,   APB1ENR   GPIOAEN RCC::APB1ENR::GPIOAEN::Enable::Set() ; // ,     GPIOA RCC::AHB1ENR::GPIOAEN::Enable::Set() ; // , RCC::APB2ENR::TIM1EN::Enable  //   APB1ENR RCC::APB1ENRPack<RCC::APB1ENR::TIM2EN::Enable, RCC::APB2ENR::TIM1EN::Enable>::Set(); // ,  BSRR    auto result = GPIOA::BSRR::Get() ; // ,  Reset    if (GPIOA::BSRR::BS1::Reset::IsSet()) { //do something } // ,       GPIOA::IDR::IDR5::On::Set() } 

, , .

, , , ?


, ++, , 1 :

:
 int main() { uint32_t res = RCC->AHB2ENR; res &=~ RCC_AHB1ENR_GPIOAEN_Msk ; res |= RCC_AHB1ENR_GPIOAEN ; RCC->AHB2ENR = res ; res = GPIOA->MODER ; res &=~ (GPIO_MODER_MODER5 | GPIO_MODER_MODER4 | GPIO_MODER_MODER1) ; res |= (GPIO_MODER_MODER5_0 | GPIO_MODER_MODER4_0 | GPIO_MODER_MODER1_0) ; GPIOA->MODER = res ; GPIOA->BSRR = (GPIO_BSRR_BS5 | GPIO_BSRR_BS4 | GPIO_BSRR_BS1) ; return 0 ; } 


++:
 int main() { RCC::AHB1ENR::GPIOAEN::Enable::Set() ; GPIOA::MODERPack< GPIOA::MODER::MODER5::Output, GPIOA::MODER::MODER4::Output, GPIOA::MODER::MODER1::Output>::Set() ; GPIOA::BSRRPack< GPIOA::BSRR::BS5::Set, GPIOA::BSRR::BS4::Set, GPIOA::BSRR::BS1::Set>::Write() ; return 0 ; } 


IAR. : :

:

imagem

C++ :

imagem

18 , , .

, :

imagem

13 .

++ :

imagem

: , .

, . , ?


, , . . , . , , . , . SVD , , , .

, , , , enum, . , , .
imagem



, , . , gpioa rcc, :

 #include "gpioaregisters.hpp" //for GPIOA #include "rccregisters.hpp" //for RCC int main() { RCC::AHB1ENR::GPIOAEN::Enable::Set() ; GPIOA::MODER::MODER15::Output::Set() ; GPIOA::MODERPack< GPIOA::MODER::MODER12::Output, GPIOA::MODER::MODER14::Analog >::Set() ; } 

, SVD , , .

, , , SVD , - ST , :

 template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Value0 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Value1 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Value2 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Value3 = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ; 

, Value, - :

 template <typename Reg, size_t offset, size_t size, typename AccessMode, typename BaseType> struct GPIOA_MODER_MODER_Values: public RegisterField<Reg, offset, size, AccessMode> { using Input = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 0U> ; using Output = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 1U> ; using Alternate = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 2U> ; using Analog = FieldValue<GPIOA_MODER_MODER_Values, BaseType, 3U> ; } ; 

.

, ST , 0.

, , enum .

, .

IAR 8.40.1

« Online GDB»

PS: putyavka RegisterField::Get()
Ryppka assert.


Typesafe Register Access in C++
One Approach to Using Hardware Registers in C++
SVD Description (*.svd) Format

Source: https://habr.com/ru/post/pt459642/


All Articles