-
Notifications
You must be signed in to change notification settings - Fork 0
Ethernet
This section is based upon this document written by Pablo, which explains how to make an Ethernet project from scratch.
Ethernet needs the macro DATA_IN_D2_SRAM both in C and C++ in order for it to work
To add this macro in STM32cubeIDE:
.......................................................
The linker script defines the layout of the memory inside the microcontroller, and we need an space inside the Flash in order for Ethernet to work. For that, we will add to the linker script STM32H723ZGTX_FLASH.id these lines:
/* ETH_CODE: add placement of DMA descriptors, rest is used by RX_POOL */
.lwip_sec (NOLOAD) :
{
. = ABSOLUTE(0x30000000);
*(.RxDecripSection)
. = ABSOLUTE(0x30000100);
*(.TxDecripSection)
} >RAM_D2
There are some weird cases when the structure netif doesn t complete its configuration correctly at the start of the board, which leads into Ethernet not working at all. In such cases, it is better to just restart the board completely. This line of code will make it happen automatically.
On lwip.c, inside the MX_LWIP_INIT, on the USER CODE 3 space, add the next lines:
/* USER CODE BEGIN 3 */
if(!netif_is_link_up(&gnetif)){
HAL_NVIC_SystemReset();
}
/* USER CODE END 3 */
There is a bug on LWIP that changes the flag that defines the state of the base data structure used for all the interfaces. If a long enough time passes without any activity on any interface, the netif changes the flag to a 15, which isn t a defined state for the flag. This workaround will reset the data structure if that were to happen.
On lwip.c, inside the MX_LWIP_PROCESS method, on the USER CODE 4_3 space, add the next lines:
/* USER CODE BEGIN 4_3 */
if(gnetif.flags == 15){
netif_set_up(&gnetif);
}
/* USER CODE END 4_3 */
On LWIP/Target/ethernetif.c, on the low_level_output function, add these lines just before the return errval:
HAL_ETH_Transmit(&heth, &TxConfig, 0);```
This is a missing piece of code that is in every stm32 family but on the H7, and is known to give a bunch of problems among the community. The function should look like this after changing it:
```static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
uint32_t i = 0U;
struct pbuf *q = NULL;
err_t errval = ERR_OK;
ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};
memset(Txbuffer, 0 , ETH_TX_DESC_CNT*sizeof(ETH_BufferTypeDef));
for(q = p; q != NULL; q = q->next)
{
if(i >= ETH_TX_DESC_CNT)
return ERR_IF;
Txbuffer[i].buffer = q->payload;
Txbuffer[i].len = q->len;
if(i>0)
{
Txbuffer[i-1].next = &Txbuffer[i];
}
if(q->next == NULL)
{
Txbuffer[i].next = NULL;
}
i++;
}
TxConfig.Length = p->tot_len;
TxConfig.TxBuffer = Txbuffer;
TxConfig.pData = p;
SCB_CleanInvalidateDCache();
HAL_ETH_Transmit(&heth, &TxConfig, 0);
return errval;
}
Same as Ill-formed interface struct start, there are a few specific cases where the PHY interrupt for the Ethernet isn t correctly configured on hardware. This leads into an error on each access to the PHYRegister, and is the only known case for an error on the PHYRegister access to happen at all. The solution is, again, just reset the board entirely when this happens.
On LWIP/Target/ethernetif.c, modify ETH_PHY_IO_ReadReg:
int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal)
{
if(HAL_ETH_ReadPHYRegister(&heth, DevAddr, RegAddr, pRegVal) != HAL_OK)
{
HAL_NVIC_SystemReset();
return -1;
}
return 0;
}
and ETH_PHY_IO_WriteReg:
int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal)
{
if(HAL_ETH_WritePHYRegister(&heth, DevAddr, RegAddr, RegVal) != HAL_OK)
{
HAL_NVIC_SystemReset();
return -1;
}
return 0;
}
There are multiple reasons why an user may need to modify the network configuration of the template project. If the network has a net interface other than 192.168.0.x, if you are planning on using more than one stm32 on the same network, or you are interested in changing the mac for any reason, you will need to configurate properly these lwip parameters:
ST-LIB doesn t have the utility to use dynamic ips, so you have to assign one manually that is available in the network in which the board will be connected. ST-LIB will resolve DHCP on itself, so you only need to change the array declared in template-project/LWIP/App/lwip.c on MX_LWIP_INIT (lines 63 to 66).
Below, an example to configurate the board ip address to 70.77.37.4:
void MX_LWIP_Init(void)
{
/* IP addresses initialization */
IP_ADDRESS[0] = 70;
IP_ADDRESS[1] = 77;
IP_ADDRESS[2] = 37;
IP_ADDRESS[3] = 4;
...
The network mask will indicate the board (and any other device connected on the network) which ip's can be directly accessed without comunicating out of the network. This mask is dependant on the network that the board is connected and is equal for all of the connected devices inside the network.
Most common networks use 255.255.0.0 and 255.255.255.0 masks. The configuration is made on template-project/LWIP/App/lwip.c on MX_LWIP_INIT, just below of the ip address configuration (lines 67-70), and works the same as the ip address configuration (from right to left on the matrix)
If the board needs to comunicate with a device that is not available in the network interface (defined by the network mask), the board will send the message meant for that device to the gateaway address (commonly the router or switch), wrapped with the target's ip. If the board is not meant to comunicate with a device out of the network interface, this configuration is not needed.
The configuration is made on template-project/LWIP/App/lwip.c on MX_LWIP_INIT, on lines 71-74. Works the same as the other configurations in this file.
To resolve the DHCP and ARP comunication, the board needs a 'physical' address that is unique to itself. It can be any address as long as no other device in the network has it, as there is no protocol to resolve MAC address conflicts. The most common practice is to change the last numbers to the values at the end of the board's ip address (the first 3 numbers will make sure that the MAC address cannot conflict with any devices but stm boards), but any array of numbers not used on the network will do.
The configuration is made on template-project/LWIP/Target/ethernetif.c on low_level_init, on lines 195-200. Following the standard procedure, for the ip 70.77.37.4 and mask 255.255.0.0, we would change the last two numbers of the MAC to 37 and 4:
static void low_level_init(struct netif *netif)
{
HAL_StatusTypeDef hal_eth_init_status = HAL_OK;
/* Start ETH HAL Init */
uint8_t MACAddr[6] ;
heth.Instance = ETH;
MACAddr[0] = 0x00;
MACAddr[1] = 0x80;
MACAddr[2] = 0xE1;
MACAddr[3] = 0x00;
MACAddr[4] = 37;
MACAddr[5] = 4;
When code is generated on the stm32cubeIDE, the changes into ethernetif.c are removed (as they are not inside USER CODE), so they need to be added again. This affects only the bugs Cache clean on ethernetif and Ill-formed PHY configuration.
Additionally, the configuration for the mac, ip, interface and gateaway are also reset to the ones configurated in the .ioc, and as such, may need to be changed again.
All ethernet utilities and protocols need the basics of ethernet working, which are handled by the ST-LIB. The first thing needed to get started is to give the ip to the ST-LIB::start(). This function has a base ip so the user doesn t need to bother with Ethernet if he doesn t want to use it.
Yet, if you want to use Ethernet and you are using a board ip different from 192.168.0.3, you will need to provide your ip on the ST-LIB::start() and on each class that uses Ethernet comunication. While LWIP receives the ip on an array format, ST-LIB uses a string format. An example of the ST-LIB::start() for ip 70.77.37.4 would be ST-LIB::start("70.77.37.4").
When ST-LIB::start() is run, it will wait until a proper ethernet connection is made, so he can establish a working interface and assure that comunication can be held. The ST-LIB assumes that no code has to be run until these are made, and as such, not having an ethernet cable connected will make the board halt. If a user wants to make a code that works without an ethernet cable, he will need to comment the Ethernet lines inside HALAL::start() (which is itself inside ST-LIB::start())
After a proper ethernet interface is established, ST-LIB::update() needs to be run on a loop to make housekeeping tasks, process messages, and keep connections alive. LWIP has buffers for each of these tasks, and as such, there is not an strict time constraint to be met. Because of that, common practice is to put ST-LIB::update() on the main loop.
IPV4 is the class that handles all the ips in the ST-LIB. IPV4 can receive a string or an ip_addr_t (lwip variable used for ips) on its constructor, and after being built it will hold both values, making them interchangeable.
All classes on the ST-LIB that interact with the Ethernet interface have at least one IPV4, which will hold the string value for debugging and the ip_addr_t value to handle the LWIP code.
The Packet class and its children are quite complex on the way they work, but simple to use. Because of that, we will explain only the latter here, and the inner workings will have its own page.
The Packet, Order, StackPacket and HeapPacket classes themselves are virtual classes created as a base structure for the instantiable classes. The StackOrder and HeapOrder are the usable classes with a constructor intended to define them and functions to be called by the Ethernet protocol classes, which we will refer now on as Orders or Order instance.
The user itself doesn t need to use any of the Orders functions, and should instead use the functions of the communication classes, such as DatagramSocket or ServerSocket. The only thing needed by the user to set a working Order instance is to call the constructor. The Orders themselves are templates defined by the size of its payload, the values held by it, and an optional callback executed each time the packet is received. Given an example where a packet holds three ints, its template will be <12,int,int,int>. In most cases the template resolves itself.
The constructor of these classes starts with a uint16_t value known as the packet_id. This value, added to the payload as the first two bytes, will differenciate one packet type from the others, so they can be casted properly when received. This means that a Order instance has to be defined both on the sending and receiving end.
After the packet_id, the constructor receives the pointers to the values to be sent (or received); and there is an option to put in the constructor a function to be executed each time a packet with the proper packet_id is received. After being built, the Ethernet classes will handle the packets received with a registered id, and the user will only need them to use the Order instances as a parameter for the send_order functions for the ethernet communication protocol classes.
Below an example of how to declare a StackOrder with id 100 that receives a number and executes the useless function. It has to be declared after ST-LIB::start():
uint32_t useless_count = 0;
uint32_t received_number = 0;
void useless(){
received_useless++;
}
int main(){
ST-LIB::start("70.77.37.4");
StackOrder ping(100,&received_number,useless);
while(1){
ST-LIB::update();
}
}
This code wouldn t be able to receive the Order as there is no communication protocol declared on any port, so the board doesn t listen to any incoming message. But it is an example of how to declare an Order to be received and processed by the board.
UDP comunication is quite simple, you create a DatagramSocket which will receive all defined packets sent to the board by a given ip, and a send function that receives a packet and sends it to the ip of the DatagramSocket. It is the fastest, simplest, and most clear-cut way of communication.
But it has its flaws. As UDP doesn t use the concept of connection to work, there is no way to know if a message is received by the other end, or if there is even an end to receive the messages to begin with. Furthermore, it doesn t have a protocol to recover lost messages, so there is a very small, non-zero chance for a packet to be lost even if both ends of the Socket are up and working. It is considered the less safe communication protocol because of that, and has to be used having in account that the message may not reach the other end.
Below, an example of a DatagramSocket, connecting our board (70.77.37.4) to a device on the ip 70.77.37.9, using the port 10000:
ST-LIB::start("70.77.37.4")
StackOrder hello(100);
DatagramSocket connection("70.77.37.4",10000,"70.77.37.9",10000);
while(1){
ST-LIB::update();
}
TCP communication is the opposite of the UDP. Its slower, uses a server - client structure, and needs set-up and housekeeping to work properly. Its advantages are:
- you know if the other end is available for communication
- you are sure that the sent packet will be received by the other end on a given timeframe (if the other end is still connected)
- you can better control when you want to receive packets.
- you can debug better the packets, as they hold the time when they were given inside of them, among other useful information
For a TCP connection, there is a client side and a server side, the first one connects to the second, and the server can only wait for clients to connect, and not establish a connection to an ip by itself. Below, an example of 70.77.37.4 client connecting to 70.77.37.9 server, sending a hello order each second:
ST-LIB::start("70.77.37.4")
StackOrder hello(100);
Socket client("70.77.37.4",10000,"70.77.37.9",10000);
Time::register_low_precision_alarm(1000, [&](){
if(client.is_connected()){
client.send_order(hello);
}
});
while(1){
ST-LIB::update();
}
and the server side, which sends a hello order each second:
ST-LIB::start("70.77.37.9");
StackOrder hello(100);
ServerSocket server("70.77.37.9",10000);
Time::register_low_precision_alarm(1000, [&](){
if(server.is_connected()){
server.send_order(hello);
}
});
while(1){
ST-LIB::update();
}
ServerSocket is an end to end connection, that accepts only one client. If the user needs more than one client on the same ip and port, he will need to use the Server class, which works exactly as ServerSocket but handles multiple connections, and has additional functions.
