Atmega88PA read and write registers

A program to read and write the registers through the serial interface (ttyU0). The code is written (bare) without any external libraries or dependencies (except for stdint.h). Hopefully this could be helpful to someone when debugging or exploring a microcontroller (MCU).

Why not using a Raspberry or BeagleBone? You could but MCUs are low cost. Hence you do not have to worry if you destroy the MCU. Especially if you are switching inductors or working with high voltage. MCUs also provide easy access to the peripherals and internal memory.

Required hardware

The software program avrdude can be used to program the microcontroller together with a programmer. Also note that a Raspberry Pi connected to a FT232RL (in bit bang mode) can be used as a programmer.

Electrical

I recommend using galvanic isolation to prevent demaging your PC due to accidental voltage spikes or a short-ciruit. You can use an off the shell USB galvanic protector. I have used some opto-couplers, transistors and resistors to accomplish an galvanic separation between the PC and the USB to UART module.

Only connect the ground, RX and TX between the microcontroller and the USB to UART module. The power is supplied by the USB port to the USB to UART module. Connect the microcontroller to VCC, AVCC, ground. Please read the datasheet to add some capacitors for filtering or read some books like "The Art of Elecktronics" to get familiar with the basic of eletronical ciruits.

Software

There are three files needed to build the main.hex that is uploaded to the microcontroller. The linkscript (avr5.ld) is used to indicate to the linker how to assemble the final elf file. The crt.S contains interrupt vector and the initialization procedures. And you can find the main code in main.c.

The following commands can be executed to create 'main.hex':
avr-gcc -nostdlib -g -c crt.S main.c
avr-ld -nostdlib -script avr5.ld -Map test.map -o main.elf crt.o main.o
avr-objcopy -j .text -j .data -O binary main.elf main.hex
avr5.ld
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */

OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
OUTPUT_ARCH(avr:5)

MEMORY
{
  FLASH   (rx) : ORIGIN = 0x000000, LENGTH = 7k
  SRAM    (rw!x) : ORIGIN = 0x800100, LENGTH = 1k
}

PROVIDE(__sram_start = 0x0100);

SECTIONS
{
  .text   :
  {
    . = 0;
    *(.vectors)
    KEEP(*(.vectors))
    . = ALIGN(2);
    *(.vector_reset)
    KEEP (*(.vector_reset))
    . = ALIGN(2);

    *(.text)
    . = ALIGN(2);
     __end_text = . ;
  } > FLASH

  .data  :
  {
    __data_start = . ;
    *(.rodata*)
    *(.data*)
    . = ALIGN(2);
    __data_end = . ;
  } > SRAM AT > FLASH

  .bss  :
  {
     __bss_start = . ;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(2);
     __bss_end = . ;
  } > SRAM
}
crt.s

These are the first instructions that executed on the MCU. The MCU starts executing the "rjmp __vector_reset" which is the beginning of the program memory (FLASH).

One of the persistant issue I had was that the MCU was not executing correctly after a reset. I suspected it had something to do with the SRAM memory. In an attempt to fix the problem I initialized the whole SRAM with zeros. Normally, only the BSS area need to be filled with zeros. It did not resolve the issue. I kept the assembler code as it is because the MCU only has a small memory size.

Eventually I found the root case. Clearing the the working registers r0 until and including r31 fixed the problem.

.extern __sram_start
.extern __bss_start
.extern __bss_end

.extern __data_start
.extern __data_end

.equ MCUSR, 0x34
.equ WDTCSR, 0x60

.section ".vectors"
.global _vectors

_vectors:
.org 0
  rjmp __vector_reset
  reti ; __vector_ext_int0;
  reti ; __vector_ext_int1
  reti ; __vector_pcint0
  reti ; __vector_pcint1
  reti ; __vector_pcint2
  reti ; __vector_wdt
  reti ; __vector_tim2_compa
  reti ; __vector_tim2_compb
  reti ; __vector_tim2_ovf
  reti ; __vector_tim1_capt
  reti ; __vector_tim1_compa
  reti ; __vector_tim1_compb
  reti ; __vector_tim1_ovf
  reti ; __vector_tim0_compa
  reti ; __vector_tim0_compb
  reti ; __vector_tim0_ovf
  reti ; __vector_spi_stc
  rjmp __vector_usart_rxc
  reti ; __vector_usart_udre
  reti ; __vector_usart_txc
  reti ; __vector_adc
  reti ; __vector_ee_rdy
  reti ; __vector_ana_comp
  reti ; __vector_twi
  reti ; __vector_spm_rdy

.section ".vector_reset"
.global __vector_reset

__vector_reset:
  ; Disable Watchdog timer.
  cli
  wdr

  in r16, MCUSR
  andi r16, ~0x08
  out MCUSR, r16

  lds r16, WDTCSR
  ori r16, 0x18
  sts WDTCSR, r16

  ldi r16, 0
  sts WDTCSR, r16

  ; zero sram.
  ldi r26, lo8(0x0100)
  ldi r27, hi8(0x0100)

zero_sram:
  clr r20
  st X+, r20

  cpi r26, lo8(0x0500)
  ldi r16, hi8(0x0500)
  cpc r27, r16
  brne zero_sram

  ; Initialize stack pointer
  ; SRAM: 0x0100 to 0x04ff.
  ldi r16, 0x04
  out 0x3e, r16
  ldi r16, 0xff
  out 0x3d, r16

  ; copy data from flash to SRAM.
  ldi r26, lo8(__data_start)
  ldi r27, hi8(__data_start)
  ldi r30, lo8(__end_text)
  ldi r31, hi8(__end_text)

  ldi r16, lo8(__data_start)
  ldi r17, hi8(__data_start)
  ldi r18, lo8(__data_end)
  ldi r19, hi8(__data_end)
  sub r18, r16
  sbc r19, r17

  ldi r24, 0x00
  ldi r25, 0x00

copy_loop:
  lpm r0, Z+
  st X+, r0

  adiw r24, 1

  cp r18, r24
  cpc r19, r25
  brne copy_loop

  ; clear working registers
  clr r0
  clr r1
  clr r2
  clr r3
  clr r4
  clr r5
  clr r6
  clr r7
  clr r8
  clr r9
  clr r10
  clr r11
  clr r12
  clr r13
  clr r14
  clr r15
  clr r16
  clr r17
  clr r18
  clr r19
  clr r20
  clr r21
  clr r22
  clr r23
  clr r24
  clr r25
  clr r26
  clr r27
  clr r28
  clr r29
  clr r30
  clr r31

  rcall main
main.c
#include "stdint.h"

#define TIFR0 (*(volatile uint8_t *)(0x35))
#define TCCR0B (*(volatile uint8_t *)(0x45))

#define MCUSR (*(volatile uint8_t *)(0x54))
#define SREG (*(volatile uint8_t *)(0x5f))
#define WDTCSR (*(volatile uint8_t *)(0x60))
#define CLKPR (*(volatile uint8_t *)(0x61))

#define UCSR0A (*(volatile uint8_t *)(0xc0))
#define UCSR0B (*(volatile uint8_t *)(0xc1))
#define UCSR0C (*(volatile uint8_t *)(0xc2))
#define UBRR0L (*(volatile uint8_t *)(0xc4))
#define UBRR0H (*(volatile uint8_t *)(0xc5))
#define UDR0 (*(volatile uint8_t *)(0xc6))

extern uint8_t __data_start;
uint8_t * pdata_start;
extern uint8_t __bss_start;
extern uint8_t __bss_end;
uint8_t * pbss_start;
uint8_t * pbss_end;

uint8_t i;

uint8_t message01[] = "Register control is ready.\n\r\0";
uint8_t message_help[] = "\t# register control:\n\r \tr[0-9]{2}: read register\n\r \tR[0-9]{4}: set instruction\n\r \tX: apply instruction\n\r\0";
uint8_t message_register[] = "r(??)=??\n\r\0";

/*
\t# register control:\n\r
\tr[0-9]{2}: read register\n\r
\tR[0-9]{4}: set instruction\n\r
\tX: apply instruction\n\r
\0
*/

uint8_t bss_variable;
uint8_t command;
uint8_t lock_command;
uint8_t temporary;
uint8_t register_buffer[4];
uint8_t usart_data[4];
uint8_t usart_data_index;

void usart_init(void);
void usart_write(uint8_t data);
void expand_byte_to_ascii(uint8_t * data);
void write_string(uint8_t * message);

void __vector_usart_rxc(void) __attribute__((signal));

void __vector_usart_rxc(void)
{
  uint8_t uart_data = UDR0;

  if(lock_command == 0) {
    command = uart_data;

    if(command == 'm') {
      // Read SRAM
      pdata_start = (uint8_t *) &__data_start;
      pbss_end = (uint8_t *) &__bss_end;

      while(1) {
        register_buffer[0] = *(pdata_start);
        expand_byte_to_ascii(register_buffer);
        register_buffer[2] = '\0';
        write_string(register_buffer);

        pdata_start += 1;
        if(pdata_start > pbss_end) {
          break;
        }
      }
      usart_write('\n');
      usart_write('\r');

      command = 0;
    } else if(command == '?') {
      // help.
      write_string(message_help);
      command = 0;
    } else if(command == 'o') {
      // test.
      write_string(message01);
      command = 0;
    } else if (command == 'r') {
      // read register.
      lock_command = 'r';
      usart_data_index = 2;
    } else if (command == 'R') {
      // set instruction
      lock_command = 'R';
      usart_data_index = 4;
    } else if (command == 'X') {
      // apply instruction.
      *((uint8_t *) register_buffer[0]) = register_buffer[1];
      command = 0;
    }
  } else if(lock_command == 'r') {
    // Read register.
    usart_data_index -= 1;

    if(uart_data >= 48 && uart_data <= 57) {
      usart_data[usart_data_index] = uart_data-48;
    } else if(uart_data >= 87) {
      usart_data[usart_data_index] = uart_data-87;
    }

    if(usart_data_index == 0) {
      temporary = ((uint8_t) (usart_data[1] << 4)) | usart_data[0];

      message_register[2] = temporary;
      expand_byte_to_ascii(&message_register[2]);

      message_register[6] = *((volatile uint8_t *) temporary);
      expand_byte_to_ascii(&message_register[6]);
      write_string(message_register);

      register_buffer[0] = temporary;
      register_buffer[1] = *((volatile uint8_t *) temporary);

      command = 0;
      lock_command = 0;
    }
  } else if(lock_command == 'R') {
    // set buffer register.
    usart_data_index -= 1;

    if(uart_data >= 48 && uart_data <= 57) {
      usart_data[usart_data_index] = uart_data-48;
    } else if(uart_data >= 87) {
      usart_data[usart_data_index] = uart_data-87;
    }

    if(usart_data_index == 0) {
      temporary = ((uint8_t) (usart_data[3] << 4)) | usart_data[2];
      message_register[2] = temporary;
      expand_byte_to_ascii(&message_register[2]);

      register_buffer[0] = temporary;

      temporary = ((uint8_t) (usart_data[1] << 4)) | usart_data[0];
      message_register[6] = temporary;
      expand_byte_to_ascii(&message_register[6]);

      register_buffer[1] = temporary;

      write_string(message_register);

      command = 0;
      lock_command = 0;
    }
  }
}

void expand_byte_to_ascii(uint8_t * data) {
  uint8_t _data = 0;

  _data = *data & 0x0f;
  if(_data <= 9) {
    *(data+1) = 48+_data;
  } else if(_data > 9 && _data <= 15) {
    *(data+1) = 87+_data;
  }

  _data = *data >> 4;
  if(_data <= 9) {
    *(data) = 48+_data;
  } else {
    *(data) = 87+_data;
  }
}

void usart_init() {
  UBRR0H = 0;
  UBRR0L = 51;

  // RXEN0(4)=1, TXEN0(3)=1, UCSZ02(2)=0
  UCSR0B |= 0x18;

  // Event parity, 2 stopbits, 8 bits
  // UPMn1(5)=1, UPMn0(4)=0, USBS0(3)=1, UCSZ01(2)=1 , UCSZ00(1)=1
  UCSR0C |= 0x1E;

  // RXCIE(7)=1
  UCSR0B |= 0x80;
}

void usart_write(uint8_t data) {
  while(!(UCSR0A & 0x20));
  UDR0 = data;
  while(!(UCSR0A & 0x40));
  UCSR0A |= 0x40;
}

void write_string(uint8_t * message)
{
  uint16_t i = 0;

  while(1) {
    usart_write(message[i]);

    i += 1;

    if(message[i] == '\0')
      break;
  }
}

int main(void) {
  uint16_t i = 65535;

  // I(7)=1
  SREG |= 0x80;

  usart_init();

  while(i) {
    i -= 1;
  }

  write_string(message01);

  while(1);
  return 0;
}

Serial console

Open two terminals. The first terminal is a serial connection to the MCU, execute: "cu -l /dev/ttyU0". In the second terminal run the command "ksh configure_tty ttyU0 9600". See the content of the configure_tty.ksh script below.

Type a question mark in the first terminal to get a list of available commands that can be executed on the MCU.

configure_tty.ksh
#!/bin/ksh

TTY_DEV="$1"
TTY_BAUDRATE="$2"

[[ -z "$TTY_BAUDRATE" ]] && TTY_BAUDRATE="9600"
[[ -z "$TTY_DEV" ]] && { echo "$0 TTY_DEV"; exit 1; }

stty -f /dev/$TTY_DEV speed $TTY_BAUDRATE

# local
stty -f /dev/$TTY_DEV -echo echonl -icanon -isig -iexten

# control
stty -f /dev/$TTY_DEV parenb -parodd cstopb cs8
stty -f /dev/$TTY_DEV cread clocal hupcl

# input
stty -f /dev/$TTY_DEV -brkint -icrnl -inpck -istrip -ixon
# output
stty -f /dev/$TTY_DEV -opost
#stty -f /dev/$TTY_DEV -opost


stty -f /dev/$TTY_DEV time 0
stty -f /dev/$TTY_DEV min 1

Fuse bits

The fuse bits can be set using the programmer.

I ran the MCU on a clock frequency of 8Mhz, by default the MCU in configured to divide the internal clock by 8, resulting in a clock frequency of 1Mhz. I you run the MUC at a other frequency then do not forget the modify the UBRR0H and UBRR0L registers. This MCU can handle up to 20MHz and could be achieved by using an external oscillator source.

Questions

I love to get in touch with other embedded system enthusiasts. Do not hesitate to ask any questions: info@aoeu-it.nl

Liability

The information provided herein is for general educational purpose only and does not constitute professional advice. While effort has been made to ensure the accuracy and reliability of the information, no guarantee is given regarding its completeness or applicability. The reader assumes full responsibility for any actions taken based on the information provided. The author shall not be held liable for any direct, indirect, consequential or incidental damages arising from the use or misuse of the information presented.