6 분 소요

PLC 모뎀 작업을 일단락하고 충전기의 핵심으로 넘어갔다. 차량과 대화하는 CP(Control Pilot) 신호다. CP 전압을 읽으려면 ADC가 맞아야 하고, ADC가 맞으려면 기준전압이 맞아야 한다.

SC6_0의 DTS에는 9월에 이런 확인 절차를 적어 두었다.

dmesg | grep -i vrefbuf
  - 조용하면       VREFBUF가 2.5V 확보 -> 현재 설정 그대로
  - "timed out!"   VREF+가 외부에 묶임 -> vref-supply = <&vddref>

실행해 보니 아무것도 나오지 않았다. 조용하니 정상이라고 생각했다. 틀린 판단이었다.


1. ADC가 없다

값을 읽으려고 IIO 장치 목록을 열었다.

root@myir:~# grep -H . /sys/bus/iio/devices/iio:device*/name
/sys/bus/iio/devices/iio:device0/name:40000000.timer:timer@1
/sys/bus/iio/devices/iio:device1/name:40002000.timer:timer@3
/sys/bus/iio/devices/iio:device2/name:44000000.timer:timer@0
/sys/bus/iio/devices/iio:device3/name:44001000.timer:timer@7

넷 다 타이머(PWM용 트리거)다. ADC가 없다. VREFBUF가 조용했던 건 정상이라서가 아니었다. 기준전압을 켜 볼 ADC가 아예 없어서였다.

커널 설정부터 확인했다.

CONFIG_STM32_ADC_CORE=y
CONFIG_STM32_ADC=y
CONFIG_REGULATOR_STM32_VREFBUF=y

드라이버는 커널에 들어 있다. 보드에서도 등록돼 있었다.

root@myir:~# ls /sys/bus/platform/drivers/ | grep -i adc
stm32-adc
stm32-adc-core
...
root@myir:~# cat /sys/kernel/debug/devices_deferred
cpufreq-dt
48003000.adc

devices_deferred는 필요한 무언가를 기다리다가 끝내 올라오지 못한 장치 목록이다. ADC(48003000.adc)가 거기 있었다.


2. 에러 없이 기다리는 드라이버

stm32-adc-core.c의 probe를 보면 이유가 보인다. 필요한 자원을 하나씩 요청하는데, 모두 같은 패턴이다.

priv->vref = devm_regulator_get(&pdev->dev, "vref");
if (IS_ERR(priv->vref)) {
	ret = PTR_ERR(priv->vref);
	if (ret != -EPROBE_DEFER)
		dev_err(&pdev->dev, "vref get failed, %d\n", ret);
	return ret;
}

-EPROBE_DEFER(“아직 준비 안 됐으니 나중에 다시”)일 때는 에러를 찍지 않는다. 부팅 중에는 순서상 흔한 일이라 일부러 조용히 넘기는 것이다. 문제는 그 “나중”이 영원히 오지 않을 때다. 그러면 로그 한 줄 없이 장치가 사라진다.

기다릴 만한 후보는 여섯 개였다. 전원 셋(vdd, vdda, vref), 클럭 둘, 시스템 설정 하나.


3. 단서는 이미 로그에 있었다

부팅 로그에 계속 보이던 줄이 있었다.

[   34.408433] vddref: disabling

나는 이걸 “쓰지 않는 전원을 커널이 정리하는 정상 메시지”라고 설명했었다. 커널은 부팅이 끝날 무렵 아무도 쓰지 않는 레귤레이터를 끄는데, 그 메시지는 맞다. 하지만 vddref는 ADC의 vdd/vdda 전원이다. ADC가 정상으로 올라왔다면 이 전원을 켜서 쓰고 있었을 것이고, 커널이 끌 일이 없다.

이 줄은 “ADC가 안 올라왔다”는 증거였다. 동시에 vddref는 존재한다는 증거이기도 하다. 그러니 기다리는 대상은 vddref가 아니다.


4. 끊긴 고리

DTB를 역컴파일해서 전원 연결을 따라갔다.

ADC ──vref──▶ vrefbuf (2.5V) ──vdda-supply──▶ vdd (buck3) ──▶ ✘

MYiR의 공통 DTSI에 이렇게 적혀 있었다.

&vrefbuf {
	regulator-min-microvolt = <2500000>;
	regulator-max-microvolt = <2500000>;
	vdda-supply = <&vdd>;
	status = "okay";
};

vdd는 buck3, 즉 원래 ST 전원 관리 칩(STPMIC1)의 출력 이름이다. 그런데 이 모듈의 보드 DTS에서는 이렇게 정의돼 있다.

regulators {
	compatible = "simple-bus";
	...
	vdd: buck3 {
		regulator-name = "vdd";
		regulator-min-microvolt = <3300000>;
		...
	};

compatible이 없는 노드다. PMIC 드라이버가 붙는 자리가 아니라, 다른 보드 DTS에서 옮겨 온 흔적처럼 이름만 있는 빈 노드다. 이 모듈은 PMIC 없이 고정 전원으로 구성된 것으로 보인다. 어떤 드라이버도 이 노드를 레귤레이터로 등록하지 않는다.

그러면 이런 일이 생긴다.

  1. vrefbuf 자체는 등록된다. 자기 공급원은 나중에 찾으면 되기 때문이다.
  2. ADC가 vref로 vrefbuf를 요청하는 순간, 커널은 vrefbuf의 공급원(vdd)까지 찾아야 한다.
  3. 영원히 못 찾으니 -EPROBE_DEFER가 난다. 에러는 찍히지 않는다.

9월에 &adc의 vref-supply를 vrefbuf로 바꾼 순간부터 ADC는 사라져 있었다.


5. 이어 보니 진짜 답이 나왔다

vrefbuf의 공급원을 실제로 등록되는 3.3V 고정 레귤레이터 vddref로 이어 주었다.

&vrefbuf {
	vdda-supply = <&vddref>;
};

재부팅하자 처음으로 진짜 판별이 나왔다.

[    1.840164] vref: supplied by vddref
[    1.953689] regulator regulator.6: stm32 vrefbuf timed out!
[    1.953727] stm32-adc-core 48003000.adc: vref enable failed
[    1.954069] stm32-adc-core: probe of 48003000.adc failed with error -110

공급원 연결은 해결됐다. 이제 ADC가 기다리지 않고 실제로 vrefbuf를 켰다. 그런데 2.5V가 준비됐다는 신호(VRR)가 오지 않았다.

드라이버는 이 경우를 대비해 두었다.

ret = readl_poll_timeout(priv->base + STM32_VREFBUF_CSR, val,
			 val & STM32_VRR, 650, 10000);
if (ret) {
	dev_err(&rdev->dev, "stm32 vrefbuf timed out!\n");
	val = readl_relaxed(priv->base + STM32_VREFBUF_CSR);
	val = (val & ~STM32_ENVR) | STM32_HIZ;
	writel_relaxed(val, priv->base + STM32_VREFBUF_CSR);
}

10ms 안에 준비되지 않으면 버퍼를 끄고 핀을 놓아 준다(HIZ). 외부 전압과 맞붙어도 길어야 10ms라 하드웨어가 상할 일은 없다.

timed out!의 의미는 9월에 적어 둔 그대로다. 모듈이 VREF+ 핀을 외부 전압(VDDA 3.3V로 추정)에 묶어 두었다. 내부 2.5V 기준전압은 쓸 수 없다. SC6_0 넷리스트를 다시 봐도 모듈 커넥터에 VREF+나 VDDA 핀은 없고, ADC 관련 핀은 ANA0(84번)과 ANA1(83번)뿐이다. 캐리어 보드 쪽에서는 손댈 방법이 없다.


6. MYiR가 맞았다

9월의 DTS 주석에는 이렇게 적혀 있었다.

MYiR BSP는 &vrefbuf를 2.5V로 켜두면서도 &adc의 vref-supply는 3.3V 고정 레귤레이터(&vddref)로 물려두었다. 둘 다 참일 수 없는 모순이고…

모순이 아니었다. vrefbuf 노드는 ST 레퍼런스에서 물려받은 설정이 남은 것이고, 실제 ADC는 3.3V로 쓰도록 맞춰 둔 것이었다. 최종 설정은 이렇다.

&vrefbuf {
	status = "disabled";
};

&adc {
	/delete-property/ pinctrl-names;
	/delete-property/ pinctrl-0;
	vref-supply = <&vddref>;
	status = "okay";
	...
};
  9월 10/21 오전 10/21 최종
ADC 기준전압 vrefbuf vrefbuf vddref (3.3V)
vrefbuf 공급원 vdd (빈 노드) vddref (꺼짐)
결과 ADC 무한 대기 timed out! → ADC 실패 ADC 정상

7. 값 읽기

root@myir:~# grep -H . /sys/bus/iio/devices/iio:device*/name
...
/sys/bus/iio/devices/iio:device4/name:48003000.adc:adc@0

root@myir:~# cat /sys/bus/iio/devices/iio:device4/in_voltage_scale \
                 /sys/bus/iio/devices/iio:device4/in_voltage0_raw \
                 /sys/bus/iio/devices/iio:device4/in_voltage1_raw
0.050354003
967
21981

scale 0.050354003은 3300mV를 16비트(65536칸)로 나눈 값이다. 커널이 3.3V 기준으로 계산하고 있다는 뜻이다.

신호 raw 전압 (raw × scale)
AD_CP (ANA0) 967 약 0.05V
AD_TEMP (ANA1) 21981 약 1.11V

만약 기준전압이 2.5V라고 잘못 믿은 채로 ADC가 올라왔다면, AD_TEMP를 0.84V로 읽었을 것이다. 9월 주석에 적어 둔 “에러 없이 값만 32% 어긋나는” 경우가 바로 이것이다. 이번에는 ADC가 아예 안 올라오는 바람에 오히려 문제가 드러났다.

AD_CP가 0.05V로 바닥에 붙은 건 PWM을 아직 켜지 않아서다. CP 출력이 -12V 쪽에 있다.


8. CP PWM으로 AD_CP 움직여 보기

CP 출력은 PA15(TIM2_CH1)이고, 보드에서는 pwmchip0으로 잡힌다(TIM2 주소 40000000).

echo 0 > /sys/class/pwm/pwmchip0/export
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period       # 1kHz
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle   # 100% = +12V 고정
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
cat /sys/bus/iio/devices/iio:device4/in_voltage0_raw

echo 0 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle         # 0% = -12V 고정
cat /sys/bus/iio/devices/iio:device4/in_voltage0_raw

듀티 0%와 100%에서 AD_CP 값이 바뀌는 것을 확인했다. PWM 출력 → CP 구동 회로 → AD_CP 측정의 경로가 처음부터 끝까지 살아 있다.

충전기는 9V를 만들 수 없다

시험하면서 궁금했던 게 있다. 충전 중에는 CP가 9V나 6V가 되는데, 그걸 충전기가 바꾸는 걸까?

아니다. CP는 충전기와 차량이 한 선을 같이 쓰는 분압 회로다.

   충전기                                   차량
 ±12V ──[1kΩ]──┬────── CP ──────┬──|>|──┬──[2.74kΩ]── PE
               │                │       └──[1.3kΩ]──S2── PE
             AD_CP              │

충전기는 ±12V를 1kΩ을 거쳐 내보내기만 한다. 차량이 저항을 걸면 High 전압이 9V(2.74kΩ, State B)나 6V(1.3kΩ 추가, State C)로 내려간다. 충전기가 정하는 것은 듀티뿐이다. 듀티는 차량에 허용 전류를 알리는 데 쓰고, 10~85% 구간에서는 듀티(%) × 0.6 = A다. 예를 들어 26.7%면 16A다.

그래서 차 없이 시험하려면 차량 쪽을 흉내 내면 된다. 다이오드 + 2.7kΩ을 CP와 PE 사이에 걸면 9V, 여기에 1.3kΩ을 병렬로 더하면 6V다. 다이오드는 실제 차량에도 들어 있는 부품이다. PWM의 Low 쪽을 -12V로 유지시켜서, 충전기가 “진짜 차량”인지 확인하는 근거가 된다.


9. 설계자에게 공유할 것

설계자는 9월에 VREF = 2.5V로 확인해 주었고, 캐리어 회로도 그 기준으로 맞춰져 있다(R138 분압 1.243V = 2.5V의 절반 등). 실제로는 3.3V다.

영향 내용
측정 범위 AD_CP 최대 약 2.66V도 3.3V 안이라 잘리지 않는다. 2.5V였다면 오히려 위쪽이 잘릴 위험이 있었다
전압 환산 소프트웨어는 3.3V 기준으로 계산해야 한다
분해능 2.5V 기준 대비 약 24% 낮아진다

회로를 고칠지는 설계자가 판단할 일이다.


10. 남은 것

항목 메모
멀티미터 대조 C114(AD_CP), C76(AD_TEMP) 전압과 ADC 값을 비교해 3.3V 기준을 최종 확정
CP 상태별 기준표 +12V / -12V / 9V / 6V일 때의 AD_CP raw 값. 충전 프로그램이 상태를 판단할 기준
설계자 공유 기준전압 3.3V, 분해능 감소
AD_TEMP 환산 NTC 규격을 확인해 전압을 온도로 바꾸는 식

정리

"vrefbuf 조용함 = 정상"         → ADC가 없어서 조용했던 것
devices_deferred 에 adc         → 무언가를 에러 없이 무한 대기
vddref: disabling               → ADC가 전원을 안 쓰고 있다는 증거
vrefbuf → vdd(buck3) → 빈 노드   → PMIC 흔적만 남은 노드, 영원히 못 찾음
공급원을 이어 보니 timed out!     → VREF+는 모듈에서 외부에 묶여 있음
MYiR의 "모순"                    → 이유 있는 설정이었다

이번에 배운 것은 두 가지다.

판별 명령이 조용할 때는, 그 판별을 할 주체가 살아 있는지부터 봐야 한다. “에러가 없다”와 “에러를 낼 기회가 없었다”는 로그로는 구별되지 않는다. devices_deferred와 /sys/bus/iio 같은 “있어야 할 것이 있는지” 확인이 같이 가야 한다.

벤더 설정이 이상해 보이면, 고치기 전에 그 이유를 먼저 찾아야 한다. 9월의 나는 MYiR의 설정을 모순이라고 고쳤고, 한 달 뒤 같은 자리로 되돌아왔다.

댓글남기기