// Copyright 2016 Rob Riggs // All rights reserved. #include "bm78.h" #include "bm78_eeprom.hpp" #include "GPIO.hpp" #include "Log.h" #include "main.h" #include "HdlcFrame.hpp" #include "stm32l4xx_hal.h" #include #include #include #include extern RTC_HandleTypeDef hrtc; extern UART_HandleTypeDef huart3; extern CRC_HandleTypeDef hcrc; namespace mobilinkd { namespace tnc { namespace bm78 { /** * The BM78 module is a dual-mode BT3.0 & BLE5.0 UART modules. It supports * transparent data transfer in one or both modes. The module documentation * only clearly describes how to configure the modules using a (horrible) * Windows application. Microchip publishes a library for use with their * PIC chips that we initially used as a guide for implementing our own * configuration code. We now use a hybrid mechanism, writing out the * entire configuration and tweaking a few settings. * * The module must be booted into EEPROM programming mode in order to make * changes. This mode uses 115200 baud with no hardware flow control. * * We program the module for the following features: * * - The module is set for 115200 baud with hardware flow control. * - The name is changed to TNC3. * - The BT3.0 pairing PIN is set to 1234 * - The BLE5.0 pairing PIN is set to "123456". * - The module power setting is set as low as possible for BLE. */ void bm78_reset() { // Must use HAL_Delay() here as osDelay() may not be available. mobilinkd::tnc::gpio::BT_RESET::off(); HAL_Delay(1); mobilinkd::tnc::gpio::BT_RESET::on(); } /** * Enter BM78 EEPROM programming mode. * * This pulls EAN low on the BM78 and resets the module to enter EEPROM * programming mode and disable HW flow control on the UART. * * @note The timing of this process is not well documented. */ void enter_program_mode() { // Ensure we start out disconnected. gpio::BT_CMD::off(); HAL_Delay(10); gpio::BT_RESET::off(); HAL_Delay(1); // Spec says minimum 63ns. gpio::BT_RESET::on(); HAL_Delay(200); // Timing for EEPROM programming is not specified. huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart3.Init.BaudRate = 115200; if (HAL_UART_Init(&huart3) != HAL_OK) { CxxErrorHandler(); } } bool read_mac_address() { // Read (cmd = 0x29) the first 6 bytes at address 0. uint8_t cmd[] = { 0x01, 0x29, 0xfc, 0x03, 0x00, 0x00, 0x06 }; constexpr const uint16_t BLOCK_SIZE = 6; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 100) != HAL_OK) { ERROR("read_mac_address transmit failed"); return false; } uint8_t buffer[BLOCK_SIZE + 10]; if (HAL_UART_Receive(&huart3, buffer, sizeof(buffer), 1000) != HAL_OK) { ERROR("read_mac_address receive failed"); return false; } for (size_t i = 0; i != BLOCK_SIZE; ++i) { mac_address[5 - i] = buffer[i + 10]; // Reverse the bytes } return true; } /** * Exit BM78 EEPROM programming mode and return to pass-through mode. * * This asserts EAN on the BM78 and resets the module to exit EEPROM * programming mode, reconfigures the UART for HW flow control, then * waits for the module to become ready. */ void exit_program_mode() { gpio::BT_CMD::on(); HAL_Delay(10); gpio::BT_RESET::off(); HAL_Delay(1); // Spec says minimum 63ns. gpio::BT_RESET::on(); huart3.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; huart3.Init.BaudRate = 115200; if (HAL_UART_Init(&huart3) != HAL_OK) { CxxErrorHandler(); } bm78_wait_until_ready(); } /** * Parse the result of the previous write. Return true if the write was * successful and false if either the response could not be read or the * write failed. * * @pre A block of EEPROM data was written to the BM78. * @post The write response has been written. * * The result of the write is 7 bytes in length. The write was successful * if the last byte returned is a 0. * * @param function is the name of the calling function and is used when * logging to indicate which write operation failed. * @return true when the write was successful, otherwise false. */ bool parse_write_result(const char* function) { uint8_t result[7]; HAL_StatusTypeDef status; if ((status = HAL_UART_Receive(&huart3, result, sizeof(result), 1000)) != HAL_OK) { ERROR("%s receive failed (%d)", function, status); return false; } if (result[6] != 0) { ERROR("%s operation failed", function); } else { INFO("%s succeeded", function); } return result[6] == 0; } /** * Parse the EEPROM data and write the segments to the BM78 EEPROM. * * @pre The BM78 is in programming mode. * @post The BM78 is programmed. * * The data from the EEPROM programming UI is a sparsely populated memory * map. That data has been converted into
format * in the eeprom_data block. The last block has a zero length, indicating * EOF. The address is two bytes, big endian, followed by a one byte * length and *length* bytes of data. * * It is important that memory areas that are not populated by the UI are * not overwritten as they may contain device-specific data elements. For * example, the first 6 bytes of data are the Bluetooth device MAC address. * * The write command is 7 bytes long. The first three bytes are static: * {0x01, 0x27, 0xfc}. The 4th byte is *length* + 3. The 5th and 6th * are the address, and the 7th is *length*. The data to be written * follows. * * Once the data has been written, the BM78 module sends a response. This * must be read and parse before the next block is written. * * @return true if the BM78 is programmed, otherwise false. */ bool write_eeprom() { const uint8_t* data = eeprom_data; uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x00, 0x00, 0x00, 0x00}; bool result = true; while (result) { auto len = data[2]; cmd[0] = 0x01; cmd[1] = 0x27; cmd[2] = 0xfc; cmd[3] = len + 3; cmd[4] = data[0]; cmd[5] = data[1]; cmd[6] = len; if (len == 0) break; data += 3; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 1000) != HAL_OK) { ERROR("%s transmit header failed", __PRETTY_FUNCTION__); return false; } if (HAL_UART_Transmit(&huart3, const_cast(data), len, 1000) != HAL_OK) { ERROR("%s transmit data failed", __PRETTY_FUNCTION__); return false; } else { DEBUG("%s transmit succeeded %d bytes at 0x%02X%02X", __FUNCTION__, cmd[6], cmd[4], cmd[5]); } result = parse_write_result(__PRETTY_FUNCTION__); data += len; } return result; } bool write_serial() { uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 02, 0x42, 0x10}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 1000) != HAL_OK) { ERROR("%s transmit header failed", __PRETTY_FUNCTION__); return false; } if (HAL_UART_Transmit(&huart3, reinterpret_cast(serial_number_64), 16, 1000) != HAL_OK) { ERROR("%s transmit data failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_name() { uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x00, 0x0b, 0x10 , 'T', 'N', 'C', '3', ' ', 'M', 'o', 'b', 'i', 'l', 'i', 'n', 'k', 'd' , 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_pin() { uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x00, 0x5b, 0x10 , '1', '2', '3', '4', '5', '6', 0x00, 0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_misc() { // System options. Security parameters. // 0x01AD: 01 04 00 19 41 00 uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x09, 0x01, 0xad, 0x06 , 0x02, 0x04, 0x00, 0x59, 0x09, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_service_name() { // LE Service Name. // 0x0217: 08 KISS TNC uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x0c, 0x02, 0x17, 0x09 , 0x08, 'K', 'I', 'S', 'S', ' ', 'T', 'N', 'C'}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_service_uuid() { // LE Service UUID. // 0x0354: 6F E6 FD 82 C1 36 65 A4 16 4E 88 55 5E 6A EB 27 uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x03, 0x54, 0x10 , 0x6F, 0xE6, 0xFD, 0x82, 0xC1, 0x36, 0x65, 0xA4 , 0x16, 0x4E, 0x88, 0x55, 0x5E, 0x6A, 0xEB, 0x27}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_tx_attribute_uuid() { // TX attribute UUID. // 0x0364: BB 68 1F 96 B0 01 49 AE C9 46 2A BA 02 00 00 00 uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x03, 0x64, 0x10 , 0xBB, 0x68, 0x1F, 0x96, 0xB0, 0x01, 0x49, 0xAE , 0xC9, 0x46, 0x2A, 0xBA, 0x02, 0x00, 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_rx_attribute_uuid() { // RX attribute UUID. // 0x0374: BB 68 1F 96 B0 01 49 AE C9 46 2A BA 01 00 00 00 uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x03, 0x74, 0x10 , 0xBB, 0x68, 0x1F, 0x96, 0xB0, 0x01, 0x49, 0xAE , 0xC9, 0x46, 0x2A, 0xBA, 0x01, 0x00, 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_attribute_properties() { // RX notify; TX write/write without response. // 0x0384: 10 0C uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x05, 0x03, 0x84, 0x02, 0x10, 0x0c}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_manufacturer() { // LE manufacturer string. // 0x0300: Mobilinkd LLC uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x13, 0x03, 0x00, 0x10 , 'M', 'o', 'b', 'i', 'l', 'i', 'n', 'k', 'd', ' ', 'L', 'L', 'C' , 0x00, 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool set_le_advertising() { // undocumented but required for proper advertising. // 0x03A0: 31 bytes uint8_t cmd[] = {0x01, 0x27, 0xfc, 0x22, 0x03, 0xa0, 0x1f , 0x1B, 0x05, 0x09, 0x54, 0x4E, 0x43, 0x33, 0x11 , 0x06, 0x6F, 0xE6, 0xFD, 0x82, 0xC1, 0x36, 0x65 , 0xA4, 0x16, 0x4E, 0x88, 0x55, 0x5E, 0x6A, 0xEB , 0x27, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00}; if (HAL_UART_Transmit(&huart3, cmd, sizeof(cmd), 10) != HAL_OK) { ERROR("%s transmit failed", __PRETTY_FUNCTION__); return false; } return parse_write_result(__PRETTY_FUNCTION__); } bool configure_le_service() { bool result = true; result &= set_le_service_name(); result &= set_le_service_uuid(); result &= set_le_tx_attribute_uuid(); result &= set_le_rx_attribute_uuid(); result &= set_le_attribute_properties(); result &= set_le_manufacturer(); result &= set_le_advertising(); return result; } /** * Set the baud rate to 115200, turn on RTS/CTS flow control and then * reset the BLE module. * * @return true on success, otherwise false. */ bool set_uart() { huart3.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; huart3.Init.BaudRate = 115200; if (HAL_UART_Init(&huart3) != HAL_OK) { CxxErrorHandler(); } return true; } bool set_work() { return true; } bool set_power() { return true; } bool set_secure() { return true; } bool set_reliable() { return true; } }}} // mobilinkd::tnc::bm78 void bm78_wait_until_ready() { auto start = HAL_GetTick(); // Must wait until P1_5 (BT_STATE2) is high and P0_4 (BT_STATE1) is low. GPIO_PinState bt_state1, bt_state2; do { if (HAL_GetTick() > start + 2000) CxxErrorHandler(); // Timed out. HAL_Delay(100); bt_state2 = HAL_GPIO_ReadPin(BT_STATE2_GPIO_Port, BT_STATE2_Pin); bt_state1 = HAL_GPIO_ReadPin(BT_STATE1_GPIO_Port, BT_STATE1_Pin); DEBUG("bt_state2=%d, bt_state1=%d", bt_state2, bt_state1); } while (!((bt_state2 == GPIO_PIN_SET) and (bt_state1 == GPIO_PIN_RESET))); } uint32_t eeprom_crc() { const uint8_t* data = eeprom_data; while (true) { data++; data++; auto len = *data++; if (!len) break; data += len; } uint32_t size = data - eeprom_data; return HAL_CRC_Calculate(&hcrc, (uint32_t*)eeprom_data, size); } int bm78_initialized() { using namespace mobilinkd::tnc; using namespace mobilinkd::tnc::bm78; auto crc = eeprom_crc(); return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) == crc; } int bm78_initialize() { using namespace mobilinkd::tnc; using namespace mobilinkd::tnc::bm78; int result = 0; enter_program_mode(); if (!write_eeprom()) result = 1; else if (!write_serial()) result = 2; else if (!read_mac_address()) result = 3; exit_program_mode(); #if 1 if (result == 0) { /* Write CRC to RTC back-up register RTC_BKP_DR1 to indicate that the BM78 module has been initialized. */ HAL_PWR_EnableBkUpAccess(); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, eeprom_crc()); HAL_PWR_DisableBkUpAccess(); } #endif return result; } void bm78_state_change(void) {} int bm78_disable(void) {return 0;} int bm78_enable(void) {return 0;}