28 de jul de 2011

Controle utilizando PWM

O que é PWM?
Modulação por largura de pulso ou Pulse Width Modulation (PWM) é uma técnica que permite controlar a quantidade de energia fornecida a um determinado dispositivo eletrônico / elétrico. Basicamente o controle é obtido através da variação da largura de uma tensão retangular (DC) ao longo de um período de tempo. Quanto maior a largura do retângulo maior a potência na carga, a essa variação de largura, ou seja de tempo onde o sinal tem nível alto chamamos de duty cycle ou ciclo de trabalho.
Resumidamente o sinal PWM é uma onda digital retangular onde a frequência é constante e o tempo em que o sinal possui nível alto pode ser variado entre 0 e 100% (duty cycle).
 
Formas de onda pwm



























Figura -1 Duty Cycle ou ciclo de trabalho. Fonte http://www.arduino.cc/en/Tutorial/PWM acessado em 28/07/2011

Um ótimo texto para ajudar a relembrar os conceitos sobre sinais elétricos pode ser encontrado em http://www.feiradeciencias.com.br/sala12/12_T12.asp acessado dia 26/07/2011.
Algumas das aplicações de um controle PWM seguem abaixo:
- Controlar a luminosidade de um LED;
- Gerar sinais de áudio;
- Gerar um sinal modulado;
- Controlar a velocidade de motores;
- Obter valores analógicos a partir de meios digitais;

Neste artigo vou mostrar como utilizar um arduino para controlar a luminosidade de um LED e a velocidade de um motor DC.


Controlando a luminosidade de um LED
Em uma ligação direta a uma bateria um LED emitirá uma boa quantidade de luz (é necessário limitar a corrente no LED com auxilio de um resistor), essa quantidade de luz pode ser diminuída ao se ajustar a quantidade de corrente fornecida ao LED, quanto maior a corrente, mais luz ele emitirá (lembro que existe um limite máximo de corrente que um LED suporta, consulte o datasheet de qualquer componente antes de utilizá-lo).
Para controlar a corrente pode-se mudar a resistência que é algo simples, mas nem sempre é algo prático, ou mudar a tensão, o que já é mais complicado. Contudo a principal desvantagem desses dois métodos é a perda de energia, quanto maior a tensão e corrente nos resistores maior é a perda P=U.I, sim essa perda é mínima se estivermos trabalhando com somente um LED, mas agora imagine um semáforo usando 80 LED`s por "bolacha", agora imagine duzentos semáforos em uma cidade..........
O sinal PWM normalmente tem uma frequência fixa com um duty cycle que pode variar de 0% a 100%. Ajustando o duty cycle nós podemos facilmente controlar o brilho de um LED ou a velocidade de um motor.
A tensão média aplicada a um LED / resistor após o controle por PWM será proporcional ao ciclo de trabalho.
Tensão média = Duty Cycle * Vcc
Se tivermos um duty cycle de 50% e uam tensão aplicada de 5V a tensão média em cima de um led será de 2,5V


PWM e o arduino
No Arduino Uno os pinos 3, 5, 6, 9, 10 e 11 podem ser usados para gerar um sinal PWM, podemos variar o duty cycle em até 256 passos ( 0 a 255 )
As frequências PWM padrões do arduino são:
Pinos 3, 9, 10 e 11 = 488Hz;
Pinos 5 e 6 = 976 Hz;
Não entrarei em detalhes , mas é possível alterar essas frequências. Para tal deve se configurar os  timers do arduino, porém algumas funções e bibliotecas poderão parar de funcionar após essas alterações, caso esteja interessado recomendo os seguintes links: http://www.arduino.cc/playground/Main/TimerPWMCheatsheet  acessado em 28/07/2011.
  
A função analogWrite()
Escreve um valor analógico (Onda PWM) em um pino. Pode ser usada para ligar um LED e variar sua luminosidade ou controlar a velocidade de um motor. Após chamar a função analogWite(), o pino irá gerar uma onda quadrada estável de frequência fixa (488Hz ou 976hz) sendo o duty cycle escolhido pelo usuário.

Sintaxe

analogWrite(pino, valor)

Parâmetros

pino: Número do pino que gerará o sinal PWM
value: Valor do duty cycle: deve ser um valor entre 0 (sempre desligado) e 255 (sempre ligado).

Notas e problemas conhecidos

Sugerimos evitar usar os pinos 5 e 6 pois os mesmos terão um duty cycle maior do que o experado, isto é devido as interações com as funções milis() e delay() que dividem internamente o mesmo timer usado para gerar estes sinais PWM

 

Montagem do circuito (Motor)

Materiais a serem utilizados:

1x Arduino UNO
1x BD139
1x Resistência de 100 Ohms
1x Potênciometro de 10K Ohms
1x Cooler de computador
1x Fonte 12 Volts
circuito de controle de velocidade de motor
Figura 2 - Diagrama de montagem, não se esquecer de conectar os negativos juntos conforme mostra o esquema.


vista BD139




















 Figura 3 - Pinagem do transistor BD139 



cooler para usar com arduino

























Figura 4 - Cooler de computador utilizado.



potenciometo em protoboard


















Figura 5 - Potenciômetro com knob.


O arduino possuí inúmeros pinos de saída e com eles podemos acionar diversos componentes como LEDs, displays de LCD, buzzers, sensores de temperatura etc. Entretanto para acionarmos cargas que demandam de mais potência os pinos de saída do arduino por muitas vezes podem não fornecer a corrente necessária além de corrermos o risco de queimarmos o arduino ao exigirmos dele correntes que ele não pode fornecer.

Para acionarmos essas cargas podemos utilizar os seguintes componentes:

- Relés
- Transistors bipolares
- Transistors de efeito de campo
- Triacs
- IC`s como o ULN2003
- Optoacopladores

Neste post entrarei em maiores detalhes sobre a utilização de um transistor bipolar, especificamente o BD139.

Como selecionar um transistor?

A corrente de coletor (Ic) que o transistor oferece deve ser maior do que a corrente que sua carga necessita, o transistor atuando como chave, ou seja em estado de saturação permite a passagem de corrente entre o Coletor e o emissor.

A corrente de base (Ib) deve ser calculada com intuito de deixar o transistor em estado de saturação. No datasheet do BD139 temos a seguinte informação:

 
A corrente de coletor no meu caso é de 0,2A (corrente consumida pelo meu cooler ligado diretamente aos 12V).
Como o datasheet nos diz que com IB de 0,05A podemos Ter um Ic de 0,5A que está acima das minhas necessidades posso considerar então a resistência de base como:

Rb = (V arduino)/Ic  
Rb= (5 Volts)/0,05 = 100 Ohms

Outro fator importante ao se escolher um transistor para trabalhar com PWM a altas freqüências é evitar transistors com alto tempo de “base storage” ou seja que possuem um pouco de delay enquanto ele está chaveando ON / OFF, esse fenômeno é mais comuns em BJT`s de potência como o BD139 que utilizei. A freqüência utilizada neste projeto  fica em torno de 500 Hz e não resultará em nenhum problema.


Código Fonte (Motor)

#define TRANSISTOR 9
#define POT 1
int le_pot=0;

void setup()
{
pinMode(TRANSISTOR, OUTPUT);
}

void loop()
{
le_pot=(analogRead(POT))/4;
analogWrite(TRANSISTOR, le_pot); 
}

 
Resultado final (Motor)


 
Vídeo 1 - Controle de um cooler com um Arduino.



Montagem do circuito (LEDs)

Materiais a serem utilizados:

1x Arduino UNO

3x LED`s
3x Resistência de 220 Ohms
1x Botão NA
1x Resistência de 10K Ohms

MOntagem de leds pwm
Figura 6 - Diagrama de montagem.


Código Fonte (LEDs)

Este sketch consiste em fazer os 3 LED`s terem seu brilho aumentado a partir do acionamento do botão, ao atingir brilho máximo e se pressionar novamente o botão o processo é revertido e o brilho começa a decair.

Esse controle é feito dentro dos dois blocos de IF e o estado do botão é passado a duas variáveis distintas com intuito de controlar se é o primeiro acionamento ou o segundo.

 //Exemplo de PWM com 3 LEDs R/G/B 
 // Efeito Fade com apertar de botão

 #define LED_R 9
 #define LED_G 10
 #define LED_B 11
 #define BOTAO 7
 int i =0;
 int var=0;
 int antiga_var=0;

 void setup()
 {
   pinMode(LED_R, OUTPUT);
   pinMode(LED_G, OUTPUT);
   pinMode(LED_B, OUTPUT);
   pinMode(BOTAO, INPUT);
 }

void loop()
{
var = digitalRead(BOTAO); // Lê o estado do botão pressionado = 1 solta = 0
delay(10); // Efeito de Debounce

 if ((var==HIGH) && (antiga_var==LOW)){
// Efeito de aumento de brilho
for (i=0;i<255;i++) 
// Conta de 0 a 255 e executa uma vez por contagem o conteudo entre{} 
{
 analogWrite(LED_R, i); 
 analogWrite(LED_G, i);
 analogWrite(LED_B, i);
delay(10); // Espera 10 mili segundos
}
 antiga_var = var;
 var=LOW;
 }


 if ((var==HIGH) && (antiga_var==HIGH)){
// Efeito de aumento de brilho
for (i=255;i>0;i--) 
// Conta de 0 a 255 e executa uma vez por contagem o conteudo entre{} 
{
 analogWrite(LED_R, i); 
 analogWrite(LED_G, i);
 analogWrite(LED_B, i);
delay(25); // Espera 10 mili segundos
}
 antiga_var=LOW;
 var=LOW;
 }


}

Resultado final (LEDs)




Vídeo 2 - Controle de brilho com um Arduino.

5 de jul de 2011

Aprimorando um projeto - Termômetro com registro de temperatura mínima, máxima, atual e com arredondamento de valores lidos.

Antes de iniciar está leitura recomendo que leiam os artigos anteriores especialmente estes:
utilizando um display de LCD, leitura de temperatura exibindo resultado no computador e Leitura de temperatura exibindo resultado no LCD 

Faça a montagem da placa como mostrado no projeto de leitura de temperatura exibida no LCD,
este novo projeto somente implementa novas funções ao antigo e no final citará algumas dicas para tornar este projeto algo ainda melhor, podendo até servir de base para algum trabalho de conclusão de curso técnico.


Função para arredondar um valor lido.


Dúvidas em relação ao uso de funções? recomendo o seguinte site: 
http://www.inf.pucrs.br/~pinho/LaproI/Funcoes/AulaDeFuncoes.htm (acessado no dia 05/07/2011)


Se quisermos mostrar valores dentro de uma faixa pré determinada podemos tratar os valores lidos pelo sensor e estipular uma regra de controle.
Enviamos um valor a função (parâmetro) este valor será do tipo float portanto utilizaremos do seguinte estratagema para calcular somente o valor fracional.

Vamos supor que o sensor envia o seguinte valor para a função "30,25"
Decalaramos uma variável temporária do tipo int para armazenar o valor de 30,25
como a váriavel é do tipo int ela ignorará o valor fracional.


float arredondado=30,25;
int temp_aux=0;
arredondado=30,25;
temp_aux=arredondado; 


Ao lermos o valor da variável temp_aux leremos somente 30
Então para calcular somente o valor fracional faremos arredonda-temp_aux que seria (30,25)-(30)=0,25



 float arredondamento(float arredonda)
  {
  int temp_aux=0;
  float temp_fracional=0;
  float temp_final=0;
  temp_aux=arredonda;
  temp_fracional=(arredonda-temp_aux);
     
  if (temp_fracional>=0.125 && temp_fracional<0.375)
    {
    temp_fracional=0.25;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }
}



O bloco IF acima testa se um valor é maior ou igual a 0,125 e menor que 0,375, se as duas condições forem satisfeitas a temperatura retornada terá um valor de 0,25 adicionada ao seu valor inteiro.



Exibindo valores mínimos e máximos no display LCD



Para exibir toda essa gama de valores precisaremos de um display de 20x4, caso você possua um display de somente duas linhas, altere o código para exibir somente os valores que desejar.

A grande pergunta que deve ser feita neste projeto é como descobrir e manter atualizado os valores mínimos e máximos de temperatura durante um período,  para realizar esse controle utilizaremos uma lógica condicional muito simples.
Primeiro devemos declarar duas variáveis que irão armazenar os valores máximos e mínimos definiremos um valor inicial para as mesmas que garantirão uma condição inicial verdadeira.
 

float temp_maxima=-150;  // valor muito BAIXO para condição de temp MAX
float temp_minima=300;  // valor muito ALTO para condição de temp MIN



Repare que as variáveis declaradas são do tipo float pois estas receberão valores reais fracionários dentro do laço de controle se a condição for verdadeira


 Bloco responsável por imprimir a temperatura máxima
    if (temp_maxima<valor_arredondado)
    {
    temp_maxima=valor_arredondado;
    lcd.setCursor(0,2);
    lcd.print("Temp max: " );
    lcd.print(temp_maxima);
    lcd.write(0); // Imprime o caractere º
    lcd.print("C");
    }



 Bloco responsável por imprimir a temperatura mínima

   if (temp_minima>valor_arredondado)
    {
    temp_minima=valor_arredondado;
    lcd.setCursor(0,3);
    lcd.print("Temp min: " );
    lcd.print(temp_minima);
    lcd.write(0); // Imprime o caractere º
    lcd.print(C");
    }
 



O funcionamento deste bloco de código segue a mesma linha do anterior, temp_minima terá um valor inicial muito alto o que garantirá uma condição inicial verdadeira, todo o controle de impressão no LCD e de posicionamento está no bloco do if para somente ser executado quando uma condição verdadeira for detectada, garantindo maior velocidade ao programa.

 Repare que como declaramos temp_maxima com o valor de -150 a primeira condição sempre vai ser verdadeira, por ser verdadeiros o laço do IF será executado e a variável temp_máxima receberá o primeiro valor enviado pelo programa e nas próximas execuções irá atualizar o valor somente se temp_maxima<valor_arredondado.
 


Código fonte


#define sensor 0
#include <LiquidCrystal.h>
/* Biblioteca com funcoes para uso de um LCD baseado no Hitachi HD 44780 */
LiquidCrystal lcd(1, 0, 5, 4, 3, 2); // deve ser colocada fora do setup() para valer em todos os blocos do programa
/* Define os pinos de ligação do LCD ao arduino com esta ordem LiquidCrystal(rs, enable, d4, d5, d6, d7)  */

float temp_maxima=-150;  // valor muito BAIXO para condição de temp MAX
float temp_minima=300;  // valor muito ALTO para condição de temp MIN

void setup()
  {
  //lcd.begin(cols, rows)
  lcd.begin(20, 4); /* Tipo de LCD usado no meu caso de 20 colunas por 4 linhas */
  lcd.setCursor(0, 0); /* O Cursor iniciara na coluna zero linha 0 */
  lcd.print("    Central AVR!");
 
  float le_temp(float x_sensor);
  float arredondamento(float arredonda);
  }

void loop()
{
byte a[8] = {  B01110,  B01010,  B01010,  B001110,  B00000,    B00000,  B00000,  B00000}; // Caractete criado 
lcd.createChar(0,a); // define nosso caractere º como uma variável
float temperatura_lida, valor_arredondado; 

// Bloco responsável por imprimir a temperatura atual
  temperatura_lida=le_temp(sensor); //chama a função le_temp() e passa como parametro a variavel sensor
  valor_arredondado = arredondamento(temperatura_lida); //chama a função arredondamento e passa como parametro temperatura_lida
  lcd.setCursor(0,1);
  lcd.print("Temp atual: " );
  lcd.print(valor_arredondado);
  lcd.write(0); // Imprime o caractere º
  lcd.print("C");

// Bloco responsavel por imprimir a temperatura maxima
   if (temp_maxima<valor_arredondado)
    {
    temp_maxima=valor_arredondado;
    lcd.setCursor(0,2);
    lcd.print("Temp max: " );
    lcd.print(temp_maxima);
    lcd.write(0); // Imprime o caractere º
    lcd.print("C");
    }

// Bloco responsavel por imprimir a temperatura mínima
   if (temp_minima>valor_arredondado)
    {
    temp_minima=valor_arredondado;
    lcd.setCursor(0,3);
    lcd.print("Temp min: " );
    lcd.print(temp_minima);
    lcd.write(0); // Imprime o caractere º
    lcd.print("C");
    }
}

float le_temp(float x_sensor)
  {
   float temperatura=0;
   float temperatura1=0;
   float temperatura2=0;
   float temperatura3=0;
   float temperatura4=0;
   float valor_lido=0;
     
    valor_lido=analogRead(x_sensor); /* Lê tensao do LM35 */
    temperatura1=(valor_lido*5*100)/1024; /* Conversao do valor lido */
    delay(50); /* Espera 100 mili segundos antes de prosseguir para a próxima medição*/
    valor_lido=analogRead(x_sensor); /* Lê tensao do LM35 */
    temperatura2=(valor_lido*5*100)/1024; /* Conversao do valor lido */
    delay(50); /* Espera 100 mili segundos antes de prosseguir para a próxima medição*/
    valor_lido=analogRead(x_sensor); /* Lê tensao do LM35 */
    temperatura3=(valor_lido*5*100)/1024; /* Conversão do valor lido */
    delay(50); /* Espera 100 mili segundos antes de prosseguir para a próxima medição*/
    valor_lido=analogRead(x_sensor); /* Lê tensao do LM35 */
    temperatura4=(valor_lido*5*100)/1024; /* Conversão do valor lido */
    temperatura=(temperatura1+temperatura2+temperatura3+temperatura4)/4;
    return temperatura;
  }
   
    /* Código responsável pelo arredondamento */
  float arredondamento(float arredonda)
  {
  int temp_aux=0;
  float temp_fracional=0;
  float temp_final=0;
  temp_aux=arredonda;
  temp_fracional=(arredonda-temp_aux);

  if (temp_fracional>=0 && temp_fracional<0.125)
    {
    temp_fracional=0.00;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }
   
  if (temp_fracional>=0.125 && temp_fracional<0.375)
    {
    temp_fracional=0.25;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }

  if (temp_fracional>=0.375 && temp_fracional<0.625)
    {
    temp_fracional=0.50;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }

  if (temp_fracional>=0.625 && temp_fracional<0.875)
    {
    temp_fracional=0.75;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }

  if (temp_fracional>=0.875 && temp_fracional<1)
    {
    temp_fracional=1;
    temp_final=(temp_aux+temp_fracional);
    return temp_final;
    }
  } 




Resultado final



Figura 1 - Display LCD 20x4 informando temperatura lida em um período.
 
Dicas para melhorar um projeto com termômetro.


Uma função adicional de fácil aplicação seria instalar um buzzer no arduino e disparar um alarme toda vez que uma temperatura pré determinada for atingida. Outra aplicação interessante mas um pouco mais complexa seria fazer um controlador de temperatura, onde se usaria uma resistência para gerar calor, existem duas maneiras simples de controlar a temperatura, a primeira seria controle ON/OFF e a segunda controle por PWM, lembrando que o arduino possui pinos dedicados a essa função.

Interessado em entrar no mundo Arduino? Confira meus produtos no mercado livre:

http://produto.mercadolivre.com.br/MLB-231090632-arduino-duemilanove-microcontrolador-avr-_JM
http://produto.mercadolivre.com.br/MLB-231096670-sensor-de-fumaca-para-arduino-pic-avr-8051-_JM
http://produto.mercadolivre.com.br/MLB-231098016-matriz-de-led-8x8-para-arduino-pic-avr-8051-_JM

12 de jun de 2011

Projeto básico - Utilizando um teclado com o arduino e exibindo o resultado em um display de LCD


Teclados

Teclados são dispositivos de entrada normalmente utilizados para realizar a interface do homem com a máquina. Os teclados matriciais como o usado neste projeto são os mais conhecidos devido a sua arquitetura simples e fácil integração com microcontroladores.
Figura 1 - Teclado matricial estilo membrana.

Lendo os dados enviados por um teclado

Existem vários métodos de se ler dados enviados por um teclado e estes basicamente se diferenciam pela maneira com que são conectados a um microcontrolador, mas a lógica permanece a mesma.

No micro controlador as colunas são definidas como pinos de entrada e as linhas como pinos de saída.

 Figura 2 - Detalhe das conexões de um teclado matricial.


Primeiro definimos as linhas como pinos de saída em nível alto (HIGH) e as colunas como pinos de entrada com nível baixo (LOW).
Para detectar qual tecla foi pressionada cada linha recebe individualmente nível lógico alto e todas as colunas recebem nível baixo, o micro controlador espera alguma mudança de estado nas colunas, se uma tecla foi pressionada o nível alto será enviado para a entrada do micro processador, pois as colunas foram definidas como pinos de entrada.
Para garantir níveis altos e baixos nos pinos de entrada do microcontrolador podemos utilizar resistores de pull-up ou pull-down.

 Figura 3 - Esquema de funcionamento individual.

Analisando a imagem acima, sabemos que não teremos tensão na coluna C1 pois ela está  ligada a uma resistência de 10K e ao terra, mas se pressionarmos a chave que interliga L1 com C1 teremos 5 volts em cima do resistor, estes 5 volts serão interpretados como lógica alta pelo arduino toda vez que a chave for pressionada.

Interface

Todas as linhas deverão ser conectadas diretamente ao arduino e as colunas devem ser ligadas a uma resistência aterrada como mostra a figura abaixo:


Figura 4 - Esquema de ligação simplificado

Infelizmente em um teclado comercial como o de membrana não é possível ver as conexões internas, portanto deveremos medir continuidade em todos os contatos de saída.
Fixa-se uma ponta do multímetro em um dos terminais e se pressiona uma tecla (mantenha a tecla pressionada) e coloque a outra ponta do multímetro em outro terminal. Veja se entre esses dois pinos há condutividade, se não houver coloque a segunda ponta do multímetro em outros terminais até confirmar condutividade. Fazendo isto você terá descoberto  a união entre uma linha e uma coluna, continue com este procedimento até descobrir todas as colunas e todas as linhas.

O teclado de 4 linhas por 3 colunas utilizado neste projeto terá um total  de 7 saídas, cada saída será ligada ao arduino através de um fio ou conexão, veja abaixo os resultados que encontrei depois da medição com o multímetro.
  • COLUNA 1 = Fio 4
  • COLUNA 2 = Fio 3
  • COLUNA 3 = Fio 2
  • LINHA 1 = Fio 1
  • LINHA 2 = Fio 5
  • LINHA 3 = Fio 6
  • LINHA 4 = Fio 7

Após descobrir a função de cada fio, vamos definir em quais pinos eles entraram no arduino:


#define col1 13 // Com resistor de pull down                                                                
#define col2 12 // Com resistor de pull down 
#define col3 6 // Com resistor de pull down 
#define l1 10
#define l2 9
#define l3 8
#define l4 7


Figura 5 - Esquema de ligação das colunas com os resistores. As linhas devem ser ligadas diretas no arduino.



O código


#include <LiquidCrystal.h>  // Biblioteca com funcoes para uso de um LCD baseado no Hitachi HD 44780 
LiquidCrystal lcd(1, 0, 5, 4, 3, 2);// Define os pinos de ligacao do LCD ao arduino com esta ordem LiquidCrystal(rs, enable, d4, d5, d6, d7)
#define col1 13 // Com resistor de pull down                                                                
#define col2 12 // Com resistor de pull down 
#define col3 6 // Com resistor de pull down 
#define l1 10 // Linha 1
#define l2 9
#define l3 8
#define l4 7 // Linha 4

void setup()
{
/*lcd.begin(cols, rows) */
lcd.begin(20, 4); /* Tipo de LCD usado no meu caso de 20 colunas por 4 linhas */
lcd.print("    Central AVR!");
   
// Declarando as colunas como INPUT, fios 4,3,2 do teclado
pinMode(col1,INPUT); /* Coluna 1, fio 4 */
pinMode(col2,INPUT); /* Coluna 2, fio 3 */
pinMode(col3,INPUT); /* Coluna 3, fio 2 */

// Declarando as linhas como OUTPUT fios 1,5,6,7 do teclado
pinMode(l1,OUTPUT); /* Linha 1, fio 1 */
pinMode(l2,OUTPUT);
pinMode(l3,OUTPUT); 
pinMode(l4,OUTPUT); /* Linha 4, fio 7 */  
}

void loop() 
{
int l[]={l1, l2, l3, l4}; // Array de 4 posições contendo os 4 pinos de linhas
int i=0;  
int k=0;
int tecla_apertada=0;
for (i=0; i<4; i++)
{
digitalWrite(l1,LOW); 
digitalWrite(l2,LOW);
digitalWrite(l3,LOW);
digitalWrite(l4,LOW);
digitalWrite(l[i],HIGH); // Torna uma linha alta por vez
   k=i*3; // Responsavel pela mudança de valores nas linhas após cada ciclo do for
  if(digitalRead(col1)==HIGH) // Se alguma tecla da coluna 1 for apertada executa o código abaixo
  {
    delay(10);
    tecla_apertada=k+1;
    lcd.setCursor(0, 1); /* O Cursor iniciara na coluna zero linha 3 */
    lcd.print("Coluna 1: ");
    lcd.print(tecla_apertada);
    k=0; 
  }

  if(digitalRead(col2)==HIGH)
  {
    delay(10);
    tecla_apertada=k+2;
    lcd.setCursor(0, 2); /* O Cursor iniciara na coluna zero linha 3 */
    lcd.print("Coluna 2: ");
    lcd.print(tecla_apertada); 
    k=0;
  }
 if(digitalRead(col3)==HIGH)
  {
    delay(10);
    tecla_apertada=k+3;
    lcd.setCursor(0, 3); // O Cursor iniciara na coluna zero linha 3 
    lcd.print("Coluna 3: ");
    lcd.print(tecla_apertada);
    k=0; 
  }
}

}


Algumas explicações sobre o funcionamento do programa.

A parte mais "complexa" deste código reside no laço do for onde utilizei um array para organizar a mudança para lógica HIGH das linhas, lembrando que o mesmo deve ser feito individualmente.


for (i=0; i<4; i++)
{
digitalWrite(l1,LOW); 
digitalWrite(l2,LOW);
digitalWrite(l3,LOW);
digitalWrite(l4,LOW);
digitalWrite(l[i],HIGH); // Torna uma linha alta por vez
   k=i*3; // Responsavel pela mudança de valores nas linhas após cada ciclo do for
.
.
.
.
.
}


Supondo i=0 a condição do for será verdadeira e o código dentro do laço será executado, todas as linhas são setada em nível LOW e apenas a linha 1 será setada em nível HIGH pois i=0 e no array l[] posição zero temos l1.
 digitalWrite(l[0],HIGH); // Torna uma linha alta por vez

O código executará até o final das condições(IF) e se não não for pressionada nenhuma tecla novamente todas as linhas receberão nível lógico LOW e somente a linha 2 será setada em nivel HIGH pois i=1 e no array l[] posição um temos l2.
digitalWrite(l[1],HIGH); // Torna uma linha alta por vez

k=i*3 é uma jogada para retornar um valor condizente com a linha com lógica HIGH, novamente supondo  i=0 ou seja a primeira execução do for temos k=0*3 portanto k=0 se a primeira coluna foi acionada o valor a ser impresso será 1 pois tecla_apertada=k+1;
Supondo i=1 ou seja a segunda execução do for temos k=1*3 portanto k=3 se a terceira coluna foi acionada o valor a ser impresso será 6 pois  tecla_apertada=k+3;
 

 
Resultado final