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.
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.
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.
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
/* 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
}
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
#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;
}
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
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.
I love to get in touch with other embedded system enthusiasts. Do not hesitate to ask any questions: info@aoeu-it.nl
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.