



Rust é uma linguagem focada em segurança e desempenho que vem crescendo muito nos últimos anos, principalmente nas comunidades de sistemas embarcados, como uma alternativa promissora a C++. Nesse post, vamos dar uma introdução sobre ela, criando um Blink para STM32 bluepill e mostrando as semelhanças e diferenças entre ela e a C++.
Antes de falar de Rust, temos que entender quais as necessidades de um sistema embarcado e como isso se relaciona com o desenvolvimento. Existem alguns tópicos principais que um projeto de sistema embarcado precisa resolver:
C e C++ são linguagens excelentes para sistemas embarcados: são leves, rápidas e com um ótimo controle de baixo nível. Mas essas linguagens têm problemas que podem dificultar a sua vida.
"C faz com que seja muito fácil atirar nos próprios pés. C++ faz com que isso se torne mais difícil, mas quando você consegue, destrói toda a perna." - Bjarne Stroustrup, criador da C++
Essa frase, dita pelo criador da C++, faz referência a um dos maiores problemas de C/C++: o gerenciamento de memória. A linguagem C é feita para ser simples e ter poucas abstrações, o que faz ela ser eficiente. Mas não oferece nenhuma ajuda ao desenvolvedor. Em códigos grandes e complexos, fica muito fácil alguém cometer um erro que vai passar de forma despercebida.
(Curiosidade: os vetores de ataques mais utilizados por hackers para ter acesso a um dispositivo são gerados por problemas no gerenciamento de memória, uma das mais famosas é o buffer overflow)
C++ adiciona um recurso chamado "abstrações de custo zero" para ajudar no desenvolvimento do software sem perda de desempenho. Essas abstrações também são utilizadas na técnica de gerenciamento de memória chamado RAII, que permite gerenciar a memória automaticamente. Porém, C++ também tem problemas:
Rust é uma linguagem de programação desenvolvida pela Mozilla em 2009, focada na eficiência, segurança e confiabilidade. Considerada a linguagem mais amada pela pesquisa da StackOverFlow por 7 anos seguidos, tem ganhado muita popularidade e está crescendo rápido na programação de sistemas. Rust já faz parte do Kernel Linux, Android e até a Espressif, fabricante dos famosos ESP32, já esta criando soluções com ela. Para entender o sucesso de Rust, é importante dar uma breve introdução sobre como ela funciona.
Rust é uma linguagem capaz de realizar todas as funcionalidades do C++, mas sem os problemas associados a ela. Além disso, Rust apresenta um conjunto de recursos modernos, com um dos seus principais objetivos sendo a segurança, especialmente no que se refere a erros comuns de programação que podem levar a vulnerabilidades como vazamento de memória, data-race, etc. Rust utiliza o conceito de Ownership, uma "versão mais sofisticada" do RAII em C++. Essa técnica é uma abstração de custo zero assim como o RAII.
Em Rust, as variáveis são imutáveis por padrão. Ou seja, elas não podem ser modificadas após a sua declaração. Quando um valor é atribuído a uma variável, ela se torna o dona (Owner) desse valor, e cada valor só pode ter um dono por vez. Quando o dono sai de escopo, o valor é automaticamente destruído. Esse valor pode ser emprestado para outros donos, mas apenas um de cada vez, garantindo que a memória seja gerenciada de forma segura. Para garantir essa segurança, Rust possui uma verificação chamada "borrow checker", que verifica em tempo de compilação se há algum problema de propriedade ou empréstimo de valores. Esse recurso permite que Rust gerencie a memória de forma eficiente, sem custo adicional de desempenho.
Outra vantagem é o gerenciador de pacotes Cargo, que permite, com facilidade, a criação e o gerenciamento de projetos. Vamos falar mais sobre ele na hora de criar nosso projeto.
Nesse projeto, vamos montar um ambiente de desenvolvimento de Rust embarcado. Para isso, vamos instalar os seguintes softwares:
Clique em continuar e ele vai começar o download do instalador. Após isso, ele vai abrir a tela de instalação. Nessa tela, selecione a opção "Desenvolvimento para desktop com C++" e verifique, nos detalhes de instalação, se as seguintes caixas estão marcadas:
Clique em "Instalar". Após o fim da instalação, feche o instalador.
Para instalar a linguagem Rust, baixe o instalador "rustup" pelo link: https://www.rust-lang.org/pt-BR/tools/install
Após o download, abra o instalador e essa tela vai aparecer:
Digite 1 e aparte ENTER. A instalação vai começar. Esse processo pode demorar. Não feche o terminal até o termino da instalação. Se tudo ocorrer bem, essa será a mensagem:
Aperte ENTER e o terminal vai fechar automaticamente.
rustup target install thumbv7m-none-eabi
Isso vai instalar o compilador para ARM thumb. Depois disso, execute:
cargo install cargo-embed
Isso vai instalar as ferramentas para upload de código para a placa. Isso pode demorar um pouco. Quando tudo acabar, pode fechar o terminal.
Copie o caminho da pasta "bin" e siga os passos.
Abra o menu Iniciar e pesquise por "Variáveis de ambiente"
Abra e clique em "Variaveis de Ambiente"
Selecione "Path" e clique em editar
Clique em novo. Ele vai abrir uma caixa de texto. Cole o caminho da pasta bin do OpenOCD e aperte em "Ok". Você vai voltar à segunda janela e clicar em "Ok" novamente. Essa janela também vai fechar. Na última, clique em Aplicar.
Clique em "Ok"
Clique em "Próximo"
Clique em "Eu Concordo"
Clique em "Instalar"
Marque a caixa "Add path to environment variable" e clique em "concluir".
(Download projeto completo: https://github.com/RecursiveError/rust-embarcado/releases/download/1.0.1/blink_teste.zip)
Primeiro, vamos criar uma pasta para nosso projeto. Nomes de pastas de projetos em Rust devem conter apenas letras minúsculas, números e underline ("_"). Aqui, vamos chamar de "blink_teste".
Agora, vamos abrir o Visual Studio Code. A primeira coisa que vamos fazer é instalar a extensão "rust-analyzer", seguindo os passos abaixo:
Agora, vamos abrir a pasta do projeto. Para isso, clique em "File" e, depois, "Open Folder". Isso vai abrir o navegador de arquivos. Nele, selecione a pasta que você criou.
Abra um terminal clicando em "Terminal" e, depois, "New terminal". Com o terminal aberto, digite: "cargo init --bin". Isso vai iniciar nosso projeto em Rust.
Após isso, a seguinte estrutura de pastas será gerada:
Agora, temos que configurar o projeto para trabalhar com o STM32. As abstrações para Rust embarcado seguem o esquema:
(fonte: https://docs.rust-embedded.org/book/start/registers.html )
PAC é uma biblioteca para acesso de periféricos geradas a partir dos arquivos SVD. A junção disso com a biblioteca da arquitetura forma as HAL de Rust. Essas HAL são padronizadas pela embedded-hal, o que significa que uma biblioteca feita em cima da embdded-hal vai funcionar em qualquer HAL Rust, independente da arquitetura. Nesse projeto, vamos usar a stm32f1xx_hal. Primeiro, vamos criar um arquivo chamado memory.x:
memory.x são semelhantes aos arquivos ".ld" usados com C++. Dentro de memory.x, cole o seguinte código:
/*----deixe apenas o linker referente a sua placa e remova o resto----*/
/* Linker script for the STM32F103C8xx*/
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
/* Linker script for the STM32F103C6xx*/
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 32K
RAM : ORIGIN = 0x20000000, LENGTH = 10K
}
Não se preocupe em memorizar esse código. Ele é feito para ser copiado e colado, assim como os ".ld". Para utilizar, remova o linker que não é referente a sua placa. Neste projeto, estamos usando o STM32F013C8. Então, no final, o arquivo deve ficar assim:
/* Linker script for the STM32F103C8xx*/
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
(não esqueça de salva o arquivo após as mudanças)
Agora, vamos configurar o compilador para ARM thumb:
Crie uma nova pasta, chamada .cargo. Dentro desta, crie um arquivo chamado config.toml:
dentro de config.toml, cole o seguinte código:
[target.thumbv7m-none-eabi] runner = 'arm-none-eabi-gdb' rustflags = [ "-C", "link-arg=-Tlink.x", ] [build] target = "thumbv7m-none-eabi"Com isso, o ambiente para stm32 está finalizado. Instalando as bibliotecas: Para incluir bibliotecas num projeto Rust, basta adicionar o nome e a versão no arquivo "Cargo.toml" que foi gerado com o comando "cargo init". Nesse projeto vamos usar as seguintes bibliotecas: Cargo.toml
[package] name = "blink_teste" version = "0.1.0" edition = "2021" [dependencies] embedded-hal = "0.2.7" nb = "1" cortex-m = "0.7.6" cortex-m-rt = "0.7.1" panic-halt = "0.2.0" [dependencies.stm32f1xx-hal] version = "0.10.0" #remova '#' em 'features' selecionar a placa correta #STM32F103C8xx #features = ["rt", "stm32f103", "medium"] #STM32F103C6xx #features = ["rt", "stm32f103"]Para utilizar, é necessário remover o '#' localizado atrás de 'features' de acordo com sua placa (não esqueça de salva o arquivo após as mudanças). Essa seleção, feita no memory.x e cargo.toml, é referente à densidade de memória da placa. Caso não esteja usando uma placa STM32 bluepill, a identificação da densidade de memória pode ser feita da seguinte maneira: Normalmente, o segundo caractere após o número do nome do dispositivo é referente à densidade de memória. Por exemplo, em "STM32F103C8T6", o nome do dispositivo é SMT32F103. Então, o segundo caractere após isso é 8. Com isso, é só verificar, nessa tabela, a configuração correta no Cargo.toml:
medium featurehigh featurexl feature// projeto bare-metal portanto sem std e sem main (codigo inicia no entry point)
#![no_std]
#![no_main]
use panic_halt as _; //panic handler padrão
use cortex_m_rt::entry;
use stm32f1xx_hal::{pac, prelude::*};
//declara nosso _start
#[entry]
fn main() -> !{
let cp = cortex_m::Peripherals::take().unwrap(); //perifericos da arquitetura
let dp = pac::Peripherals::take().unwrap(); //perifericos da placa
//incia os perifericos
let mut _afio = dp.AFIO.constrain();
let mut flash = dp.FLASH.constrain();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// define o pino pb9 como saida
let mut gpiob = dp.GPIOB.split();
let mut led = gpiob.pb9.into_push_pull_output(&mut gpiob.crh);
//cria um delay bloqueando com o clock da placa
let mut delay = cp.SYST.delay(&clocks);
loop{
//pisca o led a cada 1seg
led.toggle();
delay.delay_ms(1000u16);
}
}
pode acontecer de você se deparar com o seguinte erro:
Esse é um bug da extensão do Vscode "Rust-analyzer". Ele não identifica que o target se trata de um ambiente bare-metal. Mas, não se preocupe: isso é apenas visual. O projeto ainda compila e funciona normalmente. Apenas ignore esta mensagem.
Para enviar para placa, abra o terminal (na pasta do projeto) e digite o comando:
cargo embed --chip STM32F103C8 --release
Se os drives do STlink e o openOCD foram instalados corretamente, ele vai identificar automaticamente a porta e enviar o código.
A primeira coisa a explicar está logo no início do arquivo:
#![no_std] e #![no_main], essas linhas dizem ao compilador como queremos compilar esse arquivo. Nesse caso, sem a std e sem a main, porque é um código bare-metal, não tem sistema e começa de um endereço predefinido chamado "entry point". Essas coisas também existem no C, mas, nela, você chama passando os argumentos para o compilador.
Seguindo até a "main", podemos ver #[entry] e "!". #[entry] marca a função em que o código inicia (entry point). Isso, em C, fica no arquivo de strartup. "!" indica que a função não pode retornar e pula direto para o panic_halt caso a função retorne.
Nas primeiras linhas da função, podemos ver a separação entre PAC(dp) e Cortex-M(cp) visto na imagem abstração, as linhas abaixo são apenas iniciação de periféricos.
Chegando na parte principal, podemos notar umas semelhanças. Vonfiguramos o clock RCC, definimos o funcionamento do GPIO pelo registrador CRx, criamos um delay baseado no SYST do NVIC, assim como em outras HAL, como a CubeMx, e, por fim, apenas invertemos o valor do GPIO a cada 1seg. Você pode fazer debug de seus projetos embarcados em Rust usando GDB. Primeiro, você precisa compilar o código para debug. Para isso, rode o comando:
cargo embed --chip STM32F103C8
Isso vai compilar e enviar o código de debug para a placa. Agora, no terminal do projeto, digite o comando
arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/blink_teste (blink_teste é o nome do projeto. Em Rust, os binários compilados pelo cargo têm o mesmo nome do projeto)
Agora, em outro terminal (esse não precisa estar na pasta do projeto), digite o seguinte comando:
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
Isso vai fazer o openOCD se conectar com a placa. Então, ele vai abrir uma conexão para GDB na porta 3333
Agora, volte para o GDB e digite o seguinte comando:
target extended-remote localhost:3333
Isso vai fazer a conexão com o openOCD. Pronto! Agora, você tem o GDB para debug de Rust embarcado.
Para sair do GDB e do OpenOCD, digite Ctrl+C.
Usar no terminal pode ser difícil para novos usuário e, infelizmente, o terminal Windows não tem suporte para a interface TUI do GDB. Mas você sabia que nos podemos usar o Visual Studio Code como uma interface interativa para uso do GDB em projetos embarcados? Para isso, vá até a aba de extensões e "instale Cortex-Debug":
(Download do projeto completo[incluindo config de Debug]: https://github.com/RecursiveError/rust-embarcado/releases/download/1.0.1/blink_teste.zip)
Após a instalação, volte para o projeto, crie uma pasta chamada ".vscode" e, dentro dela, dois arquivos chamados "tasks.json" e "launch.json":
Dentro de tasks.json, coloque o seguinte código:
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Cargo build",
"type": "shell",
"command": "cargo",
"args": ["build"],
"problemMatcher": [
"$rustc"
],
"group": "build"
},
{
"label": "Build binary",
"type": "shell",
"command": "arm-none-eabi-objcopy",
"args": [
"--output-target", "binary",
"target/thumbv7m-none-eabi/debug/${workspaceFolderBasename}",
"main.bin",
],
"problemMatcher": [
"$rustc"
],
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": "Cargo build"
}
]
}
e dentro launch.json:
launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Rust",
"request": "launch",
"type": "cortex-debug",
"cwd": "${workspaceRoot}",
"executable": "target/thumbv7m-none-eabi/debug/${workspaceFolderBasename}",
"svdFile": "STM32F103.svd",
"servertype": "openocd",
"configFiles": [
"interface/stlink-v2.cfg",
"target/stm32f1x.cfg"],
"preLaunchTask": "Build binary",
"preLaunchCommands": [
"monitor init",
"monitor reset init",
"monitor halt",
"monitor flash write_image erase main.bin 0x08000000"
],
"postLaunchCommands": ["continue"]
}
]
}
Você também vai ter que baixar o arquivo svd da placa e colocar na pasta do projeto. No nosso caso, o SMT32F103, você pode baixar por este link: https://github.com/RecursiveError/rust-embarcado/releases/download/1.0.0/STM32F103.svd
Para rodar, basta ir até a aba de debug do Vscode e selecionar a opção "Debug Rust" ou apertar "F5":
Discutimos, anteriormente, as vantagens da linguagem. Mas e quanto às desvantagens? Embora Rust seja altamente eficiente e eficaz em sua função, há algumas considerações a se fazer. A linguagem é conhecida por ter uma curva de aprendizado difícil, o que pode ser desafiador no início, mas o investimento vale a pena. Além disso, o tempo de compilação é mais longo devido às verificações rigorosas da linguagem e o tamanho do binário pode ser ligeiramente maior, o que pode ser um problema em algumas situações.
Links Usados neste post: GC: https://pt.wikipedia.org/wiki/Coletor_de_lixo_(informática) C/C++: https://pt.wikipedia.org/wiki/Bjarne_Stroustrup https://pt.wikipedia.org/wiki/Transbordamento_de_dados https://en.cppreference.com/w/cpp/language/raii Rust: https://survey.stackoverflow.co/2022/#technology-most-loved-dreaded-and-wanted https://mabez.dev/blog/posts/esp-rust-espressif/ https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html suporte a arquivos SVD: svd2rust HAL API padronizada: embedded_hal Comunidade ativa: exemplos para diversas plataformas livro de Rust: https://doc.rust-lang.org/book/ (tradução pt-BR: https://rust-br.github.io/rust-book-pt-br/) curso Rustlings: https://github.com/rust-lang/rustlings/ Livro "Rust by examples": https://doc.rust-lang.org/rust-by-example/ Livro "The Embedded Rust": https://docs.rust-embedded.org/book/
|
Rust é uma linguagem focada em segurança e desempenho que vem crescendo muito nos últimos anos, principalmente nas comunidades de sistemas embarcados, como uma alternativa promissora a C++. Nesse post, vamos dar uma introdução sobre ela, criando um Blink para STM32 bluepill e mostrando as semelhanças e diferenças entre ela e a C++.
Encontre tudo na Loja Eletrogate com frete grátis para compras acima de R$ 200