O projeto não contém ArduinoOriginalmente, esse projeto tinha que parecer diferente - uma estrutura monumental composta por um pedestal com latas e bombas, um aquário montado sobre ele e um oásis de tomate em cima. Planejou-se uma cachoeira no paraíso de um oásis de tomate e formas de vida de peixes no aquário, cujo principal requisito era a capacidade de comer os habitantes não planejados do aquário e manter o copo limpo; os principais candidatos são somiki e gourami. Como você deve ter adivinhado, meu lema é "a preguiça é o motor do progresso" (e o que você pode fazer para não limpar o aquário e não molhar os tomates).Um monumento a esse lema provavelmente teria sido erguido se ainda não tivesse entrado em colapso na fase de coordenação dos esboços com sua esposa. Ela não se inspirou na idéia de fazer dessa bandura a principal decoração da sala de estar, e nem a cachoeira a convenceu disso. Mas a idéia de um sistema autônomo, uma simbiose de biologia e eletrônica, não queria sair da minha cabeça, e o projeto reduziu-se ao tamanho de um vaso de flores - a aquaponia se transformou em hidroponia, a vida dos peixes foi salva.A idéia principal da hidroponia é o uso de uma solução aquosa de nutrientes em vez do solo. Isso permite que uma ordem de magnitude acelere o crescimento das plantas. No entanto, não se pode apenas baixar as raízes na água - elas precisam de oxigênio, sem as quais começarão a morrer. Nesse sentido, existem opções - soprar água constantemente com um compressor, como em um aquário, ou inundar periodicamente as raízes com uma solução nutritiva e drená-la depois de algum tempo. A primeira opção tem uma desvantagem - o zumbido constante do compressor. A segunda opção tem uma vantagem - a maioria das raízes está no ar, respirando ativamente, e o efeito de acelerar o crescimento deve ser ainda maior. Além disso, eles são imersos em um substrato de grânulos porosos especiais que retêm a umidade. A escolha foi óbvia, tomei a segunda opção como base.No caso dos peixes, o sistema pode estar quase completamente fechado - as secreções de peixes são processadas por bactérias especiais em um biofiltro, o produto processado é alimentado às plantas, uma camada de areia filtra a água e a água limpa é devolvida ao aquário. Em um caso ideal, as rações são ocasionalmente polvilhadas em um alimentador automático, e os tomates se juntam dos arbustos. Mas não cresceu juntos, talvez seja para melhor - quem sabe como terminaria o pedido pelo correio de uma cepa das bactérias necessárias.Como resultado, o dispositivo do tomateiro ganhou contornos. Dois vasos - o mais baixo com água, o superior com um substrato e uma planta. Para inundações, usaremos uma pequena bomba chinesa com motor DC, para drenagem, usaremos um sifão automático. O princípio de operação do sifão no vídeo:Hidroponia com um sifão semelhante:O cérebro do dispositivo é o microcontrolador ATMEGA328P (simplesmente porque o placer estava à mão). Suas tarefas incluem gerenciar as inundações e descargas de acordo com um cronograma, monitorar o nível da água no tanque e sinalizar sua falta, controlar a iluminação da usina (queremos ter uma certa duração mínima da luz do dia; quando a luz natural termina, a luz artificial é ativada gradualmente), uma interface de usuário para visualização o status, gerenciamento e configuração de toda essa economia. Obviamente, isso requer algum tipo de solução para o sensor de nível de água, sensores de luz, um relógio em tempo real e algum tipo de terminal do usuário.Antes de descrever os detalhes, uma lista de recursos do projeto:Aqui você pode ver fotos do resultado e do processo de fabricação.Vídeo curto:O projeto está disponível no GitHub . Lá, nas versões, é apresentado um arquivo com o projeto de peça eletrônica no KiCAD e os projetos de sinos e assobios de design no SolidWorks (arquivos STL para impressão são anexados).Recursos do conjunto do firmware—
« ». , , , USB AVR (, , , , ), . - , , 'ADK_ROOT' , 'scons'.
Esquema da parte eletrônica:
Mais detalhes, uma descrição das armadilhas e um pouco de código. Descrição dos problemas de software no final . Talvez alguém esteja interessado em ver um novo exemplo de trabalho com I2C, um valcoder, um módulo RTC e uma tela gráfica. Todo o código do projeto foi escrito "do zero" sem o uso de soluções de terceiros (porque eu posso).Sensor de nível de água
A questão mais sensível foi decidida primeiro. Havia, é claro, uma variante de algum tipo de flutuador para que, por exemplo, movesse o trilho no qual o código Gray foi aplicado e os sensores ópticos fossem lidos. Mas realmente não parecia confiável. A pesquisa no eBay não deu resultado - havia interruptores de bóia (atingiram o nível desejado ou não) ou eletrodos e leituras imersos com base na condutividade do meio, mas isso foi imediatamente observado, pois a composição da água mudava constantemente junto com a condutividade dos fertilizantes e dissolução adicionados impurezas do substrato. Como resultado, surgiu a idéia de usar um telêmetro ultrassônico, um daqueles que geralmente são colocados em robôs diferentes. Conforme planejado, o sensor é colocado na tampa do tanque e o sinal é refletido diretamente da superfície da água. Foi adquirido o HC-SR04 (a escolha do menor valor da distância mínima de trabalho - ele tem 2cm),e o conceito foi verificado em um balde de água. Descobriu-se que funcionava por si próprio (havia receios de que não houvesse reflexo normal da superfície da água ou que não houvesse diretividade suficiente do feixe e haveria reflexos indesejados nas paredes do tanque). A propósito, o telêmetro também era uma opção de backup, mas infravermelho. Na superfície da água era para jogar uma bóia com um refletor. O único problema é a distância mínima de trabalho de 10 cm (daquelas que encontrei), que já é um pouco demais para as dimensões especificadas.Na superfície da água era para jogar uma bóia com um refletor. O único problema é a distância mínima de trabalho de 10 cm (daquelas que encontrei), que já é um pouco demais para as dimensões especificadas.Na superfície da água era para jogar uma bóia com um refletor. O único problema é a distância mínima de trabalho de 10 cm (daquelas que encontrei), que já é um pouco demais para as dimensões especificadas.
De acordo com os resultados do projeto, essa abordagem está funcionando e pode ser usada na prática, sem problemas. Vale a pena tomar medidas para isolar a placa da umidade (vedação no caso). Isso é apenas o próprio sensor permanecer aberto, talvez ele ainda esteja por aí.A interface do sensor é simples - um pulso é enviado à entrada do acionador, que aciona um sinal de eco. Um pulso é gerado na saída de eco, cuja duração é igual ao tempo desde o início da radiação até a aceitação do sinal de eco refletido. Medindo a duração do pulso, conhecendo a velocidade do som e o fato de o sinal ir para o objeto e voltar, você pode calcular a distância. No projeto, isso é implementado na classe LevelGauge. Para medir o comprimento do pulso, é usada a capacidade de hardware da “captura de entrada” do MK AVR. Nesse caso, o temporizador do hardware é redefinido na borda ascendente do pulso e, no valor descendente do temporizador, o hardware é armazenado no registro ICR1 e é gerada uma interrupção. Assim, é possível medir a duração do pulso com precisão suficiente e um consumo mínimo de tempo do processador.Mesmo com esse modelo de sensor, uma falha foi notada - quando a energia era aplicada, a linha de eco permanecia constantemente ativa. Ele ignorou aplicando um pulso ao gatilho e esperando até o primeiro ciclo de eco-localização passar.Luz de fundo
A luz de fundo é feita por três LEDs de aderência. Inclinei a moldura triangular do perfil de alumínio e colei os LEDs com epóxi. Encomendei um estabilizador de corrente chinês a 700mA para alimentação. Quedas de cerca de três volts em cada diodo, o estabilizador requer uma diferença entre as tensões de entrada e saída de pelo menos dois volts, e eu iria alimentar a wunderwafer inteira de uma fonte de alimentação de 12 volts. A partir daqui, é fácil calcular por que exatamente três LEDs.Os diodos são brancos quentes. Pareceu-me natural, o espectro solar e tudo isso. Mas, como descobri mais tarde, depois que as pedi, as plantas geralmente usam uma combinação de vermelho e azul. Tanto quanto eu entendo, toda a questão é apenas em eficiência. Se você tem uma fazenda grande com iluminação 24 horas, está interessado em que toda a energia gasta seja gasta para sempre. Sob iluminação branca, as folhas verdes refletem o componente verde, uma parte significativa da energia gasta em iluminação será desperdiçada.

Uma característica importante do estabilizador é a presença de uma entrada para regulação PWM, que eu uso para ajustar o brilho. Aqui está outro rake chinês. Em primeiro lugar, acabou sendo apenas uma função de ativação / desativação atual. Ou seja, eu esperava que a corrente de saída não fosse modulada e seu valor dependesse do ciclo de trabalho do sinal PWM, mas a corrente simplesmente repetiu os pulsos na entrada de controle. Mas isso não é tão ruim, outra emboscada foi que o regulador reage inadequadamente ao PWM com uma frequência bastante alta. Eu tive que abaixá-lo para 300Hz, no qual funcionava mais ou menos normalmente. O sinal PWM é gerado pelo microcontrolador no hardware usando um dos temporizadores.
Outra parte importante do conjunto da luz de fundo são os sensores de luz. Fototransistores foram selecionados nesta função. E sim, existem dois deles - um acima dos LEDs para medir a luz natural, o segundo sob os LEDs para fornecer feedback. É verdade que a funcionalidade de extensão automática da luz do dia ainda não foi implementada, como no verão, e não era necessária (e a motivação é um assunto sério). Supunha-se que, assim que o primeiro sensor detecta uma diminuição no nível de iluminação (e o tempo alocado para o dia ainda não expirou), a luz é regulada para que o segundo sensor produza um nível correspondente à iluminação desejada. Para fazer isso, você precisa implementar um controlador PID simples no código. Porém, enquanto estiver na interface, você poderá ver apenas as leituras atuais do sensor e aumentar manualmente o brilho da luz de fundo desejada.Preste atenção na conexão dos sensores. Cada um deles tem duas faixas fixas, que são selecionadas conectando a zero o resistor correspondente. O pé do microcontrolador, conectado ao segundo resistor, neste momento é transferido para um estado de alta resistência. Você pode ativar os dois resistores simultaneamente, e haverá três faixas de medição fixas. O sinal dos resistores do emissor é passado através de um circuito RC para filtrar os pulsos moduladores - a luz dos LEDs pulsa junto com o sinal PWM no regulador de corrente.Bomba
O equipamento chinês mais barato, com um motor DC. Emboscadas, é claro, estão disponíveis. Apesar de dizer 12V, ele não funciona por muito tempo nessa tensão. Um queimou antes da montagem da estrutura. O esquema prevê PWM para ele, a potência máxima é configurada na interface, na prática, não foi definida acima de 70%. Já neste nível, ele uiva descontroladamente no trabalho, mas na maioria das vezes ele trabalha com uma potência muito mais baixa - cerca de 30% e burburinho. Sobre seus modos de operação abaixo, na descrição da lógica das inundações. O capacitor maior (C8 no diagrama) deve ser posicionado mais próximo ao circuito de energia da bomba, caso contrário, haverá grande interferência em todo o circuito (na prática, verificou-se que o regulador de corrente para LEDs é mais sensível a eles, a música leve inicia).Relógio de tempo real
Havia uma idéia maluca de usar os recursos do microcontrolador para esses propósitos. O gerador de relógio de quartzo tem uma precisão muito boa, em outro projeto essa abordagem funcionou bem. Mas o problema é que absolutamente todos os temporizadores de hardware já foram utilizados para outros fins. Não havia escolha a não ser encontrar o módulo RTC externo. Elogie os chineses, eles estão lá e são baratos.
O módulo baseado no DS3231 possui uma interface I2C, sua própria fonte de alimentação redundante - o tempo não vai dar errado com o apagão. Há uma saída de meandro em várias frequências fixas - 1 kHz, 4 kHz e 8 kHz. Isso foi muito útil para sinais de áudio - novamente, você não precisa carregar o MCU e não havia temporizadores livres para isso. A EEPROM de 32Kbit é um bônus, mas não é usada neste projeto.Surpreendentemente, é muito preciso: em poucos meses, perdi a força por vários segundos. Ele afirmou que leva em consideração a influência da temperatura na frequência do gerador, e aparentemente isso funciona. Se, no entanto, o tempo acabar, existe a possibilidade de correção da frequência do software. As leituras do sensor de temperatura estão disponíveis e, neste projeto, são exibidas na interface.A classe Rtc é responsável por trabalhar com este módulo no código.Exibição
Há muito tempo eu queria fazer algo com uma exibição gráfica. Procurar a interface mais barata com o I2C deu essa opção.
OLED monocromático exibe 128x64 pixels com base no popular controlador SSD1306. Ao escolher, você precisa examinar atentamente a descrição - o mesmo chip suporta outras interfaces, exceto I2C, e existem opções sem ele. Ou eles escrevem que é universal, também suporta I2C, mas, na realidade, será necessário modificar levemente a placa reorganizando os nulos em outros sites. Portanto, se você planeja usar o I2C, é melhor escolher um em que apenas o I2C seja exibido na placa, haverá menos problemas com uma placa que não possua quase nenhuma documentação (documentação apenas para o chip). Esta versão funciona a partir de 5V, a placa possui um regulador de 3,3V necessário para o controlador. Eu conheci críticas que, em algumas versões, pode não ser.A exibição geralmente é satisfeita. Notei apenas um recurso desagradável - o brilho de uma linha de pixels depende de quantos pixels estão acesos. Quanto mais iluminado, menor o brilho. O contraste entre as linhas pode ser visível se áreas completamente preenchidas com alguns elementos estreitos se alternarem na tela. Mas, na prática, isso não é visível nas minhas fotos e não é impressionante.O controlador pode ser configurado para operar em vários modos de exibição do conteúdo da memória da tela em uma matriz de pixels. Era mais conveniente para mim quando cada byte é mapeado em uma coluna vertical com oito pixels de altura e as colunas vão horizontalmente da esquerda para a direita, preenchendo a tela com linhas de oito pixels de altura. Nesse modo, é mais conveniente desenhar texto.Freqüentemente, é praticada uma abordagem na qual a memória de exibição é duplicada no RAM MCU - primeiro, todas as ações com a imagem são executadas na RAM e, em seguida, todos os pixels alterados são copiados para a memória de exibição. Neste projeto, essa abordagem não é usada para economizar recursos. Todos os locais alterados são redesenhados imediatamente na memória de exibição.Conforme sugerido nos comentários, as telas OLED desaparecem com o tempo. Eu também suspeitava disso (lembrando o que é o protetor de tela) e providenciei que a exibição desligasse alguns minutos após a última atividade nos controles. Ele liga quando liga ou pressiona o codificador.No código, o trabalho com a exibição é implementado na classe Display.Valkoder:
Na minha opinião, o valcoder é a melhor opção de controle para dispositivos que tenham pelo menos alguma interface de usuário. É compacto e muito confortável. É conveniente para eles folhear e selecionar itens de menu, alterar os valores de quaisquer parâmetros, alternar modos, etc.Para conectar, são necessárias três pernas de entrada do microcontrolador. Um para o botão (você pode pressionar a alça), dois para o próprio valcoder. Há um sinal de código cinza do codificador . A cada passo do turno, um bit muda em duas linhas. A sequência determina a direção da rotação.Tudo parece ser simples, mas, aparentemente, os desenvolvedores nem sempre são capazes de oferecer suporte de alta qualidade a esse dispositivo. Por exemplo, na minha impressora 3D, há uma placa RAMPS e uma placa com uma tela e o mesmo codificador está conectado. O firmware Marlin funciona com ele, mas a experiência de usá-lo é muito ruim - não há sensação de confiabilidade - quando você clica no botão ao girá-lo, a interface geralmente para no item de menu ou no valor do parâmetro errado no qual era esperado. Com rotação rápida, parece que os cliques estão pulando. Em algum momento, a troca não começa durante os cliques, mas em algum lugar é muito desagradável. Sim, o que é Marlin, às vezes tenho o mesmo sentimento no sistema multimídia embutido no carro. Nesse sentido, algumas dicas (e, é claro, examinam o código nas proximidades da classe RotEnc).Primeiro, um ponto bastante óbvio para quem conecta qualquer botão ao microcontrolador - você precisa lidar com o ressalto. Esse codificador mecânico e suas linhas de sinal são, de fato, os mesmos botões, e eles também têm uma conversa. Primeiro, filtramos a conversa e depois processamos a sequência de estados das linhas de sinal. Pode haver valcoders com sensores ópticos, isso já depende dos esquemas de processamento de sinal deles. Se as pernas de um fototransistor forem trazidas diretamente para fora, ele poderá se agitar com rotação lenta, mas se houver algum esquema de processamento que introduza histerese, a supressão de software não será necessária. Mas esses dispositivos são mais caros e raramente são usados em dispositivos amadores, os mais comuns são mecânicos, alguns dólares por lote.Em segundo lugar, um ponto um pouco menos óbvio, provavelmente um daqueles em que Marlin se queimou - a alça tem posições estáveis durante a rotação - clica (clica). Este modelo possui quatro etapas de uma sequência de código para cada clique. Portanto, você precisa responder aos cliques, e não às etapas da sequência. E o mais importante é sincronizar com posições estáveis. Muitos simplesmente inserem a constante STEPS_PER_CLICK e, por exemplo, respondem a cada quarto passo. Mas o problema é que o sinal não é perfeito, as seqüências podem não estar totalmente corretas. Com uma determinada ortografia, o código pode "se perder", como resultado, cada quarto passo será obtido em algum lugar no meio do clique, o que será desconfortável para o usuário. Ao mesmo tempo, a posição fixa da alça para um modelo específico corresponde a um valor de código fixo,deve ser anexado a ele.Terceiro, novamente, um ponto bastante óbvio para desenvolvedores mais ou menos experientes de sistemas de microcontroladores - use interrupções de hardware para alterar o estado das linhas de entrada. No mínimo, haverá menos risco de "perder" as etapas da sequência. Mas, em geral, como você sabe, as interrupções são tudo. O MCU deve dormir sempre que possível, acordando apenas com interrupções - da periferia externa ou de um timer para executar uma tarefa atrasada. Esses são os princípios do bom design da arquitetura do sistema.Design como um todo
É feito de materiais improvisados e várias peças impressas em uma impressora 3D da ABS.O princípio de operação do sifão é ilustrado no vídeo acima. Para mim, é um tubo de PVC externo e um tubo interno com um funil no final. Para o sifão clássico, é necessário outro joelho, mas já era difícil fazê-lo construtivamente para mim. Quando foram encontrados problemas com o dreno, um pequeno banho foi preso na parede do tanque inferior, na qual a extremidade do tubo interno estava imersa, criando resistência ao dreno, para que o sifão pudesse funcionar.Descobriu-se que o ABS é um material muito hidrofóbico. A água literalmente não transborda; eu até tive que refazer o funil do sifão. Vale a pena considerar essa propriedade, é impossível criar alguns sistemas hidráulicos em miniatura (por exemplo, eu queria criar superfícies guia no funil do sifão, torcer a água, melhorar a resposta do sifão. Mas com essas dimensões e hidrofobicidade do ABS, isso não faz sentido) .Eu também tentei primeiro colar tudo junto com uma pistola de cola quente. Não funciona - no começo tudo parecia estar bem, mas depois de alguns dias caiu. A melhor opção é a Ásia Central. Os detalhes, mesmo debaixo d'água, mantêm-se firmes.O maior erro de cálculo no design são os recipientes transparentes. Esqueci completamente o fato de que a água brota na luz. Eu tive que envolvê-lo com material opaco. Bem, você pode adicionar periodicamente permanganato de potássio para desinfecção, isso não parece prejudicar as plantas.O algoritmo de inundação é o seguinte - primeiro a bomba é ligada com baixa potência e enche silenciosamente todo o tanque superior. O processo é monitorado por um sensor de nível. Quando a água começa a transbordar através do funil do sifão, a diminuição do nível no tanque inferior para, o que é detectado pelo sensor. Um pequeno fluxo criado com baixa potência não é suficiente para acionar um sifão. A bomba para, o volume bombeado para o tanque superior é lembrado. As raízes são mantidas na solução por vários minutos, após os quais a bomba liga novamente. Primeiro, em baixa potência, até que a água atinja o funil novamente (durante o tempo de inatividade, ele cai mais baixo devido ao efeito do sifão) e, quando o nível do funil é atingido, a bomba liga para aumentar a potência, fornecendo um fluxo suficiente para o sifão funcionar. É garantido que o fluxo através do sifão seja superior ao fluxo da bomba,Como resultado, o nível no tanque inferior começa a subir, isso é detectado pelo sensor e a bomba para.Os ciclos de inundação iniciam-se periodicamente, em intervalos de tempo fixos e configuráveis, do amanhecer ao anoitecer. De acordo com o plano, o amanhecer deveria ser fixado pelo sensor de luz, e a duração da luz do dia, se necessário, era estendida ao valor definido, mas até então as mãos não o alcançavam. O horário do amanhecer é simplesmente definido nas configurações.E onde está o C ++ 11?
Talvez alguém duvide que o C ++ 11 possa ser útil na programação de microcontroladores (entre aqueles que geralmente sabem que os microcontroladores podem ser programados em C ++). Vou tentar dar exemplos específicos dos benefícios do C ++ 11 nessa área (além de pequenas coisas óbvias e agradáveis, como constexpr, override, default etc.).Colocação de recursos de sequência
Muitas pessoas sabem que a RAM nos microcontroladores é um recurso muito limitado. Isso pode ser um problema se seu aplicativo, por exemplo, tiver uma interface com o usuário e seu programa usar um número bastante grande de linhas. Se no código para escrever algo comoPromptUser("Are you sure you want to format SD-card?");
a linha passada nos argumentos será colocada na seção de dados inicializados (doravante, o comportamento do compilador GCC para a plataforma AVR) - ou seja, na área de RAM, que na inicialização (antes que a função principal seja chamada) seja inicializada a partir da memória flash do programa. A função PromptUser () passará um ponteiro para o local desejado na RAM. Se você usar uma abordagem semelhante ao longo do programa, a RAM terminará rapidamente (no ATMEGA328P usado neste projeto, são apenas 2 kilobytes e também para BSS, heap e stack). Para contornar essa limitação, funções como PromptUser () aprendem a trabalhar não com ponteiros para RAM, mas com ponteiros para uma região na memória flash do programa. Você pode ler a partir daí apenas com a ajuda de instruções especiais, que, por exemplo, no avr-libc, estão agrupadas em funções da família eeprom_read_ [byte | word | dword | ...].Nesse caso, a cadeia de caracteres deve primeiro ser colocada em uma variável equipada com o atributo PROGMEM, que informa ao compilador que deve ser colocada na memória do programa .char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
PromptUser(prompt);
Isso é inconveniente se você deseja declarar todas as linhas centralmente. Então você deve primeiro declarar sua declaração no arquivo de cabeçalho:extern char prompt[] PROGMEM;
E em um arquivo .cpp separado, defina:char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
Duplicação de código, o que não é bom, e muito inconveniente quando existem muitas dessas linhas. Sim, isso pode ser contornado criando uma macro complicada e incluindo o arquivo de cabeçalho em um arquivo .cpp separado, no qual a macro será expandida para a definição, enquanto em outros contextos será expandida para a declaração. Mas com o C ++ 11, há uma opção mais limpa se você usar a inicialização dos membros da classe ao declarar. No arquivo de cabeçalho, declare a classe com as linhas:#define DEF_STR(__name, __text) \
const char __name[sizeof(__text)] = __text;
class Strings {
public:
DEF_STR(Prompt, "Are you sure you want to format SD-card?")
DEF_STR(OtherString, "...")
…
} __attribute__((packed));
extern const Strings strings PROGMEM;
No arquivo .cpp:const Strings strings PROGMEM;
Agora todas as linhas são declaradas em um só lugar, colocadas na memória do programa e você pode acessá-las assim:PromptUser(strings.prompt);
Neste projeto, uma abordagem baseada no mesmo princípio é usada para determinar bitmaps - várias imagens exibidas em uma exibição gráfica.
struct Bitmap {
const u8 *data;
u8 numPages,
numColumns;
} __PACKED;
template<u8... data>
constexpr static u8
Bitmap_NumDataBytes()
{
return sizeof...(data);
}
#define DEF_BITMAP(__name, __numPages, ...) \
const u8 __CONCAT(__name, __data__) \
[Bitmap_NumDataBytes<__VA_ARGS__>()] = { __VA_ARGS__ }; \
const Bitmap __name { \
reinterpret_cast<const u8 *>(OFFSETOF(Bitmaps, __CONCAT(__name, __data__))), \
__numPages, \
sizeof(__CONCAT(__name, __data__)) / __numPages};
class Bitmaps {
public:
DEF_BITMAP(Thermometer, 1,
0b01101010,
0b10011110,
0b10000001,
0b10011110,
0b01101010
)
DEF_BITMAP(Sun, 1,
0b00100100,
0b00011000,
0b10100101,
0b01000010,
0b01000010,
0b10100101,
0b00011000,
0b00100100
)
...
};
extern const Bitmaps bitmaps PROGMEM;
A diferença é que, além dos próprios dados da imagem, também é necessário colocar atributos (tamanhos da imagem). Cada byte define uma coluna de oito pixels. As colunas podem preencher uma ou mais linhas; seu número é indicado pelo segundo parâmetro após o nome. Acontece que a altura dos bitmaps deve ser um múltiplo de oito para uma largura arbitrária, o que é bastante aceitável para este projeto.Literais binários
Você já deve ter notado que os bitmaps no exemplo anterior usam literais binários para determinar. Isso é realmente muito conveniente - você pode editar bitmaps simples diretamente no código, especialmente se o editor permitir destacá-los. Por exemplo, definições de caracteres de fonte no arquivo font.h:
Modelos variáveis
Onde então sem eles então. Bem, por exemplo, os comandos para o controlador de exibição podem ter um comprimento de um a vários bytes. Enviado com o seguinte código:SendCommand(Command::DISPLAY_ON);
SendCommand(Command::SET_COM_PINS, COM_PINS | COM_PINS_ALTERNATIVE);
SendCommand(Command::SET_COLUMN_ADDRESS, curVp.minCol, curVp.maxCol);
Conveniente, não é?
template <typename... TByte>
void
SendCommand(TByte... bytes)
{
cmdSize = sizeof...(bytes);
controlSent = false;
cmdInProgress = true;
SetCmdByte(sizeof...(bytes) - 1, bytes...);
i2cBus.RequestTransfer(DISPLAY_ADDRESS, true,
CommandTransferHandler);
}
template <typename... TByte>
inline void
SetCmdByte(int idx, u8 byte, TByte... bytes)
{
cmdBuf[idx] = byte;
SetCmdByte(idx - 1, bytes...);
}
inline void
SetCmdByte(int, u8 byte)
{
cmdBuf[0] = byte;
}
O arquivo variant.h descreve uma classe que se assemelha vagamente a boost :: variant usando modelos variados. É usado para organizar as páginas da interface do usuário. O ponto está novamente na economia de memória - onde o gerenciamento dinâmico de memória é um luxo inadmissível, você precisa se esquivar (embora 2K ainda seja muito, você não podia se esquivar, mas na mesma linha ATMEGA seu tamanho atinge 512 bytes e cada byte por conta). Na minha interface, uma página é exibida na tela a qualquer momento. Assim, para todas as páginas você pode usar o mesmo pedaço de memória, o que é chamado de união em C. Para classes em C ++, isso geralmente é chamado de variante. Ao contrário da união, precisamos lembrar de chamar o destruidor do conteúdo anterior antes de chamar o construtor do novo. Variant<MainPage,
Menu,
LinearValueSelector,
TimeSelector> curPage;
...
template <class TPage>
static constexpr u8
GetPageTypeCode()
{
return decltype(curPage)::GetTypeCode<TPage>();
}
...
curPage.Engage(nextPageTypeCode, page);
Para a compilação, os binutils GCC e GNU são usados para a plataforma AVR (no Ubuntu há um pacote pronto gcc-avr). Os detalhes do processo de montagem foram dados acima. Os parâmetros para o compilador são parecidos com este (omitimentos e inclusões específicos do projeto são omitidos): Link: Converte a seção de código em formato hexadecimal: Crie uma imagem EEPROM: Firmware para o microcontrolador: PS Os primeiros tomates já estavam maduros e não tinham um sabor muito bom. Aparentemente, eles não gostaram de algo na dieta. Provavelmente tem que mudar a cultura.avr-g++ -o build/native-debug/src/firmware/cpu/lighting.cpp.o -c -fno-exceptions -fno-rtti -std=c++1y -Wall -Werror -Wextra -ggdb3 -Os -mcall-prologues -mmcu=atmega328p -fshort-wchar -fshort-enums src/firmware/cpu/lighting.cpp
avr-g++ -o build/native-debug/src/firmware/cpu/cpu -mmcu=atmega328p build/native-debug/src/firmware/cpu/adc.cpp.o build/native-debug/src/firmware/cpu/application.cpp.o …
avr-objcopy -j .text -j .data -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_rom.hex
avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex build/native-debug/src/firmware/cpu/cpu build/native-debug/src/firmware/cpu/cpu_eeprom.hex
avrdude -p atmega328p -c avrisp2 -P /dev/avrisp -U flash:w:build/native-debug/src/firmware/cpu/cpu_rom.hex:i