페이지

글목록

레이블이 DSPS인 게시물을 표시합니다. 모든 게시물 표시
레이블이 DSPS인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 21일 월요일

[dialog] DSPS 통신 관련 이모저모? ^^

안녕하세요, 오랜만입니다.

회사일이 너무 많아서, 글을 못 썼습니다. 쓰고 싶은 내용은 너무 많은데, 시간이 안나네요. ^^

요새, 스마트 도어락을 개발 의뢰를 받는 바람에, 
PSOC4 의 터치키 와 Proximity Sensor(터치 키의 일종인데 50cm 가량의 거리에서 사람의 접근을 감지) 를 회로설계하고,
433MHz RF Transceiver I/F , CC430F5137(MSP430 CPU+ RF Transceiver) , DA14583 DSPS Profile , NFC 리더 IC 등등을
일단 회로 설계를 하고 아트웍까지 직접하는 바람에 1주일을 거의 밤새고 이제 겨우 끝났습니다.

그리고 나니, 회사 Flexible LED Sign 제품의 프로그램을 Update 해봤습니다.
이번에 Update 한 내용은 DA14583 으로 DSPS를 구현해서 ARM 칩에 붙이고, 예전에 개발 외주를 맡겼던
스마트폰용 앱으로 LED sign 에 여러가지 명령을 전송하고 응답을 받는 내용입니다.

현재, DSPS 를 통해 흐름제어 없이 스마트폰으로부터 X-modem 프로토콜로 SPI FLASH 메모리에 쓰기 포함해서 
약 3-MByte 의 데이터를 약 10KByte/sec 의 속도로 전송이 가능했습니다. 

앱개발자의 말에 따르면 간간이 딜레이를 넣어서 DA14583의 FIFO가 Overflow 되지 않게 하여 HW 흐름제어 없이도 동작이 가능했다고 합니다.

뭐 일반 명령어 쯤이야 아무런 문제가 안되는데, 대용량 데이터를 흐름제어 없이도 정확하게 송수신이 가능하다는 점을 말씀드리고자  합니다.

잠깐 저희 회사 현재 개발중인 LED sign 소개해 볼까요? ^^;
2.5mm 두께에, 
SPI FLASH(128Mbit)에 다이얼로그 칩의 DSPS 프로파일로 스마트폰으로부터 이미지를 다운받아서 
풀칼라 LED 32 x 32 사이즈를 최대 98Hz로 디스플레이 합니다.
다이얼로그 DSPS 를 통해서 스마트폰으로 각종 제어도 가능합니다.
아, 이번엔 인조가죽에 플렉시블 LED를 적용해 봤습니다. ^^

동영상을 나중에 한번 올려 볼텐데, 일단 사진 자료만 올려 봅니다.





2016년 2월 14일 일요일

[dialog] SUOTA Update at DSPS Profile-2

SUOTA 는 2가지 방법으로 제공됩니다.
 Scheme 1: The secondary bootloader is stored in the external non-volatile memory. (SPI/I2C Flash Memory)
 Scheme 2: The secondary bootloader is burnt into the internal OTP.

저는 이중에서 Scheme 1 을 사용했습니다.

먼저 다음의 SDK5.03에 있는 디렉토리의 secondary bootloader 프로젝트를 엽니다.
D:\..\DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\secondary_bootloader\secondary_bootloader.uvprojx

컴파일하면 secondary_bootloader.hex 파일이 나옵니다.
기본으로 SPI 메모리와 security Mode 는 No security 로 되어 있는데, 그냥 이 옵션으로 하겠습니다. 자세한 내용은 넘어갑니다.

하드웨어는 dialog Basic Kit로 하겠습니다.
따라서 점퍼셋팅은 SPI Flash 를 사용하도록 셋팅합니다. 저는 추가로 디버그용으로 UART Txd 핀을 1개 더 연결했습니다.



Scheme 1 으로 SUOTA를 구성했을 때, 메모리맵 구조는 다음과 같습니다.


잘 안보이는데, AN-B-010 문서의 10 페이지를 찾아 보십시요.

나중에, DSPS 이미지를 0x13000 어드레스 영역에 SUOTA로 올릴 것이고 일단은 Proximity 이미지 2개를 이름하고 sw version 만 서로 다르게 하여 만들 것입니다.

자, 그럼 8.Creating the binary files and images & programming the flash 로 넘어갑니다.
1. Download DA1458x_SDK_5.0.3 zip
2. 2. Open & Build the bootloader project from the path:
\DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\secondary_bootloader.secondary_bootloader.uvprojx
3. 3. Copy the secondary_bootloader.hex (from the path
utilities/secondary_bootloader/Out) into the mkimage folder (utilities/mkimage path).
4. Open proximity reporter project from the path:
DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\projects\target_apps\ble_examples\prox_reporter\Keil_5\prox_reporter.uvprojx
5. Change device name in NVDS (user_config.h) to:     #define USER_DEVICE_NAME    "SUOTA-1"
6. Change the software version in the ble_580_sw_version.h to: DA14580_SW_VERSION
“v_3.0.6.1”


7. Build the project and rename the full_emb_sysram.hex as fw_1.hex.
이거 이름이 저렇게 되어서 헤깔리더라고요. 빌드하면 다음과 같이 hex 파일이 나옵니다.
DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\projects\target_apps\ble_examples\prox_reporter\Keil_5\out_580\prox_reporter_580.hex
prox_reporter_580.hex 파일을 fw_1.hex로 바꾸라는 것이죠.

8. Copy ble_580_sw_version.h (from the DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\sdk\platform\include) into the
mkimage folder (DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\mkimage path) and change the name to fw_version_1.h.

9. Change device name in NVDS (user_config.h) to:     #define USER_DEVICE_NAME    "SUOTA-2"
10. Change the software version in the ble_580_sw_version.h to: DA14580_SW_VERSION
“v_3.0.6.2”

11. Build the project and rename the full_emb_sysram.hex as fw_2.hex.
이거 이름이 저렇게 되어서 헤깔리더라고요. 빌드하면 다음과 같이 hex 파일이 나옵니다.
DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\projects\target_apps\ble_examples\prox_reporter\Keil_5\out_580\prox_reporter_580.hex
prox_reporter_580.hex 파일을 fw_2.hex로 바꾸라는 것이죠.

12. Copy ble_580_sw_version.h (from the DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\sdk\platform\include) into the
mkimage folder (DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\mkimage path) and change the name to fw_version_2.h.

저는 추가로 DSPS 프로젝트를 컴파일하여 나온 hex 파일을 sps_device.hex 바꾸고 sw_version_3.h 내용을 다음과 같이 바꾸어서, sps_device.hex 와  sw_version_3.h utilities\mkimage path 에 놓았습니다.
#define DA14580_SW_VERSION "v_3.0.6.4"
#define DA14580_SW_VERSION_DATE "2015-10-15 16:01 "
#define DA14580_SW_VERSION_STATUS "REPOSITORY VERSION"

13. Convert the 3 .HEX files to .BIN with the hex2bin program (can be found in the
flash_programmer folder). This can be easily done by dragging and dropping the file on the
HextoBin.exe
 secondary_bootloader.hex -> secondary_bootloader.bin
 fw_1.hex -> fw_1.bin
 fw_2.hex -> fw_2.bin

DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\flash_programmer 폴더의 hex2bin.exe 프로그램이 있는데,
이 프로그램 위에 fw_1.hex 파일을 마우스로 선택해서 끌어다 옮겨 놓으면 fw_1.bin 이 생깁니다.
마찬가지로 fw_2.bin , sps_device.bin 을 만듭니다.

At this point, you should have the following binary & header files added to the mkimage folder:

8.2 Creating the image files 내용에 따라 진행해 봅시다.
일단 D:\manual\Dialog\DA1458x_SDK_5.0.3\DA1458x_SDK\5.0.3\utilities\mkimage 폴더로 가면 메뉴얼과 다르게 mkimage.exe 파일이 없습니다.


여기의 하위 msvc 폴더로 가면 msvc.sln 이 있는데, MS visual C++ 프로젝트 파일인 듯 합니다.
visual studio 가 있어야 실행이 되는데 저는 2013 버전으로 해서 빌드하니 Debug 폴더에 mkimage.exe 파일이 생겼습니다.

그리고 나서 메뉴얼에 있는 위치에 mkimage.exe 파일을 copy 했습니다.

bin 파일을 img 파일로 변환합니다.
cmd 명령어로 명령창을 띄우고, mkimage 폴더의 위치로 이동합니다.

거기에서 다음과 같은 명령어를 실행하여 fw_1.img , fw_2.img, dsps_v1.img 를 만듭니다.
mkimage.exe single fw_1.bin fw_version_1.h fw_1.img
mkimage.exe single fw_2.bin fw_version_2.h fw_2.img
mkimage.exe single sps_device.bin fw_version_3.h dsps_v1.img


At this point, the 2 images have been created as shown below:

8.3 Creation of the multi_part.bin for the SPI memory using Scheme 1
Scheme 1 has the OTP blank. Therefore, the secondary bootloader will have to be stored in the
external memory.
Using Scheme 1, the file multi_part.bin which will be programmed into the SPI memory has the
outline architecture shown below:
다음의 메모리맵 구조로 된 multi_part.bin 파일을 만듭니다. SCHEME 1 구조인, 외부 메모리에 부트로더와 실행 이미지 2개 및 Product Header로 구성된 multi_part.bin 파일을 만듭니다.

mkimage.exe 로 다음의 명령어를 이용해서 만듭니다.
mkimage.exe multi spi secondary_bootloader.bin fw_1.img 0x8000 fw_2.img 0x13000 0x1F000 multi_part.bin

다음 내용은, smartsnipets 툴로 multi_part.bin 파일을 SPI 메모리에 굽는 내용인데, 똑같은 내용이라 메뉴얼의 8.5 Preparing the SPI memory: erasing the SPI memory 내용을 참고하십시요.
중요한 점은 17 페이지의 Figure 17 처럼 팝업창이 뜨면 No 버튼을 눌러주세요. 안 그러면 앱을 돌렸을때, SUOTA 디바이스가 안 뜹니다.

위의 이미지를 구워 넣고, 구글 play 스토어에서 SUOTA 앱을 받아서 스마트폰에 설치하고 실행하면 다음과 같은 화면이 떠서 SUOTA가 제대로 동작하고 있음을 알 수 있습니다.

제가 실제 테스트해 본 경우에는, 리셋 버튼을 눌러 줘야 SUTA 앱에서 SUOTA-1 이 검색되네요.

메뉴얼의 10 Running SUOTA from an Android platform 을 보고 따라 하면 잘 되는데, 제가 추가로 테스트 한 내용을 적어 봅니다.
이전에 dsps_v101.img(dsps_v1.img 는 erase 기능이 없는 버전이라 기능을 추가해서 dsps_v101.img로 다시 만들었습니다.)은 DSPS 프로그램을 조금 수정하여 Erase 명령을 집어 넣어 0x13000 부터 32KByte(0~0x3FFF) , 즉 0x13000~0x16FFF 영역을 지우도록 했습니다.

이후의 스마트폰으로 테스트한 상황을 쭉 나열해 보겠습니다.
1. multi_part.bin을 SPI FLASH에 넣은 후,  리셋하고 SUOTA 앱을 실행하면 SUOTA-1 device Name 이 뜹니다.
2. 이 디바이스로 들어가면 Update device 메뉴가 뜹니다.
3. 누르고 들어가면, 제가 PC에서 스마트폰에 넣어둔 이미지들이 여러가지 보이네요. 3번에서 이미지를 고르면 바로 4번으로 넘어갑니다. 제가 dsps_v101.img 를 선택하고 4번에서 Send to device 를 누르니 “Same Image Error”가 나와서 Download가 안되네요. 이유는 모르겠습니다. (이 때, 일단 fw_2.img 를 넣은 다음에 다시 dsps_v101.img를 다운로드하면 잘 됩니다.)
4. 3번에서 dsps_v101.img 가 다운 안되서 fw_2.img를 선택하고 Send to device 버튼을 누릅니다.

5. 다운로드가 정상으로 완료되고 부팅을 할거냐고 물어보면 OK 합니다.
6. SUOTA-2 로 디바이스가 바뀌었습니다. 제대로 BANK-2 에 다운로드 되었음을 알 수 있습니다.
7. SUOTA-2 로 들어가서 Update device를 누릅니다.
8. dsps_v101.img를 선택하고 Send to device 누릅니다.

9. BANK-2 에 제대로 다운로드 되었고 리셋한다는 팝업이 뜨면 OK 합니다.
10. 이제 DSPS F/W가 동작하므로 SUOTA 앱에는 device name이 보이지 않습니다.
11. DSPS 앱을 실행하니 DSPS-1 이라는 device name 이 뜨네요.
12. 제가 BLE 프로토콜로 [erixxxx 라고 스트링을 송신하면 0x13000~0x16FFF 까지 32KByte 를 지우는 동작이 수행됩니다.
현재는 SRAM에서 프로그램이 돌고 있으므로 바로 변화는 없습니다. 나중에 지우고 리셋 동작을 추가하면 바로 SUOTA-1로 동작이 될 것 같습니다.

13. 리셋을 하면, SRAM에서 실행되던 것이, 다시 부팅하면서 SPI에서 읽어서 SRAM에 loading 하게 됩니다.
이 때, 이전에 dsps_v101 에서 메모리를 지워버렸으므로 부트로더가 프로그램이 없는 것으로 인식해서 자동으로 부팅 가능한 SUOTA-1 이 실핸됩니다.
14. SUOTA-1 로 들어갑니다.
15. 다시 dsps_v101.img 를 선택합니다.
16. BANK-2 로 다시 send to device 버튼을 누릅니다.

17. 다운로드가 잘 되었고, 리붓 -> 확인을 누릅니다.
18. 리셋 되었습니다.
19. DSPS 프로그램이 동작 되므로 SUOTA 앱에 안보입니다.
20. DSPS 앱으로 보면 보입니다. dsps_v101.img 프로그램 이미지가 잘 실행 되었군요.
이렇게 앞으로, BANK-2 에다가 어떤 일반 dialog 프로그램이라도 다운로드 할 수가 있습니다.
하지만, 0x13000 영역을 지우는 코드가 반드시 있어야 다시 SUOTA-1 로 돌아가서 다시 SUOTA로 새로운 프로그램 이미지를 다운로드할 수 있습니다.

하지만 주의할 점은, 반드시 BANK-2에만 다운로드해야 한다는 점입니다. 잘못해서 BANK-1에 다운로드 하면 다시 SUOTA로 부팅이 안되고, 처음의 multi_part.bin 을 JTAG로 SPI FLASH에 구워 넣어줘야 합니다.


21. 다시 DSPS 프로그램이 동작하네요. 그리고 다시 Erase 명령을 실행시키고 리셋 하면?
22. SUOTA 프로그램이 부트로더에 의해 자동으로 동작하여 SUOTA 앱으로 device name 이 검색되고, 프로그램 이미지를 다운로드 가능합니다.


자, 이렇게 SUOTA를 이용해서 SUOTA 가 아닌 다른 프로그램도 다운로드 가능한 방법을 알아봤습니다.
꼼 수 이지만, 조금 더 손보면 괜찮을 것 같습니다.

끝으로 여기서 사용한, 0x13000 영역을 지우는 명령이 들어있는 DSPS 프로그램 프로젝트를 압축해서 첨부하겠습니다.
app_sps_scheduler.c 의 void app_user_callback(void) 함수에서 "[erixxxx" 를 검출하면 다음의 함수가 실행되어 32KByte 블럭을 지우는 코드가 들어있습니다. (host 프로젝트가 아니고 device 프로젝트를 실행하시면 됩니다)
DA14580_DSPS_3.150.2___13_2_2015\DA14580_DSPS_3.150.2\dk_apps\keil_projects\sps\sps_device

void app_spi_erase_prog_test(void)
{
    // Enable FLASH and SPI
    spi_flash_peripheral_init();
    spi_flash_block_erase(0x13000,BLOCK_ERASE_32);
}


[erasable DSPS 프로그램 다운로드 링크]

2016년 2월 11일 목요일

[dialog] SUOTA Update at DSPS Profile

^^ ㅎㅎ.

약간의 꼼수를 이용하여 DSPS 프로그램을 Update 하는 방법을 알아냈습니다.

간략하게 말씀드리면 secondary bootloader 와 proximity 프로그램 2개를 멀티로 초기에 SPI FLASH에 올리고
2번 BANK에 DSPS 프로그램을 SUOTA로 Update 하면 DSPS가 동작합니다.

그럼 1번 뱅크에는 처음에 이미 저장된 proximity 프로그램이 있고, 현재 동작은 DSPS 프로그램이 동작하고 있겠죠?

만약 다음번에 DSPS 프로그램을 업데이트 하고 싶으면 어떻게 해야 할지 생각해 봤습니다.
DSPS 프로그램 내에서 DSPS 프로그램이 위치하고 있는 영역의 SPI 영역을 지워버렸더니, 
리셋 후, secondary bootloader가 자동으로 BANK1의 proximity 프로그램을 생행을 하는군요.

그렇다고 함은, 다시 SUOTA 앱이 동작하는 것을 의미하죠.

이 때, 다시 BANK2에 DSPS 프로그램을 업데이트 하니까 되더군요.

이렇게 하면 ,  어떤 프로그램이라도 특정 영역(BANK2)을 지우는 기능만 있으면 모두 SUOTA를 사용해서 무선 업데이트가 가능하리라 생각이 됩니다.

이상입니다. 조금 더 자세한 설명은 오늘 저녁쯤에 작업해서 업데이트하겠습니다.

2016년 2월 10일 수요일

[dialog] DSPS Profile 에서 Connect 신호 추가하기

보통 BLE to UART 모듈들이 BLE가 연결 됐는지를 알려주는 출력 신호를 제공하지요? (다른 모듈을 안써봐서 모르겠네요. ^^)

그런데 저는 이런 기능이 1개 있으면 좋겠다는 생각에 BLE가 연결되고 끊어지는 부분을 여기저기 찾아봐서 알아 냈습니다.

동작은 잘 되더군요.

먼저 연결되는 부분은 다음과 같습니다.

app_sps_device_project.c 의 
void app_connection_func(struct gapc_connection_req_ind const *param)
{
    /*--------------------------------------------------------------
    * ENABLE REQUIRED PROFILES
    *-------------------------------------------------------------*/
    //enable SPS device role or SPS host and set the proper values
    app_sps_enable();
    
    gattc_exc_mtu_cmd();
  GPIO_SetActive(BLE_CON_PORT,BLE_CON_PIN);     // LED ON
  GPIO_SetInactive(BLE_CON1_PORT,BLE_CON1_PIN);  // CON PORT LOW
   
}

그 담으로 끊어지는 부분은 다음과 같습니다.

app_sps_device_project.c 의 
void app_disconnect_func(ke_task_id_t task_id, struct gapc_disconnect_ind const *param)
{
    uint8_t state = ke_state_get(task_id);
  GPIO_SetInactive(BLE_CON_PORT,BLE_CON_PIN);   // LED OFF
  GPIO_SetActive(BLE_CON1_PORT,BLE_CON1_PIN);  // CON PORT HIGH
   if ((state == APP_SECURITY) || (state == APP_CONNECTED)  || (state == APP_PARAM_UPD))
    {
        // Restart Advertising
        app_adv_start();
    }
    else
    {
        // We are not in a Connected State
        ASSERT_ERR(0);
    }
}


이렇게 하면 연결되면 CON Port가 High가 되고, 끊어지면 Con Port가 LOW 가 되서 외부 MCU에서 확인을 하고 BLE로 송수신을 할 수 있습니다.

2016년 2월 7일 일요일

[dialog] SPI memory 를 data memory로 이용하기

지난번 DSPS 프로그램에, Data memory 용 spi 읽기/쓰기를 적용해 봤습니다.

저는 SPI Flash 메모리를 주로 쓰는데, 메모리 용량이 2Mb(256KByte) 나 있으므로 남는 공간이 많습니다. 이 메모리는 처음 부팅시에만 쓰므로 어떻게 하면 사용할 방법이 없을까 하고 고민해 보니 EEPROM 처럼 필요한 데이터를 저장하는 용도로 쓸 수 있겠다 싶어서 테스트 해 봤는데, 잘 되는군요.

안그래도 da14580 은 EEPROM이 없어서 필요한 정보를 전원이 꺼지면 유지할 수가 없어서, EEPROM을 새로 달아야하나 했는데, 이걸로 해결이 되는 군요.

w25x20cl_a02 메모리(2Mbit) 의 제일 마지막 sector(0x3f000)를 사용했고,
Flash memory라 데이터를 쓰기 위해서는 Erase를 먼저 하고 써야 써집니다.
최소 Erase동작은 sector 단위(4KByte)로 할 수 있습니다.
그래서 마지막 섹터의 메모리 어드레스(0x3f000~0x3ffff) 를 지운다음 Write 합니다.

동작만 되는지 확인을 하다 보니 코드가 많이 지저분해 졌습니다.

테스트하기 위해 추가한 함수는 다음과 같습니다.
void app_spi_write_test(void)
{
    uint16_t man_dev_id;
    int16_t i;

wr_data[0] = '0';
for(i = 1 ; i < 512 ; i++)
{
wr_data[i] = wr_data[i-1] + 1;
}
    // Enable FLASH and SPI
    spi_flash_peripheral_init();
    // spi_flash_chip_erase();
    // Read SPI Flash Manufacturer/Device ID
    man_dev_id = spi_read_flash_memory_man_and_dev_id();
    
    spi_cs_low();
    
    spi_cs_high();
    
    spi_flash_write_data(wr_data, 0x3f000, 512);
}

void app_spi_erase_test(void)
{
    // Enable FLASH and SPI
    spi_flash_peripheral_init();
spi_flash_block_erase(0x3f000,SECTOR_ERASE);
}

void app_spi_read_test(void)
{
    int16_t btrd;
    int16_t i;
    uint16_t read_size = 256;  // set this variable to the desired read size
    // Enable FLASH and SPI
    spi_flash_peripheral_init();
    btrd = spi_flash_read_data(rd_data, 0x3f000, read_size);
}

static void spi_flash_peripheral_init()
{
    spi_FLASH_CS_Pad.pin = SPI_CS_PIN;
    spi_FLASH_CS_Pad.port = SPI_GPIO_PORT;
    // Enable SPI & SPI FLASH

    spi_init(&spi_FLASH_CS_Pad, SPI_MODE_8BIT, SPI_ROLE_MASTER, SPI_CLK_IDLE_POL_LOW, SPI_PHA_MODE_0, SPI_MINT_DISABLE, SPI_XTAL_DIV_8);

    detected_spi_flash_device_index = spi_flash_auto_detect();

    if(detected_spi_flash_device_index == SPI_FLASH_AUTO_DETECT_NOT_DETECTED)
    {
        // The device was not identified.
        // The default parameters are used (SPI_FLASH_SIZE, SPI_FLASH_PAGE)
        // Alternatively, an error can be asserted here.
        spi_flash_init(SPI_FLASH_SIZE, SPI_FLASH_PAGE);
    }
}

이전 프로토콜 테스트 하던 코드에 다음을 추가하여,
스마트폰으로 erase,write,read 테스트를 해 봤습니다.
void app_user_callback(void)
{
        //get data and pointer
        //uint8_t size = app_uart_pull(user_tx_write_pointer, TX_CALLBACK_SIZE, &tx_state_ptr);
int size;
char try_test[100];
while(1)
{
size = app_item_count(&user_buffer);
if (size >= RX_USER_CALLBACK_SIZE)
{
app_user_pull(rx_user_read_pointer, 1);
if (rx_user_read_pointer[0] == '[')
{
app_user_pull(rx_user_read_pointer, RX_USER_CALLBACK_SIZE-1);
if (strncmp(rx_user_read_pointer,"cmd",3) == 0)
{
sprintf(try_test,"cmd recieved OK\n\r");
app_ble_push(try_test,17); // UART Tx out
user_BLE_txbuffer(try_test, 17); // BLE Tx out
}
else if (strncmp(rx_user_read_pointer,"rqm",3) == 0)
{
sprintf(try_test,"rqm recieved OK\n\r");
user_BLE_txbuffer(try_test, 17); // BLE Tx out
app_spi_read_test();
user_BLE_txbuffer(rd_data, 20); // BLE Tx out
}
else if (strncmp(rx_user_read_pointer,"wrm",3) == 0)
{
sprintf(try_test,"wrm recieved OK\n\r");
user_BLE_txbuffer(try_test, 17); // BLE Tx out
app_spi_write_test();
}
else if (strncmp(rx_user_read_pointer,"era",3) == 0)
{
sprintf(try_test,"era recieved OK\n\r");
user_BLE_txbuffer(try_test, 17); // BLE Tx out
app_spi_erase_test();
}
else
{
sprintf(try_test,"cmd recieved FAIL\n\r");
app_ble_push(try_test,19);         // UART Tx out
user_BLE_txbuffer(try_test, 19); // BLE Tx out
}
break;
}
}
else
{
break;
}
}
}

테스트 결과,
Write를 하니 메모리에 0123456.... 을 쓰도록 했는데, 쓰고 나서 읽었더니 제대로 써 진것을 확인하였습니다.


프로그램 소스는 다음 링크에 있습니다.


2016년 2월 6일 토요일

[dialog] BLE protocol Test with DSPS profile (3/3)

이번에는 스마트폰에 DSPS 앱을 깔아서 DA14580 과 서로 연결한 후, 특정 프로토콜에 응답하는 프로그램을 만들어 보겠습니다.

물론 기본 DSPS는 동작하고, 추가로 기능을 넣었습니다.

이전에는 BLE에서 데이터가 들어온 것처럼 속여서 UART로 내보냈는데, 이번에는 UART에서 데이터가 들어온 것으로 속이면 BLE로 데이터가 전송이 되겠죠?

그런데 이번에는 조금 더 신경 쓸 내용이 있습니다. BLE에서 받은 데이터가 그대로 UART로 나가 버리므로 어떻게 BLE에서 수신된 데이터를 가로채야 할지 막막했습니다.

여러가지 생각을 해 보다가, 그냥 버퍼를 하나 더 만들었습니다. 그리고 나서 , BLE 수신 링버퍼에 데이터를 집어 넣을 때 만든 버퍼에도 넣었(push)습니다.
이렇게 하니 BLE에서 받은 데이터가 새로 만든 링버퍼에 들어가서 자유롭게 BLE로부터 수신된 데이터를 분석할 수 있었습니다.

난 나쁜놈인가? da14580을 속이고 가로채고 등쳐먹는 느낌이라, 왠지 기분이 좋지 않네요. ^^. 

1. BLE로 부터 데이터를 가로채기 위해서 먼저, 링버퍼를 하나 만듭니다. DSPS에서 링버퍼 struct 와 함수를 지원하니 같은 타입으로 1개 만들었습니다.
app_sps_scheduler.c 의 윗 부분으로 가서 다음과 같이 user_buffer 라는 이름으로 1개 만들었습니다.
/// SPS application buffer pointers
RingBuffer bletouart_buffer __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY
RingBuffer uarttoble_buffer __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY
RingBuffer user_buffer __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY

2. 또, 이 링버퍼로부터 데이터를 몇개 가져와서 처리하는데 사용할 byte 타입 버퍼도 한개 추가합니다.
/// UART callback pointers
uint8_t tx_write_pointer[TX_CALLBACK_SIZE+1] __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTIONMEMORY
uint8_t rx_read_pointer[RX_CALLBACK_SIZE] __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY
uint8_t rx_user_read_pointer[RX_USER_CALLBACK_SIZE+1] __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY
uint8_t tx_state_ptr __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY
uint8_t rx_state_ptr __attribute__((section("retention_mem_area0"),zero_init)); //@RETENTION MEMORY 

3. 아직은 버퍼가 만들어진 것은 아니고, app_sps_scheduler.c 의 다음과 같이 app_buffer_init 함수를 통해 초기화와 링버퍼를 만듭니다.
void app_buffer_init(void)
{
    if(!app_buffer_initialized(&bletouart_buffer) && !app_buffer_initialized(&uarttoble_buffer) &&!app_buffer_initialized(&user_buffer) )
    {
        //initialize buffers
        app_buffer_create(&bletouart_buffer, TX_BUFFER_ITEM_COUNT, TX_BUFFER_ITEM_SIZE, TX_BUFFER_LWM, TX_BUFFER_HWM);
        app_buffer_create(&uarttoble_buffer, RX_BUFFER_ITEM_COUNT, RX_BUFFER_ITEM_SIZE, RX_BUFFER_LWM, RX_BUFFER_HWM);
        app_buffer_create(&user_buffer, RX_BUFFER_ITEM_COUNT, RX_BUFFER_ITEM_SIZE, RX_BUFFER_LWM, RX_BUFFER_HWM);
    }
}

4. app_sps_scheduler.c 의 함수들이 static type들이라서 다른곳에서 함수를 사용하려면 h 파일에 프로토타입을 선언해야하고, 어쩌다 보면 중복 선언되서 에러도 나서 app_sps_scheduler.c 에 함수를 만들었습니다.

5. 새로 만든 링버퍼에 데이터를 집어(push)넣는 app_user_push()함수
void app_user_push(uint8_t* wrdata, uint8_t writeAmount)
{
    //write items to buffer;
    app_write_items(&user_buffer, wrdata, writeAmount);
}

6. 링버퍼에서 데이터를 끄집어(pull) 내는 app_user_pull 함수 
static uint8_t app_user_pull(uint8_t* rddata, uint8_t readamount)
{
    uint8_t readcount = 0;
        //pull data
        readcount = app_read_items(&user_buffer, rddata, readamount);
    return readcount;
}

7. da14580 을 속여서 uart 버퍼에 데이터를 집어넣어서 BLE에 출력하게 하는 user_BLE_txbuffer 함수
void user_BLE_txbuffer(uint8_t* wrdata, uint8_t writeAmount)
{
app_write_items(&uarttoble_buffer,wrdata,writeAmount);
}

8. 다음은 타이머 인터럽트 callback 함수에 넣고, 일정 시간마다 BLE로부터 수신된 사용자 링버퍼 데이터를 분석해서 처리하는 app_user_callback 함수도 만듭니다.
이 함수는 제가 단순하게 테스트를 위해 만든 함수로, 아주 어설프게 만들었으므로... 쓰시고자 하는 분들이 적절하게 좀 더 고쳐서 잘 만들면 되겠습니다. ^^
일단은, 전체 프로토콜 사이즈를 검사해서 스트링 사이즈가 RX_USER_CALLBACK_SIZE 보다 크면 
첫번째 캐릭터를 1개 링버퍼에서 꺼내서 '[' 캐릭터가 있다면 나머지 스트링도 다 끄집어 내어 2,3,4 번째 캐릭터가 "cmd"이면 잘 수신되었다고 BLE로 송신하는 내용입니다.

 void app_user_callback(void)
{
        //get data and pointer
        //uint8_t size = app_uart_pull(user_tx_write_pointer, TX_CALLBACK_SIZE, &tx_state_ptr);
int size;
char try_test[100];
 while(1) { size = app_item_count(&user_buffer); if (size >= RX_USER_CALLBACK_SIZE) { app_user_pull(rx_user_read_pointer, 1); if (rx_user_read_pointer[0] == '[') { app_user_pull(rx_user_read_pointer, RX_USER_CALLBACK_SIZE-1); if (strncmp(rx_user_read_pointer,"cmd",3) == 0) { sprintf(try_test,"cmd recieved OK\n\r"); app_ble_push(try_test,17); // UART Tx out user_BLE_txbuffer(try_test, 17); // BLE Tx out } else { sprintf(try_test,"cmd recieved FAIL\n\r"); app_ble_push(try_test,19); // UART Tx out user_BLE_txbuffer(try_test, 19); // BLE Tx out } break; } } else { break; } } 
}


9. 제일 중요한 부분을 빼 먹었군요. 실제로 BLE로 받은 데이터를 사용자 버퍼로 가져오는 부분인데, ble로부터 받은 데이터를 ble 버퍼에 집어넣는 함수와 같은 위치에 넣으면 됩니다.
찾아보니 gattc_write_cmd_ind_handler 함수 안에 있어서 사용자 버퍼에 집어넣는 함수를 같이 위치해 놓았습니다.
이렇게 하면 BLE로 수신된 데이터가 ble 버퍼와 사용자 버퍼에 동시에 들어갑니다.
static int gattc_write_cmd_ind_handler(ke_msg_id_t const msgid,
                                      struct gattc_write_cmd_ind const *param,
                                      ke_task_id_t const dest_id,
                                      ke_task_id_t const src_id)
{    
    if (param->handle == sps_server_env.shdl + SPS_SERVER_TX_DATA_CFG) 
    {
        //send write response if required
        if (param->response==0x01)
        {
            atts_write_rsp_send(sps_server_env.con_info.conidx, param->handle, PRF_ERR_OK);
        }
    }
    else if (param->handle == (sps_server_env.shdl + SPS_SERVER_RX_DATA_VAL))
    {
        // send data to the application
        app_ble_push((uint8_t*)&(param->value[0]), param->length);
        app_user_push((uint8_t*)&(param->value[0]), param->length);
    }
    else if (param->handle == (sps_server_env.shdl + SPS_SERVER_RX_DATA_CFG))
    {
        //send write response if required
        if (param->response==0x01)
        {
            atts_write_rsp_send(sps_server_env.con_info.conidx, param->handle, PRF_ERR_OK);
        }
    }
    else if (param->handle == sps_server_env.shdl + SPS_FLOW_CTRL_VAL)
    {
        //update ble flow control flag
        app_update_transmit_allowed(&ble_flags, param->value[0]);
    }
    else if (param->handle == sps_server_env.shdl + SPS_FLOW_CTRL_CFG)
    {
        //provide ble flow control state when flow control att is configured to 0x0100
        if (param->value[0]==0x01 && param->value[1]==0x00)
        {
            struct sps_server_request_flow_control_ind * msg = KE_MSG_ALLOC(SPS_SERVER_REQ_FLOW_CONTROL_IND, sps_server_env.appid,
                                                    TASK_SPS_SERVER, sps_server_request_flow_control_ind);
    
            ke_msg_send(msg);
        }
        //send write response if required
        if (param->response==0x01)
        {
            atts_write_rsp_send(sps_server_env.con_info.conidx, param->handle, PRF_ERR_OK);
        }
    }
    return (KE_MSG_CONSUMED);
}


10. 자 마지막으로 periph_setup.c의  timer callback 함수에 프로토콜 처리함수인 app_user_callback 함수를 실행하도록 하면 됩니다.
void timer0_general_user_callback_function(void) 
{
    static uint8_t n = 0;
    static uint32_t cnt_100ms = 0;
uint8_t try_test[100];
uint16_t port_Input=0;

cnt_100ms++;
    // when pass  10 * 100ms
    if ( 10 == n ) 
    {
port_Input = GPIO_GetPinStatus(GPIO_PORT_0,GPIO_PIN_6) + 
                          ((GPIO_GetPinStatus(GPIO_PORT_1,GPIO_PIN_1))<<1 span="">

sprintf((char *)try_test,"time = %8d ms , Port state : %01X\n\r\0",(cnt_100ms*100),port_Input);
app_ble_push(try_test,size_user_str(try_test));
app_user_callback();
       n = 0;
        //timeout_expiration--;
        if (GPIO_GetPinStatus(LED_PORT, LED_PIN))
        {
            GPIO_SetInactive(LED_PORT, LED_PIN);
        }
        else
        {
            GPIO_SetActive(LED_PORT, LED_PIN);
        }
     }
     n++;
}

11. 테스트 결과

동작 잘 돼네요. ^^


파일은 다음 링크에 첨부되어 있습니다.

[dialog] I/O control, UART Tx Write Test with DSPS (DA14580) (2/3)

이번에는 지난 번에 만들어 놓은 Timer callbak 함수 내에 일정한 주기로 포트를 읽어서 입력된 포트값의 상태를 읽어서 UART에 Write 하는 동작을 테스트 해 보겠습니다.

먼저, 입력 포트를 추가합니다.

periph.c 의 GPIO_reservations 함수에 다음을 추가합니다. Expert Kit 의 스위치는 2개가 있는데, P0_6 과 P1_1 이어서 이 2개의 포트를 추가합니다.

void GPIO_reservations(void)
{
   RESERVE_GPIO(SW0, GPIO_PORT_0, GPIO_PIN_6, PID_GPIO);    // SW IN(K2)
   RESERVE_GPIO(SW1, GPIO_PORT_1, GPIO_PIN_1, PID_GPIO);    // SW IN(K1)
   
   RESERVE_GPIO(LED, LED_PORT, LED_PIN, PID_GPIO);    // LED OUT

   RESERVE_GPIO( UART1_TX, UART1_TX_PORT, UART1_TX_PIN, PID_UART1_TX);  
   RESERVE_GPIO( UART1_RX, UART1_RX_PORT, UART1_RX_PIN, PID_UART1_RX);
#if (UART_HW_FLOW_ENABLED) 
   RESERVE_GPIO( UART1_RTS, UART1_RTS_PORT, UART1_RTS_PIN, PID_UART1_RTSN);  
   RESERVE_GPIO( UART1_CTS, UART1_CTS_PORT, UART1_CTS_PIN, PID_UART1_CTSN);
#endif /*UART_HW_FLOW_ENABLED*/
}

set_pad_functions 함수에도 코드를 추가합니다.
void set_pad_functions(void)        // set gpio port function mode
{
   GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_6, INPUT_PULLUP, PID_GPIO, true);
   GPIO_ConfigurePin(GPIO_PORT_1, GPIO_PIN_1, INPUT_PULLUP, PID_GPIO, true);

   GPIO_ConfigurePin(LED_PORT, LED_PIN, OUTPUT, PID_GPIO, false);

    GPIO_ConfigurePin( UART1_TX_PORT, UART1_TX_PIN, OUTPUT, PID_UART1_TX, false );
    GPIO_ConfigurePin( UART1_RX_PORT, UART1_RX_PIN, INPUT_PULLUP, PID_UART1_RX, false );
#if (UART_HW_FLOW_ENABLED) 
    GPIO_ConfigurePin( UART1_RTS_PORT, UART1_RTS_PIN, OUTPUT, PID_UART1_RTSN, false );
    GPIO_ConfigurePin( UART1_CTS_PORT, UART1_CTS_PIN, INPUT_PULLUP, PID_UART1_CTSN, false );
#endif /*UART_HW_FLOW_ENABLED*/
}



다음으로, 지난번에 만들어 두었던 타이머 인터럽트에 의해 1초마다 콜백되는 timer0_general_user_callback_function 함수에 포트를 읽어서 UART로 송신하는 프로그램을 추가해 보겠습니다.

UART에 Write 하는 방법은 사실 좀 오랫동안 찾아 해메다가 알아냈습니다. 좀 편법적인듯도 하지만 동작은 잘 되네요. DSPS 프로그램은 버퍼가 2개 있는데 1)BLE 쪽 송/수신 버퍼 와 2)UART 송/수신 버퍼 입니다. DSPS 동작 특성은 BLE 로 받으면 무조건 UART로 보내게 되어 있으므로 BLE 수신 버퍼에 데이터를 Write 하면 UART에 출력됩니다. 이 버퍼는 링버퍼로 이루어져 있는데 아무 때나, 버퍼에 최대 용량 이내로 데이터를 넣어주면 UART로 출력되고, 다 나가면 버퍼는 비워집니다.

쉽게 말하면, da14580을 BLE에서 데이터가 들어온 것으로 속이는 것입니다.
BLE 수신시 링버퍼에 데이터를 넣는(push) 함수가 app_ble_push() 입니다.
이 함수에 전송할 데이터와 데이터 갯수를 전달해 주면 UART에 출력됩니다.

void timer0_general_user_callback_function(void) 
{
   static uint8_t n = 0;
   static uint32_t cnt_100ms = 0;
   uint8_t try_test[100];
   uint16_t port_Input=0;

   cnt_100ms++;
    // when pass  10 * 100ms
    if ( 10 == n ) 
    {
      port_Input = GPIO_GetPinStatus(GPIO_PORT_0,GPIO_PIN_6) + ((GPIO_GetPinStatus(GPIO_PORT_1,GPIO_PIN_1))<<1 span="">

      sprintf((char *)try_test,"time = %8d ms , Port state : %01X\n\r\0",(cnt_100ms*100),port_Input);

      app_ble_push(try_test,size_user_str(try_test));
       n = 0;
        if (GPIO_GetPinStatus(LED_PORT, LED_PIN))
        {
            GPIO_SetInactive(LED_PORT, LED_PIN);
        }
        else
        {
            GPIO_SetActive(LED_PORT, LED_PIN);
        }
     }
     n++;
}

자 그럼, 출력된 UART 를 터미날로 볼까요?


버튼 상태가 잘 읽히고, DA14580을 속여서 UART로 데이터가 잘 송신되고 있습니다. ^^