I want understand the code about the interactions between instances of the Servo and PWM classes of robot_hat with picarx

I observed the traces when instantiating the Picarx class.
To do this, I inserted “print” statements in a specific format.
The logs are formatted as follows:

" {format } ::= {Num_Line} {key} {level} {method name} { data} "

{Num_Line} ::= num line off log, not num line off code Python
{key} ::= “code” for using the Linux ‘grep’ function
{level} ::= The length of the underlines depends on the depth of the method calls
{method name} ::= PWM, I2C, ARR, PSC, SRVO, PICAR, etc
{data} ::= Information Sought

Example

image

By following the Python code for creating an instance of the Picarx class, the logs let you see what’s happening.
I’m interested in the part where the servo motors are declared (not the motors themselves, not the ultrasonic part, etc.)

Let’s start by instantiating the Picarx class.

image

  • Line 0 to 1: Instantiation of the Picarx class for the “P0” servo motor
  • Lines 2–3: Instantiation of the Servo class for the “P0” servo motor, which will instantiate the PWM class for the “P0” servo, which in turn will call its freq() method

  • Line 4 : Creation of the “timer” class variable
  • Lines 5 and … : Determining the address of the I2C port on bus 1 → 0x14 found

  • Lines 6 to 10 Call to the method self.freq( 50 )
    Calculation of the prescaler and arr values
    for the frequency self._freq == 50 Hz
    The freq() method calls the prescaler() method, i.e., PSC

  • Lines 11 to 13 Call (PCS) the method self.prescaler( 1200 )
  • Lines 14 to 16 For canal “P0” timer_index === 0
    Therefore, timer[0][arr] === 1
    This will result in a rather surprising calculation for
    the variable self._freq
  • Line 17 Frequency calculation: self._freq == 60000 Hz

That makes sense, since timer[0][arr] should be equal to 1200 as
calculated previously, not one

  • Line 18 … Storing the value of (prescaler - 1) = (1200 - 1) == 1199 == 0x4AF in memory location 0x40
  • Line 19 to 20 End of methode prescaler()

I have more data to provide, but I’m stuck because the editor won’t accept more than 5 documents linked to the text.

21 code_______FREQ:
22 code___________ARR
23 code___________ARR: -------- self.periode(arr=1200)
24 code___________ARR: timer_index=0
25 code___________ARR: timer = [{‘arr’: 1200}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}]
26 code___________ARR: timer[self.timer_index][arr] = 1200
27 code_______I2CW: _i2c_write(reg=0x44, value=0x4b0)
code___________I2C-write( self, data=[68, 4, 176]==[0x44, 0x4, 0xb0, ]
28 code___________ARR: -------- self.periode( ) OUT
29 code___________ARR
30 code_______FREQ: -------- self.freq( ) OUT
31 code_______FREQ
32 code____PWM: ---------- pwm.init OUT ( )
33 code____PWM

  • Line 21 Return to the freq() method and call the period() method (ARR)
  • Line 22 to 23 The calculations will be based on the value arr=1200
  • Lines 24 to 26 For canal “P0” timer_index === 0
    Therefore, timer[0][arr] === 1200
    This time, this value is consistent.
  • Lines 27 … Then writes this value to memory address 0x44
    This method does not modify the variable self._freq
  • Lines 28 to 31 Return to the freq() method, and end of the freq() method
  • Lines 32 to 32 End of the PWM class instantiation for channel “P0”

*At this point in the code, we return to the “init” method of the Servo class. The period(4095) method will be called.
*

34 code_SRVO: call self.period(self.PERIOD = 4095)
35 code___________ARR
36 code___________ARR: -------- self.periode(arr=4095)
37 code___________ARR: timer_index=0
38 code___________ARR: timer = [{‘arr’: 4095}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}]
39 code___________ARR: timer[self.timer_index][arr] = 4095
40 code_______I2CW: _i2c_write(reg=0x44, value=0xfff)
code___________I2C-write( self, data=[68, 15, 255]==[0x44, 0xf, 0xff, ]
41 code___________ARR: -------- self.periode( ) OUT
42 code___________ARR

  • Lines 34 to 36 Call the “periode” method (ARR), which will update the “timer” variable of the PWM class.
  • Lines 37 to 39 For canal “P0” timer_index === 0
    Therefore, timer[0][arr] === 4095
    This time, this value is consistent.
    But all the calculations made during the instantiation phase of the PWM class will be discarded, as we will see.Surprising, isn’t it?
  • Lines 40 … Then writes this value to memory address 0x44
    This method does not modify the variable self._freq
  • Lines 41 to 42 Return to the “init” method of the Servo class

43 code_SRVO: self.ClOCK=72000000.0, self.FREQ=50, self.PERIOD=4095
44 code_SRVO: call self.prescaler(prescaler = 351.64835164835165)
45 code___________PSC
46 code___________PSC: -------- self.prescaler( prescaler=351.64835164835165 )
47 code___________PSC: self._prescaler = 352
48 code___________PSC: timer_index=0
49 code___________PSC: timer = [{‘arr’: 4095}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}, {‘arr’: 1}]
50 code___________PSC: timer[self.timer_index][arr] = 4095
51 code___________PSC: self._freq = 49.95004995004995 <?<?<?<?<?<?<?<?<?<?<?<? != 50
52 code_______I2CW: _i2c_write(reg=0x40, value=0x15f)
code___________I2C-write( self, data=[64, 1, 95]==[0x40, 0x1, 0x5f, ]
53 code___________PSC: -------- self.prescaler( ) OUT
54 code___________PSC
55 code_SRVO:----------Servo.init() OUT
56 code_SRVO
57 code_____PICAR: initialisation SERVO P1

  • Lines 43 to 47 Using the three constants
    self.ClOCK=72000000
    self.FREQ=50,
    self.PERIOD=4095
    The prescaler calculation gives → prescaler = 351.64835164835165
  • Lines 48 to 50 For canal “P0” timer_index === 0
    Therefore, timer[0][arr] === 4095
    This time, this value is consistent.
  • Line 51 Calculation of the value of the variable “_freq” = 49.95…Hz.
    This value does not match 50 Hz, but that is normal since the value
    of the variable “_prescaler” has been rounded.
    Everything is consistent.
  • Lines 52 … Storing the value of (prescaler - 1) = (352 - 1) == 351 == 0x15F in memory location 0x40
  • Lines 53 to 56 The Servo class has been instantiated for channel “P0”
  • Line 57 The instantiation of the Picarx class continues by instantiating the Servo class for the “P1” channel.
    For “P1” chanel, The process will be the same as the previous one.
    There will be a new instance of the PWM class for “P1”, and everything
    that was surprising will once again be just as surprising to me.

In summary:

  • Why does instantiating the PWM class perform calculations that will be ignored, and which, moreover, introduce an error because timer[self.timer_index][arr] has not been initialised before the calculations of self._freq 50 Hz requested, 60KHz calculated.
  • Why does the instantiation of the PWM class call the freq() method, rather than simply determining the active port on the I2C bus?
    Leaving it up to the Servo or Motor class instance to choose the optimal calculation methods:
    i.e self.period( ) or self.prescaler( ) or self.freq()

We reviewed the initialization flow you described. Your observation is correct: PWM.init() calls freq(50), which temporarily calculates _freq using the initial ARR value before period() updates it. The value is recalculated afterward, and Servo.init() subsequently replaces the PWM timer configuration with PERIOD = 4095 and the corresponding prescaler. Therefore, the temporary 60000 Hz value is not the final servo PWM frequency.

However, your point about the redundant initialization and class design is valid, and we will review whether this initialization sequence can be improved.

Thanks for your reply.
I’m going to keep exploring the code for this little robot. I’ve learned a lot about how to use servo motors.
I’m 72 years old, and I realize that today’s teenagers are incredibly lucky to have access to such an extraordinary “toy” for learning the basics of electronics, microcomputing, programming, and mechanics.
For example, I saw that you programmed an “electric differential” for the rear axle to simulate a mechanical differential, based on the orientation of the front wheels. Great idea.
Alright! I’m going back to it!

Thank you very much for your kind words — you are remarkable too!

Diving into the code at 72 and following it all the way down to the interaction between the Servo and PWM classes is truly admirable, and your curiosity really inspires us. You are right that today’s teenagers are lucky to have such an extraordinary tool for learning the basics of electronics, microcomputing, programming, and mechanics — and someone who keeps that same enthusiasm for learning at any age is just as inspiring to us.

We are glad this little robot can keep you company on your journey of exploration, and we look forward to you discovering many more interesting details. Have fun!

Hello!
A quick question about the period value (arr) 4095 = (1 << 12) - 1.

Is there an electronic component that processes the PWM signal to adapt it to the servomotor? This component would have a 12-digit size.

Why this choice?

Servo motors have 180 positions (or maybe 200). I thought that period (arr) would be a multiple of 180 (or 200) and that the CCR would advance in 1-tick increments.

There’s something here I don’t understand.

Thank you very much for continuing to dig into the code of this little robot — your question is a very good one, and it is indeed something many people wonder about when reading the PWM and servo code. Let me go through your three points one by one.

Is there an electronic component that processes the PWM signal to adapt it to the servomotor, with a 12-bit size?
No. The PWM signal on the Robot HAT is generated directly by the timer of the onboard MCU and output on the servo signal line as a 3.3V logic level; there is no extra “12-bit” processing chip in between. The servo has its own internal control circuit and only cares about the high-level pulse width on the signal line (roughly 0.5 ms to 2.5 ms), not about the exact ARR value of the timer. In other words, ARR = 4095 is just the counting resolution of the MCU timer and has nothing to do with the servo’s internal electronics.

Why this choice, ARR = 4095 = (1 << 12) - 1?
Because the timer counts with 12-bit resolution (0 to 4095). At 50 Hz (20 ms period), 20 ms / 4096 ≈ 4.88 µs per step, which corresponds to about 180° / 4096 ≈ 0.044° of servo angle — far finer than the mechanical precision of the servo itself. The 12-bit choice is there to give a sufficiently fine pulse-width adjustment at 50 Hz, not to match the “number of positions” of the servo.

Servo motors have 180 (or maybe 200) positions — why isn’t ARR a multiple of 180 (or 200), with CCR advancing in 1-tick increments per position?
The servo angle is a linear function of the pulse width (e.g. 0.5 ms → 0°, 2.5 ms → 180°), not a 1:1 mapping to the counter value. In the code, the mapping is done by an “angle → pulse width (CCR)” conversion function; each CCR tick is about 4.88 µs, i.e. about 0.044°, so in practice the angular resolution is much better than 1°. That is why there is no need to make ARR a multiple of 180 or 200.

If you would like to check the exact conversion formula, I suggest looking directly at the angle-to-pulse-width part of the Servo/PWM classes in the robot_hat library; the exact numbers may differ slightly between library versions, so please refer to the source of the library installed on your own system.

Thank you again for your curiosity and your spirit of exploration — please feel free to keep in touch!

Thank you very much for all the information.
Your email is a sort of PWM signal; it gave me a nudge that helped me get back on track with reliable information :wink:
Actually, what made me think of a component with a 12-bit register was that SunFounder sells a PCA9685 16-Channel 12-Bit PWM Servo Driver, and for the sake of code portability, the value of 4095 had been chosen for this component, knowing that the AT32 would be able to adapt. That’s one of the reasons for my queries.
Thank you again for your patience with me.

Translated with DeepL.com (free version)