Limite de 32 K para dados na ROM dos microcontroladores AVR

O que poderia ser pior do que muletas? Somente muletas incompletamente documentadas.


imagem


Aqui está uma captura de tela do mais recente ambiente de desenvolvimento integrado oficial para microcontroladores AVR de 8 bits, Atmel Studio 7, a linguagem de programação C. Como você pode ver na coluna Valor, a variável my_array contém o número 0x8089. Em outras palavras, a matriz my_array está localizada na memória, começando no endereço 0x8089.


Ao mesmo tempo, a coluna Tipo fornece informações um pouco diferentes: my_array é uma matriz de 4 elementos do tipo int16_t localizados na ROM (isso é indicado pela palavra prog, ao contrário dos dados da RAM), começando no endereço 0x18089. Pare, mas, afinal, 0x8089! = 0x18089. Qual é o endereço real da matriz?


Linguagem C e arquitetura Harvard


Os microcontroladores AVR de 8 bits fabricados anteriormente pela Atmel, e agora Microchip, são populares, principalmente pelo fato de serem a base do Arduino, construído na arquitetura de Harvard, ou seja, o código e os dados estão localizados em diferentes espaços de endereço. A documentação oficial contém exemplos de código em dois idiomas: assembler e C. Anteriormente, o fabricante oferecia um ambiente de desenvolvimento integrado gratuito que suporta apenas o montador. Mas e aqueles que gostariam de programar em C, ou mesmo C ++? Havia soluções pagas, por exemplo, IAR AVR e CodeVisionAVR. Pessoalmente, nunca o usei, porque quando comecei a programar o AVR em 2008, já havia o WinAVR gratuito com capacidade de integração com o AVR Studio 4, e ele é simplesmente incluído no atual Atmel Studio 7.


O projeto WinAVR é ​​baseado no compilador GNU GCC, desenvolvido para a arquitetura von Neumann, que implica um espaço de endereço único para código e dados. Ao adaptar o GCC ao AVR, a seguinte muleta foi aplicada: os endereços 0 a 0x007fffff são alocados para o código (ROM, flash) e 0x00800100 para 0x0080ffff para dados (RAM, SRAM). Havia todos os tipos de truques, por exemplo, endereços de 0x00800000 a 0x008000ff representados registros que podem ser acessados ​​pelos mesmos códigos de operação que a RAM. Em princípio, se você é um programador simples, como um arduino iniciante, e não um hacker, misturando assembler e C / C ++ no mesmo firmware, não precisa saber tudo isso.


Além do compilador, o WinAVR inclui várias bibliotecas (parte da biblioteca C padrão e módulos específicos do AVR) na forma do projeto AVR Libc. A versão mais recente, 2.0.0, foi lançada há quase três anos, e a documentação está disponível não apenas no site do projeto, mas também no site do fabricante do microcontrolador. Existem também traduções russas não oficiais.


Dados no espaço de endereço do código


Às vezes, em um microcontrolador, você precisa colocar não apenas muitos, mas muitos dados: tanto que eles simplesmente não se encaixam na RAM. Além disso, esses dados são imutáveis, conhecidos no momento do firmware. Por exemplo, uma imagem raster, uma melodia ou algum tipo de mesa. Ao mesmo tempo, o código geralmente ocupa apenas uma pequena fração da ROM disponível. Então, por que não usar o espaço restante para dados? Fácil! A documentação do avr-libc 2.0.0 abrange um capítulo inteiro de 5 dados no espaço do programa. Se você omitir a parte das linhas, tudo será extremamente simples. Considere um exemplo. Para RAM, escrevemos assim:


unsigned char array2d[2][3] = {...}; unsigned char element = array2d[i][j]; 

E para ROM como este:


 #include <avr/pgmspace.h> const unsigned char array2d[2][3] PROGMEM = {...}; unsigned char element = pgm_read_byte(&(array2d[i][j])); 

É tão simples que essa tecnologia foi coberta repetidamente, mesmo no RuNet.


Então qual é o problema?


Lembre-se da afirmação de que 640 KB é suficiente para todos? Lembra como você mudou da arquitetura de 16 bits para 32 bits e de 32 bits para 64 bits? Como o Windows 98 funcionou de maneira instável em mais de 512 MB de RAM enquanto foi projetado para 2 GB? Você já atualizou o BIOS para que a placa-mãe funcione com discos rígidos maiores que 8 GB? Lembre-se dos jumpers em 80 GB de discos rígidos, reduzindo o volume para 32 GB?


O primeiro problema me ultrapassou quando tentei criar uma matriz de pelo menos 32 KB na ROM. Por que na ROM, e não na RAM? Como atualmente, os AVRs de 8 bits com mais de 32 KB de RAM simplesmente não existem. E com mais de 256 B - existem. É provavelmente por isso que os criadores do compilador escolheram 16 b (2 B) para ponteiros na RAM (e ao mesmo tempo para o tipo int), que podem ser encontrados na leitura do parágrafo Tipos de dados localizado no capítulo 11.14. Quais registros são usados ​​pelo compilador C? Documentação do AVR Libc. Ah, e não íamos hackear, mas aqui estão os registros ... Mas voltando ao conjunto. Acontece que você não pode criar um objeto maior que 32.767 B (2 ^ (16 - 1) - 1 B). Não sei por que era necessário tornar significativo o comprimento do objeto, mas isso é um fato: nenhum objeto, mesmo uma matriz multidimensional, pode ter um comprimento de 32.768 B ou mais. Um pouco como uma limitação no espaço de endereço de aplicativos de 32 bits (4 GB) em um sistema operacional de 64 bits, não é?


Até onde eu sei, esse problema não tem solução. Se você deseja colocar um objeto com um comprimento de 32.768 na ROM, divida-o em objetos menores.


Voltamos ao parágrafo Tipos de dados: ponteiros são 16 bits. Aplicamos esse conhecimento ao capítulo 5 de Dados no espaço do programa. Não, a teoria é indispensável, é necessária prática. Eu escrevi um programa de teste, lancei um depurador (infelizmente, software, não hardware) e vi que a função pgm_read_byte capaz de retornar apenas dados cujos endereços cabem em 16 bits (64 KB; obrigado, não 15). Em seguida, ocorre um estouro, a parte antiga é descartada. É lógico, já que os ponteiros são de 16 bits. Mas surgem duas perguntas: por que isso não está escrito no Capítulo 5 (uma pergunta retórica, mas foi ele quem me levou a escrever este artigo) e como superar o limite de ROM de 64 KB sem mudar para o assembler.


Felizmente, além do Capítulo 5, há outra Referência de arquivo pgmspace.h 25.18, da qual aprendemos que a família de funções pgm_read_* é apenas uma pgm_read_*_near para pgm_read_*_near , que aceita endereços de 16 bits, e também existe pgm_read_*_far , e você pode enviar Endereço de 32 bits Eureka!


Nós escrevemos o código:


 unsigned char element = pgm_read_byte_far(&(array2d[i][j])); 

Compila, mas não funciona como gostaríamos (se array2d estiver localizado após 32 KB). Porque Sim, porque a operação & retorna um número assinado de 16 bits! É engraçado que a pgm_read_*_near aceite endereços não assinados de 16 bits, ou seja, ele pode trabalhar com 64 KB de dados e a operação & é útil apenas para 32 KB.


Vamos seguir em frente. O que temos no pgmspace.h além do pgm_read_* ? A função pgm_get_far_address(var) , que já possui meia página de descrição, e substitui a operação & .


Provavelmente certo:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d[i][j])); 

Erro de compilação. Lemos a descrição: 'var' deve ser resolvido no momento da vinculação como um símbolo existente, ou seja, um nome de variável de tipo simples, um nome de matriz (não um elemento indexado da matriz, se o índice for uma constante, o compilador não reclama, mas falha ao obter o endereço se a otimização estiver ativada), um nome de estrutura ou um nome de campo de estrutura, um identificador de função, um identificador definido por vinculador, ...


Colocamos outra muleta: passamos de índices de matriz para aritmética de ponteiro:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d) + i*3*sizeof(unsigned char) + j*sizeof(unsigned char)); 

Agora tudo funciona.


Conclusões


Se você escreve em C / C ++ para microcontroladores AVR de 8 bits usando o compilador GCC e armazena os dados na ROM, então:


  • com um tamanho de ROM não superior a 32 KB, você não terá problemas lendo apenas os dados do Capítulo 5 no espaço de programa;
  • para ROMs maiores que 32 KB, você deve usar a família de funções pgm_read_*_far , a função pgm_get_far_address vez de & , aritmética do ponteiro em vez dos índices da matriz, e o tamanho de qualquer objeto não pode exceder 32.767 B.

Referências


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


All Articles