Would you like to react to this message? Create an account in a few clicks or log in to continue.

You are not connected. Please login or register

Implementing new MCU perifericals.

Go down  Message [Page 1 of 1]

1Implementing new MCU perifericals. Empty Implementing new MCU perifericals. Tue Jan 25, 2022 4:26 am



Mcu perifericals are implemented as "Modules".
These Modules should be reusable by any Mcu.

Let's say we want to implement an USB module for AVR.
Even if this module is to be used by a certain AVR model, we should implement a base class than can be used by any Mcu.
In some cases this base class should be reusable by any component, not only Mcus, but let's keep it simple and focus in Mcus.

First we need the base class for USB: McuUsb
- All Mcu Modules should subclass McuModule and in most cases eElement.
- All base modules should be accesible from McuCreator.

So this is the first step, create the basic implementation of McuUsb:

Filename: mcuusb.h
#ifndef MCUUSB_H
#define MCUUSB_H

#include "mcumodule.h"
#include "e-element.h"

class MAINMODULE_EXPORT McuUsb : public McuModule, public eElement
        friend class McuCreator;  // Let McuCreator do whatever it wants

        McuUsb( eMcu* mcu, QString name );

Filename: mcuusb.cpp
#include "mcuusb.h"

McuUsb::McuUsb( eMcu* mcu, QString name )
      : McuModule( mcu, name )
      , eElement( name )
In this class we should implement the basic functionality of USB communication.

Next we need a base class for all Avr USBs:
We ussually need some method for configuration and initialize().
In the periferical xml file we define wich register configure this periferical, for example:

<usb name="USB0" configregsA="USBCONFIG" ...

Every time the register USBCONFIG is written, our configureA method will be called with the value as argument.

Filename: avrusb.h
#ifndef AVRUSB_H
#define AVRUSB_H

#include "mcuusb.h"

class MAINMODULE_EXPORT AvrUsb : public McuUsb
        AvrUsb( eMcu* mcu, QString name );
        virtual void initialize() override;
        virtual void configureA( uint8_t newUSBCONFIG ) override;

Filename: avrusb.cpp
#include "avrusb.h"
#include "e_mcu.h"

AvrUsb::AvrUsb( eMcu* mcu, QString name )
      : McuUsb( mcu, name )

void AvrUsb::initialize() // Called at simulation start
    // Initialize stuff

void AvrUsb::configureA( uint8_t newUSBCONFIG )
    // Read bits from newUSBCONFIG and configure USB
In this class we should implement the common things for all AVR USBs.
In some cases this is enought if there are not different variants of this periferical, but is common that different models use different registers and bits and have different features, then we should subclass AvrUsb for each variant.

Now let's see how to deal with configuration bits:
Ussually a configuration register has different bits or groups of bits that determine how to configure our periferical.

Let's say our imaginary USB configuration register has these bits:
Bits 0 to 3: USBM0, USBM1, USBM2. These bits determine the USB mode
Bit 5: USBEN. This bit enable or disable the USB Module.

In order to use these bits we need a data structure to hold them: regBits_t
This is defined in mcutypes.h
So in our avrusb.h file we need :

#include "mcutypes.h"

        regBits_t m_USBEN;
        regBits_t m_USBM;
And in our avrusb.cpp we get those bits in the constructor.
Functions to get those bits are in file: datautils.h

#include "datautils.h"

AvrUsb::AvrUsb( eMcu* mcu, QString name )
      : McuUsb( mcu, name )
    m_USBEN = getRegBits( "USBEN", mcu );
    m_USBM  = getRegBits( "USBM0,USBM1,USBM2", mcu );

Then we can get the bit values in our configureA() method like this:

void AvrUsb::configureA( uint8_t newUSBCONFIG )
    bool enabled = getRegBitsBool( newUSBCONFIG, m_USBEN );
    // Now Enable or Disable
    uint8_t mode = getRegBitsVal( newUSBCONFIG, m_USBM );
    // Now configure USB Module depending on mode

This is the very basics to implement a periferical,  there are some features that need explanation, for example:
- What happens with bits that can only be set or cleared or are read only?
- How we deal with bits cleared by writting them to 1.
- How to interface with other perifericals.
- How to trigger and clear Interrupts.
- How to get and release control on Pins.
- How to set actions to be performed later in time.
- How we deal with non physical registers.
- How we deal with Read/Write sequences activating certain actions.
- Some registers do different things when written or readen.
- Some registes are buffered.


Perifericals are described in xml files, for example a description of a comparator:

 <comp name="COMP" configregsA="ACSR" configbitsB="AIN0D,AIN1D"
                    pins="PORTD6,PORTD7" />
These xml files are readen by the class McuCreator which creates and configures instances of every part, for example for a comparator:
File: mcucreator.cpp

        else if( part == "comp" ) createAcomp( &el );
void McuCreator::createAcomp( QDomElement* e )
    McuComp* comp = NULL;
    QString name = e->attribute( "name" );
    int type = e->attribute("type").toInt();

    if     ( m_core == "AVR" )    comp = new AvrComp( mcu, name );
    else if( m_core == "Pic14" )  comp = PicComp::createComparator( mcu, name, type );
    else if( m_core == "Pic14e" ) comp = PicComp::createComparator( mcu, name, type );
    if( !comp ) return;

    mcu->m_modules.emplace_back( comp );
    setConfigRegs( e, comp );

    QStringList pins = e->attribute( "pins" ).split(",");
    for( QString pinName : pins )
        McuPin* pin = McuPort::getPin( pinName );
        if( pin ) comp->m_pins.emplace_back( pin );
    if( e->hasAttribute("interrupt") ) setInterrupt( e->attribute("interrupt"), comp );

This also needs to be implemented for a new periferical.

Back to top  Message [Page 1 of 1]

Permissions in this forum:
You cannot reply to topics in this forum