Baixe o app para aproveitar ainda mais
Prévia do material em texto
EA871 – LAB. DE PROGRAMAÇÃO BÁSICA DE SISTEMAS DIGITAIS EXPERIMENTO 2 – Ferramentas de Desenvolvimento Profs. Antonio Augusto Fasolo Quevedo e Wu Shin-Ting OBJETIVO: Apresentação do ambiente de desenvolvimento de programas para MKL25Z128 ASSUNTOS: Construção de um projeto no ambiente IDE CodeWarrior 10: linguagem de programação C, documentação e depuração de programas O que você deve ser capaz ao final deste experimento? Ter uma noção da placa de desenvolvimento (kit) FRDM-KL25. Ter uma noção da placa auxiliar (shield) EA871. Ter uma visão da organização das ferramentas disponíveis no IDE CodeWarrior. Saber criar um projeto no IDE CodeWarrior. Ter uma noção de como um firmware (um código executável) é construído. Ter uma visão da relocação dos endereços de um firmware. Depurar um código executado no microcontrolador em tempo real. INTRODUÇÃO Os conceitos da disciplina EA869 serão revistos e ampliados numa perspectiva prática. Para tanto, será utilizada uma placa (kit) de desenvolvimento denominada FRDM-KL25. Este kit da NXP/Freescale Semiconductors possui diversas conexões com circuitos digitais, em vários padrões de interface, e com periféricos analógicos. O “comandante” da placa é o microcontrolador MKL25Z128VLK4 de 32 bits, da série Kinetis L [1]. Este dispositivo é um microcontrolador, isto é, são agregados nele um núcleo de processamento Cortex-M0+ de arquitetura ARM® e vários módulos, como temporizadores, interfaces de comunicação, conversores DA/AD, controlador de interrupção e unidades de memória. Aliados o baixo custo e o baixo consumo de energia ao tamanho reduzido e à flexibilidade no desenho de um novo projeto, ele constitui uma alternativa para desenvolver aplicativos portáteis e de alto desempenho. São integrados na placa FRDM-KL25 um circuito de alimentação e de clock (8MHz), um touchpad capacitivo, um acelerômetro, e um led RGB [2]. Além disso, há na placa um adaptador serial e de depuração, OpenSDA, que permite que sejam transferidos programas executáveis (firmware) desenvolvidos num computador-hospedeiro para a memória interna do microcontrolador-alvo (MKL25Z) e que os mesmos sejam depuráveis através de um aplicativo instalado no computador-hospedeiro. O adaptador viabiliza ainda a comunicação serial entre o computador-hospedeiro e o microcontrolador-alvo por meio de uma porta mini- USB. É interessante observar que o circuito do OpenSDA é baseado num outro microcontrolador da família Kinetis K20 da NXP/Freescale, K20DX128VFM5. Em cima da placa FRDM-KL25 está conectada uma placa auxiliar (shield FEEC EA871) com alguns periféricos adicionais, mais especificamente um LCD (Liquid Cristal Display), 8 https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/KL25P80M48SF0RM.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/FRDMKL25Z.pdf leds vermelhos (ligados a um latch), 3 botoeiras, e um circuito de chaveamento de média potência baseado em transistor Darlington. Há ainda pinos de acesso direto aos pinos PTB0, PTB1, PTE20, PTE21, PTE22, PTE23 do MKL25Z [3]. Isso proporciona mais alternativas para a prática de programação do microcontrolador-alvo. Diferente do que vimos no roteiro 1 em que os programas são pré-processados, compilados, ligados, executados e depurados num mesmo processador [7], usaremos ferramentas cruzadas (cross tools) para escrever a sequência de ações definidas no pseudocódigo em instruções de uma linguagem de programação (editor), para gerar códigos-objeto (linguagem de máquina) a partir dos códigos-fonte (compilador cruzado), e para construir a partir dos códigos-objeto compilados separadamente num código executável no microcontrolador-alvo (ligador cruzado). Neste caso, para executá-lo efetivamente no microcontrolador-alvo, é necessário transferir via OpenSDA o código executável no formato elf para a memória FLASH do microcontrolador. Finalmente, para depurar e testar o programa em execução no microcontrolador via um computador-hospedeiro, o depurador no computador-hospedeiro precisa ter controle sobre o fluxo de execução do programa, com paradas e passos de execução configuráveis, e ter acesso ao espaço de memória do microcontrolador para monitorar/modificar as variáveis envolvidas. Todas essas funções podem estar integradas num único ambiente de desenvolvimento, denominado IDE (Integrated Development Environment). Usaremos nesta disciplina o CodeWarrior 10, um IDE baseado na plataforma Eclipse [4,5]. No IDE CodeWarrior são disponíveis cadeias de ferramentas (toolchains) para duas linguagens de médio nível, C e C++. Optamos por C para desenvolvimento dos programas nesta disciplina. Ao adotarmos o procedimento padrão de criação de um novo projeto no IDE, é gerada automaticamente uma série de arquivos nas pastas Project_Headers, Project_Settings/Startup_Code e o arquivo main.c, necessários para gerar um executável bare-metal, de extensão .elf: um executável capaz de rodar diretamente sobre a circuitaria do microcontrolador sem nenhuma camada adicional de códigos. É ainda gerado um script na pasta Project_Settings/LinkerFiles, contendo informações necessárias para relocação dos endereços quando se carrega um firmware no espaço de endereços de memória do microcontrolador. Uma vez transferido o firmware do computador-hospedeiro para o microcontrolador, o IDE permite sincronizar a execução de uma sequência de instruções no microcontrolador com o fluxo de monitoramento do computador-hospedeiro através da interface SWD [6]. Para facilitar o diagnóstico dos erros, é possível visualizar um mesmo dado sob diferentes formatos de representação, criar pontos de parada condicionais e alterar o conteúdo da memória. Os programas em C são editados na perspectiva C/C++ do IDE CodeWarrior. A Figura 1 ilustra um projeto em C composto por duas funções, main e fatorial. Através de Disassemble (pré-processador/compilador/montador) do IDE, os códigos em C são traduzidos para instruções de máquina como ilustram as Figuras 2 e 3. As informações nessas figuras são organizadas em 3 colunas. A primeira coluna contém o endereço da memória em que cada instrução ocupa, a segunda coluna, as instruções de máquina em hexadecimal, e a terceira coluna, os mnemônicos correspondentes. Observe na primeira coluna que, por padrão, cada função é traduzida para um espaço de memória que começa em https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and-trace/coresight-architecture/serial-wire-debug http://www.dca.fee.unicamp.br/cursos/EA871/references/apostila_C/AmbienteDesenvolvimentoSoftware.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/CWMCUQRCARD.pdf https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/roteiros/roteiro1.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/Esquematico_EA871-Rev3.pdf 0 (origem), e observe as linhas que contém os códigos em C seguidas de um bloco de mnemônicos correspondente. Faz parte da cadeia de ferramentas o ligador (linker) que “liga” as funções traduzidas, realocando os endereços de todas as instruções e todas as variáveis de acordo com o espaço físico disponível no dispositivo em que o programa vai ser instalado. Isso assegura que cada dado tenha um endereço único, como mostra a primeira coluna, em verde, da Figura 4. Compare os endereços realocados com os da Figura 2. Ao invés de começar em 0, os endereços iniciaram em 0x000008c8. É importante ressaltar que esses novos endereços não são arbitrários. Eles devem ser especificados pelo desenvolvedor. Nesta disciplina usaremos a configuração padrão sugerida pelo IDE CodeWarrior que se encontra no script, na pasta Project_Settings/LinkerFiles/ MKL25Z128_flash.ld. A Figura 5 mostra uma parte desse script. Figura 1: Um programa em Ccomposto de 2 funções main e fatorial. Figura 2: Resultado de disassemble da função main. Figura 3: Resultado de disassemble da função fatorial. Figura 4: Resultado de ligação e relocação dos endereços da função main. Os endereços efetivos de cada instrução são apresentados na primeira coluna. Figura 5: Especificação dos endereços (iniciais) da pilha (_estack), do segmento de instruções (m_text), do segmento de dados (m_data) e do vetor de interrupções (m_interrupts) no script MKL25Z128_flash.ld. Os códigos ligados são automaticamente carregados nos endereços realocados da memória física do microcontrolador-alvo via um protocolo de transferência válido. Uma vez carregado o programa, basta resetar o microcontrolador para iniciar a execução do programa. Vale lembrar que a conexão dos computadores-hospedeiro do LE-30 com os microcontroladores é via OpenSDA. Se ocorrer algum erro na transferência, verifique se o projeto de interesse está ativado em CodeWarrior Projects view (em negrito e inserido no campo Name), se a configuração Debug Settings (Settings > Debug Settings) está habilitada para OpenSDA como mostra a Figura 6. Figura 6: Conexão de transferência de firmwares para MKL25Z128. Executaremos os programas como desenvolvedor, através da Perspectiva Debug do IDE CodeWarrior, para podermos depurar e corrigi-los. Essencialmente, há dois tipos de erros que o IDE pode nos ajudar a detectar a fonte: erros sintáticos e erros lógicos. Os erros sintáticos são tipicamente identificados pelo compilador e são mostrados como erros ou avisos (warnings) em Problem view e Console view. Não despezem os avisos. Em muitos casos, a correção dos avisos é suficiente para corrigir alguns erros aparentemente não explicáveis. Os erros lógicos são mais difíceis de serem detectados, muitas vezes contra- intuitivos à lógica que construímos. Para evitar esses erros, procure verbalizar a sua lógica graficamente (diagrama de blocos, fluxograma, diagrama de estados, etc.) ou através de um pseudocódigo. Isso facilita a análise da lógica do programa como todo. Para detectar os erros, use um depurador para acompanhar rotina/função por rotina/função, bloco por bloco, ou até linha por linha, o fluxo de controle que o compilador entendeu dos códigos implementados. A Figura 7 mostra os views disponíveis na perspectiva Debug do ambiente CodeWarrior, através dos quais podemos monitorar os valores das variáveis, inclusive a adição das globais pelo ícone destacado com a seta vermelha (Variables), os pontos de parada setados (Breakpoints), o conteúdo de todos os registradores do microcontrolador mapeados no espaço de memória (Registers), o conteúdo de um espaço de memória (Memory) e os módulos carregados no microcontrolador (Modules). Figura 7: Diferentes views na perspectiva de Debug padrão: Variables, Breakpoints, Registers, Memory e Modules. Vale atentar ao fato de que nem todos os códigos disponíveis nas bibliotecas do IDE tem seus códigos-fonte acessíveis, como uma instrução em ponto flutuante emulada. Quando se tenta acessá-los aparece na tela uma mensagem de aviso como ilustra o acesso à instrução do endereço 0x00000802 na Figura 8. Para “saltar” as execuções, passo a passo, em códigos de máquina mostrados em Disassembly view, adicione um ponto de parada (breakpoint) numa próxima instrução em C que não seja uma operação em ponto flutuante. É bom manter em mente que o IDE suporta somente até 2 breakpoints ativados simultaneamente. Figura 8: Mensagem de aviso pela falta de códigos-fonte. Entre as ferramentas adicionais do IDE encontra-se a ferramenta Print Size que mostra o tamanho do espaço de memória ocupado pelo firmware. Figura 9 ilustra uma forma de habilitar essa ferramenta no CodeWarrior. Figura 9: Habilitação da ferramenta Print Size. Se habilitarmos Print Size, veremos no final da execução do ligador a quantidade de bytes ocupados pelas instruções (text), pelos dados (data) e pelos dados não inicializados (bss, block starting symbol) na base decimal (dec). Figura 10 apresenta a saída de Print Size para o programa mostrado na Figura 1. Figura 10: Espaços de memória ocupados pelo programa na Figura 1. EXPERIMENTO Neste experimento vamos migrar os códigos do Roteiro 1 [7] desenvolvidos no ambiente do computador-hospedeiro para o ambiente do microcontrolador-alvo, que não dispõe nenhum suporte adicional além dos códigos básicos de inicialização dos registradores do microcontrolador. 1. Criação e execução de projetos sem funções de entrada/saída: No nosso microcontrolador-alvo as funções de entrada (scanf) e saída (printf) não são disponíveis. Se tentarmos gerar códigos executáveis a partir de divide.c [8], fatorial.c [9] e ocor_palavras.c [10], o ligador não encontrará a definição das duas funções. Podemos, porém, substituí-las por acessos diretos aos endereços das variáveis mostradas na aba Variables da perspectiva Debug ao forçarmos a parada do fluxo de controle do programa por meio de breakpoints. Os nomes e os endereços das variáveis são mostrados, respectivamente, na primeira e na terceira coluna da aba Variables. Por exemplo, com a seguinte adaptação de fatorial.c [9], podemos gerar um projeto executável no nosso microcontrolador e variar o valor da variável de entrada n, assim https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/codes/fatorial.c https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/codes/ocor_palavras.c https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/codes/fatorial.c https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/codes/divide.c https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/roteiros/roteiro1.pdf como ler o valor do resultado res, se colocarmos um breakpoint na linha de instrução fatorial (n, &res) e na linha seguinte. : int main (){ int n, /* guarda o numero dado */ res; for (;;) { //printf("\n\tCalculo do fatorial de um numero\n"); //printf("\nDigite um inteiro nao-negativo: "); //scanf("%d", &n); fatorial(n, &res); //printf("O valor de %d!: %d\n", n, res); } return 0; } Crie projetos executáveis no nosso microcontrolador a partir dos programas divide.c e ocor_palavras.c. Certifique na aba Modules o firmware carregado no microcontrolador. 2. Acessos diretos às variáveis em Variables: O processador do nosso microcontrolador é de 32 bits. Todas as operações lógico-aritméticas são em 32 bits. Quando os operandos são variáveis de tipos de dados de tamanho menor, eles são automaticamente estendidos para 32 bits. Vamos analisar o impacto da capacidade de armazenamento de um espaço alocado a uma variável e do seu tipo de dado nos resultados de uma operação aritmética usando Variables da perspectiva Debug. Modifique, de forma consistente, o tipo de dado da variável res da função main da função fatorial para os tipos uint8_t, uint16_t, uint32_t, int8_t, int16_t e int32_t. Anote os resultados armazenados no endereço res para n=5, 6, 7, 8, 9, 10, 11, 12, 13. Justifique sucintamente o que você observou. uint8_t uint16_t uint32_t int8_t int16_t int32_t n=5 n=6 n=7 n=8 n=9 n=10 n=11 n=12 n=13 Obs.: Quando o compilador detecta um valor maior que a capacidade de representação do espaço disponível, ele pode gerar uma alerta de overflow. Não despreze alertas do compilador. 3. Visualização da ordenação dos bytes na memória em Memory: Byte é a unidade de armazenamento em memórias. Quando o valor de uma variável é representado por mais de um byte, há duas formas de ordenação (endianess): little endian e big endian. Em little endian o byte menos significativo do valor é alinhado com o menor endereço, enquanto na ordenação big endian, o byte mais significativo é alinhado com o menor endereço. Use a aba Memory da perspectiva Debug para descobrir qual é a ordenação adotada pelomicrocontrolador que estamos usando. Justifique com base na renderização Hex Integer com o número de colunas setado em 1 (Format > ColumnSize > 1) das variáveis de tamanho maior que 1 byte do programa fatorial.c. 4. Acessos diretos aos registradores do microcontrolador em Registers: O nosso microcontrolador dispõe de uma série de circuitos dedicados. A sua programação para uma tarefa específica consiste, essencialmente, na ativação dos circuitos necessários e no controle das operações desses circuitos através dos seus registradores mapeados no espaço de endereços da memória. Pela aba Registers da perspectiva Debug podemos acessar todos esses registradores. Os nomes e os endereços das variáveis são mostrados, respectivamente, na primeira e na terceira coluna da aba Variables. Importe o projeto pisca-pisca [11] no IDE CodeWarrior. Execute o projeto no modo de depuração e expanda os detalhes dos registradores SIM_SCGC5, SIM_SCGC6 e PORTB_PCR19. https://www.dca.fee.unicamp.br/cursos/EA8771/2s2022/codes/pisca_pisca.zip Capture as explicações detalhadas de cada campo dos registradores. Explique com as suas palavras as funções desses registradores na programação do microcontrolador. 5. Block Starting Symbol (bss), Heap e Pilha: O espaço de endereços em que a memória RAM do nosso microcontrolador é mapeada é organizada em bss, heap e pilha (stack). O segmento bss contém as variáveis estáticas cujo tempo de vida coincide com o tempo de execução do programa, enquanto a pilha contém as variáveis locais, cujo tempo de vida é o tempo de execução da função em que elas são declaradas. O heap contém as variáveis alocadas dinamicamente, de responsabilidade do programador. Trabalharemos somente com bss e pilha. Use o Gnu Size (Print Size) para completar na tabela abaixo o tamanho em bytes do segmento de instruções (txt), do segmento de dados (data) e do segmento de dados alocados estaticamente na inicialização (bss) do projeto divide: (a) para o programa adaptado, (b) após adicionar o qualificador global para a variável a e static para a variável b, (c) após alterar, em relação ao programa original adaptado, o tipo de dado das quatro variáveis, a, b, c e d, para o tipo uint8_t redefinido no arquivo stdint.h [12], e (d) após adicionar o qualificador global para a variável a e static para a variável b no programa do item (c). Anote também o endereço e o tamanho em bytes de cada variável declarada na função main mostrados na aba Variables. Com base no que foi observado, explique o impacto dos qualificadores static e global das variáveis declaradas no espaço de memória ocupado, em termos do segmento de instruções (txt), do segmento bss e do segmento de dados (data). (a) (b) (c) (d) End. Inicial Bytes End. Inicial Bytes End. Inicial Bytes End. Inicial Bytes a b c d .txt --------- --------- --------- .data --------- --------- --------- .bss --------- --------- --------- Obs.: Este exercício tem também o objetivo de mostrar que nem sempre usar tipos de dados de menor tamanho resulta num uso mais eficiente de recursos por causa dos alinhamentos dos endereços de acesso impostos pelo processador [13]. 6. Realocação dos endereços: Os espaços em que os dados e as instruções são carregados na memória do microcontrolador devem ser especificados. O toolchain realoca automaticamente os endereços locais atribuídos pelo compilador em endereços efetivos. Anote na tabela abaixo os endereços iniciais e finais (próximo endereço não ocupado) das instruções do programa ocor_palavras.c, como também de todas as suas variáveis. Altere os endereços iniciais dos segmentos de instruções (m_text), do segmento de dados (m_data) e do topo da pilha (_estack) para 0x0000_1000, 0x1fff_f500 e 0x2000_2f60, respectivamente, no script de ligação Project_Settings/Linker_Files/MKL25Z128_flash.ld. . Com base no https://aticleworld.com/data-alignment-and-structure-padding-bytes/ https://pubs.opengroup.org/onlinepubs/009696899/basedefs/stdint.h.html conceito de realocação de códigos, explique as variações nos endereços após a reconfiguração do conteúdo do arquivo MKL25Z128_flash.ld. 0x00000800 0x1FFFF000 0x20003000 0x0000_1000 0x1fff_f500 0x2000_2f60 Tamanho do espaço de memória ocupado programa início fim ocorre início fim m início fim n início fim i início fim j início fim numOcor início fim frase início fim palavra início fim 7. Testes dos periféricos do shield EA871: Assista o vídeo de gravação de um firmware, no formato hex, via IDE CodeWarrior disponível no link da referência [14]. Este firmware testa os periféricos do shield EA871 (teste_kit.hex). RELATÓRIO O relatório deve ser devidamente identificado, contendo a identificação do instituto e da disciplina, o experimento realizado, o nome e RA do aluno. Para este experimento, responda os itens 1-6 do roteiro num arquivo no formato pdf. Suba-o no sistema Moodle. https://www.ggte.unicamp.br/ea/ http://www.dca.fee.unicamp.br/cursos/EA871/2s2022/testes/teste_kit.hex https://youtu.be/vNxa7vUcTkE REFERÊNCIAS [1] FRDM-KL25Z User’s Manual – Freescale Semiconductors (doc. Number KL25P80M48SF0RM, Setembro 2012) https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/KL25P80M48SF0RM.pdf [2] FRDM-KL25Z User’s Manual – Freescale Semiconductors, Setembro 2012. https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/FRDMKL25Z.pdf [3] Esquemático do shield FEEC. https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/Esquematico_EA8 71-Rev3.pdf [4] Freescale. CodeWarrior Development Suite: Eclipse Quick Reference Windows. https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/CWMCUQRCAR D.pdf [5] Wu Shin-Ting e A.A.F. Quevedo. Ambiente de Desenvolvimento – Software http://www.dca.fee.unicamp.br/cursos/EA871/references/apostila_C/AmbienteDesenvolvime ntoSoftware.pdf [6] 2-Pin Debug Port https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and- trace/coresight-architecture/serial-wire-debug [7] Roteiro 1 https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/roteiros/roteiro1.pdf [8] fatorial.c https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/fatorial.c [9] divide.c https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/divide.c [10] ocor_palavras.c https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/ocor_palavras.c [11] pisca_pisca https://www.dca.fee.unicamp.br/cursos/EA8771/2s2022/codes/pisca_pisca.zip [12] stdinit.h https://pubs.opengroup.org/onlinepubs/009696899/basedefs/stdint.h.html [13] AticleWorld. Unserstanding of structure padding in C with alignment https://aticleworld.com/data-alignment-and-structure-padding-bytes/ [14] Lucas Lui Motta. Gravação de um firmware em microcontrolador via IDE CodeWarrior. https://youtu.be/vNxa7vUcTkE Revisado em Agosto/2022 Revisado em Fevereiro/2022 Revisado em Julho/2021 Revisado em Março/2021 https://youtu.be/vNxa7vUcTkE https://aticleworld.com/data-alignment-and-structure-padding-bytes/ https://pubs.opengroup.org/onlinepubs/009696899/basedefs/stdint.h.html https://www.dca.fee.unicamp.br/cursos/EA8771/2s2022/codes/pisca_pisca.zip https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/ocor_palavras.c https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/divide.c https://www.dca.fee.unicamp.br/cursos/EA8771/1s2022/codes/fatorial.c https://www.dca.fee.unicamp.br/cursos/EA871/1s2022/roteiros/roteiro1.pdf https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and-trace/coresight-architecture/serial-wire-debug https://developer.arm.com/architectures/cpu-architecture/debug-visibility-and-trace/coresight-architecture/serial-wire-debug http://www.dca.fee.unicamp.br/cursos/EA871/references/apostila_C/AmbienteDesenvolvimentoSoftware.pdf http://www.dca.fee.unicamp.br/cursos/EA871/references/apostila_C/AmbienteDesenvolvimentoSoftware.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/CWMCUQRCARD.pdfhttps://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/CWMCUQRCARD.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/Esquematico_EA871-Rev3.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/complementos/Esquematico_EA871-Rev3.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/FRDMKL25Z.pdf https://www.dca.fee.unicamp.br/cursos/EA871/references/ARM/KL25P80M48SF0RM.pdf
Compartilhar