10 분 소요

지난주 금요일(10/30)에 우리 보드(SC6_0)를 ST 최신 BSP로 옮겨 커널 6.6 로그인까지 했다. 로그인이 됐다는 것은 CPU, 메모리, 콘솔, SD 카드가 동작한다는 뜻일 뿐이다. 충전기로 쓰려면 릴레이, CP, 전력량계 같은 주변장치가 살아야 한다.

오늘은 그 주변장치를 하나씩 켜고 시험했다. 결과부터 정리하면 다음과 같다.

항목 결과 DTS 변경
DOUT1~4 (J13), DIN1·2 ✅ 없음 (GPIO 그대로)
이더넷 ✅ DHCP, 외부 ping PHY 드라이버, rgmii-id
UART U3(J8), U8(J9), U5(전력량계) ✅ UART 4개 추가
I2C6 ATECC608B 보안칩 ✅ 깨우기 응답 I2C6 추가
릴레이 K1, K2 ✅ 소리, LED 없음 (GPIO 그대로)
CP PWM + ADC ✅ ±12V 출력과 측정 TIM2, ADC1 추가

0. 시험 방식

주변장치마다 같은 순서를 반복했다.

1. MYiR 5.4 DTS에서 해당 장치 설정을 찾는다 (핀, 옵션, 주석)
2. 회로도 넷리스트로 실제 연결을 확인한다
3. 6.6 커널 DTS에 옮기고 WSL에서 시험 컴파일
4. 빌드 PC에서 커널과 DTB만 다시 빌드 (bitbake -C compile linux-stm32mp)
5. SD 카드의 bootfs에서 uImage와 DTB만 교체
6. 보드에서 시험 명령 실행

커널과 DTB만 바꾸면 되기 때문에 한 바퀴에 10분 남짓 걸렸다. bitbake는 외부 DT 파일이 바뀐 것을 알아채지 못한다. 그래서 -C compile로 컴파일 단계부터 강제로 다시 돌렸다.

GPIO만 쓰는 장치(DOUT, DIN, 릴레이)는 DTS를 건드릴 필요가 없었다. 리눅스에서 GPIO 줄은 누구도 잡고 있지 않으면 사용자 공간에서 바로 쓸 수 있다.


1. GPIO — DOUT과 DIN

이미지에 들어 있는 libgpiod는 v2(2.1.3) 다. 인터넷 예제 대부분은 v1 문법이라 그대로 치면 안 된다.

# v2 문법: -c 로 칩 지정, 줄=값
gpioset -c gpiochip6 -t0 10=1     # PG10 High 후 바로 종료
gpioget -c gpiochip7 13           # PH13 읽기
gpiomon -c gpiochip7 13 6         # PH13, PH6 변화 감시

gpiochip 번호는 포트 순서와 같다. GPIOA = 0, B = 1 … H = 7, I = 8이다.

  • DOUT1~4(PG10, PG12, PI9, PI1): J13 표시 LED(LD19~22)가 하나씩 켜졌다.
  • DIN1(PH13), DIN2(PH6): gpiomon으로 rising/falling 이벤트가 찍혔다.

주의할 점: v2의 gpioset은 끝난 뒤에도 출력값이 유지된다. 끌 때는 명시적으로 =0을 써야 한다. 시험 중 한 줄은 백그라운드에 남은 gpioset 프로세스가 계속 잡고 있어서, 재부팅하고 나서야 풀렸다.


2. 이더넷 — 칩 이름부터 틀렸다

2-1. AR8035가 아니었다

금요일 글에서 이더넷 PHY를 AR8035라고 썼다. MYiR DTS와 드라이버 이름이 모두 ar8035였기 때문이다. 6.6에서는 AR8035용 드라이버(at803x) 대신 범용 드라이버(Generic PHY)가 붙었다. 그래서 “PHY ID를 읽을 때 PHY가 리셋 상태였을 것”이라고 추정하고 AR8035 ID를 DTS에 강제로 적었다.

그런데 커널이 읽은 PHY ID는 0x0000010a였다. AR8035는 0x004dd072다. 원래 MYiR OS(5.4)에서 같은 값을 확인해 보니 dmesg에 이런 줄이 있었다.

yzhang..get YT8511, abt to set 125m clk out, phyaddr=6, phyid=0000010a
yt8511_config_out_125m, phy clk out, val=0x008057

실제 칩은 Motorcomm YT8511이었다. 정리하면 이렇다.

  • MYiR의 myir,ar8035 드라이버는 리셋 핀(PG0)을 High로 올리는 것 말고는 아무것도 하지 않았다. ID가 안 맞아서 설정 코드가 건너뛰어졌다.
  • 실제 PHY 설정(125MHz 클럭 출력, 지연)은 MYiR 커널 안에 따로 들어 있는 YT8511 코드가 했다.
  • 5.4에서도 드라이버는 처음부터 Generic PHY였다.

교훈: 벤더 자료에 적힌 부품 이름도 실제로 읽은 ID와 대조해야 한다. 금요일 글의 AR8035 부분은 틀린 내용이다.

2-2. 6.6에서는 mainline 드라이버 하나로 해결

6.6 mainline에는 Motorcomm 드라이버(motorcomm.c)가 있다. ST 기본 설정에는 빠져 있어서 커널 설정 조각(fragment)으로 켰다.

# meta-sc6/recipes-kernel/linux/files/fragment-90-sc6.config
CONFIG_MOTORCOMM_PHY=y

이 드라이버는 초기화할 때 125MHz 클럭 출력과 RGMII 지연을 phy-mode에 맞춰 직접 설정한다. 그래서 MYiR 커널의 별도 코드가 필요 없다. DTS에서는 AR8035용 속성을 모두 지웠다.

stm32-dwmac 5800a000.ethernet end0: PHY [stmmac-0:06] driver [YT8511 Gigabit Ethernet]
stm32-dwmac 5800a000.ethernet end0: Link is Up - 100Mbps/Full

2-3. 링크는 올라왔는데 IP를 못 받는다

드라이버도 맞고 링크도 올라왔지만 DHCP가 실패했다. udhcpc로 직접 요청해도 no lease, failing이었다. 송신과 수신 중 어디가 막혔는지 카운터부터 봤다.

RX:  bytes packets errors dropped
   5569594   45154      0   17480
TX:  bytes packets errors dropped
     47369     155      0       0
  • RX 45,154개, 에러 0: 같은 네트워크의 다른 장비 패킷이 깨지지 않고 잘 들어온다. 수신은 정상이다.
  • TX 155개, 에러 0: MAC은 보냈다고 기록하지만 DHCP 서버가 응답하지 않는다. 보낸 패킷이 상대에게 제대로 도착하지 않는다.

받기는 되는데 보내기만 안 된다. RGMII에서 이런 증상은 대개 송신 쪽 지연(TX delay) 문제다.

2-4. 원인: rgmii-rxid가 100M 송신 지연을 0으로 만든다

RGMII는 데이터와 클럭이 같은 순간에 바뀐다. 받는 쪽이 데이터를 안정된 시점에 읽으려면 어딘가에서 클럭을 약 2ns 늦춰야 한다. STM32 MAC에는 이 지연 기능이 없으므로 PHY가 송신과 수신 지연을 모두 넣어야 한다.

처음에는 MYiR 설정값(레지스터 0x0C = 0x8057: RX 지연 켬, 1G용 TX 지연 5)에 가장 가까워 보이는 rgmii-rxid를 골랐다. 그런데 드라이버 코드를 보니 이렇게 되어 있었다.

case PHY_INTERFACE_MODE_RGMII_RXID:
	ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_DIS;   /* 1G TX 지연 = 2 */
	fe = YT8511_DELAY_FE_TX_DIS;                     /* 100M TX 지연 = 2 */
	break;
case PHY_INTERFACE_MODE_RGMII_ID:
	ge = YT8511_DELAY_RX | YT8511_DELAY_GE_TX_EN;    /* 1G TX 지연 = 15 */
	fe = YT8511_DELAY_FE_TX_EN;                      /* 100M TX 지연 = 15 */
	break;

YT8511은 100M용 TX 지연을 별도 레지스터(0x0D) 에 둔다. MYiR 코드는 이 레지스터를 건드리지 않아 칩 기본값을 썼다. 반면 6.6 드라이버는 rgmii-rxid에서 이 값을 2(거의 0)로 직접 써 버린다. 공유기가 100M 장비라 링크가 100M로 잡혔고, 그 상태에서 송신 지연이 사라진 것이다.

phy-mode = "rgmii-id"로 바꾸자 바로 해결됐다. ST 공식 보드 DK1도 rgmii-id를 쓴다.

udhcpc: lease of 192.168.45.200 obtained from 192.168.45.1
64 bytes from 8.8.8.8: seq=0 ttl=116 time=41.136 ms

남은 일은 두 가지다.

  • 1G 상대에서 TX 지연 15가 맞는지는 아직 시험하지 못했다.
  • MAC 주소: 칩 OTP가 비어 있어서 리눅스가 만든 로컬 주소를 쓰고 있다. 제품에는 정식 MAC이 필요하다.

3. UART — 핀 맵은 넷리스트에서

3-1. 장치 번호를 기존 OS와 맞췄다

UART 4개를 추가하면서 장치 번호(ttySTMn)를 MYiR 5.4 OS와 같게 맞췄다. 기존 문서와 설정(예: 전력량계 = /dev/ttySTM3)을 그대로 쓰기 위해서다.

aliases {
	serial0 = &uart4;	/* 콘솔 */
	serial1 = &usart3;	/* U3 LCD, RS232 → J8 */
	serial2 = &uart8;	/* U8 카드리더, RS232 → J9 */
	serial3 = &uart5;	/* U5 전력량계 PM1, Modbus */
	serial4 = &uart7;	/* U7 블루투스 */
};

핀 그룹은 ST 기본 그룹을 쓰지 않고 보드 DTS 안에 직접 정의했다. 같은 UART라도 ST 평가보드와 핀이 다르기 때문이다. 예를 들어 ST/MYiR 기본 uart5 그룹은 PB6/PB5를 쓰는데, 우리 보드에서는 그 핀이 I2C1_SCL과 LCD 리셋이다.

3-2. U3가 어디로 나가는지 회로도로 찾기

“U3가 보드 어디에 있느냐”는 질문에 KiCad 넷리스트(.xml)를 따라갔다.

/LCD_TX : U14 PD8 (USART3_TX) → U9 SP3232 T1IN → T1OUT → R79 10Ω → J8 4번
/LCD_RX : U14 PD9 (USART3_RX) ← U9 SP3232 R1OUT ← R1IN ← R80 10Ω ← J8 3번

PCB 파일의 패드 연결까지 대조해 정리한 결과다.

커넥터 UART 1 2 3 4 5
J8 (LCD) U3 ttySTM1 +12V +5V RX TX GND
J9 (카드리더) U8 ttySTM2 +5V RX TX GND —

둘 다 SP3232를 거친 RS232 레벨(±5.5V) 이다. 3.3V USB-TTL 변환기가 아니라 USB-RS232 케이블로 시험해야 한다. 커넥터 1번 핀에 전원(+12V, +5V)이 나오므로 시험용 케이블에는 연결하지 않는다.

3-3. PC와 주고받기, 그리고 HEX로 보기

stty -F /dev/ttySTM1 115200 raw -echo
echo "hello from SC6 U3" > /dev/ttySTM1      # 보드 → PC
cat /dev/ttySTM1                              # PC → 보드

PC 쪽에서 받은 값은 68 65 6c 6c 6f ... 55 33 0a였다. 마지막 0a는 echo가 붙인 줄바꿈(LF)이다.

보드에서 수신 데이터를 HEX로 보려고 cat | od -tx1을 쓰면 od가 16바이트를 모을 때까지 아무것도 안 보여 준다. 1바이트씩 읽어서 넘기는 방법이 확실하다.

while :; do dd if=/dev/ttySTM1 bs=1 count=1 2>/dev/null | od -An -tx1 | tr -d '\n'; done

U3와 U8 모두 양방향으로 통과했다. 송수신할 때 SP3232 옆의 TX/RX LED(LD5~LD8)도 깜빡인다.

3-4. 전력량계는 Modbus 요청 한 번으로

U5에는 보드 위의 전력량계(JSY-MK-163T)가 직접 붙어 있어서 케이블이 필요 없다. “주소 1, 레지스터 0부터 1개 읽기” 요청을 보냈다.

stty -F /dev/ttySTM3 4800 raw -echo
(timeout 1 cat /dev/ttySTM3 | od -An -tx1) & sleep 0.2
printf '\x01\x03\x00\x00\x00\x01\x84\x0A' > /dev/ttySTM3; wait
 01 03 02 01 63 f9 fd

주소 1, 정상 응답(03), 데이터 2바이트(0x0163)가 왔다. CRC f9 fd도 직접 계산해서 맞는 것을 확인했다. 통신 설정은 4800bps 8N1이다. 레지스터 값이 무슨 뜻인지는 데이터시트가 있어야 알 수 있다.


4. I2C6 — 보안칩 ATECC608B 깨우기

4-1. 무엇에 쓰려던 칩인가

I2C6에는 ATECC608B-SSHDA(Microchip 보안칩)가 있다. 회로도의 신호 이름은 HSM_SCL/HSM_SDA다. 개인키를 칩 안에 넣고 밖으로 못 꺼내게 하는 부품이다. 충전기에서는 OCPP 보안 프로필 3이나 ISO 15118 Plug & Charge의 인증서 키 보관에 쓸 수 있다.

넣은 의도는 문서에 남아 있지 않았다. -SSHDA는 공장에서 아무것도 설정되지 않은 빈 칩이고, EVerest도 이 칩을 기본 지원하지 않는다. 그래서 오늘은 통신 확인까지만 했다. 칩 설정과 잠금은 되돌릴 수 없는 작업이라 용도가 정해진 뒤에 한다.

4-2. 보안 영역 확인

I2C6은 STM32MP15에서 보안 영역에 둘 수 있는 장치다. 리눅스가 쓰려면 OP-TEE가 열어 줘야 한다. 확인해 보니 문제가 없었다.

  • OP-TEE ETZPC 설정에서 I2C6이 DECPROT_NS_RW(리눅스 읽기/쓰기 허용)로 되어 있다.
  • I2C6 클럭과 리셋은 DK1 SCMI dtsi가 이미 OP-TEE 쪽(SCMI)으로 연결해 두었다.

4-3. 깨우기 신호와 i2ctransfer의 안전장치

ATECC는 평소 잠들어 있다. SDA를 60µs 이상 Low로 잡으면 깨어나고, 깨어난 직후 읽으면 고정된 4바이트 04 11 33 43을 돌려준다.

i2c-tools로 이 신호를 만드는 흔한 방법은 주소 0x00에 0x00을 쓰는 것이다. 100kHz에서는 0x00 한 바이트가 약 80µs 동안 Low를 유지해서 깨우기 조건을 채운다. 400kHz에서는 약 20µs라 부족하다. 그래서 MYiR의 400kHz 대신 100kHz로 설정했다.

첫 시도는 이렇게 실패했다.

Error: Chip address out of range (0x08-0x77)!

i2ctransfer는 예약 주소(0x00)로 보내는 것을 기본으로 막는다. -a 옵션을 붙여야 한다.

i2ctransfer -y -a 0 w1@0x00 0x00; i2ctransfer -y 0 r4@0x60
Error: Sending messages failed: No such device or address   ← 0x00엔 장치가 없으니 정상
0x04 0x11 0x33 0x43                                         ← 깨우기 응답

5. 릴레이 K1/K2 — 시험 전에 전원부터 확인

넷리스트로 구동 회로를 따라가 보니 이런 구조였다.

  K1 (주 릴레이) K2 (보조 릴레이)
제어 핀 PB8 (C_AC_ON) PE11 (B_AC_ON)
구동 IRLML6244 N-MOSFET, 게이트 100Ω, 풀다운 10K 같음
접점 L·N 모두 → 충전 출력 단자 L → J16 (보조 AC 출력)
표시 LED LD24 LD23

여기서 하나를 확인해야 했다. 릴레이 코일의 +12V는 AC 입력에서 만들어진다. AC-DC 모듈(MFM-20-12)이 AC_LI/AC_NI를 받아 +12V를 공급한다. 보드를 AC로 켠 상태에서 K1을 붙이면 충전 출력 단자에 220V가 그대로 나온다. 출력 쪽에 아무것도 연결하지 않은 상태에서 시험했다.

gpioset -c gpiochip4 -t0 11=1    # K2 켜기 (PE11)
gpioset -c gpiochip1 -t0 8=1     # K1 켜기 (PB8)

둘 다 딸깍 소리와 함께 LED가 켜졌다. MYiR는 이 두 핀을 PWM 타이머(TIM4_CH3, TIM1_CH2)로 설정해 두었다. 릴레이를 붙인 뒤 유지 전류를 줄이려는 용도로 보인다. 이 부분은 나중에 EVerest 보드 지원 모듈을 만들 때 정한다.


6. CP PWM과 ADC — 장비 없이 출력 확인하기

6-1. CP 회로

CP(Control Pilot)는 충전기와 차량이 상태를 주고받는 선이다. 충전기는 ±12V 1kHz PWM을 내보내고, 듀티로 허용 전류를 알린다.

PA15 (TIM2_CH1) → U4 VOFD341A (절연 게이트 드라이버) → ±12.4V 절연 전원
  → R22 1kΩ → CP (J6 1번, J6 2번 = PE)
               └→ U1 → U22 AMC1200 (절연 증폭기) → AD_CP (ADC1 IN0)

두 가지가 눈에 띄었다.

  • CP 회로는 보드 GND와 절연되어 있다. 멀티미터로 잴 때는 J6 2번(PE)을 기준으로 잡아야 한다.
  • CP 출력이 절연 증폭기를 거쳐 ADC로 다시 들어온다. 그래서 PWM과 ADC를 함께 켜면 오실로스코프 없이도 출력을 확인할 수 있다.

6-2. DTS

&timers2 {
	/delete-property/dmas;
	/delete-property/dma-names;
	status = "okay";
	pwm {
		pinctrl-0 = <&pwm2_ch1_pins_sc6>;	/* PA15 AF1, 풀다운 */
		status = "okay";
	};
};

&adc {
	vdd-supply = <&vdd>;
	vdda-supply = <&vdd>;
	vref-supply = <&vdd>;		/* 3.3V */
	status = "okay";
	adc1: adc@0 {
		status = "okay";
		channel@0 { reg = <0>; label = "AD_CP";   st,min-sample-time-ns = <10000>; };
		channel@1 { reg = <1>; label = "AD_TEMP"; st,min-sample-time-ns = <10000>; };
	};
};

ADC 기준전압을 3.3V로 둔 데는 이유가 있다. 회로는 2.5V 기준으로 설계됐지만, 10/21에 MYiR 5.4에서 시험해 보니 내부 2.5V 기준(VREFBUF)이 timed out으로 켜지지 않았다. 모듈이 VREF+ 핀을 3.3V에 묶어 둔 것으로 보인다. PA15는 ST 기본 CEC 그룹도 쓰는 핀이라, CEC가 꺼져 있는지도 DTB에서 확인했다.

6-3. 시험

듀티를 바꿔 가며 AD_CP를 8번씩 읽었다.

P=/sys/class/pwm/pwmchip0; A=/sys/bus/iio/devices/iio:device0
echo 0 > $P/export; echo 1000000 > $P/pwm0/period; echo 1 > $P/pwm0/enable
for d in 1000000 0 500000 50000; do
  echo $d > $P/pwm0/duty_cycle; sleep 0.5
  echo "duty $((d/10000))%: $(for i in 1 2 3 4 5 6 7 8; do cat $A/in_voltage0_raw; done | tr '\n' ' ')"
done
duty 100%: 47973 47977 47984 47994 47992 48000 47972 48036
duty 0%: 996 1021 1007 1003 1032 1004 997 1014
duty 50%: 47994 47956 1012 48003 1031 959 48049 1005
duty 5%: 959 1034 994 1014 985 982 1025 1017
듀티 CP AD_CP 판단
100% +12V 고정 약 47,990 (2.42V) 안정적
0% −12V 고정 약 1,010 (0.05V) 안정적
50% 사각파 두 값이 4번씩 섞임 읽는 순간이 High인지 Low인지에 따라

스케일은 0.050354 mV/LSB(3.3V, 16비트)다. CP −12V~+12V가 ADC 입력 0.05V~2.42V로 들어오므로 3.3V 안에서 잘리지 않는다. 두 값의 가운데는 약 1.23V인데, 회로가 CP 0V에 맞춰 만든 기준 분압값(R138, 1.243V)과 거의 같다. 측정 경로가 설계대로 동작한다는 뜻이다.


7. 정리

커밋

1f0ec73 Linux 6.6: CP PWM(TIM2) + ADC1 추가 (CP 출력/측정 통과)
973ce1c Linux 6.6: I2C6 추가 (ATECC608B 통신 통과)
4c9064e Linux 6.6: UART 4개 추가 (U3/U8/U5 통과)
206609d Linux 6.6: M4 통과 + 이더넷(YT8511) 동작

오늘 배운 것

  1. 벤더 자료의 부품 이름을 믿지 말고 ID를 읽어라. DTS와 드라이버 이름은 AR8035였지만 실제 칩은 YT8511이었다.
  2. “받기는 되는데 보내기만 안 된다”면 RGMII TX 지연을 의심하라. 확인은 RX/TX 카운터로 바로 할 수 있다. 같은 phy-mode라도 드라이버마다 지연 레지스터를 다루는 방식이 다르니 코드를 직접 봐야 한다.
  3. 핀 맵은 넷리스트에서 찾는 것이 빠르고 정확하다. 신호 이름 하나에서 시작해 부품과 핀을 따라가면 커넥터 핀 번호까지 나온다.
  4. 보드 전원이 어디서 오는지 먼저 확인하라. 릴레이 코일 전원이 AC에서 나오는 보드에서는 릴레이 시험이 곧 220V 출력 시험이다.
  5. 출력을 다시 읽는 회로가 있으면 장비가 없어도 시험할 수 있다. CP PWM을 ADC로 확인한 것처럼.

다음 할 일

  • QCA7005 (PLC, ISO 15118 통신)
  • PM/EM/RCD 인터럽트 (PH12, PH14, PH15)
  • 외부 워치독 (TIM8, PI2)
  • WiFi/BT (AP6212), USB OTG
  • 이더넷 1G 시험, MAC 주소 고정
  • 그다음은 EVerest를 실제 하드웨어에 연결하기

댓글남기기