stm32plus::net, MAC Module

The MAC module is responsible for providing an interface between application code that wants to send and receive ethernet frames and the ethernet PHY that provides the protocol for doing so. The MAC is also responsible for implementing error detection and reporting as well as collision detection and flow control.

The MAC peripheral in the STM32F107 and STM32F4 is essentially the same, only a few more options have been added to the F4 and they’ve been added in a backwards compatible way – credit to ST for thinking of the developers.

Basically what you get is a dedicated DMA channel that is used to transfer ethernet frames to a FIFO for transmission and from a FIFO to memory upon reception.

stm32plus::net exposes the following MAC features:

  • MII and RMII supported in full or half-duplex at 10 or 100Mb/s.
  • All MCU pinouts supported.
  • Hardware checksum and CRC calculation for TCP/IP/UDP.
  • Source MAC address filtering
  • Reception of Ethernet v2 and 802.3 with QTAG frames.
  • Transmission of Ethernet v2 frames.
  • Entirely asynchronous IRQ-based implementation.
If you read the system architecture section of the STM32 reference manuals you’ll notice that the ethernet DMA channels are only connected to the internal SRAM and the external FSMC. Note the lack of flash connectivity. That means you cannot transmit constant data compiled into flash over the ethernet; it must be copied to SRAM first. stm32plus::net will detect if you try to transmit from flash and will raise an error.

Working with the STM32 MAC was a very positive experience. It’s clearly been designed by someone who understands how it will be used in the real world, i.e. for implementing internet protocols.

For example, when you want to transmit a data buffer then that data buffer will work its way down the TCP/IP stack collecting headers from the transport, network and datalink layers before arriving at the MAC. The STM32 MAC provides the ability for an ethernet frame to be sourced from two memory pointers that, when put together make up the complete frame. This is clearly aimed at collecting headers from the first pointer and the user data from the second pointer.

stm32plus::net makes full use of this capability to avoid copying data submitted for transmission and needlessly wasting your memory. Your data is transmitted in-place and the headers collected in a buffer that’s allocated once to the exact size of the headers required.

Frame reception is a bit less efficient because we cannot know ahead of time what size frames are going to be sent to us so we use the facility to provide a linked-list of full-size, preallocated frame buffers ready for incoming data. stm32plus::net goes to great lengths to avoid copying data from these receive buffers. As frames work their way up the stack I simply map structure pointers directly into the receive buffer and then raise appropriate events.

MAC implementation

stm32plus::net comes with support for MII and RMII in any MCU pin configuration. Four convenient typedefs allow the selection of the MII or RMII interface in the default or remapped pin configurations. Select just one of these for your physical layer:

typedef DatalinkLayer<MyPhysicalLayer,DefaultRmiiInterface,Mac> MyDatalinkLayer;
typedef DatalinkLayer<MyPhysicalLayer,RemapRmiiInterface,Mac> MyDatalinkLayer;
typedef DatalinkLayer<MyPhysicalLayer,DefaultMiiInterface,Mac> MyDatalinkLayer;
typedef DatalinkLayer<MyPhysicalLayer,RemapMiiInterface,Mac> MyDatalinkLayer;

When I say default or remapped pin configurations I refer specifically to the configurations available on the STM32F107. These are also available pin-for-pin on the STM32F4. Please see MacDefaultPinPackage.h and MacRemapPinPackage.h for the detailed pinouts.

If you’re targetting the F4 and you want a different pinout then you can define your own interface typedef like this (using RMII as an example):

template<class TPhysicalLayer>
using MyCustomRmiiInterface=RmiiInterface<TPhysicalLayer,MyCustomPinPackage>;

Where MyCustomPinPackage is a structure declared in the same way as those that you can see in MacDefaultPinPackage.h and MacRemapPinPackage.h. Your new datalink layer declaration would then look like this:

typedef DatalinkLayer<MyPhysicalLayer,MyCustomRmiiInterface,Mac> MyDatalinkLayer;

Configuration parameters

The following parameters are made available in the stack’s configuration object for customisation:

// NVIC priority (default 0)
uint8_t mac_irqPriority;     

// NVIC sub-priority (default 0)
uint8_t mac_irqSubPriority;  

// default this to 1518 bytes (1500 data plus header (incl vlan option)
uint16_t mac_mtu;

// address of this device on the LAN: default is 02-00-00-00-00-00
MacAddress mac_address; 

// number of receive buffers (default is 5)
uint8_t mac_receiveBufferCount;

// number of transmit buffers (default is 5)
uint8_t mac_transmitBufferCount;	

// max time to wait for a pending frame to go (default is 200ms)
uint32_t mac_txWaitMillis;

mac_irqPriority and mac_irqSubPriority are the NVIC priority values for the ethernet interrupt. The default is zero for both of them. You only need to adjust if you need to set the priority relative to other things that you’ve got going on.

mac_mtuis the maximum transmission unit size for your network segment. The default is probably correct and should be left alone. Many other parts of the stack have calculations based upon this value.

mac_address is your station’s MAC address. If you’re new to this then you’re probably wondering why the STM32 hasn’t got a MAC address hard-coded into it. Well the reason is that well-known MAC addresses are purchased in blocks by the manufacturer of the device that’s using the STM32 and then burned in to the firmware of each device during production.

MAC addresses only need to be unique on a LAN, they are not transmitted between LANs by a router. Addresses beginning 02 are locally administered which means you can pick one and as long as no-one else on your LAN has picked the same one then you’ll be OK.

The STM32 MAC allows you to queue up multiple buffers for sending and receiving to cater for short periods when either you’re too busy to handle an incoming frame or the network is too busy to accept a transmitted frame. mac_receiveBufferCount and mac_transmitBufferCount allow you to set the number of these buffers.

Each receive buffer costs you 1524 bytes of SRAM, quite expensive because they each have to be able to cope with a complete 1500 byte ethernet frame. On the other hand, transmit buffers cost you only about 36 bytes of fixed overhead each because we transmit your data in-place and only allocate space for the internet headers for the actual time it takes to transmit the frame.

Although stm32plus::net allows you to specify the number of transmit buffers it’s still possible that if you transmit fast enough and the network conditions are unfavourable then you may fill all the buffers before DMA has had a chance to move them out to the MAC’s FIFO. If this happens then stm32plus::net will wait for a maximum of mac_txWaitMillis milliseconds for a transmit buffer to become free. This wait time does not apply to frames generated by IRQ code, such as ICMP echo replies. IRQ code will not wait and will return an error immediately.

Methods exposed

bool phyReadRegister(uint16_t phyAddress,uint16_t regNumber,uint16_t& value,uint16_t timeoutMillis);
bool phyWriteRegister(uint16_t phyAddress,uint16_t regNumber,uint16_t value,uint16_t timeoutMillis);
uint32_t getDatalinkTransmitHeaderSize() const;
uint32_t getDatalinkMtuSize() const;

phyReadRegister allows you to read a register value from your configured PHY. phyWriteRegister is the opposite for writing to a PHY register.

getDatalinkTransmitHeaderSize gets the number of bytes used by the link layer’s headers. It returns 14.

getDatalinkMtuSize gets the maximum number of bytes that the MAC can send in one frame, including headers and checksums. The value is 1518.

Events raised

The MAC module raises the following events.

sender NetworkNotificationEventSender
event MacAddressAnnouncementEvent
context normal
purpose Announce MAC address on startup
		 * Event descriptor for a client Mac address announcement

		struct MacAddressAnnouncementEvent : NetEventDescriptor {

			 * References to the value

			const MacAddress& macAddress;

			 * Constructor

			MacAddressAnnouncementEvent(const MacAddress& address)
				: NetEventDescriptor(NetEventType::MAC_ADDRESS_ANNOUNCEMENT),
					macAddress(address) {

The MAC raises this event when startup() is called. Some higher-level modules in the stack such as ARP require the MAC address and will subscribe to this event to gain knowledge of it.

sender NetworkReceiveEventSender
event DatalinkFrameEvent
identifier NetEventType::DATALINK_FRAME
context IRQ
purpose An ethernet frame has arrived
		 * Event descriptor for a datalink frame

		struct DatalinkFrameEvent : NetEventDescriptor {

			 * The frame reference

			DatalinkFrame& datalinkFrame;

			 * Constructor
			 * @param frame

			DatalinkFrameEvent(DatalinkFrame& frame)
				: NetEventDescriptor(NetEventType::DATALINK_FRAME),
					datalinkFrame(frame) {

The MAC raises this event to notify higher level code that an ethernet frame has arrived, has been decoded and is ready for processing. Various flags may be set depending on the type of frame and the capabilities of the MAC. See MacBase.cpp for the details.

sender NetworkNotificationEventSender
event DatalinkFrameSentEvent
identifier NetEventType::DATALINK_FRAME_SENT
context IRQ
purpose An ethernet frame has been sent
		 * Event descriptor for a datalink frame-sent event. This event is
		 * sent when a frame has been put on to the wire and the NetBuffer
		 * is about to be released.

		struct DatalinkFrameSentEvent : NetEventDescriptor {

			 * The net buffer reference

			NetBuffer& netBuffer;

			 * Constructor
			 * @param nb The NetBuffer holding the frame that's just gone

			DatalinkFrameSentEvent(NetBuffer& nb)
				: NetEventDescriptor(NetEventType::DATALINK_FRAME_SENT),
					netBuffer(nb) {

This event is raised in response to the MAC transmit interrupt being fired. It gives you a chance to perform any post-transmit work before the NetBuffer that describes the transmitted frame is released.