- 문제점 : Ubuntu에 Github markdown format을 지원하는 에디터가 없음
- 적절한 markdown editor가 없어 개인 PC에서 잘 보이던 Readme.md파일이 github에 올라가면 깨져보인다.
- 해결책 : https://jbt.github.io/markdown-editor 에서 편집
- 문제점 : DK 보드 부팅안됨
- 발생 원인
- Bootloader를 Flash 중 보드가 반응이 없어 전원을 재인가하니깐, 보드가 PC와 연결이 안된다.
- 타겟보드에서 Bootloader를 Loading하지 못해서 보드 부팅이 안되는 것으로 추측이 돤다.
- Bootloader loading을 못하는 이유는 Flash Memory에 Bootloader가 비정쌍적으로 Flash 되었거나, 일부만 Flash 된 것으로 추측이 돤다.
- 발생 원인
- 증상
- DK보드를 PC에 연결하면, 장치관리자에 COM PORT로 인식이 안됨.
- 보드의 "BOOT/RESET" 버튼을 누른 후 부팅을 해도 BOOTLOADER 모드로 진입이 안됨.
- 해결책 : 보드 교체 또는 하드웨어 디버거 장비로 Bootloader reflash.
- 내용 : Flash Memory의 내용을 삭제가 안되는 버그는 SDK의 버그가 아니고, FILE_ID의 값을 0으로 사용할 경우 발생한다.
- Flash에 데이터를 저장할 때 FILE_ID와 RECORD_ID를 사용한다.
- FILE_ID를 0으로 사용하면 Data의 삭제 또는 수정이 안되는 경우가 발생한다.
- FILE_ID를 0에서 0x5555로 변경하니 해당 기능 동작이 잘 된다.
-
SDK 버그 발견버그 내용 : Flash Memory의 내용을 삭제해도, 삭제가 안되는 경우가 발생한다.버그 API : fds_record_delete()배경예약 스켸줄을 저정하기 위해선 칩의 Flash Memory를 사용해야 한다.Flash Memory를 편하게 사용하기 Flash Data Storage(fds) 라이브러리를 사용하였다.
- 문제점
BLE를 통해서 삭제 명령을 받았을 경우, fds_record_delete() API를 호출해서 Flash에 저장된 예약 스케쥴을 삭제한다.fds_record_delete를 호출해도, 해당 데이터가 삭제가 안되는 경우가 자주 발생하고 있다.
해결책SDK 내부 버그로 추청이 되며, SDK 버전 업데이트 또는, 제조사에게 수정 요청을 해야한다.
** KNOWN BUG로 처리하였습니다.**
-
초기 계획
- UART를 동작시켜, 디버그 메시지 확인
- 버튼(HW)를 눌려 모터 동작
- 배터리(ADC), 시간동기 기능 구현
- UART를 통해 각 기능 제어
- BLE을 이용해 각 기능 제어
- 예약기능 동작
- OTA
- 저전력 방안 바면
-
배경 : 시간 증가의 정확도 향상을 위해 RTC 하드웨어 자원을 시간 관리 모듈(NOW)만 사용하도록 설계함
- 타겟 보드의 시간을 증가시키기 위해, RTC에서 매 초마다 이벤트를 발생시켜 현재 시간을 증가하도록 설계했다.
- 설계 도중, RTC 시간 이벤트를 다른 모듈과 공유했을 때, 다른 모듈에서 이벤트를 길게 잡고 있으면, NOW 모듈이 매 초마다 이벤트를 못 받는 경우가 발생할 수 있다.
- 즉, RTC 이벤트를 여러 모듈과 공유하면, 타겟 보드의 시간이 느리게 흐를 수 있다.
- 따라서, RTC 이벤트를 NOW 모듈만 독점적으로 쓰게 하고, RTC 이벤트를 사용하는 APP_TIMER LIBRARY, BUTTON LIBRARY는 사용을 안하고, 기능 구현을 하였다.(초기 계획의 1~4까지 구현 완료)
-
문제점 : BLE를 사용하기 위해선 무조건 APP_TIMER LIBRARY를 사용해야 함.
- BLE연결을 위해선 BLE LIBRARY를 사용해햐하며, BLE LIBRARY는 APP TIMER를 사용하고 있었다.
- 즉, BLE를 사용하기 위해선 APP TIMER를 사용해야하며, RTC 이벤트를 NOW 모듈만 독점적으로 사용할 수 없다.
-
수정해야 될 사항
- BLE 연결을 먼저 구현한다.
- RTC 이벤트의 Handler는 하나만 등록할 수 있다. 따라서, NOW 모듈에서 등록한 이벤트 핸들러는 삭제하고, APP_TIMER에 기 구현된 이벤트 핸들러를 사용한다.
- RTC는 APP_TIMER에 이벤트 핸들러를 등록해서 사용한다.
- 버튼 눌림을 확인하기 위해 SIMPLE_TIMER LIBRARY를 이용해 직접 구현하였는데, SDK에서 제공하는 BUTTON_LIBRARY를 이용해 구현한다.
-
변경된 계획
- BLE 연결
- BLE를 통해 배터리 잔량 읽기
- NOW모듈을 RTC를 독점하는 구조에서, APP TIMER를 써서 시간을 증가시키도록 수정
- BLE를 통해 시간 동기
- 버튼 모듈 수정
- 모터 제어
- HW SWITCH를 눌러 버튼 동작
- BLE를 통해 모터제어
- 예약기능 구현
- UART를 통해 각 기능 제어
- OTA
- 저전력 방안 마련
이런 시행착오를 들은 전 펌웨어 개발자는 매우 즐거워했다는.....
번호 | 요구사항 |
---|---|
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | OTA |
8 | |
9 | |
10 | |
11 | |
12 |
각 컴포넌트간에 Decoupling을 최소화하기 위하여 Callback함수를 적극적으로 이용하여 설계하였다. 예를 들어 battery_service에서 배터리 레벨을 얻기 위해 battery.h에 있는 API를 직접 호출하지 않고, 베터리 레벨을 얻어도는 함수 포인터를 받아서 해당 함수를 호출하는 방법으로 설계했다. motor_service, current_time_service도 이와 비슷하게 설계하였다.
- battery.h : ADC를 이용하여 배터리 잔량을 확인한다.
- now.h : 시간 동기를 하며, 하드웨어자원을 이용하여 보드에 저장된 시간을 흐르게 한다.
- motor.h : PWM을 이용하여 모터를 구동한다.
- toggle_switch.h : 타켓보드에 연결된 버튼의 눌러짐을 확인한다.
- app_button library를 이용하여 구현했으며, 스위치가 눌러졌을 때 호출할 콜백을 등록하여, 이벤트를 받는다.
- UART 관련 모듈
- uart_queue.h : UART로 들어온 명령어를 Circular Queue에 저장한다.
- io_uart.h : UART을 통해 들어온 문자를 받고, UART를 통해 문자열을 출력한다.
- uart_service.h
- Service UUID : 0000f000-0000-1000-8000-008050f9b34fb
- Characteristic UUID : 0000f000-0000-1000-8000-008050f9b34fb
- Write
- Type : unsigned char
- Value : 1
- Write
- Service UUID : 0x1805
- Characteristic UUID : 0x2A2B
- Write
- Type : uint8_t time[4]
- Value : Unix Time(Epoch Time), UTC + 0
- Read
- Type : uint8_t time[4]
- Value : Board Time
- Format : Unix Time(Epoch Time), UTC + 0
- Write
- 기본 규칙
- day값은 'OR' 연산을 해서 중복으로 표시할 수 있다.
- 모든 시간은 UTC+0를 따른다.
- Service UUID : 0000fa01-0000-1000-8000-00805f9b34fb
- Characteristic UUID : 0000fa01-0000-1000-8000-00805f9b34fb
- 예약 List 보기
- Read
struct schedule{
uint8_t id; //0xff, if empty schedule
uint8_t day; //0x1:Sun, 0x2:Mon, 0x4:Tue, 0x8:Wen, 0x10:Thu, 0x20:Fri, 0x40:Sat
uint8_t hour; //0~23
uint8_t minute; //0~59
}
struct schedule schedules[10]; // 예약 리스트
- Characteristic UUID : 0000fa02-0000-1000-8000-00805f9b34fb
- 예약 기능 추가, 수정, 삭제
- Write
- 추가
struct schedule{
uint8_t id; //Always 0xff
uint8_t day; //0x1:Sun, 0x2:Mon, 0x4:Tue, 0x8:Wen, 0x10:Thu, 0x20:Fri, 0x40:Sat
uint8_t hour; //0~23
uint8_t minute; //0~59
};
-
- 수정
struct schedule{
uint8_t id; //Updating schedule ID
uint8_t day; //0x1:Sun, 0x2:Mon, 0x4:Tue, 0x8:Wen, 0x10:Thu, 0x20:Fri, 0x40:Sat
uint8_t hour; //0~23
uint8_t minute; //0~59
};
-
- 삭제
struct schedule{
uint8_t id; //Deleting schedule ID
uint8_t day; //Always 0xff
uint8_t hour; //Always 0xff
uint8_t minute; //Always 0xff
};
명령어 | 설명 |
---|---|
BAT | 배터리 잔량을 출력한다. |
NOW | 보드에 저장된 시간을 출력한다. |
SCH | 예약정보를 출력한다. |
SWT | 모터를 동작시킨다. |
- 사용하지 않는 하드웨어 Block의 전원은 끈다.
- DMA, PPI 등을 적극 활용하여 CPU의 사용을 줄인다.
- CPU가 소모전류가 높은 편이라, 가급적 CPU의 사용을 줄이면 저전력에 유리하다.
- CPU를 사용할 땐, 최소한의 일만 하도, IDLE 모드에 들어가도록 한다.
- 낮은 주파수의 CLOCK을 사용하여 소모 전류를 줄인다.
- 높은 CLOCK을 사용하면, 프로세싱 속도는 빠르지만, 소모전류 측면에서는 불리하다.
- BLE의 Advertising 주기를 최대한 길게 한다.
- Advertising 주기가 길면, "RADIO" 하드웨어 Block 사용을 적게하기 때문에 저전력에 유리하다.
- 하지만 주기가 길면, BT 연결에 시간이 오래 걸린다.
- BLE 연결 후 전송하는 데이터의 횟수 및 크기를 최소화 한다.
- 데이터을 적게 전송할 수록 "RADIO" 하드웨어 Block의 사용을 줄일 수 있기 때문에 저전력에 유리하다.
- 기본적으로 System Off 상태로 두고, RTC 이벤트, GPIO Event가 발생하면 System On모드로 들어간 후, 다시 System Off모드로 빠진다.
- RTC Event는 시간 동기, BLE Advertising 주기에 맞쳐 발생하며, GPIO Event는 스위치가 눌러지면 발생한다.
- 하드웨어 Block 중 GPIO, BLE, RTC를 제외한 다른 Peripherial(ex UART) 모두 끈다.
- 보드의 시간을 흐르게 하기 위한 RTC Timer 이벤트의 간격(Interval)을 1초에서 60초로 한다.
- 예약 스케쥴의 최소 간격이 60초이기 때문에, 보드의 시간도 60초 단위로 증가를 시킨다.
- BLE의 Advertising, Connection 주기를 RTC Timer 이벤트의 약수로 한다.
- 해당 주기를 RTC Timer 이벤트의 약수로 하면, 시간 동기을 위해 CPU가 깨어날 때, BLE 관련 일을 같이 할 수 있기 때문에, CPU가 깨어나는 횟수를 60초에 1번씩 줄일 수 있다.
- BAT 서비스는 Notification 모드로 사용하여, 배터리 잔량이 10% 이하일 때만 해당 정보를 Central로 전송한다.
- BLE로 전송하는 데이터 량을 줄일 수 있다.
- Low Volaage 모드 사용도 검토는 했으나, 배터리의 전압 공급이 안정적이지 않을 것으로 판단되어 방안에 포함시키진 않았다.
- nRF51 칩의 BLE STACK은 SOFT DEVICE란 이름의 펌웨어로 구현되었다. 따라서 BLE 사용 시 소모 전류를 줄이기 위해서 하드웨어 Block의 적극적인 활용(Ex DMA, PPI)을 검토하였지만, 이 부분은 SOFT DEVICE에 기 구현되어 있으며, 튜닝할 수 있은 요소는 없다. 따라서, BLE 관련 저전력 방안은 BLE 스펙의 Advertising, Connetion Interval 조정, 데이터 사이즈 최소 등 제한적인 방법밖에 제시할 수 없어 아쉬움이 남는다.
- nRF51 저전력 방법 참고 자료 : https://devzone.nordicsemi.com/question/5186/how-to-minimize-current-consumption-for-ble-application-on-nrf51822/
- Private Key 생성
- 펌웨어 업데이트 전 유효한 펌웨어인지 검증하기 위해서 private key가 필요하다.
Generate key :
$ nrfutil keys generate private.pem
Display key :
$ nrfutil keys display -key pk -format code private.pem
- Bootloader 빌드 방법
- Bootloader source directory : $NRF_SDK/examples/dfu/bootloader_secure/
- dfu_private_key.c 파일을 열어 1에서 생성한 private key의 값을 넣는다.
- 아래 명령어로 빌드
$ cd $NRF_SDK/examples/dfu/bootloader_secure/pca10028/armgcc
$ make
- 배포가능한 펌웨어 생성
$ nrfutil pkg generate --hw-version 51 --sd-req $REQ_SD_VER --application-version $VERSION_NO --application $APP_HEX_FILE --key-file private.pem $APP_ZIP_FILE
- Bootloader의 DFU 서비스 추가
nRF51 SDK는 DFU 서비스를 라이브러리로 제공하고 있다.
- 소스 파일 : components/ble/ble_services/ble_dfu
- 사용방법
- Application 시작 시 ble_dfu_init()를 호출하여 DFU Service를 초기화한다.
- Application에 ble event를 받으면 ble_dfu_on_ble_evt()를 호출하여, DFU Service가 이벤트를 받을 수 있도록 한다.
- 최대전압 : 4.09V
- 최소전압 : 3.41V
- MAX_ADC_VALUE = 4.09 / 2 * 1024 / 3.6
- MIN_ADC_VALUE = 3.41 / 2 * 1024 / 3.6
- LEVEL = (ADC_VALUE - MIN_ADC_VALUE) / MAX_ADC_VALUE * 100
- MAX_ADC_VALUE = 4.09 / 2 * 1000 / 3.6
- MIN_ADC_VALUE = 3.41 / 2 * 1000 / 3.6
- LEVEL = (ADC_VALUE - MIN_ADC_VALUE) / MAX_ADC_VALUE * 100
battery.h에 배터리 래벨의 최대, 최소 전압을 받도록 인터페이스를 정의했다. 배터리 전압은 소수점으로 표시되기 때문에 Floating Point 계산을 하지 않기 위해 최대, 최소 전압 x1000한 값을 받는다. 이미 x1000을 한 값을 받기 때문에, 계산상에 x1024한 값 대신에 x1000한 값을 그대로 쓰도록 구현하였다.
-
nRF51 보드들 PC와 연결했을 때 USB 인식이 안됨
- 원인 : 부트로더를 잘못 구워서 인식이 안됨
- 해결 : 부트로더 Reflashing
- 부트로더 다운로드
- 부트로더 모드 진입
- nRF51 보드의 전원을 끈다.
- BOOT/RESET 버튼을 계속 누른다
- nRF51보드 전원을 킨다.
- BOOT/RESET 버튼을 땐다.
- "BOOTLOADER" USB 디바이스 생성 된다.
- 부트로더 Flashing
- 다운로드 받은 부트로더를 "BOOTLOADER" USB 디바이스로 복사(DRAG & DROP)
- 정상적으로 되면 LD6 LED가 깜빡인다.
-
UART로 printf 출력이 안 나올경우
- 해결 : sdk_config.h에 아래 코드 추가
#ifndef RETARGET_ENABLE
#definen RETARGET_ENABLE 1
#endif
- APP_BUTTON에서 Button Event가 발생 안함
- 원인 : SOFTDEVICE에 CLOCK 설정을 하지 않아서 Timer Tick이 동작하지 않았다.
- 해결 : 아래코드 호출하도록 수정
nrf_clock_lf_cfg_t clock_lf_cfg = NRF_CLOCK_LFCLKSRC;
SOFTDEVICE_HANDLER_INIT(&clock_lf_cfg, NULL);
- nrf_log_XXX 출력
- 해결 : sdk_config.h에 아래 코드 추가
#ifndef NRF_LOG_ENABLE
#definen NRF_LOG_ENABLE 1
#endif
- bootloader Build 시 uEEC.h 해더 파일이 없어서 에러 발생
- 원인 : external/ 에 있는 라이브러리들은 수동으로 설치를 해야 한다.
- 해결 : http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v12.0.0%2Flib_crypto.html&cp=4_0_0_3_3_1_2&anchor=lib_crypto_installing
- Advertising, Connection 제어
- Device가 다른 장치에 어떻게 보여질껀지, 어떻게 연결할 것인가를 정의한다.
- Broadcaster : Trasmit advertising packet to others.
- Observer : Listen advertising packet.
- Perioperial : advertises to central devices. After connecting, no longer broadcast data and stay connted to the device that accepted connection request.
- Central : initiates a connection with a peripheral device by first listening to the advertising packets.
- client : Requesting read/write attributes to the GATT server.
- server : Store attribues. the servier must response the attribute request from client.
- toolchain 설정 : components/toolchain/gcc/Makefile.posix
- Makefile 위치 : pca10028/s130/armgcc/Makefile
- Build 명령어
make
make flash
make flash_softdevice
칩 데이터시트 : https://lancaster-university.github.io/microbit-docs/resources/datasheets/nRF51822.pdf
S130 Softdevice Specification : http://infocenter.nordicsemi.com/pdf/S130_SDS_v2.0.pdf