implementare RS485

Uno dei protocolli fisici più utilizzati in campo industriale è RS485, che permette di creare un bus di comunicazione tra molteplici dispositivi con solo 2 cavi ed è adatto alla comunicazione su lunghe distanze. Nel kernel linux è già implementata la logica del driver per poter gestire periferiche rs485, ma questo non è sufficiente. È necessario un componente che permetta di pilotare la nostra periferica hardware a partire dai comandi ricevuti dal kernel. Facendola breve, possiamo utilizzare una semplice porta UART, ma è necessario che sia disponibile un pin che segnali quando il nostro dispositivo ha intenzione di comunicare.

Il microprocessore stm32mp2xx contiene molte periferiche, ma non dispone direttamente di una rs485. Dal datasheet possiamo vedere che sono presenti 9 UART e che tutte permettono di pilotare un pin di flow control durante la trasmissione.

STM32MP25xC/F devices embed four USART and five UART (see Table 8. USART/UART features for feature
summary).
They provide hardware management of CTS and RTS signals, and RS485 driver enable.
They can communicate at speeds of up to 12 Mbit/s.
All USARTs provide smartcard mode (ISO 7816 compliant) and SPI-like communication capability.
All USARTs can be served by the DMA controller.

Andiamo quindi a vedere come abilitare la nostra scheda per il RS485, che ci permetterà di implementare in seguito protocolli come Modbus o altri.

DTS

Ispezionando i device tree forniti da st

linux/arch/arm64/boot/dts/st/stm32mp257f-dk.dts
 1// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
 2/*
 3 * Copyright (C) STMicroelectronics 2023 - All Rights Reserved
 4 * Author: Alexandre Torgue <[email protected]> for STMicroelectronics.
 5 */
 6
 7/dts-v1/;
 8
 9#include <dt-bindings/gpio/gpio.h>
10#include <dt-bindings/input/input.h>
11#include <dt-bindings/leds/common.h>
12#include <dt-bindings/rtc/rtc-stm32.h>
13#include "stm32mp257.dtsi"
14#include "stm32mp25xf.dtsi"
15#include "stm32mp25-pinctrl.dtsi"
16#include "stm32mp25xxak-pinctrl.dtsi"
17#include "stm32mp257f-dk-resmem.dtsi"
18
19/ {
20	model = "STMicroelectronics STM32MP257F-DK Discovery Board";
21	compatible = "st,stm32mp257f-dk", "st,stm32mp257";
22
23	aliases {
24		serial0 = &usart2;
25		serial1 = &usart6;
26		serial2 = &usart1;
27		ethernet0 = &eth1;
28	};
linux/arch/arm64/boot/dts/st/stm32mp257f-dk.dts
843/* Bluetooth */
844&usart1 {
845	pinctrl-names = "default", "sleep", "idle";
846	pinctrl-0 = <&usart1_pins_a>;
847	pinctrl-1 = <&usart1_sleep_pins_a>;
848	pinctrl-2 = <&usart1_idle_pins_a>;
849	uart-has-rtscts;
850	status = "okay";
851
852	bluetooth {
853		shutdown-gpios = <&gpiog 4 GPIO_ACTIVE_HIGH>;
854		compatible = "brcm,bcm43438-bt";
855		clocks = <&rtc>;
856		clock-names = "lpo";
857		max-speed = <2000000>;
858		vbat-supply = <&scmi_v3v3>;
859		vddio-supply = <&scmi_v3v3>;
860	};
861};
862
863&usart2 {
864	bootph-all;
865	pinctrl-names = "default", "idle", "sleep";
866	pinctrl-0 = <&usart2_pins_a>;
867	pinctrl-1 = <&usart2_idle_pins_a>;
868	pinctrl-2 = <&usart2_sleep_pins_a>;
869	/delete-property/dmas;
870	/delete-property/dma-names;
871	status = "okay";
872};
873
874&usart2_pins_a {
875	bootph-all;
876	pins1 {
877		bootph-all;
878	};
879	pins2 {
880		bootph-all;
881	};
882};
883
884&usart6 {
885	pinctrl-names = "default", "idle", "sleep";
886	pinctrl-0 = <&usart6_pins_a>;
887	pinctrl-1 = <&usart6_idle_pins_a>;
888	pinctrl-2 = <&usart6_sleep_pins_a>;
889	uart-has-rtscts;
890	status = "disabled";
891};

Notiamo che sono già configurate 3 UART.

  • usart1 è dedicata ad una periferica bluetooth
  • usart2 è abilitata già al boot e potrebbe essere la porta seriale di default
  • usart6 è disabilitata e presenta un parametro uart-has-rtscts che suggerisce la presenza di un flow control

Esplorando anche i file dtsi che vengono inclusi nel dts:

linux/arch/arm64/boot/dts/st/stm32mp231.dtsi
1117usart6: serial@40220000 {
1118				compatible = "st,stm32h7-uart";
1119				reg = <0x40220000 0x400>;
1120				interrupts-extended = <&exti1 29 IRQ_TYPE_LEVEL_HIGH>;
1121				clocks = <&rcc CK_KER_USART6>;
1122				dmas = <&hpdma 19 0x20 0x10012>,
1123				       <&hpdma 20 0x20 0x3021>;
1124				dma-names = "rx", "tx";
1125				access-controllers = <&rifsc 36>;
1126				power-domains = <&d1_pd>;
1127				wakeup-source;
1128				status = "disabled";
1129			};
linux/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi
1#include <dt-bindings/pinctrl/stm32-pinfunc.h>
linux/arch/arm64/boot/dts/st/stm32mp25-pinctrl.dtsi
875usart6_pins_a: usart6-0 {
876		pins1 {
877			pinmux = <STM32_PINMUX('F', 13, AF3)>, /* USART6_TX */
878				 <STM32_PINMUX('G', 5, AF3)>;  /* USART6_RTS */
879			bias-disable;
880			drive-push-pull;
881			slew-rate = <0>;
882		};
883		pins2 {
884			pinmux = <STM32_PINMUX('F', 14, AF3)>, /* USART6_RX */
885				 <STM32_PINMUX('F', 15, AF3)>; /* USART6_CTS_NSS */
886			bias-pull-up;
887		};
888	};
889
890	usart6_idle_pins_a: usart6-idle-0 {
891		pins1 {
892			pinmux = <STM32_PINMUX('F', 13, ANALOG)>, /* USART6_TX */
893				 <STM32_PINMUX('F', 15, ANALOG)>; /* USART6_CTS_NSS */
894		};
895		pins2 {
896			pinmux = <STM32_PINMUX('G', 5, AF3)>; /* USART6_RTS */
897			bias-disable;
898			drive-push-pull;
899			slew-rate = <0>;
900		};
901		pins3 {
902			pinmux = <STM32_PINMUX('F', 14, AF3)>; /* USART6_RX */
903			bias-pull-up;
904		};
905	};
906
907	usart6_sleep_pins_a: usart6-sleep-0 {
908		pins {
909			pinmux = <STM32_PINMUX('F', 13, ANALOG)>, /* USART6_TX */
910				 <STM32_PINMUX('G', 5, ANALOG)>,  /* USART6_RTS */
911				 <STM32_PINMUX('F', 15, ANALOG)>, /* USART6_CTS_NSS */
912				 <STM32_PINMUX('F', 14, ANALOG)>; /* USART6_RX */
913		};
914	};

Per capire come i parametri presenti nel device-tree vengono interpretati dobbiamo analizzare il campo compatible, in quanto il suo valore ci indica quale driver sarà associato.

linux/arch/arm64/boot/dts/st/stm32mp231.dtsi
1118compatible = "st,stm32h7-uart";

E cercando la stringa st,stm32h7-uart nella cartella drivers otteniamo:

terminal
0grep -r "st,stm32h7-uart" drivers
terminal
0drivers/tty/serial/stm32-usart.c

E all’interno di stm32-usart.c vediamo che il codice si occupa di configurare struct serial_rs485, che è il compito richiesto ad un driver rs485. Quindi questo è il kernel driver che ci interessa e corrisponde alle seguenti stringhe compatible:

linux/drivers/tty/serial/stm32-usart.c
2017#ifdef CONFIG_OF
2018static const struct of_device_id stm32_match[] = {
2019	{ .compatible = "st,stm32-uart", .data = &stm32f4_info},
2020	{ .compatible = "st,stm32f7-uart", .data = &stm32f7_info},
2021	{ .compatible = "st,stm32h7-uart", .data = &stm32h7_info},
2022	{},
2023};
2024
2025MODULE_DEVICE_TABLE(of, stm32_match);
2026#endif

Per

rs485

work in progress