목차
대회 끝
대회가 끝났다. 순서 새치기를 위해 3월 28일로 작성하지만 오늘은 8월 30일이다. 우리 팀은 검차에서 탈락했다. 대회 전날까지 2주간 열몇명이 5시간씩 자면서 날밤을 샜지만 결국 차를 완성시키지 못했다. 미완성인 차가 검차에서 탈락하는 것은 자명한 일이다. 우리는 대회날 아침까지 차단 회로의 FAULT 작동조차 단 한 번도 검증해보지 못했다. 배터리 검차는 겨우 통과시켰지만 전기시스템 검차는 무리였다.
전기차에 처음으로 도전하는 팀에서 1년만에 밑바닥부터 공부해 회로를 설계하고 PCB로 만들면서 정말 눈물겨운 삽질을 수도 없이 해야 했다. 한 개 고치면 세 개가 새로 튀어나오는 치명적인 설계 결함들로 날밤을 샜던 걸 떠올리니 치가 떨린다. 만능기판 대신 예쁘게 만드려고 뜬 PCB는 결국 뜯어내고 새로 연결한 부품들로 만신창이가 됐다.
차량의 12V 계통, 12V를 맞으면 타버리는 작고ㄹ 소중한 논리 회로 소자들, 전선이 소시지보다 두꺼운 300V HV 세 개가 한 기판에 어우러져야 하는 것이 전기차의 전기시스템파트다. 절연부터 풋프린트까지 고려해야 할 온갖 요소가 정말 산더미인데 놀랍게도 아무도 우리에게 단 하나도 알려주지 않았다.
그러한 이유로 이후에 새로 입문하고 도전하는 사람들에게 전해주고 싶은, 1년동안 돈과 피와 땀을 흘리며 얻은 리빙 포인트를 몇 가지 먼저 적어보려 한다.
우리 팀이 검차 통과를 못 했다 하여 혹시 불안해 하는 사람이 있을까봐 말씀드린다. 확실히 검증이 끝난 회로가 아니면 이후의 상세 설명 글에서도 검증되지 않았다고 언급할 것이니 다른 것들은 안심하셔도 된다.
리빙 포인트
0. 설계한 회로는 블랙박스 검증 전까지 절대 믿지 마세요.
우리는 학부생 나부랭이다. 학교에서 회로 실험과목들 들으면서 이미 검증된 회로들만 직접 만들어 보는 것과, 입력 신호랑 내가 원하는 출력 신호만 가지고 그 중간 회로를 부품까지 골라가며 설계해야 하는 실무 회로설계는 그냥 완전히 다른 문제였다.
회로 실무설계를 기본부터 배운 것도 아닌데, 우리는 그럴 시간도 없이 일단 원하는 동작을 하는 회로 몇 개를 뚝딱 만들어야 한다. 당연히 처음 설계한 회로는 절대로 작동하지 않는다. 개발된지 정확히 50년이나 지난 555 타이머도 우리가 설계한 회로에 집어넣어보니 원하는 대로 작동하지 않았다. 각 IC의 사용법을 완벽히 모르는 상태에서 설계한 회로는 믿어서는 안 된다.
회로 검증은 철저하게 해야 한다. 아날로그 입력 두 개를 받아서 원하는 전압과 비교하고 두 비교기 출력이 모두 참일때만 타이머를 원하는 시간으로 작동시켜서 원하는 디지털 출력을 내야 하는 BSPD를 예로 들어보자.
- BSPD의 아날로그 입력 두 개는 각각 LM311로 들어가 전압을 비교하여 디지털 출력 신호로 바뀐다. 빵판에 꽂아서 해봤더니 잘 된다. 오케이 다음.
- LM311 두 개의 디지털 출력은 74LVC1G08GV AND게이트로 들어가 두 출력이 모두 참일 때만 신호를 출력해야 한다. 해보니 된다. 좋아 다음.
- 74LVC1G08GV AND게이트는 쥐꼬리만 한 부품이라 핀을 따서 출력을 잇기가 쉽지 않았다. 그냥 5V 디지털 출력이 나올 것이라 치고 5V로 555 타이머를 켰다. 타이머는 원하는 대로 작동한다. 다음 단계.
- 타이머 출력도 5V이니, MOSFET 하나를 뒤에 붙여 스위칭을 해서 BSPD 디지털 출력을 만든다.
- 다 됐겠지? 설계한 회로로 PCB를 만든다.
- 5일 걸려 PCB가 온다. 부품 올려 납땜하고 테스트해 본다.
- 어림도 없지. 안 된다.
회로가 처리해야 하는 일련의 동작들을 여러 부분으로 나누어 각각 테스트하고서 됐겠지? 하며 만족해서는 절대 안 된다. 다 합쳐보면 제대로 작동하지 않는다. 될 거라 치고는 경계해야 한다. 반드시…
555 타이머는 출력단 전압에서 1V 정도의 전압 강하가 일어난다. 타이머 뒷단의 MOSFET은 5V로는 작동하지만 4V는 문턱 전압에 아슬아슬하게 걸려 작동을 보장하기 어렵다. 타이머 출력은 5V가 나오겠지? 라는 안일한 마인드로 타이머 대신 파워서플라이를 사용해 MOSFET을 테스트한다면 나중에 눈물을 흘리게 될 것이다.
회로 검증은 반드시 입력 신호 두 개, 출력 신호 하나. 그리고 중간에 우리가 설계한 회로. 이렇게 해야 한다. 중간에 무슨 일이 일어나는지는 모르겠고 입력을 줬을 때 출력이 완벽하게 나오지 않는다면 다시 해야 한다.
1. 쓰기 어려운 부품은 고르지도 말고, 고른 건 넉넉하게 구매하세요. 꼭…
여기서 쓰기 어렵다 함은 다음을 의미한다.
- 눈에 잘 보이지도 않는 쥐꼬리만 한 SMD 타입 IC
- 비싼데 구하기도 힘들고 체결했다 분리하기 어려우며 툭하면 클램핑이 빠지는 커넥터들
- 기타 테스트하는데 많은 수고가 드는 물리적으로 쓰기 어려운 부품들
우리는 BSPD 회로에 BD77501과 74LVC1G08GV이라는, 길이가 3mm인데 핀이 한 쪽에 세 개씩 달려있는 눈꼽만한 패키징의 IC들을 포함시켰다. 문제는 회로라는 게 처음 설계가 절대로 완벽하게 작동하지 않는다는 것이다. 테스트하다 보면 5V 걸어야 하는데 실수로 12V 걸어서 어디선가 연기가 퐁퐁 샘솟는 일이 하루가 멀다하고 생긴다.
죽은 부품을 교체하고 다시 테스트해야 하는데 부품이 핸드폰 상단 카톡 알림 아이콘만 하다. 제거하고 다시 땜질하는데 20분이 걸린다. 몇 번 반복하면 PCB의 도금이 벗겨저 나가 납땜이 안 먹는다. PCB라면 차라리 낫다. 만능기판이라면 0.7mm 간격 다리에 에나멜선을 하나씩 달아줘야 한다.

죽여줘...
그러나 74LVC1G08GV AND게이트는 사실 트랜지스터 두 개만 있으면 구현할 수 있다.

BD77501은 노이즈 저항성(?)을 키우기 위해 잘 모르는데 달았었다. 없어도 일단 작동은 된다. 이런건 과감히 빼자.
LM311 비교기, 2N3904/2N2222 범용 트랜지스터, AMS1117 레귤레이터 이런 애들은 개당 몇 백원, 몇 십원밖에 안 한다. 회로에 2개 들어가니까 여유롭게 5배수로 10개 사야지~ 하지 말고 그냥 20배수로 구매하길 권장한다. 2N3904는 100개씩 사도 배송비 값밖에 안 나온다. 테스트하면서 부품 죽고 다리 잘리고 교체해야 할 일이 허다하다. 부족해서 새로 시키고 기다리는 것보다 많이 사는게 천만 배 낫다.
+ PCB 풋프린트 꼭 확인하세요. 납땜하기 편한 걸로..
2N3904는 TO-92 패키징을 사용한다. 근데 TO-92에는 다리가 두 가지 종류가 있다.

다리가 포크처럼 생긴 애가 있고, 오징어처럼 생긴 애가 있다. 길쭉한 건 핀 간격이 1.27mm이고, 포크같은 건 일반적인 만능기판에 딱 맞도록 그걸 2.54mm 간격으로 늘려놓은 것이다. 당연히 2.54mm 쪽이 훨씬 납땜하기 편하고 풋프린트 거리도 멀다. 가능한 한 이런걸 사용해야 나중에 기판에서 수정하기 편하다.
별도의 GPS같은 모듈을 설계한 PCB 위에 올릴 때도 꼭 실제 모듈 사이즈를 실측한 후에 풋프린트 크기를 정하길 바란다. 실측하지 않고 그냥 제조사에서 준 걸 쓰면 꼭 사이즈가 실제보다 작아 부품 간에 간섭이 일어나고, 5만 원짜리 PCB를 새로 떠야 하는 불상사가 생긴다.
2. 처음부터 오버스펙 설계하지 말고 다 되면 나중에 개선하세요.
위의 쓰기 어려운 부품은 빼라는 말과 궤를 같이 하는 내용이다. 회로 소형화와 노이즈 저항성 강화 등 안정화는 일단 작동이 되면 그 이후에 고민할 일이다. 최초 설계때부터 이런 걸로 골머리앓지 말고 일단 작동하는 회로를 만들도록 하자.
우리는 차가 달릴 때 진동으로 커넥터가 분리되는 것을 막겠다고 실제 상용차에 들어가는 몰렉스의 DuraClik™ 커넥터 시리즈를 사용했다. 이것들은 커넥터 하나에 이천 원씩 하면서 끼울 땐 더럽게 안 끼워지고, 뺄 땐 더 안 빠지다가 부러진다. 클램핑도 잘 안 돼서 터미널 끼웠는데 터미널이 커넥터에서, 전선이 터미널에서 빠져버린다. 이러면 되살릴 수도 없다. 클램핑 했는데 인접한 두 터미널이 합선이 나 있는 기상천외한 문제도 있었다.
PCB 대신 만능기판에 회로 만들어서 잘 굴리는 다른 학교들도 있는데, 초심자 주제에 욕심이 과했다. 공장에서 찍어내서 수십만 킬로미터씩 달려야 하는 양산차들에는 필요하겠지만, 1년만에 만들어서 6개월 굴릴 차를 처음 개발할 때에는 변화에 유연해야 하는 만큼 좀 마음을 비울 필요가 있다.
3. 신호의 출처와 종류를 정확히 파악하세요.
Floating과 LOW는 다르다
회로 설계하면서 머릿속 어딘가엔 있었지만 간과했던 사실이 하나 있다. 디지털은 HIGH와 LOW의 두 가지 상태만 존재하는 게 아니다. Z라는 플로팅 상태가 하나 더 있다. LM311은 입력단자 하나를 GND에 연결해서 0V를 입력했을 때는 제대로 동작해도, 아예 연결하지 않아 플로팅으로 두었을 때는 출력이 예상치 않게 나온다. 우리가 사용법을 제대로 익히지 않고 사용해서 그런 문제가 발생했는지는 모르겠으나, 아무것도 연결하지 않았을 때 당연히 멀티미터에는 0V가 찍히지만 비교가 제대로 되지 않아서 멀쩡한 회로를 다 뜯고 부품을 새로 교체하느라 하루를 꼬박 날리게 됐다. 로직 IC들을 사용할 때, 항상 플로팅에 유의해야 한다.
한편, 아두이노나 STM32같은 대부분의 마이크로컨트롤러는 디지털 출력에 플로팅 상태가 없다. HIGH는 확실히 자신의 로직 레벨(5V/3.3V)이고 LOW는 0V로 출력된다. 그러나 차량에 들어가는 몇 가지 부품들은 그렇지 않았다. 회로 설계를 하면서 몇 가지 외부 신호를 처리해야 했는데, 이 신호를 만드는 부품들은 IMD, BMS, 모터 컨트롤러 정도다. 설계할 회로는 이 부품들이 만드는 디지털 출력 신호를 가지고 차단 회로가 릴레이를 작동시켜 회로를 끊거나, ECU가 FAULT 상황을 인지하고 기록에 남기고 경고등 LED를 점등해야 한다.
그런데 BMS와 모터 컨트롤러의 디지털 출력은 정확히 HIGH/LOW로 갈리지 않고, MOSFET으로 제어되는 Open Drain이라는 방식을 사용해 신호를 출력한다. 이 방식에서 출력은 확실한 LOW와, Floating HIGH 상태로 나온다.
Open Drain MOSFET과 Open Collector BJT
적어도 내가 급하게 이해한 것으로는 출력단에 붙어있는 것이 MOSFET인 오픈 드레인 방식이 훨씬 전기적으로 강인하기 때문인 것으로 보인다. BMS나 컨트롤러가 제어해야 하는 것은 대개 주먹만한 릴레이라던가.. 그런 것이라 전류를 더 많이 먹기 때문이다. STM32보다 한참 강한 디지털 I/O를 갖고 있는 아두이노의 ATmega32도 50mA가 한계인데, 이러한 오픈 드레인 디지털 출력은 200mA정도는 버티는 듯 하다.
또 하나는 출력이 오픈 드레인이면 HIGH일 때 출력 전압을 내 맘대로 설정할 수 있다는 것이다. BMS 데이터시트를 읽고 Floating HIGH라는 것이 그냥 BMS 공급 전압으로 자동으로 HIGH가 나오는 줄 알았으나… 그런 것은 아니었고 풀업 걸고 전원에 물려줘야 HIGH가 내 전압으로 나오는 것이었다. 이걸 대회가 끝나고서야 명확히 이해했다. 오픈 컬렉터 (open collector) 및 오픈 드레인 (open drain) 출력 이 글이 도움이 됐다. 이 글 하나는 완벽히 이해하고 넘어가야 이후에 지장이 없을 것이다. 나는 그렇지 못해 지장이 있었다(…)
아날로그 신호
이 외에도, BSPD에 들어가야 하는 브레이크 압력 센서는 아날로그 전원을 모터 컨트롤러에게서 받고, 아날로그 신호만 BSPD LM311 입력으로 들어가도록 설계했었다. 그러나 알고 보니 모터 컨트롤러의 아날로그 전원은 LV와 분리되어 있는 신호였고, 심지어 데이터시트에 Analog ground(AGND) should NOT be connected to chassis ground 라고 NOT을 대문자로 강조 처리까지 하면서 적혀 있었다. 이걸 대회장 현장에서야 처음 알게 됐다. 대충 이 신호는 디지털/아날로그니까 이렇게 나오겠지 가 얼마나 위험한 생각인지 몰랐었다. 꼭 회로를 설계하기 전에 이 신호는 어떻게 처리해주어야 정상 작동할 수 있는지 생각하고 설계하도록 하자.
우리 팀은 BMS는 배터리파트 관할, 모터 컨트롤러는 파워트레인파트 관할이라 전기시스템파트인 우리 측에서 이러한 내용을 모두 파악하는데 미흡함이 있었다. 이렇게 파트 간에 주고받아야 하는 전기적 신호는 반드시 사전에 확인할 수 있도록 하길 바란다.
4. MCU가 이상하면 일단 그냥 메모리를 의심하세요. free()?
컴퓨터는 정직하다. 원격 계측 정보 수집 등에 사용하는 MCU가 있고, 문제가 있는데 매번 실행 때마다 조금씩 증상이나 멈추는 지점이 달라진다면 그냥 다 메모리 문제다. 어디선가 엄청난 확률로 중성미자 우주선이 날아와 내 STM32를 한 대 쳐서 메모리에 비트 하나를 뒤집는 게 아니라면 런타임 에러는 그냥 다 메모리 문제다.
아두이노처럼 메모리 신경 안 써도 대충 알아서 되는 거 쓰면 별로 해당사항 없다. 나는 STM32로 처음부터 끝까지 다 C로 짰었고, malloc()해놓고 free()하지 않는다거나, NULL 캐릭터를 잠시 소홀히 여겼다든가, 포인터를 좀 안일하게 사용하면 ECU가 부팅하고 몇 분쯤 후에 멈춰섰다.
아무렴 다른 프로젝트에서는 그냥 리셋해주면 그만이지만, 자동차가 달리다가 4분 후에 멈춰서면 얼마나 아찔한가. STM32의 SRAM은 작고 귀여운 192KiB이다. 램을 소중히 여기고 항상 의심하자.
그리고 STM32 개발할 때 STlink 꼭 하나 사서 쓰자. 처음에 뭣도 모르고 STlink 없이 시리얼로 프로그램 업로드하고 printf로 출력 찍어가면서 ECU 전체를 개발했는데 무슨 삽질이었나 싶다. 돈을 쓰면 개발이 쾌적해진다. 알리에서 몇 천원짜리 STlink 샀는데, 배송이 늦어서 대회 다 끝나고서야 도착했다.
5. Event Interrupt와 Timeout을 적절하게 사용하세요.
STM32를 쓰면서 한 가지 더 아두이노와 달랐던 점은 이벤트 처리 방식이었다. 아두이노는 대충 라이브러리 검색해서 인클루드하고 라이브러리 함수 호출하면 뒤에서 알아서 해줬지만, STM32에는 그딴 거 없다. 워낙 하드웨어 바리에이션이 다양해서 범용 라이브러리를 찾을 수 없는 것들이 많아 라이브러리를 셀프로 구현해야 했다. I2C든 시리얼이든 통신을 할 때 처리 방식이 대개 인터럽트와 타임아웃 방식으로 나뉘는데, 적어도 내가 이해한 차이는 다음과 같다.
Interrupt 방식
- CPU가 주변 장치(peripherals)한테 일을 시킨다. 일 시키고 자기는 할 거 한다.
- 주변 장치는 할 거 다 하면 CPU에 인터럽트를 날린다.
- CPU는 인터럽트 루틴으로 들어가서 무언가를 처리하고 나온다.
장점: CPU가 노는 시간 없이 일하도록 갈굴 수 있다. 특히 자동차처럼 GPIO 입력이나 CAN 메시지를 놓치지 않고 모니터링해야 하는 경우에 좋다.
단점: 순차적으로 처리해야 하는 통신 시퀸스에 굉장히 취약하다. 예를 들어 1602 LCD의 경우 처음 초기화 시 수ms 간격으로 여러 가지 초기화 메시지를 I2C 버스에 보내야 하는데, 이걸 다 송신 인터럽트로 구현하자니 머리가 깨질 것 같았다.
Timeout 방식
- CPU가 주변 장치한테 일을 시킨다. 일 시키고 끝날 때까지 지켜본다.
- 주변 장치는 일을 끝내고 레지스터에 완료 비트같은 걸 set한다.
- CPU는 그걸 보고 다음 할 일로 넘어간다. 설정한 timeout 시간동안 비트가 set되지 않으면 무언가 에러를 내든지 한다.
장점: 순차 처리에 좋다. Timeout 여유있게 주면 이전 작업이 끝나야 넘어가는 것을 하나의 context 안에서 확실하게 처리할 수 있다.
단점: 작업이 완료될 때까지 CPU가 논다. 운전자는 브레이크를 밟았는데 MCU가 LCD에 화면 그리는 거 기다리면서 멍때리고 있으면? 물론 브레이크는 그런 식으로 작동하지 않지만 아무튼 썩 탐탁한 상황은 아니다.
인터럽트 루틴에서는 복잡한 거 하지 마세요. 제발. 제발…
한 가지 중요한 것은 인터럽트 방식을 사용한다면 인터럽트 루틴(콜백) 안에서는 간단한 일만 해야 한다는 점이다.
예를 들어, UART 수신 인터럽트는 시리얼 통신 스트림에서 문자 하나가 도착할 때마다 발생한다. 문자 하나가 올 때마다 메모리를 할당해 문자열을 복사하고 strtok()으로 문자열을 나눈 뒤 원하는 문자열이 있나 찾아보고 있으면 무선 통신 모듈에 시리얼로 해당 문자열을 전송하고 그러면 이제 인터럽트 흐름이 나락으로 떨어진다.
인터럽트 처리하고 있는데 또 똑같은 인터럽트가 나면 어떻게 되는지 모르겠지만 일단 작동은 안 된다. 문제 찾기도 어렵다. 나는 그냥 전역 변수로 수신 버퍼와 수신 플래그를 하나씩 선언해 놓고, 인터럽트 루틴에서 수신이 완료되면 플래그를 set하도록 구현해 놓았다. 그럼 이제 main 함수의 무한 루프에서 호출되는 매니저 함수가 수신 플래그를 확인하고 유효하면 버퍼의 데이터를 처리한 다음 플래그를 reset하는 방식이다.