EmbeddedRelated.com
The 2024 Embedded Online Conference

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;
}

10-bit A/D Data Sampling and Transmission

Stephen Friederichs May 17, 2013 Coded in C for the ATMega328P
/**@file endianness.c
   @brief Code to transmit 16-bit ADC samples in big or little-endian order
   @author Stephen Friederichs
   @date 5/12/13
   
   ADC Channels:
   0 - Accelerometer X axis (Vertical)
   1 - Accelerometer Y axis (Horizontal)
   2 - Accelerometer Z axis (Lateral)
   3 - Accelerometer 0G detect (Freefall detect)
   
   The heartbeat LED is on Port D, pin 7
*/   

/**@def F_CPU
   @brief Clock frequency = 8MHZ - this is set by fuses and registers, not by this define
   @note Always define this before including delay.h!
*/
#define F_CPU 8000000
	
/**@include io.h
   @brief Include for AVR I/O register definitions
*/
#include <avr/io.h>

/**@include stdint.h
   @brief Include for standard integer definitions (ie, uint8_t, int32_t, etc)
*/
#include <stdint.h>

/**@include delay.h
   @brief Include for delay functions such as _delay_ms() and _delay_us()
*/
#include <util/delay.h>

/* Basic bit manipulation macros - everyone should use these.  Please, steal these! Don't not use them and
 don't rewrite them yourself!
*/
#define SET(x,y) x |= (1 << y)
#define CLEAR(x,y) x &= ~(1<< y)
#define READ(x,y) ((0x00 == ((x & (1<<y))>> y))?0x00:0x01)
#define TOGGLE(x,y) (x ^= (1 << y))

int main(void)
{
	
	//Variable to count the number of times the timer interrupt has fired
	uint16_t ticks = 0;
	uint16_t accel_data = 0;
	uint8_t transmit_enable = 0x00;
	uint8_t * uart_data_pointer = &accel_data;
	
	/*Initialization Code*/
	
	/*	ATMega328 Datasheet Table 14-1 Pg 78
		Configure PD7 for use as Heartbeat LED
		Set as Output Low (initially)
	*/
	SET(DDRD,7);	//Direction: output
	CLEAR(PORTD,7);	//State: Lo

	/*	TCCR1A - ATMega328 Datasheet Section 16.11.2 pg 134 - TCCR1A
		No input capture used - bits 7:6 are 0
		No waveform generation used - bits 4:3 are 0
		Clock source select is bits 2:0 but are not yet set - wait until the 
		main loop is ready to start
	*/	
	TCCR1A = 0x00;
		
	/*	TCCR1C - ATMega328 Datasheet Section 16.11.3 pg 135 
		This register is only used for output compare.  
		There's no output compare in this application so this can be all 0's
	*/
	TCCR1C = 0x00;
		

	/*	TCCR1B
		
		Note: I've disabled the CKDIV8 fuse so that the clock source is 8MHz

		As per ATMega328 Datasheet Section 16.9.1 page 123, setting the timer 
		to Normal mode causes the counter to count up until it reaches 0xFFFF
		at which point it will overrun and start back at 0.  To configure this
		timer/counter to produce a period of 1ms we need to start counting 
		at a value that causes it to reach 65535 in 1ms. 
		
		What is that value?

		With a clock prescaler of 32 each count of the timer is roughly
		(1/8MHz)*32 = 1uS
		1ms / 1us /tick = 1000 ticks /ms
		The counter counts up to 65536, so to determine what value we have to
		start at we subtract 1000 from 65536:
		65536-1000 = 64536
	*/

	#define TIMER1_PERIOD 64536
		
	TCNT1 = TIMER1_PERIOD;

	//Configure ADC to read accelerometer data
	
	//ATMega328 - Section 24.9.1 Pg 254 - ADMUX Register

	/*ADC result - left-adjusted (Bit 5).  The ADC result is 10-bits wide.  
          In practice, the least-significant 2 bits are often too noisy to be
          of any use, so they are discarded. To support this, the ATMega328P is 
	  capable of storing the upper eight bits of the ADC result in the 
          ADCH register alone.  In this case, I want all 10 bits of the data 
          so I can show how to handle endianness in serial transmissions.  As 
          a result, the most significant two bits are stored in ADCH and the least 
          significant 8 are stored in ADCL.
	*/
	
	/*ADC Channel - I only care about one - the Y axis on the accelerometer 
          which is channel 1.*/
	
	ADMUX =		(0x01 << 6)  /*Reference - AVCC - 5V. */ 
			|(0x00 << 5) /* Right-adjust ADC result - refer to 
                                        Section 24.9.3.2 pg 256*/
			|(0x01 << 0); /*Channel set to X-Axis output on 
                                        accelerometer*/
	
	/*	ATMega328 Datasheet - Section 24.9.2 - ADCSRA - ADC Status
                and Control Register
		ADCEN - Bit 7 - Enable ADC - Obviously set this to 1
		ADCSC - Bit 6 - Start Converstion - Not yet: 0
		ADATE - Bit 5 - Auto-trigger ADC - I'll be manually triggering 
                                the ADC, so 0
		ADCIF - Bit 4 - ADC Interrupt Flag - Set when conversion 
                                completes.  Ignore.
		ADCIE - Bit 3 - ADC Interrupt Enable - Everything will be polled 
                        for this, so 0
		ADPS - Bits 2:0 - ADC Prescaler
	*/
	
	/*ATMega328 Section 24.4 Pg245 discusses what the prescaler should be set to:
	
	By default, the successive approximation circuitry requires an input clock 
        frequency between 50kHz and 200kHz to get maximum resolution.
	
	The ClkIO is 8MHz and the prescaler options are 2,4,8,16,32,64 and 128. 
        1MHz/8 = ~125KHz, so that seems good. That value is 3
	*/
	
	ADCSRA = (0x01 << 7)	//Enable ADC
		|(0x03);	//Set prescaler to 1/8 ClkIO - 125KHz
			  
	/*	ATMega328 Datasheet Section 24.9.5 Pg 257 - DIDR0
		This register allows digital input buffers on ADC pins to be
                disabled.  This saves power, so I'll do it
	*/
	
	DIDR0 = 0x01;	//Turn off digital filtering on ADC channel 0
	
	//Configure UART for 38400 8N1 Tx Communication
	
	//Step 1 - Baud rate
	/*	ATMega328 Datasheet Section 20.10 - Table 20-6 pg 192
		Baud rate settings for fosc of 8MHZ
		Choosing baud rate of 38.4K for minimum error
		U2Xn = 0 - Use standard (not double) data rate
		UBRRn = 12
	*/
	
	UBRR0 = 12;
	
 	/*	UCSR0A - UART 0 Control and Status Register A
		ATMega328 Datasheet Section 20.11.2 pg 194
		Bits 7:2 - Status bits
		Bit 1 - Double UART transmission speed - No: 0
		Bit 0 - Multi-Processor Communication Mode - No:0
	*/
	UCSR0A = 0x00;
	
	/*	UCSR0B - UART 0 Control and Status Register B
		ATMega328 Datasheet Section 20.11.3 pg 
		Bit 7 - Rx Complete Interrupt Enable - 0
		Bit 6 - Tx Complete Interrupt Enable - 0
		Bit 5 - USART Data Register Empty interrupt enable - 0
		Bit 4 - Receiver Enable - Set to 1
		Bit 3 - Transmitter Enable - Set to 1
		Bit 2 - Character Size Bit 2 - Set to 0 for 8 bits
		Bit 1 - 9th receive bit - Ignore
		Bit 0 - 9th transmit bit - Ignore
	*/
	UCSR0B = 0x00	| (1 << 3)
					| (1 << 4);
	
	/*	UCSR0C - UART 0 Control and Status Register C
		ATMega328 Datasheet Section 20.11.4 - Pg 196
		Bits 7:6 - Set to asynchronous (clockless) mode: 00
		Bits 5:4 - Parity setting - None : 00
		Bit 3 - Stop select - 1 : 0
		Bit 2:1 - Character size - 8 : 11
		Bit 0 - Clock polarity: Don't care : 0
	*/
	UCSR0C = 0x03 << 1;
	
	//Send a known pattern upon startup to verify the UART works
	UDR0 = 0xA5;
	
	//Wait until transmit is complete
	while(0x00 == READ(UCSR0A,6));
	
	UDR0 = 0x5A;
	
	while(0x00 == READ(UCSR0A,6));
	
	UDR0 = 0xA5;
	
	//Wait until transmit is complete
	while(0x00 == READ(UCSR0A,6));
	
	
	/*	Flash the LED for a second to show that initialization has successfully 
		occurred
	*/
	SET(PORTD,7);
	_delay_ms(1000);
	CLEAR(PORTD,7);
	
	/*	Start the timer/counter
		ATMega328 Datasheet Section 16.11.2 Pg 135 - TCCR1B
		No Waveform generation: bits 4:3 = 0
		No input capture: bits 7:6 = 0
		Clock select: ClkIO/8 - bits 2:0 = 010b = 0x02
	*/	
	TCCR1B = 0x02;	//This starts the counter/timer
	
	while(1)
	{
		/*	Timer overflow - Reading the accelerometer at a 1KHz rate 
                        and flash the heartbeat LED at a reasonable period as well
		*/
		if(READ(TIFR1,0))
		{
			/*	ATMega328 Datasheet Section 16.11.9 pg137
				Setting TIFR1 bit 1 clears the overflow flag 
			*/
			SET(TIFR1,0);	
				
			/*	Reload the timer/counter count value to the 
                                previous value so that the period remains the same
			*/
			TCNT1 = TIMER1_PERIOD;
			
			//Read accelerometer data via ADC
			SET(ADCSRA,6);	//Start ADC conversion
			
			/*      Wait until conversion finishes - this should never
                                be more than 25*(8000000/8)^-1 seconds, which is 
                                about 25us. Typical measured time is ~14.5us
                        */
			while(0x00 == READ(ADCSRA,4));	
	
			SET(ADCSRA,4);	//Clear the interrupt flag by setting it to 1
			
			//Clear acceleration data variable before loading new value
			accel_data = 0;
			
			/*      When reading the full 10-bits from the ADC the 
                                lower register must be read first
			*/
                        accel_data |= (uint16_t)ADCL;
			
			//Then the upper 2 bits
			accel_data |= (uint16_t)(ADCH << 8);
			
			/*      Transmission of data is toggled by transmitting a 
                                '0' (0x30) byte over serial
                        */
			if(0x01 == (READ(UCSR0A,7)))
			{
				if(0x30 == UDR0)
				{
					transmit_enable = 
                                             (0x00 == transmit_enable?0xFF:0x00);
				}
			}
											
			if(0xFF == transmit_enable)
			{
#ifdef BIG_ENDIAN
				//Send high byte...
				UDR0 = uart_data_pointer[1];
				while(0x00 == READ(UCSR0A,6));
			
				//...then low byte
				UDR0 = uart_data_pointer[0];
				while(0x00 == READ(UCSR0A,6));
#else
				//Send low byte...
				UDR0 = uart_data_pointer[0];
				while(0x00 == READ(UCSR0A,6));

				//...then high byte
				UDR0 = uart_data_pointer[1];
				while(0x00 == READ(UCSR0A,6));
#endif
			}			
			//Blink Heartbeat LED
			/*	
			The timer period is 1ms. To keep everything simple the LED will toggle
			every 512 ticks - roughly every .5s.  
			*/
			
			ticks++;
			//If true, the current ticks is a multiple of 512
			//So blink the heartbeat LED
			if(0x8000 == (ticks <<  7))
			{
				TOGGLE(PORTD,7);
			}
		}
		
		//Main Loops
	
	}

}

LED Blinker Using a Timer/Counter

Stephen Friederichs April 2, 2013 Coded in C for the ATMega328P
/**@file timer_blinker.c
   @brief A more advanced LED blinker using a timer
   @author Stephen Friederichs
   @date 3/28/13
   @note This code assumes that the LED is active high (pin sources current)   
*/   

/**@def F_CPU
   @brief Clock frequency = 8MHZ - this is set by fuses and registers, not by this define
   @note Always define this before including delay.h!
*/
#define F_CPU 8000000
	
/**@include io.h
   @brief Include for AVR I/O register definitions
*/
#include <avr/io.h>

/**@include stdint.h
   @brief Include for standard integer definitions (ie, uint8_t, int32_t, etc)
*/
#include <stdint.h>

/**@include delay.h
   @brief Include for delay functions such as _delay_ms() and _delay_us()
*/
#include <util/delay.h>

/* Basic bit manipulation macros - everyone should use these.  Please, steal these! Don't not use them and
 don't rewrite them yourself!
*/
#define SET(x,y) x |= (1 << y)
#define CLEAR(x,y) x &= ~(1<< y)
#define READ(x,y) ((0x00 == ((x & (1<<y))>> y))?0x00:0x01)
#define TOGGLE(x,y) (x ^= (1<<y))

int main(void)
{
	/*Initialization Code*/
	
	/*	ATMega328 Datasheet Table 14-1 Pg 78
		Configure PD7 for use as Heartbeat LED
		Set as Output Low (initially)
	*/
	SET(DDRD,7);	//Direction: output
	CLEAR(PORTD,7);	//State: Lo

	/*	TCCR1A - ATMega328 Datasheet Section 16.11.1 pg 132
		No waveform generation is required on this timer, so set all
		ports to normal operation
	*/	
	TCCR1A = 0x00;
		
	/*	TCCR1C - ATMega328 Datasheet Section 16.11.3 pg 135 
		This register is only used for output compare.  
		There's no output compare in this application so this can be all 0's
	*/
	TCCR1C = 0x00;
		

	/*	TCCR1B
		
		Note: I've disabled the CKDIV8 fuse so that the clock source is 8MHz
	
		ATMega328 Datasheet Section 16.11.2 pg 134 - TCCR1A
		No input capture used - bits 7:6 are 0
		No waveform generation used - bits 4:3 are 0
		Clock source select is bits 2:0 but are not yet set - wait until the 
		main loop is ready to start
		
		As per ATMega328 Datasheet Section 16.9.1 page 123, setting the timer 
		to Normal mode causes the counter to count up until it reaches 0xFFFF
		at which point it will overrun and start back at 0.  To configure this
		timer/counter to produce a period of 500ms we need to start counting 
		at a value that causes it to reach 65535 in 500ms. 
		
		What is that value?

		With a clock prescaler of 256 each count of the timer is roughly
		(1/8MHz)*256 = 32uS
		500ms / 32us /tick = 15625 ticks /500ms
		The counter counts up to 65535, so to determine what value we have to
		start at we subtract 15635 from 65536:
		65536-15625 = 49910
	*/

	#define TIMER1_PERIOD 49910
		
	TCNT1 = TIMER1_PERIOD;

	/*	Flash the LED for a second to show that initialization has successfully 
		occurred
	*/
	SET(PORTD,7);
	_delay_ms(1000);
	CLEAR(PORTD,7);
	
	/*	Start the timer/counter
		ATMega328 Datasheet Section 16.11.2 Pg 135 - TCCR1B
		No Waveform generation: bits 4:3 = 0
		No input capture: bits 7:6 = 0
		Clock select: ClkIO/256 - bits 2:0 = 100b = 0x04
	*/	
	TCCR1B = 0x04;	//This starts the counter/timer
	
	while(1)
	{
		/*	Handle the Heartbeat LED
			When the timer/counter reaches 65535 the 500ms period will have 
			elapsed and TIFR1 bit 1 will be '1'
		*/
		if(READ(TIFR1,0))
		{
			/*	ATMega328 Datasheet Section 16.11.9 pg137
				Setting TIFR1 bit 1 clears the overflow flag 
			*/
			SET(TIFR1,0);	
				
			/*	Toggle the LED to flash it at 1Hz*/ 
			TOGGLE(PORTD,7);
			
			/*	Reload the timer/counter count value to the previous value
				so that the period remains the same
			*/
			TCNT1 = TIMER1_PERIOD;
		}
		
		/*	Now you can do useful work here - no delay loops!*/
	
	}

}

Delay Loop LED Blinker

Stephen Friederichs April 2, 2013 Coded in C for the ATMega328P
/**@file led_blink.c
   @brief The most basic approach to blinking an LED on AVR microcontrollers - specifically the ATMega328P
   @author Stephen Friederichs
   @date 3/28/13
   @note This code assumes that the LED is active high (pin sources current)   
*/   

/**@def F_CPU
   @brief Clock frequency = 8MHZ - this is set by fuses and registers, not by this define
   @note Always define this before including delay.h!
*/
#define F_CPU 8000000
	
/**@include io.h
   @brief Include for AVR I/O register definitions
*/
#include <avr/io.h>

/**@include stdint.h
   @brief Include for standard integer definitions (ie, uint8_t, int32_t, etc)
*/
#include <stdint.h>

/**@include delay.h
   @brief Include for delay functions such as _delay_ms() and _delay_us()
*/
#include <util/delay.h>

/* Basic bit manipulation macros - everyone should use these.  Please, steal these! Don't not use them and
 don't rewrite them yourself!
*/
#define SET(x,y) x |= (1 << y)
#define CLEAR(x,y) x &= ~(1<< y)
#define READ(x,y) ((FALSE == ((x & (1<<y))>> y))?FALSE:TRUE)
#define TOGGLE(x,y) (x ^= (1<<y))

int main(void)
{
	/*Initialization Code*/
	
	/*	ATMega328 Datasheet Table 14-1 Pg 78
		Configure PD7 for use as Heartbeat LED
		Set as Output Low (initially)
	*/
	SET(DDRD,7);	//Direction: output
	CLEAR(PORTD,7);	//State: Lo

	/*	Flash the LED for a second to show that initialization has successfully 
		occurred
	*/
	SET(PORTD,7);
	_delay_ms(1000);
	CLEAR(PORTD,7);
	
	while(1)
	{
		/*Set PD7 low for 500ms*/
		CLEAR(PORTD,7);
		_delay_ms(500);
		
		/*Then set it high for 500ms*/
		SET(PORTD,7);
		_delay_ms(500);
		
		/*Before repeating the process forever...*/
	
	}

}

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;
}

The 2024 Embedded Online Conference