ATtiny10 用プログラム (11)

ピン変化割り込みと AD 変換完了割り込みを利用したソフトウェア UART 受信プログラムのソース・リストを下に示します。

  • AD 変換完了割り込みサービス・ルーチン (108 バイト)
  • ピン変化割り込みサービス・ルーチン (50 バイト)
  • 初期化関数 tn10_sw_uart_setup() (40 バイト)

から構成されており、当然のことながら、直接呼び出すのは「tn10_sw_uart_setup()」だけです。
シリアル受信データのアクセス用の関数は用意しておらず、グローバル変数を直接アクセスします。
グローバル変数領域を 4 バイト使用し、割り込みサービス・ルーチンでは、自動的にスタックに退避される PC の分も含めて 6 バイトのスタックを使用します。
また、RxD サンプリングのタイミングを正確に保つために、他の割り込みは使用不可とします。
「sw_uart_rx.h」

/*************************************************/
/* sw_uart_rx.h : Software UART (Rx only)        */
/*                for ATtiny10                   */
/*                using PCINT0 and ADC interrupt */
/*                                               */
/* 2012/01/12 : Created by pcm1723               */
/*************************************************/
#ifndef _SW_UART_RX_H_
#define _SW_UART_RX_H_

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

// uncomment following '#define'
// to use GATE output as UART DEBUG output
//#define UART_DEBUG

// Synth CV output port bit assign (PWM DAC)
#define CV_BIT   (PORTB0)
// Synth GATE output port bit assign
#define GATE_BIT (PORTB1)
// MIDI RxD input port bit assign
#define RXD_BIT  (PORTB2)
// number of Rx data bits
#define UART_DATABITS (8)
// Rx transfer Complete flag bit
#define sw_RXC (7)

// Synth GATE ON
#define GATE_ON  (PORTB |=   _BV(GATE_BIT) )
// Synth GATE OFF
#define GATE_OFF (PORTB &= ~(_BV(GATE_BIT)))
// stop ADC free running mode
#define tn10_sw_uart_stop_adc()    (ADCSRA &= ~(_BV(ADEN)))
// start ADC free running mode
#define tn10_sw_uart_start_adc()   (ADCSRA |=  (_BV(ADSC) | _BV(ADEN)))
// disable Pin Change Interrupt
#define tn10_sw_uart_disable_pci() (PCICR  &= ~(_BV(PCIE0)))
// enable Pin Change Interrupt (and clear pending interrupt)
#define tn10_sw_uart_enable_pci() \
  do { \
    PCIFR |= _BV(PCIF0); \
    PCICR |= _BV(PCIE0); \
  } while (0)

// software UART setup
extern void tn10_sw_uart_setup( void );

extern volatile uint8_t uart_flag;   // Rx Data ready flag
extern volatile uint8_t uart_rx_buf; // Rx Data buffer

#endif // #ifndef

CV 出力、GATE 出力、RxD 入力のビット割り付けは、それぞれ、CV_BIT、GATE_BIT、RXD_BIT のシンボルの定義で行います。
上のソース・ファイルでは、

  • CV 出力 = PB0 (ピン 1)
  • GATE 出力 = PB1 (ピン 3)
  • RxD 入力 = PB2 (ピン 4)

としています。
「sw_uart_rx.c」

/*************************************************/
/* sw_uart_rx.c : Software UART (Rx only)        */
/*                for ATtiny10                   */
/*                using PCINT0 and ADC interrupt */
/*                                               */
/* 2012/01/12 : Created by pcm1723               */
/*************************************************/

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include "sw_uart_rx.h"

volatile        uint8_t uart_flag;   // Rx Data ready flag
volatile        uint8_t uart_rx_buf; // Rx Data register
volatile static uint8_t uart_stat;   // (private) state counter
volatile static uint8_t uart_rx_sr;  // (private) Rx shift register

//
// ADC conversion complete interrupt
//
ISR(ADC_vect)
{
#if defined(UART_DEBUG)
    GATE_ON;
#endif 
  if (0x01 & (++uart_stat)) { // data sampling timing?
// read data bit and shift in
    uart_rx_sr >>= 1; 
    if (_BV(RXD_BIT) & PINB) { // test RxD value
      uart_rx_sr |= (1 << (UART_DATABITS-1)); // RxD = 'H'
    } // if (_BV(RXD_BIT) & PINB) { ...
    if ((2*(UART_DATABITS-1)) <= uart_stat) { // MSB found
      tn10_sw_uart_stop_adc(); // stop Baud clock
      uart_rx_buf = uart_rx_sr; // copy to buffer
      uart_flag |= _BV(sw_RXC);// Rx receive complete flag
      uart_rx_sr = 0;
      uart_stat = 0; // start bit hunt mode
      tn10_sw_uart_enable_pci(); // hunt start bit
    } // if ((2*(UART_DATABITS-1)) < uart_stat) ...
  } // if (0 == (0x01 & ...
#if defined(UART_DEBUG)
  GATE_OFF;
#endif
} // ISR(ADC_vect)

//
// Pin Change Interrupt Service Routine
//
ISR(PCINT0_vect)
{
#if defined(UART_DEBUG)
  GATE_ON;
#endif
    if (0 == (_BV(RXD_BIT) & PINB)) { // RxD == 'L' ?
// start bit edge ('H' --> 'L') found, wait before next check
      __asm__ __volatile__ // delay loop
        ( "\n\t"
          "ldi r24,15\n"
          "dly_L1:\n\t"
          "dec r24\n\t"
          "brne dly_L1\n\t"
          : : );
// check RxD level again (still 'L' ?)
      if (0 == (_BV(RXD_BIT) & PINB)) { // RxD == 'L' confirmed
        tn10_sw_uart_disable_pci(); // disable Pin Change Interrupt
        tn10_sw_uart_start_adc(); // start Baud rate interrupt
      } // if (0 == ...
// if (RxD != 'L'), NOISE detected, hunt start bit edge again
    } // if (0 == ...
#if defined(UART_DEBUG)
  GATE_OFF;
#endif
} // ISR(PCINT0_vect)

//
// software UART (Rx only) setup
//
void tn10_sw_uart_setup( void )
{
// ADC setup for Baudrate interrupt generation
  ADMUX  = 3; // ADC dummy input from PB3 (pin 6)
  ADCSRB = 0; // ADC free running mode             
//
// for (legacy) MIDI (31.25 kbps)
// ADC clock = 812.5 kHz @ 6.5  MHz CPU clock
// ADC interrupt rate = 812.5 kHz / 13 = 62.5 kHz @ 6.5 MHz CPU clock
//
// for serial MIDI (38.4 kbps)
// ADC clock = 997.4 kHz @ 7.9872 MHz CPU clock
// ADC interrupt rate = 997.2 kHz / 13 = 76.8 kHz @ 7.9872 MHz CPU clock
  ADCSRA = (  _BV(ADATE) // ADc Auto Trigger Enable
            | _BV(ADIE)  // ADc Interrupt Enable
            | 3);        // prescaler = 1/8
//
// Pin Change interrupt setup for soft UART Rx
//
  DDRB  &= ~(_BV(RXD_BIT));  // set RxD bit to INPUT
  PUEB  |= _BV(RXD_BIT);     // enable pullup
  PCMSK  = _BV(RXD_BIT);     // enable Rx bit Pin Change Interrupt
  tn10_sw_uart_stop_adc();   // stop ADC free running mode
  tn10_sw_uart_enable_pci(); // enable Pin Change Interrupt
  uart_stat  = 0;            // set start bit hunt mode
  uart_flag &= ~(_BV(sw_RXC)); // clear Rx ready flag
} // void tn10_sw_uart_setup()

AD 変換クロック数は変更できないので、シリアル MIDI (38.4 kbps) の場合は内部 8 MHz オシレータの周波数を 8 MHz (正確には 7.9872 MHz) に、レガシー MIDI (31.25 kbps) の場合は内部 8 MHz オシレータの周波数を 6.5 MHz にするように、 OSCCAL レジスタに適切な値を設定する必要があります。
プログラムの断片を示すことによって、使い方を説明します。

. . . < 中略 > . . .

void tn10_port_setup( void )
{
// set clock prescaler to 1/1 (8 MHz clock)
  CCP     = 0xD8; // set protection signature
  CLKPSR  = 0;    // clock prescale = 1/1 (8 MHz clock)
// add OSCCAL offset
  CCP     = 0xD8; // set protection signature
  OSCCAL += OSCCAL_ADJ; // add OSCCAL offset

  . . . < 中略 > . . .

  tn10_sw_uart_setup(); // software UART setup
  sei(); // enable global interrupt flag
} // void tn10_port_setup()

int main() 
{
  tn10_port_setup(); // hardware and software setup
  for (;;) { // infinite loop
    if (_BV(sw_RXC) & uart_flag) { // UART Rx ready?
      uart_flag &= ~(_BV(sw_RXC)); // clear UART flag
      mididec(uart_rx_buf);
    } // if 
  } // for (;;)
} // int main()

ハードウェアの設定は「tn10_port_setup()」関数にまとめてあり、クロックの設定、タイマの設定などを行い、「tn10_sw_uart_setup()」関数を呼び出して、ハード、ソフトの初期化を行います。
シリアル受信データがあるかどうかは、

    if (_BV(sw_RXC) & uart_flag) { // UART Rx ready?
      uart_flag &= ~(_BV(sw_RXC)); // clear UART flag
      mididec(uart_rx_buf);
    } // if 

のように、グローバル変数「uart_flag」の対応ビットが立っているかどうかで判定し、処理を分けます。
受信データがあるときには、ハードウェアと違って自動的にフラグはクリアされないので、「uart_flag」の対応ビットを明示的にクリアします。
受信データにはグローバル変数「uart_rx_data」を介してアクセスできます。