AVRSH: a Command Interpreter Shell for Arduino/AVR.




Introduction: AVRSH: a Command Interpreter Shell for Arduino/AVR.

About: Gian is a computational biologist,maker, author, humorist. He holds degrees in Molecular/Cellular Biology, Biochemistry, Computer Science. He has a collection of 8-bit microcontrollers and a room full of compu…
Ever wanted to be "logged in" to your AVR microcontroller? Ever thought it would be cool to "cat" a register to see its contents? Have you always wanted a way to power up and power down individual peripheral sub-systems of your AVR or Arduino in *real time* ? Me, too, so I wrote the AVR Shell, a UNIX-like shell.

It's UNIX-like because it's reminiscent of the shell account you went out and bought to run your irc nick collision bots on, as well as having a command or two in common. It also has a filesystem that resembles UNIX extfs, using an external EEPROM, but that's become a project unto itself so I'll be releasing that module separately under a different instructable when it's production-ready.

Here's a list of the things you can currently do with the AVR Shell:
  • Read all your Data Direction Registers (DDRn), ports, and pins in real-time
  • Write to all your DDRn's, ports, and pins to turn on motors, LED's, or read sensors in real-time
  • List all known registers on the system
  • Create and store values in user-defined variables backed up by EEPROM.
  • Create a root password and authenticate against it (used for telnet access)
  • Read the configured CPU clock speed
  • Change your CPU clock speed by setting a prescaler
  • Start and stop 16-bit timers for timing of various things
  • Power up and/or power down peripheral sub-systems: Analog to Digital Converters (ADC), Serial Peripheral Interface (SPI), Two-wire Interface (TWI/I2C), UART/USART. Useful for when you want to reduce power consumption of the microcontroller or to enable certain functions.
  • Written in C++ with reusable objects.

This instructable will walk through the installation, use, and customization of avrsh.

Step 1: What You'll Need

This instructable doesn't require much except that you:

  • Have an Arduino or ATmega328P. Other AVR's could work, but you may need to modify the code to list any registers that are unique to your MCU. The names only need to match what is listed in the <avr/io*.h> header file unique to your MCU. Many of the register names are the same between AVRs, so your mileage may vary when porting.
  • Have a way to connect to the serial USART of your Arduino/AVR. The system has been tested most extensively with the AVR Terminal, a Windows app that makes a serial connection via your USB or COM port. Works with Arduinos using the USB connection and any AVR using the USB-BUB from Moderndevice.com. Other terminal options include: Putty, minicom (Linux and FreeBSD), screen (Linux/FreeBSD), Hyperterminal, Teraterm. I've found putty and teraterm send some garbage when connecting so your first command may be garbled.
  • Have the AVR Shell firmware installed and running, which you can download from these pages, or always get the latest version at BattleDroids.net.

To install the AVR Terminal, just unpack it and run it. To install the AVR Shell firmware, download it and either directly upload the hex file and connect your serial terminal at 9600 baud, or compile it yourself with "make" and then "make program" to upload the hex. Note, you may need to change the AVRDUDE settings to reflect your COM port.

Note: The PROGMEM attribute is broken in the current AVR GCC implementation for C++ and this is a known bug. If you compile it, expect to get many warning messages saying "warning: only initialized variables can be placed into program memory area." Besides being annoying to see, this warning is harmless. As C++ on the embedded platform isn't high on the AVR GCC priorities list, it is unknown when this will be fixed. If you check out the code, you will see where I have made work arounds to reduce this warning by implementing my own attribute statements.

Pretty simple. Download and install anything that you might need to then flip the page and let's get crackin'.

Step 2: Reading and Writing Registers

The AVR Shell was written primarily to access some sensors that I had connected to my AVR. It started with a simple LED then moved to light sensors, temperature sensors, and finally to two ultrasonic transducers. avrsh can set the digital components of these sensors by writing to the registers that control them.

Manipulating AVR registers while running
To get a list of all known registers on your Arduino, type:
print registers
and you'll get a printout looking like this...
I know about the following registers:
TIFR0      PORTC      TIFR1      PORTD      TIFR2      DDRD
PCIFR      DDRB       EIFR       DDRC       EIMSK      PINB
EECR       PINC       EEDR       PIND       SREG       EEARL
GPIOR0     EEARH      GPIOR1     GTCCR      GPIOR2     TCCR0A
TCCR0B     TCNT0      OCR0A      OCR0B      SPCR       SPDR
ACSR       SMCR       MCUSR      MCUCR      SPMCSR     WDTCSR
CLKPR      PRR        OSCCAL     PCICR      EICRA      PCMSK0
PCMSK1     TIMSK0     TIMSK1     TIMSK2     ADCL       ADCH
ADCSRA     ADCSRB     ADMUX      DIDR0      DIDR1      TCCR1A
TCCR1B     TCCR1C     TCNT1L     TCNT1H     ICR1L      ICR1H
OCR1AL     OCR1AH     OCR1BL     OCR1BH     TCCR2A     TCCR2B
TCNT2      OCR2A      OCR2B      ASSR       TWBR       TWSR
TWAR       TWDR       TWCR       TWAMR      UCSR0A     UCSR0B
UCSR0C     UBRR0L     UBRR0H     UDR0       PORTB

To see how the individual bits are set in any register, use the cat or echo command:
cat %GPIOR0
Here I'm asking the command interpreter to display, or echo, the contents of the General Purpose I/O Register #0. Note the percent sign (%) in front of the register name. You need this to indicate to the shell that this is a reserved keyword identifying a register. The typical output from an echo command looks like this:
GPIOR0(0x0) set to [00000000]
The output shows the name of the register, the hexadecimal value found in the register and the binary representation of the register (showing each bit as a 1 or 0). To set a particular bit in any register, use the "index of" operator []. For example, let's say I want the 3rd bit to a 1.
%GPIOR0[3] = 1
and the shell will give you a response indicating it's action and the result:
GPIOR0(0x0) set to [00000000] -->  (0x8) set to [00001000]
Dont' forget the percent sign to tell the shell you're working with a register. Also note that by setting the 3rd bit, that's 4 bits in because our AVR's use a zero-based index. In other words, counting to the 3rd bit you count 0, 1, 2, 3, which is the 4th place, but the 3rd bit. You can clear a bit in the same way by setting a bit to zero.

By setting bits like this you can change the functioning of your AVR on the fly. For instance, by changing the CTC timer match value found in OCR1A. It also lets you peek into particular settings that you would have to programmatically check in your code, such as the UBBR value for your baud rate.

Working with DDRn, PORTn, and PINn
The I/O pins are also assigned to registers and can be set in exactly the same way, but a special syntax has been created to work with these types of registers.

In code, there is a normal process for, say, turning on an LED or other device that requires a digital high or low. It requires setting the Data Direction Register to indicate the pin is for output, and then writing a 1 or 0 to the particular bit in the correct port. Assuming we have an LED connected to digital pin 13 (PB5) and we want to turn it on, here's how to do that while your AVR is running:
set pin pb5 outputwrite pin pb5 high
The output, besides being able to see your LED come on, would look like this:
root@ATmega328p> set pin pb5 outputSet pb5 for outputroot@ATmega328p> write pin pb5 highWrote logic high to pin pb5
The "root@ATmega328p>" is the shell's prompt that indicates it is ready to accept commands from you. To turn the LED off, you would simply write a low to the pin. If you want to read the digital input from a pin, use the read command. Using our above example:
root@ATmega328p> read pin pb5Pin: pb5 is HIGH
Alternatively, just echo the pin register that controls that pin port. For example, if we have dip switches connected to digital pin 7 and 8 (PD7 and PD8), you could send the command:
echo %PIND
and the shell would then display the contents of that register, showing you all of the input/output states of connected devices and whether the state of the switch was on or off.

Step 3: Reading and Writing Fuses

Fuses are special types of registers. They control everything from the clock speed of your microcontroller to what programming methods are available to write-protecting EEPROM. Sometimes you will need to change these settings, especially if you're creating a stand-alone AVR system. I'm not sure you should change your fuse settings on Arduino. Be careful with your fuses; you can lock yourself out if you set them incorrectly.

In a previous instructable, I demonstrated how you can read and set your fuses using your programmer and avrdude. Here, I'll show you how to read back your fuses at run time to see how your MCU has actually set them. Note, that this isn't the compile-time setting that you get from the definitions in <avr/io*.h> but the actual fuses as the MCU reads them at run time.

From Table 27-9 in the ATmega328P datasheet (databook, more like it) the bits of the Fuse Low Byte are as follows:
An interesting thing to note is that with fuses, 0 means programmed and a 1 means that that particular bit is unprogrammed. Somewhat counter-intuitive, but once you know it you know it.
  • CKDIV8 sets your CPU clock to be divided by 8. The ATmega328P comes from the factory programmed to use its internal oscillator at 8MHz with CKDIV8 programmed (ie set to 0) giving you a final F_CPU or CPU frequency of 1MHz. On Arduino's, this is changed since they are configured to use an external oscillator at 16MHz.
  • CKOUT when programmed will output your CPU clock on PB0, which is digital pin 8 on Arduinos.
  • SUT[1..0] specifies the startup time for your AVR.
  • CKSEL[3..0] sets the clock source, such as the internal RC oscillator, external oscillator, etc.
When you read your fuses, it will be returned to you in hexadecimal. This is the format that you need if you want to write the fuses via avrdude. On my arduino, here's what I get when I read the lower fuse byte:
root@ATmega328p> read lfuseLower Fuse: 0xff
So, all bits are set to 1. I did the same procedure on an Arduino clone and got the same value. Checking one of my stand-alone AVR systems, I got 0xDA which is the value I had set some time back when configuring the chip.

The same procedure is used for checking the High Fuse Byte, Extended Fuse Byte, and Lock fuses. The calibration and signature fuse bytes have been disabled in the code with an #if 0 preprocessor directive, which you can change if you feel scrappy.

Step 4: Other Commands

There are several other commands that the default command interpreter understands that you may find useful. You can see all the implemented and future-release commands by issuing help or menu at the prompt. I'll quickly cover them here as they are mostly self-explanatory.

CPU Clock Frequency Settings
You can find out what your firmware has been configured to use as the CPU clock settings with the fcpu command:
root@ATmega328p> fcpuCPU Freq:  16000000
That's 16 million, or 16 million herz, more commonly known as 16 MHz. You can change this on the fly, for whatever reason, with the clock command. This command takes one argument: the prescaler to use when dividing your clock speed. The clock command understands these prescaler values:

  • ckdiv2
  • ckdiv4
  • ckdiv8
  • ckdiv16
  • ckdiv32
  • ckdiv64
  • ckdiv128
  • ckdiv256

Using the command:
clock ckdiv2 
when your cpu speed is 16MHz would result in your clock speed being changed to 8MHz. Using a prescaler of ckdiv64 with an initial clock speed of 16MHz will result in a final clock speed of 250 KHz. Why on Earth would you want to make your MCU slower? Well, for one, a lower clock speed consumes less power and if you have your MCU running off of a battery in a project enclosure you may not need it to run at top speed, and could therefore, lower the speed and reduce it's power consumption, increasing the battery life. Also, if you are using the clock for any sort of timing issues with another MCU, say, implementing a software UART or some such thing, you may want to set it to a particular value that is easy to get a nice even baud rate with lower error rates.

Powering Up and Powering Down Peripheral Sub-Systems
On the same note as reducing power consumption mentioned earlier, you may want to further reduce power by shutting down some of the on-board peripherals that you are not using. The command interpreter and shell can currently power up and power down the following peripherals:

  • Analog-to-Digital Converter (ADC). This peripheral is used when you have an analog sensor providing data (like temperature, light, acceleration, etc) and need to convert it to a digital value.
  • Serial Peripheral Interface (SPI). The SPI bus is used to communicate with other SPI-enabled devices, like external memories, LED drivers, external ADC's, etc. Parts of the SPI are used for ISP programming, or at least the pins are, so be careful when shutting this down if you are programming via ISP.
  • Two-Wire Interface. Some external devices use the I2C bus to communicate, although these are rapidly being replaced by SPI-enabled devices as SPI has a greater throughput.
  • USART. This is your serial interface. You probably don't want to turn this off if you are connected to the AVR via the serial connection! However, I added this in here as a skeleton for porting to devices that have multiple USART's like the ATmega162 or ATmega644P.
  • all. This argument to the powerup or powerdown command turns on all of the peripherals mentioned or turns them all off with one command. Again, use this command wisely.
  • root@ATmega328p> powerdown twiPowerdown of twi complete.root@ATmega328p> powerup twiPowerup of twi complete.

Starting and Stopping Timers
The shell has a built-in 16-bit timer that is available for use. You start the timer with the timer command:
timer start
and stop the timer with the stop argument:
timer stop
This timer will not conflict with the internal USART timer. See the code for the implementation details of the USART timer, if that sort of gory detail interests you.
root@ATmega328p> timer startStarted timer.root@ATmega328p> timer stopElapsed time: ~ 157 seconds

The shell can store an 8-character password into EEPROM. This password mechanism was created to support the telnet login capabilities, but could be expanded to protect other things. For example, you could require certain commands, like changing register values, through the authentication mechanism.
Set the password with the password command:
root@ATmega328p> passwd blahWrote root password to EEPROM
Authorize against he password (or require authorization programatically through the code) with the auth command. Note, that if you attempt to change the root password and there is already a root password set, you must authorize yourself against the old password before being allowed to change it to a new password.
root@ATmega328p> passwd blinkyYou must authorize yourself first.root@ATmega328p> auth blahAuthorized.root@ATmega328p> passwd blinkyWrote NEW root password to EEPROM
Of course, you will need to load the avrsh.eep file if you erase the firmware to have your old values and variables restored. The Makefile will create the EEPROM file for you.

The shell understands the notion of user-defined variables. The code limits this to 20, but you can change that if you like by changing the define MAX_VARIABLES in script.h. You can save any 16-bit value (that is, any number up to 65,536) to a variable to be recalled later. The syntax is similar to registers except a dollar sign ($) is used to denote variables to the shell. List all your variables with the print variables command.
print variablesUser-defined variables:Index Name      ->  Value(01): $FREE$    ->      0(02): $FREE$    ->      0(03): $FREE$    ->      0(04): $FREE$    ->      0(05): $FREE$    ->      0(06): $FREE$    ->      0(07): $FREE$    ->      0(08): $FREE$    ->      0(09): $FREE$    ->      0(10): $FREE$    ->      0(11): $FREE$    ->      0(12): $FREE$    ->      0(13): $FREE$    ->      0(14): $FREE$    ->      0(15): $FREE$    ->      0(16): $FREE$    ->      0(17): $FREE$    ->      0(18): $FREE$    ->      0(19): $FREE$    ->      0(20): $FREE$    ->      0Complete.
Set a variable:
$newvar = 25$timeout = 23245
Get the value of a given variable:
root@ATmega328p> echo $newvar$  newvar --> 25
You can see what all variables you've currently instantiated with the print command that you already know.
User-defined variables:Index Name      ->  Value(01): newvar    ->     25(02): timeout   ->  23245(03): $FREE$    ->      0(04): $FREE$    ->      0(05): $FREE$    ->      0(06): $FREE$    ->      0(07): $FREE$    ->      0(08): $FREE$    ->      0(09): $FREE$    ->      0(10): $FREE$    ->      0(11): $FREE$    ->      0(12): $FREE$    ->      0(13): $FREE$    ->      0(14): $FREE$    ->      0(15): $FREE$    ->      0(16): $FREE$    ->      0(17): $FREE$    ->      0(18): $FREE$    ->      0(19): $FREE$    ->      0(20): $FREE$    ->      0Complete.
The $FREE$ name just indicates that that variable location is free and has not been assigned a variable name yet.

Step 5: Customizing the Shell

You are free to hack at the code and customize it to your own needs, if you like. If I had known I'd be releasing this code, I would have made a separate command interpreter class and command structure and simply iterated through this calling a function pointer. It would reduce the amount of code, but as it stands the shell parses the command line and calls the appropriate shell method.

To add in your own custom commands, do the following:

1. Add your command to the parse list
The command parser will parse the command line and give you the command and any arguments separately. The arguments are passed as pointers to pointers, or an array of pointers, however you like to work with them. This is found in shell.cpp. Open up shell.cpp and find the ExecCmd method of the AVRShell class.

You may wish to add the command to program memory. If you do, add the command in progmem.h and progmem.cpp. You can add the command to program memory directly using the PSTR() macro, but you'll generate another warning of the type mentioned earlier. Again, this is a known bug working with C++, but you can get around this by adding the command directly in the progmem.* files, as I have done. If you don't mind adding to your SRAM usage, you can add the command as I have illustrated with the "clock" command.

Say you wanted to add a new command called "newcmd." Go to AVRShell::ExecCmd and find a convenient place to insert the following code:
else if (!strcmp(c,"newcmd"))    cmdNewCmd(args);
This will add your command and call the cmdNewCmd method that you will write in the next step.

2. Write your custom command code
In the same file, add your custom command code. This is the method definition. You will still want to add the declaration to shell.h. Just append it to the other commands. In the previous example, the code might look something like this:
voidAVRShell::cmdNewCmd(char ** args){    sprintf_P(buff,PSTR("Your command is %s\r\n",args[0]);    WriteRAM(buff);}
There are several things here. First, "buff" is a 40-character array buffer provided in the code for your use. We use the program memory version of sprintf since we're passing it a PSTR. You can use the regular version if you like, but ensure you don't pass the format in a PSTR. Also, the arguments are in the args array. If you typed "newcmd arg1 arg2" you can get at these arguments with the args[0] and args[1] subscripts. You can pass a maximum of MAX_ARGS arguments, as defined in the code. Feel free to change that value when you recompile if you need many more arguments to be passed at once.

The WriteLine and WriteRAM are global functions that return the UART's methods of the same name. The 2nd argument to this function is implicit. If you pass nothing, a command prompt will be written afterwards. If yo pass a 0 as the 2nd argument, a prompt will not be written. This is useful when you want to write several separate strings to output before the command prompt is returned to the user.

3. Have the shell execute the command code
You have already told the shell executor to execute the method cmdNewCmd when you setup the new command, but add it to the shell.h file to have it understood by the shell object. Just add it below the last command or in front of the first command, or anywhere in there.

And that's it. Recompile and upload the firmware to your Arduino and your new command is available from the shell at the prompt.

Step 6: Summary

You should know how to install and connect to your AVR/Arduino and get a live prompt on your running microcontroller. You know several commands that will pull runtime data from the MCU or set values into the MCU on the fly. You have also been shown how to add your own custom code to create your own unique commands to the shell to further customize it for your own needs. You can even gut the command interpreter to have it only contain your custom commands, if that suits your needs.

I hope you've enjoyed this instructable and that the AVR Shell can be useful for you, either as a real-time command interpreter or as a learning process in implementing your own.

As always, I look forward to any comments or suggestions on how this instructable can be improved!

Have fun with your AVR!

Be the First to Share


    • Game Design: Student Design Challenge

      Game Design: Student Design Challenge
    • Big and Small Contest

      Big and Small Contest
    • Make It Bridge

      Make It Bridge



    7 years ago on Introduction

    how can i use that to read contents of the sram?


    8 years ago on Step 1

    This is very inspiring. I am working on an ATmega1284 based microcomputer and your code has given me some valuable insights on how to create a CLI (I mainly do HW not SW so this is very helpful).


    Reply 7 years ago on Step 1

    Heyas, thanks for the kind words. I'm like your project you mention. I have a similar one, myself. If you have gotten some work done on it or want to discuss more technical aspects of persistent microkernels on MCUs drop me an email.



    11 years ago on Introduction

    little issue here not sure if its me or just the regular look in c but it wont compile at all in avr studio 5 .. been siting an working away the errors buts an very long list of them .....

    so question is what compiler was used and what platform of c as it really don't want to be nice to me ..


    Reply 11 years ago on Introduction

    Sorry to hear you're having so many problems with the compile. To be honest, I haven't tried compiling it under AVR Studio 5 yet. I believe I used either WinAVR or avr-libc and the avr (gcc 4.5.2) package under Linux.

    If I get some free time I'll try converting the package over to AVR Studio 5 and post a zip file.

    Good luck!


    Reply 8 years ago on Introduction


    I tried to compile avrsh on linux (using avr-libc and avr-gcc-4.8.2) but all I get is:

    In file included from common.h:15:0,
    from common.cpp:8:
    common.h:114:27: error: variable ‘PortPins’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’
    extern PortPin PortPins[] PROGMEM;
    make: *** [obj/common.o] Erreur 1

    Do you have some idea?


    Reply 11 years ago on Introduction

    nice :)

    hopefully i will learn from it


    10 years ago on Introduction

    Wow I am so excited to use this. Seriously thank you so much for making this, nevdull!

    I had no problem uploading to my Atmega328p and even connecting to it with CoolTerm (Mac OS) since I can see the 'root@Atmega328p' command prompt. But I'm having problems sending messages. I get things like the following:

    root@ATmega328p> print registers
    <> p....reVk..rs.: unknown command.

    Anyone using CoolTerm and having the same problem? I'm assuming there's some setting within CoolTerm that may fix it?


    Reply 10 years ago on Introduction

    Ok just in case anyone is using CoolTerm to connect, make sure of the following under 'Options':

    (1) Make sure 'Line Mode' is selected under 'Terminal'
    (2) Make sure 'Use transmit character delay' is selected under 'Transmit Options' with its default delay of 3ms


    Reply 10 years ago on Introduction

    Do you have the ability to simply launch 'telnet' from the command line on your mac? That might be another alternative.



    Reply 10 years ago on Introduction


    Hey, I'm glad you liked my project and instructable. As for your problem, I had many of the same type of errors but the two main culprits always were either:

    1. The telnet client is sending telnet packets/commands and the AVR shell doesn't know how to interpret them. I'm just riding on port 23 TCP but don't implement any of the telnet protocol. I've found TerraTerm and a couple other windows telnet clients did this. I wrote my own which I've linked in the project elsewhere.
    2. There is a baud-rate mismatch. If you have your baud rate based on your FCPU MHz set very far outside of the specs for the AVR shell, they won't sync up and all you'll get is garbage.

    So, depends whether you're constantly getting garbage (problem 1) or just getting garbage upon connection (problem 2).

    Hope you manage to figure out the issue and have some fun with the shell. For a full-throttle, extreme version of this, check out the MUNIX project which endeavors to build a viable UNIX-like workstation using nothing but 8-bit AVRs and linear/logic chips. From the keyboard controller (ATtiny45) to the USB host controller (ATmega32U) to the main dual processors (ATxmega256A3), this workstation has all the code for filesystems, unix-like shells, process control, memory management, linking and loading. More at http://www.munixos.net

    Have fun and thanks for the comment!


    11 years ago on Step 6

    This is truly amazing. But one question comes to mind. How'd I go about sending the resulting HEX file to the Arduino? Does it use one of the tools that're otherwise popular for the hardware platform?


    12 years ago on Introduction

    Thanks for sharing your project!

    I like it, max Kudos!



    13 years ago on Introduction

     Can you view the code on the avr? thats what im looking for...


    13 years ago on Introduction

    Nice job.  How long did it take to write this (the code, not the Instructable)?  Maybe someday I'll know enough to use this!


    Reply 13 years ago on Introduction

    thanks mathman,
    I wrote the shell over the course of a weekend, and the windows terminal in about  a week.  It took longer for the terminal than I expected because I was doing research-oriented development and added a TCP/IP server to it so it could proxy telnet to my AVR and Arduino.  I have a half-written "Telnet to your Arduino" instructable that I just never had the heart to finish. haha.

    The shell is somewhat naive, being a first-pass stand-alone shell.  I have a much more robust command interpreter that's being released with the μnix workstation and μnix Operating System when it gets to some level of usability.


    13 years ago on Introduction

    Would pins 2 and 3 be used for RX and TX for this? I loaded the .hex on my Atmega168 (Avrdude verified it after and looked ok) and setup a Bus Pirate V3 as:
    Uart 9600
    no parity
    1 stop bit 
    Receive polarity idle 0
    Output type normal (high=3.3volts, low=ground)

    I was trying to use it as a Uart bridge which makes the bus pirate act as a regular RS232 usb adapter. When i connect i get nothing but 0xFF over and over.

    Any ideas?