A STMicroelectronics fornece, além da biblioteca HAL, a biblioteca LL (Low-Level Library), que é uma alternativa de nível mais baixo para acesso direto ao hardware. Neste artigo, vamos entender as principais diferenças entre a LL e a HAL e aplicar a LL em um exemplo simples para controle de sequências de LEDs.
Biblioteca LL (Low-Level Library) e Suas Diferenças em Relação à HAL
A biblioteca LL (Low-Level Library) opera em um nível mais próximo do hardware, oferecendo controle direto sobre os registradores dos periféricos do microcontrolador. A LL é recomendada para cenários de aplicação com requisitos de alto desempenho, como sistemas de controle em tempo real. Enquanto a biblioteca HAL é adequada para desenvolvimento rápido e design de protótipos.
Vantagens da Biblioteca LL
- Desempenho: Por trabalhar diretamente com os registradores, os drivers LL oferecem maior velocidade de execução.
- Eficiência de Memória: As funções da LL demandam um espaço de memória consideravelmente menor em comparação à HAL, sendo adequada para dispositivos com recursos limitados.
- Controle Preciso: Os drivers fornecidos pelo LL correspondem diretamente às funcionalidades dos periféricos descritos no manual de referência. Eles não adicionam camadas de abstração, permitindo um controle mais detalhado e que desenvolvedores otimizem o comportamento de seus sistemas conforme as necessidades específicas.
- Operações Atômicas: Cada operação altera diretamente o conteúdo dos registradores periféricos. Não utilizam variáveis internas ou memória adicional para armazenar estados, contadores ou ponteiros de dados.
Desvantagens da Biblioteca LL
- Complexidade e Curva Aprendizado: O uso dos drivers da biblioteca LL é relativamente difícil e requer um certo entendimento de controle de hardware.
- Não Portátil: A portabilidade das funções da biblioteca LL é baixa. Diferentes modelos de chips STM32 podem ter diferentes versões das funções da biblioteca LL, que precisam ser consultadas de acordo com o modelo do chip.
- Sem Suporte a Alguns Periféricos: Não inclui suporte para periféricos mais complexos ou que demandam uma pilha de software robusta, como USB.
Comparação com a Biblioteca HAL
| Característica | LL (Low-Layer) | HAL (Hardware Abstraction Layer |
| Nível de controle | Acesso direto aos registradores | Nível alto de abstração |
| Complexidade de uso | Maior | menor |
| Portabilidade | Menor | Maior |
| Consumo Memória | Menor | Maior |
| Desempenho | Alto | Moderado |
| Aplicação | Aplicações de alto desempenho | Prototipagem e desenvolvimento rápido |
Controle de Sequência de LEDs com Botão usando HAL e LL
O projeto consiste em usar a Franzininho C0 para alternar a piscagem de LEDs entre diferentes padrões quando um botão for pressionado. O objetivo é observar a diferença de consumo de memória e complexidade de implementação entre HAL e LL.
Pinout Franzininho C0
Será utilizado o LED1, LED2 e BOTÃO que estão conectados, respectivamente, aos pinos PB6, PB7 e PA8.
Lógica esperada no Funcionamento Projeto
- Quando o microcontrolador se inicializa, os LEDs começam desligados.
- Ao pressionar o botão, o padrão de piscagem dos LEDs muda. Cada vez que o botão é pressionado, o padrão avança, retornando ao padrão inicial após o último.
- LED 1 aceso.
- LED 2 aceso.
- Alternar LEDs piscando
- Todos os LEDs acesos.
Configuração CubeMX para HAL
- Crie um novo projeto para a placa “STM32C011F6P6”.
- Entre na página de configurações de relógio e ajuste HCLK para 48 MHz. Volte para a página de configuração dos pinos, selecione “Trace and Debug” e habilite “Serial Wire”.
- Selecione os pinos “PB6” e “PB7” e ajuste ambos como “Gpio_Output”. Depois, renomeie a Label como LED1 e LED2.
- Depois, selecione o pino “PA8” como “Gpio_Input” e renomeie a Label para “Botao”.
- Por fim, clique na aba “Project” e escolha a opção “Generate Code”.
Código utilizando HAL
Ao gerar o código você terá a seguinte estrutura:
Observe a estrutura e o código criado no arquivo ‘main.c’. Note que todas as funções, como a inicialização da GPIO, utilizam os drivers da HAL.
No arquivo ‘main.c‘, modifique a função int main com o seguinte código:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
static uint8_t estado_controle = 0; // Estado inicial
static uint8_t botao_pressionado = 0; // Estado inicial
static uint32_t ultimo_tempo = 0; // Para debounce
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// Lê o estado atual do botão
if (HAL_GPIO_ReadPin(BOTAO_GPIO_Port, BOTAO_Pin) == GPIO_PIN_RESET) {
// Verifica se o botão estava previamente liberado
if (!botao_pressionado && (HAL_GetTick() - ultimo_tempo > 70)) {
// Avança o estado
estado_controle++;
if (estado_controle > 4) {
estado_controle = 1;
}
// Atualiza o tempo do último acionamento
ultimo_tempo = HAL_GetTick();
// Marca o botão como pressionado
botao_pressionado = 1;
}
} else {
// Reseta o estado do botão para "não pressionado" quando liberado
botao_pressionado = 0;
}
// Controle dos LEDs com base no estado do controle
switch (estado_controle) {
case 1:
// LED1 aceso, LED2 apagado
HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_RESET);
break;
case 2:
// LED2 aceso, LED1 apagado
HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_SET);
break;
case 3:
// Alternando LEDs
HAL_GPIO_TogglePin(GPIOB, LED1_Pin | LED2_Pin);
HAL_Delay(500); // Alternância a cada 500 ms
break;
case 4:
// Ambos LEDs acesos
HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_SET);
break;
default:
break;
}
}
/* USER CODE END 3 */
}
Basicamente, este código inicializa o hardware (HAL), configura o clock do sistema e os periféricos GPIO. Em seguida, monitora o botão utilizando as funções HAL_GPIO_ReadPin e HAL_GetTick para implementar a lógica de debounce com um intervalo de 70 ms. Com base no estado atual da variável estado_controle, os LEDs são controlados através das funções HAL_GPIO_WritePin e HAL_GPIO_TogglePin, alternando entre diferentes padrões.
Build e Gravação
Após o build, abra a ferramenta Build Analyzer.
Essa ferramenta fornece uma visão do uso de memória do projeto. Com o nosso projeto, utilizando drivers da HAL, foi registrado um uso de 14,53% da memória flash.
Para realizar a gravação do código no microcontrolador, você pode utilizar um ST-LINK diretamente pela IDE, clicando em “Run” na barra de ferramentas. Alternativamente, é possível usar o STM32CubeProgrammer com um cabo USB. Para um guia detalhado sobre o uso do STM32CubeProgrammer, consulte o tutorial disponível em:
Configuração CubeMX para LL
Para utilizar os drivers LL (Low Layer) em vez dos HAL (Hardware Abstraction Layer), siga os passos abaixo:
- Abra o arquivo de configuração do projeto (.ioc) no STM32CubeMX.
- Todas as configurações previamente realizadas na configuração anterior serão mantidas.
- No menu, entre em Project Manager > Advanced Settings.
- Localize a seção de configuração dos drivers e altere o padrão de HAL para LL para os periféricos desejados.
- Após realizar essa alteração, gere novamente o código clicando em Generate Code. O código gerado passará a utilizar os drivers LL em vez dos HAL.
Código utilizando LL
A nova estrutura do projeto utilizará drivers LL.
E o novo código que deve ser usado dentro do arquivo “main.c” em int main deverá seguir as funções da LL:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
/* SysTick_IRQn interrupt configuration */
NVIC_SetPriority(SysTick_IRQn, 3);
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
static uint8_t estado_controle = 0; // Estado inicial
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if (LL_GPIO_IsInputPinSet(BOTAO_GPIO_Port , BOTAO_Pin) == 0) {
LL_mDelay(100); // Espera pelo tempo de debounce
// Verifica novamente se o botão ainda está pressionado após o delay
if (LL_GPIO_IsInputPinSet(BOTAO_GPIO_Port , BOTAO_Pin) == 0) {
estado_controle++;
if (estado_controle > 4) {
estado_controle = 1;
}
}
}
// Controle dos LEDs com base no estado do controle
switch (estado_controle) {
case 1:
// LED1 aceso, LED2 apagado
LL_GPIO_SetOutputPin(LED1_GPIO_Port, LED1_Pin);
LL_GPIO_ResetOutputPin(LED2_GPIO_Port, LED2_Pin);
break;
case 2:
// LED2 aceso, LED1 apagado
LL_GPIO_SetOutputPin(LED2_GPIO_Port, LED2_Pin);
LL_GPIO_ResetOutputPin(LED1_GPIO_Port, LED1_Pin);
break;
case 3:
// Alternando LEDs
LL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
LL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
LL_mDelay(500); // Alternância a cada 500 ms
break;
case 4:
// Ambos LEDs acesos
LL_GPIO_SetOutputPin(LED1_GPIO_Port, LED1_Pin);
LL_GPIO_SetOutputPin(LED2_GPIO_Port, LED2_Pin);
break;
default:
break;
}
}
/* USER CODE END 3 */
}
Assim como o código anterior, este também configura o relógio do sistema e inicializa os periféricos GPIO, mas utiliza a biblioteca LL (Low Layer). O programa realiza a leitura do botão e controla os LEDs de acordo com o estado de controle. Para a leitura de entradas, é usada a função LL_GPIO_IsInputPinSet.
Ao contrário da HAL, que utiliza uma única função para alterar o nível lógico de um pino, na LL são usadas funções específicas: LL_GPIO_SetOutputPin para definir um nível lógico alto e LL_GPIO_ResetOutputPin para um nível lógico baixo.
Outro ponto importante é que, na HAL, existe a função HAL_GetTick, que retorna o tempo em milissegundos desde a inicialização do microcontrolador. Nos drivers LL, essa função não está disponível e precisaria ser implementada de outra forma. Como este é um tutorial introdutório, foi utilizado um LL_mDelay como forma de contornar a ausência dessa função para implementar o debounce do botão. No entanto, destaco que essa abordagem não é a solução ideal para aplicações reais.
Build e Gravação
Realize o mesmo processo anterior para o build. Ao usar a biblioteca LL teremos uma redução na porcentagem do uso da memória flash. De 14,53% passou para 8.91%, uma diminuição de quase 40%.
Funcionamento final
Em ambos os códigos, o funcionamento deve ser similar ao demonstrado no vídeo abaixo.
Conclusão
Ao longo deste artigo, conhecemos as principais diferenças entre as bibliotecas HAL (Hardware Abstraction Layer) e LL (Low-Level Library) da STMicroelectronics e demonstramos a implementação de um projeto prático de controle de LEDs usando ambas as bibliotecas. Analisamos o impacto no consumo de memória e a complexidade de implementação.
A biblioteca HAL, com seu nível mais alto de abstração, permite um desenvolvimento mais rápido e fácil, adequada para prototipagem e projetos onde o tempo de desenvolvimento é importante. Por outro lado, a biblioteca LL oferece um controle mais detalhado sobre o hardware, proporcionando um desempenho superior e um uso reduzido de memória.
A partir da implementação do exemplo de controle de LEDs, foi possível observar uma diminuição significativa no uso de memória flash ao usar a biblioteca LL. Enquanto a solução baseada em HAL registrou um uso de 14,53% da memória, a abordagem com LL reduziu esse consumo para 8,91%, representando uma economia de quase 40% no uso de memória. Essa redução pode ser útil para dispositivos com recursos limitados.
A compreensão de ambas as bibliotecas é importante, pois permite que o projetista escolha a melhor a depender do projeto.
Desafio
Busque nas referências da LL (low-layer drivers) e tente reproduzir os seguintes exemplos utilizando os drivers da LL.
- Interrupções Externas e USART: https://embarcados.com.br/aprendendo-a-trabalhar-com-interrupcoes-externas-e-usart-na-franzininho-c0/
- ADC: https://embarcados.com.br/trabalhando-com-adc-no-franzininho-c0-no-stm32cubeide/
- I2C: https://embarcados.com.br/configuracao-da-interface-i2c-na-franzininho-c0-para-utilizacao-de-display-oled/
Referências
Description of STM32C0 HAL and LL (low-layer drivers): https://www.st.com/resource/en/user_manual/um3029-description-of-stm32c0-hal-and-lowlayer-drivers-stmicroelectronics.pdf







Bom dia e um feliz anos novo
esta tendo uma falha na compilação usando RTOS, na parta threadx, no include tx_api.h
na seguinte macro #define tx_thread_create(t,n,e,i,s,l,p,r,c,a) _txe_thread_create((t),(n),(e),(i),(s),(l),(p),(r),(c),(a),(sizeof(TX_THREAD)))
falha: incompatible type for argument 3 of ‘_txe_thread_create’
parece que esta tudo certo, mas não consigo encontrar a solução
Obrigado
Olá, tudo bem? Espero que tenha conseguido resolver o problema, mas caso não, primeiro verifique se ThreadX foi habilitado (No CubeMx, em “Middleware and Software,” procure por “THREADX,” e habilite a opção “Core.”). Depois, verifique se os parâmetros que estão sendo passados em tx_thread_create foram declarados. Lembrando que o tx_thread_create segue UINT tx_thread_create( TX_THREAD *thread_ptr, // Ponteiro para a estrutura da thread CHAR *name_ptr, // Nome descritivo da thread VOID (*entry_function)(ULONG), // Ponteiro para a função da thread ULONG entry_input, // Parâmetro passado para a função de entrada VOID *stack_start, // Ponteiro para o início da stack da thread ULONG… Leia mais »
boa tarde e um feliz ano novo