exemplo, uso do Decoder 4511), e por este motivo a implementação é bastante completa permitindo que seja desenvolvida na direção que vc ou outros desejem. Esta também é uma ótima oportunidade para mostrar a implementação de coisas que podem até parecer complexas a princípio, mas que são na realidade bem simples. Para isto, o Hardware é descrito em detalhes, e o código está totalmente organizado (de forma hierárquica) e comentado funcionalmente (o que é indispensável para se entender sem esforço, como ele funciona). Mesmo assim irei descrever aqui, alguns pontos principais para aqueles que não querem se aprofundar e desejam apenas utilizar os recursos. Aconselho a quem for utilizar, ler integralmente o texto aqui publicado.
Implementei também uma Simulação no Proteus, a qual permite testar o funcionamento do Sistema e também medir alguns parâmetros importantes. Sobre a Simulação, alguns aspectos são apresentados mais adiante.
O código implementado e os arquivos para a Simulação, estão disponíveis para download no final deste post (há também um vídeo mostrando a execução da simulação).
Sobre o Circuito
A implementação do Hardware usa o Arduino UNO, o Decoder CMOS de 7 segmentos 4511 (mais "corretamente" o 4511B), e três Displays de 7 segmentos de Katodo Comum (já que o 4511 permite acionar diretamente esse tipo de Display). Há também os três Botões usados para inserção da Senha, e dois LEDs que indicam se a Senha inserida está correta ou não. Os demais componentes são Resistores e Transistores. O circuito implementado pode ser visto na figura a seguir:
(clique na figura para "zoom")
Vamos falar um pouco então sobre os elementos usados neste Hardware:
1) Arduino: está sendo usado o UNO (uma vez que foi este o Arduino informado no tópico em questão), e isto facilita muito "traduzir" este Sistema para outros Arduinos. Conforme pode ser visto na figura anterior, são usados 4 pinos para especificar um valor de 0 a 9 para o Decoder 4511, sendo estes pinos o 4, 5, 6, e 7, conectados respectivamente aos bits "A" (ou bit "0"), "B" (ou bit "1"), "C" (ou bit "2"), e "D" (ou bit "3") do 4511. Mas poderiam ser usados quaisquer pinos de saída Digital do Arduino e em qualquer ordem desejada, pois o código implementado permite isso.
Para o controle ON/OFF de cada Display, foram usados os pinos "A1", "A2", e "A3" do UNO. Aqui também, quaisquer pinos podem ser usados e em qualquer ordem.
Para leitura dos estados dos três Botões e controle dos dois LEDs, outros 5 pinos são usados, e novamente podem ser especificados quaisquer outros pinos.
Assim ainda restam disponíveis 8 pinos do Arduino UNO. Observar que os pinos restantes permitem que se use algumas funções especiais, como pelo menos uma entrada do ADC (pino A0), Interface I2C (pinos A4 e A5), Interrupções Externas (pinos 2 e 3, mas há também as Interrupções do tipo "pin change" para outros pinos), PWM (pino 3), além da conexão ao Terminal do Arduino via Serial 0 (pinos 0 e 1). Claro, todos os 8 pinos também podem ser usados para I/O convencional. Então quando vc escolher qualquer outra combinação de pinos para o Sistema, analise antes qual é a que melhor se encaixa para suas necessidades.
2) Decoder 7 segmentos e Displays: está sendo utilizado o 4511. Este Decoder embora seja CMOS, possui internamente em cada uma de suas 7 saídas para os LEDs de segmentos, um Driver com Transistor Bipolar para fornecer a corrente para os respectivos LEDs (claro, via Resistor externo para limitar e determinar a corrente de saída). É um Decoder para Displays de Katodo Comum. As entradas "LT" (Light Test), e "BI" (Blanking Input), são ativas em "0" e como não estão sendo usadas, são ligadas a "1" (os 5V da alimentação). A entrada "LE" (Latch Enable) deve ser ligada ao "0" (GND do circuito), para que o Latch interno do 4511 fique "transparente", ou seja desabilitado, para que as saídas de 7 segmentos sempre estejam reproduzindo o valor binário presente nas entradas A, B, C, e D.
Os Resistores conectados às saídas QA a QG do 4511, determinam a corrente para cada segmento. Para os valores no circuito, 330 Ω, a corrente será algo próximo a 10mA. Para este cálculo, desconte de 5V, a tensão sobre um LED de segmento, e então terá a tensão sobre o Resistor. No caso, vamos considerar que o LED do Display tem uma tensão Anodo/Katodo de 1.5V quando ligado. Então sobre o Resistor teremos 5V - 1.5V = 3.5V (aqui estamos "desprezando" a queda no Transistor Bipolar interno do 4511). Então a corrente será 3.5V / 330 Ω ≈ 10.6 mA. Da mesma forma, pode-se também calcular o Resistor, especificando-se a corrente desejada, mas evite passar de 20mA, a fim de limitar o total de corrente fornecido pelo 4511 (veja o datasheet do mesmo para mais detalhes). Caso correntes bem maiores sejam desejadas, pode-se usar Transistores adicionais (tipo NPN) nos pinos de segmentos.
Observar que estamos considerando que os Transistores Q1 a Q3 estão funcionalmente saturados quando acionados pelas saídas do Arduino. Isto é fácil de garantir, supondo-se um HFE médio (por exemplo 100) e escolhendo-se uma corrente de base em torno de 1 mA. Caso alguém queira saber sobre estes cálculos, pergunte aqui neste mesmo tópico, ou me contate via email do LDG.
Nota Importante: é possível dispensar Q1 a Q3 e R11 a R13. Para isso basta que se ligue as saídas do Arduino (no caso pinos A1, A2, e A3), diretamente aos Katodos dos Displays. Nenhuma alteração no código é necessária, uma vez que os Transistores usados são do tipo PNP, o que significa que o código aciona estes por Lógica "LOW" (isto foi feito propositalmente). Notar porém, que o uso dos Transistores traz uma maior flexibilidade na escolha das correntes dos segmentos dos Displays, já que a corrente confiável para uma saída do Arduino é de 20mA, e NÃO 40mA como muitos adoram proclamar!!! Vejam: 40mA é para os "limites máximos absolutos" e não preciso dizer mais nada. Mas há um ponto que ajuda muito aqui: os Displays são acionados de forma multiplexada, e sempre há apenas um único Display acionado de cada vez. Isso significa que a corrente média total de um Display será 1/3 (um terço, já que são três Displays) da corrente determinada pelos Resistores dos segmentos. Como temos 7 segmentos, se a corrente individual é de 10 mA, então a corrente total seria 7 x 10mA = 70mA quando todos os segmentos estiverem ligados (reproduzindo o número "8"), mas a corrente efetiva seria 1/3 disso, ou seja: 70mA / 3 = 23mA. Claro, isso é ligeiramente acima da corrente "segura" para um pino do UNO, mas considerando-se que dificilmente todos os segmentos estarão ligados nos três Displays, então esse limite marginal pode ser desconsiderado. Mas de todo caso, o uso dos Transistores Q1 a Q3 é mais sensato, garantindo inclusive que não se extrapole a máxima corrente de todos os pinos somados do processador do UNO (o AtMega328).
3) Botões: para estes, estão sendo utilizados os Pullups internos do Processador do UNO, o que dispensou o uso de Resistores externos. A Lógica implementada no código faz o "debouncing" dos Botões automaticamente (e sem travar a execução do código). Isso permite dispensar Capacitores externos para o "debouncing" (e eventuais Diodos de Proteção, necessários se os Capacitores tivessem valores relativamente altos).
Sobre a Varredura dos Displays e dos Botões
Como pode ser visto na figura inicial que mostra o circuito do Sistema, os Displays são acionados por meio da varredura dos mesmos, ou seja, a exibição dos valores em cada um dos três Displays, é feita de forma multiplexada. Logo, somente um Display está efetivamente "ligado" num determinado momento. A frequência de varredura (ou taxa de "Refresh"), foi escolhida como sendo 50 Hz. Vamos entender os motivos disso. Primeiro, deve ser uma frequência que não permita a retina do olho humano acompanhar o liga/desliga dos Displays, dando a impressão que todos os três Displays estão sempre ligados, e portanto quanto maior esta frequência, melhor. E para que as demais funcionalidades do código sejam executadas sem haver preocupação em manter a taxa de varredura do Display, essa varredura é executada em uma rotina de Interrupção (ISR), e para gerar essa Interrupção em uma taxa constante, é utilizado o "TIMER1" do Hardware do Processador do Arduino. Assim na inicialização do Sistema, o código programa o "TIMER1" para gerar a taxa de varredura dos Displays. A cada interrupção, é feita a varredura de um único Display. Logo, para varrer os três Displays a 50 Hz, é necessário uma taxa de Interrupção de 3 x 50Hz = 150 Hz. Os detalhes do código que fazem essa varredura são mostrados mais adiante. Observar que a ISR (rotina de Interrupção) deve ser eficiente, para garantir que as demais funcionalidades do Sistema sejam executadas de forma "fluida", ou seja, sem parecer que as Interrupções estão ocorrendo 150 vezes por segundo.
No código implementado, a varredura dos Displays de fato é muito eficiente, e consome efetivamente pouco tempo. Por este motivo, para tornar o Sistema também "despreocupado" com a leitura e gerenciamento dos Botões, essa tarefa também é executada na mesma rotina de Interrupção do "TIMER1". Essencialmente, ali é feita a leitura dos estados dos Botões e conforme estes estados, é determinado se um Botão foi acionado, desacionado, ou se está pressionado por um determinado tempo. O código implementa isso de forma enxuta, longe de sacrificar tempo de processamento do restante do Sistema.
Se forem acrescentados ao Sistema dispositivos "One Wire" (ex.: Sensor DS18B20) ou semelhante (ex.: DHT11 ou DHT22), alguns cuidados devem ser tomados para se garantir os tempos de "slot" para estes dispositivos. Mas é algo perfeitamente factível, e posteriormente posso mostrar como fazer isso (há pelo menos três formas de implementação).
Para dispositivos I2C, nenhum cuidado especial é necessário, uma vez que estes também são cadenciados por IRQ (Interrupt Request) e não há um momento específico para que estas IRQs sejam atendidas.
Quaisquer dúvidas relacionadas, posso responder aqui mesmo neste tópico.
Sobre o Código e Funcionamento do Sistema
A Senha do Sistema é definida no início do código, conforme mostrado na figura a seguir:
(clique na figura para "zoom")
Há no Sistema dois LEDs, um Verde (ou LED "OK") e um Amarelo (ou LED "ERRO"). O LED Verde é utilizado para sinalizar quando a Senha correta é inserida, e o Amarelo indica quando a Senha inserida está incorreta. A definição no código para a configuração de Hardware dos LEDs, pode ser vista na figura a seguir:
(clique na figura para "zoom")
Três Botões controlam a operação do Sistema. O Botão "1" decrementa o valor exibido no Display atualmente selecionado. O Botão "2" incrementa o valor exibido. Já o Botão "3" tem várias funções, sendo a mais comum, selecionar o próximo Display para entrada da Senha.
O funcionamento do código é bastante simples: enquanto no "background" a varredura dos Displays e dos Botões é executada quando as Interrupções do TIMER1 ocorrem (na taxa de 150 Hz), temos que no "foreground" (ou execução "normal") o código decrementa ou incrementa o valor exibido no Display atualmente selecionado conforme acionamento dos Botões "1" ou "2". E quando o Botão "3" é acionado, então o código simplesmente seleciona o próximo Display.
Para cada um dos três Displays há uma variável do tipo "byte", que pode assumir valores de 0 a 9. São exatamente o valor dessas três variáveis que são exibidas pelo mecanismo de varredura do Display (no "background", ou seja, na rotina de Interrupção). E no início de tudo, as três variáveis estão zeradas, o que resulta em exibir "000" nos Displays. Há também um variável do tipo byte que indica qual Display está atualmente selecionado, e a mesma assume valores de 1 a 3. E no início, esta variável assume o valor "1", selecionando assim o Display "1" para a entrada de um Dígito da Senha (neste caso o Dígito das centenas). A figura a seguir mostra as três variáveis para os valores exibidos em cada Display, além da variável que indica qual Display está atualmente selecionado:
(clique na figura para "zoom")
Se um Display está no valor "0" e é decrementado, ele "recicla" para "9". E se um Display está no valor "9" e é incrementado, ele recicla para "0". Então o que o código tem que fazer é verificar quando cada Botão é acionado e aplicar as ações descritas.
Mas quando o Display "3" está selecionado e o Botão "3" é acionado, isso deve ser interpretado como "fim da entrada de Senha", e o Sistema então deve verificar se a Senha mostrada nos Displays, corresponde à Senha do Sistema.
Se a Senha que foi "inserida" via Displays estiver correta, então o LED Verde (LD1 no circuito) acende. Mas se a Senha estiver incorreta, então o LED Amarelo (LD2) acende.
Após a verificação da Senha, o Sistema fica aguardando que o Botão "3" seja novamente acionado, para reiniciar uma nova entrada de Senha. Neste ponto, os dois LEDs apagam, sendo selecionado o Display "1" para entrada de um Dígito da Senha, e os Displays exibem "000", e portanto o Sistema volta ao "início".
A forma mais simples e confiável de implementar esse sequenciamento do código, é através de uma Máquina de Estados, que é mostrada mais a frente neste post. Apenas 3 estados foram necessários para implementar todo o processamento.
Há no código um bloco que trata de todo o gerenciamento de leitura dos Botões, e que é executado no "background", ou seja, na rotina de Interrupção do TIMER1 (onde também é feita a varredura dos Displays). Como dito anteriormente, este bloco de código verifica se um Botão foi acionado ou desacionado, e também "mede" o tempo que um Botão está acionado. Estas informações estão disponíveis para serem usadas no "foreground" do código permitindo assim que este tome as ações conforme os estados dos Botões. Mas para que isto seja possível, é necessário que os Botões sejam definidos no Sistema. Esta definição é simples e consiste de duas etapas. A primeira é a definição dos pinos de Hardware onde os Botões estão conectados, conforme mostrado na figura a seguir:
(clique na figura para "zoom")
A segunda etapa é a criação de estruturas de dados para armazenar os status dos Botões, e de funções que configuram estes Botões e atualizam os status dos mesmos. Isto pode ser visto na figura a seguir:
(clique na figura para "zoom")
Caso fossem incluídos mais Botões no Sistema, bastaria se estender a mesma ideia mostrada na figura, acrescentando as definições para estes Botões juntamente aos já existentes.
Notar que a função "atualiza_Status_Botoes" será executada no "background", ou seja, na rotina de Interrupção do TIMER1. Ela está incluída na figura anterior para se mostrar como é simples a definição completa para uso de um Botão no Sistema. Esta função será especificada como a que justamente atualiza os status dos Botões, através de um "HOOK" na rotina de Interrupção (mostrado mais adiante quando a ISR for apresentada).
Em relação ao Decoder 4511, são necessários 4 pinos do Arduino para se especificar qual valor será exibido em um determinado momento. A definição destes pinos é mostrada na figura a seguir:
(clique na figura para "zoom")
Como já dito, quaisquer pinos podem ser especificados, e em qualquer ordem, pois o código se encarrega de setar cada um deles para reproduzir o valor binário para o Decoder 4511.
Para o controle ON/OFF dos três Displays, 3 pinos devem ser especificados para este controle. Novamente, quaisquer pinos podem ser especificados, e em qualquer ordem. No código, esta definição pode ser visto na figura a seguir:
(clique na figura para "zoom")
A figura a seguir, mostra a captura da tela da Simulação do Sistema no Proteus, em um momento que o Display "1" está sendo varrido, e por isso apenas o mesmo aparece "ligado" na figura exibindo o valor "3":
(clique na figura para "zoom")
Como a taxa de varredura total é de 150Hz (ou seja, 3x 50Hz), então o período de varredura é de 6.67ms (ou seja, 1 / 150Hz). Logo a cada 6.67ms o próximo Display é ligado, sendo setado em binário nos pinos A..D do 4511, o valor a ser exibido naquele Display. Ou seja, cada Display fica "ligado" justamente por 6.67ms, e fica desligado por 13.33ms, o que resulta em um ciclo de 20ms, implicando na Frequência individual de 50Hz para cada Display.
Obs.: a captura da figura anterior foi facilitada pelo fato de que a Simulação executa em um tempo "virtual", completamente independente do tempo real, e o Simulador aumenta ou diminui o step de tempo conforme detalhes da Simulação são executados. Por isso mesmo, quando se executa a simulação, é possível acompanhar partes do processo de varredura, o que causa um efeito que pode parecer "estranho", já que nunca vemos todos os três Displays ligados ao mesmo tempo. Eu disse "partes do processo de varredura", porque enquanto a simulação evolui, o próprio Simulador faz capturas do status desta simulação, para então "printar" estes status na tela do Simulador (aqueles pontinhos vermelhos e azuis que vemos nos prints), e essa taxa de captura não tem nenhuma relação com o conteúdo da simulação, o que resulta em um efeito sub-amostrado (é o mesmo efeito que uma luz stroboscópica existente em pistas de dança das danceterias provoca em nossa retina, resultando em algo semelhante a um vídeo onde faltam cenas). Isto pode ser melhor visto no vídeo da simulação mostrado no final deste post (resulta em um "pisca/pisca" dos valores exibidos no Display, e claro isso é apenas na Simulação).
Como já dito, a taxa de varredura total deve ser 3 vezes a taxa desejada para um único Display. Assim como desejamos 50Hz para cada Display, a taxa total de varredura deve ser 150Hz, e isso é definido no código conforme mostrado na figura a seguir:
(clique na figura para "zoom")
Em uma das simulações, acrescentei um Frequencímetro para medir a frequência de varredura para cada um dos Displays, e o resultado pode ser visto na figura a seguir:
(clique na figura para "zoom")
Como pode ser visto na figura anterior, acrescentei também uma chave rotativa (no circuito é a SW1), que permite selecionar em qual dos Displays estaremos medindo a taxa de varredura. Para todos foi medido 50Hz, como seria de se esperar.
(devido à limitação de espaço este post continua logo a seguir: Link)
…
Adicionado por Elcids Chagas ao 13:49 em 15 julho 2021
ISABLE_Z false // Warn on display about possibly reduced accuracy //#define DISABLE_REDUCED_ACCURACY_WARNING
// @section extruder
#define DISABLE_E false // For all extruders #define DISABLE_INACTIVE_EXTRUDER true // Keep only the active extruder enabled.
// @section machine
// Invert the stepper direction. Change (or reverse the motor connector) if an axis goes the wrong way. #define INVERT_X_DIR false #define INVERT_Y_DIR false #define INVERT_Z_DIR false
// @section extruder
// For direct drive extruder v9 set to true, for geared extruder set to false. #define INVERT_E0_DIR false #define INVERT_E1_DIR false #define INVERT_E2_DIR false #define INVERT_E3_DIR false #define INVERT_E4_DIR false
// @section homing
//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed
//#define UNKNOWN_Z_NO_RAISE // Don't raise Z (lower the bed) if Z is "unknown." For beds that fall when Z is powered off.
//#define Z_HOMING_HEIGHT 4 // (in mm) Minimal z height before homing (G28) for Z clearance above the bed, clamps, ... // Be sure you have this distance over your Z_MAX_POS in case.
// Direction of endstops when homing; 1=MAX, -1=MIN // :[-1,1] #define X_HOME_DIR -1 #define Y_HOME_DIR -1 #define Z_HOME_DIR -1
// @section machine
// The size of the print bed #define X_BED_SIZE 400 #define Y_BED_SIZE 400
// Travel limits (mm) after homing, corresponding to endstop positions. #define X_MIN_POS 0 #define Y_MIN_POS 0 #define Z_MIN_POS 0 #define X_MAX_POS X_BED_SIZE #define Y_MAX_POS Y_BED_SIZE #define Z_MAX_POS 400
/** * Software Endstops * * - Prevent moves outside the set machine bounds. * - Individual axes can be disabled, if desired. * - X and Y only apply to Cartesian robots. * - Use 'M211' to set software endstops on/off or report current state */
// Min software endstops constrain movement within minimum coordinate bounds #define MIN_SOFTWARE_ENDSTOPS #if ENABLED(MIN_SOFTWARE_ENDSTOPS) #define MIN_SOFTWARE_ENDSTOP_X #define MIN_SOFTWARE_ENDSTOP_Y #define MIN_SOFTWARE_ENDSTOP_Z #endif
// Max software endstops constrain movement within maximum coordinate bounds #define MAX_SOFTWARE_ENDSTOPS #if ENABLED(MAX_SOFTWARE_ENDSTOPS) #define MAX_SOFTWARE_ENDSTOP_X #define MAX_SOFTWARE_ENDSTOP_Y #define MAX_SOFTWARE_ENDSTOP_Z #endif
#if ENABLED(MIN_SOFTWARE_ENDSTOPS) || ENABLED(MAX_SOFTWARE_ENDSTOPS) //#define SOFT_ENDSTOPS_MENU_ITEM // Enable/Disable software endstops from the LCD #endif
/** * Filament Runout Sensors * Mechanical or opto endstops are used to check for the presence of filament. * * RAMPS-based boards use SERVO3_PIN for the first runout sensor. * For other boards you may need to define FIL_RUNOUT_PIN, FIL_RUNOUT2_PIN, etc. * By default the firmware assumes HIGH=FILAMENT PRESENT. */ //#define FILAMENT_RUNOUT_SENSOR #if ENABLED(FILAMENT_RUNOUT_SENSOR) #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. #define FIL_RUNOUT_INVERTING false // set to true to invert the logic of the sensor. #define FIL_RUNOUT_PULLUP // Use internal pullup for filament runout pins. #define FILAMENT_RUNOUT_SCRIPT "M600" #endif
//=========================================================================== //=============================== Bed Leveling ============================== //=========================================================================== // @section calibrate
/** * Choose one of the options below to enable G29 Bed Leveling. The parameters * and behavior of G29 will change depending on your selection. * * If using a Probe for Z Homing, enable Z_SAFE_HOMING also! * * - AUTO_BED_LEVELING_3POINT * Probe 3 arbitrary points on the bed (that aren't collinear) * You specify the XY coordinates of all 3 points. * The result is a single tilted plane. Best for a flat bed. * * - AUTO_BED_LEVELING_LINEAR * Probe several points in a grid. * You specify the rectangle and the density of sample points. * The result is a single tilted plane. Best for a flat bed. * * - AUTO_BED_LEVELING_BILINEAR * Probe several points in a grid. * You specify the rectangle and the density of sample points. * The result is a mesh, best for large or uneven beds. * * - AUTO_BED_LEVELING_UBL (Unified Bed Leveling) * A comprehensive bed leveling system combining the features and benefits * of other systems. UBL also includes integrated Mesh Generation, Mesh * Validation and Mesh Editing systems. * * - MESH_BED_LEVELING * Probe a grid manually * The result is a mesh, suitable for large or uneven beds. (See BILINEAR.) * For machines without a probe, Mesh Bed Leveling provides a method to perform * leveling in steps so you can manually adjust the Z height at each grid-point. * With an LCD controller the process is guided step-by-step. */ //#define AUTO_BED_LEVELING_3POINT //#define AUTO_BED_LEVELING_LINEAR //#define AUTO_BED_LEVELING_BILINEAR //#define AUTO_BED_LEVELING_UBL #define MESH_BED_LEVELING // descomentar se quizer fazer o nivelamento no painel de LCD com pontos na mesa de impressao
/** * Normally G28 leaves leveling disabled on completion. Enable * this option to have G28 restore the prior leveling state. */ //#define RESTORE_LEVELING_AFTER_G28
/** * Enable detailed logging of G28, G29, M48, etc. * Turn on with the command 'M111 S32'. * NOTE: Requires a lot of PROGMEM! */ //#define DEBUG_LEVELING_FEATURE
#if ENABLED(MESH_BED_LEVELING) || ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(AUTO_BED_LEVELING_UBL) // Gradually reduce leveling correction until a set height is reached, // at which point movement will be level to the machine's XY plane. // The height can be set with M420 Z<height> #define ENABLE_LEVELING_FADE_HEIGHT
// For Cartesian machines, instead of dividing moves on mesh boundaries, // split up moves into short segments like a Delta. This follows the // contours of the bed more closely than edge-to-edge straight moves. #define SEGMENT_LEVELED_MOVES #define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one)
/** * Enable the G26 Mesh Validation Pattern tool. */ //#define G26_MESH_VALIDATION #if ENABLED(G26_MESH_VALIDATION) #define MESH_TEST_NOZZLE_SIZE 0.4 // (mm) Diameter of primary nozzle. #define MESH_TEST_LAYER_HEIGHT 0.2 // (mm) Default layer height for the G26 Mesh Validation Tool. #define MESH_TEST_HOTEND_TEMP 205.0 // (°C) Default nozzle temperature for the G26 Mesh Validation Tool. #define MESH_TEST_BED_TEMP 60.0 // (°C) Default bed temperature for the G26 Mesh Validation Tool. #endif
#endif
#if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_BILINEAR)
// Set the number of grid points per dimension. #define GRID_MAX_POINTS_X 2 // coloquei dois para fazer testes nos quatro cantos da mesa, com tres seria feito em seis pontos #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X
// Set the boundaries for probing (where the probe can reach). //#define LEFT_PROBE_BED_POSITION MIN_PROBE_EDGE //#define RIGHT_PROBE_BED_POSITION (X_BED_SIZE - MIN_PROBE_EDGE) //#define FRONT_PROBE_BED_POSITION MIN_PROBE_EDGE //#define BACK_PROBE_BED_POSITION (Y_BED_SIZE - MIN_PROBE_EDGE)
// Probe along the Y axis, advancing X after each column //#define PROBE_Y_FIRST
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
// Beyond the probed grid, continue the implied tilt? // Default is to maintain the height of the nearest edge. //#define EXTRAPOLATE_BEYOND_GRID
// // Experimental Subdivision of the grid by Catmull-Rom method. // Synthesizes intermediate points to produce a more detailed mesh. // //#define ABL_BILINEAR_SUBDIVISION #if ENABLED(ABL_BILINEAR_SUBDIVISION) // Number of subdivisions between probe points #define BILINEAR_SUBDIVISIONS 3 #endif
#endif
#elif ENABLED(AUTO_BED_LEVELING_UBL)
//=========================================================================== //========================= Unified Bed Leveling ============================ //===========================================================================
//#define MESH_EDIT_GFX_OVERLAY // Display a graphics overlay while editing the mesh
#define MESH_INSET 1 // Set Mesh bounds as an inset region of the bed #define GRID_MAX_POINTS_X 10 // Don't use more than 15 points per axis, implementation limited. #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X
#define UBL_MESH_EDIT_MOVES_Z // Sophisticated users prefer no movement of nozzle #define UBL_SAVE_ACTIVE_ON_M500 // Save the currently active mesh in the current slot on M500
//#define UBL_Z_RAISE_WHEN_OFF_MESH 2.5 // When the nozzle is off the mesh, this value is used // as the Z-Height correction value.
#elif ENABLED(MESH_BED_LEVELING)
//=========================================================================== //=================================== Mesh ================================== //===========================================================================
#define MESH_INSET 10 // Set Mesh bounds as an inset region of the bed #define GRID_MAX_POINTS_X 3 // Don't use more than 7 points per axis, implementation limited. #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X
//#define MESH_G28_REST_ORIGIN // After homing all axes ('G28' or 'G28 XYZ') rest Z at Z_MIN_POS
#endif // BED_LEVELING
/** * Points to probe for all 3-point Leveling procedures. * Override if the automatically selected points are inadequate. */ #if ENABLED(AUTO_BED_LEVELING_3POINT) || ENABLED(AUTO_BED_LEVELING_UBL) //#define PROBE_PT_1_X 15 //#define PROBE_PT_1_Y 180 //#define PROBE_PT_2_X 15 //#define PROBE_PT_2_Y 20 //#define PROBE_PT_3_X 170 //#define PROBE_PT_3_Y 20 #endif
/** * Add a bed leveling sub-menu for ABL or MBL. * Include a guided procedure if manual probing is enabled. */ #define LCD_BED_LEVELING // poder fazer o nivelamento usando o painel LCD como controle
#if ENABLED(LCD_BED_LEVELING) #define MBL_Z_STEP 0.025 // Step size while manually probing Z axis. #define LCD_PROBE_Z_RANGE 4 // Z Range centered on Z_MIN_POS for LCD Z adjustment #endif
// Add a menu item to move between bed corners for manual bed adjustment #define LEVEL_BED_CORNERS //habilitar o nivelamento de cantos na mesa de impressao
#if ENABLED(LEVEL_BED_CORNERS) #define LEVEL_CORNERS_INSET 30 // (mm) An inset for corner leveling #define LEVEL_CORNERS_Z_HOP 4.0 // (mm) Move nozzle up before moving between corners //#define LEVEL_CENTER_TOO // Move to the center after the last corner #endif
/** * Commands to execute at the end of G29 probing. * Useful to retract or move the Z probe out of the way. */ //#define Z_PROBE_END_SCRIPT "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10"
// @section homing
// The center of the bed is at (X=0, Y=0) //#define BED_CENTER_AT_0_0
// Manually set the home position. Leave these undefined for automatic settings. // For DELTA this is the top-center of the Cartesian print volume. //#define MANUAL_X_HOME_POS 0 //#define MANUAL_Y_HOME_POS 0 //#define MANUAL_Z_HOME_POS 0
// Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. // // With this feature enabled: // // - Allow Z homing only after X and Y homing AND stepper drivers still enabled. // - If stepper drivers time out, it will need X and Y homing again before Z homing. // - Move the Z probe (or nozzle) to a defined XY point before Z Homing when homing all axes (G28). // - Prevent Z homing when the Z probe is outside bed area. // //#define Z_SAFE_HOMING
#if ENABLED(Z_SAFE_HOMING) #define Z_SAFE_HOMING_X_POINT ((X_BED_SIZE) / 2) // X point for Z homing when homing all axes (G28). #define Z_SAFE_HOMING_Y_POINT ((Y_BED_SIZE) / 2) // Y point for Z homing when homing all axes (G28). #endif
// Homing speeds (mm/m) #define HOMING_FEEDRATE_XY (50*60) #define HOMING_FEEDRATE_Z (4*60)
// @section calibrate
/** * Bed Skew Compensation * * This feature corrects for misalignment in the XYZ axes. * * Take the following steps to get the bed skew in the XY plane: * 1. Print a test square (e.g., https://www.thingiverse.com/thing:2563185) * 2. For XY_DIAG_AC measure the diagonal A to C * 3. For XY_DIAG_BD measure the diagonal B to D * 4. For XY_SIDE_AD measure the edge A to D * * Marlin automatically computes skew factors from these measurements. * Skew factors may also be computed and set manually: * * - Compute AB : SQRT(2*AC*AC+2*BD*BD-4*AD*AD)/2 * - XY_SKEW_FACTOR : TAN(PI/2-ACOS((AC*AC-AB*AB-AD*AD)/(2*AB*AD))) * * If desired, follow the same procedure for XZ and YZ. * Use these diagrams for reference: * * Y Z Z * ^ B-------C ^ B-------C ^ B-------C * | / / | / / | / / * | / / | / / | / / * | A-------D | A-------D | A-------D * +-------------->X +-------------->X +-------------->Y * XY_SKEW_FACTOR XZ_SKEW_FACTOR YZ_SKEW_FACTOR */ //#define SKEW_CORRECTION
#if ENABLED(SKEW_CORRECTION) // Input all length measurements here: #define XY_DIAG_AC 282.8427124746 #define XY_DIAG_BD 282.8427124746 #define XY_SIDE_AD 200
// Or, set the default skew factors directly here // to override the above measurements: #define XY_SKEW_FACTOR 0.0
//#define SKEW_CORRECTION_FOR_Z #if ENABLED(SKEW_CORRECTION_FOR_Z) #define XZ_DIAG_AC 282.8427124746 #define XZ_DIAG_BD 282.8427124746 #define YZ_DIAG_AC 282.8427124746 #define YZ_DIAG_BD 282.8427124746 #define YZ_SIDE_AD 200 #define XZ_SKEW_FACTOR 0.0 #define YZ_SKEW_FACTOR 0.0 #endif
// Enable this option for M852 to set skew at runtime //#define SKEW_CORRECTION_GCODE #endif
//============================================================================= //============================= Additional Features =========================== //=============================================================================
// @section extras
// // EEPROM // // The microcontroller can store settings in the EEPROM, e.g. max velocity... // M500 - stores parameters in EEPROM // M501 - reads parameters from EEPROM (if you need reset them after you changed them temporarily). // M502 - reverts to the default "factory settings". You still need to store them in EEPROM afterwards if you want to. // #define EEPROM_SETTINGS // Enable for M500 and M501 commands //possibilita o salvamentos da configuracao direto na memoria EEPROM do arduino #define DISABLE_M503 // Saves ~2700 bytes of PROGMEM. Disable for release!//estava comentado testes #define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save PROGMEM.
// // Host Keepalive // // When enabled Marlin will send a busy status message to the host // every couple of seconds when it can't accept commands. // #define HOST_KEEPALIVE_FEATURE // Disable this if your host doesn't like keepalive messages #define DEFAULT_KEEPALIVE_INTERVAL 2 // Number of seconds between "busy" messages. Set with M113. #define BUSY_WHILE_HEATING // Some hosts require "busy" messages even during heating
// // M100 Free Memory Watcher // //#define M100_FREE_MEMORY_WATCHER // Add M100 (Free Memory Watcher) to debug memory usage
// // G20/G21 Inch mode support // //#define INCH_MODE_SUPPORT
// // M149 Set temperature units support // //#define TEMPERATURE_UNITS_SUPPORT
// @section temperature
// Preheat Constants #define PREHEAT_1_TEMP_HOTEND 180 #define PREHEAT_1_TEMP_BED 70 #define PREHEAT_1_FAN_SPEED 100 // Value from 0 to 255
#define PREHEAT_2_TEMP_HOTEND 220 #define PREHEAT_2_TEMP_BED 110 #define PREHEAT_2_FAN_SPEED 100 // Value from 0 to 255
/** * Nozzle Park * * Park the nozzle at the given XYZ position on idle or G27. * * The "P" parameter controls the action applied to the Z axis: * * P0 (Default) If Z is below park Z raise the nozzle. * P1 Raise the nozzle always to Z-park height. * P2 Raise the nozzle by Z-park amount, limited to Z_MAX_POS. */ //#define NOZZLE_PARK_FEATURE
#if ENABLED(NOZZLE_PARK_FEATURE) // Specify a park position as { X, Y, Z } #define NOZZLE_PARK_POINT { (X_MIN_POS + 10), (Y_MAX_POS - 10), 20 } #define NOZZLE_PARK_XY_FEEDRATE 100 // X and Y axes feedrate in mm/s (also used for delta printers Z axis) #define NOZZLE_PARK_Z_FEEDRATE 5 // Z axis feedrate in mm/s (not used for delta printers) #endif
/** * Clean Nozzle Feature -- EXPERIMENTAL * * Adds the G12 command to perform a nozzle cleaning process. * * Parameters: * P Pattern * S Strokes / Repetitions * T Triangles (P1 only) * * Patterns: * P0 Straight line (default). This process requires a sponge type material * at a fixed bed location. "S" specifies strokes (i.e. back-forth motions) * between the start / end points. * * P1 Zig-zag pattern between (X0, Y0) and (X1, Y1), "T" specifies the * number of zig-zag triangles to do. "S" defines the number of strokes. * Zig-zags are done in whichever is the narrower dimension. * For example, "G12 P1 S1 T3" will execute: * * -- * | (X0, Y1) | /\ /\ /\ | (X1, Y1) * | | / \ / \ / \ | * A | | / \ / \ / \ | * | | / \ / \ / \ | * | (X0, Y0) | / \/ \/ \ | (X1, Y0) * -- +--------------------------------+ * |________|_________|_________| * T1 T2 T3 * * P2 Circular pattern with middle at NOZZLE_CLEAN_CIRCLE_MIDDLE. * "R" specifies the radius. "S" specifies the stroke count. * Before starting, the nozzle moves to NOZZLE_CLEAN_START_POINT. * * Caveats: The ending Z should be the same as starting Z. * Attention: EXPERIMENTAL. G-code arguments may change. * */ //#define NOZZLE_CLEAN_FEATURE
#if ENABLED(NOZZLE_CLEAN_FEATURE) // Default number of pattern repetitions #define NOZZLE_CLEAN_STROKES 12
// Default number of triangles #define NOZZLE_CLEAN_TRIANGLES 3
// Specify positions as { X, Y, Z } #define NOZZLE_CLEAN_START_POINT { 30, 30, (Z_MIN_POS + 1)} #define NOZZLE_CLEAN_END_POINT {100, 60, (Z_MIN_POS + 1)}
//continua…
Adicionado por Richard Falcos ao 11:35 em 8 dezembro 2019
Verde = 8;
const int ledAmarelo = 9;
const int ledVermelho = 10;
//Função setup, executado uma vez ao ligar o Arduino.
void setup(){
//Ativando o serial monitor que exibirá os valores lidos no sensor.
Serial.begin(9600);
//Definindo pinos digitais dos leds como de saída.
pinMode(ledVerde,OUTPUT);
pinMode(ledAmarelo,OUTPUT);
pinMode(ledVermelho,OUTPUT);
}
//Função loop, executado enquanto o Arduino estiver ligado.
void loop(){
//Lendo o valor do sensor.
int valorSensor = analogRead(sensor);
//Valores da luminosidade podem ser alterados conforme necessidade.
//Luminosidade baixa.
if (valorSensor < 750) {
apagaLeds();
digitalWrite(ledVermelho,HIGH);
}
//Luminosidade média.
if (valorSensor >= 750 && valorSensor <= 800) {
apagaLeds();
digitalWrite(ledAmarelo,HIGH);
}
//Luminosidade alta.
if (valorSensor > 800) {
apagaLeds();
digitalWrite(ledVerde,HIGH);
}
//Exibindo o valor do sensor no serial monitor.
Serial.println(valorSensor);
delay(50);
}
//Função criada para apagar todos os leds de uma vez.
void apagaLeds() {
digitalWrite(ledVerde,LOW);
digitalWrite(ledAmarelo,LOW);
digitalWrite(ledVermelho,LOW);
}
e
#include "LiquidCrystal.h"
#include "Limits.h"
const int sensorLuz = 0; //Pino analógico que o sensor de luz está conectado.
const int sensorTemp = 1; //Pino analógico que o sensor de temperatura está conectado.
//Variáveis
int valorSensorLuz = 0; //usada para ler o valor do sensor de luz.
int valorSensorTemp = 0; //usada para ler o valor do sensor de temperatura.
int menorValorTemp = INT_MAX; //usada para armazenar o menor valor da temperatura.
//Criando um objeto da classe LiquidCrystal e
//inicializando com os pinos da interface.
LiquidCrystal lcd(9, 8, 5, 4, 3, 2);
void setup() {
//Inicializando o LCD e informando o tamanho de 16 colunas e 2 linhas
//que é o tamanho do LCD JHD 162A usado neste projeto.
lcd.begin(16, 2);
}
void loop() {
//Lendo o valor do sensor de luz
valorSensorLuz = analogRead(sensorLuz);
//Para evitar as grandes variações de leitura do componente
//LM35 são feitas 8 leitura é o menor valor lido prevalece.
menorValorTemp = INT_MAX; //Inicializando com o maior valor int possível
for (int i = 1; i <= 8; i++) {
//Lendo o valor do sensor de temperatura.
valorSensorTemp = analogRead(sensorTemp);
//Transformando valor lido no sensor de temperatura em graus celsius aproximados.
valorSensorTemp *= 0.54 ;
//Mantendo sempre a menor temperatura lida
if (valorSensorTemp < menorValorTemp) {
menorValorTemp = valorSensorTemp;
}
delay(150);
}
//Exibindo valor da leitura do sensor de temperatura no display LCD.
lcd.clear(); //limpa o display do LCD.
lcd.print("Temp: "); //imprime a string no display do LCD.
lcd.print(menorValorTemp);
lcd.write(B11011111); //Simbolo de graus celsius
lcd.print("C");
//Exibindo valor da leitura do sensor de luz no display LCD.
lcd.setCursor(0,1); //posiciona o cursor na coluna 0 linha 1 do LCD.
lcd.print("Luz: "); //imprime a string no display do LCD.
lcd.print(valorSensorLuz);
delay(2000); //aguarda 2 segundos
}
eu tinha juntado, mas os valores do ldr ficaram variando loucamente kkkk e esse é o problema
links dos dois projetos
http://www.comofazerascoisas.com.br/projeto-arduino-com-display-lcd-sensor-de-temperatura-e-sensor-de-luz.html
http://www.comofazerascoisas.com.br/projeto-arduino-sensor-de-luz-ldr-com-leds.html
…
Adicionado por Raykazy ao 13:38 em 22 fevereiro 2016
essão 3d, vejo muitas pessoas tentando se aventurar nessa área, cometendo os mesmos erros que muitos dos iniciantes já passaram e com pouca leitura e compreensão de aspectos que estão espalhados pela internet, então aqui vão algumas dicas para quem está iniciando. Não vou montar um passo a passo de como montar porque isto pode variar muito de cada modelo e de quanto você quer gastar, mas espero que essas dicas sejam um guia para um iniciante.
O primeiro erro básico de muitos iniciantes é quanto ao tamanho. Quem nunca teve uma impressora 3d geralmente quer montar tamanhos “gigantes” de impressoras sem nunca ter montando uma básica com área de impressão de 200mm x 200mm x 200mm. Uma impressão pode demorar muito, as vezes dias, dependendo da qualidade que se deseja, isso em impressora de tamanha comum, em uma gigante fica inviável pelo tempo, pelo custo de material gasto e pelo fato de que muitos erros podem ocorrer. O ideal para imprimir peças muito grandes é dividir o objeto em vários outros menores e imprimir por partes. Se existir algum problema você perde apenas uma parte e não uma peça inteira.
O segundo erro básico é sobre o tipo de objetos que uma impressora 3d desktop pode imprimir. Atualmente os materiais que trabalham razoavelmente bem são plásticos, como ABS e PLA, Nylon e Policarbonato. Todos eles em formato de filamento vendidos em rolos por kg.
Outro fato é quanto aos custos. Se você não pode gastar no mínimo de R$ 50,00 a R$ 120,00 por mês, então é um tremendo desperdício comprar uma impressora 3d. De onde eu tirei esse valor? É o preço médio do kg do filamento. Se você não vai utilizar a impressora para fabricar objetos, gastar com plástico, então é melhor rever seus conceitos. Claro que você pode fabricar seu próprio filamento, mas isso é muito mais difícil que montar uma impressora, então comece do básico.
Modelo pronto x Kit de montagem x Montar minha própria impressora?
O que posso dizer na data que escrevo este texto é que as empresas que vendem impressoras desktop prontas possuem exatamente a mesma tecnologia das impressoras open hardware. Isso mesmo, não existe diferença na tecnologia e muitas vezes até as peças e softwares são os mesmos que são utilizadas em uma que você pode montar na sua própria casa. Não estou dizendo isto das INDUSTRIAIS, isto vale apenas para as desktop.
Então qual a vantagem em comprar uma impressora montada? Bom, você irá comprar um produto testado, regulado, com suporte técnico e garantia. Não pense que isso é fácil, montar o hardware de uma impressora é a parte mais fácil do processo, ajustar e deixar tudo funcionando perfeitamente exige tempo e muito esforço. Vai pagar o preço por essas vantagens, em geral uma impressora montada custa mais que o dobro do valor que seria gasto caso fosse montar sua própria impressora.
Já os kits de montagem oferecem a vantagem de ter todas as peças necessárias para a montagem, com um produto que já foi montado e testado por alguém e com garantia de que esse conjunto de peças irá funcionar com tudo montado corretamente, além de garantia de peças e suporte para montagem/regulagem. Os kits de montagem custam um pouco menos que as prontas mas ainda assim saem muito mais caro que montar por si mesmo.
E por fim entramos onde irei comentar mais, montar sua própria impressora. A primeira coisa que alguém que queira montar sua própria impressora deve fazer é ler o site reprap inteiro. Isso mesmo, leia tudo, veja todos os modelos, entenda como tudo funciona. Reprap é um projeto que foi fundado em 2005 por Adrian Bowyer, doutor em engenharia em uma universidade no Reino Unido. A ideia é que seja possível construir impressoras que possam se replicar (Inception lvl2), evoluir, de fonte aberta e visando baixo custo na construção. Perfeito não é mesmo? Uma tecnologia disruptiva que está dominando o mercado de impressoras 3d. Atualmente é o maior número de máquinas no mundo segundo dados do próprio site da reprap. Vou apresentar então alguns componentes e conceitos para que você decida qual modelo de reprap irá montar.
Tecnologia de Movimento
Como um torno CNC, uma impressora 3d se movimenta em 3 eixos para a construção de objetos, atualmente essencialmente de plástico.
Nos modelos cartesianos, o eixo x se move para direita e esquerda, y para frente e trás e z para cima e baixo. As repraps mais comuns desse sistema são as prusa i3, prusa mendel e mendelmax. É o tipo de impressora mais fácil de montar e que existe maior quantidade de material disponível. Todo iniciante deveria começar por uma cartesiana. Eu recomendo uma Graber i3, que é basicamente uma Prusa i3 mas que é montado todo seu frame (estrutura) de madeira MDF ou acrílico 6mm, dispensando o uso de barras de rosca para a estrutura.
CoreXY e H-Bot são sistemas onde o movimento dos eixos X e Y são combinados para se moverem de forma conjunta. Não confunda as coisas, apesar de parecidos CoreXY é um sistema e H-Bot é outro. Em ambos, o eixo Z funciona de forma normal (subindo e descendo). Este sistema é geralmente utilizado em impressoras cúbicas, onde existe uma estrutura em formato de caixa que envolve toda a impressora. Muitas impressoras prontas utilizam o sistema H-Bot. Se você pensa em montar uma impressora com aparência de uma caixa fechada parecida com Replicator 2 ou metamáquina recomento este sistema.
Delta é um sistema onde o movimento dos 3 eixos (X, Y e Z) trabalham de forma conjunta. A vantagem desse sistema é que pode ser ajustado pra imprimir em grandes velocidades. Quanto mais “ao centro” o objeto estiver, mais rápido uma delta consegue imprimir. Os modelos mais comuns são Kossel e Rostock . O extrusor fica no centro, segurado por hastes de fibra de carbono ou alumínio que são guiados por 3 motores que com rotações combinadas geram o movimento.
Componetes
Independente do modelo que você escolha, muitos componentes serão iguais, com algumas modificações, mas servem da mesma forma.
- Eletrônica: a maioria das impressoras, utiliza como base o Arduino , um shield e drivers de controle de motores. O conjunto mais comum é um arduino mega + placa ramps 1.4 + drivers A4988. Como o arduino e a ramps são open hardware, existe uma variedade grande de variantes mas que utilizam a mesma arquitetura como base, como por exemplo Sanguinololu, Gen7, Megatronics, Rumba, Bumba, etc. Cada uma implementa melhorias e modificações, mas nada muito diferente do que o padrão possui. Existem modelos também com outros tipos de arquitetura, como placas com chips ARM. Acredito que no futuro essa seja uma evolução comum pelo fato de que processadores ARM podem fornecer maior velocidade de impressão, mas atualmente o arduino serve muito bem. Existem outros periféricos também como visor LCD e leitores de cartão SD que dispensam o uso de um computador para enviar comandos para a impressora. Os modelos mais comuns são de placas que já vem tudo embutido, um visor com botão e controle para navegação de menus e leitor. Você pode montar uma impressora sem esses periféricos, mas é altamente recomendável utilizá-los. Impressões podem demorar horas e é comum falhas de comunicação entre PC – impressora, além de travamentos, etc, que podem jogar fora um trabalho quase concluído. Estes tipos de problemas não ocorrem com o uso de LCD e Leitor SD. Servo motores também podem ser utilizados para fazer o nivelamento automático da zona de impressão. A alimentação elétrica pode ser feita por fontes de computador ATX ou fontes industriais 12v de no mínimo 350w 30A. Fontes industriais são mais baratas e trabalham melhor que muitas fontes ATX, mesmo as mais caras. Fontes 24v podem ser utilizadas com clones de arduino modificados para suportar esta tensão. O arduino padrão só trabalha com 12v. Eu recomento o básico para um iniciante, Arduino Mega + Ramps 1.4 + drivers + visor LCD com leitor SD + fonte industrial 12v 350w 30a.
- Firmware: dentro da placa é necessário um firmware, que receberá comandos enviados pelo computador ou pelo cartão SD. Os mais utilizados, que são compatíveis com arquitetura arduino são Marlin, Repetier Firmware e GRBL. Após fazer o download do firmware é necessário abrir o código fonte e alterar alguns parâmetros, como tipo de placa, tipo de sensor térmico, tipo de visor LCD, calculo do movimento dos eixos X, Y, Z e E (extrusor), etc. Se você não entende nada disso não se assuste, é mais fácil do que parece, mas é preciso algum conhecimento com a IDE do arduino (software de edição de código do Arduino) para enviar o código para o Arduino. Aqui tem um tutorial básico de configuração do Marlin, basicamente você precisa editar apenas o arquivo configuration.h .
- Outros Software: além do firmware é necessário um software que converta um arquivo 3d (geralmente é utilizado o formato .stl) em um arquivo gcode, que é o formato que o firmware compreende. Esse software é conhecido como Slicer (fatiador), isto é, ele pega o modelo 3d stl e fatia em várias camadas que serão geradas pelo extrusor da impressora. Aqui não existe formula pronta, é necessário testar e aprender como o fatiamento foi executado pelo sua impressora. Existem muitas variáveis para regulagem no software. O software mais utilizado é o Slic3r.
Existe também softwares para controlar a impressora através do computador. Ele envia comandos diretamente para o firmware através da porta USB. Os mais utilizados são Repetier Host e Pronterface. Aqui segue um link do básico do repetier host e configuração do Slic3r feito pelo Paulo Fernandes da 3dmachine.com.br que me ajudou muito no início.
Já para modelar um objeto 3d eu recomendo Sketchup para objetos mecânicos ou que necessitem de medidas precisas e o Blender para objetos “orgânicos” ou de modelagem livre.
- Motores: o padrão amplamente utilizado em repraps são os motores nema 17 1.8 degree 4,8 torque. Não conheço nenhum fabricante de motores nacional, todos são importados. Você pode utilizar outros motores, mas se não tem conhecimento em eletrônica e configuração de firmware, compatibilidade com placa, etc, compre um que é recomendado no site da reprap aqui. Não precisa ser exatamente nesses vendedores que o site recomenda, pode ser em qualquer lugar desde que respeite essas condições técnicas. No ebay ou no aliexpress existe uma grande variedade, mas muito cuidado para não comprar gato por lebre. Os motores são muito importantes na qualidade e precisão das peças impressas. A quantidade vai variar do modelo de impressora, mas o básico são 4, um para cada eixo x, y, z e um para o extrusor de plástico. Atenção que as cartesianas geralmente utilizam 2 motores no eixo z.
- Extrusor/Hotend: a peça que derrete o plástico é chamado de hotend. O filamento entra no hotend para ser aquecido até o ponto de derreter e ser depositado em camadas para formar o objeto. Existem duas medidas de hotend, para as duas medidas de filamento disponíveis, de 1.75 ou 3mm. O hotend pode ser comprado de fabricantes nacionais como o da Sethi3d que é de excelente qualidade ou importado no ebay, aliexpress, e3d, reprapdiscount, etc. Além do hotend é necessário peças impressas ou de acrílico/mdf para acoplar o hotend na impressora e engrenagens para um motor nema 17 mover o filamento para dentro do hotend de forma sincronizada. Este conjunto é chamado de extrusor. Existe uma variedade muito grande de modelos de extrusor e para conseguir algum o mais fácil é comprar de algum proprietário de impressora ou em sites como mercado livre. Nas cartesianas um modelo bem comum é o Greg's Wade. Nas impressoras do tipo delta, as hastes que suportam o hotend devem possuir o mínimo de peso possível para evitar vibrações, assim o extrusor delas precisa ser diferente, com o filamento sendo enviado através de um bowdem (tudo) da parte mecânica do extrusor até o interior do hotend.
- Correias e Polias: quase todos os modelos precisam de correias e polias para transmissão do movimento dos motores para os eixos x e y. O z pode variar, como nas cartesianas que utiliza barra de rosca no eixo z. As do tipo delta não utiliza barras de rosca, apenas correias e polias. O tipo de correia pode varias, mas é amplamente utilizado GT2, T2,5 e T5. Não preciso nem dizer que o polia deve ser sempre do mesmo tipo da correia, o que pode variar é a quantidade de dentes, quanto menos dentes melhor a resolução. E de preferência compre correias e polias do mesmo fornecedor. Procure se informar de como a correia é fixada no respectivo eixo, geralmente é utilizado alguma peça impressa de plástico. Pode ser encontrado com fabricantes nacionais, mercadolivre, ebay, aliexpress, etc. Recomendo GT2 com polias de alumínio.
- Frame (peças da estrutura): a impressora precisa de um corpo/esqueleto para ser construída. Quanto mais rígida e forte melhor, mas os custos irão variar muito. Aqui vai do gosto e do bolso de cada um. Existem frames que são necessário peças plásticas impressas e ferragens para a montagem (prusa mendel), peças plásticas e perfils de alumínio 15x15 ou 20x20 (Delta Kossel Mini e MendelMax 2.0), peças de madeira ou acrílico e ferragens (Prusai3) ou apenas peças de madeira ou acrílico (Graberi3). Um frame de madeira para uma Graberi3 custa em média R$ 80,00. Já um de acrílico pode variar de R$ 135,00~R$ 300,00. A maioria dos modelos necessita de barras lisas de metal para deslizar os eixos e rolamentos. Geralmente utilizam barras lisas e rolamentos lineares de 8, 10 ou 12mm. As barras de inox são mais rígidas, não apresentam ferrugem com o passar do tempo e em geral são mais alinhadas, mas em compensação custam muito mais caro. Barras de rosca são utilizadas na estrutura de algumas e também no eixo Z. As medidas variam, geralmente 5 ou 6mm. Todos os modelos necessitam de uma grande quantidade de parafusos e porcas. Após definir o modelo de impressora que irá construir, procure a lista de ferragens necessária para a sua construção. A maioria das ferragens pode ser comprado em qualquer loja de ferragens, os itens mais difíceis de se encontrar são as barras lisas. Alguns fabricantes vendem kits com as barras e rolamentos como a 3dmachine e a sethi3d.
- Heat Bed: o local onde o plástico derretido é depositado em camadas para formar o objeto é preciso ser aquecido para que as camadas grudem umas nas outras. O heat Bed é uma placa de PCB ou alumínio com um circuito impresso para que fique aquecido por toda a área da placa. O padrão atual é o MK2B. Pode ser comprado no ebay, aliexpress, mercadolivre ou sites de fabricantes nacionais. As de alumínio aquecem mais rápido e tem uma rigidez melhor.
- Sensores: dois tipos de sensores são indispensáveis para a construção de uma impressora, os termistores para regular corretamente a temperatura tanto do hotend como da heat bed e os sensores de fim de curso para os eixos x, y e z. Se comprar um hotend completo já montado, o termistor já vem acoplado, necessitando somente ligar na placa os fios do termistor e de alimentação do cartucho aquecedor do hotend. O do heat bed é preciso ser colado com uma fita de alta temperatura (Kapton). Se o termistor não ficar fixo corretamente no hotend ou na heatbed, eles podem informar um valor errado que pode causar a queima de componentes. Os sensores de fim de curso são responsáveis por indicar onde é o ponto mínimo de movimento para os eixos. O ponto máximo pode ser informado via software, mas o mínimo é necessário. Para fixar eles no frame da impressora é preciso peças impressas de plástico (suporte de endstop) ou fixados com abraçadeiras de nylon. Os sensores podem ser comprados no ebay, aliexpress, mercadolivre ou sites de fabricantes nacionais.
- Filamento: apesar do preço alto, os fabricantes de filamento nacionais apresentam uma qualidade muito boa e como o câmbio de dolar oscila muito, pode compensar ou não importar filamento. Os principais nacionais são F3D e Movtech. Atenção para comprar o filamento na mesma medida do hotend, 1.75 ou 3mm.
Boa sorte para quem chegou até aqui disposto a montar sua própria impressora. Não esqueça de comprar um paquímetro para ajustar sua impressora. Segue alguns links úteis:
http://www.sethi3d.com.br/
http://www.3dmachine.com.br/
http://movtech.webstorelw.com.br/
http://www.phq3d.com.br/
http://helimaniarc.lojaintegrada.com.br/
http://f2link.f2b.com.br/impressora3d
http://e3d-online.com/
http://www.reprapdiscount.com/
http://www.forsetisolucoes.com.br/comercio-de-perfis.html
http://www.thingiverse.com/
http://reprap.org/wiki/RepRap_Options
fonte: "Blog do Junior Tada"…
armengol@gmail.com and Andras Tucsni. These functions implement functions 3, 6, and 16 (read holding registers, preset single register and preset multiple registers) of the Modbus RTU Protocol, to be used over the Arduino serial connection. This implementation DOES NOT fully comply with the Modbus specifications. This Arduino adaptation is derived from the work By P.Costigan email: phil@pcscada.com.au http://pcscada.com.au These library of functions are designed to enable a program send and receive data from a device that communicates using the Modbus protocol. Copyright (C) 2000 Philip Costigan P.C. SCADA LINK PTY. LTD. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The functions included here have been derived from the Modicon Modbus Protocol Reference Guide which can be obtained from Schneider at www.schneiderautomation.com. This code has its origins with paul@pmcrae.freeserve.co.uk (http://www.pmcrae.freeserve.co.uk) who wrote a small program to read 100 registers from a modbus slave. I have used his code as a catalist to produce this more functional set of functions. Thanks paul. */
/* * configure_mb_slave(baud, parity, tx_en_pin) * * sets the communication parameters for of the serial line. * * baud: baudrate in bps (typical values 9600, 19200... 115200) * parity: a single character sets the parity mode (character frame format): * 'n' no parity (8N1); 'e' even parity (8E1), 'o' for odd parity (8O1). * tx_en_pin: arduino pin number that controls transmision/reception * of an external half-duplex device (e.g. a RS485 interface chip). * 0 or 1 disables this function (for a two-device network) * >2 for point-to-multipoint topology (e.g. several arduinos) */void configure_mb_slave(long baud, char parity, char txenpin);
/* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * checks if there is any valid request from the modbus master. If there is, * performs the action requested * * slave: slave id (1 to 127) * regs: an array with the holding registers. They start at address 1 (master point of view) * regs_size: total number of holding registers. * returns: 0 if no request from master, * NO_REPLY (-1) if no reply is sent to the master * an exception code (1 to 4) in case of a modbus exceptions * the number of bytes sent as reply ( > 4) if OK. */
int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size);
/* Modbus RTU common parameters, the Master MUST use the same parameters */enum { COMM=BPS = 9600, MB_SLAVE = 1, /* modbus slave id */};/* slave registers example */enum { MB_PINO_A5, MB_REG1, MB_REGS /* total number of registers on slave */};
int regs[MB_REGS]; /* this is the slave's modbus data map */
void setup() { /* Modbus setup example, the master must use the same COM parameters */ /* 1600 bps, 8N1, two-device network */ configure_mb_slave(1600, 'n', 0); emon1.current(1, 111.1); // Current: input pin, calibration.}
void loop() {
/* This is all for the Modbus slave */ double Irms = emon1.calcIrms(1480); // Calculate Irms only
/* your code goes here */ //myservo.write(regs[0]); Serial.print(Irms*230.0); // Apparent power Serial.print(" "); Serial.println(Irms); // Irms
/* your code goes here */}
/**************************************************************************** * BEGIN MODBUS RTU SLAVE FUNCTIONS ****************************************************************************/
/* global variables */unsigned int Txenpin = 0; /* Enable transmission pin, used on RS485 networks */
/* enum of supported modbus function codes. If you implement a new one, put its function code here ! */enum { FC_READ_REGS = 0x03, //Read contiguous block of holding register FC_WRITE_REG = 0x06, //Write single holding register FC_WRITE_REGS = 0x10 //Write block of contiguous registers};
/* supported functions. If you implement a new one, put its function code into this array! */const unsigned char fsupported[] = { FC_READ_REGS, FC_WRITE_REG, FC_WRITE_REGS };
/* constants */enum { MAX_READ_REGS = 0x7D, MAX_WRITE_REGS = 0x7B, MAX_MESSAGE_LENGTH = 256 };
enum { RESPONSE_SIZE = 6, EXCEPTION_SIZE = 3, CHECKSUM_SIZE = 2 };
/* exceptions code */enum { NO_REPLY = -1, EXC_FUNC_CODE = 1, EXC_ADDR_RANGE = 2, EXC_REGS_QUANT = 3, EXC_EXECUTE = 4 };
/* positions inside the query/response array */enum { SLAVE = 0, FUNC, START_H, START_L, REGS_H, REGS_L, BYTE_CNT };
/*CRC INPUTS: buf -> Array containing message to be sent to controller. start -> Start of loop in crc counter, usually 0. cnt -> Amount of bytes in message being sent to controller/ OUTPUTS: temp -> Returns crc byte for message. COMMENTS: This routine calculates the crc high and low byte of a message. Note that this crc is only used for Modbus, not Modbus+ etc. ****************************************************************************/
unsigned int crc(unsigned char *buf, unsigned char start,unsigned char cnt) { unsigned char i, j; unsigned temp, temp2, flag;
temp = 0xFFFF;
for (i = start; i < cnt; i++) { temp = temp ^ buf[i];
for (j = 1; j <= 8; j++) { flag = temp & 0x0001; temp = temp >> 1; if (flag) temp = temp ^ 0xA001; } }
/* Reverse byte order. */ temp2 = temp >> 8; temp = (temp << 8) | temp2; temp &= 0xFFFF;
return (temp);}
/*********************************************************************** * * The following functions construct the required query into * a modbus query packet. * ***********************************************************************/
/* * Start of the packet of a read_holding_register response */void build_read_packet(unsigned char slave, unsigned char function,unsigned char count, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[2] = count * 2;}
/* * Start of the packet of a preset_multiple_register response */void build_write_packet(unsigned char slave, unsigned char function,unsigned int start_addr, unsigned char count,unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = start_addr >> 8; packet[START_L] = start_addr & 0x00ff; packet[REGS_H] = 0x00; packet[REGS_L] = count;}
/* * Start of the packet of a write_single_register response */void build_write_single_packet(unsigned char slave, unsigned char function, unsigned int write_addr, unsigned int reg_val, unsigned char* packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = write_addr >> 8; packet[START_L] = write_addr & 0x00ff; packet[REGS_H] = reg_val >> 8; packet[REGS_L] = reg_val & 0x00ff;}
/* * Start of the packet of an exception response */void build_error_packet(unsigned char slave, unsigned char function,unsigned char exception, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function + 0x80; packet[2] = exception;}
/************************************************************************* * * modbus_query( packet, length) * * Function to add a checksum to the end of a packet. * Please note that the packet array must be at least 2 fields longer than * string_length. **************************************************************************/
void modbus_reply(unsigned char *packet, unsigned char string_length) { int temp_crc;
temp_crc = crc(packet, 0, string_length); packet[string_length] = temp_crc >> 8; string_length++; packet[string_length] = temp_crc & 0x00FF;}
/*********************************************************************** * * send_reply( query_string, query_length ) * * Function to send a reply to a modbus master. * Returns: total number of characters sent ************************************************************************/
int send_reply(unsigned char *query, unsigned char string_length) { unsigned char i;
if (Txenpin > 1) { // set MAX485 to speak mode UCSR0A=UCSR0A |(1 << TXC0); digitalWrite( Txenpin, HIGH); delay(1); }
modbus_reply(query, string_length); string_length += 2;
for (i = 0; i < string_length; i++) { Serial.print(query[i], BYTE); }
if (Txenpin > 1) {// set MAX485 to listen mode while (!(UCSR0A & (1 << TXC0))); digitalWrite( Txenpin, LOW); }
return i; /* it does not mean that the write was succesful, though */}
/*********************************************************************** * * receive_request( array_for_data ) * * Function to monitor for a request from the modbus master. * * Returns: Total number of characters received if OK * 0 if there is no request * A negative error code on failure ***********************************************************************/
int receive_request(unsigned char *received_string) { int bytes_received = 0;
/* FIXME: does Serial.available wait 1.5T or 3.5T before exiting the loop? */ while (Serial.available()) { received_string[bytes_received] = Serial.read(); bytes_received++; if (bytes_received >= MAX_MESSAGE_LENGTH) return NO_REPLY; /* port error */ }
return (bytes_received);}
/********************************************************************* * * modbus_request(slave_id, request_data_array) * * Function to the correct request is returned and that the checksum * is correct. * * Returns: string_length if OK * 0 if failed * Less than 0 for exception errors * * Note: All functions used for sending or receiving data via * modbus return these return values. * **********************************************************************/
int modbus_request(unsigned char slave, unsigned char *data) { int response_length; unsigned int crc_calc = 0; unsigned int crc_received = 0; unsigned char recv_crc_hi; unsigned char recv_crc_lo;
response_length = receive_request(data);
if (response_length > 0) { crc_calc = crc(data, 0, response_length - 2); recv_crc_hi = (unsigned) data[response_length - 2]; recv_crc_lo = (unsigned) data[response_length - 1]; crc_received = data[response_length - 2]; crc_received = (unsigned) crc_received << 8; crc_received = crc_received | (unsigned) data[response_length - 1];
/*********** check CRC of response ************/ if (crc_calc != crc_received) { return NO_REPLY; }
/* check for slave id */ if (slave != data[SLAVE]) { return NO_REPLY; } } return (response_length);}
/********************************************************************* * * validate_request(request_data_array, request_length, available_regs) * * Function to check that the request can be processed by the slave. * * Returns: 0 if OK * A negative exception code on error * **********************************************************************/
int validate_request(unsigned char *data, unsigned char length,unsigned int regs_size) { int i, fcnt = 0; unsigned int regs_num = 0; unsigned int start_addr = 0; unsigned char max_regs_num;
/* check function code */ for (i = 0; i < sizeof(fsupported); i++) { if (fsupported[i] == data[FUNC]) { fcnt = 1; break; } } if (0 == fcnt) return EXC_FUNC_CODE;
if (FC_WRITE_REG == data[FUNC]) { /* For function write single reg, this is the target reg.*/ regs_num = ((int) data[START_H] << 8) + (int) data[START_L]; if (regs_num >= regs_size) return EXC_ADDR_RANGE; return 0; } /* For functions read/write regs, this is the range. */ regs_num = ((int) data[REGS_H] << 8) + (int) data[REGS_L]; /* check quantity of registers */ if (FC_READ_REGS == data[FUNC]) max_regs_num = MAX_READ_REGS; else if (FC_WRITE_REGS == data[FUNC]) max_regs_num = MAX_WRITE_REGS;
if ((regs_num < 1) || (regs_num > max_regs_num)) return EXC_REGS_QUANT;
/* check registers range, start address is 0 */ start_addr = ((int) data[START_H] << 8) + (int) data[START_L]; if ((start_addr + regs_num) > regs_size) return EXC_ADDR_RANGE;
return 0; /* OK, no exception */}
/************************************************************************ * * write_regs(first_register, data_array, registers_array) * * writes into the slave's holding registers the data in query, * starting at start_addr. * * Returns: the number of registers written ************************************************************************/
int write_regs(unsigned int start_addr, unsigned char *query, int *regs) { int temp; unsigned int i;
for (i = 0; i < query[REGS_L]; i++) { /* shift reg hi_byte to temp */ temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8; /* OR with lo_byte */ temp = temp | (int) query[(BYTE_CNT + 2) + i * 2];
regs[start_addr + i] = temp; } return i;}
/************************************************************************ * * preset_multiple_registers(slave_id, first_register, number_of_registers, * data_array, registers_array) * * Write the data from an array into the holding registers of the slave. * *************************************************************************/
int preset_multiple_registers(unsigned char slave,unsigned int start_addr,unsigned char count, unsigned char *query,int *regs) { unsigned char function = FC_WRITE_REGS; /* Preset Multiple Registers */ int status = 0; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE];
build_write_packet(slave, function, start_addr, count, packet);
if (write_regs(start_addr, query, regs)) { status = send_reply(packet, RESPONSE_SIZE); }
return (status);}
/************************************************************************ * * write_single_register(slave_id, write_addr, data_array, registers_array) * * Write a single int val into a single holding register of the slave. * *************************************************************************/
int write_single_register(unsigned char slave, unsigned int write_addr, unsigned char *query, int *regs) { unsigned char function = FC_WRITE_REG; /* Function: Write Single Register */ int status = 0; unsigned int reg_val; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE];
reg_val = query[REGS_H] << 8 | query[REGS_L]; build_write_single_packet(slave, function, write_addr, reg_val, packet); regs[write_addr] = (int) reg_val;/* written.start_addr=write_addr; written.num_regs=1;*/ status = send_reply(packet, RESPONSE_SIZE);
return (status);}
/************************************************************************ * * read_holding_registers(slave_id, first_register, number_of_registers, * registers_array) * * reads the slave's holdings registers and sends them to the Modbus master * *************************************************************************/
int read_holding_registers(unsigned char slave, unsigned int start_addr,
unsigned char reg_count, int *regs) { unsigned char function = 0x03; /* Function 03: Read Holding Registers */ int packet_size = 3; int status; unsigned int i; unsigned char packet[MAX_MESSAGE_LENGTH];
build_read_packet(slave, function, reg_count, packet);
for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) { packet[packet_size] = regs[i] >> 8; packet_size++; packet[packet_size] = regs[i] & 0x00FF; packet_size++; }
status = send_reply(packet, packet_size);
return (status);}
void configure_mb_slave(long baud, char parity, char txenpin){ Serial.begin(baud);
switch (parity) { case 'e': // 8E1 UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; case 'o': // 8O1 UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0)); break; case 'n': // 8N1 UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; default: break; }
if (txenpin > 1) { // pin 0 & pin 1 are reserved for RX/TX Txenpin = txenpin; /* set global variable */ pinMode(Txenpin, OUTPUT); digitalWrite(Txenpin, LOW); }
return;}
/* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * checks if there is any valid request from the modbus master. If there is, * performs the action requested */
unsigned long Nowdt = 0;unsigned int lastBytesReceived;const unsigned long T35 = 5;
int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size) { unsigned char query[MAX_MESSAGE_LENGTH]; unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE]; unsigned int start_addr; int exception; int length = Serial.available(); unsigned long now = millis();
if (length == 0) { lastBytesReceived = 0; return 0; }
if (lastBytesReceived != length) { lastBytesReceived = length; Nowdt = now + T35; return 0; } if (now < Nowdt) return 0;
lastBytesReceived = 0;
length = modbus_request(slave, query); if (length < 1) return length;
exception = validate_request(query, length, regs_size); if (exception) { build_error_packet(slave, query[FUNC], exception, errpacket); send_reply(errpacket, EXCEPTION_SIZE); return (exception); } start_addr = ((int) query[START_H] << 8) + (int) query[START_L]; switch (query[FUNC]) { case FC_READ_REGS: return read_holding_registers(slave, start_addr, query[REGS_L], regs); break; case FC_WRITE_REGS: return preset_multiple_registers(slave, start_addr, query[REGS_L], query, regs); break; case FC_WRITE_REG: write_single_register(slave, start_addr, query, regs); break; }}…
TTT.TTT,LLL.LLL
#include <LiquidCrystal.h>LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define DEBUG /// Habilita debug na serial#define led 13
byte Index = 0; //Indexador para o BufRX.const int BUFFER_SIZE = 28; //Tamanho do buffer serial.unsigned char BufSerial[BUFFER_SIZE]; //Buffer para armazenar os dados recebidos da serial.const int INTERVALO = 20; //Tempo para receber os dados seriaisbool ChegouDados = false;
String flag, pesoBruto, tara, pesoLiquido;
void setup() { Serial.begin(9600); pinMode(led, OUTPUT), digitalWrite(led, LOW);
lcd.begin(16, 2); lcd.print("# Balanca serial #"); lcd.setCursor(0, 1); lcd.print("iniciando");
#ifdef DEBUG Serial.println("iniciando Balança");#endif
for (int i = 0; i < 7; i++) { lcd.setCursor((i + 1), 1); lcd.print("*"); delay(100); } delay(1000); lcd.clear(); Serial.println("Balança iniciada");}
void loop() {
if (ChegouDados) { ChegouDados = false;
#ifdef DEBUG Serial.println((char*)BufSerial);#endif
String streamData = (char*)BufSerial; int commaIndex = streamData.indexOf(','); int secondCommaIndex = streamData.indexOf(',', commaIndex + 1); int thirdCommaIndex = streamData.indexOf(',', secondCommaIndex + 1);
flag = streamData.substring(0, commaIndex); pesoBruto = streamData.substring(commaIndex + 1, secondCommaIndex); tara = streamData.substring(secondCommaIndex + 1, thirdCommaIndex); pesoLiquido = streamData.substring(thirdCommaIndex + 1);
#ifdef DEBUG Serial.println(flag); Serial.println(pesoBruto); Serial.println(tara); Serial.println(pesoLiquido);#endif
if (flag == "1")// Verifica se Peso esta estável para imprimir no LCD { digitalWrite(led, HIGH); } else { digitalWrite(led, LOW); } lcd.setCursor(0, 1); lcd.print("Peso L = "); lcd.print(pesoLiquido);
memset(BufSerial, 0, BUFFER_SIZE - 1); //Limpa o buffer. }///ChegouDados
}
void serialEvent() // Trata interrupção de serial { while (Serial.available()) //Lê dados enquanto o buffer Serial tiver dados. { static unsigned long TempoAnterior = 0; //Inicializa em 0ms. unsigned long TempoCorrente = millis(); //Retorna o tempo desde que o programa foi executado.
//Timeout para limpar o BufRX e zerar o indexador (Index), quando (TempoCorrente - TempoAnterior) for maior que INTERVALO. if ( (TempoCorrente - TempoAnterior) > INTERVALO) //Executa as instruções abaixo após se passarem 20ms sem //receber dados { //TempoAnterior = TempoCorrente; Index = 0; //Inicializa ixdexador do array. memset(BufSerial, 0, BUFFER_SIZE - 1); //Limpa o buffer. }
unsigned char SerialByte = Serial.read(); // Guarda o byte do buffer serial;
delay(2);
switch (SerialByte) { case '\r': //CR. case '\n': //LF.
if (Index == 0) //Se o primeiro byte for CR ou LF, despreza-os. { return; } Index = 0; //Zera o indexador do buffer. ChegouDados = true; //Avisa que chegou Dados na serial. break;
default: if (Index < BUFFER_SIZE - 1) //Checa se há espaço no buffer. { BufSerial[Index++] = SerialByte; //Armazena o byte lido pela serial, no buffer. TempoAnterior = TempoCorrente; } } }}…
nfusões também. Tentarei não ser muito técnico.
Mas antes peço que olhe um Diagrama do Circuito de um daqueles módulos que vc disse ter o sinal "JD-VCC". Para simplificar, vamos considerar o módulo Relé com apenas "1 canal". O Diagrama está na figura a seguir:
(clique na figura para "zoom")
Na figura, vc pode observar que a sequência dos sinais no Circuito, foi alterada. A sequência dos pinos está ao lado da "foto" do módulo Relé, sendo os pinos numerados de 1 a 5. Mas esta numeração não é importante. O importante são os sinais em si, e no Circuito eu alterei esta sequência apenas para para facilitar o entendimento do funcionamento.
Vamos considerar 2 cenários: um onde o "JD-VCC" não está conectado ao "VCC", e um onde o "JD-VCC" está conectado ao "VCC" (via "jumper" encaixado nos dois terminais no conector). Então segue:
1) "JD-VCC" não conectado ao "VCC": se vc seguir o circuito iniciando pelo sinal "VCC", passando pelo Resistor R1, depois pelo LED interno do Opto-Acoplador (terminais 1 e 2 de U1), e finalmente pelo LED "LD1", então se chega ao sinal "IN1". Agora esqueça o restante do circuito, e se concentre nesse "caminho" que eu descrevi.
Se vc ligar o sinal "VCC" ao 5V do Arduino, e o sinal "IN1" a um pino de saída do Arduino, então tanto o LED do Opto-Acoplador, como o LED LD1 serão acionados quando se tem "LOW" no sinal "IN1", e caso tenhamos "HIGH" em "IN1" então ambos os LEDs ficarão desacionados. Assim dizemos que o acionamento é através do Nivel "LOW" (ou "0" se preferir).
Agora considere ligar o sinal "VCC" a um pino de saída do Arduino, e o sinal "IN1" ao GND do Arduino (NÃO é o GND da plaquinha do Relé!!!). Nesta condição, para que os LEDs sejam acionados, é necessário que tenhamos "HIGH" na saída do Arduino, e caso tenhamos "LOW" os LEDs estarão desacionados.
Conclusão: o sinal "VCC" é apenas o nome que deram ao sinal, mas de fato vc deve encará-lo apenas como o sinal que está ligado (via R1) ao "Anodo" do Opto-Acoplador (terminal 1 de U1). Assim, para que os LEDs sejam acionados, é preciso que este terminal tenha uma tensão elétrica positiva em relação ao sinal "IN1". Logo, se vc fixa o sinal "VCC" em 5V (do Arduino), então obrigatoriamente para acionar os LEDs, "IN1" deverá ser "LOW". Já se vc fixa o sinal "VCC" em 0V (o GND do Arduino), obrigatoriamente para acionar os LEDs "IN1" deverá ser "HIGH". Não tem outra forma.
Então observe, que vc pode escolher qual será o Nível Lógico ("LOW" ou "HIGH") que acionará o circuito, o que é portanto 100% flexível em relação a esta escolha do Nivel Lógico. E claro: o sinal "VCC" na plaquinha é apenas o nome que deram, e não significa que vc deve ligá-lo ao VCC do Arduino (5V). Obviamente, a escolha do nome "VCC" para o sinal, não foi muito feliz.
Mas há ainda outra questão: a isolação. Observe que o "caminho" do circuito que descrevi, em nenhum momento entra em contato elétrico com os demais sinais do Circuito, ou melhor: em nenhum momento aquele "caminho" entra em contato com o restante do Circuito que aciona diretamente a Bobina do Relé (R2, Q1, D1). Então dizemos que um circuito está isolado eletricamente do outro. Claro que existem distâncias físicas entre os componentes na plaquinha, e algumas são bem pequenas, talvez até menos de 1mm (isto irá variar de placa pra placa, já que existem muitos modelos no mercado). Os circuitos estão efetivamente isolados, mas o quanto é "forte" esta isolação, vai depender destas distâncias. Por exemplo: se vc aplicar entre os dois circuitos (pode ser entre quaisquer dois pontos entre os dois) uma tensão de 1000V (mil Volts), e a menor distância for de 1mm, provavelmente a isolação irá aguentar, mas se vc aplicar 2500V então poderá começar a "pular" faíscas elétricas entre os dois pontos (da mesma forma que pulam na "cabeça" da vela em um motor de carro). Este assunto é um tanto denso, e estou olhando pelo lado mais simples, pois na prática, a isolação dependerá de diversas características, como o material existente entre os dois pontos (no exemplo são dois: o material da placa e o "Ar"), e ainda existem variações a considerar na medição das distâncias (chamadas de "clearance").
Mas o que interessa aqui é que nas condições que descrevi para as ligações, existe isolação entre os circuitos de acionamento dos LEDs, e o restante do circuito que aciona a Bobina do Relé (D1, Q1, R2, e o transistor interno do Opto-Acoplador).
Claro, para que efetivamente o Relé seja acionado (quando vc acionar os LEDs) , vc precisa aplicar uma tensão entre o sinal "JD-VCC" e o sinal "GND", e esta tensão é a "nominal" para a Bobina do Relé. Ou seja, aplique a tensão entre "JD-VCC" e o "GND", e controle o "ON/OFF" do Relé através de um circuito isolado acionado pelo Arduino.
Logo, podemos completar a conclusão nesta "configuração": podemos escolher o Nivel Lógico que acionará o Relé, e ainda temos isolação elétrica entre o circuito do Relé e o Arduino. No entanto note que precisamos de uma Fonte "separada" para alimentarmos a Bobina do relé, e a tensão dessa Fonte será a mesma da Bobina do Relé, o que nos permite usar Relés com diferentes tensões da Bobina (sem estar limitado aos 5V do Arduino).
2) "JD-VCC" conectado ao "VCC": a primeira implicação óbvia, é que automaticamente já não temos mais a isolação elétrica entre os dois circuitos. A segunda, é que a tensão elétrica do sinal "VCC" será a mesma do sinal "JD-VCC". Mas como vimos, para acionarmos a Bobina do Relé, a tensão em "JD-VCC" deve ser em relação ao sinal "GND" da plaquinha. Então por uma questão de simplificar o controle, é conveniente que o sinal "GND" seja também conectado ao "GND" do Arduino, pois assim tanto o "VCC" (que está agora ligado ao "JD-VCC") como o "IN1" terão como referência uma mesma tensão (o GND do Arduino). E se a Bobina do Relé for de 5V, então parece conveniente também ligar o "VCC/JD-VCC" ao 5V do Arduino. E por consequência, obrigatoriamente o "IN1" deverá ser "LOW" para acionar o Relé (como vimos no item "1"), e não há outra possibilidade, uma vez que o "VCC" está ligado ao "JD-VCC" e este último tem que ser a alimentação "positiva" para a Bobina do Relé.
Conclusão: além de perdermos a isolação entre os dois Circuitos (Arduino e acionamento da Bobina do Relé), também não temos escolha em relação do Nível Lógico que liga o Relé, que agora deve ser obrigatoriamente "LOW". Qual a vantagem então? apenas uma: vc não precisa de uma Fonte "separada" para acionar a Bobina do relé, pois está aproveitando o 5V do Arduino para fazer isso, mas claro que agora também terá obrigatoriamente que usar Relés com Bobina de 5V (mas isto pode ser contornado se vc ao invés de ligar o "IN1" ao Arduino, usar um Transistor entre o Arduino e o "IN1").
E claro: a corrente "puxada" pela Bobina do Relé, será drenada do 5V do Arduino. Para a maioria dos Relés de 5V nas plaquinas, essa corrente é tipicamente de 70mA, ou seja: é significativa, e se vc tem daqueles módulos com dois Relés ou mais, cada Bobina irá puxar esta corrente do 5V do Arduino. Para um módulo com 4 Relés, a corrente "puxada" será 4x70mA = 280mA.
Devido a essa corrente "alta" para módulos com vários Relés, é comum que o "pessoal" use uma Fonte externa para o 5V do Arduino. Essa fonte alimentará o 5V do Arduino diretamente, e proverá a corrente necessária para as Bobinas dos diversos Relés no Sistema. Mas existe um problema que precisa ser considerado: não temos mais a isolação entre o Arduino e os circuitos da Bobina do Relé. Ok, mas qual o problema disso? Veja: devido à forma física como o Relé é construído, existe fácil acoplamento magnético entre a Bobina e os Contatos do Relé (óbvio né, afinal é o campo magnético da Bobina que fecha os Contatos do Relé). Assim, se algum "spike" de tensão ou corrente ocorre entre os contatos do Relé, devido ao acoplamento magnético estes "spikes" também induzem tensão nos terminais da Bobina, chegando portanto ao Arduino (já que agora não mais existe mais a isolação). Na maioria das vezes estes skipes não chegam a serem fortes o suficiente para danificar os circuitos do Arduino (embora isso seja possível), mas são fortes o suficiente para alterar Níveis Lógicos "estáticos" nos pinos do Arduino (não entrarei em detalhe de como ocorre isso, porque é muito técnico e exige muitos detalhes em conhecimento de circuitos de comutação). Então é comum que os próprios pinos do Arduino que acionam os Relés, mudem de estado, gerando acionamentos/desligamentos imprevistos dos Relés. Inclusive se o "spike" ocorre em um Relé, pode afetar qualquer outro pino do Arduino mesmo que este pino não seja usado para o controle de um Relé. Pode ser tão "bravo", que o próprio Processador do Arduino "se perde" na execução do código, sendo comum inclusive que sofra um Reset.
Devido às possibilidades de configuração da plaquinha do Relé, existem também uma série de outras consequências, mas não vou adentrar para não demorar mais no texto.
================================================================
No caso dessa sua placa de 8 relés, acho que vc já percebeu que o Fabricante fixou definitivamente o "JD-VCC" ao "VCC". Logo, a primeira consequência disto é que não há mais isolação entre o Arduino e os circuitos das Bobinas dos Relés. E porque isto foi feito? Ocorre que a maioria esmagadora das pessoas usa esses módulos de Relés com o "JD-VCC" sempre conectado ao "VCC" (ou seja, sem isolação), e por isso o Fabricante tomou a decisão de fazer essa conexão permanente no traçado do circuito. Claro, devido a esta falta da isolação, vc precisa ficar atento aos "spikes" que possam ocorrer nos contatos do Relé. Sobre isto, pesquise sobre "Snubbers" que são circuitos simples usados para minimizar a intensidade e portanto o efeito dos "spikes" (e assim impedir os problemas que descrevi sobre estes).
Mas vc deve se lembrar que quando o "JD-VCC" é conectado ao "VCC", não temos a opção de escolher o Nível Lógico que aciona os Relés. Ocorre que pra não perder esse "feature", o Fabricante da placa mudou ligeiramente o circuito que aciona o LED do Opto-Acoplador, de uma forma que agora vc consegue selecionar para cada Relé, qual o Nível Lógico de acionamento. E isto é feito naqueles pontos com "jumpers" selecionando "HIGH" ou "LOW", existentes na Placa. Apenas isso. Assim respondendo à sua última pergunta: isto não tem relação com "sobrecarga" no Arduino. É apenas uma seleção da lógica de acionamento.
E porque só existe o "DC+" e o "DC-" ? Simples: o "DC+" é o "JD-VCC" permanentemente conectado ao "VCC", e o "DC-" é o "GND" que existia na plaquinha que eu descrevi. Então obviamente, é obrigatório para esta placa que este "DC-" esteja ligado ao GND do Arduino. Mas existe um "truque" aqui: se vc tiver uma fonte de 5V alimentando diretamente o Arduino, pode minimizar os problemas de "spike", ligando o "DC+" com um fio indo diretamente ao 5V da Fonte (ou seja: sem "puxar" esta ligação da placa do Arduino). E para o "DC-" dois fios devem ser ligados: um indo diretamente ao GND da Fonte (igual feito para o "DC+", e com a mesma bitola deste), e um segundo fio (este pode ser mais fino) ligado entre o "DC-" e o GND do Arduino. Veja: isso não elimina os "spikes", mas minimiza os efeitos destes.
Como vc deve imaginar pelas explanações que postei aqui, o que vc descreveu sobre o comportamento ON/OFF da sua placa de 8 Relés, é absolutamente o que seria esperado.
Bem não entendi o que vc quis dizer sobre "ponte". Posso tentar imaginar que seria ligar direto com fio (sem usar os "jumpers") para fazer a seleção "HIGH" ou "LOW" para cada Rele. Mas não sei se é isto que vc quis dizer. Se for isso, então vc pode sim fazer a tal "ponte".
Mas talvez vc esteja se referindo a uma "ponte de relés" para acionar Motores, para controle ON/OFF e da direção da rotação. Se for isso, vc pode também fazer sem problemas (mas não se esqueça de pesquisar sobre os "Snubbers", pois pode precisar deles).
Espero ter ajudado.
Abrçs,
Elcids…
Adicionado por Elcids Chagas ao 9:47 em 20 março 2020
e-se garantir que os Níveis Lógicos sejam corretamente interpretados em cada ponta do cabo, e isso se refere tanto aos níveis de tensão, quanto às transições dos sinais digitais de LOW pra HIGH e de HIGH pra LOW (onde os tempos são chamados de "rise time" e "fall time" respectivamente) e os tempos mínimos que os sinais ficam em "LOW" ou em "HIGH".
No caso da Interface "One Wire", especificamente no caso Sensor DS18B20, isto pode ser resumido na figura mostrada a seguir que capturei do datasheet do sensor:
(clique na figura para "zoom")
O ponto fundamental a observar nesta figura, são os tempos de "slot", que nada mais são do que intervalos de tempo usados para sincronizar e garantir a correta interpretação dos bits usados na comunicação entre o DS18B20 e quem está se comunicando com sensor. E como se vê na figura anterior, são uma série de parâmetros, a maioria da ordem de algumas dezenas de micro-segundos.
Na figura anterior, a parte marcada na área na cor azul, é quando o estamos enviando um comando para o DS18B20. Já na área marcada na cor verde, é quando estamos lendo os dados fornecidos pelo DS18B20.
Especificamente, quando se está lendo os dados do DS18B20, o datasheet do mesmo faz algumas considerações a serem seguidas, e isto pode ser visto na figura a seguir:
(clique na figura para "zoom")
Na figura anterior, os pontos mais críticos eu marquei em rosa e em amarelo (são intervalos de tempo). Basicamente, a responsabilidade de seguir estas especificações, é da Biblioteca que vc está usando. Então acredita-se que esta Biblioteca siga isto corretamente. Mas como eu disse no início, os sinais digitais em cabos de longa distância, podem se degradar, tanto em termos de tensão quanto em termos de tempo, e aí reside a maioria dos problemas, pois é muito comum que as áreas que marquei em rosa e amarelo, se degradem causando a interpretação errada dos bits.
A recomendação, está em uma página do datasheet do DS18B20 que eu marquei na cor amarela mostrado na figura a seguir:
(clique na figura para "zoom")
Caso vc queira saber mais sobre as temporizações mostradas nas figuras, basta perguntar aqui no tópico.
2) segundo, deve-se prover a proteção adequada para os circuitos, em cada ponta do cabo. Ocorre que devido ao grande comprimento do cabo, aparecem "spikes" de tensão nas pontas (e claro, ao longo do cabo também). Estes "spikes" podem ter amplitudes muito maiores que as tensões de trabalho dos circuitos que estão conectados às pontas dos cabos. Por exemplo, se os sinais digitais usados são de 5V, facilmente podem aparecerem "spikes" de mais de 50V nas pontas. E aí já viu, né? Mesmo que estes "spikes" durem alguns micro-segundos ou nano-segundos, eles podem ser suficientes para danificar permanentemente os circuitos conectados às pontas dos cabos (e isto é bem fácil de ocorrer). Às vezes os "spikes" não danificam os circuitos de imediato, mas já viu o ditado: "água mole em pedra dura, tanto bate até que fura" (e acredite, neste caso ocorre mesmo um "furo" no componente eletrônico, claro, um furo microscópico que só é possível ver através de processos e instrumentos adequados).
Assim, para seu caso, proceda como no diagrama mostrado na figura a seguir:
(clique na figura para "zoom")
Vou fazer algumas considerações sobre alguns pontos desse circuito:
1) a tensão de alimentação enviada pelo cabo, é de 5V. Como vc está usando o ESP32, a melhor forma de se obter essa tensão, vai depender de como vc está alimentado a placa do ESP32. Por exemplo: se vc está alimentando via conector USB, então vc pode obter essa tensão a partir do pino "VIN" da plaquinha do ESP, pois embora a tensão ali presente seja um pouco menor que 5V (será algo próximo a 4.7V), ainda assim está dentro de uma margem aceitável para o Sistema. Mas se vc estiver alimentando a plaquinha do ESP justamente pelo pino "VIN", e esta tensão for de 5V, então pode usar este pino da mesma forma.
Observe no diagrama, que a tensão de alimentação, é enviada através de um "par trançado" do cabo CAT5 (no diagrama, é o par "laranja - branco/laranja"). O uso do "par trançado" para enviar a alimentação, é essencial, pois diversos ruídos de alta frequência serão filtrados com isso. Mas cuidado para não trocar o 5V com o GND em cada ponta do cabo (senão já sabe o que ocorre).
2) na região marcada com linha tracejada na cor azul, que fica do lado do ESP32, está o circuito que filtra o sinal digital "vindo" do DS18B20, e protege o pino do ESP32 contra "spikes" de alta-tensão existentes no cabo (que aparecem quando os sinais digitais mudam de estado). Este circuito não interfere quando é o ESP32 quem envia bits para o DS18B20. A proteção, é implementada tanto com o Resistor R1, quanto pelos Diodos D1 e D2. Observe que o Katodo de D1 está ligado aos 3.3V, ou seja, ligado à tensão de alimentação do ESP32.
O Resistor R2 (de valor 2k2), faz o papel do pullpup do Barramento "One Wire", mas do lado do ESP32. Ele é essencial para o funcionamento correto do Sistema (e haverá outro, já no lado do Sensor).
Observe que como marcado na figura, esta região em azul tracejado, deverá ficar o mais próxima possível da plaquinha do ESP32 (evite passar de 30 centímetros).
E veja que para o sinal de comunicação, é usado um fio simples (no diagrama, é o fio "branco/verde"). Ou seja: não use um "par trançado" com as pontas curto-circuitadas. Isso é para não aumentar a Capacitância entre este sinal e os demais fios (Alimentação e Blindagem). Assim escolha um fio e use apenas ele para o sinal de comunicação do "One Wire".
3) dentro da região tracejada na cor verde no lado do Sensor, temos uma área também na cor verde, onde estão os componentes R3, C2, e C3. Estes constituem um filtro para a alimentação do DS18B20. Este filtro é fundamental. Nele, R3 e C2 filtram oscilações de baixa frequência, enquanto que R3 e C3 filtram as de alta-frequência. Sem este filtro, não é possível garantir a estabilidade primária do sinal digital fornecido pelo DS18B20, quando estamos lendo os dados do mesmo.
Atenção à polaridade do Capacitor Eletrolítico C2 (o positivo está marcado na figura com um "+" na cor vermelha). A tensão de trabalho desse Capacitor deve ser acima de 16V, mas evite que seja muito alta (até 100V está de bom tamanho).
4) sobre R4, C4, D3, e D4, estes fazem a mesma coisa que aqueles componentes de mesmo valor fazem no lado do ESP32. Então não é preciso dizer mais nada sobre eles. Apenas notar que aqui, o Katodo de D3 está ligado aos 5V (no lado do ESP, o Katodo de D1 vai aos 3.3V).
E o Resistor R5 faz o mesmo que R2 faz do lado do ESP32. E estes dois Resistores são fundamentais.
5) notar que todo o circuito dentro da região tracejada na cor verde, deve ficar o mais próximo possível do Sensor DS18B20. Por isso, se possível, corte o cabo original que vem com o Sensor de forma a reduzir a distância em questão. Se a princípio vc não quiser fazer isso, tudo bem, pode manter o cabo do Sensor com o comprimento original, mas claro, se vc manter o cabo assim, faça vários testes pra ver se está tudo funcionando corretamente (ou seja, se o comprimento do cabo original não está afetando o funcionamento do Sistema) e de forma estável.
6) se o cabo CAT5 tiver malha de blindagem, NÃO ligue esta blindagem. Deixe desconectada nas duas extremidades do cabo. Embora a blindagem possa até eliminar algumas interferências, este benefício não compensa neste Sistema. Isto porque se vc ligar a blindagem ao GND, irá aumentar a Capacitância distribuída no cabo, especificamente entre o sinal do fio de comunicação e o GND, o que será péssimo para aquele sinal. Não queremos que esta Capacitância aumente, porque ela degrada o sinal de comunicação. Então deixe desligada a blindagem.
7) se existir um TERRA (da instalação da rede elétrica) no lado do DS18B20, não ligue este TERRA ao GND do circuito do DS18B20. Este tipo de ligação, tem alguns benefícios, mas não é necessário no seu Sistema, pois há também "malefícios" e por isso a princípio não compensa.
8) finalmente, atente para os tipos de alguns componentes. Especificamente, C1, C3, e C4, devem ser obrigatoriamente do tipo "Cerâmico" (NÃO serve de poliéster metalizado !!!). Atenção aos valores de C1 e C4, que são de 100pF (100 pico Farad). Não aumente além disso. Já C3 pode ter outro valor, desde que maior que 100nF (100 nano Farad, ou 100 kpF).
Já os Diodos D1 a D4, use preferencialmente do tipo "Schottky" (pode ser BAT85, BAT82, ou similares). Mas pode tentar também com o famigerado 1N4148. Já os 1N400x (os 1N4001, 1N4002, 1N4007, etc), nem pensar, pois são inadequados para esta aplicação.
Para detalhes do circuito, use o PDF que imprimi: "DS18B20_longa_distancia_01.pdf"
Ah sim: se possível use no seu código, o método de "leitura assíncrona" para o DS18B20. É muito mais eficiente em termos de tempo de processamento, e praticamente não bloqueia seu código. Mas isso é apenas uma recomendação. Se tiver alguma dúvida de como implementar isso, pergunte aqui (mas há exemplo na própria Biblioteca do Sensor).
Espero ter ajudado.
Abrçs,
Elcids…
Adicionado por Elcids Chagas ao 12:12 em 17 julho 2021
nosso caso é modbus serial, antes carregue esse sketch abaixo no seu arduino pegue um led e coloque na porta 3 do arduino a porta digital, dando tudo certo vc vai lá no watch list e marca 1 ou zero o o seu led vai acender e apagar via modbus serial é bem legal do resto é só configurar outros sensores que deve funcionar,
/* Modbus serial - RTU Slave Arduino Sketch Marcos Daniel Wiechert wiechertdaniel@yahoo.com.br Baseado na biblioteca de Juan Pablo Zometa : jpmzometa@gmail.com http://sites.google.com/site/jpmzometa/ and Samuel Marco: sammarcoarmengol@gmail.com and Andras Tucsni. As funções do protocolo MODBUS implementadas neste código: 3 - Read holding registers; 6 - Preset single register; 16 - Preset multiple registers. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The functions included here have been derived from the Modicon Modbus Protocol Reference Guide which can be obtained from Schneider at www.schneiderautomation.com. This code has its origins with paul@pmcrae.freeserve.co.uk (http://www.pmcrae.freeserve.co.uk) who wrote a small program to read 100 registers from a modbus slave. *//* * configure_mb_slave(baud, parity, tx_en_pin) * * configuração dos parametros da porta serial. * * baud: taxa de transmissão em bps (valores típicos entre 9600, 19200... 115200) * parity: seta o modo de paridade: * 'n' sem paridade (8N1); 'e' paridede impar (8E1), 'o' paridade par (8O1). * tx_en_pin: pino do arduino que controla a transmissão/recepção em uma linha RS485. * 0 or 1 desliga esta função (para rede RS232) * >2 para uma rede multiponto. */void configure_mb_slave(long baud, char parity, char txenpin);/* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada * * slave: endereço do escravo (arduino) (1 to 127) * regs: uma matriz com os holding registers. Eles começam no endereço 1 (mestre ponto de vista) * Regs_size: número total de holding registers. * Retorna: 0 se não houver pedido do mestre, * NO_REPLY (-1) se nenhuma resposta é enviada para o mestre * Caso um código de exceção (1 a 4) em algumas exceções de modbus * O número de bytes enviados como resposta (> 4) se OK. */int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size);/* Aqui começa o código do exemplo *//* Parâmetros Modbus RTU de comunicação, o Mestre e os escravos devem usar os mesmos parâmetros */enum { COMM_BPS = 9600, /* baud rate */ MB_SLAVE = 1, /* endereço do escravo modbus */ PARITY = 'n' /* paridade */};/* registros do escravo (holding registers)* * Aqui ficam ordenados todos os registros de leitura e escrita* da comunicação entre o mestre e o escravo (SCADA e arduino) * */enum { MB_PINO_3, /* Controle do Led no pino 3 (desliga=0 liga=1) */ MB_REGS /* número total de registros do escravo */};int regs[MB_REGS];int ledPin3 = 3;void setup() { /* configura cominicação modbus * 9600 bps, 8N1, RS485 network */ configure_mb_slave(COMM_BPS, PARITY, 2); pinMode(ledPin3, OUTPUT); }void loop() { /* verifica se há solicitações do mestre */ update_mb_slave(MB_SLAVE, regs, MB_REGS); /* os valores dos registros são definidos pelo mestre modbus (SCADA) */ switch(regs[MB_PINO_3]) { case 1: digitalWrite(ledPin3, HIGH); break; default: /* apagado */ digitalWrite(ledPin3, LOW); } } /**************************************************************************** * INICIO DAS FUNÇÕES ESCRAVO Modbus RTU ****************************************************************************//* variaveis globais */unsigned int Txenpin = 2; /*Definir o pino usado para colocar o driver RS485 em modo de transmissão, utilizado somente em redes RS485 quando colocar em 0 ou 1 para redes RS232 *//* Lista de códigos de função modbus suportados. Se você implementar um novo, colocar o seu código de função aqui! */enum { FC_READ_REGS = 0x03, //Read contiguous block of holding register (Ler um bloco contíguo de registos) FC_WRITE_REG = 0x06, //Write single holding register (Escrever em um único registro) FC_WRITE_REGS = 0x10 //Write block of contiguous registers (Escrever em um bloco contíguo de registos)};/* Funções suportadas. Se você implementar um novo, colocar seu código em função nessa matriz! */const unsigned char fsupported[] = { FC_READ_REGS, FC_WRITE_REG, FC_WRITE_REGS };/* constantes */enum { MAX_READ_REGS = 0x7D, MAX_WRITE_REGS = 0x7B, MAX_MESSAGE_LENGTH = 256 };enum { RESPONSE_SIZE = 6, EXCEPTION_SIZE = 3, CHECKSUM_SIZE = 2 };/* código de exceções */enum { NO_REPLY = -1, EXC_FUNC_CODE = 1, EXC_ADDR_RANGE = 2, EXC_REGS_QUANT = 3, EXC_EXECUTE = 4 };/* posições dentro da matriz de consulta / resposta */enum { SLAVE = 0, FUNC, START_H, START_L, REGS_H, REGS_L, BYTE_CNT };/*CRC INPUTS: buf -> Matriz contendo a mensagem a ser enviada para o controlador mestre. start -> Início do loop no crc do contador, normalmente 0. cnt -> Quantidade de bytes na mensagem a ser enviada para o controlador mestre OUTPUTS: temp -> Retorna byte crc para a mensagem. COMMENTÁRIOS: Esta rotina calcula o byte crc alto e baixo de uma mensagem. Note que este CRC é usado somente para Modbus, não em Modbus PLUS ou TCP. ****************************************************************************/unsigned int crc(unsigned char *buf, unsigned char start,unsigned char cnt) { unsigned char i, j; unsigned temp, temp2, flag; temp = 0xFFFF; for (i = start; i < cnt; i++) { temp = temp ^ buf[i]; for (j = 1; j <= 8; j++) { flag = temp & 0x0001; temp = temp >> 1; if (flag) temp = temp ^ 0xA001; } } /* Inverter a ordem dos bytes. */ temp2 = temp >> 8; temp = (temp << 8) | temp2; temp &= 0xFFFF; return (temp);}/*********************************************************************** * * As seguintes funções constroem o frame de * um pacote de consulta modbus. * ***********************************************************************//* * Início do pacote de uma resposta read_holding_register */void build_read_packet(unsigned char slave, unsigned char function,unsigned char count, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[2] = count * 2;} /* * Início do pacote de uma resposta preset_multiple_register */void build_write_packet(unsigned char slave, unsigned char function,unsigned int start_addr, unsigned char count,unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = start_addr >> 8; packet[START_L] = start_addr & 0x00ff; packet[REGS_H] = 0x00; packet[REGS_L] = count;} /* * Início do pacote de uma resposta write_single_register */void build_write_single_packet(unsigned char slave, unsigned char function, unsigned int write_addr, unsigned int reg_val, unsigned char* packet) { packet[SLAVE] = slave; packet[FUNC] = function; packet[START_H] = write_addr >> 8; packet[START_L] = write_addr & 0x00ff; packet[REGS_H] = reg_val >> 8; packet[REGS_L] = reg_val & 0x00ff;} /* * Início do pacote de uma resposta excepção */void build_error_packet(unsigned char slave, unsigned char function,unsigned char exception, unsigned char *packet) { packet[SLAVE] = slave; packet[FUNC] = function + 0x80; packet[2] = exception;} /************************************************************************* * * modbus_query( packet, length) * * Função para adicionar uma soma de verificação para o fim de um pacote. * Por favor, note que a matriz pacote deve ser de pelo menos 2 campos mais do que * String_length. **************************************************************************/void modbus_reply(unsigned char *packet, unsigned char string_length) { int temp_crc; temp_crc = crc(packet, 0, string_length); packet[string_length] = temp_crc >> 8; string_length++; packet[string_length] = temp_crc & 0x00FF;} /*********************************************************************** * * send_reply( query_string, query_length ) * * Função para enviar uma resposta a um mestre Modbus. * Retorna: o número total de caracteres enviados ************************************************************************/int send_reply(unsigned char *query, unsigned char string_length) { unsigned char i; if (Txenpin > 1) { // coloca o MAX485 no modo de transmissão UCSR0A=UCSR0A |(1 << TXC0); digitalWrite( Txenpin, HIGH); delayMicroseconds(3640); // aguarda silencio de 3.5 caracteres em 9600bps } modbus_reply(query, string_length); string_length += 2; for (i = 0; i < string_length; i++) { Serial.print(query[i], BYTE); } if (Txenpin > 1) {// coloca o MAX485 no modo de recepção while (!(UCSR0A & (1 << TXC0))); digitalWrite( Txenpin, LOW); } return i; /* isso não significa que a gravação foi bem sucedida */}/*********************************************************************** * * receive_request( array_for_data ) * * Função para monitorar um pedido do mestre modbus. * * Retorna: Número total de caracteres recebidos se OK * 0 se não houver nenhum pedido * Um código de erro negativo em caso de falha ***********************************************************************/int receive_request(unsigned char *received_string) { int bytes_received = 0; /* FIXME: não Serial.available esperar 1.5T ou 3.5T antes de sair do loop? */ while (Serial.available()) { received_string[bytes_received] = Serial.read(); bytes_received++; if (bytes_received >= MAX_MESSAGE_LENGTH) return NO_REPLY; /* erro de porta */ } return (bytes_received);}/********************************************************************* * * modbus_request(slave_id, request_data_array) * * Função que é retornada quando o pedido está correto * e a soma de verificação está correto. * Retorna: string_length se OK * 0 se não * Menos de 0 para erros de exceção * * Nota: Todas as funções usadas para enviar ou receber dados via * Modbus devolver esses valores de retorno. * **********************************************************************/int modbus_request(unsigned char slave, unsigned char *data) { int response_length; unsigned int crc_calc = 0; unsigned int crc_received = 0; unsigned char recv_crc_hi; unsigned char recv_crc_lo; response_length = receive_request(data); if (response_length > 0) { crc_calc = crc(data, 0, response_length - 2); recv_crc_hi = (unsigned) data[response_length - 2]; recv_crc_lo = (unsigned) data[response_length - 1]; crc_received = data[response_length - 2]; crc_received = (unsigned) crc_received << 8; crc_received = crc_received | (unsigned) data[response_length - 1]; /*********** verificar CRC da resposta ************/ if (crc_calc != crc_received) { return NO_REPLY; } /* verificar a ID do escravo */ if (slave != data[SLAVE]) { return NO_REPLY; } } return (response_length);}/********************************************************************* * * validate_request(request_data_array, request_length, available_regs) * * Função para verificar se o pedido pode ser processado pelo escravo. * * Retorna: 0 se OK * Um código de exceção negativa em caso de erro * **********************************************************************/int validate_request(unsigned char *data, unsigned char length,unsigned int regs_size) { int i, fcnt = 0; unsigned int regs_num = 0; unsigned int start_addr = 0; unsigned char max_regs_num; /* verificar o código de função */ for (i = 0; i < sizeof(fsupported); i++) { if (fsupported[i] == data[FUNC]) { fcnt = 1; break; } } if (0 == fcnt) return EXC_FUNC_CODE; if (FC_WRITE_REG == data[FUNC]) { /* Para a função de escrever um reg único, este é o registro alvo.*/ regs_num = ((int) data[START_H] << 8) + (int) data[START_L]; if (regs_num >= regs_size) return EXC_ADDR_RANGE; return 0; } /* Para as funções de leitura / escrita de registros, este é o intervalo. */ regs_num = ((int) data[REGS_H] << 8) + (int) data[REGS_L]; /* verifica a quantidade de registros */ if (FC_READ_REGS == data[FUNC]) max_regs_num = MAX_READ_REGS; else if (FC_WRITE_REGS == data[FUNC]) max_regs_num = MAX_WRITE_REGS; if ((regs_num < 1) || (regs_num > max_regs_num)) return EXC_REGS_QUANT; /* verificará a quantidade de registros, endereço inicial é 0 */ start_addr = ((int) data[START_H] << 8) + (int) data[START_L]; if ((start_addr + regs_num) > regs_size) return EXC_ADDR_RANGE; return 0; /* OK, sem exceção */}/************************************************************************ * * write_regs(first_register, data_array, registers_array) * * escreve nos registradores do escravo os dados em consulta, * A partir de start_addr. * * Retorna: o número de registros escritos ************************************************************************/int write_regs(unsigned int start_addr, unsigned char *query, int *regs) { int temp; unsigned int i; for (i = 0; i < query[REGS_L]; i++) { /* mudar reg hi_byte para temp */ temp = (int) query[(BYTE_CNT + 1) + i * 2] << 8; /* OR com lo_byte */ temp = temp | (int) query[(BYTE_CNT + 2) + i * 2]; regs[start_addr + i] = temp; } return i;}/************************************************************************ * * preset_multiple_registers(slave_id, first_register, number_of_registers, * data_array, registers_array) * * Escreva os dados na matriz dos registos do escravo. * *************************************************************************/int preset_multiple_registers(unsigned char slave,unsigned int start_addr,unsigned char count, unsigned char *query,int *regs) { unsigned char function = FC_WRITE_REGS; /* Escrever em múltiplos registros */ int status = 0; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; build_write_packet(slave, function, start_addr, count, packet); if (write_regs(start_addr, query, regs)) { status = send_reply(packet, RESPONSE_SIZE); } return (status);}/************************************************************************ * * write_single_register(slave_id, write_addr, data_array, registers_array) * * Escrever um único valor inteiro em um único registo do escravo. * *************************************************************************/int write_single_register(unsigned char slave, unsigned int write_addr, unsigned char *query, int *regs) { unsigned char function = FC_WRITE_REG; /* Função: Write Single Register */ int status = 0; unsigned int reg_val; unsigned char packet[RESPONSE_SIZE + CHECKSUM_SIZE]; reg_val = query[REGS_H] << 8 | query[REGS_L]; build_write_single_packet(slave, function, write_addr, reg_val, packet); regs[write_addr] = (int) reg_val;/* written.start_addr=write_addr; written.num_regs=1;*/ status = send_reply(packet, RESPONSE_SIZE); return (status);}/************************************************************************ * * read_holding_registers(slave_id, first_register, number_of_registers, * registers_array) * * lê os registros do escravo e envia para o mestre Modbus * *************************************************************************/int read_holding_registers(unsigned char slave, unsigned int start_addr,unsigned char reg_count, int *regs) { unsigned char function = 0x03; /* Função 03: Read Holding Registers */ int packet_size = 3; int status; unsigned int i; unsigned char packet[MAX_MESSAGE_LENGTH]; build_read_packet(slave, function, reg_count, packet); for (i = start_addr; i < (start_addr + (unsigned int) reg_count); i++) { packet[packet_size] = regs[i] >> 8; packet_size++; packet[packet_size] = regs[i] & 0x00FF; packet_size++; } status = send_reply(packet, packet_size); return (status);}void configure_mb_slave(long baud, char parity, char txenpin){ Serial.begin(baud); switch (parity) { case 'e': // 8E1 UCSR0C |= ((1<<UPM01) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; case 'o': // 8O1 UCSR0C |= ((1<<UPM01) | (1<<UPM00) | (1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UCSZ02) | (1<<USBS0)); break; case 'n': // 8N1 UCSR0C |= ((1<<UCSZ01) | (1<<UCSZ00)); // UCSR0C &= ~((1<<UPM01) | (1<<UPM00) | (1<<UCSZ02) | (1<<USBS0)); break; default: break; } if (txenpin > 1) { // pino 0 & pino 1 são reservados para RX/TX Txenpin = txenpin; /* definir variável global */ pinMode(Txenpin, OUTPUT); digitalWrite(Txenpin, LOW); } return;} /* * update_mb_slave(slave_id, holding_regs_array, number_of_regs) * * verifica se há qualquer pedido válido do mestre modbus. Se houver, * executa a ação solicitada */unsigned long Nowdt = 0;unsigned int lastBytesReceived;const unsigned long T35 = 5;int update_mb_slave(unsigned char slave, int *regs,unsigned int regs_size) { unsigned char query[MAX_MESSAGE_LENGTH]; unsigned char errpacket[EXCEPTION_SIZE + CHECKSUM_SIZE]; unsigned int start_addr; int exception; int length = Serial.available(); unsigned long now = millis(); if (length == 0) { lastBytesReceived = 0; return 0; } if (lastBytesReceived != length) { lastBytesReceived = length; Nowdt = now + T35; return 0; } if (now < Nowdt) return 0; lastBytesReceived = 0; length = modbus_request(slave, query); if (length < 1) return length; exception = validate_request(query, length, regs_size); if (exception) { build_error_packet(slave, query[FUNC], exception, errpacket); send_reply(errpacket, EXCEPTION_SIZE); return (exception); } start_addr = ((int) query[START_H] << 8) + (int) query[START_L]; switch (query[FUNC]) { case FC_READ_REGS: return read_holding_registers(slave, start_addr, query[REGS_L], regs); break; case FC_WRITE_REGS: return preset_multiple_registers(slave, start_addr, query[REGS_L], query, regs); break; case FC_WRITE_REG: write_single_register(slave, start_addr, query, regs); break; }}…