Serial.write()
를 사용하여 53 바이트를 PC로 전송합니다. 시간 측정을 위해 micros()
(쓰기 기능 전후)를 사용합니다. 각 전송 후 1 초의 지연이 있습니다.
Serial.write()
기능의 시간은 전송 속도 1000000에서 532 us, 9600 전송 속도에서 360 us입니다.
Serial.write()
함수는 9600 보오율로 53 바이트의 전송이 53 * 8 / 9600 * 1e6 = 44167 us이기 때문에 분명히 비동기 적입니다. (단, 전송 속도가 1000000 인 경우 함수가 비동기식이라는 것이 분명하지 않습니다.)
Serial.availableForWrite()
를 Serial.write()
버퍼에 충분한 공간이 있는지 확인합니다 (매번 63을 반환하며 버퍼 크기는 기본 64입니다).
이 숫자를 이해하지 못합니다. 사용 memcpy()
53 바이트를 복사하는 데는 32 us 밖에 걸리지 않습니다. 직렬 버퍼에 복사하는 것은 memcpy()
함수와 동일하지 않습니까? 그리고 그 이유는 전송 속도가 다른 경우 복사 시간에 차이가 있습니까? Serial.write ()는 결과에 따라 전송 속도가 높을수록 더 느립니다. 왜 Serial.availableForWrite()
는 버퍼가 63을 반환하는 동안 크기는 64입니다 (SERIAL_TX_BUFFER_SIZE에 따름)?
업데이트 : 모든 답변에 감사드립니다.
직렬 통신을위한 다른 라이브러리를 시도했습니다 : https://github.com/greiman/SerialPort
원래보다 빠른 것 같습니다. 2 팀을 사용합니다. 이 3 개의 deltaTimes는 다음과 같이 전체 단계를 나타냅니다.
writeTime + memcpyTime + delaymicros = computedTime! = realTime
처음 2 번이 측정됩니다. delaymicros는 이론적 지연 (900 us)입니다. 이로부터 단계 시간을 계산할 수 있습니다. 이 계산 된 단계 시간은 실시간과 다릅니다 (측정 된 단계 시간, 전체 단계도 측정합니다). 추가 시간은 지연에서 찾을 수있는 것 같습니다.
SerialPort 라이브러리 : 100 + 30 + 900 = 1030! = 1350
writeReal = 100 + 1350-1030 = 430
Arduino Serial : 570 + 30 + 900 = 1500! = 1520
writeReal = 570 + 1520-1500 = 590
그런 다음 delaymicros 시간을 측정했습니다. 이론상 900 명), 빠진 시간은 그곳에서 찾을 수 있습니다. 900 us 지연을 프로그래밍했지만 실제 지연은 첫 번째 테스트에서 약 1200, 두 번째 테스트에서 920이었습니다.
이러한 측정은 쓰기 기능 만 측정하지 않기 때문에 인터럽트의 존재를 증명할 수 있습니다. 모든 쓰기 시간을 제공합니다 (특히 다운로드 한 직렬 라이브러리의 경우). 다운로드 된 라이브러리는 더 빠르게 작동 할 수 있지만 오류로 인해 더 큰 tx 버퍼가 필요합니다 (64 대신 256이 올바르게 작동 함).
코드는 다음과 같습니다. : sendig 함수에서 checkSum을 사용합니다 (570-532 = 38 us). Simulink를 사용하여 데이터를 수신하고 처리합니다.
struct sendMsg1 { byte errorCheck;//1 unsigned long dT1;//4 unsigned long dT2;//4 unsigned long t;//4 unsigned long plus[10];//40 };//53 sendMsg1 msg1; byte copyMsg1[53]; unsigned long time1; unsigned long time2; unsigned long time3; unsigned long time4; void setup() { Serial.begin(1000000); } void loop() { sensorRead(); sendMsg1_f(); //time3 = micros(); delayMicroseconds(900); //time4 = micros(); } void sensorRead() { time3 = micros(); msg1.t = micros(); for (unsigned long i = 0; i < 1; i++) { memcpy((void*)&(copyMsg1[i*sizeof(sendMsg1)]), (void*)&msg1, sizeof(sendMsg1)); } time4 = micros(); msg1.dT2 = time4 - time3; } void sendMsg1_f() { time1 = micros(); msg1.errorCheck = 0; for (int i = 0; i < sizeof(sendMsg1) - 1; i++) { msg1.errorCheck += ((byte*)&msg1)[i]; } Serial.write((byte*)&msg1,sizeof(sendMsg1)); time2 = micros(); msg1.dT1 = time2 - time1; }
댓글
- 이러한 결과를 얻는 데 사용한 코드를 제공 할 수 있습니까?
- 링 버퍼 구현으로 인해 버퍼 1 바이트가 손실됩니다. tail 전체 버퍼의 헤드는 동일한 인덱스를 가리킬 수 없습니다. ‘ 테일 헤드는 동일한 인덱스에 있기 때문에 빈 버퍼의 경우
An swer
구현을 살펴 보는 경우 :
size_t HardwareSerial::write(uint8_t c) { _written = true; // If the buffer and the data register is empty, just write the byte // to the data register and be done. This shortcut helps // significantly improve the effective datarate at high (> // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) { // If TXC is cleared before writing UDR and the previous byte // completes before writing to UDR, TXC will be set but a byte // is still being transmitted causing flush() to return too soon. // So writing UDR must happen first. // Writing UDR and clearing TC must be done atomically, otherwise // interrupts might delay the TXC clear so the byte written to UDR // is transmitted (setting TXC) before clearing TXC. Then TXC will // be cleared when no bytes are left, causing flush() to hang ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *_udr = c; #ifdef MPCM0 *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0); #else *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << TXC0))); #endif } return 1; } tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE; // If the output buffer is full, there"s nothing for it other than to // wait for the interrupt handler to empty it a bit while (i == _tx_buffer_tail) { if (bit_is_clear(SREG, SREG_I)) { // Interrupts are disabled, so we"ll have to poll the data // register empty flag ourselves. If it is set, pretend an // interrupt has happened and call the handler to free up // space for us. if(bit_is_set(*_ucsra, UDRE0)) _tx_udr_empty_irq(); } else { // nop, the interrupt handler will free up space for us } } _tx_buffer[_tx_buffer_head] = c; // make atomic to prevent execution of ISR between setting the // head pointer and setting the interrupt flag resulting in buffer // retransmission ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { _tx_buffer_head = i; sbi(*_ucsrb, UDRIE0); } return 1; }
기본적으로 모든 질문에 답합니다.
버퍼가 비어 있고 “아무것도 보내지 않는 경우”문자를 직접 보내므로 동기식입니다. (하지만 문자를 보내는 것이 함수 콜백의 오버 헤드보다 훨씬 빠르기 때문에)
아니요, 버퍼를 교체하기 때문에 memcpy를 사용할 수 없지만 실제로 데이터 레지스터를 시작하는 것과 같이 다른 작업은 수행하지 않습니다. 인터럽트 또는 헤드 / 테일 카운터의 적절한 설정 (라운드 버퍼이므로 버퍼 외부에 덮어 쓰는 시나리오가있을 수 있음)
그리고 쓰기 기능은 각 문자에 대해 개별적으로 호출됩니다 (다른 모든 전문화 of write are using this one)
또한 버퍼가 가득 차면 “다른 문자를위한 공간이 생길 때까지 기다립니다.
Answer
직렬 버퍼로 복사하는 것은
, 아니요.
직렬 버퍼는 순환 버퍼 입니다. 그 때문에 다음 문자를 배치하기 위해 버퍼의 정확한 위치를 계산하는 데 많은 계산이 필요합니다. 시간이 걸립니다. memcpy()
는 한 블록의 메모리를 다른 블록 위에 직접 복사합니다. 검사를하지 않고 순환 버퍼처럼 감쌀 수 없습니다.
높은 전송 속도가 느려 보이는 이유는 각 문자가 인터럽트에 의해 버퍼에서 가져 오기 때문입니다.보오율이 높을수록 인터럽트가 더 자주 트리거되므로 CPU가 해당 인터럽트를 처리하여 다음 문자를 전송하는 데 더 많은 시간을 소비합니다. 마찬가지로 CPU가 직렬 버퍼에 데이터를 배치하는 데 사용할 수있는 시간이 짧습니다.
답변
테스트 한 기능은 아마도 Serial.write(const uint8_t *buffer, size_t size)
일 것입니다. HardwareSerial.h에서 검색하면
using Print::write; // pull in write(str) and write(buf, size) from Print
와 구현은 Print.cpp에 있습니다.
/* default implementation: may be overridden */ size_t Print::write(const uint8_t *buffer, size_t size) { size_t n = 0; while (size--) { if (write(*buffer++)) n++; else break; } return n; }
이것은 바이트 1을 작성합니다. 하나씩, 모든 바이트에 대해 HardwareSerial::write(uint8_t c)
의 전체 코드가 실행됩니다. 여기에는 가득 찬 또는 비어있는 버퍼에 대한 테스트와 _tx_buffer_head
업데이트를위한 산술이 포함됩니다.
코드 위의 주석에서 언급했듯이, 가능했을 것입니다. 특수한 구현으로 이것을 재정의하십시오. 원칙적으로 memcpy()
에 대한 최대 두 번의 호출을 사용하고 _tx_buffer_head
를 한 번만 업데이트하여 선형 버퍼를 링 버퍼에 복사 할 수 있습니다. 적어도 사용중인 범위의 버퍼 크기 (링 버퍼 용량에 가깝지만 그보다 작음)의 경우 현재 구현보다 더 효율적일 수 있습니다.
그만한 가치가 있습니까? 아마도 당신의 유스 케이스에 그럴 것입니다. 그러나 코드를 더 복잡하게 만들고 더 많은 플래시가 필요할 수도 있습니다. 그리고 작은 버퍼를 작성하는 사람들에게는 이점이 정말 적을 것입니다. 이러한 종류의 최적화를 구현하는 풀 요청이 수락 될 수 있는지 확실하지 않습니다. 그렇게 생각하면 시도해 볼 수 있습니다.