a day ago •
So at Roboys we have been using custom motorboards from the very beginning. The problem with those has always been the communication. We have a lot of motors to control in our humanoid. The influence of EMI is huge and SPI at high frequencies was a bad choice. The past is the past, and we need to move on. For our new motorboards we wanted something more reliable but also simple to connect. The simplest bi-directional communication setup is probably UART. You may think, wait but UART sucks in terms of baudrate. Well you are correct in the realms of micro controllers. If you take fpgas however, you are free to crank up the communication speed to very high frequencies. The only limiting factor is the wiring between your nodes. When it comes to EMI your best options are:
- shield your wires
- use differential signals
We decided on the LTC2855 from Linear Technology, because of its max throughput (20Mb/s) and the price. This chip integrates a driver and a receiver. It translates any outgoing signals to differential signals and any incoming differential signals are filtered to a single wire. Now, on long wires and high frequencies, you must often deal with unwanted echos of your signals from unterminated wire ends. This will pollute your transmission and you should always add termination resistors to compensate for this. The LTC2855 can add these resistors for you internally, all you need to do is pull one of its pins high for this.
The following illustrates all components of rev0.5:
For the UART communication we decided to write our own protocol, because it's fun. So here it is (oh BTW, this is totally preliminary and very likely to change in the future):
We call this the iCEbus (you get it?)
So the central node is our beloved de10-nano-soc, which integrates a dual arm core with 100k LEs FPGA. It's a dev-board from altera at about 110euro. We designed a shield for it featuring 8 LTC2855 to communicate with up to 8 motorboards per iCEbus:
So now I would like to talk a bit more about the iCEbus protocol. It works by a header mechanism in combination with a CRC16 checksum. All motorboards listen on the data transmitted from the de10. Each one shifts in bytes received with their UART modules (adapted from opencores). If a certain header is detected, the following data is received and after having received a certain amount of bytes (depending what header was received), the CRC16 checksum over the received data is evaluated and compared to the target CRC16 checksum (transmitted by the de10). If these sums match, and only then, the command will be processed. So far we implemented four message types:
- setpoint: header 0xBIGBOOBS (4 byte) motorID (1 byte) setpoint (4 byte) crc16 (2 byte)
- control mode: header 0xBAADAA55 (4 byte) motorID (1 byte) control mode (1 byte) crc16 (2 byte)
- status request: header 0xDABBAD00 (4 byte) motorID (1 byte) crc16 (2 byte)
- status response: header 0x1CEB00DA (4 byte) motorID (1 byte) position (4 byte) velocity (4 byte) displacement (4 byte) current (2 byte) crc16 (2 byte)
So the first two messages obviously control the setpoint and control mode of each motorboard. The third message requests a status update from the specified motorboard and the status response is the respective answer from the motorboard, obviously.
We were able to crank up the communication speed to 2MHz without any problems. This unlocks communication with each motorboard on an iCEbus with 500Hz (our target is 2kHz, but since ROS1 is not really able to handle more than 500Hz, we settled for the 2MHz for now). As I said, this protocol is very priliminary and is very likely to be augmented by different fields and messages in the future.
Control of the overall system is established via ROS. The de10 runs ubuntu 16.04 and exposes full control via custom ROS messages. These are processed in the arm cores of the de10, which then use Alteras AXI lightweight bridge to write the respective commands to the fpga. The communication with the motorboards is run completely transparent to the user. The de10 fpga code automatically checks if the setpoints and control_modes of each motorboard match what is wanted from them and triggers automatic messages if they dont match. Because all the message handling works in parallel on both sides, and because of the dedicated frame matcher mechanism the iCEbus protocol is preempt-able and hot plug-able.
08/17/2019 at 10:35 •
The PCB has arrived and assembly by hand took less than an hour. It consists of a DRV8323HRTA mosfet driver from texas instruments. Six mosfets for controlling three phases and three TLI4970 current sensors from Infineon on each phase. The rest of the components is bulk capacity and connectors to the TinyFPGA and sensors.
I build a small test bed keeping all components in place and labeling all cables for sanity reasons. I used a bldc motor from Maxon which has hall sensors integrated and an optical encoder measuring the motor axis rotation. The motor is fixed to the ground plane with a 3D-printed holder. The motor winch has a 4x1mm diametral magnet attached to it and an A1335 angle sensor from Allegro is used to measure the absolute winch rotation. For testing i used a MKR4000 vidor. This board features a samd microcontroller a cyclone10 fpga and even has a nina wifi module. One can code in quartus for the fpga and because the samd processor shares all its IOs with the fpga you can easily control the fpga via SPI. I use the arduino IDE for high level coding.
The verilog code for controlling the bldc motor is surprisingly simple:
assign bMKR_D[5:0] = PHASES; reg [5:0] PHASES; reg [9:0] pwm_delay; reg signed [31:0] pwm; always @(posedge wCLK24) begin: BLDC_COMMUTATION if( pwm>=0 && pwm_delay>(1023-pwm))begin if(bMKR_A && ~bMKR_A && bMKR_A) begin PHASES <= 6'b100100; end if(bMKR_A && ~bMKR_A && ~bMKR_A)begin PHASES <= 6'b100001; end if(bMKR_A && bMKR_A && ~bMKR_A) begin PHASES <= 6'b001001; end if(~bMKR_A && bMKR_A && ~bMKR_A)begin PHASES <= 6'b011000; end if(~bMKR_A && bMKR_A && bMKR_A) begin PHASES <= 6'b010010; end if(~bMKR_A && ~bMKR_A && bMKR_A)begin PHASES <= 6'b000110; end end else if ( pwm<0 && pwm_delay>(1023+pwm)) begin if(bMKR_A && ~bMKR_A && bMKR_A) begin PHASES <= 6'b011000; end if(bMKR_A && ~bMKR_A && ~bMKR_A)begin PHASES <= 6'b010010; end if(bMKR_A && bMKR_A && ~bMKR_A) begin PHASES <= 6'b000110; end if(~bMKR_A && bMKR_A && ~bMKR_A)begin PHASES <= 6'b100100; end if(~bMKR_A && bMKR_A && bMKR_A) begin PHASES <= 6'b100001; end if(~bMKR_A && ~bMKR_A && bMKR_A)begin PHASES <= 6'b001001; end end else begin PHASES <= 0; end pwm_delay <= pwm_delay+1; end
The phases commute depending on the hall sensor input.
Make sure to activate a weak pull up resistor in the pin planner in quartus:
I used a poti connected to an adc pin of the vidor for setting the pwm value. There are three more sensors connected in this setup:
- optical encoder
- temperature sensor ( MLX90640 ) connected to samd via i2c
For the optical encoder I milled a simple pcb for connecting the differential signals to a chip that translates them to A/B signals. The encoder works on 5V so I put a level shifter before going to the fpga. Opencores has several quadrature decoder modules, I picked this one.
Next steps are to validate the quadrature encoder readings using the A1335 angle sensor on the winch. I want to make 100% sure I'm not missing any ticks even on highest speed.