Forum Menu




 


Log in Problems?
New User? Sign Up!
AVR Freaks Forum Index

Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Author Message
jrkennedy44
PostPosted: Feb 24, 2011 - 06:49 PM
Newbie


Joined: Sep 07, 2009
Posts: 3


I'm using a library written by Mario Richter for the dogm lcds. It works great on my dogm 162, 3.3V, 4-bit interface, but, after running for several hours, with writes every few seconds, it "randomly" toggles PD7. The LCD is connected to PA0-PA3, and the control pins are PD4-PD6. I'm struggling to understand why PD7 is toggled (or set low, I'm not sure which - the pin is low when I discover the problem). I notice the problem because PD7 is connected to the output-enable pin of a tri-state buffer, and when it's set low, it stops all communication on the bus.

If I remove the LCD routines from the program, everything works fine for days (indefinitely?). With the LCD routines, it always sets PD7 low after a few hours. The program is still running, but is ineffective.

Thanks for any help! This site is such a great resource.

Regards,
Jeff

Header File:
Code:


// ************************************************************
//                          L C D - R O U T I N E S
//         for Electronic Assembly's DOGM LCD series
//                      -- Header-File --
// ************************************************************
//
//    Author:         Mario Richter
//    File:         dogm.h
//    Project:      Routines for DOGM LCD series
//    Date:           March 2009
//    Target MCU:   not specified
//
// DESCRIPTION
//
//    Routines for the DOGMxxx LCD series by Electonic Assembly
//  with ST7036 controller.
//
// ************************************************************

#ifndef __DOGM__
#define __DOGM__

#include <util/delay.h>

// Port Definitions -- change to suit your application
// ------------------------------------------------------------

#define DOGM_D4_PORT      PORTA
#define DOGM_D4_PIN       (1<<PA3)

#define DOGM_D5_PORT      PORTA
#define DOGM_D5_PIN       (1<<PA2)

#define DOGM_D6_PORT      PORTA
#define DOGM_D6_PIN       (1<<PA1)

#define DOGM_D7_PORT      PORTA
#define DOGM_D7_PIN       (1<<PA0)

#define DOGM_E_PORT       PORTD
#define DOGM_E_PIN        (1<<PD4)

#define DOGM_RW_PORT      PORTD
#define DOGM_RW_PIN       (1<<PD5)

#define DOGM_RS_PORT      PORTD
#define DOGM_RS_PIN       (1<<PD6)


// DDR(x) and PIN(x) macros - taken from Peter Fleury's LCD routines
// ------------------------------------------------------------

#define DDR(x) (*(&x - 1))      /* address of data direction register of port x */
#if defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__)
    /* on ATmega64/128 PINF is on port 0x00 and not 0x60 */
    #define PIN(x) ( &PORTF==&(x) ? _SFR_IO8(0x00) : (*(&x - 2)) )
#else
   #define PIN(x) (*(&x - 2))    /* address of input register of port x          */
#endif


// Function Declarations
// ------------------------------------------------------------

void dogm_init(void);                         // Initialize LCD
void dogm_putc(char);                         // Write character to display
void dogm_puts(char *);                       // Write string to display
void dogm_gotoxy(uint8_t, uint8_t);           // Go to position
void dogm_clear(void);                        // Clear display, go to first char in first line

// Internal routines
void dogm_port_init(void);                    // Initialize ports for LCD module
void dogm_write_check(uint8_t, uint8_t);      // Do busy check and write 8 bit to LCD
void dogm_write_nocheck(uint8_t, uint8_t);    // Write 8 bit to LCD without busy check
void dogm_write_once(uint8_t);                // Write 4 bit to LCD without busy check
void dogm_chk_busy(void);                     // Busy Check

#endif


C file:
Code:


// ************************************************************
//                          L C D - R O U T I N E S
//         for Electronic Assembly's DOGM LCD series
// ************************************************************
//
//  Version:    0.10 (2009-03-17)
//
//    Author:         Mario Richter
//    File:         dogm.c
//    Project:      Routines for DOGM LCD series
//    Date:           March 2009
//    Target MCU:   not specified
//
// DESCRIPTION
//
//    Routines for the DOGMxxx LCD series by Electonic Assembly
//  with ST7036 controller. Port connections are specified in
//  dogm.h header file.
//
// ************************************************************

#include <avr/io.h>                        // Aliases for IO ports etc.
#include <stdint.h>                        // Standard data types

#include "dogm.h"


// ************************************************************
// Initialize LCD module
// ************************************************************

void dogm_init(void)
{
  dogm_port_init();                     // initialize ports

  _delay_ms(400);                       // 40ms delay
  dogm_write_once(0x30);                // Function Set #1: DL=1
  _delay_ms(2);                         // 2ms delay
  dogm_write_once(0x30);                // Function Set #2: DL=1

  _delay_ms(30);                        // 30us delay
  dogm_write_once(0x30);                // Function Set #3: DL=1
  _delay_ms(100);                        // 30us delay

  dogm_chk_busy();                      // Check busy flag
  dogm_write_once(0x20);                // Function Set #4: DL=0

  _delay_ms(30);                        // 30us delay
  dogm_write_check(0, 0x29);            // Function Set #5: DL=0, N=1, IS2=0, IS1=1
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x15);            // Bias Set: BS=0, FX=0
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x72);            // Contrast Set: C3=1, C2=C1=C0=0
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x55);            // Power/ICON/Contrast: Icon=1, Bon=1, C5=1, C4=0
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x6E);            // Follower Ctrl: Fon=1, Rab2=0, Rab1=1, Rab0=0
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x0C);            // DISPLAY ON: D=1, C=0, B=0
  _delay_ms(30);                        // 30us delay

  dogm_write_check(0, 0x01);            // CLEAR DISPLAY
  _delay_ms(2);                         // 2ms delay

  dogm_write_check(0, 0x06);            // Entry mode set: I/D=1, S=0
  _delay_ms(30);                        // 30us delay
}


// ************************************************************
// Write single character to display
// ************************************************************

void dogm_putc(char c)
{
  dogm_write_check(1, c);
}


// ************************************************************
// Write string to display
// ************************************************************

void dogm_puts(char * str)
{
  while(*str != '\0')
    dogm_putc(*str++);
}


// ************************************************************
// Go to position xy
// ************************************************************

void dogm_gotoxy(uint8_t x, uint8_t y)
{
  uint8_t addr;

  addr = (y * 0x40) + x;
  dogm_write_check(0, 0x80 | addr);
}


// ************************************************************
// Clear display
// ************************************************************

void dogm_clear()
{
  dogm_write_check(0, 0x01);
}


// ************************************************************
// Initialize ports for LCD module
// ************************************************************

void dogm_port_init(void)
{
  DDRD = 0;
  DDR(DOGM_E_PORT)  |= DOGM_E_PIN;
  DDR(DOGM_RS_PORT) |= DOGM_RS_PIN;
  DDR(DOGM_RW_PORT) |= DOGM_RW_PIN;

  DDR(DOGM_D4_PORT) |= DOGM_D4_PIN;
  DDR(DOGM_D5_PORT) |= DOGM_D5_PIN;
  DDR(DOGM_D6_PORT) |= DOGM_D6_PIN;
  DDR(DOGM_D7_PORT) |= DOGM_D7_PIN;
}


// ************************************************************
// Busy Check
// ************************************************************

void dogm_chk_busy(void)
{
  uint8_t busy = 1;

  // Configure LCD data ports as inputs
  DDR(DOGM_D4_PORT) &= ~DOGM_D4_PIN;
  DDR(DOGM_D5_PORT) &= ~DOGM_D5_PIN;
  DDR(DOGM_D6_PORT) &= ~DOGM_D6_PIN;
  DDR(DOGM_D7_PORT) &= ~DOGM_D7_PIN;

  // Set RW high to read busy flag
  DOGM_RW_PORT  |= DOGM_RW_PIN;

  // Set RS low to read from control registers
  DOGM_RS_PORT  &= ~DOGM_RS_PIN;

  // Loop while LCD is busy
  while(busy)
  {
    // Get upper 4 bits, D7 is busy flag
    DOGM_E_PORT |= DOGM_E_PIN;
    _delay_us(2);
    busy = PINA & DOGM_D7_PIN;
    DOGM_E_PORT &= ~DOGM_E_PIN;

    // Get lower 4 bits
    DOGM_E_PORT |= DOGM_E_PIN;
    _delay_us(2);
    DOGM_E_PORT &= ~DOGM_E_PIN;
  }

  // Set RW low to write to LCD again
  DOGM_RW_PORT  &= ~DOGM_RW_PIN;

  // Configure LCD data ports as outputs
  DDR(DOGM_D4_PORT) |= DOGM_D4_PIN;
  DDR(DOGM_D5_PORT) |= DOGM_D5_PIN;
  DDR(DOGM_D6_PORT) |= DOGM_D6_PIN;
  DDR(DOGM_D7_PORT) |= DOGM_D7_PIN;
}


// ************************************************************
// Do busy check and write 8 bit to LCD
// ************************************************************

void dogm_write_check(uint8_t rs, uint8_t data)
{
  dogm_chk_busy();               // check busy flag
  dogm_write_nocheck(rs, data);   // write 8 bit data to LCD
}


// ************************************************************
// Write 8 bit to LCD without busy check
// ************************************************************

void dogm_write_nocheck(uint8_t rs, uint8_t data)
{
  // if target is control register (rs=0), clear RS output, else set it
  if(rs)
    DOGM_RS_PORT  |= DOGM_RS_PIN;
  else
    DOGM_RS_PORT  &= ~DOGM_RS_PIN;

  dogm_write_once(data);          // write upper 4 bit to LCD
  dogm_write_once(data << 4);     // write lower 4 bit to LCD
}


// ************************************************************
// Write 4 bit to LCD without busy check
// Upper 4 bits of data are used as D7..D4
// ************************************************************

void dogm_write_once(uint8_t data)
{
  // Clear RW
  DOGM_RW_PORT  &= ~DOGM_RW_PIN;

  // D7
  if(data & 0x80)
    DOGM_D7_PORT  |= DOGM_D7_PIN;
  else
    DOGM_D7_PORT  &= ~DOGM_D7_PIN;

  // D6
  if(data & 0x40)
    DOGM_D6_PORT  |= DOGM_D6_PIN;
  else
    DOGM_D6_PORT  &= ~DOGM_D6_PIN;

  // D5
  if(data & 0x20)
    DOGM_D5_PORT  |= DOGM_D5_PIN;
  else
    DOGM_D5_PORT  &= ~DOGM_D5_PIN;

  // D4
  if(data & 0x10)
    DOGM_D4_PORT  |= DOGM_D4_PIN;
  else
    DOGM_D4_PORT  &= ~DOGM_D4_PIN;

  // Generate enable impulse
  DOGM_E_PORT   |= DOGM_E_PIN;
  _delay_us(2);
  DOGM_E_PORT   &= ~DOGM_E_PIN;
}
 
 View user's profile Send private message  
Reply with quote Back to top
lfmorrison
PostPosted: Feb 24, 2011 - 07:23 PM
Raving lunatic


Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada

Looks like a race condition to me.

Is either this code, or the code which drives the tristate buffer, ever executed within the context of an interrupt service routine? Or within two different threads of a preemptive RTOS?

What model of AVR are you using? Is it one that permits SBI and CBI access to the I/O port in question? Or is it in an I/O register which must be modified using an explicit sequence of IN/MODIFY/OUT instructions?

What optimization level are you using? (Is the compiler actually using SBI and CBI instructions to toggle individual bits, or is it using explicit IN/MODIFY/OUT sequences even though the SBI and CBI instructions could be used instead?)

- Luke
 
 View user's profile Send private message  
Reply with quote Back to top
jrkennedy44
PostPosted: Feb 24, 2011 - 07:33 PM
Newbie


Joined: Sep 07, 2009
Posts: 3


Hi Luke, thanks for the response. This code is only called from main.c, but PD7 (tristate enable) is toggled from from an ISR. Would you suggest a cli/sei pair within the LCD code? i.e.,
Code:

void dogm_puts(char * str)
{
  cli();
  while(*str != '\0')
    dogm_putc(*str++);
  sei();
}


The ISR timing is such that it could be delayed a few microseconds while the LCD is written.

I'm using an ATMEGA644P, which should support SBI/CBI. PD7 is set/cleared with "|=" and "&= ~" commands, i.e., "PORTD |= (1<<TxEn_pin)". I'm using -01 optimization, it looks like this generates read/modify/write instructions (LDI, LDI, LDD, ANDI, STD). It seems like you're saying a different optimization will generate SBI/CBI instead? I guess I could use them explicitly in the c code, as well.

Thanks!


Last edited by jrkennedy44 on Feb 24, 2011 - 08:27 PM; edited 2 times in total
 
 View user's profile Send private message  
Reply with quote Back to top
lfmorrison
PostPosted: Feb 24, 2011 - 07:53 PM
Raving lunatic


Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada

That may be a solution.

If you'd prefer, you could fine-tune things a little more, to minimize the time you spend with interrupts unnecessarily inhibited.

You would only need to ensure atomicity of the specific lines of code in which the value of PORTD is modified. (Or, as named in your code, DOGM_E_PORT, DOGM_RW_PORT, and DOGM_RS_PORT.)

It is recommended to use the ATOMIC_BLOCK construct provided in <util/atomic.h> to improve the chances that the cli() and sei() operations aren't re-ordered behind your back to the point that they no longer enclose the code you want protected.

(There's ongoing debate about the possibility that the ATOMIC_BLOCK construct might sometimes keep interrupts inhibited longer than you intended in certain circumstances, but there's also general agreement that it won't accidentally reorder the inhibit so that it starts too late, or reorder the uninhibit so that it ends too soon.)

For example:
Code:
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
  // Set RW high to read busy flag
  DOGM_RW_PORT  |= DOGM_RW_PIN;

  // Set RS low to read from control registers
  DOGM_RS_PORT  &= ~DOGM_RS_PIN;
}
 
 View user's profile Send private message  
Reply with quote Back to top
mckeemj
PostPosted: Feb 24, 2011 - 08:23 PM
Resident


Joined: Jun 26, 2006
Posts: 627
Location: San Luis Valley, Colorado ( 2,318m )

Regardless of any other changes you might make I would switch to using -Os and check for sbi/cli again. I have never seen the compiler use RMW when it could be atomic at higher optimization levels ( disregarding for the moment one instance where the port and bit were known, but were required to be opaque due to reference through a function pointer as per the C standard ). Even if code size reduction is unnecessary ( though there should be some between -O1 and -Os ) making the accesses atomic could certainly be worth it alone.

Martin Jay McKee

_________________
As with most things in engineering, the answer is an unabashed, "It depends."
 
 View user's profile Send private message  
Reply with quote Back to top
jrkennedy44
PostPosted: Feb 25, 2011 - 03:16 PM
Newbie


Joined: Sep 07, 2009
Posts: 3


Luke, Martin,
Thanks! I made the changes to include atomic blocks and it has been running since yesterday - it appears to be fixed. Also, I change the optimization to -Os and the R/M/W instructions were replaced by sbi/cli's. Glad to have learned something new! (but still oh-so-far to go...)

cheers,
Jeff
 
 View user's profile Send private message  
Reply with quote Back to top
lfmorrison
PostPosted: Feb 25, 2011 - 07:30 PM
Raving lunatic


Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada

jrkennedy44 wrote:
Luke, Martin,
Thanks! I made the changes to include atomic blocks and it has been running since yesterday - it appears to be fixed. Also, I change the optimization to -Os and the R/M/W instructions were replaced by sbi/cli's. Glad to have learned something new! (but still oh-so-far to go...)

cheers,
Jeff

Keep in mind, the atomic blocks may no longer be necessary, now that the low-level bit modifications are using inherently atomic instructions.

- Luke
 
 View user's profile Send private message  
Reply with quote Back to top
Display posts from previous:     
Jump to:  
All times are GMT + 1 Hour
Post new topic   Reply to topic
View previous topic Printable version Log in to check your private messages View next topic
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits