- the UART communication is improved based on UART Idle line detection interrupt
- an Rx ring buffer is used to manage the UART incoming data
- both Tx and Rx are efficiently handled using DMA
#45#64#65
Other:
- minor visual improvements
The original Hardware supports two 4-pin cables that originally were connected to the two sensor boards. They break out GND, 12/15V and USART2&3 of the Hoverboard mainboard.
Both USART2 & 3 can be used for UART and I2C, PA2&3 can be used as 12bit ADCs.
The original Hardware supports two 4-pin cables that originally were connected to the two sensor boards. They break out GND, 12/15V and USART2&3 of the Hoverboard mainboard. Both USART2&3 can be used for UART and I2C, PA2&3 can be used as 12bit ADCs. Note that while USART3 (right sideboard cable) is 5V tolerant, USART2 (left sideboard cable) is **not** 5V tolerant.
Typically, the mainboard brain is an [STM32F103RCT6](/docs/literatur/[10]_STM32F103xC_datasheet.pdf), however some mainboards feature a [GD32F103RCT6](/docs/literatur/[11]_GD32F103xx-Datasheet-Rev-2.7.pdf) which is also supported by this firmware.
For the reverse-engineered schematics of the mainboard, see [20150722_hoverboard_sch.pdf](/docs/20150722_hoverboard_sch.pdf)
The reverse-engineered schematics of the mainboard can be found here:
- **VOLTAGE MODE**: in this mode the controller applies a constant Voltage to the motors
- **SPEED MODE**: in this mode a closed-loop controller realizes the input target speed by rejecting any of the disturbance (resistive load) applied to the motor
- **TORQUE MODE**: in this mode the target torque set by the user is realized. This mode enables motor "freewheeling" when the torque target is "0". Recommended for most applications with a sitting human driver. If you want the motor to brake instead of "freewheel" when torque target is "0", modify code to set torque target below "0" when `speedAvgAbs > 0`.
In all the modes, the controller features maximum motor speed and maximum motor current protection. This brings great advantages to fulfil the needs of many robotic applications while maintaining safe operation.
- The C code for the controller was auto-code generated using [Matlab/Simulink](https://nl.mathworks.com/solutions/embedded-code-generation.html) from a model which I developed from scratch specifically for hoverboard control. For more details regarding the working principle of the controller please consult the [Matlab/Simulink model](/01_Matlab).
- A [webview](/01_Matlab/BLDC_controller_ert_rtw/html/webview) was created, so Matlab/Simulink installation is not needed, unless you want to regenerate the code. The webview is an html page that can be opened with browsers like: Microsoft Internet Explorer or Microsoft Edge.
### Firmware Architecture
The main firmware architecture includes:
The firmware architecture includes:
- **Estimations**: estimates the rotor position, angle and motor speed based on Hall sensors signal
- **Diagnostics**: implements error detection such as unconnected Hall sensor, motor blocked, MOSFET defective
- **Control Manager**: manages the transitions between control modes (Voltage, Speed, Torque)
@ -54,8 +45,18 @@ The FOC algorithm architecture is illustrated in the figure below:
In this firmware 3 control types are available:
- Commutation
- SIN (Sinusoidal)
- FOC (Field Oriented Control)
- FOC (Field Oriented Control) with the following 3 control modes:
- **VOLTAGE MODE**: in this mode the controller applies a constant Voltage to the motors
- **SPEED MODE**: in this mode a closed-loop controller realizes the input speed target by rejecting any of the disturbance (resistive load) applied to the motor
- **TORQUE MODE**: in this mode the input torque target is realized. This mode enables motor "freewheeling" when the torque target is `0`. Recommended for most applications with a sitting human driver. If motor braking is desired instead of "freewheel" when torque target is `0`, then a torque target below `0` should be set when `speedAvgAbs > 0`.

In all FOC control modes, the controller features maximum motor speed and maximum motor current protection. This brings great advantages to fulfil the needs of many robotic applications while maintaining safe operation.
The C code for the controller was auto-code generated using [Matlab/Simulink](https://nl.mathworks.com/solutions/embedded-code-generation.html) from a model which I developed from scratch specifically for hoverboard control. For more details regarding the working principle of the controller please consult the [Matlab/Simulink model](/01_Matlab).
A [webview](/01_Matlab/BLDC_controller_ert_rtw/html/webview) was created, so Matlab/Simulink installation is not needed, unless you want to regenerate the code. The webview is an html page that can be opened with browsers like: Microsoft Internet Explorer or Microsoft Edge.
pos=rx_buffer_L_len-__HAL_DMA_GET_COUNTER(huart2.hdmarx);// Calculate current position in buffer
#endif
#if defined(DEBUG_SERIAL_USART2)
if(pos!=old_pos){// Check change in received data
if(pos>old_pos){// "Linear" buffer mode: check if current position is over previous one
usart_process_debug(&rx_buffer_L[old_pos],pos-old_pos);// Process data
}else{// "Overflow" buffer mode
usart_process_debug(&rx_buffer_L[old_pos],rx_buffer_L_len-old_pos);// First Process data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
usart_process_debug(&rx_buffer_L[0],pos);// Process remaining data
}
}
}
#endif // DEBUG_SERIAL_USART2
#ifdef CONTROL_SERIAL_USART2
uint8_t*ptr;
if(pos!=old_pos){// Check change in received data
ptr=(uint8_t*)&command_raw;// Initialize the pointer with command_raw address
if(pos>old_pos&&(pos-old_pos)==command_len){// "Linear" buffer mode: check if current position is over previous one AND data length equals expected length
memcpy(ptr,&rx_buffer_L[old_pos],command_len);// Copy data. This is possible only if command_raw is contiguous! (meaning all the structure members have the same size)
usart_process_command(&command_raw,&command,2);// Process data
}elseif((rx_buffer_L_len-old_pos+pos)==command_len){// "Overflow" buffer mode: check if data length equals expected length
memcpy(ptr,&rx_buffer_L[old_pos],rx_buffer_L_len-old_pos);// First copy data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
ptr+=rx_buffer_L_len-old_pos;// Move to correct position in command_raw
memcpy(ptr,&rx_buffer_L[0],pos);// Copy remaining data
}
usart_process_command(&command_raw,&command,2);// Process data
}
}
#endif // CONTROL_SERIAL_USART2
#ifdef SIDEBOARD_SERIAL_USART2
uint8_t*ptr;
if(pos!=old_pos){// Check change in received data
ptr=(uint8_t*)&Sideboard_L_raw;// Initialize the pointer with Sideboard_raw address
if(pos>old_pos&&(pos-old_pos)==Sideboard_L_len){// "Linear" buffer mode: check if current position is over previous one AND data length equals expected length
memcpy(ptr,&rx_buffer_L[old_pos],Sideboard_L_len);// Copy data. This is possible only if Sideboard_raw is contiguous! (meaning all the structure members have the same size)
usart_process_sideboard(&Sideboard_L_raw,&Sideboard_L,2);// Process data
}elseif((rx_buffer_L_len-old_pos+pos)==Sideboard_L_len){// "Overflow" buffer mode: check if data length equals expected length
memcpy(ptr,&rx_buffer_L[old_pos],rx_buffer_L_len-old_pos);// First copy data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
ptr+=rx_buffer_L_len-old_pos;// Move to correct position in Sideboard_raw
memcpy(ptr,&rx_buffer_L[0],pos);// Copy remaining data
}
usart_process_sideboard(&Sideboard_L_raw,&Sideboard_L,2);// Process data
pos=rx_buffer_R_len-__HAL_DMA_GET_COUNTER(huart3.hdmarx);// Calculate current position in buffer
#endif
#if defined(DEBUG_SERIAL_USART3)
if(pos!=old_pos){// Check change in received data
if(pos>old_pos){// "Linear" buffer mode: check if current position is over previous one
usart_process_debug(&rx_buffer_R[old_pos],pos-old_pos);// Process data
}else{// "Overflow" buffer mode
usart_process_debug(&rx_buffer_R[old_pos],rx_buffer_R_len-old_pos);// First Process data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
usart_process_debug(&rx_buffer_R[0],pos);// Process remaining data
}
}
}
#endif // DEBUG_SERIAL_USART3
#ifdef CONTROL_SERIAL_USART3
uint8_t*ptr;
if(pos!=old_pos){// Check change in received data
ptr=(uint8_t*)&command_raw;// Initialize the pointer with command_raw address
if(pos>old_pos&&(pos-old_pos)==command_len){// "Linear" buffer mode: check if current position is over previous one AND data length equals expected length
memcpy(ptr,&rx_buffer_R[old_pos],command_len);// Copy data. This is possible only if command_raw is contiguous! (meaning all the structure members have the same size)
usart_process_command(&command_raw,&command,3);// Process data
}elseif((rx_buffer_R_len-old_pos+pos)==command_len){// "Overflow" buffer mode: check if data length equals expected length
memcpy(ptr,&rx_buffer_R[old_pos],rx_buffer_R_len-old_pos);// First copy data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
ptr+=rx_buffer_R_len-old_pos;// Move to correct position in command_raw
memcpy(ptr,&rx_buffer_R[0],pos);// Copy remaining data
}
usart_process_command(&command_raw,&command,3);// Process data
}
}
#endif // CONTROL_SERIAL_USART3
#ifdef SIDEBOARD_SERIAL_USART3
uint8_t*ptr;
if(pos!=old_pos){// Check change in received data
ptr=(uint8_t*)&Sideboard_R_raw;// Initialize the pointer with Sideboard_raw address
if(pos>old_pos&&(pos-old_pos)==Sideboard_R_len){// "Linear" buffer mode: check if current position is over previous one AND data length equals expected length
memcpy(ptr,&rx_buffer_R[old_pos],Sideboard_R_len);// Copy data. This is possible only if Sideboard_raw is contiguous! (meaning all the structure members have the same size)
usart_process_sideboard(&Sideboard_R_raw,&Sideboard_R,3);// Process data
}elseif((rx_buffer_R_len-old_pos+pos)==Sideboard_R_len){// "Overflow" buffer mode: check if data length equals expected length
memcpy(ptr,&rx_buffer_R[old_pos],rx_buffer_R_len-old_pos);// First copy data from the end of buffer
if(pos>0){// Check and continue with beginning of buffer
ptr+=rx_buffer_R_len-old_pos;// Move to correct position in Sideboard_raw
memcpy(ptr,&rx_buffer_R[0],pos);// Copy remaining data
}
usart_process_sideboard(&Sideboard_R_raw,&Sideboard_R,3);// Process data