I searched for information about how the PID controllers are implemented, but I could not find any. Then, I read the ROS controller code and found that they are simple PID controllers. The derivative is implemented as "e[k] - e[k-1] / dt" and the integration is a simple numeric integration as sum(e[k]*dt) (e[k] is the error at time step k). No filter is used for the derivative action. Both the derivative and integral action consider the reference. So, you can approximate the continuous transfer function of the controller as C(s) = Kp + Kd * s + Ki / s, where Kp, Kd, and Ki are the controller gains. The controller output (in the case of an effortController) is torque directly. Therefore, the joint dynamics is going to be simply G(s) = 1 / (J*s + B), where J is the inertia of the body (or the kinematic chain in the case of a complex manipulator) the joint is attached to and B is the "damping" configured in the joint. The closed loop transfer function is given by control theory: Gc(s) = C(s)*G(s) / (1 + C(s)*G(s)).

In an actual electric motor, you cannot actuate torque directly. But you can model the backemf effect by using an equivalent damping. Moreover, you can take into account the voltage saturation by considering an equivalent torque saturation in this case.

I have tested simple Gazebo worlds with one and two joints and the evolution of position and speed of the joint agrees very well with equivalent Simulink models, including the voltage saturation effect.