El proyecto no contiene Arduino.Originalmente, se suponía que este proyecto tenía un aspecto diferente: una estructura monumental que consiste en un pedestal con latas y bombas, un acuario montado en él y un oasis de tomate en la parte superior. Se planeó una cascada en el paraíso de un oasis de tomate, y se forman peces en el acuario, cuyo principal requisito era la capacidad de comer a los habitantes no planificados del acuario y mantener limpio el vidrio; Los principales candidatos son Somiki y Gourami. Como habrás adivinado, mi lema es "la pereza es el motor del progreso" (y qué puedes hacer para no limpiar el acuario y no regar los tomates).Probablemente se habría erigido un monumento a este lema si aún no se hubiera derrumbado en la etapa de coordinación de bosquejos con su esposa. No se inspiró en la idea de hacer de esta bandura la decoración principal de la sala de estar, e incluso la cascada no la convenció de esto. Pero la idea de un sistema autónomo, una simbiosis de biología y electrónica, no quería salir volando de mi cabeza, y el proyecto se redujo al tamaño de una maceta: la acuaponía se convirtió en hidroponía, se salvaron vidas de peces.La idea principal de la hidroponía es el uso de una solución acuosa de nutrientes en lugar de tierra. Esto permite un orden de magnitud para acelerar el crecimiento de las plantas. Sin embargo, uno no puede simplemente bajar las raíces al agua: necesitan oxígeno, sin el cual comenzarán a morir. A este respecto, hay opciones: soplar agua constantemente con un compresor, como en un acuario, o periódicamente inundar las raíces con una solución nutritiva y drenarla después de un tiempo. La primera opción tiene un inconveniente: el zumbido constante del compresor. La segunda opción tiene una ventaja: la mayoría de las raíces están en el aire, respirando activamente, y el efecto de acelerar el crecimiento debería ser aún mayor. Además, se sumergen en un sustrato de gránulos porosos especiales que retienen la humedad. La elección era obvia, tomé la segunda opción como base.En el caso de los peces, el sistema podría estar casi completamente cerrado: las secreciones de peces son procesadas por bacterias especiales en un biofiltro, el producto procesado se alimenta a las plantas, una capa de arena filtra el agua y el agua limpia se devuelve al acuario. En un caso ideal, el alimento se espolvorea ocasionalmente en un alimentador automático y los tomates se juntan de los arbustos. Pero no creció juntos, tal vez sea para mejor, quién sabe cómo terminaría el pedido por correo de una cepa de las bacterias necesarias.Como resultado, el dispositivo de la planta de tomate tomó contornos. Dos recipientes: el inferior con agua, el superior con un sustrato y una planta. Para inundaciones usaremos una pequeña bomba china con un motor DC, para drenaje utilizaremos un sifón automático. El principio de funcionamiento del sifón en el video:Hidroponía con un sifón similar:El cerebro del dispositivo es el microcontrolador ATMEGA328P (simplemente porque el placer estaba a la mano). Sus tareas incluyen gestionar las inundaciones y descargas de acuerdo con un cronograma, monitorear el nivel de agua en el tanque y señalar su falta, controlar la iluminación de la planta (queremos tener una cierta longitud mínima de luz del día; cuando la luz natural termina, la iluminación artificial se enciende gradualmente), una interfaz de usuario para ver El estado, la gestión y la configuración de toda esta economía. Obviamente, esto requiere algún tipo de solución para el sensor de nivel de agua, sensores de luz, un reloj en tiempo real y algún tipo de terminal de usuario.Antes de describir los detalles, una lista de recursos del proyecto:Aquí puede ver fotos del resultado y el proceso de fabricación.Video corto:El proyecto está disponible en GitHub . Allí, en los lanzamientos, se presenta un archivo con el proyecto de pieza electrónica en KiCAD y los proyectos de campanas y silbatos de diseño en SolidWorks (se adjuntan archivos STL para imprimir).Características del ensamblaje de firmware—
« ». , , , USB AVR (, , , , ), . - , , 'ADK_ROOT' , 'scons'.
Esquema de la parte electrónica:
más detalles, una descripción de las trampas y un poco de código. Descripción de los problemas de software al final . Quizás alguien esté interesado en ver un nuevo ejemplo de trabajo con I2C, un valcoder, un módulo RTC y una pantalla gráfica. Todo el código en el proyecto fue escrito "desde cero" sin usar soluciones de terceros (porque puedo).Sensor de nivel de agua
El tema más delicado se decidió primero. Había, por supuesto, una variante de algún tipo de flotador para que, por ejemplo, moviera el riel en el que se aplicaba el código Gray y se leyeran los sensores ópticos. Pero realmente no parecía confiable. La búsqueda en eBay no dio resultado: hubo interruptores de flotador (se alcanzó o no el nivel deseado), o electrodos sumergidos y lecturas basadas en la conductividad del medio, pero esto se observó de inmediato, ya que la composición del agua cambiaría constantemente junto con la conductividad de los fertilizantes agregados y la disolución. impurezas del sustrato. Como resultado, surgió la idea de utilizar un telémetro ultrasónico, uno de los que generalmente se colocan en diferentes robots. Según lo planeado, el sensor se coloca en la tapa del tanque y la señal se refleja directamente desde la superficie del agua. Se compró HC-SR04 (la elección del valor más pequeño de la distancia mínima de trabajo, tiene 2 cm),y el concepto fue revisado en un balde de agua. Resultó que funcionaba por sí mismo (existía el temor de que no hubiera un reflejo normal de la superficie del agua, o que no hubiera suficiente directividad del haz y hubiera reflejos no deseados de las paredes del tanque). Por cierto, el telémetro también era una opción de respaldo, pero infrarrojo. En la superficie del agua se suponía que arrojaría un flotador con un reflector. El único problema es su distancia mínima de trabajo de 10 cm (de los que encontré), que ya es un poco demasiado para las dimensiones dadas.En la superficie del agua se suponía que arrojaría un flotador con un reflector. El único problema es su distancia mínima de trabajo de 10 cm (de los que encontré), que ya es un poco demasiado para las dimensiones dadas.En la superficie del agua se suponía que arrojaría un flotador con un reflector. El único problema es su distancia mínima de trabajo de 10 cm (de los que encontré), que ya es un poco demasiado para las dimensiones dadas.
Según los resultados del proyecto, este enfoque está funcionando y se puede utilizar en la práctica, no se notaron problemas. Vale la pena tomar medidas para aislar la placa de la humedad (sellado en el estuche). Esos son solo los sensores que permanecen abiertos, tal vez aún se da la vuelta.La interfaz del sensor es simple: se envía un pulso a la entrada de activación, que activa una señal de eco. Se genera un pulso en la salida de eco, cuya longitud es igual al tiempo desde el comienzo de la radiación hasta la aceptación de la señal de eco reflejada. Al medir la longitud del pulso, conocer la velocidad del sonido y el hecho de que la señal va al objeto y viceversa, puede calcular la distancia. En el proyecto, esto se implementa en la clase LevelGauge. Para medir la longitud del pulso, se utiliza la capacidad de hardware de la "captura de entrada" MK AVR. En este caso, el temporizador de hardware se restablece en el borde ascendente del pulso, y en el valor descendente del temporizador, el hardware se almacena en el registro ICR1 y se genera una interrupción. Por lo tanto, es posible medir la duración del pulso con suficiente precisión y un consumo mínimo de tiempo de procesador.Incluso con este modelo del sensor, se notó una falla: cuando se aplicó la energía, la línea de eco permaneció constantemente activa. Pasó por alto aplicando un pulso al gatillo y esperando hasta que pasara el primer ciclo de localización del eco.Iluminar desde el fondo
La retroiluminación está compuesta por tres LED de agarre. Doblé el marco triangular del perfil de aluminio, pegué los LED con epoxi. Pedí un estabilizador de corriente chino a 700mA para poder. Alrededor de tres voltios caen en cada diodo, el estabilizador requiere una diferencia entre los voltajes de entrada y salida de al menos dos voltios, e iba a alimentar todo el wunderwafer desde una fuente de alimentación de 12 voltios. Desde aquí es fácil calcular por qué exactamente tres LED.Los diodos son de color blanco cálido. Me pareció natural, el espectro solar y todo eso. Pero como descubrí más tarde, después de ordenarlos, las plantas generalmente usan una combinación de rojo y azul. Por lo que yo entiendo, toda la cuestión es solo de eficiencia. Si tiene una granja grande con iluminación las 24 horas, le interesa que toda la energía gastada se gaste para siempre. Bajo iluminación blanca, las hojas verdes reflejarán el componente verde, una parte significativa de la energía gastada en iluminación se desperdiciará.

Una característica importante del estabilizador es la presencia de una entrada para la regulación PWM, que uso para ajustar el brillo. Aquí hay otro rastrillo chino. En primer lugar, resultó ser solo una función de encendido / apagado actual. Es decir, esperaba que la corriente de salida no se modulara, y su valor dependería del ciclo de trabajo de la señal PWM, pero la corriente simplemente repitió los pulsos en la entrada de control. Pero esto no es tan malo, otra emboscada fue que el regulador reacciona de manera inadecuada a PWM con una frecuencia bastante alta. Tuve que bajarlo a 300Hz, en el que funcionó más o menos normalmente. La señal PWM es generada por el microcontrolador en el hardware utilizando uno de los temporizadores.
Otra parte importante del conjunto de retroiluminación son los sensores de luz. Los fototransistores fueron seleccionados en este papel. Y sí, hay dos: uno encima de los LED para medir la luz natural, el segundo debajo de los LED para proporcionar información. Es cierto que la funcionalidad de extensión automática de la luz del día aún no se ha implementado, como lo fue en el verano, y no era necesaria (y la motivación es un asunto serio). Se supuso que tan pronto como el primer sensor detecta una disminución en el nivel de iluminación (y el tiempo asignado para las horas de luz aún no ha expirado), la luz se regula para que el segundo sensor produzca un nivel correspondiente a la iluminación deseada. Para hacer esto, debe implementar un controlador PID simple en el código. Pero mientras está en la interfaz, solo puede ver las lecturas actuales del sensor y enrollar manualmente el brillo de la luz de fondo deseada.Presta atención a la conexión de los sensores. Cada uno de ellos tiene dos rangos fijos, que se seleccionan conectando a cero la resistencia correspondiente. El pie del microcontrolador, conectado a la segunda resistencia, en este momento se transfiere a un estado de alta resistencia. Puede encender ambas resistencias simultáneamente, luego habrá tres rangos de medición fijos. La señal de las resistencias del emisor se pasa a través de un circuito RC para filtrar los pulsos de modulación: la luz de los LED pulsa junto con la señal PWM en el regulador de corriente.Bomba
El equipo chino más barato, con motor de corriente continua. Las emboscadas, por supuesto, están disponibles. A pesar de que dice 12V, no funciona durante mucho tiempo a este voltaje. Uno se quemó antes del montaje de la estructura. El esquema proporciona PWM para ello, la potencia máxima se configura en la interfaz, en la práctica no se estableció por encima del 70%. Ya a este nivel, aúlla salvajemente en el trabajo, pero la mayoría de las veces trabaja a una potencia mucho menor, alrededor del 30% y retumba en silencio. Sobre sus modos de funcionamiento a continuación, en la descripción de la lógica de las inundaciones. El condensador más grande (C8 en el diagrama) debe colocarse más cerca del circuito de alimentación de la bomba, de lo contrario habrá una gran interferencia en todo el circuito (en la práctica, resultó que el regulador de corriente para los LED es más sensible a ellos, comienza la música ligera).Reloj en tiempo real
Había una idea loca de usar los recursos del microcontrolador para estos fines. El generador de reloj de cuarzo tiene una precisión bastante buena, en otro proyecto este enfoque funcionó bien. Pero el problema es que absolutamente todos los temporizadores de hardware ya se tomaron para otros fines. No había más remedio que encontrar el módulo RTC externo. Elogie a los chinos, están allí y son baratos.
El módulo basado en el DS3231 tiene una interfaz I2C, su propia fuente de alimentación redundante: el tiempo no fallará con el apagón. Hay una salida de meandro en varias frecuencias fijas: 1 kHz, 4 kHz y 8 kHz. Esto fue muy útil para las señales de audio; de nuevo, no es necesario cargar la MCU y no había temporizadores libres para esto. La EEPROM de 32Kbit es una ventaja, pero no se usa en este proyecto.Sorprendentemente, es muy preciso: en unos meses perdió su fuerza durante varios segundos. Afirmó que tiene en cuenta la influencia de la temperatura en la frecuencia del generador, y aparentemente esto funciona. Sin embargo, si el tiempo pasa, existe la posibilidad de corrección de frecuencia de software. Las lecturas del sensor de temperatura están disponibles, y en este proyecto se muestran en la interfaz.La clase Rtc es responsable de trabajar con este módulo en el código.Monitor
Hace tiempo que quería hacer algo con una pantalla gráfica. La búsqueda del más barato con la interfaz I2C le dio esta opción.
Pantalla OLED monocromática de 128x64 píxeles basada en el popular controlador SSD1306. Al elegir, debe mirar detenidamente la descripción: el mismo chip admite otras interfaces, excepto I2C, y hay opciones sin él. O escriben que es universal, también es compatible con I2C, pero en realidad será necesario modificar ligeramente la placa reorganizando los valores nulos a otros sitios. Por lo tanto, si planea usar I2C, es mejor elegir uno donde solo se muestre I2C en la placa, habrá menos problemas con una placa que no tiene casi ninguna documentación (documentación solo para el chip). Esta versión funciona desde 5V, la placa tiene un regulador de 3.3V requerido para el controlador. Me encontré con críticas que en algunas versiones pueden no serlo.La pantalla está generalmente satisfecha. Solo noté una característica desagradable: el brillo de una fila de píxeles depende de cuántos píxeles se iluminen. Cuanto más iluminado, menor es el brillo. El contraste entre las líneas puede ser notable si se alternan áreas completamente llenas con algunos elementos estrechos en la pantalla. Pero en la práctica, esto no es visible en mis fotos y no es sorprendente.El controlador se puede configurar para operar en varios modos de visualización del contenido de la memoria de la pantalla en una matriz de píxeles. Fue más conveniente para mí cuando cada byte se mapea en una columna vertical de ocho píxeles de altura, y las columnas van horizontalmente de izquierda a derecha, llenando la pantalla con líneas de ocho píxeles de altura. En este modo, es más conveniente dibujar texto.A menudo, se practica un enfoque en el que la memoria de visualización se duplica en la RAM MCU: primero, todas las acciones con la imagen se realizan en RAM, y luego todos los píxeles modificados se copian en la memoria de visualización. En este proyecto, este enfoque no se utiliza para ahorrar recursos. Todos los lugares modificados se vuelven a dibujar inmediatamente en la memoria de la pantalla.Como se sugiere en los comentarios, las pantallas OLED se desvanecen con el tiempo. También sospeché esto (recordando qué es el protector de pantalla), y proporcioné que la pantalla se apagara después de unos minutos después de la última actividad en los controles. Se enciende al girar o presionar el codificador.En el código, el trabajo con la pantalla se implementa en la clase Display.Valkoder:
En mi opinión, el valcoder es la mejor opción de control para dispositivos que tienen al menos alguna interfaz de usuario. Es compacto y muy cómodo. Es conveniente para ellos hojear y seleccionar elementos del menú, cambiar los valores de cualquier parámetro, cambiar de modo, etc.Para conectar, se requieren tres patas de entrada del microcontrolador. Uno para el botón (puede presionar la manija), dos para el propio valcoder. Hay una señal de código gris del codificador . En cada paso de giro, un bit cambia en dos líneas. La secuencia determina la dirección de rotación.Todo parece ser simple, pero, aparentemente, los desarrolladores no siempre pueden ofrecer soporte de alta calidad para dicho dispositivo. Por ejemplo, en mi impresora 3D hay una placa RAMPS y una placa con pantalla y está conectado exactamente el mismo codificador. El firmware de Marlin funciona con él, pero la experiencia de usarlo es muy mala, no hay sensación de confiabilidad, cuando hace clic en la perilla al girarla, la interfaz a menudo se detiene en el elemento de menú o valor de parámetro incorrecto en el que se esperaba. Con una rotación rápida, parece que se saltan los clics. En algún momento, el cambio no comienza durante los clics, pero en algún punto intermedio es muy desagradable. Sí, qué es Marlin, a veces tengo la misma sensación sobre el sistema multimedia incorporado en el automóvil. En este sentido, algunos consejos (y, por supuesto, mire el código en la vecindad de la clase RotEnc).En primer lugar, un punto bastante obvio para cualquiera que conecte cualquier botón al microcontrolador: debe lidiar con el rebote. Este codificador mecánico y sus líneas de señal son, de hecho, los mismos botones, y también tienen un parloteo. Primero, filtramos la conversación, luego procesamos la secuencia de estados de las líneas de señal. Puede haber valcoders con sensores ópticos, ya depende de los esquemas de procesamiento de señal de ellos. Si las patas de un fototransistor se sacan directamente, entonces puede sonar allí con una rotación lenta, pero si hay algún esquema de procesamiento que introduzca histéresis, entonces no se requiere la supresión de software. Pero tales dispositivos son más caros y rara vez se usan en dispositivos aficionados, los más comunes son mecánicos, unos pocos dólares por grupo.En segundo lugar, un punto un poco menos obvio, probablemente uno de aquellos en los que Marlin se quemó: el mango tiene posiciones estables durante la rotación: clics (clics). Este modelo tiene cuatro pasos de una secuencia de código para cada clic. Por lo tanto, debe responder a los clics y no a los pasos de la secuencia. Y lo más importante es sincronizar con posiciones estables. Muchos simplemente ingresan la constante STEPS_PER_CLICK y, por ejemplo, responden a cada cuarto paso. Pero el problema es que la señal no es perfecta, las secuencias pueden no ser del todo correctas. Con una cierta ortografía, el código puede "extraviarse", como resultado, cada cuarto paso se obtendrá en algún lugar en el medio del clic, lo que será incómodo para el usuario. Al mismo tiempo, la posición fija del controlador para un modelo particular corresponde a un valor de código fijo,debe estar unido a él.En tercer lugar, una vez más, un punto bastante obvio para los desarrolladores más o menos experimentados de sistemas de microcontroladores: use interrupciones de hardware para cambiar el estado de las líneas de entrada. Como mínimo, habrá menos riesgo de "perder" los pasos de la secuencia. Pero en general, como saben, las interrupciones son nuestro todo. La MCU debe dormir siempre que sea posible, despertando solo con interrupciones, ya sea desde la periferia exterior o desde un temporizador para realizar una tarea retrasada. Estos son los principios del buen diseño de la arquitectura del sistema.Diseño en su conjunto
Está hecho de materiales improvisados y varias partes impresas en una impresora 3D de ABS.El principio de funcionamiento del sifón se ilustra en el video de arriba. Para mí, es un tubo externo de PVC y un tubo interno con un embudo al final. Para el sifón clásico, se requiere otra rodilla, pero ya era difícil hacerlo constructivamente para mí. Cuando se encontraron problemas con el drenaje, se pegó un pequeño baño en la pared del tanque inferior, en el que se sumergió el extremo del tubo interior, y se creó resistencia al drenaje, para que el sifón pudiera funcionar.Resultó que el ABS es un material muy hidrófobo. El agua literalmente no se desborda; incluso tuve que rehacer el embudo del sifón. Vale la pena considerar esta propiedad, es imposible crear algunos sistemas hidráulicos en miniatura (por ejemplo, quería hacer superficies de guía en el embudo del sifón, para torcer el agua, para mejorar la respuesta del sifón. Pero con tales dimensiones e hidrofobicidad del ABS esto no tiene sentido) .También intenté primero pegar todo junto con una pistola de pegamento caliente. No funciona, al principio todo parecía mantenerse firme, pero después de unos días se cayó. La mejor opción es Asia Central. Los detalles, incluso bajo el agua, se mantienen apretados.El mayor error de cálculo en el diseño son los contenedores transparentes. Olvidé por completo el hecho de que el agua florece a la luz. Tuve que envolverlo con material opaco. Bueno, periódicamente puede agregar permanganato de potasio para la desinfección, esto no parece dañar las plantas.El algoritmo de inundación es el siguiente: primero se enciende la bomba a baja potencia y llena silenciosamente todo el tanque superior. El proceso es monitoreado por un sensor de nivel. Cuando el agua comienza a desbordarse a través del embudo del sifón, la disminución del nivel en el tanque inferior se detiene, lo cual es detectado por el sensor. Un pequeño flujo creado a baja potencia no es suficiente para activar un sifón. La bomba se detiene, se recuerda el volumen bombeado al tanque superior. Las raíces se mantienen en la solución durante varios minutos, después de lo cual la bomba se enciende nuevamente. Primero, a baja potencia, hasta que el agua llegue al embudo nuevamente (durante el tiempo de inactividad, baja más debido al efecto del sifón), y cuando se alcanza el nivel del embudo, la bomba comienza a aumentar la potencia, proporcionando un flujo suficiente para que el sifón funcione. Se garantiza que el flujo a través del sifón será mayor que el flujo de la bomba,Como resultado, el nivel en el tanque inferior comienza a aumentar, el sensor lo detecta y la bomba se detiene.Los ciclos de inundación comienzan periódicamente, a intervalos fijos y configurables, desde el amanecer hasta el anochecer. Según el plan, se suponía que el amanecer debía ser fijado por el sensor de luz, y la duración de la luz del día, si era necesario, se extendía al valor establecido, pero hasta entonces las manos no lo habían alcanzado. El tiempo del amanecer se establece simplemente en la configuración.¿Y dónde está C ++ 11?
Quizás alguien dudará de que C ++ 11 pueda ser útil en la programación de microcontroladores (entre aquellos que generalmente saben que los microcontroladores se pueden programar en C ++). Trataré de dar ejemplos específicos de los beneficios de C ++ 11 en esta área (además de obvias cosas agradables como constexpr, override, default, etc.).Colocación de recursos de cadena
Mucha gente sabe que la RAM en los microcontroladores es un recurso muy limitado. Esto puede ser un problema si su aplicación, por ejemplo, tiene una interfaz de usuario y su programa usa una cantidad bastante grande de líneas. Si en el código escribir algo comoPromptUser("Are you sure you want to format SD-card?");
entonces la línea pasada en los argumentos se colocará en la sección de datos inicializados (en adelante, el comportamiento del compilador GCC para la plataforma AVR), es decir, en el área RAM, que al inicio (antes de llamar a la función principal) se inicializa desde la memoria flash del programa. La función PromptUser () pasará un puntero a la ubicación deseada en la RAM. Si utiliza un enfoque similar en todo el programa, la RAM terminará bastante rápido (en el ATMEGA328P utilizado en este proyecto es de solo 2 kilobytes, y esto también es para BSS, montón y pila). Para evitar esta limitación, funciones como PromptUser () aprenden a trabajar no con punteros a RAM, sino con punteros a una región en la memoria flash del programa. Puede leer desde allí solo con la ayuda de instrucciones especiales, que, por ejemplo, en avr-libc están envueltas en funciones de la familia eeprom_read_ [byte | word | dword | ...].En este caso, la cadena debe colocarse primero en una variable equipada con el atributo PROGMEM, que le dice al compilador que debe colocarse en la memoria del programa .char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
PromptUser(prompt);
Esto es inconveniente si desea declarar todas las líneas de forma centralizada. Luego, primero debe declarar su declaración en el archivo de encabezado:extern char prompt[] PROGMEM;
Y en un archivo .cpp separado, defina:char prompt[] PROGMEM = "Are you sure you want to format SD-card?";
Duplicación de código, lo que no es bueno y muy inconveniente cuando hay muchas de esas líneas. Sí, esto se puede eludir haciendo una macro complicada e incluyendo el archivo de encabezado en un archivo .cpp separado, en el que la macro se expandirá a la definición, mientras que en otros contextos se expandirá a la declaración. Pero con C ++ 11 hay una opción más limpia si usa la inicialización de los miembros de la clase al declarar. En el archivo de encabezado, declare la clase con las líneas:#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;
En el archivo .cpp:const Strings strings PROGMEM;
Ahora todas las líneas se declaran en un solo lugar, se colocan en la memoria del programa, y puede acceder a ellas de esta manera:PromptUser(strings.prompt);
En este proyecto, se utiliza un enfoque basado en el mismo principio para determinar mapas de bits: varias imágenes que se muestran en una pantalla 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;
La diferencia es que, además de los datos de la imagen en sí, también es necesario colocar atributos (tamaños de imagen). Cada byte define una columna de ocho píxeles. Las columnas pueden llenar una o más filas; su número se indica mediante el segundo parámetro después del nombre. Resulta que la altura de los mapas de bits debe ser un múltiplo de ocho para un ancho arbitrario, lo cual es bastante aceptable para este proyecto.Literales binarios
Es posible que ya haya notado que los mapas de bits en el ejemplo anterior usan literales binarios para determinar. Esto es realmente muy conveniente: puede editar mapas de bits simples directamente en el código, especialmente si el editor permite resaltarlos. Por ejemplo, las definiciones de caracteres de fuente en el archivo font.h:
Plantillas variadas
Donde entonces sin ellos entonces. Bueno, por ejemplo, los comandos para el controlador de pantalla pueden tener una longitud de uno a varios bytes. Enviado con el siguiente 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, ¿no es así?
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;
}
El archivo variant.h describe una clase que se asemeja vagamente a boost :: variant usando plantillas variadic. Se utiliza para organizar páginas de interfaz de usuario. El punto es nuevamente en el ahorro de memoria: donde la administración dinámica de la memoria es un lujo inadmisible, debe esquivar (aunque 2K todavía es mucho, no puede esquivarlo, pero en la misma línea ATMEGA su tamaño alcanza 512 bytes, y cada byte por cuenta). En mi interfaz, se muestra una página en la pantalla en cualquier momento. En consecuencia, para todas las páginas puede usar la misma memoria, lo que se llama unión en C. Para las clases en C ++, esto generalmente se llama variante. A diferencia de la unión, debemos recordar llamar al destructor del contenido anterior antes de llamar al constructor del nuevo. Variant<MainPage,
Menu,
LinearValueSelector,
TimeSelector> curPage;
...
template <class TPage>
static constexpr u8
GetPageTypeCode()
{
return decltype(curPage)::GetTypeCode<TPage>();
}
...
curPage.Engage(nextPageTypeCode, page);
Para la compilación, los binutils de GCC y GNU se utilizan para la plataforma AVR (en Ubuntu hay un paquete listo para usar gcc-avr). Los detalles del proceso de montaje se dieron anteriormente. Los parámetros para el compilador se parecen a esto (se omiten los detalles y las inclusiones específicas del proyecto): Enlace: Convierta la sección de código a formato hexadecimal: Cree una imagen EEPROM: Firmware para el microcontrolador: PS Los primeros tomates ya estaban maduros y no sabían muy bien. Aparentemente, no les gustó algo en la dieta. Probablemente tenga que cambiar la 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