589 lines
36 KiB
Python
589 lines
36 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
KT mBMS CAN 통신 프로토콜 규격서 Word(.docx) 생성 스크립트
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
from docx import Document
|
||
from docx.shared import Inches, Pt, RGBColor
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
|
||
from docx.oxml import OxmlElement, parse_xml
|
||
from docx.oxml.ns import nsdecls, qn
|
||
|
||
def set_cell_background(cell, hex_color):
|
||
"""셀 배경색 지정"""
|
||
shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{hex_color}"/>')
|
||
cell._tc.get_or_add_tcPr().append(shading_elm)
|
||
|
||
def set_cell_margins(cell, top=100, bottom=100, left=150, right=150):
|
||
"""셀 여백 지정 (단위: dxa, 1pt = 20dxa)"""
|
||
tcPr = cell._tc.get_or_add_tcPr()
|
||
tcMar = parse_xml(
|
||
f'<w:tcMar {nsdecls("w")}>'
|
||
f'<w:top w:w="{top}" w:type="dxa"/>'
|
||
f'<w:bottom w:w="{bottom}" w:type="dxa"/>'
|
||
f'<w:left w:w="{left}" w:type="dxa"/>'
|
||
f'<w:right w:w="{right}" w:type="dxa"/>'
|
||
f'</w:tcMar>'
|
||
)
|
||
tcPr.append(tcMar)
|
||
|
||
def set_table_borders(table, color="D3D3D3", sz="4", val="single"):
|
||
"""테이블 전체 테두리 지정"""
|
||
tblPr = table._tbl.tblPr
|
||
borders = parse_xml(
|
||
f'<w:tblBorders {nsdecls("w")}>'
|
||
f'<w:top w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'<w:left w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'<w:bottom w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'<w:right w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'<w:insideH w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'<w:insideV w:val="{val}" w:sz="{sz}" w:space="0" w:color="{color}"/>'
|
||
f'</w:tblBorders>'
|
||
)
|
||
tblPr.append(borders)
|
||
|
||
def make_callout_box(doc, text, title="IMPORTANT"):
|
||
"""호출 박스 (Alert Box) 생성"""
|
||
tbl = doc.add_table(rows=1, cols=1)
|
||
tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||
tbl.autofit = False
|
||
|
||
cell = tbl.cell(0, 0)
|
||
cell.width = Inches(6.5)
|
||
set_cell_background(cell, "F0F4F8")
|
||
set_cell_margins(cell, top=140, bottom=140, left=200, right=200)
|
||
|
||
# 왼쪽 두꺼운 테두리만 설정
|
||
tcPr = cell._tc.get_or_add_tcPr()
|
||
borders = parse_xml(
|
||
f'<w:tcBorders {nsdecls("w")}>'
|
||
f'<w:left w:val="single" w:sz="24" w:space="0" w:color="1F4E79"/>'
|
||
f'<w:top w:val="none"/>'
|
||
f'<w:right w:val="none"/>'
|
||
f'<w:bottom w:val="none"/>'
|
||
f'</w:tcBorders>'
|
||
)
|
||
tcPr.append(borders)
|
||
|
||
p = cell.paragraphs[0]
|
||
p.paragraph_format.space_before = Pt(0)
|
||
p.paragraph_format.space_after = Pt(2)
|
||
run_t = p.add_run(f"[{title}] ")
|
||
run_t.bold = True
|
||
run_t.font.name = "맑은 고딕"
|
||
run_t.font.size = Pt(9.5)
|
||
run_t.font.color.rgb = RGBColor(31, 78, 121)
|
||
|
||
run_b = p.add_run(text)
|
||
run_b.font.name = "맑은 고딕"
|
||
run_b.font.size = Pt(9.5)
|
||
run_b.font.color.rgb = RGBColor(50, 50, 50)
|
||
|
||
doc.add_paragraph().paragraph_format.space_after = Pt(4)
|
||
|
||
def format_cell(cell, text, bold=False, color=RGBColor(50, 50, 50), size=Pt(9), align=WD_ALIGN_PARAGRAPH.LEFT, font_name="맑은 고딕"):
|
||
p = cell.paragraphs[0]
|
||
p.alignment = align
|
||
p.paragraph_format.space_before = Pt(0)
|
||
p.paragraph_format.space_after = Pt(0)
|
||
p.paragraph_format.line_spacing = 1.15
|
||
run = p.add_run(text)
|
||
run.bold = bold
|
||
run.font.name = font_name
|
||
run.font.size = size
|
||
run.font.color.rgb = color
|
||
|
||
def create_styled_table(doc, headers, data, col_widths=None, alignments=None):
|
||
"""스타일이 적용된 데이터 테이블 생성"""
|
||
table = doc.add_table(rows=len(data) + 1, cols=len(headers))
|
||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||
table.autofit = False
|
||
set_table_borders(table, color="D9D9D9", sz="4")
|
||
|
||
# 헤더 행
|
||
hdr_row = table.rows[0]
|
||
# 헤더 반복 (Repeat header row on every page)
|
||
trPr = hdr_row._tr.get_or_add_trPr()
|
||
trPr.append(parse_xml(f'<w:tblHeader {nsdecls("w")}/>'))
|
||
|
||
for i, title in enumerate(headers):
|
||
cell = hdr_row.cells[i]
|
||
set_cell_background(cell, "1F4E79")
|
||
set_cell_margins(cell, top=140, bottom=140, left=160, right=160)
|
||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||
format_cell(cell, title, bold=True, color=RGBColor(255, 255, 255), size=Pt(9.5), align=WD_ALIGN_PARAGRAPH.CENTER)
|
||
if col_widths and i < len(col_widths):
|
||
cell.width = col_widths[i]
|
||
|
||
# 데이터 행
|
||
for r_idx, row_data in enumerate(data):
|
||
row = table.rows[r_idx + 1]
|
||
bg_color = "F9FAFC" if (r_idx % 2 == 1) else "FFFFFF"
|
||
for c_idx, cell_value in enumerate(row_data):
|
||
cell = row.cells[c_idx]
|
||
set_cell_background(cell, bg_color)
|
||
set_cell_margins(cell, top=100, bottom=100, left=140, right=140)
|
||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||
|
||
align = WD_ALIGN_PARAGRAPH.LEFT
|
||
if alignments and c_idx < len(alignments):
|
||
align = alignments[c_idx]
|
||
|
||
# 굵은 글씨 여부 (첫 열 또는 코드 열 등)
|
||
is_bold = (c_idx == 0 and len(headers) > 3)
|
||
format_cell(cell, cell_value, bold=is_bold, color=RGBColor(50, 50, 50), size=Pt(9), align=align)
|
||
if col_widths and c_idx < len(col_widths):
|
||
cell.width = col_widths[c_idx]
|
||
|
||
# 테이블 아래 여백 문단
|
||
p_after = doc.add_paragraph()
|
||
p_after.paragraph_format.space_before = Pt(0)
|
||
p_after.paragraph_format.space_after = Pt(6)
|
||
|
||
def build_protocol_document():
|
||
doc = Document()
|
||
|
||
# 페이지 여백 설정 (A4: 210mm x 297mm, 여백: 20mm)
|
||
sections = doc.sections
|
||
for section in sections:
|
||
section.top_margin = Inches(0.8)
|
||
section.bottom_margin = Inches(0.8)
|
||
section.left_margin = Inches(0.8)
|
||
section.right_margin = Inches(0.8)
|
||
|
||
# 헤더 & 푸터
|
||
header = section.header
|
||
hp = header.paragraphs[0]
|
||
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||
hrun = hp.add_run("KT mBMS CAN 통신 프로토콜 규격서 (v1.0)")
|
||
hrun.font.name = "맑은 고딕"
|
||
hrun.font.size = Pt(8.5)
|
||
hrun.font.color.rgb = RGBColor(140, 140, 140)
|
||
|
||
footer = section.footer
|
||
fp = footer.paragraphs[0]
|
||
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
frun = fp.add_run("AMOTECH Energy Solution Team | Confidential")
|
||
frun.font.name = "맑은 고딕"
|
||
frun.font.size = Pt(8.5)
|
||
frun.font.color.rgb = RGBColor(140, 140, 140)
|
||
|
||
# 1. 문서 제목
|
||
title_p = doc.add_paragraph()
|
||
title_p.paragraph_format.space_before = Pt(4)
|
||
title_p.paragraph_format.space_after = Pt(4)
|
||
title_run = title_p.add_run("KT mBMS CAN 통신 프로토콜 규격서")
|
||
title_run.bold = True
|
||
title_run.font.name = "맑은 고딕"
|
||
title_run.font.size = Pt(21)
|
||
title_run.font.color.rgb = RGBColor(31, 78, 121)
|
||
|
||
sub_p = doc.add_paragraph()
|
||
sub_p.paragraph_format.space_before = Pt(0)
|
||
sub_p.paragraph_format.space_after = Pt(14)
|
||
sub_run = sub_p.add_run("3종 하드웨어 공통 통합 규격 (V1 / V2 / V3 펌웨어 및 관제 GUI 연동)")
|
||
sub_run.font.name = "맑은 고딕"
|
||
sub_run.font.size = Pt(12)
|
||
sub_run.font.color.rgb = RGBColor(80, 80, 80)
|
||
|
||
# 메타 정보 박스
|
||
meta_table = doc.add_table(rows=4, cols=2)
|
||
meta_table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||
set_table_borders(meta_table, color="D0D7DE", sz="4")
|
||
meta_data = [
|
||
("문서 버전 / 작성일자", "V1.0.0 / 2026년 10월 22일"),
|
||
("적용 대상 펌웨어", "• V1 (1st HW): mBMS_APP_V2403 (STM32F407 + BQ76PL455, 외부 릴레이 제어)\n• V2 (2nd HW): APP_R3221 (HC32F460 + BQ76940, 외부 릴레이 제어)\n• V3 (3rd HW): APP_R3323 (HC32F460 + ADBMS1818, 외부 릴레이 제어)"),
|
||
("참조 프로그램", "LFP_Manager_GL (V3.3.0.16) - KT 표준 모니터링/진단 GUI 프로그램"),
|
||
("주요 통신 사양", "CAN 2.0B Extended Frame (29-bit Identifier, 250 kbps, Base ID: 200)")
|
||
]
|
||
for r_i, (k, v) in enumerate(meta_data):
|
||
row = meta_table.rows[r_i]
|
||
c0 = row.cells[0]
|
||
c1 = row.cells[1]
|
||
c0.width = Inches(2.0)
|
||
c1.width = Inches(4.5)
|
||
set_cell_background(c0, "F2F4F8")
|
||
set_cell_background(c1, "FFFFFF")
|
||
set_cell_margins(c0, top=80, bottom=80, left=120, right=120)
|
||
set_cell_margins(c1, top=80, bottom=80, left=120, right=120)
|
||
format_cell(c0, k, bold=True, color=RGBColor(31, 78, 121), size=Pt(9))
|
||
format_cell(c1, v, bold=False, color=RGBColor(40, 40, 40), size=Pt(9))
|
||
|
||
doc.add_paragraph().paragraph_format.space_after = Pt(10)
|
||
|
||
# 섹션 1
|
||
h1 = doc.add_heading("1. 개요 및 물리 계층 (Physical Layer)", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
p = doc.add_paragraph("본 규격서는 KT mBMS 하드웨어 3종(V1, V2, V3)과 상위 관제 시스템(호스트 GUI, 랙 제어기 등) 간의 표준 CAN(Controller Area Network) 통신 프로토콜을 정의합니다. 모든 하드웨어(V1, V2, V3)는 보드 내부의 N-MOSFET을 제어하여 외부 대용량 릴레이(충전/방전/프리차지 릴레이)를 구동하는 동일한 전력 제어 구조를 공유하며, 보드 외적인 시스템 구성 및 배선은 완전히 동일합니다. 보드 내부의 MCU 및 AFE IC 등 모니터링 엔진의 차이에 관계없이 동일한 통신 프레임 규격 및 데이터 맵을 100% 공유합니다.")
|
||
p.paragraph_format.space_after = Pt(8)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["항목 (Item)", "사양 (Specification)", "비고"],
|
||
data=[
|
||
["통신 규격", "CAN 2.0B Extended Frame (29-bit Identifier)", "SAE J1939 PDU1 구조 준용"],
|
||
["통신 속도 (Baud Rate)", "250 kbps (기본 표준 속도)", "125k, 250k, 500k, 1Mbps 가변 지원 가능"],
|
||
["종단 저항 (Termination)", "120 Ω (양 끝단 장착)", "차동 신호 반사 방지"],
|
||
["데이터 길이 (DLC)", "8 Bytes 고정", "미사용 바이트는 0xFF 또는 0x00 패딩"],
|
||
["기본 노드 주소 (Base Addr)", "200 (0xC8)", "모듈 ID(1~16)에 따라 201 ~ 216 자동 할당"],
|
||
["브로드캐스트 주소", "255 (0xFF)", "전체 BMS 모듈 동시 지시"],
|
||
["호스트(Master/GUI) 주소", "1 또는 200", "GUI 송신 시 SA=1, PS=200+ID"]
|
||
],
|
||
col_widths=[Inches(2.2), Inches(3.2), Inches(1.5)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 2
|
||
h1 = doc.add_heading("2. CAN 29-bit Extended Identifier 구조", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
p = doc.add_paragraph("CAN 메시지 식별자는 SAE J1939 PDU1 (Point-to-Point 및 Broadcast) 방식을 기반으로 다음과 같이 29비트가 배치됩니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
# 비트 구조 테이블
|
||
create_styled_table(
|
||
doc,
|
||
headers=["Priority / Index", "Reserved (R)", "Data Page (DP)", "PDU Format (PF)", "PDU Specific (PS)", "Source Addr (SA)"],
|
||
data=[
|
||
["Bit 28 ~ 26 (3b)", "Bit 25 (1b)", "Bit 24 (1b)", "Bit 23 ~ 16 (8b)", "Bit 15 ~ 8 (8b)", "Bit 7 ~ 0 (8b)"],
|
||
["서비스 우선순위", "0 고정", "0 고정", "명령어 코드(Command)", "수신자 주소(DA)", "송신자 주소(SA)"]
|
||
],
|
||
col_widths=[Inches(1.2), Inches(0.9), Inches(1.0), Inches(1.3), Inches(1.2), Inches(1.1)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER] * 6
|
||
)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["필드명", "비트 범위", "크기", "설명 및 규칙"],
|
||
data=[
|
||
["Index (Priority)", "Bit 28 ~ 26", "3 bits", "• 0x6 (6): 일반 모니터링 / 제어 / 파라미터 셋업\n• 0x1 (1): 제조정보 / 인벤토리 데이터 (Inventory Data)\n• 0x4 (4): 펌웨어 다운로드 및 원격 업데이트 (F/W Update)"],
|
||
["R (Reserved)", "Bit 25", "1 bit", "항상 0 고정"],
|
||
["DP (Data Page)", "Bit 24", "1 bit", "기본 0 고정 (확장 커맨드 시 1)"],
|
||
["PF (PDU Format)", "Bit 23 ~ 16", "8 bits", "명령어 코드 (Command / Function Code)\n• 호스트 요청 코드 (Request Code: 1 ~ 25)\n• BMS 응답 코드 (Response Code: 11, 12, 13, 191~194, 31, 32 등)"],
|
||
["PS (PDU Specific)", "Bit 15 ~ 8", "8 bits", "수신 대상 주소 (Destination Address / Target)\n• 호스트 → BMS: 200 + Target_ID (1번 모듈 = 201 / 전체 = 0xFF)\n• BMS → 호스트: 호스트 주소 (200 또는 1)"],
|
||
["SA (Source Addr)", "Bit 7 ~ 0", "8 bits", "송신자 주소 (Source Address)\n• 호스트 → BMS: 1 (호스트 식별 주소)\n• BMS → 호스트: 200 + My_ID (자신의 노드 주소, 예: 201)"]
|
||
],
|
||
col_widths=[Inches(1.5), Inches(1.1), Inches(0.8), Inches(3.4)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 3
|
||
h1 = doc.add_heading("3. 일반 모니터링 및 제어 프로토콜 (Index = 6)", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
p = doc.add_paragraph("호스트(GUI)가 특정 슬레이브 ID를 대상으로 데이터를 요청(Request)하면, 대상 BMS가 즉시 해당 데이터를 포함한 응답(Response) 패킷을 1개 또는 복수 개 연속 전송합니다. 파라미터 설정 시에는 요청 패킷 내에 유효 설정 값을 담아 전송하며, 단순 조회 시에는 데이터 바이트를 0xFF로 채워 송신합니다.")
|
||
p.paragraph_format.space_after = Pt(8)
|
||
|
||
cmd_data = [
|
||
["시스템 종합 상태", "1", "11, 12, 13, 14", "팩전압, 전류, SOC, 운용상태, 경보/보호 비트, 펌웨어 버전 (연속 4패킷)"],
|
||
["셀 개별 전압", "19", "191 ~ 194", "Cell #1 ~ #16 개별 전압 측정값 (1mV 단위, 4개 패킷 분할)"],
|
||
["온도 계측값", "3", "31, 32", "Cell 온도 6개 + BMS 기판 / FET 주변 온도 2개 (0.1℃ 단위)"],
|
||
["SOC / SOH", "4", "41", "정밀 상대 SOC(0.1%), SOH(%), 충방전 사이클 수"],
|
||
["셀/온도 편차 파라미터", "2", "21", "셀 전압 편차(mV) 및 온도 편차(0.1℃), 각각의 보호 시간(분 단위) 통합 설정 및 응답"],
|
||
["셀 전압 파라미터", "5", "51", "CUV / COV Warning 및 Trip 임계치 (mV 단위)"],
|
||
["온도 보호 파라미터", "6", "61", "충/방전 저온 및 고온 Warning / Trip 임계치 (+80 Offset, ℃)"],
|
||
["팩 전압 파라미터", "7", "71", "SUV / SOV Warning 및 Trip 임계치 (0.01V 단위)"],
|
||
["배터리 구성 사양", "8", "81", "셀 수량(15/16), 온도센서 수량, 공칭 용량(0.1Ah)"],
|
||
["셀 밸런싱 파라미터", "10", "101", "자동 밸런싱 개시 전압, 편차 윈도우, 주기"],
|
||
["셀 밸런싱 상태", "11", "111", "실시간 밸런싱 동작 여부 및 셀별 동작 비트맵"],
|
||
["수동 밸런싱 제어", "12", "-", "특정 셀 강제 밸런싱 수동 ON / OFF 지시"],
|
||
["SOC 강제 설정", "13", "-", "현장 SOC 값 수동 보정 (0 ~ 100%)"],
|
||
["Low SOC 파라미터", "14", "141", "저용량 경보 및 복구 SOC 임계치 (%)"],
|
||
["옵션 파라미터", "15", "151", "충전 옵션 및 시스템 플래그"],
|
||
["기본값 복원/셧다운", "16", "-", "공장 출하 파라미터 복원(Default) 또는 강제 차단 제어"],
|
||
["전류 캘리브레이션", "17", "171", "충/방전 전류 센서 게인(Gain) 및 션트 레인지 보정"],
|
||
["팩전압 캘리브레이션", "18", "181", "팩 전압 센서 게인 보정"],
|
||
["하트비트 / 샘플링", "20", "201", "BMS 누적 카운터 및 1회 AFE 샘플링 소요 시간(ms)"],
|
||
["RTC 일시 동기화", "21", "211", "현재 날짜/시간(년,월,일,시,분,초) 및 최종 리셋 원인"],
|
||
["과전류 보호 파라미터", "22", "221", "충/방전 과전류(OCC/OCD) Warning 및 Trip (+30000, 0.1A)"],
|
||
["전압 복구 파라미터", "23", "231", "CUV, COV, SUV, SOV 복구 전압 임계치"],
|
||
["온도/전류 복구 파라미터", "24", "241", "저온/고온 및 과전류 복구 임계치"],
|
||
["원격 소프트 리셋", "25", "-", "BMS 시스템 원격 재부팅 수행"]
|
||
]
|
||
create_styled_table(
|
||
doc,
|
||
headers=["기능 (Function)", "요청 PF", "응답 PF", "설명 및 페이로드 요약"],
|
||
data=cmd_data,
|
||
col_widths=[Inches(1.8), Inches(0.8), Inches(1.1), Inches(3.1)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 4
|
||
h1 = doc.add_heading("4. 메시지별 상세 데이터 페이로드 규격", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
# 4.1 시스템 종합 상태
|
||
h2 = doc.add_heading("4.1 시스템 종합 상태 (PF: 1 → 11, 12, 13, 14)", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
|
||
p = doc.add_paragraph("• 호스트 요청 (PF: 1): Data[0~5]=0xFF, Data[6]=DCP Flag(0x01: 강제차단 연동), Data[7]=0xFF(상태요청) / 0 or 1(자동 밸런싱 On/Off)\n• BMS 응답: PF 11, 12, 13, 14 총 4개의 연속 패킷을 5ms 간격으로 송신합니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
doc.add_heading("▶ Status #1 (PF: 11) - 기본 계측 및 알람 1", level=3)
|
||
create_styled_table(
|
||
doc,
|
||
headers=["Byte", "항목", "타입", "단위/스케일", "상세 설명"],
|
||
data=[
|
||
["0 ~ 1", "팩 전압 (Battery Voltage)", "uint16", "0.01 V", "예: 5120 = 51.20 V"],
|
||
["2", "SOC (State of Charge)", "uint8", "1 %", "현재 배터리 잔여 용량 (0 ~ 100%)"],
|
||
["3", "Auto Cell Balancing", "uint8", "0 or 1", "0: 비활성, 1: 자동 밸런싱 동작 중"],
|
||
["4", "Warning 상태 비트 1", "uint8", "Bitfield", "[Bit 0] COV Warning [Bit 1] CUV Warning\n[Bit 2] High Temp W [Bit 3] Low Temp W\n[Bit 4] SOV Warning [Bit 5] SUV Warning\n[Bit 6] Cell Voltage Diff Warning"],
|
||
["5", "Protection 상태 비트 1", "uint8", "Bitfield", "[Bit 0] COV Fault [Bit 1] CUV Fault\n[Bit 2] High Temp F [Bit 3] Low Temp F\n[Bit 4] SOV Fault [Bit 5] SUV Fault\n[Bit 6] Cell Voltage Diff Fault (24h 차단)\n[Bit 7] AFE Fail / 센서 단선 결함"],
|
||
["6 ~ 7", "전류 (Current)", "int16", "0.1 A", "Offset: +30000\n실제 전류 = (수신값 - 30000) × 0.1 A\n(+값: 충전 전류, -값: 방전 전류)"]
|
||
],
|
||
col_widths=[Inches(0.8), Inches(2.0), Inches(0.8), Inches(1.1), Inches(2.1)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
doc.add_heading("▶ Status #2 (PF: 12) - 알람 2, 동작 상태 및 버전", level=3)
|
||
create_styled_table(
|
||
doc,
|
||
headers=["Byte", "항목명", "타입", "상세 내용"],
|
||
data=[
|
||
["0", "경고/보호 비트필드 2", "uint8", "[Bit 0] 모듈 내 온도 편차 차단 (3.0℃ 24시간 지속 차단 래치)\n[Bit 1] 충전 과전류 3회 완전 차단 (60초 내 3회 영구 락아웃)\n[Bit 2] Low SOC 경고\n[Bit 4] 충전 고온 경고 [Bit 5] 충전 저온 경고\n[Bit 6] 방전 고온 경고 [Bit 7] 방전 저온 경고"],
|
||
["1", "보호 비트필드 2", "uint8", "[Bit 0] 충전 과전류 경고 [Bit 1] 일반 충전 과전류 차단 (자동 복구 가능)\n[Bit 2] 방전 과전류 경고 [Bit 3] 방전 과전류 차단\n[Bit 4] 충전 고온 차단 [Bit 5] 충전 저온 차단\n[Bit 6] 방전 고온 차단 [Bit 7] 방전 저온 차단"],
|
||
["2", "동작 상태 (op_status)", "uint8", "0: 대기 (Standby) 1: 충전 (Charging)\n2: 방전 (Discharging) 3: 플로팅 (Floating)\n4: 승온 (Warming Up)"],
|
||
["3 ~ 5", "고유 식별자", "-", "Byte 3: 고유 서명 0x57 ('W')\nByte 4: 하드웨어 구분자 (0x11:V1, 0x22:V2, 0x33:V3)\nByte 5: 빌드 리비전 0x02 (R402 빌드)"],
|
||
["6 ~ 7", "펌웨어 버전", "uint16", "4-nibble BCD 포맷 (예: 0x4001 -> V4.0.0.1)"]
|
||
],
|
||
col_widths=[Inches(0.8), Inches(2.2), Inches(0.8), Inches(3.0)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
doc.add_heading("▶ Status #3 (PF: 13) & Status #4 (PF: 14)", level=3)
|
||
create_styled_table(
|
||
doc,
|
||
headers=["응답 PF", "Byte", "항목", "상세 설명"],
|
||
data=[
|
||
["PF 13", "0 ~ 1", "battery_status.value", "내부 상태 레지스터 (Bit 0: INIT, Bit 1: CHG_C, Bit 2: DSG_C, Bit 3: ZVC_C, Bit 4: CB, Bit 5: ACB, Bit 6: TCA, Bit 7: TDA 등)"],
|
||
["PF 13", "2 ~ 3", "protect.value", "16비트 전체 보호 플래그 레지스터"],
|
||
["PF 13", "4 ~ 7", "Reserved", "0x00"],
|
||
["PF 14", "0 ~ 1", "COV Protect Cell Mask", "과전압 보호 발생 셀 비트마스크 (Bit 0~14: Cell 1~15)"],
|
||
["PF 14", "2 ~ 3", "COV Warning Cell Mask", "과전압 경보 발생 셀 비트마스크"],
|
||
["PF 14", "4 ~ 5", "CUV Protect Cell Mask", "저전압 보호 발생 셀 비트마스크"],
|
||
["PF 14", "6 ~ 7", "CUV Warning Cell Mask", "저전압 경보 발생 셀 비트마스크"]
|
||
],
|
||
col_widths=[Inches(0.9), Inches(0.8), Inches(2.1), Inches(3.0)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 4.2 셀 개별 전압
|
||
h2 = doc.add_heading("4.2 셀 개별 전압 계측 (PF: 19 → 191 ~ 194)", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
p = doc.add_paragraph("호스트가 PF=19를 요청하면 BMS는 4개의 연속 패킷을 5ms 간격으로 전송합니다. 전압 단위는 1mV입니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["응답 PF", "Byte 0 ~ 1", "Byte 2 ~ 3", "Byte 4 ~ 5", "Byte 6 ~ 7", "단위"],
|
||
data=[
|
||
["PF 191", "Cell #1 전압", "Cell #2 전압", "Cell #3 전압", "Cell #4 전압", "1 mV"],
|
||
["PF 192", "Cell #5 전압", "Cell #6 전압", "Cell #7 전압", "Cell #8 전압", "1 mV"],
|
||
["PF 193", "Cell #9 전압", "Cell #10 전압", "Cell #11 전압", "Cell #12 전압", "1 mV"],
|
||
["PF 194", "Cell #13 전압", "Cell #14 전압", "Cell #15 전압", "Cell #16 전압 (15S는 0)", "1 mV"]
|
||
],
|
||
col_widths=[Inches(1.1), Inches(1.3), Inches(1.3), Inches(1.3), Inches(1.3), Inches(0.5)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER] * 6
|
||
)
|
||
|
||
# 4.3 온도 계측
|
||
h2 = doc.add_heading("4.3 온도 계측 모니터링 (PF: 3 → 31, 32)", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
p = doc.add_paragraph("온도 데이터는 Offset: +800 (0.1℃ 단위) 형식으로 전송됩니다.\n• 실제 온도(℃) = (수신값 - 800) / 10 (예: 25.0℃ = 1050 전송, -10.0℃ = 700 전송)")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["응답 PF", "Byte 0 ~ 1", "Byte 2 ~ 3", "Byte 4 ~ 5", "Byte 6 ~ 7", "비고"],
|
||
data=[
|
||
["PF 31", "Temp #1 (Cell 1)", "Temp #2 (Cell 2)", "Temp #3 (Cell 3)", "Temp #4 (Cell 4)", "배터리 셀 표면 서미스터"],
|
||
["PF 32", "Temp #5 (Cell 5)", "Temp #6 (Cell 6)", "Ext #1 (BMS기판)", "Ext #2 (FET/주변)", "BMS 내부 환경 온도"]
|
||
],
|
||
col_widths=[Inches(1.0), Inches(1.3), Inches(1.3), Inches(1.3), Inches(1.3), Inches(1.6)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 4.4 셀 전압 및 온도 편차 파라미터 통합 패킷 (KT 핵심 규격)
|
||
h2 = doc.add_heading("4.4 셀 및 온도 편차 파라미터 (PF: 2 ↔ 21) [KT 핵심 요구사항]", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
p = doc.add_paragraph("• 조회: Data[0~7]=0xFF 전송 시 BMS가 현재 설정값으로 PF=21 응답\n• 설정: 아래 8바이트 값을 담아 PF=2 전송 시 파라미터 갱신 (RAM 운용, 리셋 시 초기값 복원)\n• 독립적 갱신: 각 임계값(Threshold)이 0 또는 0xFFFF인 경우 해당 파라미터(임계치 및 시간)는 변경하지 않고 기존값을 유지합니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["Byte", "파라미터 항목", "타입", "기본값 (KT 규격)", "상세 설명"],
|
||
data=[
|
||
["0 ~ 1", "Cell Voltage Diff Threshold", "uint16", "200 mV", "셀 전압 편차 차단 기준치 (KT 규격: 200mV 이상 지속 시 차단, 0/0xFFFF 시 유지)"],
|
||
["2 ~ 3", "Temperature Diff Threshold", "uint16", "30 (3.0℃)", "온도 편차 차단 기준치 (0.1℃ 단위, 3.0℃ 이상 지속 시 차단, 0/0xFFFF 시 유지)"],
|
||
["4 ~ 5", "Cell Voltage Diff Hold Time", "uint16", "1440 min (24h)", "셀 전압 편차 지속 보호 시간 (분/min 단위, 0/0xFFFF 시 유지)"],
|
||
["6 ~ 7", "Temperature Diff Hold Time", "uint16", "1440 min (24h)", "온도 편차 지속 보호 시간 (분/min 단위, 0/0xFFFF 시 유지)"]
|
||
],
|
||
col_widths=[Inches(0.8), Inches(2.2), Inches(0.8), Inches(1.3), Inches(2.4)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
make_callout_box(
|
||
doc,
|
||
"24시간 지속 차단 로직: 셀 전압 편차가 200mV 이상으로 24시간(1,440분 = 86,400초) 동안 지속 유지될 경우 protect.bit.cell_voltage_diff 플래그가 확정되어 영구 차단됩니다. 온도 편차 역시 3.0℃ 이상으로 24시간 지속 유지 시 protect.bit.temp_diff 플래그가 확정되어 차단됩니다. CAN 프레임 Byte 4~5와 Byte 6~7을 통해 전압편차 시간과 온도편차 시간을 분(min) 단위로 각각 독립 설정 및 모니터링할 수 있습니다.",
|
||
title="KT 핵심 요구사항 (셀 전압 200mV 및 온도 3.0℃ 24시간 차단 통합 제어)"
|
||
)
|
||
|
||
# 4.5 기타 보호 파라미터
|
||
h2 = doc.add_heading("4.5 보호 및 복구 파라미터 셋업 규격", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["기능", "요청/응답 PF", "Byte 0 ~ 1", "Byte 2 ~ 3", "Byte 4 ~ 5", "Byte 6 ~ 7"],
|
||
data=[
|
||
["셀 전압 보호", "5 / 51", "CUV Warning (mV)", "CUV Trip (mV)", "COV Warning (mV)", "COV Trip (mV)"],
|
||
["전압 복구", "23 / 231", "CUV Recovery (mV)", "COV Recovery (mV)", "SUV Recovery (0.01V)", "SOV Recovery (0.01V)"],
|
||
["팩 전압 보호", "7 / 71", "SUV Warning (0.01V)", "SUV Trip (0.01V)", "SOV Warning (0.01V)", "SOV Trip (0.01V)"],
|
||
["온도 보호", "6 / 61", "충전저온 W/T (+80)", "충주고온 W/T (+80)", "방전저온 W/T (+80)", "방전고온 W/T (+80)"],
|
||
["온도/전류 복구", "24 / 241", "충전저온/고온 복구", "방전저온/고온 복구", "충전과전류 복구(+3만)", "방전과전류 복구(+3만)"],
|
||
["과전류 보호", "22 / 221", "충전과전류 W (+3만)", "충전과전류 T (+3만)", "방전과전류 W (+3만)", "방전과전류 T (+3만)"]
|
||
],
|
||
col_widths=[Inches(1.2), Inches(1.0), Inches(1.3), Inches(1.3), Inches(1.3), Inches(1.3)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
make_callout_box(
|
||
doc,
|
||
"반복 OCC 3회 영구 래치 차단 (Lockout): 충전 과전류(OCC)가 1분 이내 3회 반복 발생하면 OCC_3Times_Fail = true 로 래치되어, 충전 전류가 정상으로 내려가더라도 자동 복구되지 않고 충전 스위치가 영구 차단 유지됩니다.",
|
||
title="KT 핵심 요구사항 (OCC 3회 영구 래치)"
|
||
)
|
||
|
||
# 4.6 하트비트 및 RTC
|
||
h2 = doc.add_heading("4.6 하트비트 및 시스템 일시(RTC) 동기화", level=2)
|
||
h2.paragraph_format.space_before = Pt(10)
|
||
h2.paragraph_format.space_after = Pt(4)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["기능", "PF", "바이트 구성", "설명"],
|
||
data=[
|
||
["하트비트 & 카운트다운", "20 -> 201", "Byte 0~3: Heartbeat 카운터 (ms Tick, Big-Endian)\nByte 4~5: 예약 (0x0000)\nByte 6~7: 편차 차단 잔여 시간 (GetCvDiff(), 초)", "셀 전압(200mV) 및 모듈온도(3.0℃) 편차 차단까지 남은 시간. 0초 도달 시 충전 차단 후 MCU 리셋 전까지 영구 래치."],
|
||
["RTC 일시 동기화", "21 -> 211", "Byte 0: 연 (Year - 1970, 예: 2026년 = 56)\nByte 1: 월 (1~12) Byte 2: 일 (1~31)\nByte 3: 시 (0~23) Byte 4: 분 (0~59) Byte 5: 초 (0~59)\nByte 6~7: [응답] 최근 리셋 원인 (Reboot Reason)", "상위 제어 시스템 시간과 BMS 내부 RTC 클럭 동기화 및 리셋 원인 진단"]
|
||
],
|
||
col_widths=[Inches(1.3), Inches(0.9), Inches(2.9), Inches(2.3)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 5
|
||
h1 = doc.add_heading("5. 인벤토리 및 식별자 관리 프로토콜 (Index = 1)", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
p = doc.add_paragraph("배터리 팩 모듈의 생산 이력 및 부품 추적성을 위해 제조일자, BMS 일련번호, 모듈 일련번호를 전용 채널(Index = 1)로 관리합니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["요청 PF", "응답 PF", "식별자 항목", "데이터 포맷", "크기 및 내용"],
|
||
data=[
|
||
["1", "11", "BMS 제조일자 (Manufacture Date)", "4 Bytes (uint32)", "YYYYMMDD 숫자형 정수"],
|
||
["2", "21", "BMS Serial Number (상위 8자리)", "8 Bytes ASCII", "BMS 보드 고유 일련번호 [0~7]"],
|
||
["3", "31", "BMS Serial Number (하위 8자리)", "8 Bytes ASCII", "BMS 보드 고유 일련번호 [8~15] (총 16자리)"],
|
||
["4", "41", "Module Serial Number #1", "8 Bytes ASCII", "배터리 팩 모듈 일련번호 [0~7]"],
|
||
["5", "51", "Module Serial Number #2", "8 Bytes ASCII", "배터리 팩 모듈 일련번호 [8~15]"],
|
||
["6", "61", "Module Serial Number #3", "8 Bytes ASCII", "배터리 팩 모듈 일련번호 [16~23]"],
|
||
["7", "71", "Module Serial Number #4", "8 Bytes ASCII", "배터리 팩 모듈 일련번호 [24~31] (총 32자리)"]
|
||
],
|
||
col_widths=[Inches(0.8), Inches(0.8), Inches(2.2), Inches(1.5), Inches(2.1)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 6
|
||
h1 = doc.add_heading("6. 펌웨어 무선/원격 다운로드 프로토콜 (Index = 4)", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
p = doc.add_paragraph("통합 관제 프로그램(LFP_Manager_GL)에서 현장 BMS 모듈의 펌웨어를 원격 업데이트할 때 사용하는 프로토콜(Index = 4)입니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["기능 코드 (FC)", "명칭 (Command)", "설명 및 동작"],
|
||
data=[
|
||
["0", "DL_START", "F/W 업데이트 모드 진입 지시 (BMS 수락 확인)"],
|
||
["1", "DL_SECTOR_ERASE", "타깃 MCU Flash App 섹터 삭제 지시"],
|
||
["2", "DL_SET_ADDRESS", "기록할 Flash 시작 번지 주소 설정"],
|
||
["3", "DL_READ_DATA", "Flash 기록 데이터 사후 검증용 읽기"],
|
||
["4", "DL_WRITE_DATA", "바이너리 블록 데이터 전송 및 플래시 Write"],
|
||
["5", "DL_WRITE_DATA_CSUM", "전송 블록별 Checksum 무결성 검증"],
|
||
["6", "DL_IMAGE_CSUM", "전체 펌웨어 바이너리 최종 Checksum 확인"],
|
||
["7", "DL_RESTART", "다운로드 완료 후 부트로더 재부팅 및 신규 펌웨어 기동"]
|
||
],
|
||
col_widths=[Inches(1.2), Inches(2.0), Inches(4.2)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
# 섹션 7
|
||
h1 = doc.add_heading("7. GUI 통신 시퀀스 및 폴링 가이드 (LFP_Manager_GL)", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
p = doc.add_paragraph("LFP_Manager_GL 관제 프로그램은 CAN 버스 트래픽 과부하를 방지하면서도 실시간 모니터링을 달성하기 위해 다음과 같은 2계층 폴링 주기를 적용합니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["폴링 계층", "주기", "요청 커맨드 (PF)", "수집 정보"],
|
||
data=[
|
||
["1계층: 고속 실시간 상태", "1.0 초", "PF: 1 (Status Req)", "팩전압, SOC, 전류, 알람/보호 비트, 충방전 운용상태"],
|
||
["2계층: 정밀 계측 수집", "2.0 ~ 3.0 초", "PF: 19 (Cell V)\nPF: 3 (Temp)\nPF: 4 (SOC/SOH)", "16개 셀 전압, 8개 채널 온도, SOH, 충방전 사이클 수"],
|
||
["파라미터 화면 진입 시", "1회성 일괄 조회", "PF: 5, 6, 7, 14, 22, 23, 24, 2", "전압/온도/전류 보호 임계치 및 복구치 일괄 동기화 (50ms 간격)"]
|
||
],
|
||
col_widths=[Inches(1.8), Inches(1.1), Inches(2.0), Inches(2.5)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT]
|
||
)
|
||
|
||
make_callout_box(
|
||
doc,
|
||
"통신 지연 권장값: BMS가 복수 개의 응답 패킷을 전송할 때 패킷 간 최소 5ms 딜레이(CAN_TX_CONT_DELAY = 5ms)를 두어 CAN 버퍼 오버런을 방지합니다. 호스트 응답 대기 타임아웃은 500ms를 권장합니다.",
|
||
title="통신 타이밍 권장사항"
|
||
)
|
||
|
||
# 섹션 8
|
||
h1 = doc.add_heading("8. 하드웨어 3종별 펌웨어 구현 및 검증 결과", level=1)
|
||
h1.paragraph_format.space_before = Pt(14)
|
||
h1.paragraph_format.space_after = Pt(6)
|
||
|
||
p = doc.add_paragraph("본 CAN 프로토콜 규격은 하드웨어 3종에 각각 최적화된 펌웨어에 구현되어 있으며, Keil MDK ARMCC 컴파일러를 통해 빌드 검증 및 GUI 연동 테스트를 완료하였습니다.")
|
||
p.paragraph_format.space_after = Pt(6)
|
||
|
||
create_styled_table(
|
||
doc,
|
||
headers=["구분", "하드웨어 스펙", "관련 구현 소스 파일", "빌드 검증 결과"],
|
||
data=[
|
||
["V1 펌웨어\n(mBMS_APP_V2403)", "STM32F407VGT6\nTI BQ76PL455 (UART)\n외부 릴레이 제어 (N-MOSFET 구동)", "APP/can_comm.c\nAPP/can_data_process.c\nAPP/can_update_process.c", "98개 C 파일 전체 빌드 성공\n(0 Error, ARMCC v5.06)"],
|
||
["V2 펌웨어\n(APP_R3221)", "HC32F460PETB\nTI BQ76940 (I2C)\n외부 릴레이 제어 (N-MOSFET 구동)", "App/Source/can_comm.c\nApp/Source/can_data_process.c\nApp/Source/can_inv_data_process.c", "28개 C 파일 전체 빌드 성공\n(0 Error, ARMCC v5.06)"],
|
||
["V3 펌웨어\n(APP_R3323)", "HC32F460PETB\nADI ADBMS1818 (SPI)\n외부 릴레이 제어 (N-MOSFET 구동)", "App/Source/can_comm.c\nApp/Source/can_data_process.c\nApp/Source/can_inv_data_process.c", "27개 C 파일 전체 빌드 성공\n(0 Error, ARMCC v5.06)"]
|
||
],
|
||
col_widths=[Inches(1.5), Inches(1.8), Inches(2.4), Inches(1.7)],
|
||
alignments=[WD_ALIGN_PARAGRAPH.CENTER, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.LEFT, WD_ALIGN_PARAGRAPH.CENTER]
|
||
)
|
||
|
||
output_path = r"d:\Project\Firmware\KT\mBMS\R4xx\Docs\Protocol\KT_mBMS_CAN_통신프로토콜_규격서.docx"
|
||
try:
|
||
doc.save(output_path)
|
||
print(f"Successfully generated: {output_path}")
|
||
except PermissionError:
|
||
output_path_alt = r"d:\Project\Firmware\KT\mBMS\R4xx\Docs\Protocol\KT_mBMS_CAN_통신프로토콜_규격서_Rev1.docx"
|
||
doc.save(output_path_alt)
|
||
print(f"File locked by Word. Successfully generated alternative: {output_path_alt}")
|
||
|
||
if __name__ == "__main__":
|
||
build_protocol_document()
|