Realization
This chapter will show how the realization has been done. It will use diagrams to show how some of the parts were designed and it will describe each part in more detail.
Last updated
This chapter will show how the realization has been done. It will use diagrams to show how some of the parts were designed and it will describe each part in more detail.
Last updated
This is about the following code repository: https://github.com/NXPHoverGames/RDDRONE-BMS772
In the main source file, the BMS main can be found, this is the BMS application. This function will initialize each part and start the main loop task. This task will implement the battery main state machine as seen in Figure 2 (https://app.gitbook.com/o/-L9GLsni4p7csCR7QCJ8/s/4sYITYOw8lqjlbpxfe9d/~/changes/1/software-guide-nuttx/bms-application-state-machine). In the main source code, the state is changed. In this source file there is a function to handle a changed parameter as well. This function will call other functions from the needed parts to do something with the parameter that is changed. If for example a configuration changed, such that a configuration of the BCC needs to change, it will call the right function from the Bat management part to change the configuration of the BCC as well. At the end of the main loop, the watchdog in the SBC is kicked. If this is not done in time, it will reset the MCU.
There is a lot of data that is needed or set by different tasks. Because it is not wise to move this big chunk of data through all the tasks there needs to be some sort of shared memory. Because NuttX is POSIX compliance there are shared memory functions that could be used. But for these shared memory functions a memory management unit (MMU) is needed and this microcontroller does not have an MMU. That is why the whole data management will be made in a data source file. This makes sure the data is only made once and is not global. With functions the data can be read or written, and these functions ensures protection against multiple threads accessing the data at the same time. These functions can be seen in Figure 5.
To protect the data from multiple threads trying to access it at the same time, a mutex is used. A mutex is an object that can be locked and unlocked in an atomic operation. Meaning that if both threads want to lock the mutex, the threads cannot lock the same mutex at the same time. A mutex is needed to prevent data race. The other thread needs to wait until the mutex is available.
The big data chuck is in a struct, together with a parameter info array. This array supports a fast access of the data type, the minimum, the maximum and the address of the data. This ensures it is faster to get and set data than with a large switch.
If a variable is set with the set parameter function. It will check if the variable is changed. If the variable is changed, it will call a handle parameter change function. This function will check which parameter has changed and will set other parameters if needed. At the end there is a callback to the main which will make sure the correct functions / reactions are being called. There are parameters that have an effect on the battery management and thus a function that handles changes in the battery management part is called as well to react on the parameter that changed. Next to handling the change of a parameter, this function will check if one of the savable parameters has been changed. If this is the case, it will remember this to save the parameters to flash when the DEEP SLEEP state is executed.
In NuttX there is a nuttshell, this is the UART communication with the MCU. In this nuttshell, applications can be called with or without arguments. There arguments will be given to the function it calls, in this case the BMS main. This means that a CLI can be created with calling the application with some arguments.
This CLI that is made, can be used by calling the BMS application in the nuttshell with a command and optionally up to 2 arguments for that command. When this happens the BMS main is called. Meaning that this main needs to be resistant against multiple calls, this should not restart the BMS application because than the battery power will be cut.
If there are commands given when calling the BMS application, the CLI process commands function will be called to handle it. It will parse the command and optionally the arguments and check if the inputs are valid. If it is valid, it will act based on which command has been given. The flowchart can be seen in Figure 6.
In order to set a color to the RGB LED, the set led color function should be used. The flowchart of this function can be found in Figure 7. Because this function can be used from different tasks, a mutex will be locked before it checks if the color and if blink is already set. This function will set the semaphore to start or stop the blink sequence. It will skip the semaphore timed wait function to ensure the blink sequence restarts if needed. It will begin with the new color. This function will use the NuttX userled functions.
In order to use a GPIO in NuttX, these GPIO’s need to be defined in the board file and the board specific GPIO file. This will create devices for each GPIO pin. To use the GPIO in the application an IOCTL call needs to be used. IOCTL means input-output control and it is a device specific system call.
The IOCTL is used to give commands to a driver to control a device, in this case the GPIO pins. But for an IOCTL to work an open file descriptor needs to be given. This is obtained by giving the path to the device as a string. This is too much work to do in the application for setting or reading a GPIO, that is why a GPIO BMS application driver is made. This will make sure that a GPIO can be read or written with simple write/read pin functions and a define to indicate the pin. An input pin can be an interrupt pin On an interrupt it will generate a signal that will queue the action for the handler. Keep in mind that these signals can be very intrusive.
The Bat management part can be used to monitor the battery and control the power switch. Because the Bat management part is quite large, there are other source files made to help with functions it needs to do and with utilizing the BCC. Like monitoring, to take care of measurements. Configuration, to take care of the whole configuration for the BCC. Balancing, to add easy to implement balancing function. For the main to implement the state machine, functions are made to let the main implement the functionality. Some functions are made to enable the measurements, to check for faults, to self-discharge etc.
The batManag task is created to handle the battery management. It will start the manual measurements with the BCC. Mostly it will calculate and check the current or it will measure and calculate every variable at t-meas interval. It will then perform a measurement, calculate the variables, save the values, it will check for software faults, it will calculate the new transition variables, handle the cell balancing if needed and it will trigger the callback that new measurements have been done. When saving the data, it will save the new measured and calculated variables in one go using the structs. Because the BCC will not check for an overcurrent, the current needs to be read and calculated every time to be compared with the current threshold. For short circuit protection there is a hardware circuit. If it measures a fault, it will trigger the main to act on it.
The sequence of the batManag task can be seen in Figure 8. The sem_wait and sem_post functions are called consecutively in the endless loop, this is used to start and stop the task with a function from the main. The meas task will check if the next measurement needs to be the measure everything or just the current and calculate the wait time to make sure the t-meas interval is met or with the remaining wait time. The task will wait for the next time or when the measurement is enabled, it will measure right away. To make sure the cyclic measurements does not drift, the time before the loop starts and the period t-meas are gained. The target time is calculated by adding the period with the previous target time. If t-meas should change, this is updated in the sequence using a global variable. This is left out of the sequence diagram because it is too detailed.
To calculate the state of charge (s-charge), the coulomb counter is used. The coulomb counter register holds the sum of the measured currents (until read). There is another register that holds the number of samples in the coulomb counter register. The average current is calculated by dividing the sum of the currents by the number of samples. When the time is known for which the average current is calculated, the difference in charge can be calculated with the following formula: ∆Q=I_avg*∆t. The new remaining charge is calculated by adding the difference in charge with the old remaining charge. The state of charge can then be calculated by dividing the full charge capacity by the remaining charge.
In order to provide the average power consumption over a time period of ten seconds, a constant moving average is taken. This moving average is constructed by removing the oldest measurement and adding the new measurement, which is than divided by the amount of measurements. This way the average will only be of the last ten seconds. In order to be memory efficient, the measurements used in the moving average will be sub-sampled if the measurement period is configured as less than one second. This way maximum ten old measurements need to be known. Measurements are not lost when sub-sampling, because the BCC chip will remember an average of it.
Since the user can change configurations in run time, sometimes a configuration needs to be changed in the BCC as well. When there is a change in the configuration, this is set with the setParameter function and a task in the main source file will handle the change. This function will call a function to handle the change in the bat management part. In this part it will call the right function from the configuration source file to change the configuration of the BCC.
The batManag task will check for software measured faults. The BCC chip will take care of hardware fault monitoring for the overvoltage, undervoltage, over temperature and under temperature. It will set the fault pin high when there is an error. If this happens it will trigger an interrupt in the main and it will check what fault happened. The main can than act on the fault. This ensures that the main is in control of what happens.
Since the user can change configurations in run time, sometimes a configuration needs to be changed in the BCC as well. When there is a change in the configuration, this is set with the setParameter function and a task in the main source file will handle the change. This function will call a function to handle the change in the bat management part. In this part it will call the right function from the configuration source file to change the configuration of the BCC.
The SBC part is used to control the power of voltage regulators V1 (The most used 3.3V (VCC_3V3_SBC)) and V2 (CAN PHY) (VCC_5V_SBC). With the setSbcMode() function the mode of the SBC can be set. In the normal mode both V1 and V2 are active, in the standby mode V2 is off, turning off the CAN transceiver and in the sleep mode both V1 and V2 are off, turning off almost the whole BMS board. In Figure 10 the simplified flowchart of this function can be seen. Besides power regulators, the SBC has a watchdog, which is used to reset the MCU if it doesn’t "kick" (reset) the watchdog within the set time. The reset of the MCU this is done via the NRST pin.
This part works with a DroneCAN (or Cyphal) task that waits (it sleeps until a CAN transceiver signal comes in) for an incoming CAN transmission. It will also wake up if the main signals the task that new data needs to be send. When new data needs to be sent, the task will put the data that needs to be sent in the transmit buffer. It will check if the transmit buffer if it is filled and transmit the data if it is. Then it will wait for an incoming CAN transmission or signal again. To see the flowchart see Figure 10: DroneCAN flowchart below.
There are DroneCAN and Cyphal messages implemented. Keep in mind that you can only select one at the time. The DroneCAN messages can be seen below in "DroneCAN messages implemented".
The CyphalCAN messages are a snapshot of the CyphalCAN V1 with WIP DS-015. This consists of 3 messages, the energy source, the battery status and the battery parameters. These messages can be seen in "CyphalCAN messages implemented"
For information on the DSDL message definition used for DroneCAN, please see the .uavcan files:
ardupilot_equipment_power_BatteryContinuous
uavcan_equipment_power_BatteryInfo
ardupilot_equipment_power_BatteryPeriodic
ardupilot_equipment_power_BatteryCells
ardupilot_equipment_power_BatteryInfoAux
Table 1. DroneCAN battery continuous message
Type | Name | Unit | Description |
Float16 | Temperature_cells | C | Pack mounted thermistor (preferably installed between cells), NAN: field not provided |
Float16 | Temperature_pcb | C | Battery PCB temperature (likely output FET(s) or current sense resistor), NAN: field not provided. |
Float16 | Temperature_other | C | Application dependent, NAN: field not provided |
Float32 | Current | A | Positive: defined as a discharge current. Negative: defined as a charging current, NAN: field not provided |
Float32 | Voltage | V | Battery voltage |
Float16 | State_of_charge | % | The estimated state of charge, in percent remaining (0 - 100). |
Uint8 | Slot_id | - | The physical location of the battery on the aircraft. 0: field not provided |
Float32 | Capacity_consumed | Ah | This is either the consumption since power-on or since the battery was full, depending on the value of STATUS_FLAG_CAPACITY_RELATIVE_TO_FULL, NAN: field not provided |
Uint32 | Status_flags | - | Fault, health, readiness, and other status indications. READY_TO_USE = 1 CHARGING = 2 CELL_BALANCING = 4
AUTO_DISCHARGING = 16 REQUIRES_SERVICE = 32 BAD_BATTERY = 64 PROTECTIONS_ENABLED = 128
FAULT_OVER_VOLT = 512 FAULT_UNDER_VOLT = 1024 FAULT_OVER_TEMP = 2048 FAULT_UNDER_TEMP = 4096 FAULT_OVER_CURRENT = 8192 FAULT_SHORT_CIRCUIT = 16384
CAPACITY_RELATIVE_TO_FULL = 262144 |
Table 2. DroneCAN battery info message
Type | Name | Unit | Description |
Float16 | Temperature | K |
|
Float16 | Voltage | V |
|
Float16 | Current | A |
|
Float16 | Average_power_10sec | W | Average power consumption over the last 10 seconds. |
Float16 | remaining_capacity_wh | Wh | Will be increasing during charging |
Float16 | full_charge_capacity_wh | Wh | Predicted battery capacity when it is fully charged. Falls with aging |
Float16 | hours_to_full_charge | h | Charging is expected to complete in this time; zero if not charging |
Uint11 | status_flags | - | - CHARGING must be always set as long as the battery is connected to a charger, even if the charging is complete. - CHARGED must be cleared immediately when the charger is disconnected. STATUS_FLAG_IN_USE = 1 # The battery is currently used as a power supply STATUS_FLAG_CHARGING = 2 # Charger is active STATUS_FLAG_CHARGED = 4 # Charging complete, but the charger is still active STATUS_FLAG_TEMP_HOT = 8 # Battery temperature is above normal STATUS_FLAG_TEMP_COLD = 16 # Battery temperature is below normal STATUS_FLAG_OVERLOAD = 32 # Safe operating area violation STATUS_FLAG_BAD_BATTERY = 64 # This battery should not be used anymore (e.g. low SOH) STATUS_FLAG_NEED_SERVICE = 128 # This battery requires maintenance (e.g. balancing, full recharge) STATUS_FLAG_BMS_ERROR = 256 # Battery management system/controller error, smart battery interface error STATUS_FLAG_RESERVED_A = 512 # Keep zero STATUS_FLAG_RESERVED_B = 1024 # Keep zero |
Uint7 | state_of_health_pct | % | Health of the battery, in percent, optional. STATE_OF_HEALTH_UNKNOWN = 127 # Use this constant if SOH cannot be estimated. |
Uint7 | state_of_charge_pct | % | Relative State of Charge (SOC) estimate, in percent. Percent of the full charge [0, 100]. This field is required. |
Uint7 | state_of_charge_pct_stdev | % | SOC error standard deviation; use best guess if unknown. |
Uint8 | battery_id | - | Identifies the battery within this vehicle, e.g. 0 - primary battery. |
Uint32 | model_instance_id | - | Set to zero if not applicable. Model instance ID must be unique within the same battery model name. |
Uint8[<32] | model_name | - | Battery model name. Model name is a human-readable string that normally should include the vendor name, model name, and chemistry type of this battery. This field should be assumed case-insensitive. Example: "Zubax Smart Battery v1.1 LiPo". |
Typical publishing rate should be around 0.2~1 Hz.
Table 3. DroneCAN battery periodic message
Type | Name | Unit | Description |
Uint8[<=50] | Name | - | Formatted as manufacturer_product, 0 terminated |
Uint8[<=32] | Serial_number | - | Serial number in ASCII characters, 0 terminated |
|
|
|
|
Float32 | Design_capacity |
| Fully charged design capacity. 0: field not provided. |
Uint8 | Cells_in_series | - | Number of battery cells in series. 0: field not provided. |
Float16 | Nominal_voltage | V | Battery nominal voltage. Used for conversion between Wh and Ah. 0: field not provided. |
Float16 | Discharge_minimum_voltage | V | Minimum per-cell voltage when discharging. 0: field not provided. |
Float16 | charging_minimum_voltage | V | Minimum per-cell voltage when charging. 0: field not provided. |
Float16 | charging_maximum_voltage | V | Maximum per-cell voltage when charged. 0: field not provided. |
Float32 | charging_maximum_current | A | Maximum pack continuous charge current. 0: field not provided. |
Float32 | discharge_maximum_current | A | Maximum pack continuous discharge current. 0: field not provided. |
Float32 | discharge_maximum_burst_current | A | Maximum pack discharge burst current for 30 seconds. 0: field not provided |
Float32 | full_charge_capacity | Ah | Predicted battery capacity when fully charged (accounting for battery degradation), NAN: field not provided |
Uint16 | cycle_count | - | Lifetime count of the number of charge/discharge cycles (https://en.wikipedia.org/wiki/Charge_cycle). UINT16_MAX: field not provided. |
Uint8 | state_of_health | % | State of Health (SOH) estimate, in percent (0 - 100). UINT8_MAX: field not provided. |
Battery data to be sent statically upon request or periodically at a low rate
Recommend that this message is sent at a maximum of 1Hz and nominally 0.2 Hz (IE: once every 5 seconds.).
Table 4. DroneCAN battery cells message
Type | Name | Unit | Description |
Float16 [<=24] | Voltages | V | Cell voltage array. |
Uint16 | index | - | Index of the first cell in the array, index 0 is cells at array indices 0 - 23, index 24 is cells at array indices 24 - 47, etc. |
Rate: set by parameter on smart battery (default off).
Table 5. DroneCAN battery info auxiliary message
Type | Name | Unit | Description |
|
|
|
|
Float16 [<=255] | voltage_cell | V | Battery individual cell voltages, length of following field also used as cell count. |
Uint16 | cycle_count | - |
|
|
|
|
|
|
|
|
|
Float16 | nominal_voltage | V | Nominal voltage of the battery pack. |
Bool | is_powering_off | - | Power off event imminent indication, false if unknown |
Uint8 | battery_id | - | Identifies the battery within this vehicle, e.g. 0 - primary battery |
Subject | Type | Typ. Rate [Hz] |
energy_source | 1…100 | |
status | ~1 | |
parameters | ~0.2 |
Table 6. Energy source 0.1 CyphalCAN udral
Type | Name | Unit | Description |
Uint56 | timestamp.microsecond | us | The number of microseconds that have passed since some arbitrary moment in the past. UNKNOWN = 0. |
Float32 | source.power.current | A | Battery current. |
Float32 | source.power.voltage | V | Battery voltage. |
Float32 | source.energy | J | A pessimistic estimate of the amount of energy that can be reclaimed from the source in its current state. |
Float32 | source.full_energy | J | A pessimistic estimate of the amount of energy that can be reclaimed from a fresh source (a fully charged battery) under the current conditions. |
Table 7. Battery status 0.2 CyphalCAN udral
Type | Name | Unit | Description |
Uint2 | heartbeat.readiness* | - | SLEEP = 0, STANDBY = 2, ENGAGED = 3. |
Uint2 | heartbeat.health* | - | NOMINAL = 0, ADVISORY = 1, CAUTION = 2, WARNING = 3. |
Float32[2] | temperature_min_max | K | The minimum and maximum readings of the pack temperature sensors. |
Float32 | available_charge | C | The estimated electric charge currently stored in the battery. |
Uint8 | error* | - | Error status. NONE = 0, BAD_BATTERY = 10, NEEDS_SERVICE = 11, BMS_ERROR = 20, CONFIGURATION = 30, OVERDISCHARGE = 50, OVERLOAD = 51, CELL_OVERVOLTAGE = 60, CELL_UNDERVOLTAGE = 61, CELL_COUNT = 62, TEMPERATURE_HOT = 100, TEMPERATURE_COLD = 101. |
Float16[<=255] | cell_voltages | V | The voltages of individual cells in the battery pack. |
* not implemented in the BMS example code
Table 8. Battery parameter 0.3 CyphalCAN udral
Type | Name | Unit | Description |
Uint64 | unique_id | - | Unique number. |
Float32 | mass | Kg | The total mass of the battery. |
Float32 | design_capacity | C | Design capacity. |
Float32[2] | design_cell_voltage_min_max | V | Factory cell voltages. |
Float32 | discharge_current | A | The discharge current. |
Float32 | discharge_current_burst | A | The burst discharge current at least for 5 seconds. |
Float32 | charge_current | A | The charge current. |
Float32 | charge_current_fast | A | The fast charge current. |
Float32 | charge_termination_treshold | A | End-of-charging current. |
Float32 | charge_voltage | V | Total charging voltage. |
Uint16 | cycle_count | - | The number of cycles. |
Uint16 | Void16 | - | Was cell count. |
Uint7 | state_of_health_pct | % | The state of health. |
Uint8 | technology.value | - | Battery chemistry 100 = LiPo, 101 = LiFePO4, 0 = unknown. |
Float32 | nominal_voltage | V | The nominal battery voltage. |