Other Parts Discussed in Thread: MSPM0L1304, BQ79616,
Tool/software: CCS Theia (IDE), MSPM0L1304 (host MCU)
Here is my code (with some irrelevant portions deleted). On lines 187 and 247, it is detected that the OTP programming is locked. I checked to make sure that I was following the correct procedures in the BQ79616 datasheet, but I can't see anything I'm doing wrong. Please help!
/*
* Copyright (c) 2023, Texas Instruments Incorporated
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Texas Instruments Incorporated nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ti/devices/msp/m0p/mspm0l130x.h"
#include "ti/driverlib/dl_timerg.h"
#include "ti/driverlib/dl_uart_main.h"
#include "ti/driverlib/m0p/dl_interrupt.h"
#include "ti_msp_dl_config.h"
#include <math.h>
#include <stdint.h>
/* Configurations */
#define BQ79616_WAKE_PORT GPIOA
#define BQ79616_WAKE_PIN DL_GPIO_PIN_0
#define THERMISTOR_CONNECTION_R1 10 // kohms
#define THERMISTOR_T0 298.15 // Kelvins
#define THERMISTOR_R0 10 // kohms
#define THERMISTOR_B_25_85 3434 // Kelvins
/* BQ79616 addresses */
#define BQ79616_DEVICE_ADDR 0x00
#define OTP_ECC_TEST_ADDR 0x034C
#define CONTROL1_ADDR 0x0309
#define CONTROL2_ADDR 0x030A
#define DIR0_ADDR_ADDR 0x0306
#define COMM_CTRL_ADDR 0x0308
#define OTP_CUST1_STAT_ADDR 0x051A
#define OTP_CUST2_STAT_ADDR 0x051B
#define OTP_PROG_UNLOCK1A_ADDR 0x0300
#define OTP_PROG_UNLOCK2A_ADDR 0x0352
#define OTP_PROG_STAT_ADDR 0x0519
#define OTP_PROG_CTRL_ADDR 0x030B
#define DEV_CONF_ADDR 0x0002
#define ACTIVE_CELL_ADDR 0x0003
#define OV_THRESH_ADDR 0x0009
#define UV_DISABLE1_ADDR 0x000C
#define GPIO_CONF1_ADDR 0x000E
#define FAULT_MSK1_ADDR 0x0016
#define PWR_TRANSIT_CONF_ADDR 0x0018
#define ADC_CTRL1_ADDR 0x030D
#define VCELL16_HI_ADDR 0x0568
#define TSREF_HI_ADDR 0x058C
#define BAL_CTRL2_ADDR 0x032F
#define CB_CELL16_CTRL_ADDR 0x0318
#define FAULT_RST1_ADDR 0x0331
#define FAULT_SUMMARY_ADDR 0x052D
#define FAULT_SYS_ADDR 0x0536
/* BQ79616 Error Flags */
#define OTP_FAULT (1 << 0)
#define COMMS_FAULT (1 << 1)
#define POWER_RAIL_FAULT (1 << 2)
#define DIE_TEMP_FAULT (1 << 3)
#define LFO_FREQ_FAULT (1 << 4)
#define BUFFER_SIZE 512
#define MESSAGE_TYPE_NUM_BITS 3
/* Message Types */
#define REQ_INFO 0b000
#define VOLTAGE_THRESHOLD 0b001
#define TEMP_THRESHOLD 0b010
#define BALANCING 0b011
#define VOLTAGE 0b100
#define TEMP 0b101
#define ERROR 0b110
#define INIT 0b111
#define INIT_SUCCESS 0xFF
/* BQ79616 Request Types */
#define SINGLE_DEVICE_READ 0b000
#define SINGLE_DEVICE_WRITE 0b001
#define STACK_READ 0b010
#define STACK_WRITE 0b011
#define BROADCAST_READ 0b100
#define BROADCAST_WRITE 0b101
#define BROADCAST_WRITE_REVERSE 0b110
/* Global Variables */
uint16_t cell_voltages[17]; // measured in mV
volatile bool enable_balancing_req[17];
volatile bool stop_balancing_req[17];
int16_t temp1 = -10000; // measured in 0.01 degrees C
int16_t temp2 = -10000; // measured in 0.01 degrees C
volatile uint16_t ignore_voltage_threshold = 1000; // measured in mV
volatile int16_t ignore_temp_threshold = -2000; // measured in 0.01 degrees C
volatile bool awaiting_data = false;
volatile bool awaiting_faults = false;
volatile uint8_t module_num;
volatile uint8_t num_modules;
volatile uint8_t messages[BUFFER_SIZE];
uint16_t next_message_to_send_index = 0;
volatile uint16_t next_message_to_save_index = 0;
uint8_t bq79616_response_data[128];
volatile uint32_t micros = 0;
/* Function Prototypes */
void wake_bq79616(void);
void auto_address_bq79616(void);
bool program_bq79616_otp(void);
bool program_bq79616_otp_page1(void);
bool program_bq79616_otp_page2(void);
void set_bq79616_otp_settings(void);
void reset_bq79616_faults(void);
void bq79616_faults(void);
void command_bq79616(uint8_t req_type, uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint8_t data_len);
uint8_t response_bq79616(void);
uint16_t crc_bq79616(const uint8_t *data, uint8_t length);
void delay_us(uint32_t us);
void micros_counter(void);
int main(void) {
SYSCFG_DL_init();
NVIC_EnableIRQ(MICROS_INST_INT_IRQN);
DL_Interrupt_registerInterrupt(MICROS_INST_INT_IRQN, micros_counter);
wake_bq79616();
auto_address_bq79616();
reset_bq79616_faults();
program_bq79616_otp();
}
bool program_bq79616_otp(void) {
if (program_bq79616_otp_page2() || program_bq79616_otp_page1()) {
return true;
} else { // neither OTP programming was successful - try one more time
if (program_bq79616_otp_page2() || program_bq79616_otp_page1()) {
return true;
} else { // error - neither OTP programming was successful
return false;
}
}
}
bool program_bq79616_otp_page1(void) {
uint8_t data[4];
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_CUST1_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] & 1) == 0) { // page 1 not yet programmed
// Unlock the OTP programming
data[0] = 0x02;
data[1] = 0xB7;
data[2] = 0x78;
data[3] = 0xBC;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, OTP_PROG_UNLOCK1A_ADDR, data, 4);
// Check to confirm the OTP unlock procedure is successful
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_PROG_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] >> 7) != 1) { // error - OTP programming locked
return false;
}
// Select the proper OTP page and start the OTP programming
data[0] = 0x01;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, OTP_PROG_CTRL_ADDR, data, 1);
// Do the OTP programming
set_bq79616_otp_settings();
// Wait 100 ms for the OTP programming to complete.
delay_us(100000);
// Check to ensure there is no error during OTP programming.
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_PROG_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] & 1) != 1) { // error - unsuccessful OTP programming
return false;
}
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_CUST1_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if (bq79616_response_data[0] != 0b00001111) { // error - unsuccessful OTP programming
return false;
}
// Issue a digital reset to reload the registers with the updated OTP values
data[0] = 0b10;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, CONTROL1_ADDR, data, 1);
}
return true;
}
bool program_bq79616_otp_page2(void) {
uint8_t data[4];
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_CUST2_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] & 1) == 0) { // page 2 not yet programmed
// Unlock the OTP programming
data[0] = 0x7E;
data[1] = 0x12;
data[2] = 0x08;
data[3] = 0x6F;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, OTP_PROG_UNLOCK2A_ADDR, data, 4);
// Check to confirm the OTP unlock procedure is successful
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_PROG_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] >> 7) != 1) { // error - OTP programming locked
return false;
}
// Select the proper OTP page and start the OTP programming
data[0] = 0x03;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, OTP_PROG_CTRL_ADDR, data, 1);
// Do the OTP programming
set_bq79616_otp_settings();
// Wait 100 ms for the OTP programming to complete.
delay_us(100000);
// Check to ensure there is no error during OTP programming.
data[0] = 0b000;
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_PROG_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if ((bq79616_response_data[0] & 1) != 1) { // error - unsuccessful OTP programming
return false;
}
command_bq79616(SINGLE_DEVICE_READ, BQ79616_DEVICE_ADDR, OTP_CUST2_STAT_ADDR, data, 1);
if (response_bq79616() == 0) {
return false;
}
if (bq79616_response_data[0] != 0b10001111) { // error - unsuccessful OTP programming
return false;
}
// Issue a digital reset to reload the registers with the updated OTP values
data[0] = 0b10;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, CONTROL1_ADDR, data, 1);
}
return true;
}
void set_bq79616_otp_settings(void) {
uint8_t data[2];
data[0] = 0b1100000;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, DEV_CONF_ADDR, data, 1);
data[0] = 0xA;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, ACTIVE_CELL_ADDR, data, 1);
data[0] = 0x2E;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, OV_THRESH_ADDR, data, 1);
data[0] = 0xFF;
data[1] = 0xFF;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, UV_DISABLE1_ADDR, data, 2);
data[0] = 0b00010010;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, GPIO_CONF1_ADDR, data, 1);
data[0] = 0b11111100;
data[1] = 0b0011110;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, FAULT_MSK1_ADDR, data, 2);
data[0] = 0b00000;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, PWR_TRANSIT_CONF_ADDR, data, 1);
}
void reset_bq79616_faults(void) {
uint8_t data[2];
data[0] = 0xFF;
data[1] = 0xFF;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, FAULT_RST1_ADDR, data, 2);
data[0] = 0x00;
data[1] = 0x00;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, FAULT_RST1_ADDR, data, 2);
}
void wake_bq79616(void) {
// Disable UART to allow pin mode changes
DL_UART_Main_disable(BQ79616_INST);
// Configure pin as GPIO output
DL_GPIO_initDigitalOutput(GPIO_BQ79616_IOMUX_TX);
DL_GPIO_enableOutput(BQ79616_WAKE_PORT, BQ79616_WAKE_PIN);
// Drive low for 2-2.5 ms
DL_GPIO_clearPins(BQ79616_WAKE_PORT, BQ79616_WAKE_PIN);
delay_us(2300);
// Drive high again
DL_GPIO_setPins(BQ79616_WAKE_PORT, BQ79616_WAKE_PIN);
// Wait 10 + 0.6 * number of devices ms before any further comms
delay_us(10600);
// Reconfigure the pin back to UART TX function
DL_GPIO_initPeripheralOutputFunction(
GPIO_BQ79616_IOMUX_TX, GPIO_BQ79616_IOMUX_TX_FUNC
);
// Re-enable UART
DL_UART_Main_enable(BQ79616_INST);
}
void auto_address_bq79616(void) {
uint8_t data[1];
uint8_t num_tries = 0;
while (num_tries++ < 3) {
// Dummy broadcast write OTP_ECC_TEST=0x00 to sync the DLL (delay-locked loop)
data[0] = 0x00;
command_bq79616(BROADCAST_WRITE, 0, OTP_ECC_TEST_ADDR, data, 1);
// Enable auto addressing mode by broadcast writing CONTROL1=0x01
data[0] = 0x01;
command_bq79616(BROADCAST_WRITE, 0, CONTROL1_ADDR, data, 1);
// Loop through the total number of boards setting the DIR0_ADDR of each board
data[0] = BQ79616_DEVICE_ADDR;
command_bq79616(BROADCAST_WRITE, 0, DIR0_ADDR_ADDR, data, 1);
// Broadcast write everything as a stack device first (COMM_CTRL=0x02)
data[0] = 0x02;
command_bq79616(BROADCAST_WRITE, 0, COMM_CTRL_ADDR, data, 1);
// Set device as base and top of stack (COMM_CTRL=0x01)
data[0] = 0x01;
command_bq79616(SINGLE_DEVICE_WRITE, BQ79616_DEVICE_ADDR, COMM_CTRL_ADDR, data, 1);
// Dummy broadcast read OTP_ECC_TEST to sync the DLL
data[0] = 0b000;
command_bq79616(BROADCAST_READ, 0, OTP_ECC_TEST_ADDR, data, 1);
if (response_bq79616() == 0) {
continue; // try again
}
break;
}
}
void command_bq79616(uint8_t req_type, uint8_t dev_addr, uint16_t reg_addr, uint8_t *data, uint8_t data_len) {
uint8_t frame[14];
uint8_t frame_len = 0;
frame[frame_len++] = ((1 << 7) | (req_type << 4)) | (data_len - 1);
if (req_type == SINGLE_DEVICE_READ || req_type == SINGLE_DEVICE_WRITE) {
frame[frame_len++] = dev_addr;
}
frame[frame_len++] = (uint8_t)(reg_addr >> 8);
frame[frame_len++] = (uint8_t)(reg_addr & 0xFF);
for (uint8_t i = 0; i < data_len; i++) {
frame[frame_len++] = data[i];
}
uint16_t crc = crc_bq79616(frame, frame_len);
frame[frame_len++] = (uint8_t)(crc >> 8);
frame[frame_len++] = (uint8_t)(crc & 0xFF);
for (uint8_t i = 0; i < frame_len; i++) {
DL_UART_Main_transmitDataBlocking(BQ79616_INST, frame[i]);
}
}
// returns 0 on CRC error - try again if required
uint8_t response_bq79616(void) {
uint8_t init_byte = DL_UART_Main_receiveDataBlocking(BQ79616_INST);
uint8_t data_len = init_byte + 1;
uint8_t frame[6 + data_len];
uint8_t frame_len = 0;
frame[frame_len++] = init_byte;
frame[frame_len++] = DL_UART_Main_receiveDataBlocking(BQ79616_INST); // device address byte
frame[frame_len++] = DL_UART_Main_receiveDataBlocking(BQ79616_INST); // register address byte 1
frame[frame_len++] = DL_UART_Main_receiveDataBlocking(BQ79616_INST); // register address byte 2
for (uint8_t i = 0; i < data_len; i++) {
bq79616_response_data[i] = DL_UART_Main_receiveDataBlocking(BQ79616_INST);
frame[frame_len++] = bq79616_response_data[i];
}
frame[frame_len++] = DL_UART_Main_receiveDataBlocking(BQ79616_INST); // CRC byte 1
frame[frame_len++] = DL_UART_Main_receiveDataBlocking(BQ79616_INST); // CRC byte 2
if (crc_bq79616(frame, frame_len) != 0x0000) { // error - CRC wrong
// clear RX FIFO
while (!DL_UART_Main_isRXFIFOEmpty(BQ79616_INST)) {
DL_UART_Main_receiveData(BQ79616_INST);
}
return 0;
}
return data_len;
}
uint16_t crc_bq79616(const uint8_t *data, uint8_t length) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < length; i++) {
uint8_t byte = data[i];
// Process each bit of the byte
for (uint8_t bit = 0; bit < 8; bit++) {
// XOR the LSB of CRC with the LSB of the data bit
uint8_t lsb_crc = crc & 0x0001;
uint8_t lsb_data = byte & 0x01;
uint8_t xor_val = (lsb_crc ^ lsb_data);
// Shift CRC right by 1
crc >>= 1;
// If XOR result is 1, XOR with polynomial
if (xor_val) {
crc ^= 0xA001; // Polynomial in LSB-first form
}
// Move to the next bit of the byte
byte >>= 1;
}
}
return (crc >> 8) | (crc << 8);
}
void delay_us(uint32_t us) {
DL_TimerG_startCounter(MICROS_INST);
uint32_t start = micros;
while (micros - start < us) {}
DL_TimerG_stopCounter(MICROS_INST);
}
/* Should not be called by our own program -- should only be called by the interrupt */
void micros_counter(void) {
micros++;
}
