====编码器DTS探究====
背景:ST的PLC参考设计板对应的设备树源码位于:https://github.com/peyoot/ccmp25_dt/blob/scarthgap/ccmp25-plc.dts ,编码器设备树的部分只是按早期官方文档的一个示例修改,需要进一步修改并验证其可用性。
官方文档可能有更新,LPTIM见:https://wiki.st.com/stm32mpu/wiki/LPTIM_device_tree_configuration#On_STM32MP2_series ,而
TIM DEVICE见https://wiki.st.com/stm32mpu/wiki/TIM_device_tree_configuration#On_STM32MP2_series
当前的设计,以counter_DI_1_2为例
&timers1 {
status = "okay";
/* USER CODE BEGIN timers1 */
/delete-property/dmas;
/delete-property/dma-names;
/* USER CODE END timers1 */
counter_DI_1_2 { /* remove pwm10,pwm11 and add this */
pinctrl-names = "default", "sleep";
pinctrl-0 = <&ccmp25_timers1_ch3_4_pins>;
pinctrl-1 = <&ccmp25_timers1_ch3_4_sleep_pins>;
status = "okay";
};
和
ccmp25_timers1_ch3_4_pins: ccmp25-timers1-ch3_4 {
pins {
pinmux = , /* TIM1_CH3_PWM */
; /* TIM1_CH4_PWM */
bias-disable;
};
};
ccmp25_timers1_ch3_4_sleep_pins: ccmp25-timers1-ch3_4_sleep {
pins {
pinmux = , /* TIM1_CH3_PWM */
; /* TIM1_CH4_PWM */
};
};
注意,上面这个配置并未验证,在AI的协助下,需要的知识点包括:
timers1: timer@address {
compatible = "st,stm32mp25-timers";
/* ...其他公共资源... */
pwm { compatible = "st,stm32mp25-pwm"; }; // PWM功能
timer@0 { compatible = "st,stm32mp25-timer-trigger"; }; // 触发功能
counter { compatible = "st,stm32mp25-timer-counter"; }; // 编码器/计数器功能
};
// counter子节点专门用于编码器/计数器功能,stm32mp25-timer-counter是STM32MP25系列的新绑定方式,而非以前的stm32-timer-encoder
下面,以这个编码器节点为例,可能需要的改变包括:
1、增加在counter内的compatible说明;
2、counter内增加一些描述
// 编码器模式参数
st,counter-mode = "encoder"; // 设置为编码器模式
st,encoder-mode = <3>; // 模式3:双通道边沿计数(四倍频),
st,prescaler = <0>; // 无预分频
st,filter = <0>; // 输入滤波器关闭,这句可先不用,信号抖动可增加滤波器,最大滤波(需根据信号频率调整),如st,filter = <15>
// 通道映射(CH3和CH4)
st,channel-config { //当方向相反时,调整st,encoder-mode或交换CH3/CH4的引脚
ch3-mode = "input"; // CH3作为编码器输入A
ch4-mode = "input"; // CH4作为编码器输入B
而pinctrl处也要相应修改一些内容,注意AF8改为AF2,待查文档确认,并在节点名别名处添加encoder以增加识别度,偏置也改为上拉等。
AF可以通过下面查:Alternate function mapping
https://wiki.st.com/stm32mpu/wiki/GPIO_internal_peripheral
ccmp25_timers1_ch3_4_pins: ccmp25-timers1-ch3_4-encoder {
pins {
// PD9: TIM1_CH3, PD8: TIM1_CH4
pinmux = , // CH3复用AF2
; // CH4复用AF2
bias-pull-up; // 根据编码器类型选择上拉
drive-push-pull; // 推挽输出驱动
slew-rate = <1>; // 高速模式(>50MHz信号)
};
};
ccmp25_timers1_ch3_4_sleep_pins: ccmp25-timers1-ch3_4_sleep-encoder {
pins {
pinmux = , // 睡眠时设为模拟
;
};
};
下面讲解如何确认I/O pin alternate-function multiplexer and mapping,在处理器的文档RM0457 Rev 5中,page1368开始为GPIO相关文档,page1370处说到:
Each I/O pin has a multiplexer with up to 16 alternate function inputs (AF0 to AF15), that can
be configured through GPIOx_AFRL (for pin 0 to 7) and GPIOx_AFRH (for pin 8 to 15):
对应到这里引脚为8,9,则是看GPIOx_AFRH,在page1385处为GPIOx_AFRL,在page1386处为GPIOx_AFRH。
***************疑点************
在文档的timers中,即64,65章节,pins and internal signals表格里描述到:
{{:digi:arm-embedded:ccmp2:pasted:20250331-164714.png}}
即
Each channel can be used for capture,
compare or PWM.
TIM_CH1 and TIM_CH2 can also be used
as external clock (below 1/4 of the
tim_ker_ck clock), external trigger and
quadrature encoder inputs.
TIM_CH1, TIM_CH2 and TIM_CH3 can be
used to interface with digital hall effect
sensors
也就是,定时器如果是作为capture,compare或PWM,随便用一个通道即可,但如果要用作quadrature encoder inputs,则需要用CH1和CH2, 而PLC开发板的设计文件中,大部是用CH3和CH4,需原厂确认是否有问题
假定CH3,CH4也可用作编码器接口,我目录改了一下:
/* encoder interface for DI1 and DI2 */
counter_DI_1_2 {
compatible = "st,stm32mp25-timer-counter";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&ccmp25_timers1_ch3_4_pins>;
pinctrl-1 = <&ccmp25_timers1_ch3_4_sleep_pins>;
status = "okay";
st,counter-mode = "encoder";
st,encoder-mode = <3>;
st,prescaler = <0>;
st,channel-config {
ch3-mode = "input";
ch4-mode = "input";
};
};
ccmp25_timers1_ch3_4_pins: ccmp25-timers1-ch3_4-encoder {
pins {
pinmux = , /* TIM1_CH3_PWM */
; /* TIM1_CH4_PWM */
bias-pull-up;
drive-push-pull;
slew-rate = <1>;
};
};
ccmp25_timers1_ch3_4_sleep_pins: ccmp25-timers1-ch3_4_sleep-encoder {
pins {
pinmux = , /* TIM1_CH3_PWM */
; /* TIM1_CH4_PWM */
};
};
用户空间的验证如下:
# 启用计数器
echo 1 > /sys/bus/counter/devices/counter0/enable
# 读取当前计数值
cat /sys/bus/counter/devices/counter0/count
# 旋转编码器后,观察数值变化
-----
进阶:
使用cubeMX验证,发现:TIM1里可以选择conbined channels的方式是encoder mode,并且它用了:PD11,PE12,即TIM1_CH1,TIM1_CH2 ,
{{:digi:arm-embedded:ccmp2:pasted:20250331-201456.png}}
如果不用encoder mode,则每个通道可选为input capture direct mode, 也许这才是原先想要的方式:
{{:digi:arm-embedded:ccmp2:pasted:20250331-201625.png}}
补充:用cubeMX生成的设备树发现,和smartIOMUX一样,是用AF8,因此,如果用ch3,ch4只能用input capture direct mode来操作,
根据AI给的说法:虽然也可以把将CH3和CH4配置为输入捕获模式,在中断或轮询中读取捕获值,计算脉冲数和方向,或是通过软件逻辑模拟正交编码器的计数和方向判断,但这样实时性较差,占用CPU资源,适合低速场景。
因此能用上专用的encoder的模式肯定效率高一些
一个完整的电机控制的设备树的例子参考如下,可根据板子情况参考:
/ {
// 配置时钟(确保定时器时钟源正确)
clocks {
// 假设TIM1使用APB2时钟(需根据实际时钟树配置)
tim1_clk: tim1-clk {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <16000000>; // 16 MHz(示例)
};
};
// PWM控制器配置(TIM1通道1用于PWM输出)
pwm_motor: pwm-motor {
compatible = "st,stm32-pwm";
#pwm-cells = <3>;
reg = <0x40010000 0x400>; // TIM1寄存器基地址
clocks = <&tim1_clk>;
status = "okay";
pwm@0 {
// 通道1(TIM1_CH1)作为PWM输出
reg = <0>;
st,pwm-channels = <1>; // 使用通道1
st,pwm-period-ns = <50000>; // PWM周期50us(20kHz)
};
};
// 编码器接口(TIM3通道1和2用于正交编码器)
encoder_motor: encoder-motor {
compatible = "st,stm32-timer-encoder";
reg = <0x40000400 0x400>; // TIM3寄存器基地址
st,encoder-mode = <1>; // 编码器模式:TI1和TI2均捕获边沿
st,capture-channels = <1 2>; // 使用通道1和2(TIM3_CH1和TIM3_CH2)
clocks = <&rcc TIM3_K>;
status = "okay";
};
// 霍尔传感器GPIO中断(可选,用于无刷电机换相)
hall_sensor {
compatible = "gpio-keys";
hall-u {
label = "HALL_U";
gpios = <&gpioa 0 GPIO_ACTIVE_HIGH>; // PA0为霍尔U相信号
linux,code = <0x100>; // 自定义输入事件
};
hall-v { /* 类似配置 */ };
hall-w { /* 类似配置 */ };
};
// ADC配置(用于电流采样,可选)
adc_motor: adc-motor {
compatible = "st,stm32-adc";
reg = <0x48003000 0x400>; // ADC1基地址
vref-supply = <&vrefbuf>;
st,adc-channels = <5 6>; // 使用ADC1_IN5和IN6(电流检测)
status = "okay";
};
};