
Continuamos a desmontar o componente de software da plataforma MIRO. Eu gostaria de examinar com mais detalhes exatamente o software no AVR. Portanto, dedicaremos duas partes ao problema. No primeiro, descrevemos a estrutura geral da biblioteca e, no segundo, a implementação de alguns métodos de classe-chave.
Índice:
Parte 1 ,
Parte 2 ,
Parte 3 ,
Parte 4 ,
Parte 5 .
O software para ARDUINO foi o maior dos auto-escritos. Em geral, quase toda a lógica de trabalhar diretamente com atuadores e sensores do robô está no AVR. E nesse nível, a API é implementada - uma biblioteca de software de desenvolvimento rápido para o MIRO.
A estrutura da API é descrita na seção wiki do
repositório correspondente. Até agora, apenas em russo. E agora vamos analisar o código em mais detalhes. Não intencionalmente darei declarações de classe completas, abreviando-as com as reticências "...", deixando apenas coisas que são significativas no momento.
Em nosso modelo de software, cada robô MIRO consiste em um chassi e um conjunto de dispositivos conectados. Ao projetar, assumiu-se que o chassi do robô - sempre será algum tipo de chassi com rodas - robôs ou robôs que usam alguns outros princípios de movimento devem ser considerados separadamente.
class Miro : public Robot { public: Miro(byte *PWM_pins, byte *DIR_pins); #if defined(ENCODERS_ON) Miro(byte *PWM_pins, byte *DIR_pins, byte *ENCODER_pins); #endif ~Miro(); ... };
A classe Miro é uma classe de nível superior e descreve a configuração completa do robô. Essa classe é descendente da classe Robot, que descreve apenas a funcionalidade mais básica do robô.
class Robot { public: Robot(byte *PWM_pins, byte *DIR_pins); #if defined(ENCODERS_ON) Robot(byte *PWM_pins, byte *DIR_pins, byte *ENCODER_pins); #endif ~Robot(); Chassis chassis; void Sync(); int attachDevice(Device *dev); int dettachDevice(Device *dev); ... protected: Device* _devices[ROBOT_MAX_DEVICES]; byte _device_count; };
O designer executa a configuração inicial dos pinos do chassi e os valores iniciais da configuração do robô.
O método Sync () implementa as operações necessárias para o chassi e para todos os dispositivos conectados ao robô, cada etapa do ciclo do loop principal () do esboço ARDUINO. Os métodos Miro Sync () da classe Miro invocam os métodos Sync () correspondentes do chassi e de todos os dispositivos conectados ao robô.
A classe Robot também contém um ponteiro para uma matriz de dispositivos conectados ao robô, métodos para trabalhar com essa matriz (conectar um novo dispositivo, desconectar, localizar por índice e nome). A classe Robot também contém um objeto da classe Chassis - o chassi.
Mas vamos começar com algo mais simples - com dispositivos. Cada dispositivo que pode ser conectado ao robô, seja um LED, um sensor ou um atuador que não se relacione diretamente ao chassi (carrinho), é descrito por sua classe sucessora, que é comum a todos os dispositivos na classe virtual Device:
class Device { public: virtual void Sync(); virtual void setParam(byte pnum, byte *pvalue); virtual void getParam(byte pnum, byte *pvalue); virtual byte getPinsCount(); virtual char* getName(); virtual byte getParamCount(); protected: byte *pins[2]; };
Os métodos virtuais setParam, getParam, getParamCount estão associados à atribuição, recebimento e determinação do número de parâmetros do dispositivo. O parâmetro pode ser qualquer propriedade: o brilho do LED, a posição do servoconversor, etc. A classe sucessora de cada dispositivo implementa esses métodos à sua maneira. O objetivo dos métodos getName, getPinsCount, eu acho, é claro a partir do nome. O novo método Sync encontrado é um método especial para controle e automação sem bloqueio de dispositivos de algumas operações com o dispositivo que devem ser executadas regularmente, a cada iteração do loop principal.
Vejamos agora uma implementação mais ou menos geral da classe descendente.
class MIROUsonic : virtual public Device { public: void Sync(); void setParam(byte bnum, byte *pvalue); void getParam(byte bnum, byte *pvalue); byte getPinsCount(); char* getName(); byte getParamCount(); void Init(byte trig_pin, byte echo_pin); void On(unsigned int max_dist); void On(); void Off(); int getDist(unsigned int max_dist); unsigned int getMesCount(); private: bool _isOn; unsigned int _mesCount; unsigned int _dist; unsigned int _max_dist; };
Ao determinar a classe de um telêmetro ultrassônico (acima), além dos métodos dos pais, também existem métodos:
- Init - inicialização;
- On e Off - ligue o dispositivo (telêmetro);
- getDist - retorna a distância medida pelo localizador;
- getMesCount - retorna o número de medidas tomadas desde que o dispositivo foi ligado.
Para armazenar o estado interno do dispositivo, os seguintes campos são usados:
- _isOn (TRUE - o dispositivo é ligado, controlado pelos métodos On e Off);
- _mesCount (armazena o número de dimensões usadas no método getMesCount);
- _max_dist - distância máxima requerida para medição *;
- _dist é a distância real medida.
Sobre a faixa máxima de medição* Sabe-se que o HC-SR04 difundido de acordo com o passaporte é capaz de medir distâncias de até 4 metros. No entanto, o próprio método de medição envolve aguardar o retorno do sinal ultrassônico, seguido pela codificação da duração do sinal na linha Echo. De fato, se o usuário definitivamente não precisar medir distâncias no alcance de até 4 metros, mas o suficiente, digamos 1 metro, você poderá esperar 4 vezes menos pelo sinal refletido. O próprio rangefinder gera um sinal na linha Echo assim que o recebe e executa a modulação. I.e. isso pode não afetar a duração do período entre medições adjacentes, mas a duração de uma única medição dessa maneira pode ser reduzida.
E agora para a explicação do método Sync. Se o dispositivo tiver o estado _isOn == TRUE (ativado), o próprio ciclo de medição será realizado no método Sync, e o resultado da medição será registrado no campo _dist. Nesse caso, quando você chamar getDist, o método retornará imediatamente o valor registrado em _dist, não haverá ciclo de medição. Se _isOn == FALSE (desativado), o ciclo de medição, pelo contrário, é realizado apenas durante a chamada para getDist, nada será medido no método Sync. Supõe-se que o programador chame o método Sync de todo o robô, que por sua vez chamará os métodos Sync com o mesmo nome de todos os dispositivos conectados ao robô e um objeto da classe Chassis (chassi).
Dos dispositivos na API, apenas o que o MIRO possui agora é implementado: LED, telêmetro ultrassônico, sensor de luz foto-resistivo, servoconversor, sensor de linha.
Toque levemente no chassi. Esta classe implementa o "carrinho abstrato" do robô. Ele contém métodos que permitem controlar movimentadores.
class Chassis { public: Chassis(byte *PWM_pins, byte *DIR_pins); #if defined(ENCODERS_ON) Chassis(byte *PWM_pins, byte *DIR_pins, byte *ENCODER_pins); #endif ~Chassis(); void Sync(); float getVoltage(); int wheelRotatePWMTime(int *speedPWM, unsigned long time); int wheelRotatePWM(int *speedPWM); bool wheelIsMoving(byte wheel) {return this->_wheel_move[wheel];} byte getWheelCount() { return WHEEL_COUNT; } #if defined(ENCODERS_ON) int wheelRotateAng(float *speed, float *ang, bool en_break); unsigned long wheelGetEncoder(byte wheel); ... #endif
Se considerarmos um carrinho sem codificadores e geralmente sem feedback, existem métodos de controle simples para isso usando um sinal PWM. Se houver codificadores no carrinho, a classe se torna muito mais complicada. Para simplificar a vida do usuário, métodos como o aparecem:
- wheelRotateAng - rotação da roda em ângulos de rotação pré-determinados com determinadas velocidades angulares;
- wheelGetPath - retorna o comprimento do caminho percorrido por cada roda;
- wheelGetLinSpeed - retorna a velocidade linear atual de cada roda;
- wheelGetAngSpeed - retorna a velocidade angular atual de cada roda;
- wheelGetEncoder - retorna o número de respostas dos codificadores de cada roda.
E uma série de métodos auxiliares. Bem como um método de calibração de propulsão. Porém, com mais detalhes, os principais métodos da classe Chassis serão considerados na próxima vez.
Seguindo um pouco à frente, é neste local que será apropriado notar que toda a biblioteca Miro pode ser facilmente adaptada ou complementada com qualquer outro robô com um esquema de movimento diferencial de duas rodas. E com um certo esforço - e com outras configurações de propulsão e direção. No caso de um circuito diferencial, você só precisa descrever corretamente o arquivo de configuração config.h. E sem nenhum RPi. Por exemplo, em menos de uma hora, lançamos tudo sobre esses pequenos para o torneio regional de segurança cibernética BlackMirrorCTF-2019 em nossa universidade (
link ).

Os robôs tinham uma interface para acesso via TELNET e um sistema de comando para controle remoto. O documento com o sistema de comando estava em algum lugar oculto ou codificado. Os participantes examinaram os endereços IP e abriram portas nos próprios robôs. Com uma conexão bem-sucedida, os robôs fizeram um convite e os participantes entenderam que haviam "entrado". Bem, então as equipes trouxeram os robôs ao longo da estrada para a linha de chegada. Inicialmente, eles queriam fazer a faixa inteira com robôs em algum lugar de uma sala isolada com uma câmera IP instalada, mas os organizadores tiveram alguns problemas com a câmera IP e perderam parte do charme.
Por enquanto é tudo. É possível que, no decorrer do desenvolvimento, o modelo do programa sofra alterações. A propósito, ele foi ligeiramente modificado recentemente, depois que o código parecia um pouco mais experiente OOP-shchik.
Antes da quinta parte - vamos falar sobre codificadores, ângulos e calibrações.