LCD, 4 bit data mode

Fabien Le Mentec May 21, 2013 Coded in C for the ATMEGA328P
/* note: lcd model MC21605A6W */
/* note: DB0:3 and RW must be grounded */
/* note: see https://github.com/texane/lcmeter for usage */

#include <stdint.h>
#include <avr/io.h>

#define LCD_POS_DB 0x02
#define LCD_PORT_DB PORTD
#define LCD_DIR_DB DDRD
#define LCD_MASK_DB (0x0f << LCD_POS_DB)

#define LCD_POS_EN 0x06
#define LCD_PORT_EN PORTD
#define LCD_DIR_EN DDRD
#define LCD_MASK_EN (0x01 << LCD_POS_EN)

#define LCD_POS_RS 0x07
#define LCD_PORT_RS PORTD
#define LCD_DIR_RS DDRD
#define LCD_MASK_RS (0x01 << LCD_POS_RS)

static inline void wait_50_ns(void)
{
  __asm__ __volatile__ ("nop\n\t");
}

static inline void wait_500_ns(void)
{
  /* 8 cycles at 16mhz */
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
  __asm__ __volatile__ ("nop\n\t");
}

static inline void wait_50_us(void)
{
  /* 800 cycles at 16mhz */
  uint8_t x;
  for (x = 0; x < 100; ++x) wait_500_ns();
}

static inline void wait_2_ms(void)
{
  wait_50_us();
  wait_50_us();
  wait_50_us();
  wait_50_us();
}

static inline void wait_50_ms(void)
{
  /* FIXME: was _delay_ms(50), but not working */
  uint8_t x;
  for (x = 0; x < 25; ++x) wait_2_ms();
}

static inline void lcd_pulse_en(void)
{
  /* assume EN low */
  LCD_PORT_EN |= LCD_MASK_EN;
  wait_50_us();
  LCD_PORT_EN &= ~LCD_MASK_EN;
  wait_2_ms();
}

static void lcd_write_db4(uint8_t x)
{
  /* configured in 4 bits mode */

  LCD_PORT_DB &= ~LCD_MASK_DB;
  LCD_PORT_DB |= (x >> 4) << LCD_POS_DB;
  lcd_pulse_en();

  LCD_PORT_DB &= ~LCD_MASK_DB;
  LCD_PORT_DB |= (x & 0xf) << LCD_POS_DB;
  lcd_pulse_en();
}

static void lcd_write_db8(uint8_t x)
{
  /* configured in 8 bits mode */

  /* only hi nibble transmitted, (0:3) grounded */
  LCD_PORT_DB &= ~LCD_MASK_DB;
  LCD_PORT_DB |= (x >> 4) << LCD_POS_DB;
  lcd_pulse_en();
}

/* exported interface */

void lcd_setup(void)
{
  LCD_DIR_DB |= LCD_MASK_DB;
  LCD_DIR_RS |= LCD_MASK_RS;
  LCD_DIR_EN |= LCD_MASK_EN;

  LCD_PORT_DB &= ~LCD_MASK_DB;
  LCD_PORT_RS &= ~LCD_MASK_RS;
  LCD_PORT_EN &= ~LCD_MASK_EN;

  /* small delay for the lcd to boot */
  wait_50_ms();

  /* datasheet init sequence */

#define LCD_MODE_BLINK (1 << 0)
#define LCD_MODE_CURSOR (1 << 1)
#define LCD_MODE_DISPLAY (1 << 2)

  lcd_write_db8(0x30);
  wait_2_ms();
  wait_2_ms();
  wait_500_ns();

  lcd_write_db8(0x30);
  wait_2_ms();

  lcd_write_db4(0x32);
  wait_2_ms();

  lcd_write_db4(0x28);
  wait_2_ms();

  lcd_write_db4((1 << 3) | LCD_MODE_DISPLAY);
  wait_2_ms();

  lcd_write_db4(0x01);
  wait_2_ms();

  lcd_write_db4(0x0f);
  wait_2_ms();
}

void lcd_clear(void)
{
  /* clear lcd */
  lcd_write_db4(0x01);
  wait_2_ms();
}

void lcd_home(void)
{
  /* set cursor to home */
  lcd_write_db4(0x02);
  wait_2_ms();
}

void lcd_set_ddram(uint8_t addr)
{
  lcd_write_db4((1 << 7) | addr);
  wait_2_ms();
}

void lcd_goto_xy(uint8_t x, uint8_t y)
{
  /* assume 0 <= x < 8 */
  /* assume 0 <= y < 2 */

  /* from datasheet: */
  /* first line is 0x00 to 0x27 */
  /* second line is 0x40 to 0x67 */
  static const uint8_t row[] = { 0x00, 0x40 };
  lcd_set_ddram(row[y] | x);
}

void lcd_write(const uint8_t* s, unsigned int n)
{
  wait_50_ns();

  LCD_PORT_RS |= LCD_MASK_RS;
  for (; n; --n, ++s)
  {
    lcd_write_db4(*s);
    wait_2_ms();
  }
  LCD_PORT_RS &= ~LCD_MASK_RS;
}

bitbang (software) SPI implementation

Fabien Le Mentec March 28, 2013 Coded in C for the atmega328p
#include <stdint.h>
#include <avr/io.h>

/* default pins */
#define SOFTSPI_CLK_DDR DDRD
#define SOFTSPI_CLK_PORT PORTD
#define SOFTSPI_CLK_MASK (1 << 3)
#define SOFTSPI_MOSI_DDR DDRD
#define SOFTSPI_MOSI_PORT PORTD
#define SOFTSPI_MOSI_MASK (1 << 4)

#ifndef SOFTSPI_DONT_USE_MISO
#define SOFTSPI_DONT_USE_MISO 0
#endif

#if (SOFTSPI_DONT_USE_MISO == 0)
#define SOFTSPI_MISO_DDR DDRD
#define SOFTSPI_MISO_PIN PIND
#define SOFTSPI_MISO_MASK (1 << 5)
#endif

static void softspi_setup_master(void)
{
  SOFTSPI_CLK_DDR |= SOFTSPI_CLK_MASK;
  SOFTSPI_MOSI_DDR |= SOFTSPI_MOSI_MASK;

#if (SOFTSPI_DONT_USE_MISO == 0)
  SOFTSPI_MISO_DDR |= SOFTSPI_MISO_MASK;
#endif
}

static inline void softspi_clk_low(void)
{
  SOFTSPI_CLK_PORT &= ~SOFTSPI_CLK_MASK;
}

static inline void softspi_clk_high(void)
{
  SOFTSPI_CLK_PORT |= SOFTSPI_CLK_MASK;
}

static inline void softspi_mosi_low(void)
{
  SOFTSPI_MOSI_PORT &= ~SOFTSPI_MOSI_MASK;
}

static inline void softspi_mosi_high(void)
{
  SOFTSPI_MOSI_PORT |= SOFTSPI_MOSI_MASK;
}

static inline void softspi_write_bit(uint8_t x, uint8_t m)
{
  /* dac7554 samples at clock falling edge */

  /* 5 insns per bit */

  softspi_clk_high();
  if (x & m) softspi_mosi_high(); else softspi_mosi_low();
  softspi_clk_low();
}

static void softspi_write_uint8(uint8_t x)
{
  /* transmit msb first, sample at clock falling edge */

  softspi_write_bit(x, (1 << 7));
  softspi_write_bit(x, (1 << 6));
  softspi_write_bit(x, (1 << 5));
  softspi_write_bit(x, (1 << 4));
  softspi_write_bit(x, (1 << 3));
  softspi_write_bit(x, (1 << 2));
  softspi_write_bit(x, (1 << 1));
  softspi_write_bit(x, (1 << 0));
}

static inline void softspi_write_uint16(uint16_t x)
{
  softspi_write_uint8((uint8_t)(x >> 8));
  softspi_write_uint8((uint8_t)(x & 0xff));
}

#if (SOFTSPI_DONT_USE_MISO == 0)

static inline void softspi_read_bit(uint8_t* x, uint8_t i)
{
  /* read at falling edge */

  softspi_clk_high();
#if 0
/* no need, atmega328p clock below 50mhz */
/* softspi_wait_clk(); */
#endif
  softspi_clk_low();

  if (SOFTSPI_MISO_PIN & SOFTSPI_MISO_MASK) *x |= 1 << i;
}

static uint8_t softspi_read_uint8(void)
{
  /* receive msb first, sample at clock falling edge */

  /* must be initialized to 0 */
  uint8_t x = 0;

  softspi_read_bit(&x, 7);
  softspi_read_bit(&x, 6);
  softspi_read_bit(&x, 5);
  softspi_read_bit(&x, 4);
  softspi_read_bit(&x, 3);
  softspi_read_bit(&x, 2);
  softspi_read_bit(&x, 1);
  softspi_read_bit(&x, 0);

  return x;
}

static inline uint16_t softspi_read_uint16(void)
{
  /* msB ordering */
  const uint8_t x = softspi_read_uint8();
  return ((uint16_t)x << 8) | (uint16_t)softspi_read_uint8();
}

#endif /* SOFTSPI_DONT_USE_MISO == 0 */

high resolution frequency counter implementation

Fabien Le Mentec March 23, 2013 Coded in C for the atmega328p
/* high resolution frequency counter implementation
*/

/* timer2 interrupt handler. timer1 is an extended
32 bits register (16 bits hard + 16 softs)
incremented once per:
1 / (fcpu / prescal) <=> prescal / fcpu
thus, it will overflow at:
2^16 * prescal / fcpu
on tim2 overflow, the interrupt handler is called
and stores the tim1 current value in tim1_cur_counter.
thus, the tim1 value integrated over the whole
tim1 period is:
(tim1_ovf_counter * 2^16) + tim1_cur_counter.
tim2_is_ovf is set to notify the application.
*/

static volatile uint8_t tim2_ovf_counter;
static volatile uint8_t tim2_is_ovf;
static volatile uint16_t tim1_cur_counter;

ISR(TIMER2_OVF_vect)
{
  if ((tim2_ovf_counter--) == 0)
  {
    /* disable tim1 before reading */
    TCCR1B = 0;
    tim1_cur_counter = TCNT1;

    /* disable tim2 */
    TCCR2B = 0;

    tim2_is_ovf = 1;
  }
}

/* timer2 interrupt handler. timer2 is a 8 bits counter
incremented by the input signal rising edges. since
8 bits are not enough to integrate, an auxiliary
register (tim2_ovf_counter) is updated on overflow.
tim2_ovf_counter is an 8 bits register, and will
overflow without any notice past 0xff.
*/

static volatile uint8_t tim1_ovf_counter;

ISR(TIMER1_OVF_vect)
{
  ++tim1_ovf_counter;
}

static void hfc_start(void)
{
  /* resolution: 1.907349 hz per tick */
  /* fmax: 500 khz */
  /* acquisition time: 0.524288 seconds */

  /* disable interrupts */
  TIMSK1 = 0;
  TIMSK2 = 0;

  /* reset stuff */
  tim1_ovf_counter = 0;
  tim1_cur_counter = 0;
  tim2_is_ovf = 0;

  /* 0x100 overflows make 16 bits */
  tim2_ovf_counter = 0xff;

  /* configure tim2
normal operation
prescaler 128
enable interrupt on overflow
*/
  TCNT2 = 0;
  TIMSK2 = 1 << 0;
  TCCR2A = 0;
  TCCR2B = 0;

  /* configure tim1
t1 pin (pd5) rising edge as external clock
*/
  DDRD &= ~(1 << 5);
  TCNT1 = 0;
  TIMSK1 = 1 << 0;
  TCCR1A = 0;
  TCCR1B = 0;

  /* start tim1, tim2 */
  TCCR1B = 7 << 0;
  TCCR2B = 5 << 0;
}

static uint8_t hfc_poll(void)
{
  return tim2_is_ovf;
}

static uint32_t hfc_wait(void)
{
  /* busy wait for tim1 to overflow. returns the resulting
16 bits counter, to be multiplied by the frequency
resolution (refer to hfc_start) to get the actual
frequency.
*/

  /* force inline, do not use hfc_poll */
  while (tim2_is_ovf == 0) ;

  return ((uint32_t)tim1_ovf_counter << 16) | (uint32_t)tim1_cur_counter;
}

static inline uint32_t hfc_start_wait(void)
{
  hfc_start();
  return hfc_wait();
}

static inline double hfc_to_hz(uint32_t counter)
{
  return 1.907349 * (double)counter;
}