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
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
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 :
Functions to get those bits are in file: datautils.h
Then we can get the bit values in our configureA() method like this:
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:
File: mcucreator.cpp
This also needs to be implemented for a new periferical.
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
- Code:
#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
public:
McuUsb( eMcu* mcu, QString name );
~McuUsb();
};
#endif
- Code:
#include "mcuusb.h"
McuUsb::McuUsb( eMcu* mcu, QString name )
: McuModule( mcu, name )
, eElement( name )
{
}
McuUsb::~McuUsb(){}
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
- Code:
#ifndef AVRUSB_H
#define AVRUSB_H
#include "mcuusb.h"
class MAINMODULE_EXPORT AvrUsb : public McuUsb
{
public:
AvrUsb( eMcu* mcu, QString name );
~AvrUsb();
virtual void initialize() override;
virtual void configureA( uint8_t newUSBCONFIG ) override;
};
#endif
- Code:
#include "avrusb.h"
#include "e_mcu.h"
AvrUsb::AvrUsb( eMcu* mcu, QString name )
: McuUsb( mcu, name )
{
}
AvrUsb::~AvrUsb(){}
void AvrUsb::initialize() // Called at simulation start
{
// Initialize stuff
}
void AvrUsb::configureA( uint8_t newUSBCONFIG )
{
// Read bits from newUSBCONFIG and configure USB
}
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 :
- Code:
#include "mcutypes.h"
...
protected:
regBits_t m_USBEN;
regBits_t m_USBM;
Functions to get those bits are in file: datautils.h
- Code:
#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:
- Code:
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:
- Code:
<comp name="COMP" configregsA="ACSR" configbitsB="AIN0D,AIN1D"
interrupt="ACOMP"
pins="PORTD6,PORTD7" />
File: mcucreator.cpp
- Code:
...
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.