本帖最后由 小手凉凉 于 2023-12-20 18:33 编辑

目录:
       实现功能
       主要代码逻辑

#实现功能:
       将采集到的蓄电池信息数据上报到电脑上位机,可以直观的显示电池数据状态,上报数据模块连接上位机效果如下:
image.png
#主要代码逻辑:
        新建工程后在api_common.c文件中st_BatInfo 添加需要上报的电池数据,包括电池电压、电流、温度、soc状态等等。默认初始值如下
ST_SIGCELL_Info st_CellInfo[16] = {
    {
        .cellVolt = 3720, // mV
        .cellTemp = 23, // 温度
    },
    {
        .cellVolt = 3877, // mV
        .cellTemp = 25, // 温度
    },
    {
        .cellVolt = 4203, // mV
        .cellTemp = 27, // 温度
    },
};

ST_BAT_INFO st_BatInfo =
{
    .cum_volt = 12.7f, // V
    .total_volt = 24.2f, // V
    .curr = 42.3f, // A
    .soc = 0.654f, // 56.4%

    .maxMumCellVolt = 4273, // mV
    .nembMaxCellVolt = 1, // id
    .minMumCellVolt = 3735, // mV
    .nembMinCellVolt = 5, // id

    .maxMumCellTemp = 24, // 温度
    .nembMaxCellTemp = 4, // id
    .minMumCellTemp = 10, // 温度
    .nembMinCellTemp = 2, // id

    .changeStatus = 1, // 0 静止 1 充电 2 放电
    .changeMosSt = 1,    // 充电mos管状态
    .dischangeMosSt = 1, // 放电mos管状态
    .bmsCycle = 0x00, // bms life
    .remainCapacity = 5327, // 剩余容量mAh

    .numbBatt = 10, // 电池串数
    .numbTemp = 10, // 温度个数
    .changerStatus = 1, // 充电器状态
    .loadStatus = 1, // 负载状态
    // Byte4:Bit 0: DI1 state Bit 1: DI2 state Bit 2: DI3 state Bit 3: DI4 state Bit 4: DO1 state Bit 5: DO2 state Bit 6: DO3 state Bit 7: DO4 state
    .DiDoStatus = 0x00,

    .p_sigCell = st_CellInfo,

    .numbSigCellVolt = 0x01, // 单体id

    .sigCellVolt = 3854, // mV

    .numbSigCellTemp = 0x01, // 单体id
    .sigCellTemp = 21, // 温度
};


新建api_comm_thread_func子线程,用于采集电池温度、读取电压信息数据(暂没有外设硬件部分,电池生命状态值通过读取xTaskGetTickCount()获得,soc值随变量更新而更新)
static void api_comm_thread_func(void)
{
    ST_BAT_INFO *pBatInfo = &st_BatInfo;

    static uint32_t cnt_tim = 0;

    for(;;)
    {
        pBatInfo->bmsCycle = 0xFF & (xTaskGetTickCount() / configTICK_RATE_HZ);
        pBatInfo->soc = (float)pBatInfo->bmsCycle / 0xFF;

//        uart_print_user_msg((uint8_t *)"\r api_comm_thread_func cnt data !\n");
        cnt_tim ++;
        cnt_tim = cnt_tim % 10000;
        vTaskDelay(200);
    }
}


void api_comm_thread_create(void)
{
    comm_thread = xTaskCreateStatic (
                           (TaskFunction_t)api_comm_thread_func,
                           (const char*) "Uart Thread", COMM_STACK_SIZE/4, // world
                           NULL,
                           1,
                           (StackType_t*) &comm_thread_stack,
                           (StaticTask_t*) &comm_thread_memory
                           );
    if (NULL == comm_thread)
    {
        rtos_startup_err_callback (comm_thread, 0);
    }
}


通过uart_thread_create新建uart线程用于上报数据,线程创建为静态分配内存空间,[size=13.3333px]uart_thread_func用于解析串口命令后做出应答
static uint8_t uart_thread_stack[UART_STACK_SIZE] BSP_PLACE_IN_SECTION(BSP_UNINIT_SECTION_PREFIX ".stack.uart_thread") BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT);




StaticSemaphore_t g_fsp_uart_initialized_semaphore_memory;

void uart_thread_create(void)
{

    uart_thread = xTaskCreateStatic (
                        (TaskFunction_t)uart_thread_func,
                       (const char*) "Uart Thread", UART_STACK_SIZE/4, // world
                       NULL,
                       1,
                       (StackType_t*) &uart_thread_stack,
                       (StaticTask_t*) &uart_thread_memory
                       );
    if (NULL == uart_thread)
    {
        rtos_startup_err_callback (uart_thread, 0);
    }

    SemCountUART = xSemaphoreCreateCountingStatic(256, 1, &g_fsp_uart_initialized_semaphore_memory);
}



在串口子线程中等待信号量SemCountUART释放后立即执行组命令包逻辑,在接收到信号量之前线程一直处于等待状态
static void uart_thread_func(void)
{
CMD_BUFF_t* p_recv = &UsartCmdBuff;
USART_SEND_BUFF_u *p_respSend = &usartSendBuf;
ST_BAT_INFO *pBatInfo = &st_BatInfo;

uart_print_user_msg((uint8_t *)"\r start uart_thread_func !\n");

for(;;)
{
xSemaphoreTake(SemCountUART, portMAX_DELAY);
if (p_recv->addr != 0x40)
{
continue;
}
p_respSend->sendData.Head = p_recv->Head;
p_respSend->sendData.addr = 0x01;
p_respSend->sendData.id = p_recv->id;
p_respSend->sendData.Length = p_recv->Length;

memset(p_respSend->sendData.pdata, 0, UART_DATA_SIZE);

switch (p_recv->id)
{
case 0x90:
p_respSend->sendData.pdata[0] = (uint8_t) ((uint32_t)(pBatInfo->cum_volt * 10) >> 8) & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) (pBatInfo->cum_volt * 10) & 0xFF;

p_respSend->sendData.pdata[2] = (uint8_t) ((uint32_t)(pBatInfo->total_volt * 10) >> 8) & 0xFF;
p_respSend->sendData.pdata[3] = (uint8_t) (pBatInfo->total_volt * 10) & 0xFF;

p_respSend->sendData.pdata[4] = (uint8_t) ((uint32_t)((pBatInfo->curr + 3000.0f) * 10) >> 8) & 0xFF;
p_respSend->sendData.pdata[5] = (uint8_t) ((pBatInfo->curr + 3000.0f) * 10) & 0xFF;

p_respSend->sendData.pdata[6] = (uint8_t) ((uint32_t)(pBatInfo->soc * 1000) >> 8) & 0xFF;
p_respSend->sendData.pdata[7] = (uint8_t) (pBatInfo->soc * 1000) & 0xFF;

break;
case 0x91:
p_respSend->sendData.pdata[0] = (uint8_t) ((uint32_t)(pBatInfo->maxMumCellVolt) >> 8) & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) (pBatInfo->maxMumCellVolt) & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) (pBatInfo->nembMaxCellVolt) & 0xFF;

p_respSend->sendData.pdata[3] = (uint8_t) ((uint32_t)(pBatInfo->minMumCellVolt) >> 8) & 0xFF;
p_respSend->sendData.pdata[4] = (uint8_t) pBatInfo->minMumCellVolt & 0xFF;
p_respSend->sendData.pdata[5] = (uint8_t) pBatInfo->nembMinCellVolt & 0xFF;

break;
case 0x92:
p_respSend->sendData.pdata[0] = (uint8_t) (pBatInfo->maxMumCellTemp + 40) & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) pBatInfo->nembMaxCellTemp & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) (pBatInfo->minMumCellTemp + 40) & 0xFF;
p_respSend->sendData.pdata[3] = (uint8_t) pBatInfo->nembMinCellTemp & 0xFF;
break;
case 0x93:
p_respSend->sendData.pdata[0] = (uint8_t) pBatInfo->changeStatus & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) pBatInfo->changeMosSt & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) pBatInfo->dischangeMosSt & 0xFF;
p_respSend->sendData.pdata[3] = (uint8_t) pBatInfo->bmsCycle & 0xFF;

p_respSend->sendData.pdata[4] = (uint8_t) (pBatInfo->remainCapacity >> 24) & 0xFF;
p_respSend->sendData.pdata[5] = (uint8_t) (pBatInfo->remainCapacity >> 16) & 0xFF;
p_respSend->sendData.pdata[6] = (uint8_t) (pBatInfo->remainCapacity >> 8) & 0xFF;
p_respSend->sendData.pdata[7] = (uint8_t) pBatInfo->remainCapacity & 0xFF;
break;
case 0x94:
p_respSend->sendData.pdata[0] = (uint8_t) pBatInfo->numbBatt & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) pBatInfo->numbTemp & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) pBatInfo->changerStatus & 0xFF;
p_respSend->sendData.pdata[3] = (uint8_t) pBatInfo->loadStatus & 0xFF;
p_respSend->sendData.pdata[4] = (uint8_t) pBatInfo->DiDoStatus & 0xFF;
break;
case 0x95:
// cell volt
p_respSend->sendData.pdata[0] = (uint8_t) pBatInfo->numbSigCellVolt & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) (pBatInfo->p_sigCell[0].cellVolt >> 8) & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) pBatInfo->p_sigCell[0].cellVolt & 0xFF;

p_respSend->sendData.pdata[3] = (uint8_t) (pBatInfo->p_sigCell[1].cellVolt >> 8) & 0xFF;
p_respSend->sendData.pdata[4] = (uint8_t) pBatInfo->p_sigCell[1].cellVolt & 0xFF;

p_respSend->sendData.pdata[5] = (uint8_t) (pBatInfo->p_sigCell[2].cellVolt >> 8) & 0xFF;
p_respSend->sendData.pdata[6] = (uint8_t) pBatInfo->p_sigCell[2].cellVolt & 0xFF;
break;
case 0x96:
// cell temp
p_respSend->sendData.pdata[0] = (uint8_t) pBatInfo->numbSigCellTemp & 0xFF;
p_respSend->sendData.pdata[1] = (uint8_t) (pBatInfo->p_sigCell[0].cellTemp + 40) & 0xFF;
p_respSend->sendData.pdata[2] = (uint8_t) (pBatInfo->p_sigCell[1].cellTemp + 40) & 0xFF;
p_respSend->sendData.pdata[3] = (uint8_t) (pBatInfo->p_sigCell[2].cellTemp + 40) & 0xFF;
break;
case 0x97:
// bat balance
p_respSend->sendData.pdata[0] = (uint8_t) 0x03;
break;
case 0x98:
// warning
p_respSend->sendData.pdata[0] = (uint8_t) 0x11;
p_respSend->sendData.pdata[1] = (uint8_t) 0x11;
p_respSend->sendData.pdata[2] = (uint8_t) 0x11;
p_respSend->sendData.pdata[3] = (uint8_t) 0x11;
break;
default:
break;
}
api_usart_Send_BMS_Info(&usartSendBuf);

vTaskDelay(10);
}
}


接收串口数据按一帧13byte,分别由命令头、命令id、命令地址、长度、命令内容8byte和命令检验和组成,收到数据后,粘包处理如下,最终存储到g_uart_recv_buffer[DATA_LENGTH]中
void user_uart_callback(uart_callback_args_t *p_args)
{
    /* Logged the event in global variable */
    g_uart_event = (uint8_t)p_args->event;
    BaseType_t xHigherPriorityTaskWoken = pdTRUE;

    static uint32_t bufferIdx = 0;
    uint8_t *pUsartCmd = g_uart_recv_buffer;

    if(UART_EVENT_RX_CHAR == p_args->event)
    {
        *(pUsartCmd + bufferIdx) = (uint8_t ) p_args->data;
        if (bufferIdx > 0)
        {
            if ((*(pUsartCmd + bufferIdx - 1) == 0xA5) && (*(pUsartCmd + bufferIdx) == 0x40))
            {
                if (bufferIdx != 1)
                {
                    bufferIdx = 1;
                    *(pUsartCmd + 0) = 0xA5;
                    *(pUsartCmd + 1) = 0x40;
                }
            }
        }

        if ((*(pUsartCmd + bufferIdx) != CARRIAGE_ASCII) && (bufferIdx < (DATA_LENGTH - 1u)))
        {
            bufferIdx++;
        }
        else
        {
            if (api_check_cmd_data(g_uart_recv_buffer) == true)
            {
                xSemaphoreGiveFromISR(SemCountUART, &xHigherPriorityTaskWoken);
            }
            bufferIdx = 0;
        }
    }
}


存储的数据再去匹配比对校验和,匹配ok则存储到结构体UsartCmdBuff中等待使用,最后一步便是释放信号量后,串口线程则开始解析命令内容
uint8_t api_check_cmd_data(uint8_t* p_recvdata)
{
    uint8_t checksum = 0x00;
    uint8_t i = 0;

    if ((*(p_recvdata + 0) == UsartCmdBuff.Head) && (*(p_recvdata + 1) == UsartCmdBuff.addr)) {
        for(i = 0; i < 12; i ++) {
            checksum += *(p_recvdata + i);
            checksum = 0xFF & checksum;
        }
        if (*(p_recvdata + 12) == checksum)
        {
            UsartCmdBuff.id = *(p_recvdata + 2);
            UsartCmdBuff.Length = *(p_recvdata + 3);

            for(i = 0; i < 8; i ++)
            {
                UsartCmdBuff.pdata = *(p_recvdata + i + 4);
            }
            UsartCmdBuff.checksum = *(p_recvdata + 12);
            return true;
        }
        else {
            return false;
        }
    }
    return false;
}


串口组包ok后则通过fsp_err_t uart_send_bytes(uint8_t *p_msg,uint8_t msg_len)发送出去一帧数据,回应给上位机。
fsp_err_t uart_send_bytes(uint8_t *p_msg, uint8_t msg_len)
{
    fsp_err_t err   = FSP_SUCCESS;
    uint32_t local_timeout = (DATA_LENGTH * UINT16_MAX);

    /* Reset callback capture variable */
    g_uart_event = RESET_VALUE;

    /* Writing to terminal */
    err = R_SCI_UART_Write (&g_uart0_ctrl, p_msg, msg_len);
    if (FSP_SUCCESS != err)
    {
        return err;
    }

    /* Check for event transfer complete */
    while ((UART_EVENT_TX_COMPLETE != g_uart_event) && (--local_timeout))
    {
        /* Check if any error event occurred */
        if (UART_ERROR_EVENTS == g_uart_event)
        {
            return FSP_ERR_TRANSFER_ABORTED;
        }
    }
    if(RESET_VALUE == local_timeout)
    {
        err = FSP_ERR_TIMEOUT;
    }
    return err;
}


硬件连接如下:
image.png
调试串口数据时使用发命令方式验证效果ok,如下:
image.png
最终上位机看到数据上报效果ok
image.png