'Arduino'에 해당되는 글 104건

  1. 2019.01.01 Hardware | Digital Compass - HMC5883L 사용기 - 2
  2. 2018.12.31 Hardware | ESP-01 or ESP8266 사용기 - 2 2
  3. 2018.12.21 Hardware | Breadboard Jumper 구입하기
  4. 2018.12.14 Hardware | ATtiny85 개발 보드를 이용하여 Digispark 를 DIY 하기
  5. 2018.12.11 Software | Arduino Nano Bootloader 를 update 해보자
  6. 2018.12.03 Hardware | 8x8 LED matrix 와 Colorduino 이용해 보기
  7. 2018.12.02 Hardware | MAX30105 파티클 센서 - 2
  8. 2018.11.23 Hardware | MAX30105 파티클 센서 - 1
  9. 2018.11.20 Hardware | RTC DS3231 부품 사용기 - 2
  10. 2018.11.11 Hardware | RTC DS3231 부품 사용기 - 1 2

Hardware | Digital Compass - HMC5883L 사용기 - 2

|

이 글은 전편이 있습니다.


* Hardware | Digitial Compass - HMC5883L 사용기 - 1

http://chocoball.tistory.com/entry/Hardware-Digital-Compass-HMC5883L-1


위의 포스트에서는 HMC5883L 의 중국 버전인 QMC5883L 을 사용하는 분투기(?) 였고,

이번 글은 AliExpress 에서 정식 HMC5883L 을 파는 업자를 찾아서 구입후 사용해 보는 포스트 입니다.





1. 구매


그냥 저가의 HMC5883L 을 구입하면 아마도 QMC5883L 이 배달될 껍니다.

QMC5883L 은 대략 2 USD 언더로 구입할 수 있고, 정식 HMC5883L 은 3.5 USD 정도 합니다.


중국 판매자들도 조금의 양심은 있는지, 완전 짝퉁을 정식 부품과 동일하게 올려받지는 않는것 같아요.

이번에 구입한 판매 링크는 다음과 같습니다.


* GY-273 3V-5V HMC5883L Triple Axis Compass Magnetometer Sensor Module For Arduino Three Axis Magnetic Field Module

https://www.aliexpress.com/item/GY-273-3V-5V-HMC5883L-Triple-Axis-Compass-Magnetometer-Sensor-Module-For-Arduino-Three-Axis-Magnetic/32786802981.html



문제 없이 잘 도착했습니다.

두둥!!! 정말 이번에는 정품 chip 일까. 바로 확인해 봅니다.



오~!!! L883 이 찍혀 있군요. 정품 chip 입니다.



AliExpress 어플에서 보면 배송업자에게 구매자들이 질문하는 QnA 가 있는데, 그 질문들을 참고했습니다.

꽤나 많은 사람들이 진짜 "L883" 인지 문의하는 글들이 보입니다.



자세한 샷으로 확실하게 찍어 봅니다.



역시 pin header 는 스스로 납땜하라는 배려를 보여 줍니다.

이 취미는 이 맛에 하는거죠.



이 얼마나 고민하고 기다린 제품인지 모릅니다. 그래서 도착하자 마자 여러장 찍어 봤어요.





2. 회로


저번 QMC5883L 에서 했던 회로 구성과 완벽하게 같습니다.


  HMC5883L  | Arduino Nano
---------------------------
    VCC     |     3.3V
    GND     |     GND
    SCL     |     A5
    SDA     |     A4
---------------------------


저번 그림을 동일하게 사용해 주구요.



실제로 연결해 줍니다. 이것으로 준비는 끝.

I2C 로만 연결되니 다른 센서나 부품들 보다 확연히 단순합니다. 요즘 놀고있는 ESP8266 하려면 정말...


I2C detector 로 문제 없이 인식되는지 확인도 해봅니다.


#include "Wire.h"
#include "i2cdetect.h"
 
void setup() {
    Wire.begin();
    Serial.begin(9600);
    Serial.println("i2cdetect example\n");
    Serial.print("Scanning address range 0x03-0x77\n\n");
}
 
void loop() {
    i2cdetect(); // default range from 0x03 to 0x77
    delay(2000);
}


정식 chip 이라 전혀 문제 없이 인식 됩니다.

역시 돈이 최고인건가요... 싼거 쫒다가 힘들었던 저번 기억이 새록새록 생각납니다.



연결된 모습은 정말 간단하쥬?






3. sketch


저번 QMC5883L 에서 했던 회로 구성과 완벽하게 같습니다.

Arduino IDE 에서 가장 간단한 HMC5883L_compass 를 사용해 봅니다.


/*
  HMC5883L Triple Axis Digital Compass. Compass Example.
  Read more: http://www.jarzebski.pl/arduino/czujniki-i-sensory/3-osiowy-magnetometr-hmc5883l.html
  GIT: https://github.com/jarzebski/Arduino-HMC5883L
  Web: http://www.jarzebski.pl
  (c) 2014 by Korneliusz Jarzebski
*/

#include "Wire.h"
#include "HMC5883L.h"

HMC5883L compass;

void setup() {
	Serial.begin(9600);
	
	// Initialize Initialize HMC5883L
	Serial.println("Initialize HMC5883L");
	while (!compass.begin()) {
		Serial.println("Could not find a valid HMC5883L sensor, check wiring!");
		delay(500);
	}
	
	// Set measurement range
	compass.setRange(HMC5883L_RANGE_1_3GA);
	
	// Set measurement mode
	compass.setMeasurementMode(HMC5883L_CONTINOUS);
	
	// Set data rate
	compass.setDataRate(HMC5883L_DATARATE_30HZ);
	
	// Set number of samples averaged
	compass.setSamples(HMC5883L_SAMPLES_8);
	
	// Set calibration offset. See HMC5883L_calibration.ino
	compass.setOffset(0, 0);
}

void loop() {
	Vector norm = compass.readNormalize();
	
	// Calculate heading
	float heading = atan2(norm.YAxis, norm.XAxis);
	
	// Set declination angle on your location and fix heading
	// You can find your declination on: http://magnetic-declination.com/
	// (+) Positive or (-) for negative
	// For Bytom / Poland declination angle is 4'26E (positive)
	// Formula: (deg + (min / 60.0)) / (180 / M_PI);
	
	float declinationAngle = (4.0 + (26.0 / 60.0)) / (180 / M_PI);
	heading += declinationAngle;
	
	// Correct for heading < 0deg and heading > 360deg
	if (heading < 0) {
		heading += 2 * PI;
	}
	
	if (heading > 2 * PI) {
		heading -= 2 * PI;
	}
	
	// Convert to degrees
	float headingDegrees = heading * 180/M_PI; 
	
	// Output
	Serial.print(" Heading = ");
	Serial.print(heading);
	Serial.print(" Degress = ");
	Serial.print(headingDegrees);
	Serial.println();
	
	delay(100);
}


예전에 삽질한게 무엇? 이라고 말하듯 바로 실행됩니다.







4. Processing


Processing 이라는 어플은 센서로부터 오는 신호를 시각적으로 실시간 표현해 주는 어플 입니다.


* Processing



이 Processing 을 이용하여 실시간으로 공간 데이터를 시각적으로 표현하고자 합니다.

순서는 다음과 같습니다.


- Arduino 에 Processing 과 연동하기 위한 sketch 를 업로드 한다

- Processing 을 띄워, Arduino 와 연동용으로 만든 Processing sketch 를 실행한다


고맙게도 processing 과 연동해주는 소스를 jarzebski 라는 분이 만들었습니다.


* jarzebski/Arduino-HMC5883L

https://github.com/jarzebski/Arduino-HMC5883L

Arduino-HMC5883L-master.zip


위 zip 파일을 풀어서 "Arduino > libraries" 에 풀어줍니다.

그러면 아래와 같이 HMC5883L_processing 이라는 sketch 를 찾을 수 있습니다. Arduino 에 업로드 해 줍니다.


File > Examples > HMC5883L > HMC5883L_processing



이제 Processing을 실행시켜서, 아까 다운로드 받은 소스 안에 Processing 이라는 폴더를 찾아 봅니다.

그 안에 "HMC5883L_processing.pde" 파일이 아래 경로에 있습니다.


Arduino > libraries > HMC5883L > Processing > HMC5883L_processing > HMC5883L_processing.pde


Processing 에서 위의 파일을 로드해 줍니다.



그냥 실행시키면 그냥 까만 화면이 나옵니다.

소스에서 Serial.list 부분과 baud rate 를 바꿔줘야 합니다.


우선 Serial.list 는 Arduino IDE 에서 봤을 때, 위에서부터 몇번째 COM port 를 선택했냐에 따라서 바꿔 주면 됩니다.

저의 PC 는 COM6 에 연결되어 있으니, Serial.list 의 두번째가 됩니다.

Array 로 표현되어 있으니, 대괄호 안이 0 --> 1 으로 바뀌어야 하겠습니다. Serial.list()[1] 처럼요.



추가로 baud rate 를 115200 으로 맞춥니다.

이는 아래 그림처럼 arduino 에 올린 sketch 에서도 바꿔서 맞춰줘야 합니다.



요즘은 ESP8266 도 그렇고, Arduino Nano 의 세로운 Bootloader 도 그렇고, 기본 baud rate 를 115200 으로 통일되는 것 같아요.

이 Processing 과 Arduino sketch 도 baud rate 부분은 모두 115200 으로 통일해 줍니다.


여기까지 무사히 왔다면 문제 없이 Processing 에서 구동될 것입니다.



짜잔~~!!! 잘 연동되어서 digital compass 역할을 실시간으로 보여주고 있습니다.

동영상 갑니다.






FIN


좀 멀리 돌아온 감이 있지만, 마무리가 잘 되어 기분이 좋네요.

2019년 1월 1일 이른 아침부터 준비하여 글 올리는 것도 나름 뿌듯합니다.


이 HMC5883L 이라는 물건이 calibration - 영점 조정 을 하지 않으면 쓸수 있는 물건이 아니라고 합니다.

언제가 될 지 모르겠지만, 다음에는 HMC5883L 의 calibration 에 대해서 다뤄보겠습니다.


And

Hardware | ESP-01 or ESP8266 사용기 - 2

|

이 글은 전편이 있습니다.


* Hardware | ESP-01 or ESP8266 사용기 - 1

http://chocoball.tistory.com/entry/Hardware-ESP01-or-ESP8266-using-1


이번에는 전편에서 풀지 못했던, ESP8266 chip 을 생산하는 ESPRESSIF SYSTEMS 사의 정식 firmware 를 입혀보는 포스트 입니다.

AI-Thinker 사는 firmware 를 한뭉텅이로 만들어 줘서 쉽게 firmware 를 입혔지만,

ESPRESSIF SYSTEMS 사는 address 라던지 파일 순서와 선택이 중요합니다.




1. ESP8266 adapter


전편에서도 사용했지만, 구매 정보를 올리지 않아서 여기에 기록으로 남깁니다.


* 2PCS For ESP-01 Esp8266 ESP-01S Model Of The ESP8266 Serial Breadboard Adapter To WiFi Transceiver Module Breakout UART Module

https://www.aliexpress.com/item/Breadboard-Adapter-for-ESP8266-ESP-01-ESP-01S-Wifi-Transceiver-Module-Breakout/32775467213.html



두개 구입했고 1달정도 걸렸던것 같습니다.

기다리는 것이 익숙해 지긴 했지만, 빨리 확인해보고 싶은 경우는 조금 고통스럽네요.



양 다리는 스스로 납땜해야 합니다.

DIY 임을 명확하게 인지시켜주는 제품입니다.



DIY MORE 라는 회사가 눈에 자주 띄네요.



ESP8266 모듈을 끼우면 이렇게 됩니다.

가로를 세로로 변경해주는 방법입니다.






2. Flash Download Tool


그 사이 3.6.5 로 버전업을 했습니다.


* ESPRESSIF > Support > Download > Tools

https://www.espressif.com/en/support/download/other-tools



아무래도 새로운 버전을 사용하는게 좋겠죠?

그리고 baud rate 도 처음엔 9600 만 사용해야 하는줄 알았는데, 이제는 115200 이 일반화 된것 같습니다.

그래서 요즘 버전들이 지원하는 115200 이상은 큰 문제가 없는듯 합니다.





3. NON-BOOT mode


이제 ESPRESSIF SYSTEMS 에서 내놓은 firmware 를 올려볼 차례 입니다.

여러 버전들이 존재하지만, 단순히 ready 상태를 띄워놓고 프로그래밍 하는 방법과, AT command 를 활용하는 두가지 방법이 있습니다.


우선 NON-BOOT mode 의 firmware 를 올려보겠습니다.


8Mbit = 1MiB 를 지원하는 NON-BOOT 버전은 2.0.0 이 마지막 버전인 듯 합니다.

(다른 버전에는 noboot 라는 디렉토리와 관련된 파일이 아예 없슴)


* ESP8266 NONOS SDK V2.0.0 20160810

https://www.espressif.com/en/support/download/sdks-demos

esp8266_nonos_sdk_v2.0.0_16_08_10.zip



위의 압축파일을 풀어보면, "README.md" 파일에 address 정보가 나와 있습니다.


esp8266_nonos_sdk_v2.0.0_16_08_10\ESP8266_NONOS_SDK\bin\at\README.md



# NON-BOOT MODE
## download
    eagle.flash.bin              0x00000
    eagle.irom0text.bin          0x10000
    blank.bin
        Flash size 8Mbit:        0x7e000 & 0xfe000
        Flash size 16Mbit:       0x7e000 & 0x1fe000
        Flash size 16Mbit-C1:    0xfe000 & 0x1fe000
        Flash size 32Mbit:       0x7e000 & 0x3fe000
        Flash size 32Mbit-C1:    0xfe000 & 0x3fe000
    esp_init_data_default.bin (optional)    
        Flash size 8Mbit:        0xfc000
        Flash size 16Mbit:       0x1fc000
        Flash size 16Mbit-C1:    0x1fc000
        Flash size 32Mbit:       0x3fc000
        Flash size 32Mbit-C1:    0x3fc000


플러쉬 tool 에서 메모리 사이즈에 맞는 파일과 address 를 지정해 주면 됩니다.

저는 8Mbit = 1MiB 플레쉬 메모리 이므로 해당하는 값을 챙깁니다.



위의 설정에서 참고로, "CrystalFreq" 부분을 26M 로 하는 것은, 실제로 ESP8266 에서 26MHz 오실레이터를 사용하기 때문입니다.



플레쉬 회로 구성은 1편에서 자세히 다뤘습니다.

주의할 점은, flashing 할 때에는 아래처럼 "추가 전원" 이 꼭 필요하다는 점 잊지 마시구요.



Flash Download Tools 의 콘솔 화면입니다.



문제 없이 완료되면 위의 그림처럼 보입니다.

putty 로 접속하고, 만들어지 회로에서 RST 버튼을 누르면 아래처럼 ready 상태를 확인할 수 있습니다.






4. BOOT mode


BOOT mode 로 사용하게 되면 AT command 를 통해서 직접 ESP8266 을 컨트롤 할 수 있습니다.

1MiB 짜리 ESP8266 에 대응하는 BOOT mode 의 최신버전은 현재 기준 V2.2.1 인것 같습니다.

V3.0.0 이상으로 올라가면, flash memory 가 1MiB 초과하는 버전만 대응한다고 문서에 나와 있어요.


* ESP8266 NONOS SDK V2.2.1

https://www.espressif.com/en/support/download/sdks-demos

ESP8266_NONOS_SDK-2.2.1.zip



아래 AT Instruction Set 문서의 1.2.4 섹션을 보면 8Mbit Flash address 에 대해 나와있습니다.


* ESP8266 AT Instruction Set

https://www.espressif.com/en/support/download/documents?keys=&field_type_tid%5B%5D=14

4a-esp8266_at_instruction_set_en.pdf



위의 정보에서 boot.bin 부분을 boot_v1.6.bin 으로 바꿔서 flashing 하면 됩니다.

물론 동일한 디렉토리에 존재하는 boot_v1.7.bin 도 해봤습니다. 그치만 booting 되면 1.6 으로 뜨더군요.

아마 내부 로직으로 인하여 v1.7 은 overwrite 가 되지 않는것 같습니다.



Flash 잘 되었구요.

동영상도 올려 봅니다.



확인해 보니, 완전 최신으로 업데잇 되었습니다 (얏호~).



그치만 Ai-Thinker 는 web server 가 기본 장착되어 있지만, ESPRESSIF SYSTEMS 의 firmware 에는 없나 봅니다.

IP 를 얻고 접속해 봐도 web page 가 뜨지 않습니다.





FIN


이제 ESP8266 에 대한 firmware 방법은 마스터 한것 같습니다.

다음에는 AT command 활용과 sketch 올리는 법, 그리고 메모리 업그래이드 하는 방법을 시도해 보겠습니다.


And

Hardware | Breadboard Jumper 구입하기

|

1. 전자 취미의 시작


Arduino 와 Sensor 들을 연결할 때에는 빵판이 필수 입니다.

처음 이 취미를 시작한게 2016년이고, 그때 당시 구입했던 빵판과 전선을 아직 잘 사용하고 있습니다.


* 3.3V/5V MB102 Breadboard power module+MB-102 830 points Solderless Prototype Bread board kit +65 Flexible jumper wires

https://www.aliexpress.com/item/Breadboard-830-Point-Solderless-PCB-Bread-Board-MB-102-MB102-Test-Develop-DIY/32339925888.html



Jumper wire 는 당시 구성품을 한꺼번에 구입했을 때, 딸려오는 부품 정도였습니다.

오늘의 메인 주제는 아니지만, 해당 제품을 배송 받았을 때 사진이 있어, 기록용으로 올려봅니다.



한꺼번에 여러개를 주문했더니만 A4 용지만한 봉투로 배송되었습니다.



55 USD 가격의 물품을 한꺼번에 구입했었네요.

가족에 눈치가 보여서 정말 고민고민 하고 비교하여 주문했던게 생각나네요.



봉투가 찟어져 있었지만, 다행히 구성품에는 문제가 없었습니다.






2. Jumper Wire


다만, 2년간 사용하면서 점퍼의 문제점들을 느끼게 됩니다.


* 철심 끝단 마무리가 되어 있지 않아서 빵판에 꼽을 때, 자꾸 걸림

* 걸리게 되면 빵판 안쪽 점점도 그렇고 철심 자체도 휘게 됨

* 어떤 것은 'ㄷ' 자 형태의 철심이어서 속이 비어있는 박스같음

* 속이 빈 철심은 내구성도 약하여 쉽게 부러짐

* 빵판에 꼽힌 상태에서 부러지면 철심이 빵판에 꽂힌 상태가 되어, 그 자리는 못쓰게 됨

* 전선의 형태가 기존의 곧은 모양을 유지하려고 해서, 전선 10개정도만 넘어가도 빵판이 스파게티가 됨

* 플라스틱 가이드가 자꾸 벗겨져서 가끔 손으로 밀어 넣어줘야 함


쓰고보니 꽤 문제가 있었네요.

Jumper wire 는 좀 괜찮은 것으로 사용해 보고자 AliExpress 를 뒤지게 됩니다.





3. SOLID Jumper Wire


네, 딱딱한 jumper wire 가 팔리고 있네요.

플라스틱 가이드도 없고, 구부리는 모양으로 유지될 만한 두께인 듯 합니다.


* 140pcs Solderless Breadboard Jumper 22 AWG Solid Wires Cable Kit with Box 165 x 55 x 10mm For Arduino

https://www.aliexpress.com/item/140pcs-Solderless-Breadboard-Jumper-22-AWG-Solid-Wires-Cable-Kit-with-Box-165-x-55-x/32847326551.html



가격적인 부담이 워낙 적은 부품이라 크게 기대하지는 않았습니다.



배송은 잘 되었습니다.



첫 인상은 괜찮았습니다.

길이별로 따로 구분되어 있구요.


다만, 심의 두께는 빵판에 알맞게 들어가지만,

재질의 강도가 너무 연질이어서 심하게 구부렸다 피거나 자주 구부리면, 빵판 안에서 끊어질 가능성이 있어 보입니다.


동영상으로 조금 느껴 보아요.



또한 빵판에 꼽을 때, 어디엔가 조금이라도 걸리면 쉽게 구부러져 버리는게 단점입니다.

조금 값을 줘도 되니, 좀더 강질의 breadboard jumper 를 사용하고 싶네요.





4. 비교


처음 구입한 jumper 와 비교샷 입니다.



전선이 조금밖에 없는데도 수직으로 꼽혀서 왔다갔다 해야하니, 밑에 깔리는 센서나 OLED 의 시인성도 나빠집니다.


반면, 새로 구입한 jumper 는 빵판 상면에 붙어있다 싶이 하고,

구부리면 그 모양 그대로 유지해 주니, 회로 구성 시 새로운 세상이 열립니다.



조금 많이 연질이긴 하지만, 잘 구입한것 같아요.

조금 더 강질의 jumper 를 발견하면 또 올려보겠습니다.


And

Hardware | ATtiny85 개발 보드를 이용하여 Digispark 를 DIY 하기

|

마침내 깡통 ATtiny85 를 Digispark 버전으로 만드는 방법을 찾아서 포스팅 합니다.

Digispark 란, ATtiny85 에 직접 소스를 올려서 편하게 ATtiny85 을 이용한 개발을 할 수 있도록 한 Kickstarter 프로젝트 였습니다.

현재는 클라우드 펀딩에 성공하여 기성품으로 판매되고 있습니다.


* Digispark - The tiny, Arduino enabled, usb dev board!

https://www.kickstarter.com/projects/digistump/digispark-the-tiny-arduino-enabled-usb-dev-board


프로젝트로 성공하여 기성품도 나와 있을 정도니, 당연하게도(?) AliExpress 에서 clone 으로도 판매되고 있어요.

(Open source 이기도 하니 뭐...)

1,500 원 정도면 바로 살 수 있지만, 혼자 힘으로 만들어 보고자 궂이 힘든 방법을 택해 봅니다.


참고로, 지금까지 ATtiny85 를 이용하여 놀았던 포스팅은 다음과 같으니 먼저 참고해 주세요.

지금까지 놀았던 것은, 모두 이 Digispark clone 을 만들어 보기 위한 전단계 들이었습니다.


* Hardware | ATtiny85 를 사용해 보자 - 1

http://chocoball.tistory.com/entry/Hardware-ATtiny85-1


* Hardware | ATtiny85 를 사용해 보자 - 2

http://chocoball.tistory.com/entry/Hardware-ATtiny85-2





1. Digispark Bootloader 입히기 - Arduino ISP


ATtiny85 를 뭔가 작업을 걸려면 interfacing 해주는 arduino 가 필요합니다.

이 arduino 를 programmer 라고도 하기도 합니다만, 여튼 Arduino ISP 라는 소스를 올려놔야 동작합니다.


지금까지 여러 포스팅에서 다루었으므로 여기서는 간단하게 설명하겠습니다.

일단 "ArduinoISP" 소스를 arduino 에 올립니다.


File > Examples > 11.ArduinoISP > ArduinoISP






2. Digispark Bootloader 입히기 - ATtiny85 와 연결


Arduino 와 연결은 일반적인 ArduinoISP 시에 사용하는 연결법과 동일합니다.



  ATtiny85  | Arduino Nano
---------------------------
    VCC     |     5V
    GND     |     GND
    Reset   |     D10
    Pin 0   |     D11
    Pin 1   |     D12
    Pin 2   |     D13
---------------------------


실제 연결된 모습은 아래와 같아요.



이제 Arduino IDE 에서 ATtiny85 를 인식시켜야 합니다.

Boards Manager 에서 ATtiny85 가 떠서 선택할 수 있도록, 우선 Preferences 에서 다음과 같이 작업합니다.


File > Preferences > Additional Boards Manager URLs


https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json


그러면, 아래처럼 Boards Manager 에서 attiny 보드들을 선택할 수 있도록 해주는 파일들을 인스톨 할 수 있습니다.


Tools > Board > Boards Manager > Type : Contributed > attiny



여기까지 오면, Board 선택시 ATtiny 가 아래처럼 보입니다.


Tools > Board > ATtiny



ATtiny85 의 설정은 다음과 같이 맞춥니다.


* Board : ATtiny

* Processor : ATtiny85

* Clock : 1 MHz (internal)

* Port : Arduino ISP 가 연결된 Port

* Programmer : Arduino as ISP






3. micronucleus 부트로더 입히기


Digispark 는 Pin 숫자가 적어 제약이 많기 때문에 USB chip 따로 있지 않습니다.

그 대신에 bootloader 가 가상 USB 를 만들어서 PC 와 연결을 해주죠.


그 bootloader 가 micronucleus 라는 부트로더 입니다.

Digispark 는, 이 micronucleus 부트로더를 채용하고 있습니다.


지금까지 시행착오는 EEPROM 프로그램을 이용하여 직접 ATtiny85 에 쓰기를 시도했었지만,

아래 동영상을 보니, Arduino IDE 가 사용하는 "avrdude.com" 을 이용하는 방법이었습니다.


FUSE bit 도 정확하게 알려주더군요.

아래 동영상 제작자에게 감사를.


* Load Micronucleus to attiny85 using Arduino uno

https://www.youtube.com/watch?v=o7711jBQxmY


이 Fuse 설정이 중요한데, 잘못하다가는 ATtiny85 가 벽돌이 될 수 있습니다.

벽돌된 ATtiny85 를 복구하는 방법도 있는데, 이는 다른 기회에 다뤄 보기로 하겠습니다.


일단, 여기에 성공한 Fuse 설정을 적어 놓습니다.


   Fuse type  | value
----------------------
Low Fuse      | 0xe1
High Fuse     | 0xdd
Extended fuse | 0xfe
----------------------


Digispark 에서 사용하는 부트로더 파일인 micronucleus 를 다운로드 받아 놓습니다.


* Micronucleus V2.04

https://github.com/micronucleus/micronucleus

micronucleus-master.zip


다음으로, 최종 avrdude 실행 코드를 만들기 위해, 아무 소스나 uploading 이 성공한 소스를 arduino 에 올려 봅니다.



위에서 마지막 command 를 카피해서 notepad 등에 붙여서 avrdude 의 프로그램 위치나, config 파일의 장소를 확인합니다.

그리고 command 줄의 마지막 부분을 sketch 파일이 아닌, flash 문구를 붙여서 하나로 만듭니다.


C:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/bin/avrdude -CC:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/etc/avrdude.conf -v -pattiny85 -cstk500v1 -PCOM7 -b19200 -Uflash:w:C:\Users\chocoball\Documents\Arduino\libraries\micronucleus-master\firmware\releases\t85_default.hex:i -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m


저의 경우 최종 모습이 위 command 입니다.

각 부분을 분리해 보면 다음과 같이 3개로 분해됩니다.


C:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/bin/avrdude -C
C:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/etc/avrdude.conf -v -pattiny85 -cstk500v1 -PCOM7 -b19200 -Uflash:w:
C:\Users\chocoball\Documents\Arduino\libraries\micronucleus-master\firmware\releases\t85_default.hex:i -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m


위의 제일 마지막 부분이 micronucleus 부트로더인 t85_default.hex 와 fuse bit 를 지정한 command line 입니다.

이제 준비가 완료 되었습니다.


지금까지 해온 과정을 정리해 보면,

* Arduino ISP 로 연결

* micronucleus 부트파일 다운로드

* avrdude command line 생성


avrdude command line 이 완성되었으므로, 이제 실행시키면 됩니다.

실행은 Arduino IDE 에 복사하여 실행할 방법이 없으므로, command line 창에서 실행합니다.

Windows OS 의 경우는 다음과 같아요.


시작 > 검색 > Windows PowerShell



avrdude command 를 카피하여 PowerShell 에 붙여놓기 합니다.

두둥~!!!!!



실행!!!



Boom Boom Boom !!!


PS C:\> C:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/bin/avrdu
de -CC:\Users\chocoball\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino14/etc/avrdude.
conf -v -pattiny85 -cstk500v1 -PCOM7 -b19200 -Uflash:w:C:\Users\chocoball\Documents\Arduino\libraries\micro
nucleus-master\firmware\releases\t85_default.hex:i -U lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U efuse:w:0xfe:m

avrdude.exe: Version 6.3-20171130
             Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
             Copyright (c) 2007-2014 Joerg Wunsch

             System wide configuration file is "C:\Users\chocoball\AppData\Local\Arduino15\packages\arduino
\tools\avrdude\6.3.0-arduino14/etc/avrdude.conf"

             Using Port                    : COM7
             Using Programmer              : stk500v1
             Overriding Baud Rate          : 19200
             AVR Part                      : ATtiny85
             Chip Erase delay              : 400000 us
             PAGEL                         : P00
             BS2                           : P00
             RESET disposition             : possible i/o
             RETRY pulse                   : SCK
             serial program mode           : yes
             parallel program mode         : yes
             Timeout                       : 200
             StabDelay                     : 100
             CmdexeDelay                   : 25
             SyncLoops                     : 32
             ByteDelay                     : 0
             PollIndex                     : 3
             PollValue                     : 0x53
...


...
avrdude.exe: Device signature = 0x1e930b (probably t85)
avrdude.exe: safemode: lfuse reads as E1
avrdude.exe: safemode: hfuse reads as DD
avrdude.exe: safemode: efuse reads as FE
avrdude.exe: NOTE: "flash" memory has been specified, an erase cycle will be performed
             To disable this feature, specify the -D option.
avrdude.exe: erasing chip
avrdude.exe: reading input file "C:\Users\chocoball\Documents\Arduino\libraries\micronucleus-master\firmwar
e\releases\t85_default.hex"
avrdude.exe: writing flash (8124 bytes):

Writing | ################################################## | 100% 0.22s
...


...
Reading | ################################################## | 100% 0.01s

avrdude.exe: verifying ...
avrdude.exe: 1 bytes of efuse verified

avrdude.exe: safemode: lfuse reads as E1
avrdude.exe: safemode: hfuse reads as DD
avrdude.exe: safemode: efuse reads as FE
avrdude.exe: safemode: Fuses OK (E:FE, H:DD, L:E1)

avrdude.exe done.  Thank you.


Raw log 를 첨부해 놓습니다.


* Digispark bootloader log

Digispark_bootloader_upload.txt


성공 동영상이 빠지면 섭섭하겠죠?


여기까지 오면 다 성공한 것이나 마찬가지예요.





4. Digispark 드라이버 인스톨


Digispark 드라이버 설치 과정은 아래 동영상을 참고하였습니다.


* Installing Drivers and Programming the DigiSpark ATtiny85 dev boards - Tutorial

https://www.youtube.com/watch?v=MmDBvgrYGZs


Digispark 는 USB 계열의 device 이므로, 드라이버 인스톨이 필요합니다.

우선 최신 드라이버를 다운로드 받아 놓습니다.


* Digistump Arduino Release 1.6.7

https://github.com/digistump/DigistumpArduino/releases

Digistump.Drivers.zip




그러면, 아래처럼 장치 관리자에서 잠깐 떳다가 사라집니다.

장치 관리자 메뉴에서 "숨겨진 장치 보이기"로 하면, 불분명한 장치로 인식되어 있습니다.



해당 장치에서 마우스 오른쪽 클릭으로 드라이버 설치를 선택 후,

아까 다운로드 받은 드라이버를 해동시킨 폴더를 선택해 줍니다.



짜잔~~~!!!

Digispark 장치 디바이스 드라이버 인스톨에 성공했습니다.




그러면 장치 관리자에서 아래와 같이 등록된 것을 확인할 수 있습니다.



이제 다 왔네요.





5. Digispark 에 직접 소스 올리기


Digispark 가 정상적으로 인식되었으면, Arduino as ISP 나 중간 장치를 거치지 않고,

직접 USB 에 연걸하여 Arduino IDE 에서 직접 소스를 uploading 할 수 있습니다.


Digispark 보드를 Arduino IDE 에서 선택될 수 있도록 아래 과정을 거칩니다.


File > Preferences > Additional Boards Manager URLs



URLs 부분에, official 한 Digispark 링크를 걸어 줍니다.


http://digistump.com/package_digistump_index.json


이제 Boards Manager 로 가서 관련 파일을 등록해 줍니다.


Tools > Board > Boards Manager



Digistump 라고 검색하면 인스톨 할 페키지가 뜹니다.



위의 과정까지 마치면, 아래처럼 Board 메뉴에서 Digispark 를 선택할 수 있게 됩니다.


Tools > Boards > Digispark (Default - 16.5mhz)



Programmer 를 USBasp 로 바꾸고 소스를 uploading 합니다.



업로딩을 시작하면 소스를 컴파일 하고, 최종적으로 아래 문구가 뜨면서 IDE 가 대기합니다.


Running Digispark Uploader...

Plug in device now... (will timeout in 60 seconds)



이는 Digispark 는 USB 인터페이스 chip 을 가지고 있지 않고,

micronucleus 부트로더가 virtual USB 로 처리하므로, 장치관리자에서 지속적으로 인식하지 못합니다.

그래서 이렇게 필요시에 PC 가 준비단계가 되었을 때, USB 에 연결해 주면 됩니다.



연결하면, 위처럼 Digispark 를 IDE 내에서 인식하고 Digispark / ATtiny85 내장 EEPROM 을 지운 다음 새로운 코드를 쓰게 됩니다.





FIN


Digispark 가 ATtiny85 를 사용한다는 점에 착안하여,

깡통 ATtiny85 를 이용하여 Digispark 를 직접 만들어 보자고 한지 어언 6개월...


다른 포스트에서 다뤘듯 bootloader 에 대한 경험이 쌓이고 난 후가 되서야 완료할 수 있었습니다.


이제 소형/단순 control 을 위한 ATtiny85 이용시에는,

이 Micronucleus 가 사용된 Digispark clone 으로 직접 USB 와 연결하여 개발할 수 있겠네요.


모두 FUN Arduino~!!!

And

Software | Arduino Nano Bootloader 를 update 해보자

|

1. upload 가 이상해


어느때 부턴가 arduino 에 sketch 를 upload 하면 다음과 같이 에러를 냈습니다. 뿜뿜~.



뭐지? 하고 찾아보다가 보니 bootloader 가 새롭게 업데이트 되면서 발생한 문제라고 하는군요.

Arduino IDE 가 업데이트 되면서 자동으로 바뀐것 같습니다.


기존에 선택되어 있던 "Processor : ATmega328P" 이 아니라...



아래와 같이 Processor 의 종류를 "ATmega328P (Old Bootloader)" 로 바꾸어서 upload 하면 해결 됩니다.


Tools > Processor > ATmega328P (Old Bootloader)



찾아보니 이런 내용이 올라와 있네요.

제가 즐겨 쓰는 arduino nano 버전에 사용되는 ATmega328P 에만 관련이 있어 보입니다. (Uno 도 마찬가지?)


* Getting Started with the Arduino Nano

- https://www.arduino.cc/en/Guide/ArduinoNano



일단 해결은 되었으니, 당시에는 급하게 Bootloader 까지 업데이트 할 필요는 없었습니다.





2. Bootloader 를 업데이트 해보자


새로 올라온 Nano 용 Bootloader 는 몇가지 장점이 있다고 합니다.


* Arduino Nano ATmega328P bootloader difference

https://arduino.stackexchange.com/questions/51866/arduino-nano-atmega328p-bootloader-difference


1. Optiboot will not go into an endless reset loop after a watchdog reset. ATmegaBOOT will.


2. Optiboot expects the upload communication at 115200 baud. ATmegaBOOT, 57600.

This is the reason why the old boards don't work with the Tools > Processor > ATmega328P selection and vice versa.


watchdog 을 리셑 후, 무한루프에 빠지는 문제와, 통신 속도가 달라졌다고 합니다. (저는 못느꼈던 부분....)


추가로 Bootloader 가 작아지면서, 더 많은 소스를 올릴 수 있게 되었다지만, 여분의 부분을 사용하게끔은 아직 안되었다고 하네요.

여분의 메모리 공간 활용을 위해서, 새 Bootloader 는 Optiboot 는 Uno 와 동일하므로,

Nano 이지만, 편법으로 Uno 로 설정하고 Bootloader 를 입히면 된다 합니다.


아직 큰 소스를 제작하지도 않는지라, Uno 용으로 Bootloader 를 입히는 것은 나중에 해보겠습니다.





3. Arduino as ISP 로 연결


궁금하니 새로운 Bootloader 를 올려보기로 합니다.


Bootloader 를 입히는 방법은 여러가지가 있습니다만,

저는 여분의 Arduino Nano / Micro 를 상호 사용하여 Bootloader 를 입히는 방법으로 진행합니다.


이때 사용되는 interface 는 ICSP header 를 사용합니다.



지금까지 arduino 를 사용하면서, 이 ICSP header 가 필요할까? 했는데, 이럴 때 필요하네요.



새롭게 Bootloader 를 입힐 arduino 는 ICSP 에 연결하고,

PC 와 연결되는 arduino 는 ISP 모드로 사용됩니다.


그럼 하나하나 진행해 볼까요.





4. Pin 연결


ISP 모드로 연결되는 arduino 를 SOURCE 라고 칭하고, 새로운 Bootloader 가 입히게 될 arduino 를 TARGET 이라고 하겠습니다.

TARGET 의 ICSP header 와 SOURCE 의 pin 이 어떻게 연결되는지 표로 만들어 봤습니다.


아래는 Arduino Micro 가 SOURCE 로 했기 때문에,

Nano 나 다른 기존일 때에는 거기에 맞는 FUNCTION pin 을 찾아서 연결해 주면 됩니다.


Function | SOURCE (Micro) | TARGET ICSP (Nano)
----------------------------------------------
  MISO   |      MISO      |       1
  5V     |      5V        |       2
  SCK    |      SCK       |       3
  MOSI   |      MOSI      |       4
  RESET  |      D10       |       5
  GND    |      GND       |       6
----------------------------------------------


각 Nano 및 Micro 의 pin 의 FUNCTION 과 ICSP header 정보는 다음 그림을 참고해 보세요.



Micro 는 Nano 와 조금 다른 pinout 을 가지므로 주의해야 합니다.



회로도는 다음과 같습니다.



실제로 연결한 사진은 다음과 같습니다.






5. SOURCE 에 올리는 sketch


다음으로, ISP 모드로 연결될 SOURCE 는 "ArduinoISP" sketch 가 올라가 있어야 합니다.

이 소스는 Arduino IDE 가 설치되어 있으면, 기본으로 가지고 있는 소스 입니다.


File > Examples > 11.ArduinoISP > ArduinoISP



저의 경우는 SOURCE 가 Arduino Micro 이므로, 이 micro 에 upload 해주었습니다.



위처럼 Programmer 를 "AVRISP mkII" 에서 "Arduino as ISP" 로 바꾸어 줍니다.


Board      | Arduino Nano
Processor  | ATmega328P
Port       | COM6 --> Port connected arduino as a SOURCE
Programmer | Arduino as ISP


위의 그림과 조금 다르지만, 새로운 소스는 이제 Old Bootloader 가 아니므로

Processor 에서 "ATmega328P" 로 정의해 주면 됩니다.


추가로 주의할 점은,

Programmer 에서 비슷하게 생긴 "ArduinoISP" 항목이 있지만, 속지 말고 "Arduino as ISP" 을 선택해줘야 합니다.


이제 준비가 완료 되었습니다.





6. 쉽게 끝나지 않는다


그렇죠... 이렇게 쉽게 끝나면 아쉽죠.

모든 글을 참조하여 잘 따라 했는데도 불구하고 실패합니다.



"Device signature = 0x000000" 이라는 문구가 중국 복제품이라서 그런가? 라는 생각도 해봤습니다.

중국 복제품은 아래처럼 Unknown board 라고 표시가 되거든요.



그래서, 이 부분을 강제적으로 메모리에 덮어 씌울 수 있는 방법이 없는가도 찾아 봤습니다.

없더군요... (Atmel Studio 라는게 있지만 잘 안됨...)



그래도 안되길래, ICSP header 접속시 1번 pin 을 잘못 인식하여 5V 를 엄한데 꽂아버려

chip 이 타버렸나 생각해 보기도 했습니다. 그렇게 되면 얼마 하지 않지만 또 구입해야 하니까 매우 번거럽게 됩니다.


참고로, 여러 troubleshooting 하면서 알게된 글이 아래 사이트 입니다.

발생할 수 있는 모든 가능성에 대해, 그에 따른 대응방법을 자세하게 적어 놨습니다.


Bootloader 관련하여 문제가 생겼을 시, 어떻게 조치해야 하는지를 알려주는 성지와 같은 글 입니다.


* Have I bricked my Arduino Uno? Problems with uploading to board

https://arduino.stackexchange.com/questions/13292/have-i-bricked-my-arduino-uno-problems-with-uploading-to-board




7. 심기 일전


다시 처음부터 시작한다는 마음으로

다음날 아침 일찍, 배선도 다시하고 ArduinoISP 도 새로 올려보고, 모든 과정을 다시금 천천히 해봤습니다.



음?!!!!!!!!!!

성공 했네요?!

"Done burning bootloader" 문구가 뜹니다.


성공한 로그를 첨부합니다.


Bootloader_update_201812.txt


축하 동영상 올라 갑니다. 아래와 같이 SOURCE 와 TARGET LED가 서로 사이좋게 끔뻑끔뻑 하면 됩니다.



확인해봐야 겠죠?

Processor 를 "ATmega328P" 로 변경하고, 아무 sketch 나 올려 봅니다.



잘 되네요.

로그를 첨부합니다.


Sketch_upload_201812.txt



첫날 연속 실패의 원인은, pin 을 잘못 연결하지 않았나로 추측해 봅니다.

역시 이 취미는 차분하게 해야 합니다.





8. 여러가지...


IDE 가 업데이트 되면서 지 맘대로 Bootloader 도 업데이트 할 수 있게 되었다면,

필시 그 파일이 존재할 터, 찾아봅니다.


C:\Program Files (x86)\Arduino\hardware\arduino\avr\bootloaders\optiboot



요놈!


Atmel Studio 를 잠깐 사용해 보면서 느낀 것은,

기존에 보유하고 있는 Arduino 의 상태에 대해 알아둘 필요가 있었습니다.


예를 들면, memory 크기나, 인식하는 code, Signature, Fuse bit 같은 것들.

아래 소스를 사용하면 ATmega 시리즈를 사용하는 Arduino 의 chip 현황을 자세하게 알 수 있습니다.


* nickgammon/arduino_sketches

https://github.com/nickgammon/arduino_sketches

- arduino_sketches-master.zip


특히 "Atmega Board Detector" 를 사용하면 Bootloader 소스 뿐만 아니라 거의 모든 정보를 알 수 있습니다.

여기서도 연결은 Arduino as ISP + ICSP header 조합입니다.


우선 Arduino Nano 의 정보 입니다.

Arduino_Nano_result.txt


Atmega chip detector.
Written by Nick Gammon.
Version 1.20
Compiled on Dec 10 2018 at 21:28:36 with Arduino IDE 10803.
Attempting to enter ICSP programming mode ...
Entered programming mode OK.
Signature = 0x1E 0x95 0x0F 
Processor = ATmega328P
Flash memory size = 32768 bytes.
LFuse = 0xFF 
HFuse = 0xDA 
EFuse = 0xFD 
Lock byte = 0xCF 
Clock calibration = 0x87 
Bootloader in use: Yes
EEPROM preserved through erase: No
Watchdog timer always on: No
Bootloader is 2048 bytes starting at 7800

Bootloader:

7800: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
7810: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 
...


이대로 끝나면 아쉬우니, Arduino Micro 도 확인해 봅니다.

Micro 는 ICSP header 의 1번 pin 자리가 살짝 다르니 주의해야 합니다. (아래 그림)



후후, 정상으로 돌아온 Arduino Nano 를 SOURCE 로 하고 Arduino Micro 를 확인해 보는 사진입니다.



Arduino Micro 의 정보 입니다.

Arduino_Micro_result.txt


Atmega chip detector.
Written by Nick Gammon.
Version 1.20
Compiled on Dec 10 2018 at 21:28:36 with Arduino IDE 10803.
Attempting to enter ICSP programming mode ...
Entered programming mode OK.
Signature = 0x1E 0x95 0x87 
Processor = ATmega32U4
Flash memory size = 32768 bytes.
LFuse = 0xFF 
HFuse = 0xD8 
EFuse = 0xCB 
Lock byte = 0xEF 
Clock calibration = 0x71 
Bootloader in use: Yes
EEPROM preserved through erase: No
Watchdog timer always on: No
Bootloader is 4096 bytes starting at 7000

Bootloader:

7000: 0x5F 0xC0 0x00 0x00 0x78 0xC0 0x00 0x00 0x76 0xC0 0x00 0x00 0x74 0xC0 0x00 0x00 
7010: 0x72 0xC0 0x00 0x00 0x70 0xC0 0x00 0x00 0x6E 0xC0 0x00 0x00 0x6C 0xC0 0x00 0x00 
...


Micro는 Bootloader 가 훨씬 크네요.

USB 연결을 위한 chip 도 built-in 되어 있고, 나중에 나온 CPU 이니 확장이 많이 되어있는 듯 합니다.





FIN


뭐가 달라졌을까요?

Old Bootloader 를 일부러 선택하지 않아도 된다는 정도?


또한 가장 눈에 띄는건 Baud Rate 입니다.

19200 --> 115200 으로 바뀐걸 아래와 같이 알 수 있네요.


기존


         Using Port                    : COM6
         Using Programmer              : arduino
         Overriding Baud Rate          : 19200
         AVR Part                      : ATmega328P
         Chip Erase delay              : 9000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53



NEW


         Using Port                    : COM6
         Using Programmer              : arduino
         Overriding Baud Rate          : 115200
         AVR Part                      : ATmega328P
         Chip Erase delay              : 9000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53



And

Hardware | 8x8 LED matrix 와 Colorduino 이용해 보기

|

1. 8x8 LED Matrix


한동안 LED bar 나 LED 전구, 74HC595 등을 사용하다가,

"FULL COLOR 8x8 LED Dot Matrix" 라는 문구를 보게 됩니다.


때는 바야흐로 2017년 5월 24일...

아래 제품을 구입하게 됩니다.


* Full Color 8x8 8*8 Mini Dot Matrix LED Display Red Green Bule RGB Common Anode Digital Tube Screen For Diy 60mmx60mmx5mm

https://www.aliexpress.com/item/5mm-8x8-8-8-Full-Colour-RGB-LED-Dot-Matrix-Display-Module-Common-Anode/32452391556.html



정말 이쁘게 생겼죠?




2. 도착


큰 무리 없이 잘 도착 했습니다.



dot 의 한개씩 자세히 보면, 조그마한 3가지 LED가 하나의 dot 를 이룹니다.



우리가 흔히 알고 있는 3색 - 빨강, 파랑, 녹색이 모든 색을 표현하는 원리를 이용하는 구조로 생각할 수 있습니다.



핀이 많은 것을 보면, full color 임은 확실해 보입니다.

단색일 경우는 아래 보이는 pin 수보다 훨씬 적습니다.



자... 그럼 arduino 와 어떻게 연동될까요.

인터넷 바다에서 검색에 검색을 거듭합니다.





3. 구현 방법


RGB 를 섞어 색을 만들며, 색의 변화를 컨트롤 하는 주된 기능은 "Pulse Width Modulation" 이라고 합니다.

한국에서는 "펄스 폭 변조" 라는군요. (그냥 직역이지 않소...)


* Pulse-width modulation

https://en.wikipedia.org/wiki/Pulse-width_modulation


SparkFun 에서도 관련한 설명을 해 놓은 web page 가 있어서 여기에 링크를 걸어 놓습니다.


* Pulse Width Modulation

https://learn.sparkfun.com/tutorials/pulse-width-modulation/all


간단히 이야기 하면, 펄스의 "" 만을 조정하여, 빛의 강약이나 모터의 구동 속도를 조절하는 것입니다.

눈으로 보기에는 자연스러운 흐름이지만, 주파수적으로는 끊어서 조정하는 방법이라고 합니다.



"난, arduino 를 가지고 놀려고 했는데, 공부를 해야 하는군" 이라는 생각을 다시금 깨우쳐 주는 대목입니다.


자, 그래서 8x8 led dot matrix 를 구동하려면 어떤 선례들이 있는지 찾아보니, 잘 정리된 글들이 대략 다음과 같군요.

결론부터 이야기 하면, 필자도 이런 이야기를 합니다.


"Pulse width modu - WHAT ?"


읽어보면, 결국 74HC595 + ATmega328 등을 이용하여, 전용 breakout 보드를 만들어서 컨트롤 하고 있었습니다.
이게 단순한게 아니었구나...

* 8×8 RGB LED Matrix

http://blog.spitzenpfeil.org/wordpress/projects/8x8-rgb-led-matrix/


* 64 Pixel RGB LED Display - Another Arduino Clone

- https://www.instructables.com/id/64-pixel-RGB-LED-Display-Another-Arduino-Clone/


* How to Build a 8×8 RGB LED Matrix with PWM using an Arduino

http://francisshanahan.com/index.php/2009/how-to-build-a-8x8x3-led-matrix-with-pwm-using-an-arduino/


* nrj/LEDMatrixControl

https://github.com/nrj/LEDMatrixControl


여기까지 하려면 시간이 많이많이많이 걸리겠는걸... 라고 생각 후, 일단 덮고 다른걸로 한동안 시간을 보내게 됩니다.





4. Colorduino


시간이 흘러 흘러 1년...



우연히 Colorduino 라는 제품의 존재를 알게 됩니다.


알게 된지는 꽤 되었지만, 직접 breakout 보드를 만들어 보고자 무시해 왔지만, 너무 일이 커지는듯 하여 포기하고,

1년이 훌쩍 지난 2018년 11월, 이 구동 driver 격인 breakout 보드 구입을 위해 조사하게 됩니다.


제조사는 ITead 라는 회사군요.


* ITEAD Intelligent Systems Co.Ltd.

https://www.itead.cc/


현재 Colorduino 는 version 1.4 까지 나와있는 듯 합니다.


* Colorduino V1.4 Color Rainbow Matrix RGB LED Driver Shield For Arduino

https://www.itead.cc/colorduino-v1-4.html


아래 스샷들은 제품 website 에서 가져온 내용인데,

지금까지 고민한 것들이 모두 구현되어 있는 모습을 보여주고 있습니다.



PWM 을 위해서 전용 chip이 채용되었군요.



컨트롤을 위해서 arduino 에서 사용하는 ATmega328 이 채용되었습니다.


그래서 Arduino IDE 와 FTDI 를 통해서 연결 시,

보드를 ATmega328 을 채용한 보드 - Uno, Duemilanove, Nano - 를 선택하면 문제가 없습니다.


관련된 library 및 example 소스는 WIKI 형식으로 정리가 되어 있습니다.


* Colorduino V1.3 (WIKI)

https://www.itead.cc/wiki/Colorduino_V1.3


* Colorduino V1.4 (WIKI)

https://www.itead.cc/wiki/Colorduino_V1.4


사용된 각 chip 의 datasheet 는 아래와 같이 이 post 에 첨부해 놓습니다.


* Datasheet

- Colorduino : DS_IM120410004_Colorduino.pdf

DM163 : DS_DM163.pdf

M54564FP : DS_M54564FP.pdf


* Fritzing Parts

Colorduino.fzpz





5. Colorduino / Funduino 구입


AliExpress 에서 검색하면, Colorduino 의 clone 제품인 "Funduino" 가 판매되고 있습니다.

잘 보면, Colorduino V1.3 버전을 기준으로 만든 제품입니다.


* Free shipping ! Full color 8 * 8 LED RGB matrix screen driver board Colorduino for arduino

https://www.aliexpress.com/item/Free-shipping-Full-color-8-8-LED-RGB-matrix-screen-driver-board-Colorduino-for-arduino/2045397138.html



위의 ITead 사이트의 V1.3 과 비교해 보면, 완벽히 동일하다는 것을 알 수 있습니다.





6. Funduino 도착


가격이 좀 있다 보니, 2주만에 도착했습니다.



뽁뽁이로 잘 쌓여서 도착했습니다. 믿음직 스러운 배송입니다.



상면샷 입니다. 깔끔하게 만들어져 있네요.



다시금 Colorduino 와 동일함을 느끼게 됩니다.



ATmega328 도 보이며, PWM 을 위한 DM163 도 보입니다.



뒷면에는 당당하게(?) Funduino v1.A 라고 마킹되어 있습니다.






7. 장착 및 FTDI 연결


우선 8x8 LED matrix 의 1번 pin (not 어뢰) 를 Funduino 의 1번 소캣에 맞추어 끼웁니다.



Dot matrix 에 딱 가려지는 크기 입니다. 잘 만들었네요.



FTDI 와 pin 연결은 다음과 같습니다.


  FTDI | Funduino
------------------
  DTR  |   DTR
  RX   |   TXD
  TX   |   RXD
  VCC  |   VDD
  GND  |   GND
------------------



실제로 FTDI 와 연결된 모양은 다음과 같습니다.

(한데 묶여있는 선 다발로 조금 지저분해 보이지만, 그건 오해입니다.)



일단, PC USB --> FTDI --> Funduino 를 연결하면, 미리 구워진 프로그램으로 구동됩니다.



동영상으로 찍어 봤습니다.



이쁘네요.





8. Arduino IDE 설정 및 Library 설치


Colorduino 는 기본으로 ATmega328 을 가지고 있으므로,

IDE 에서는 ATmega328 을 실장하고 있는 Arduino Nano / Uno / Duemilanove 어느것을 선택해도 됩니다.



최종적으로 소스 프로그램이 ATmega328 용으로 컴파일 되면 문제가 없으니까요.


Colorduino 의 Library 를 다운로드하여 등록합니다.



그러면 아래와 같이 example 을 로드할 수 있습니다.



아래는 Colorduino 의 Plasma 와, 문자를 스크롤 하는 Library 링크 입니다.

혹시 모르니, 실제 파일도 첨부해 놓습니다.


* Colorduino Library

https://github.com/Electromondo-Coding/Colorduino

Colorduino-master.zip


* Colorduino Scroller Library

https://github.com/Electromondo-Coding/ColorduinoScrollerLibrary

ColorduinoScrollerLibrary-master.zip


위의 Scroller 는 위의 Colorduino Library 와 서로 의존성을 갖습니다.


또다른 버전의 Colorduino Library 도 존재하는데, 그게 아래 링크 및 파일입니다.

위의 Library 와 비슷하지만, 좀더 PWM 이 부드럽게 동작하는 듯 합니다.


그래서, 아래 Colorduino Library 와 위의 Scroller Library 를 혼합하여 설치하면,

Scroller 가 동작하지 않으니 주의가 필요합니다.


* Colorduino Library

https://github.com/lincomatic/Colorduino/

Colorduino-master.zip





9. Plasma 와 Scroller


위의 두 example 을 구동시킨 동영상을 첨부합니다.

우선 Plasma 동영상 입니다.



Scroller 에서는 아래 처럼 text 를 수정하여, 원하는 text 를 뿌려줄 수 있습니다.



Scroller 의 동영상 입니다.






FIN


거의 1년 6개월 걸린, 8x8 LED Dot Matrix 의 동작확인이 이제야 끝났습니다.

뭔가 생산적으로 coding 을 해보고 싶었으나, example 소스를 보고 바로 접었습니다.


꼭 coding 을 해야 할 때가 되면 그때 하려구요.


And

Hardware | MAX30105 파티클 센서 - 2

|

이 글은 이전 MAX30105 파티클 센서를 활용하여 OLED 에 심전도를 그려보는 도전기 입니다.

이전 글은 아래 link 를 참고하세요.


* Hardware | MAX30105 파티클 센서 - 1

http://chocoball.tistory.com/entry/Hardware-MAX30105-particle-sensor-1





1. min, max 와 scalar


일단 MAX30105 에서 입력받는 값은 5만에서 많게는 10만도 넘어갑니다.

이를 32 pixel 너비를 갖는 OLED 에 표현하려면 그 값에 맞도록 축소시켜야 합니다.



이에 더하여, 값들의 기준이 아래 그림처럼 널을 뛰므로 기준값을 잡기가 여간 쉽지 않습니다.



기준값을 잡기 위해 min, max 값을 한 사이클당 판별하여 scale in 치환값을 잡아줘야 합니다.


이를 위해 일단, 이전 사이클에서 얻은 제일 작은 min 값을 이용하여,

입력받는 값에서 빼면 그래프가 아래 그림처럼 내려갑니다.



OLED 폭이 128 pixel 이므로, 이 폭을 한 사이클로 잡습니다.

EXCEL 을 통하여 위의 방식을 검증해 본 결과 문제 없이 표현되는 것을 확인할 수 있었습니다.



OLED 의 y 값에 해당하는 값을 EXCEL 에서 32 pixel 기준으로 변환된 값을 array 에 입력하고

순서대로 출력해서 OLED 에 맞는지 최종 확인해 봅니다.



요리조리 검증해 본 결과 아래 식을 통해서 min, max 값과 한 사이클을 OLED 폭인 128 에 표현하고

다시 0 부터 시작하는 - 화면이 바뀌는 방법을 확인하였습니다.


	irValue = particleSensor.getIR();
	
	if( x > 127) { //refresh OLED screen
		display.clearDisplay();
		lastx=0;
		x=1;
		
		scaler = (max_irValue - min_irValue) / 32;
		pre_min_irValue = min_irValue;
		
		// reset min, max value
		max_irValue = irValue;
		min_irValue = irValue - 1;
	}





2. y값 reverse


OLED 에 표현시, x/y 값은 우리가 일반적으로 배운 왼쪽 아래에서 시작되는 것이 아니라,

왼쪽 위로부터 값의 증가를 표현합니다.



즉, sensor 로부터 값을 입력 받으면, 상하 전치를 시켜줘야 합니다.

제가 쓰고 있는 것은 상하 32 pixel 이므로, 아래 한 pixel 을 빼면 31 이므로,

31 에서 입력받은 값을 빼주면 표현하고자 하는 위치로 바꿀 수 있습니다. 



	// invert y values to fit OLED display
	if ( pre_min_irValue > irValue) {
		y = 31;
	} else {
		y = 31 - ((irValue - pre_min_irValue) / scaler);
	}

값이 너무 적어지면, 값이 역전하여 아래 그림같이 튀어버립니다.

그래서 전 사이클에서 확인 되었던 min value 보다 적어지면 강제적으로 최저값을 할당합니다.

여기서 "y = 31" 은 OLED 상에서는 제일 밑에 표시되는, 일상의 0 값과 같습니다.






3. BPM 과 piezo buzzer


심전도의 값이 가장 높아지면 buzzer 를 울리게 하는 소스 입니다.

또한 BPM 값도 표현해 줍니다.


BPM 값은 심전도의 한 사이클 평균을 내어 1분동안 얼마나 맥박이 뛰는지를 수치화 한 값입니다.



소스는 다음과 같고, 아래 blog 를 전면 참고하였습니다.


* Heart beat Sensor and “ECG” Display

http://www.xtronical.com/basics/heart-beat-sensor-ecg-display/


	ThisTime=millis();
	if( y < UpperThreshold ) {
		if(BeatComplete) {
			BPM=ThisTime-LastTime;
			BPM=int(60/(float(BPM)/1000));
			BPMTiming=false;
			BeatComplete=false;
			tone(2,1000,250);
		}
		if(BPMTiming==false) {
			LastTime=millis();
			BPMTiming=true;
		}
	}
	if((y > LowerThreshold) && (BPMTiming))
		BeatComplete=true;






4. source


지금까지의 작업을 모두 합하여 하나의 소스로 만들었습니다.


#include "Wire.h"
#include "MAX30105.h"

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"

// Hardware SPI
#define OLED_DC     6
#define OLED_CS     7
#define OLED_RESET  8
Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);

MAX30105 particleSensor;

unsigned int irValue;
unsigned int max_irValue;
unsigned int min_irValue;
unsigned int x = 0;
unsigned int y;
unsigned int lastx = 0;
unsigned int lasty = 0;
unsigned int scaler;
unsigned int pre_min_irValue;
int BPM;
int LastTime = 0;
int ThisTime;
bool BPMTiming=false;
bool BeatComplete=false;
#define UpperThreshold 10
#define LowerThreshold 25

void setup() {
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC);
  display.clearDisplay();
  
  // initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { //Use default I2C port, 400kHz speed
    display.println("MAX30105 was not found. Please check wiring/power.");
    while (1);
  }
  
  //Setup to sense a nice looking saw tooth on the plotter
  byte ledBrightness = 0x1F; //Options: 0=Off to 255=50mA
  byte sampleAverage = 8; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  int sampleRate = 1000; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
  
  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
  
  // initiate min, max values
  irValue = particleSensor.getIR();
  max_irValue = irValue;
  min_irValue = max_irValue - 1; 
  for (int ini ; ini < 100 ; ini++) {
	  irValue = particleSensor.getIR();
	  
	  if (max_irValue < irValue) {max_irValue = irValue;}
	  if (min_irValue > irValue) {min_irValue = irValue;}
  }
  
  display.display();
}

void loop() {
	irValue = particleSensor.getIR();
	
	if( x > 127) { //refresh OLED screen
		display.clearDisplay();
		lastx=0;
		x=1;
		
		scaler = (max_irValue - min_irValue) / 32;
		pre_min_irValue = min_irValue;
		
		// reset min, max value
		max_irValue = irValue;
		min_irValue = irValue - 1;
	}
	
	display.setTextColor(WHITE);
	ThisTime=millis();

	// invert y values to fit OLED display
	if ( pre_min_irValue > irValue) {
		y = 31;
	} else {
		y = 31 - ((irValue - pre_min_irValue) / scaler);
	}
	
	// draw heartbeat lins
	display.drawLine(lastx, lasty, x, y, WHITE);
	lasty = y;
	lastx = x;
	
	// update min, max values
	if (max_irValue < irValue) {max_irValue = irValue;}
	if (min_irValue > irValue) {min_irValue = irValue;}
	
	ThisTime=millis();
	if( y < UpperThreshold ) {
		if(BeatComplete) {
			BPM=ThisTime-LastTime;
			BPM=int(60/(float(BPM)/1000));
			BPMTiming=false;
			BeatComplete=false;
			tone(2,1000,250);
		}
		if(BPMTiming==false) {
			LastTime=millis();
			BPMTiming=true;
		}
	}
	if((y > LowerThreshold) && (BPMTiming))
		BeatComplete=true;
	
	// display BMP
	display.fillRect(0,24,64,32,BLACK);
	display.setCursor(0,24);
	display.print("      BPM");
	display.setCursor(0,24);
	display.print(BPM);
	
	// check finger is on the sensor
	if (irValue < 10000) { display.setCursor(1,1); display.print("NO Finger?"); }
	
	// display all data to OLED
	display.display();
	x++;
}


결과 동영상을 올려 봅니다.







FIN


이걸 구현해 보려고 3주간 주말 = 토, 일 이틀간 x 3 = 6일 동안 삽질의 연속이었습니다.

이 문제는 소스에서 scalar 를 구하기 위한 나누기를 해야 하는데, % 연산자를 사용했기 때문이었습니다.


* Modulo

https://www.arduino.cc/reference/en/language/structure/arithmetic-operators/modulo/


% 연산자는 몫을 구하는 것이 아니라, 나머지를 구하는 연산자지요!!!

반대로 알고 있었던 것. 이걸 알아 차리는데 6일이나 걸렸습니다!!!



변수의 갯수도 줄여 보기도 하고, 각 값을 먼저 scale in 해서 결과를 내보기도 하고, 하여간 별짓 다 했습니다.

처음에 디버깅만 잘 했어도 이렇게 오래 걸리지는 않았을 듯 하네요.

디버깅 작업이 얼마나 중요한지 새삼 깨닳았습니다.



이렇게 하면 Serial Monitor 에도 이렇게 나오니 환장할 노릇이었습니다.


이 덕에 Hardware SPI 활용과 OLED refresh rate 등등을 공부하게 되었네요.

아이고야... 좀 쉬자.


SpO2 농도 값이나, 그래프를 좀더 안정적으로 보여줄 수 있도록 소스를 더 다듬고 싶었지만,

에너지를 너무 많이 써서 이번 작업은 여기까지 하겠습니다.


나중에 다시 생각나면, 소스코드를 다시 잡아 볼께요.


And

Hardware | MAX30105 파티클 센서 - 1

|

1. 파티클 센서


인터넷을 돌아다니다 particle sensor 라는 것을 알게 되었습니다.

이게 단순히 공기중의 입자를 확인하는것 뿐만아니라, 혈중 농도 및 심전도까지 확인할 수 있다고 합니다.


나중에 도플러 효과를 이용한 원격 심전도 확인 시스템을 만들 예정이라,

기초적인 지식을 습득하기 위해 일단 이런 류의 센서 사용법을 익혀 보기로 합니다.






2. 주문


물론 AliExpress 에서 주문했습니다.


* CFsunbird High Accuracy I2C MAX30105 Particle Optical Sensor Photodetectors Board Module 1.8V power supply

https://ko.aliexpress.com/item/CFsunbird-High-Accuracy-I2C-MAX30105-Particle-Optical-Sensor-Photodetectors-Board-Module-1-8V-power-supply/32819571918.html




여타 센서류보다 단일 부품으로는 꽤나 비싼편 입니다.

이는 다양한 기능이 들어가 있으며, 기본 소자 자체가 좀 있어 보입니다.


SparkFun 에서 팔고 있는 제품인데, AliExpress 에서 구입하다 보니, 조금 변형된 중국 제품입니다.

단, 소스는 완벽하게 호환됩니다.


* SparkFun Particle Sensor Breakout - MAX30105

https://learn.sparkfun.com/tutorials/max30105-particle-and-pulse-ox-sensor-hookup-guide


보통 정식 제품보다는 AliExpress 가 싼 편인데, 이 제품은 자비가 없군요.

AliExpress 에서 약 12 USD, SparkFun 제품은 13 USD 정도 합니다.





3. 도착


크게 무리없이 2주만에 도착.



납땜이 되지 않은 상태로 배달이 됩니다. 이제 AliExpress 에서 오는 것은 이게 당연한 거죠?



핵심이 되는 센서 부품을 클로즈 업 해봤습니다.



범상치 않아 보이는군요.





4. 연결


Arduino 와의 연결은 다음과 같습니다.


 MAX30105 | Arduino Nano
-------------------------
    VCC   |     3.3V
    GND   |     GND
    SDL   |     A5
    SDA   |     A4
-------------------------


실제 회로 구성은 다음과 같습니다.

Fritzing 에서 그려 보는데, 아쉽게도 아직 Fritzing 에서는 MAX30105 부품이 리스트에 존재하지 않습니다.

아직 아무도 만들지 않았나 봅니다.



Datasheet 입니다 - MAX30105_3.pdf





5. sketch - Red, IR, Green reading


이후 나오는 소스는 모두 아래 site 를 참고하였습니다.


* SparkFun Particle Sensor Breakout - MAX30105

https://learn.sparkfun.com/tutorials/max30105-particle-and-pulse-ox-sensor-hookup-guide


Library 를 인스톨 하고 가장 간단한 스케치를 로드하여 확인해 봅니다.

소스는 소자의 기본 동작인 빨간색, 적외선, 녹색을 감지하고 수치화 하는 스케치 입니다.


File > Examples > MAX3010x Pulse and Proximity Sensor > Example1_Basic_Readings



실제 소스를 여기에 옮겨 봅니다.


/*
  MAX30105 Breakout: Output all the raw Red/IR/Green readings
  By: Nathan Seidle @ SparkFun Electronics
  Date: October 2nd, 2016
  https://github.com/sparkfun/MAX30105_Breakout

  Outputs all Red/IR/Green values.

  Hardware Connections (Breakoutboard to Arduino):
  -5V = 5V (3.3V is allowed)
  -GND = GND
  -SDA = A4 (or SDA)
  -SCL = A5 (or SCL)
  -INT = Not connected

  The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V
  but it will also run at 3.3V.

  This code is released under the [MIT License](http://opensource.org/licenses/MIT).
*/

#include "Wire.h"
#include "MAX30105.h"

MAX30105 particleSensor;

#define debug Serial //Uncomment this line if you're using an Uno or ESP
//#define debug SerialUSB //Uncomment this line if you're using a SAMD21

void setup() {
  debug.begin(9600);
  debug.println("MAX30105 Basic Readings Example");

  // Initialize sensor
  if (particleSensor.begin() == false)
  {
    debug.println("MAX30105 was not found. Please check wiring/power. ");
    while (1);
  }

  particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive
}

void loop() {
  debug.print(" R[");
  debug.print(particleSensor.getRed());
  debug.print("] IR[");
  debug.print(particleSensor.getIR());
  debug.print("] G[");
  debug.print(particleSensor.getGreen());
  debug.print("]");

  debug.println();
}


우선 구동을 시작하면, 녹색과 빨간색 빛이 빠르게 점멸합니다.



저렇게 빛을 쏜 다음 반사되는 값을 읽는것이겠죠?



손가락을 센서 근처로 가져가면 값의 변동이 일어납니다.


센서의 불빛을 슬로우 모션으로 9초동안 찍어 봤습니다.

그랬더니 51초짜리 동영상이 되었네요.



얼마나 빨리 점멸을 하는지, 슬로모션으로 찍어도 실제로 보는것과 그리 차이나지 않습니다.





6. sketch - 물체 인식


IR 의 delta 값을 이용하여 물체가 일정 이상 안에 들어와 있는지 없는지를 판단합니다.

소스는 아래 path 에서 확인할 수 있습니다.


File > Examples > MAX3010x Pulse and Proximity Sensor > Example2_Presense_Sensing






7. sketch - 온도


SparkFun 사에서 제공되는 library 에서 "readTemperature()" 함수를 그대로 이용한 것 입니다.

그럼 위의 함수는 어떻게 되느냐 하면 아래와 같습니다.


// Die Temperature
// Returns temp in C
float MAX30105::readTemperature() {
  // Step 1: Config die temperature register to take 1 temperature sample
  writeRegister8(_i2caddr, MAX30105_DIETEMPCONFIG, 0x01);

  // Poll for bit to clear, reading is then complete
  // Timeout after 100ms
  unsigned long startTime = millis();
  while (millis() - startTime < 100)
  {
    uint8_t response = readRegister8(_i2caddr, MAX30105_DIETEMPCONFIG);
    if ((response & 0x01) == 0) break; //We're done!
    delay(1); //Let's not over burden the I2C bus
  }
  //TODO How do we want to fail? With what type of error?
  //? if(millis() - startTime >= 100) return(-999.0);

  // Step 2: Read die temperature register (integer)
  int8_t tempInt = readRegister8(_i2caddr, MAX30105_DIETEMPINT);
  uint8_t tempFrac = readRegister8(_i2caddr, MAX30105_DIETEMPFRAC);

  // Step 3: Calculate temperature (datasheet pg. 23)
  return (float)tempInt + ((float)tempFrac * 0.0625);


센서 내부에 register 값을 읽어와서 표현하는 것이군요.

구동 소스는 아래 path 에서 확인할 수 있습니다.


File > Examples > MAX3010x Pulse and Proximity Sensor > Example3_Temperature_Sense







8. sketch - 심전도


손가락을 센서에 대고 있으면 심전도를 그려주는 소스 입니다.


File > Examples > MAX3010x Pulse and Proximity Sensor > Example4_HeartBeat_Plotter


읽어들인 IR 값에 대한 변화를 plottor 을 이용해 그려주는 것이죠.
소스를 보면 setup 과 기준값 등을 정해주는 부분이 있을 뿐, 단순히 IR 값을 불러옵니다.

아래처럼 보려면, Tools > Serial Plotter 를 통해서 확인할 수 있습니다.


저의 심전도 모양입니다. 두근두근...





9. sketch - 심박수


대체로 심전도와 비슷한데, 이걸 1분에 몇분 정도 뛰는지를 확인해 주는 소스 입니다.

보통 병원이나 건강검진시 확인해 보는 심박수 겠죠?


구동 소스는 아래 path 에서 확인할 수 있습니다.


File > Examples > MAX3010x Pulse and Proximity Sensor > Example5_HeartRate







FIN


MAX30105 센서는 주로, 심박수 모니터링에 사용되는게 가장 적절해 보입니다.

인터넷에서 어느 분이, 이와 비슷한 센서를 이용하여 심박수를 OLED 에 표시해주는 것까지 해놓은 글이 아래 입니다.


* Heart beat Sensor and “ECG” Display

http://www.xtronical.com/basics/heart-beat-sensor-ecg-display/


저도 따라해 봤는데,

일단 센서에서 나오는 값 자체가 MAX30105 처럼 큰 값으로 나오지 않을 뿐더러,

값의 변화가 딱 OLED 크기만큼 잘 구현이 되어 있어서 잘 하신것 같은데, 도저히 따라해도 안되는군요.



겨우겨우 우겨 넣어서 OLED 에 보여주는 것 까지는 해 봤습니다.

이것 가지고는 만족스럽지 못하여, 못하는 코딩을 이용하여 한번 도전해 보겠습니다.


병원에서 보는 듯 한 모습으로 될 때까지 만들어서 공유해 볼께요.



And

Hardware | RTC DS3231 부품 사용기 - 2

|

이 포스트는 앞전에 DS3231 을 사용해 보면서, 못다한 이야기를 하기 위해 구성했습니다.


* Hardware | RTC DS3231 부품 사용기 - 1

http://chocoball.tistory.com/entry/Hardware-RTC-usning-DS3231-1



1. AT24C32 (EEPROM) I2C address


DS3231 에 붙어있는 EEPROM 은 ATMEL 사의 AT24C32 라는 칩 입니다.

32bit = 4kByte EEPROM 의 I2C 기본 주소값은 0x57 입니다.


 

 A0

A1

A2 

0x57

0

0

0

0x56

1

0

0

0x55

0

1

0

0x54

1

1

0

0x53

0

0

1

0x52

1

0

1

0x51

0

1

1

0x50

1

1

1

* 0 = open, 1 = short


참고로 0x57 은 16진수 이므로, 10진수로 표시하고자 할 때에는 87 이라고 입력해도 됩니다.


* Hex to Decimal converter

https://www.rapidtables.com/convert/number/hex-to-decimal.html






2. 유령 I2C address - 0x5f


제가 사용한 fake DS3231 은, 사용되지 않는 "0x5f" 라는 I2C 주소가 존재합니다.



모르고 지나가기엔 너무 궁금합니다.

여러 방법으로 찾아 봤는데, 가장 신뢰성 있는 설명은 다음 LINK 인 듯 합니다.


* I2C response to "Ghost Address" 0x5F

https://electronics.stackexchange.com/questions/397569/i2c-response-to-ghost-address-0x5f



즉, 정품에 포함되어 있는 32k EEPROM 은 ATMEL 문자로 시작하는데,

이 fake 제품에 다른 chip 이 있는 것이 아니라, ATMLH745 는 address 가 두개 있는 듯 합니다.


왜냐하면, 위의 첫번째 댓글에 A2 단자를 short 시켰을때 EEPROM 주소가 바뀌는데,

이 ghost address 도 연동해서 바뀐다는 테스트 결과가 나왔거든요.


A0, A1, A2 all open (default)
--------------------------------------------
|   chip  |    DIGIT   |   HEX  |  DECIMAL |
--------------------------------------------
| AT24C32 | 0b01010111 |  0x57  |    87    |
--------------------------------------------
|  ghost  | 0b01011111 |  0x5f  |    95    |
--------------------------------------------

A0, A1 open, A2 short
--------------------------------------------
|   chip  |    DIGIT   |   HEX  |  DECIMAL |
--------------------------------------------
| AT24C32 | 0b01010011 |  0x53  |    83    |
--------------------------------------------
|  ghost  | 0b01011011 |  0x5b  |    91    |
--------------------------------------------


그럼 AT24C32 주소를 0x5f 로 해보면 어떨까요?

아래처럼 소스를 조금 바꿔 봤습니다.


#include "Wire.h"
#define AT24C32_I2C_ADDRESS 0x5f //I2C address of AT24C32

byte seconds, minutes, hours, day, date, month, year;

void setup() {
	Serial.begin(9600);
	delay(1000);
	
	Wire.begin();
}

void loop() {
	getAT24C32Data();
	
	Serial.print("seconds : "); Serial.println(seconds, BIN);
	Serial.print("minutes : "); Serial.println(minutes, BIN);
	Serial.print("hours   : "); Serial.println(hours, BIN);
	Serial.print("day     : "); Serial.println(day, BIN);
	Serial.print("date    : "); Serial.println(date, BIN);
	Serial.print("month   : "); Serial.println(month, BIN);
	Serial.print("year    : "); Serial.println(year, BIN); 
	Serial.println("");
	
	delay(1000);
}

void getAT24C32Data() {
	// send request to receive data starting at register 0
	Wire.beginTransmission(AT24C32_I2C_ADDRESS);
	Wire.write(0x00); // start at register 0
	Wire.endTransmission();
	Wire.requestFrom(AT24C32_I2C_ADDRESS, 7); // request seven bytes
	
	if(Wire.available()) {
		seconds = Wire.read(); // get seconds
		minutes = Wire.read(); // get minutes
		hours   = Wire.read(); // get hours
		day     = Wire.read();
		date    = Wire.read();
		month   = Wire.read(); //temp month
		year    = Wire.read();
	} else {
		//oh noes, no data!
	}
}


원래 AT24C32 의 주소인 0x57 를 넣으면 아래처럼 나옵니다만,

0x5f 로 하면 data 는 오는데, 전부 255 값을 갖습니다.


0x57 로 할 때



0x5f 로 할 때



엄한 어드레스인 0x83 로 할 때



값이 동일하지 않아서, 완벽히 동일한 값에 접근하는것 같지는 않습니다.

그래도 뭔가 있기는 한것 같네요.


소설을 써 보자면, 중국에서 fake 칩을 만들면서 다른 기능도 넣지 않았을까...

혹시 어떤 다른 정보와 관여하는 기능이 있는것이 아닌지?





3. Battery 의 종류


밧데리는 지금 CR2032 을 끼워서 사용하고 있습니다.

시간 유지만으로 사용되면 1uA 정도 사용되므로, 200mAh 라고 한다면 약 20년은 사용 가능하다고 하네요.


* RTCモジュール DS3231+AT24C32 ZS-042

https://blogs.yahoo.co.jp/dascomp21/68145713.html



다만, 사양적으로는 충방전이 되는 LIR2032 을 사용하라고 하네요.



실제로 약 1년정도 사용하다가 베터리가 터진 케이스가 인터넷에 보고되었습니다.


회로 구성도를 봐도, 충전이 되게끔 만들어진 회로라서 rechargeable battery 를 사용하지 않으면,

지속적으로 건전지에 부담이 가게끔 되어 있다고 합니다.



굳이 CR2025 을 장기간 사용하려 한다면, 위의 그림처럼 방충전 회로와 연결되는 패턴을 끊어주면 된다 합니다.


잠깐 쓰고 빼 놓거나, 잔량이 거의 없는거라면 괜찮겠죠?

저는 체중계에서 다 쓴 건전지를 사용하고 있어서 그냥 낑궈 놓으려고 합니다.


이 정보는 아래 사이트를 참고하였습니다.


* RTC DS3231/DS1302を調べて見ました

https://blogs.yahoo.co.jp/hobbyele/64900109.html





4. BCD


EEPROM 에 집어 넣고 빼는 값의 포맷은 decimal 이지만, 실제로 저장되는 값은 binary 나열값 입니다.


더 나아가, 이는 아래 그림처럼 EEPROM 에 저장되는 방식이 얼핏 보기에는 실제의 값을 단순히 binary 로 바꾼 값처럼 보이지만,

이 binary 값은 기능적으로 입력되어 있을 뿐, 실존하는 값과는 다른 값 입니다.


결국, 입출력은 decimal, 저장된 것은 binary, 더욱이 이 decimal / binary 가 실제의 값을 표현하지는 않다는 것이죠.


그래서 값 치환이 3번 일어나게 됩니다.

- decimal 실제 값을 EEPROM 에 저장될 binary 를 상정하여 변경 (변경 1)

- 이를 decimal 로 치환하여 EERPOM 에 전송 (변경 2)

- EEPROM 에 보내면 알아서 다시 binary 의 나열로 저장 (변경 3)


값의 format 변경은, decimal --> binary --> decimal --> binary 로 되겠죠.


왜이리 복잡하게 하는가를 생각해 보면,

EEPROM 의 address 는 직접 접근 가능하지만, 각 address 의 bit 값은 따로따로 접근을 못하며,

거기에 각 bit 가 기능적으로 정의되어 있어서, "저장된 값 = 실존하는 값" 공식이 성립하지 않기 때문 입니다.



그럼 실제 source code 에서 10의 자리와 1의 자리의 연산식이 이떻게 이루어 지는지를 확인해 보죠.

초 단위의 식은 아래와 같습니다.


seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal


상위 bit 부분 - 10의 자리 수 - "((seconds & B11110000)>>4)*10" 부분만을 띄어서 보면 다음과 같은 항목이 됩니다.

왼쪽부터,

- EEPROM 에서 읽어온 값 (decimal) 인 seconds

- seconds 값을 AND 연산 하면서 자동으로 binary 로 변환

- 4 bit 를 왼쪽으로 shift (날림) 를 통해 10의 자리만을 건짐

- 10 을 곱하면서 decimal 로 변경


위의 과정의 경과를 살펴보면 아래와 같겠죠.


| seconds | (BIN)seconds | seconds & B11110000 | (BIN)(seconds & B11110000) | (seconds & B11110000)>>4 | *10 |


0 | 0 | 0 | 0 | 0 | 0
1 | 1 | 0 | 0 | 0 | 0
2 | 10 | 0 | 0 | 0 | 0
3 | 11 | 0 | 0 | 0 | 0
4 | 100 | 0 | 0 | 0 | 0
5 | 101 | 0 | 0 | 0 | 0
6 | 110 | 0 | 0 | 0 | 0
7 | 111 | 0 | 0 | 0 | 0
8 | 1000 | 0 | 0 | 0 | 0
9 | 1001 | 0 | 0 | 0 | 0
16 | 10000 | 16 | 10000 | 1 | 10
17 | 10001 | 16 | 10000 | 1 | 10
18 | 10010 | 16 | 10000 | 1 | 10
19 | 10011 | 16 | 10000 | 1 | 10
21 | 10101 | 16 | 10000 | 1 | 10
22 | 10110 | 16 | 10000 | 1 | 10
23 | 10111 | 16 | 10000 | 1 | 10
24 | 11000 | 16 | 10000 | 1 | 10
25 | 11001 | 16 | 10000 | 1 | 10
32 | 100000 | 32 | 100000 | 2 | 20
33 | 100001 | 32 | 100000 | 2 | 20
34 | 100010 | 32 | 100000 | 2 | 20
35 | 100011 | 32 | 100000 | 2 | 20
36 | 100100 | 32 | 100000 | 2 | 20
37 | 100101 | 32 | 100000 | 2 | 20
38 | 100110 | 32 | 100000 | 2 | 20
39 | 100111 | 32 | 100000 | 2 | 20
40 | 101000 | 32 | 100000 | 2 | 20
41 | 101001 | 32 | 100000 | 2 | 20
48 | 110000 | 48 | 110000 | 3 | 30
49 | 110001 | 48 | 110000 | 3 | 30
50 | 110010 | 48 | 110000 | 3 | 30
51 | 110011 | 48 | 110000 | 3 | 30
52 | 110100 | 48 | 110000 | 3 | 30
53 | 110101 | 48 | 110000 | 3 | 30
54 | 110110 | 48 | 110000 | 3 | 30
55 | 110111 | 48 | 110000 | 3 | 30
56 | 111000 | 48 | 110000 | 3 | 30
57 | 111001 | 48 | 110000 | 3 | 30
64 | 1000000 | 64 | 1000000 | 4 | 40
65 | 1000001 | 64 | 1000000 | 4 | 40
66 | 1000010 | 64 | 1000000 | 4 | 40
67 | 1000011 | 64 | 1000000 | 4 | 40
68 | 1000100 | 64 | 1000000 | 4 | 40
69 | 1000101 | 64 | 1000000 | 4 | 40
71 | 1000111 | 64 | 1000000 | 4 | 40
72 | 1001000 | 64 | 1000000 | 4 | 40
73 | 1001001 | 64 | 1000000 | 4 | 40
80 | 1010000 | 80 | 1010000 | 5 | 50
81 | 1010001 | 80 | 1010000 | 5 | 50
82 | 1010010 | 80 | 1010000 | 5 | 50
83 | 1010011 | 80 | 1010000 | 5 | 50
84 | 1010100 | 80 | 1010000 | 5 | 50
85 | 1010101 | 80 | 1010000 | 5 | 50
86 | 1010110 | 80 | 1010000 | 5 | 50
87 | 1010111 | 80 | 1010000 | 5 | 50
88 | 1011000 | 80 | 1010000 | 5 | 50
89 | 1011001 | 80 | 1010000 | 5 | 50


결과적으로 10 --> 20 --> 30 --> 40 --> 50 순으로 값을 변경한다는 것을 알 수 있습니다.

보고 있으면 오묘하죠? 숫자놀이의 향연이라고 할 수 있겠습니다.






5. 알람 설정


아래는 EEPROM 에 저장된 기능별 주소록 입니다.


- datasheet : DS3231.pdf



저 위의 표를 염두에 두면서 DS3231.h 파일을 열어보면 알람의 설정방법에 대해 기술되어 있습니다.


/* Retrieves everything you could want to know about alarm
 * one. 
 * A1Dy true makes the alarm go on A1Day = Day of Week,
 * A1Dy false makes the alarm go on A1Day = Date of month.
 *
 * byte AlarmBits sets the behavior of the alarms:
 *	Dy	A1M4	A1M3	A1M2	A1M1	Rate
 *	X	1		1		1		1		Once per second
 *	X	1		1		1		0		Alarm when seconds match
 *	X	1		1		0		0		Alarm when min, sec match
 *	X	1		0		0		0		Alarm when hour, min, sec match
 *	0	0		0		0		0		Alarm when date, h, m, s match
 *	1	0		0		0		0		Alarm when DoW, h, m, s match
 *
 *	Dy	A2M4	A2M3	A2M2	Rate
 *	X	1		1		1		Once per minute (at seconds = 00)
 *	X	1		1		0		Alarm when minutes match
 *	X	1		0		0		Alarm when hours and minutes match
 *	0	0		0		0		Alarm when date, hour, min match
 *	1	0		0		0		Alarm when DoW, hour, min match
 */


자세히 보면, Alarm 1 이 초단위까지 자세하게 설정할 수 있으며,

Alarm 2 는 최소단위가 분단위임을 알 수 있습니다.

이 차이는 Alarm 1에 5개의 address 가 할당되어 있고, Alarm 2 에 4개의 address 가 할당되어 있는게 그 차이 입니다.


"DS3231.h" 에는 setA1Time, setA2TimeturnOnAlarm() 펑션이 있습니다.


File > Examples > DS3231 > DS3231_set 의 일부분이 다음과 같습니다.


		// Test of alarm functions
		// set A1 to one minute past the time we just set the clock
		// on current day of week.
		Clock.setA1Time(DoW, Hour, Minute+1, Second, 0x0, true, false, false);
		// set A2 to two minutes past, on current day of month.
		Clock.setA2Time(Date, Hour, Minute+2, 0x0, false, false, false);
		// Turn on both alarms, with external interrupt
		Clock.turnOnAlarm(1);
		Clock.turnOnAlarm(2);


이걸 이용하여 알람을 설정할 수 있습니다.

위의 source 는 특정 시간 설정 후, 1분과 2분 경과 후에 알람이 뜨도록 되어 있네요.


알람 확인은 기본으로 제공되는 다음 소스에서 확인할 수 있습니다.


File > Examples > DS3231 > DS3231_test


print 부분의 소스가 좀 부실해서 아주 쬐끔 수정 후, 표시된 내용이 아래 입니다.


2018 11 19 2 15 56 58 24h T=24.25 O+
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

2018 11 19 2 15 56 59 24h T=24.25 O+
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

2018 11 19 2 15 57 0 24h T=24.25 O+ A1!
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

2018 11 19 2 15 57 2 24h T=24.25 O+
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

......

2018 11 19 2 15 57 59 24h T=24.25 O+
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

2018 11 19 2 15 58 0 24h T=24.25 O+ A2!
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0

2018 11 19 2 15 58 1 24h T=24.25 O+
Alarm 1: 2 DoW 15 57 0 enabled
Alarm_1 bits: 0
Alarm 2: 19 Date 15 58 enabled
Alarm_2 bits: 0


시간 설정 1분 후와 2분 후에 A1 과 A2 알람이 표시된 것을 확인할 수 있습니다.


알람 설정은, 시간 설정/표시 보다 훨씬 복잡합니다.

BCD 및 EEPROM 값에 대한 완벽한 이해가 있어야 하더군요.


소스를 새로 짤 수 있으나 너무 힘을 들이는 듯 해서,

지금까지 본것 중에 최고의 source 를 만들어 놓은 분의 website를 대신해서 기록해 봅니다.


* Arduino real time clock with alarm and temperature monitor using DS3231

https://simple-circuit.com/arduino-ds3231-real-time-clock-alarm-temperature/


이걸 만든 양반은 실제 상용으로 사용해도 될만큼 시간설정 및 알람 설정이 가능하도록 만들었습니다.

EEPROM 의 각 값들이 어떻게 사용되는지에 대한 완벽한 code 가 포함되어 있습니다.


또한 편한 library 를 사용하지 않고, 오로지 Wire.h 라이브러리만을 이용하여 직접 모든 것을 컨트롤 했습니다.


/* Arduino real time clock and calendar with 2 alarm functions and temperature monitor using DS3231
   Read DS3231 RTC datasheet to understand the code
   Time & date parameters can be set using two push buttons connected to pins 9 (B1) & 10 (B2).
   Alarm1 and alarm2 can be set using two push buttons connected to 11 (B3) & 10 (B2).
   Pin 12 becomes high when alarm occurred and button B2 returns it to low and
   turns the occurred alarm OFF.
   DS3231 interrupt pin is connected to Arduino external interrupt pin 2.
*/

// include LCD library code
#include "LiquidCrystal.h"
// include Wire library code (needed for I2C protocol devices)
#include "Wire.h"

// LCD module connections (RS, E, D4, D5, D6, D7)
LiquidCrystal lcd(3, 4, 5, 6, 7, 8);

const int button1   =  9;                   // button1 pin number
const int button2   = 10;                   // button1 pin number
const int button3   = 11;                   // button1 pin number
const int alarm_pin = 12;                   // Alarms pin number

void setup() {
  pinMode(9,  INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, OUTPUT);
  digitalWrite(alarm_pin, LOW);
  // set up the LCD's number of columns and rows
  lcd.begin(20, 4);
  Wire.begin();                                 // Join i2c bus
  attachInterrupt(digitalPinToInterrupt(2), Alarm, FALLING);
}

// Variables declaration
bool alarm1_status, alarm2_status;
char Time[]     = "  :  :  ",
     calendar[] = "      /  /20  ",
     alarm1[]   = "A1:   :  :00", alarm2[]   = "A2:   :  :00",
     temperature[] = "T:   .   C";
byte  i, second, minute, hour, day, date, month, year,
      alarm1_minute, alarm1_hour, alarm2_minute, alarm2_hour,
      status_reg;

void Alarm(){
  digitalWrite(alarm_pin, HIGH);
}
void DS3231_read(){                             // Function to read time & calendar data
  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address
  Wire.write(0);                                // Send register address
  Wire.endTransmission(false);                  // I2C restart
  Wire.requestFrom(0x68, 7);                    // Request 7 bytes from DS3231 and release I2C bus at end of reading
  second = Wire.read();                         // Read seconds from register 0
  minute = Wire.read();                         // Read minuts from register 1
  hour   = Wire.read();                         // Read hour from register 2
  day    = Wire.read();                         // Read day from register 3
  date   = Wire.read();                         // Read date from register 4
  month  = Wire.read();                         // Read month from register 5
  year   = Wire.read();                         // Read year from register 6
}
void alarms_read_display(){                     // Function to read and display alarm1, alarm2 and temperature data
  byte control_reg, temperature_lsb;
  char temperature_msb;
  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address
  Wire.write(0x08);                             // Send register address
  Wire.endTransmission(false);                  // I2C restart
  Wire.requestFrom(0x68, 11);                   // Request 11 bytes from DS3231 and release I2C bus at end of reading
  alarm1_minute = Wire.read();                  // Read alarm1 minutes
  alarm1_hour   = Wire.read();                  // Read alarm1 hours
  Wire.read();                                  // Skip alarm1 day/date register
  alarm2_minute = Wire.read();                  // Read alarm2 minutes
  alarm2_hour   = Wire.read();                  // Read alarm2 hours
  Wire.read();                                  // Skip alarm2 day/date register
  control_reg = Wire.read();                    // Read the DS3231 control register
  status_reg  = Wire.read();                    // Read the DS3231 status register
  Wire.read();                                  // Skip aging offset register
  temperature_msb = Wire.read();                // Read temperature MSB
  temperature_lsb = Wire.read();                // Read temperature LSB
    // Convert BCD to decimal
  alarm1_minute = (alarm1_minute >> 4) * 10 + (alarm1_minute & 0x0F);
  alarm1_hour   = (alarm1_hour   >> 4) * 10 + (alarm1_hour & 0x0F);
  alarm2_minute = (alarm2_minute >> 4) * 10 + (alarm2_minute & 0x0F);
  alarm2_hour   = (alarm2_hour   >> 4) * 10 + (alarm2_hour & 0x0F);
    // End conversion
  alarm1[8]     = alarm1_minute % 10  + 48;
  alarm1[7]     = alarm1_minute / 10  + 48;
  alarm1[5]     = alarm1_hour   % 10  + 48;
  alarm1[4]     = alarm1_hour   / 10  + 48;
  alarm2[8]     = alarm2_minute % 10  + 48;
  alarm2[7]     = alarm2_minute / 10  + 48;
  alarm2[5]     = alarm2_hour   % 10  + 48;
  alarm2[4]     = alarm2_hour   / 10  + 48;
  alarm1_status = bitRead(control_reg, 0);      // Read alarm1 interrupt enable bit (A1IE) from DS3231 control register
  alarm2_status = bitRead(control_reg, 1);      // Read alarm2 interrupt enable bit (A2IE) from DS3231 control register
  if(temperature_msb < 0){
    temperature_msb = abs(temperature_msb);
    temperature[2] = '-';
  }
  else
    temperature[2] = ' ';
  temperature_lsb >>= 6;
  temperature[4] = temperature_msb % 10  + 48;
  temperature[3] = temperature_msb / 10  + 48;
  if(temperature_lsb == 0 || temperature_lsb == 2){
    temperature[7] = '0';
    if(temperature_lsb == 0) temperature[6] = '0';
    else                     temperature[6] = '5';
  }
  if(temperature_lsb == 1 || temperature_lsb == 3){
    temperature[7] = '5';
    if(temperature_lsb == 1) temperature[6] = '2';
    else                     temperature[6] = '7';
  }
  temperature[8]  = 223;                        // Put the degree symbol
  lcd.setCursor(10, 0);
  lcd.print(temperature);                       // Display temperature
  lcd.setCursor(0, 2);
  lcd.print(alarm1);                            // Display alarm1
  lcd.setCursor(17, 2);
  if(alarm1_status)  lcd.print("ON ");          // If A1IE = 1 print 'ON'
  else               lcd.print("OFF");          // If A1IE = 0 print 'OFF'
  lcd.setCursor(0, 3);
  lcd.print(alarm2);                            // Display alarm2
  lcd.setCursor(17, 3);
  if(alarm2_status)  lcd.print("ON ");          // If A2IE = 1 print 'ON'
  else               lcd.print("OFF");          // If A2IE = 0 print 'OFF'
}
void calendar_display(){                        // Function to display calendar
  switch(day){
    case 1:  strcpy(calendar, "Sun   /  /20  "); break;
    case 2:  strcpy(calendar, "Mon   /  /20  "); break;
    case 3:  strcpy(calendar, "Tue   /  /20  "); break;
    case 4:  strcpy(calendar, "Wed   /  /20  "); break;
    case 5:  strcpy(calendar, "Thu   /  /20  "); break;
    case 6:  strcpy(calendar, "Fri   /  /20  "); break;
    case 7:  strcpy(calendar, "Sat   /  /20  "); break;
    default: strcpy(calendar, "Sat   /  /20  ");
  }
  calendar[13] = year  % 10 + 48;
  calendar[12] = year  / 10 + 48;
  calendar[8]  = month % 10 + 48;
  calendar[7]  = month / 10 + 48;
  calendar[5]  = date  % 10 + 48;
  calendar[4]  = date  / 10 + 48;
  lcd.setCursor(0, 1);
  lcd.print(calendar);                          // Display calendar
}
void DS3231_display(){
  // Convert BCD to decimal
  second = (second >> 4) * 10 + (second & 0x0F);
  minute = (minute >> 4) * 10 + (minute & 0x0F);
  hour = (hour >> 4) * 10 + (hour & 0x0F);
  date = (date >> 4) * 10 + (date & 0x0F);
  month = (month >> 4) * 10 + (month & 0x0F);
  year = (year >> 4) * 10 + (year & 0x0F);
  // End conversion
  Time[7]     = second % 10  + 48;
  Time[6]     = second / 10  + 48;
  Time[4]     = minute % 10  + 48;
  Time[3]     = minute / 10  + 48;
  Time[1]     = hour   % 10  + 48;
  Time[0]     = hour   / 10  + 48;
  calendar_display();                           // Call calendar display function
  lcd.setCursor(0, 0);
  lcd.print(Time);                              // Display time
}
void Blink(){
  byte j = 0;
  while(j < 10 && (digitalRead(button1) || i >= 5) && digitalRead(button2) && (digitalRead(button3) || i < 5)){
    j++;
    delay(25);
  }
}
byte edit(byte x, byte y, byte parameter){
  char text[3];
  while(!digitalRead(button1) || !digitalRead(button3));    // Wait until button B1 is released
  while(true){
    while(!digitalRead(button2)){                           // If button B2 is pressed
      parameter++;
      if(((i == 0) || (i == 5)) && parameter > 23)          // If hours > 23 ==> hours = 0
        parameter = 0;
      if(((i == 1) || (i == 6)) && parameter > 59)          // If minutes > 59 ==> minutes = 0
        parameter = 0;
      if(i == 2 && parameter > 31)                          // If date > 31 ==> date = 1
        parameter = 1;
      if(i == 3 && parameter > 12)                          // If month > 12 ==> month = 1
        parameter = 1;
      if(i == 4 && parameter > 99)                          // If year > 99 ==> year = 0
        parameter = 0;
      if(i == 7 && parameter > 1)                           // For alarms ON or OFF (1: alarm ON, 0: alarm OFF)
        parameter = 0;
      lcd.setCursor(x, y);
      if(i == 7){                                           // For alarms ON & OFF
        if(parameter == 1)  lcd.print("ON ");
        else                lcd.print("OFF");
      }
      else{
        sprintf(text,"%02u", parameter);
        lcd.print(text);
      }
      if(i >= 5){
        DS3231_read();                          // Read data from DS3231
        DS3231_display();                       // Display DS3231 time and calendar
      }
      delay(200);                               // Wait 200ms
    }
    lcd.setCursor(x, y);
    lcd.print("  ");                            // Print two spaces
    if(i == 7) lcd.print(" ");                  // Print space (for alarms ON & OFF)
    Blink();                                    // Call Blink function
    lcd.setCursor(x, y);
    if(i == 7){                                 // For alarms ON & OFF
      if(parameter == 1)  lcd.print("ON ");
      else                lcd.print("OFF");
    }
    else{
      sprintf(text,"%02u", parameter);
      lcd.print(text);
    }
    Blink();
    if(i >= 5){
      DS3231_read();
      DS3231_display();}
    if((!digitalRead(button1) && i < 5) || (!digitalRead(button3) && i >= 5)){
      i++;                                      // Increment 'i' for the next parameter
      return parameter;                         // Return parameter value and exit
    }
  }
}

void loop() {
  if(!digitalRead(button1)){                    // If B1 button is pressed
      i = 0;
      hour   = edit(0, 0, hour);
      minute = edit(3, 0, minute);
      while(!digitalRead(button1));             // Wait until button B1 released
      while(true){
        while(!digitalRead(button2)){           // If button B2 button is pressed
          day++;                                // Increment day
          if(day > 7) day = 1;
          calendar_display();                   // Call display_calendar function
          lcd.setCursor(0, 1);
          lcd.print(calendar);                  // Display calendar
          delay(200);
        }
        lcd.setCursor(0, 1);
        lcd.print("   ");                       // Print 3 spaces
        Blink();
        lcd.setCursor(0, 1);
        lcd.print(calendar);                    // Print calendar
        Blink();                                // Call Blink function
        if(!digitalRead(button1))               // If button B1 is pressed
          break;
      }
      date = edit(4, 1, date);                  // Edit date
      month = edit(7, 1, month);                // Edit month
      year = edit(12, 1, year);                 // Edit year
      // Convert decimal to BCD
      minute = ((minute / 10) << 4) + (minute % 10);
      hour = ((hour / 10) << 4) + (hour % 10);
      date = ((date / 10) << 4) + (date % 10);
      month = ((month / 10) << 4) + (month % 10);
      year = ((year / 10) << 4) + (year % 10);
      // End conversion
      // Write time & calendar data to DS3231 RTC
      Wire.beginTransmission(0x68);             // Start I2C protocol with DS3231 address
      Wire.write(0);                            // Send register address
      Wire.write(0);                            // Reset sesonds and start oscillator
      Wire.write(minute);                       // Write minute
      Wire.write(hour);                         // Write hour
      Wire.write(day);                          // Write day
      Wire.write(date);                         // Write date
      Wire.write(month);                        // Write month
      Wire.write(year);                         // Write year
      Wire.endTransmission();                   // Stop transmission and release the I2C bus
      delay(200);
    }
    if(!digitalRead(button3)){                  // If B3 button is pressed
      while(!digitalRead(button3));             // Wait until button B3 released
      i = 5;
      alarm1_hour   = edit(4,  2, alarm1_hour);
      alarm1_minute = edit(7,  2, alarm1_minute);
      alarm1_status = edit(17, 2, alarm1_status);
      i = 5;
      alarm2_hour   = edit(4,  3, alarm2_hour);
      alarm2_minute = edit(7,  3, alarm2_minute);
      alarm2_status = edit(17, 3, alarm2_status);
      alarm1_minute = ((alarm1_minute / 10) << 4) + (alarm1_minute % 10);
      alarm1_hour   = ((alarm1_hour   / 10) << 4) + (alarm1_hour % 10);
      alarm2_minute = ((alarm2_minute / 10) << 4) + (alarm2_minute % 10);
      alarm2_hour   = ((alarm2_hour   / 10) << 4) + (alarm2_hour % 10);
      // Write alarms data to DS3231
      Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address
      Wire.write(7);                              // Send register address (alarm1 seconds)
      Wire.write(0);                              // Write 0 to alarm1 seconds
      Wire.write(alarm1_minute);                  // Write alarm1 minutes value to DS3231
      Wire.write(alarm1_hour);                    // Write alarm1 hours value to DS3231
      Wire.write(0x80);                           // Alarm1 when hours, minutes, and seconds match
      Wire.write(alarm2_minute);                  // Write alarm2 minutes value to DS3231
      Wire.write(alarm2_hour);                    // Write alarm2 hours value to DS3231
      Wire.write(0x80);                           // Alarm2 when hours and minutes match
      Wire.write(4 | alarm1_status | (alarm2_status << 1));      // Write data to DS3231 control register (enable interrupt when alarm)
      Wire.write(0);                              // Clear alarm flag bits
      Wire.endTransmission();                     // Stop transmission and release the I2C bus
      delay(200);                                 // Wait 200ms
    }
    if(!digitalRead(button2) && digitalRead(alarm_pin)){         // When button B2 pressed with alarm (Reset and turn OFF the alarm)
      digitalWrite(alarm_pin, LOW);               // Turn OFF the alarm indicator
      Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address
      Wire.write(0x0E);                           // Send register address (control register)
      // Write data to control register (Turn OFF the occurred alarm and keep the other as it is)
      Wire.write(4 | (!bitRead(status_reg, 0) & alarm1_status) | ((!bitRead(status_reg, 1) & alarm2_status) << 1));
      Wire.write(0);                              // Clear alarm flag bits
      Wire.endTransmission();                     // Stop transmission and release the I2C bus
    }
    DS3231_read();                                // Read time and calendar parameters from DS3231 RTC
    alarms_read_display();                        // Read and display alarms parameters
    DS3231_display();                             // Display time & calendar
    delay(50);                                    // Wait 50ms
}
// End of code


이렇게 긴 source 는 붙여넣기 하면 이쁘지 않지만, 너무 잘 짠 코드라 여기에 남깁니다.





6. Square Wave - 정현파 만들기


RTC에 왠 정현파 재조기 인가 하는데, 시간은 정확한 oscillator 를 사용하므로,

square wave 를 정확하게 만들 수 있는 기능이 있습니다.


만들 수 있는 주파수는 다음과 같습니다.

- 1.024kHz

- 4.096kHz

- 8.192kHz

- 32.768kHz


source 는 Example 에 올라와 있는 내용을 그대로 사용해 봤습니다.


File > Examples > DS3231 > DS3231_oscillator_test


		// Oscillator functions

		void enableOscillator(bool TF, bool battery, byte frequency); 
			// turns oscillator on or off. True is on, false is off.
			// if battery is true, turns on even for battery-only operation,
			// otherwise turns off if Vcc is off.
			// frequency must be 0, 1, 2, or 3.
			// 0 = 1 Hz
			// 1 = 1.024 kHz
			// 2 = 4.096 kHz
			// 3 = 8.192 kHz (Default if frequency byte is out of range);
		void enable32kHz(bool TF); 
			// Turns the 32kHz output pin on (true); or off (false).
		bool oscillatorCheck();;
			// Checks the status of the OSF (Oscillator Stop Flag);.
			// If this returns false, then the clock is probably not
			// giving you the correct time.
			// The OSF is cleared by function setSecond();.


마침 DSO150 이라는 DIY oscilloscope 를 만들어 놓은 것이 있네요.


* Hardware | DSO150 Oscilloscope

http://chocoball.tistory.com/entry/HardwareDSO150Oscilloscope


SQW 에 DSO150 을 연결해서 확인해 봅니다.

신기하게도 정현파가 잘 뜨네요.



단, 32kHz 는 높은 주파수라서 그런지 32K 단자에서 따로 검출할 수 있습니다.






7. 시간 표시를 2 digit 로 바꾸기


시간, 분, 초의 표시가 10자리 밑이면 한자리 값으로만 나옵니다.

6초면 "06"이 아니라 "6" 으로 표시되면서 자리 위치가 자꾸 바뀌는게 마음에 들지 않습니다.



값을 EEPROM 에서 리턴 받으면 10 이하일 경우 "0" 을 삽입해 주는 코드를 추가하여 아래와 같이 수정했습니다.


#include "SPI.h"
#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
 
Adafruit_SSD1306 display = Adafruit_SSD1306();
  
#define DS3231_I2C_ADDRESS 104
 
// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.
 
byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];
 
byte tMSB, tLSB;
float temp3231;

String T_seconds, T_minutes, T_hours, D_date, D_month, D_year;
 
void setup() {
  Serial.begin(9600);
   
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.clearDisplay();
  display.display();
  delay(1000);
 
  Wire.begin();
}
 
void loop() {
  watchConsole();
   
  get3231Date();
   
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.setTextSize(1);

  if (seconds < 10) { T_seconds = "0" + String(seconds, DEC);} else {T_seconds = String(seconds, DEC);}
  if (minutes < 10) { T_minutes = "0" + String(minutes, DEC);} else {T_minutes = String(minutes, DEC);}
  if (hours < 10) { T_hours = "0" + String(hours, DEC);} else {T_hours = String(hours, DEC);}
  if (date < 10) { D_date = "0" + String(date, DEC);} else {D_date = String(date, DEC);}
  if (month < 10) { D_month = "0" + String(month, DEC);} else {D_month = String(month, DEC);}
  if (year < 10) { D_year = "0" + String(year, DEC);} else {D_year = String(year, DEC);}
  
  display.print("DATE : "); display.print(weekDay); display.print(", "); display.print(D_date); display.print("/"); display.print(D_month); display.print("/"); display.println(D_year);
  display.print("TIME : "); display.print(T_hours); display.print(":"); display.print(T_minutes); display.print(":"); display.println(T_seconds);
  display.print("TEMP : "); display.println(get3231Temp());
  display.display();
   
  delay(1000);
}
 
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val) {
  return ( (val/10*16) + (val%10) );
}
 
void watchConsole() {
   
  if (Serial.available()) { // Look for char in serial queue and process if found
    if (Serial.read() == 84) { //If command = "T" Set Date
      set3231Date();
      get3231Date();
      Serial.println(" ");
    }
  }
}
  
void set3231Date() {
  //T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
  //T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
  //Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
  // T1124154091014
  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.
  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  day     = (byte) (Serial.read() - 48); // set day of week (1=Sunday, 7=Saturday)
  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
   
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x00);
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.write(decToBcd(day));
  Wire.write(decToBcd(date));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.endTransmission();
}
 
void get3231Date() {
  // send request to receive data starting at register 0
  Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
  Wire.write(0x00); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes
   
  if(Wire.available()) {
    seconds = Wire.read(); // get seconds
    minutes = Wire.read(); // get minutes
    hours   = Wire.read(); // get hours
    day     = Wire.read();
    date    = Wire.read();
    month   = Wire.read(); //temp month
    year    = Wire.read();

    seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
    minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
    hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
    day     = (day & B00000111); // 1-7
    date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
    month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
    year    = (((year & B11110000)>>4)*10 + (year & B00001111));
  } else {
    //oh noes, no data!
  }
   
  switch (day) {
    case 1:
      strcpy(weekDay, "Sun");
      break;
    case 2:
      strcpy(weekDay, "Mon");
      break;
    case 3:
      strcpy(weekDay, "Tue");
      break;
    case 4:
      strcpy(weekDay, "Wed");
      break;
    case 5:
      strcpy(weekDay, "Thu");
      break;
    case 6:
      strcpy(weekDay, "Fri");
      break;
    case 7:
      strcpy(weekDay, "Sat");
      break;
  }
}
 
float get3231Temp() {
  //temp registers (11h-12h) get updated automatically every 64s
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x11);
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
   
  if(Wire.available()) {
    tMSB = Wire.read(); //2's complement int portion
    tLSB = Wire.read(); //fraction portion
     
    temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
    temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
  } else {
    //oh noes, no data!
  }
  return temp3231;
}


10보다 작은 수가 오면 자동으로 "0" 을 붙여주게 되었습니다.



요일 값을 year / month / date 값을 이용하여 자동으로 연산하여 넣어줄 수 있도록 할 수도 있습니다만,

너무 복잡해 지므로 관련해서 코드를 짠 분의 site 를 링크해 놓습니다.


* Day of the Week calculator

https://www.hackster.io/erikuchama/day-of-the-week-calculator-cde704






8. DS3231 내부 온도 센서와 BME280 센서와 비교


예전에 온도 전용 센서인 BME280 을 이용하여 측정해 본 경험이 있습니다.


* Hardware | BME280 sensor

http://chocoball.tistory.com/entry/HardwareBME280


DS3231 내부 온도센서의 정확성 비교를 위해 하룻저녁 두개를 같이 측정해 봤습니다.



꽤나 근접하네요.


DS3231 의 결과값에 일괄적으로 +0.25 를 했더니만 이제 좀 비슷해 진것 같습니다.

그 결과 그래프가 아래 그림입니다. 최종적으로는 +0.3 정도가 가장 적당해 보이네요.



DS3231 내부 온도센서의 정확성을 위해,

추출한 값을 100 곱한 다음, 마지막에 100 으로 나누는 방식을 채용하여 측정하였습니다.


* How to read DS3231 Internal temperature sensor, example code

http://forum.arduino.cc/index.php?topic=262986.15


아래 참고한 사이트를 보면 DS3231 을 가지고 온갖 할 수 있는 일을 다하는 사람의 글 입니다.


* Using a $1 DS3231 Real-time Clock Module with Arduino

https://thecavepearlproject.org/2014/05/21/using-a-cheap-3-ds3231-rtc-at24c32-eeprom-from-ebay/


최종 비교를 위해 사용된 sketch 는 다음과 같습니다.


#include "Wire.h"
#include "SPI.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

Adafruit_BME280 BME;
   
const int DS3231_RTC_ADDR = 0x68;
const int DS3231_TEMP_MSB = 0x11;
union int16_byte {
  int i;
  byte b[2];
} rtcTemp;

void setup() {
  Wire.begin();
  Serial.begin(9600);
  BME.begin();
}

void loop() {
   Wire.beginTransmission(DS3231_RTC_ADDR);
   Wire.write(DS3231_TEMP_MSB);
   Wire.endTransmission();
   Wire.requestFrom(DS3231_RTC_ADDR, 2);
   rtcTemp.b[1] = Wire.read(); 
   rtcTemp.b[0] = Wire.read();
   long tempC100 = (rtcTemp.i >> 6) * 25;    //degrees celsius times 100
   Serial.print( tempC100 / 100 );
   Serial.print( '.' );
   Serial.print( abs(tempC100 % 100) );
   
   Serial.print("\t");
   Serial.println(BME.readTemperature());

   delay(1000);
}





FIN


이번에 DS3231 을 가지고 놀면서 대학때 배운 BCD 도 다시 해보고,

EEPROM 에 대한 address 방식 등에 대해서도 배울 수 있어서 좋았습니다....

만, 배울게 너무 많아서 힘들었습니다.


당연히 지금껏 사용해본 sensor 중에는 활용도와 배울 점으로는 단연 top 입니다.

관련해서 전문가들도 온갖 기술을 구현해 놨고... 정말 변태같은 sensor 인듯 합니다.

(datasheet 를 보면 뭔가 더 많은데, 여기서 그만 하려구요.)


And

Hardware | RTC DS3231 부품 사용기 - 1

|

이 포스트는 DS3231 에 대한 이야기 이며, 후속편에 이어집니다.


* Hardware | RTC DS3231 부품 사용기 - 2

http://chocoball.tistory.com/entry/Hardware-RTC-usning-DS3231-2





1. RTC


보통 internet 이 달린 기기라면 NTP Server 와 연동하여 시간을 맞추고,

특정 시간에 정확하게 일을 시킬 수가 있습니다.


Internet 에 연결되지 않은 기기의 경우는 다음과 같은 과정이 필요하겠죠.


A) 시간을 설정한다.

B) 시간을 기억한다.

C) 특정 시간에 일을 시킨다.


Internet 이 연결되지 않은 기기와 비교해 보면 B) 항목이 필요합니다.

이 "시간을 기억" 하고, 언제든지 현재 시간 정보를 가져올 수 있는 부품이 DS3231 입니다.


그래서 이번에는 DS3231 을 구입하고 사용해 보겠습니다.

자세한 내용은 아래 Arduino 사이트를 참조해 보세요.


* RTC Library

https://www.arduino.cc/en/Reference/RTC





2. 구입


말할것도 없이 AliExpress 입니다.

배송까지 40일 걸렸습니다. 이정도 되면 배송에 대해서는 해탈해야 합니다.


* 1PCS DS3231 AT24C32 IIC Precision RTC Real Time Clock Memory Module For Arduino new original

https://www.aliexpress.com/item/1PCS-DS3231-AT24C32-IIC-Precision-RTC-Real-Time-Clock-Memory-Module-For-Arduino-new-original/32830730519.html



건전지 미포함 1.06 USD 무료배송이면 고민거리는 아닙니다.

(배송기간 빼고)


인터넷을 뒤지니, 위의 부품으로 거의 통일되어 있는것 같았습니다.





3. 도착


요로코롬 도착했습니다.




부품 확대 사진입니다.

메인 chip 에 DS321 이라고 적혀 있네요.

발진기인 오실레이터도 보이고, 밑에는 AT24C32 EEPROM (32Kb) 도 있습니다.

참고로 DS3231 칩 안에는 추가로 온도센서도 존재합니다.


- Data sheet : DS3232.pdf




특이하게 "왜 온도센서?" 냐 하면,

전자 발진기 - 오실레이터 - 는 온도에 따라 그 값이 변합니다.

그래서 온도에 따른 변화를 보정하기 위해 온도센서가 자리잡고 있는 것이지요.



뒷면에는 CR2032 버튼 전지를 끼울 수 있는 플라스틱이 존재합니다.

이는 전원이 차단되더라도 "시간을 기억" 하기고 있기 위한 것이지요.


이 건전지 한개로 몇년은 쓴다고 하네요.





4. Layout


배선은 일반 IIC 배선과 동일합니다.

SLA 은 A4에, SCL은 A5 이죠.


   DS3231 | Arduino Nano
-------------------------
    VCC   |     3.3V
    GND   |     GND
    SDC   |     A5
    SDA   |     A4
-------------------------


  SSD1306 | Arduino Nano
-------------------------
    VCC   |     3.3V
    GND   |     GND
    SDC   |     A5
    SDA   |     A4
-------------------------


실재 배선 모양입니다.



I2C의 특성상, 다른 센서 / 부품들 중, I2C 방식이면 arduino 의 동일한 pin 에 꼽아도 따로 인식 됩니다.

이는 각 device 가 가지는 address 가 다르기 때문이지요.


이는 I2C Scanner 를 이용해서 살펴보면, 각각 따로 인식하는 것을 알 수 있습니다.

아래에서 0x3c 는 OLED 이고, 0x68 이 DS3231 입니다.


추가로 나오는 0x57 은 AT24C32 EEPROM 입니다.



여기서 이상한 점은 0x5f 라는 address 입니다.

무얼까... 답을 찾지는 못했지만, 찾는 와중에 한가지 새로운 사실을 알게 됩니다.





5. EEPROM


EEPROM 으로는 ATmega 사의 AT24C32 이 쓰입니다.

이것의 실제 chip 번호는 ATML332 라고 적혀 있습니다.


24C32-Datasheet.pdf



그런데 제가 가지고 있는 DS3231 의 EEPROM 부분은 다음과 같습니다.



https://www.kynix.com/Detail/447536/ATMLH745.html


그렇습니다. EEPROM 이 original 이 아니고 fake 제품인 것이죠.


그러나 제품 구동은 정상적으로 돌아갑니다.

100% original chip 과 동일하지 않기 때문에 보다 복잡한 작업을 시키면 정상적으로 동작하지 않을 지도 모릅니다.


여튼, 앞으로 싼 부품은 좀 걸러야 할지도 모르겠네요.





6. Library 등록


인터넷에 돌아다니는 source 를 등록해서 사용할 수 있지만,

IDE 에서 지원해주는 Library 등록 기능을 이용하여 Example source 를 등록해 봅니다.


우선 IDE 메뉴에서 "Sketch > Add File... > Manage Libraries..." 를 선택합니다.

이건 이제 매번 써먹는 방법이지요?



Libarry Manager 의 검색창에서 "ds3231" 을 쳐서 검색합니다.

그러면 여러가지 source 들이 나오는데, 왠만하면 제일 위에 나오는 것을 선택하면 됩니다.

아래 그림처럼 adafruit 에서 만든 library 이니, 쓸만 할껍니다.



이렇게 하면 IDE 메뉴의 "File > Examples > DS3231" 항목이 생기고 sample source 를 이용할 수 있습니다.






6. sketch - 시간 설정


인터넷에 돌아다니는 source 를 등록해서 사용할 수 있지만,

위에서처럼 Arduino IDE 의 Library Manager 를 통해서 얻은 소스를 활용해 봅니다.


일단 시간을 입력합니다.

소스에 보이듯이 Serial Monitor 에 "YYMMDDwHHMMSS" 를 넣고, 마지막에 "x" 를 붙이면 설정됩니다.


------------------------------------------------

YYMMDDwHHMMSS, with an 'x' at the end

------------------------------------------------


/*
Sets the time and prints back time stamps for 5 seconds

Based on DS3231_set.pde
by Eric Ayars
4/11

Added printing back of time stamps and increased baud rate
(to better synchronize computer and RTC)
Andy Wickert
5/15/2011
*/

#include "DS3231.h"
#include "Wire.h"

DS3231 Clock;

byte Year;
byte Month;
byte Date;
byte DoW;
byte Hour;
byte Minute;
byte Second;

bool Century=false;
bool h12;
bool PM;

void GetDateStuff(byte& Year, byte& Month, byte& Day, byte& DoW, 
		byte& Hour, byte& Minute, byte& Second) {
	// Call this if you notice something coming in on 
	// the serial port. The stuff coming in should be in 
	// the order YYMMDDwHHMMSS, with an 'x' at the end.
	boolean GotString = false;
	char InChar;
	byte Temp1, Temp2;
	char InString[20];

	byte j=0;
	while (!GotString) {
		if (Serial.available()) {
			InChar = Serial.read();
			InString[j] = InChar;
			j += 1;
			if (InChar == 'x') {
				GotString = true;
			}
		}
	}
	Serial.println(InString);
	// Read Year first
	Temp1 = (byte)InString[0] -48;
	Temp2 = (byte)InString[1] -48;
	Year = Temp1*10 + Temp2;
	// now month
	Temp1 = (byte)InString[2] -48;
	Temp2 = (byte)InString[3] -48;
	Month = Temp1*10 + Temp2;
	// now date
	Temp1 = (byte)InString[4] -48;
	Temp2 = (byte)InString[5] -48;
	Day = Temp1*10 + Temp2;
	// now Day of Week
	DoW = (byte)InString[6] - 48;		
	// now Hour
	Temp1 = (byte)InString[7] -48;
	Temp2 = (byte)InString[8] -48;
	Hour = Temp1*10 + Temp2;
	// now Minute
	Temp1 = (byte)InString[9] -48;
	Temp2 = (byte)InString[10] -48;
	Minute = Temp1*10 + Temp2;
	// now Second
	Temp1 = (byte)InString[11] -48;
	Temp2 = (byte)InString[12] -48;
	Second = Temp1*10 + Temp2;
}

void setup() {
	// Start the serial port
	Serial.begin(57600);

	// Start the I2C interface
	Wire.begin();
}

void loop() {

	// If something is coming in on the serial line, it's
	// a time correction so set the clock accordingly.
	if (Serial.available()) {
		GetDateStuff(Year, Month, Date, DoW, Hour, Minute, Second);

		Clock.setClockMode(false);	// set to 24h
		//setClockMode(true);	// set to 12h

		Clock.setYear(Year);
		Clock.setMonth(Month);
		Clock.setDate(Date);
		Clock.setDoW(DoW);
		Clock.setHour(Hour);
		Clock.setMinute(Minute);
		Clock.setSecond(Second);
		
		// Give time at next five seconds
		for (int i=0; i<5; i++){
		    delay(1000);
		    Serial.print(Clock.getYear(), DEC);
		    Serial.print("-");
		    Serial.print(Clock.getMonth(Century), DEC);
		    Serial.print("-");
		    Serial.print(Clock.getDate(), DEC);
		    Serial.print(" ");
		    Serial.print(Clock.getHour(h12, PM), DEC); //24-hr
		    Serial.print(":");
		    Serial.print(Clock.getMinute(), DEC);
		    Serial.print(":");
		    Serial.println(Clock.getSecond(), DEC);
		}

	}
	delay(1000);
}


시각을 입력하니 잘 등록되고 읽어집니다.






7. sketch - 시간 설정 + 시간 가져오기 + 온도


기본 sample 을 사용해도 되지만, 찾아다니면서 하나로 된 소스는 아래인것 같습니다.

Serial Monitor 의 입력창에 "T1124154091014" 등으로 입력하면 시각이 입력되면서,

그냥 와두면, "시간 + 온도" 를 표시해 준다.


* Tutorial – Using DS1307 and DS3231 Real-time Clock Modules with Arduino

https://tronixstuff.com/2014/12/01/tutorial-using-ds1307-and-ds3231-real-time-clock-modules-with-arduino/


#include "Wire.h"

#define DS3231_I2C_ADDRESS 104

// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.

byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];

byte tMSB, tLSB;
float temp3231;

void setup() {
	Wire.begin();
	Serial.begin(9600);
}

void loop() {
	watchConsole();
	
	get3231Date();
	
	Serial.print(weekDay); Serial.print(", "); Serial.print(date, DEC); Serial.print("/"); Serial.print(month, DEC); Serial.print("/"); Serial.print(year, DEC); Serial.print(" - ");
	Serial.print(hours, DEC); Serial.print(":"); Serial.print(minutes, DEC); Serial.print(":"); Serial.print(seconds, DEC);
	
	Serial.print(" - Temp: "); Serial.println(get3231Temp());
	
	delay(1000);
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val) {
	return ( (val/10*16) + (val%10) );
}

void watchConsole() {
	
	if (Serial.available()) {      // Look for char in serial queue and process if found
		if (Serial.read() == 84) {      //If command = "T" Set Date
			set3231Date();
			get3231Date();
			Serial.println(" ");
		}
	}
}
 
void set3231Date() {
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
// T1124154091014
	seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.
	minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
	hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
	day     = (byte) (Serial.read() - 48);
	date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
	month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
	year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
	Wire.beginTransmission(DS3231_I2C_ADDRESS);
	Wire.write(0x00);
	Wire.write(decToBcd(seconds));
	Wire.write(decToBcd(minutes));
	Wire.write(decToBcd(hours));
	Wire.write(decToBcd(day));
	Wire.write(decToBcd(date));
	Wire.write(decToBcd(month));
	Wire.write(decToBcd(year));
	Wire.endTransmission();
}

void get3231Date() {
	// send request to receive data starting at register 0
	Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
	Wire.write(0x00); // start at register 0
	Wire.endTransmission();
	Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes
	
	if(Wire.available()) {
		seconds = Wire.read(); // get seconds
		minutes = Wire.read(); // get minutes
		hours   = Wire.read();   // get hours
		day     = Wire.read();
		date    = Wire.read();
		month   = Wire.read(); //temp month
		year    = Wire.read();
		
		seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
		minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
		hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
		day     = (day & B00000111); // 1-7
		date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
		month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
		year    = (((year & B11110000)>>4)*10 + (year & B00001111));
	} else {
		//oh noes, no data!
	}
	
	switch (day) {
		case 1:
			strcpy(weekDay, "Sun");
			break;
		case 2:
			strcpy(weekDay, "Mon");
			break;
		case 3:
			strcpy(weekDay, "Tue");
			break;
		case 4:
			strcpy(weekDay, "Wed");
			break;
		case 5:
			strcpy(weekDay, "Thu");
			break;
		case 6:
			strcpy(weekDay, "Fri");
			break;
		case 7:
			strcpy(weekDay, "Sat");
			break;
	}
}

float get3231Temp() {
	//temp registers (11h-12h) get updated automatically every 64s
	Wire.beginTransmission(DS3231_I2C_ADDRESS);
	Wire.write(0x11);
	Wire.endTransmission();
	Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
	
		if(Wire.available()) {
			tMSB = Wire.read(); //2's complement int portion
			tLSB = Wire.read(); //fraction portion
			
			temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
			temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
		} else {
			//oh noes, no data!
		}
	return temp3231;
}


Serial Monitor 의 결과 입니다.






8. sketch - 시간 설정 + 시간 가져오기 + 온도 + OLED


위의 소스를 조금 바꾸어 OLED 에 표시해주는 소스로 살짝 바꾸었습니다.

일주일 지난 뒤, 측정하니 여전히 잘 동작하고 있네요.



살짝 바꾼 소스 올려 봅니다.


#include "SPI.h"
#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"

Adafruit_SSD1306 display = Adafruit_SSD1306();
 
#define DS3231_I2C_ADDRESS 104

// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.

byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];

byte tMSB, tLSB;
float temp3231;

void setup() {
  Serial.begin(9600);
  
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.clearDisplay();
  display.display();
  delay(1000);

  Wire.begin();
}

void loop() {
  watchConsole();
  
  get3231Date();
  
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.setTextSize(1);
  display.print("DATE : "); display.print(weekDay); display.print(", "); display.print(date, DEC); display.print("/"); display.print(month, DEC); display.print("/"); display.println(year, DEC);
  display.print("TIME : "); display.print(hours, DEC); display.print(":"); display.print(minutes, DEC); display.print(":"); display.println(seconds, DEC);
  display.print("TEMP : "); display.println(get3231Temp());
  display.display();
  
  delay(1000);
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val) {
  return ( (val/10*16) + (val%10) );
}

void watchConsole() {
  
  if (Serial.available()) { // Look for char in serial queue and process if found
    if (Serial.read() == 84) { //If command = "T" Set Date
      set3231Date();
      get3231Date();
      Serial.println(" ");
    }
  }
}
 
void set3231Date() {
  //T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
  //T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
  //Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
  // T1124154091014
  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.
  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  day     = (byte) (Serial.read() - 48);
  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x00);
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.write(decToBcd(day));
  Wire.write(decToBcd(date));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.endTransmission();
}

void get3231Date() {
  // send request to receive data starting at register 0
  Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
  Wire.write(0x00); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes
  
  if(Wire.available()) {
    seconds = Wire.read(); // get seconds
    minutes = Wire.read(); // get minutes
    hours   = Wire.read(); // get hours
    day     = Wire.read();
    date    = Wire.read();
    month   = Wire.read(); //temp month
    year    = Wire.read();
    
    seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
    minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
    hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
    day     = (day & B00000111); // 1-7
    date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
    month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
    year    = (((year & B11110000)>>4)*10 + (year & B00001111));
  } else {
    //oh noes, no data!
  }
  
  switch (day) {
    case 1:
      strcpy(weekDay, "Sun");
      break;
    case 2:
      strcpy(weekDay, "Mon");
      break;
    case 3:
      strcpy(weekDay, "Tue");
      break;
    case 4:
      strcpy(weekDay, "Wed");
      break;
    case 5:
      strcpy(weekDay, "Thu");
      break;
    case 6:
      strcpy(weekDay, "Fri");
      break;
    case 7:
      strcpy(weekDay, "Sat");
      break;
  }
}

float get3231Temp() {
  //temp registers (11h-12h) get updated automatically every 64s
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x11);
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
  
  if(Wire.available()) {
    tMSB = Wire.read(); //2's complement int portion
    tLSB = Wire.read(); //fraction portion
    
    temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
    temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
  } else {
    //oh noes, no data!
  }
  return temp3231;
}


동영상도 올려 봅니다.






FIN


DS3231 에 대해서는 이야기 할 내용이 더 있어서 2편에서 더 다루어 보겠습니다.


And
prev | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ··· | 11 | next