#include "StepperISR.h" #if defined(HAVE_ESP32C3_RMT) && (ESP_IDF_VERSION_MAJOR == 4) // #define TEST_MODE // #define TRACE #include "fas_arch/test_probe.h" // The following concept is in use: // // The rmt buffer is split into two parts. // Each part will hold one command (or part of). // After the two parts an end marker is placed. // // Of these 32 bits, the low 16-bit entry is sent first and the high entry // second. // Every 16 bit entry defines with MSB the output level and the lower 15 bits // the ticks. // // Important difference of esp32c3 (compared to esp32): // - configuration updates need an conf_update strobe // (apparently the manual is not correct by mentioning to set conf_update // first) // - if the end marker is hit in continuous mode, there is no end interrupt // - there is no tick lost with the end marker // - minimum periods as per relation 1 and 2 to be adhered to // // #ifdef HAVE_ESP32C3_RMT #define PART_SIZE 23 #define RMT_MEM_SIZE 48 #else #error #define PART_SIZE 31 #define RMT_MEM_SIZE 64 #endif // In order to avoid threshold/end interrupt on end, add one #define enable_rmt_interrupts(channel) \ { \ RMT.tx_lim[channel].RMT_LIMIT = PART_SIZE + 2; \ RMT.tx_conf[channel].conf_update = 1; \ RMT.tx_conf[channel].conf_update = 0; \ RMT.int_clr.val |= 0x101 << channel; \ RMT.int_ena.val |= 0x101 << channel; \ } #define disable_rmt_interrupts(channel) \ { RMT.int_ena.val &= ~(0x101 << channel); } void IRAM_ATTR StepperQueue::stop_rmt(bool both) { // We are stopping the rmt by letting it run into the end at high speed. // // disable the interrupts // rmt_set_tx_intr_en(channel, false); // rmt_set_tx_thr_intr_en(channel, false, 0); // stop esp32 rmt, by let it hit the end RMT.tx_conf[channel].tx_conti_mode = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // replace second part of buffer with pauses uint32_t *data = FAS_RMT_MEM(channel); uint8_t start = both ? 0 : PART_SIZE; data = &data[start]; for (uint8_t i = start; i < 2 * PART_SIZE; i++) { // two pauses à n ticks to achieve MIN_CMD_TICKS *data++ = 0x00010001 * ((MIN_CMD_TICKS + 61) / 62); } *data = 0; // as the rmt is not running anymore, mark it as stopped _rmtStopped = true; } static void IRAM_ATTR apply_command(StepperQueue *q, bool fill_part_one, uint32_t *data) { if (!fill_part_one) { data += PART_SIZE; } uint8_t rp = q->read_idx; if (rp == q->next_write_idx) { // no command in queue if (fill_part_one) { q->bufferContainsSteps[0] = false; for (uint8_t i = 0; i < PART_SIZE; i++) { // make a pause with approx. 1ms // 258 ticks * 2 * 31 = 15996 @ 16MHz // 347 ticks * 2 * 23 = 15962 @ 16MHz *data++ = 0x010001 * (16000 / 2 / PART_SIZE); } } else { q->stop_rmt(false); } return; } // Process command struct queue_entry *e_curr = &q->entry[rp & QUEUE_LEN_MASK]; if (e_curr->toggle_dir) { // the command requests dir pin toggle // This is ok only, if the ongoing command does not contain steps if (q->bufferContainsSteps[fill_part_one ? 1 : 0]) { // So we need a pause. change the finished read entry into a pause q->bufferContainsSteps[fill_part_one ? 0 : 1] = false; for (uint8_t i = 0; i < PART_SIZE; i++) { // two pauses à n ticks to achieve MIN_CMD_TICKS *data++ = 0x00010001 * ((MIN_CMD_TICKS + 2 * PART_SIZE - 1) / (2 * PART_SIZE)); } return; } // The ongoing command does not contain steps, so change dir here should be // ok LL_TOGGLE_PIN(q->dirPin); // and delete the request e_curr->toggle_dir = 0; } uint8_t steps = e_curr->steps; uint16_t ticks = e_curr->ticks; // if (steps != 0) { // PROBE_1_TOGGLE; //} uint32_t last_entry; if (steps == 0) { q->bufferContainsSteps[fill_part_one ? 0 : 1] = false; // Perhaps the rmt performs look ahead ticks -= (PART_SIZE - 2) * 4 + 8; uint16_t ticks_l = ticks >> 1; uint16_t ticks_r = ticks - ticks_l; last_entry = ticks_l; last_entry <<= 16; last_entry |= ticks_r; *data++ = last_entry; for (uint8_t i = 1; i < PART_SIZE - 1; i++) { *data++ = 0x00020002; } last_entry = 0x00040004; } else { q->bufferContainsSteps[fill_part_one ? 0 : 1] = true; if (ticks == 0xffff) { // special treatment for this case, because an rmt entry can only cover up // to 0xfffe ticks every step must be minimum split into two rmt entries, // so at max PART/2 steps can be done. if (steps < PART_SIZE / 2) { for (uint8_t i = 1; i < steps; i++) { // steps-1 iterations *data++ = 0x40007fff | 0x8000; *data++ = 0x20002000; } *data++ = 0x40007fff | 0x8000; uint16_t delta = PART_SIZE - 2 * steps; delta <<= 5; *data++ = 0x20002000 - delta; // 2*(steps - 1) + 1 already stored => 2*steps - 1 // and after this for loop one entry added => 2*steps for (uint8_t i = 2 * steps; i < PART_SIZE - 1; i++) { *data++ = 0x00100010; } last_entry = 0x00100010; steps = 0; } else { steps -= PART_SIZE / 2; for (uint8_t i = 0; i < PART_SIZE / 2 - 1; i++) { *data++ = 0x40007fff | 0x8000; *data++ = 0x20002000; } *data++ = 0x40007fff | 0x8000; last_entry = 0x20002000; } } else if ((steps < 2 * PART_SIZE) && (steps != PART_SIZE)) { uint8_t steps_to_do = steps; if (steps > PART_SIZE) { steps_to_do /= 2; } uint16_t ticks_l = ticks >> 1; uint16_t ticks_r = ticks - ticks_l; uint32_t rmt_entry = ticks_l; rmt_entry <<= 16; rmt_entry |= ticks_r | 0x8000; // with step for (uint8_t i = 1; i < steps_to_do; i++) { *data++ = rmt_entry; } uint32_t delta = (PART_SIZE - steps_to_do) * 4 + 8; delta <<= 16; // shift in upper 16bit *data++ = rmt_entry - delta; for (uint8_t i = steps_to_do; i < PART_SIZE - 1; i++) { *data++ = 0x00020002; } last_entry = 0x00040004; steps -= steps_to_do; } else { // either >= 2*PART_SIZE or = PART_SIZE // every entry one step uint16_t ticks_l = ticks >> 1; uint16_t ticks_r = ticks - ticks_l; uint32_t rmt_entry = ticks_l; rmt_entry <<= 16; rmt_entry |= ticks_r | 0x8000; // with step for (uint8_t i = 0; i < PART_SIZE - 1; i++) { *data++ = rmt_entry; } last_entry = rmt_entry; steps -= PART_SIZE; } } // No tick lost mentioned for esp32c3 // if (!fill_part_one) { // Note: When enabling the continuous transmission mode by setting // RMT_REG_TX_CONTI_MODE, the transmitter will transmit the data on the // channel continuously, that is, from the first byte to the last one, // then from the first to the last again, and so on. In this mode, there // will be an idle level lasting one clk_div cycle between N and N+1 // transmissions. // last_entry -= 1; // } *data = last_entry; // Data is complete if (steps == 0) { // The command has been completed if (e_curr->repeat_entry == 0) { q->read_idx = rp + 1; } } else { e_curr->steps = steps; } } #if !defined(RMT_CHANNEL_MEM) && !defined(HAVE_ESP32C3_RMT) #define RMT_LIMIT tx_lim #define RMT_FIFO apb_fifo_mask #else #define RMT_LIMIT limit #define RMT_FIFO fifo_mask #endif // The threshold interrupts are happening in the "middle" of the previous entry. // Best strategy: // 1. enable threshold interrupt with PART_SIZE+2 // 2. On every threshold interrupt toggle PART_SIZE and PART_SIZE+1 // This way the first interrupt happens on the first entry of second half, // and the second interrupt on the first entry of the first half. // Afterwards alternating. This way the end interrupt is always "half buffer // away" from the threshold interrupt #ifdef TEST_MODE #define PROCESS_CHANNEL(ch) \ if (mask & RMT_CH##ch##_TX_END_INT_ST) { \ PROBE_1_TOGGLE; \ } \ if (mask & RMT_CH##ch##_TX_THR_EVENT_INT_ST) { \ uint8_t old_limit = RMT.tx_lim[ch].RMT_LIMIT; \ if (old_limit == PART_SIZE + 1) { \ /* second half of buffer sent */ \ PROBE_2_TOGGLE; \ /* demonstrate modification of RAM */ \ uint32_t *mem = FAS_RMT_MEM(ch); \ mem[PART_SIZE] = 0x33ff33ff; \ RMT.tx_lim[ch].RMT_LIMIT = PART_SIZE; \ } else { \ /* first half of buffer sent */ \ PROBE_3_TOGGLE; \ RMT.tx_lim[ch].RMT_LIMIT = PART_SIZE + 1; \ } \ RMT.tx_conf[ch].conf_update = 1; \ RMT.tx_conf[ch].conf_update = 0; \ } #else #define PROCESS_CHANNEL(ch) \ if (mask & RMT_CH##ch##_TX_END_INT_ST) { \ StepperQueue *q = &fas_queue[QUEUES_MCPWM_PCNT + ch]; \ disable_rmt_interrupts(q->channel); \ q->_isRunning = false; \ PROBE_1_TOGGLE; \ PROBE_2_TOGGLE; \ PROBE_3_TOGGLE; \ } \ if (mask & RMT_CH##ch##_TX_THR_EVENT_INT_ST) { \ uint8_t old_limit = RMT.tx_lim[ch].RMT_LIMIT; \ StepperQueue *q = &fas_queue[QUEUES_MCPWM_PCNT + ch]; \ uint32_t *mem = FAS_RMT_MEM(ch); \ if (old_limit == PART_SIZE + 1) { \ /* second half of buffer sent */ \ PROBE_2_TOGGLE; \ apply_command(q, false, mem); \ /* demonstrate modification of RAM */ \ /*mem[PART_SIZE] = 0x33fff3ff; */ \ RMT.tx_lim[ch].RMT_LIMIT = PART_SIZE; \ } else { \ /* first half of buffer sent */ \ PROBE_3_TOGGLE; \ apply_command(q, true, mem); \ RMT.tx_lim[ch].RMT_LIMIT = PART_SIZE + 1; \ } \ RMT.tx_conf[ch].conf_update = 1; \ RMT.tx_conf[ch].conf_update = 0; \ } #endif static void IRAM_ATTR tx_intr_handler(void *arg) { uint32_t mask = RMT.int_st.val; RMT.int_clr.val = mask; PROCESS_CHANNEL(0); PROCESS_CHANNEL(1); #if QUEUES_RMT >= 4 PROCESS_CHANNEL(2); PROCESS_CHANNEL(3); #endif #if QUEUES_RMT == 8 PROCESS_CHANNEL(4); PROCESS_CHANNEL(5); PROCESS_CHANNEL(6); PROCESS_CHANNEL(7); #endif } void StepperQueue::init_rmt(uint8_t channel_num, uint8_t step_pin) { #ifdef TEST_PROBE_1 if (channel_num == 0) { pinMode(TEST_PROBE_1, OUTPUT); PROBE_1(HIGH); delay(10); PROBE_1(LOW); delay(5); PROBE_1(HIGH); delay(5); PROBE_1(LOW); delay(5); } #endif #ifdef TEST_PROBE_2 pinMode(TEST_PROBE_2, OUTPUT); PROBE_2(LOW); #endif #ifdef TEST_PROBE_3 pinMode(TEST_PROBE_3, OUTPUT); PROBE_3(LOW); #endif _initVars(); _step_pin = step_pin; // digitalWrite(step_pin, LOW); // pinMode(step_pin, OUTPUT); channel = (rmt_channel_t)channel_num; if (channel_num == 0) { periph_module_enable(PERIPH_RMT_MODULE); } // APB_CLOCK=80 MHz // CLK_DIV = APB_CLOCK/5 = 16 MHz // // Relation 1 in esp32c3 technical reference: // 3 * T_APB + 5 * T_RMT_CLK < period * T_CLK_DIV // => 8 * T_APB < period * T_APB*5 // => period > 8/5 // => period >= 2 // // Relation 2 in esp32c3 technical reference before end marker: // 6 * T_APB + 12 * T_RMT_CLK < period * T_CLK_DIV // => 18 * T_APB < period * T_APB*5 // => period > 18/5 // => period >= 4 // disable_rmt_interrupts(channel); rmt_set_source_clk(channel, RMT_BASECLK_APB); rmt_set_clk_div(channel, 5); rmt_set_mem_block_num(channel, 1); rmt_set_tx_carrier(channel, false, 0, 0, RMT_CARRIER_LEVEL_LOW); rmt_tx_stop(channel); rmt_tx_memory_reset(channel); // rmt_rx_stop(channel); TX only channel ! // rmt_tx_memory_reset is not defined in arduino V340 and based on test // result not needed rmt_tx_memory_reset(channel); if (channel_num == 0) { rmt_isr_register(tx_intr_handler, NULL, ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM, NULL); RMT.sys_conf.fifo_mask = 1; // disable fifo mode } _isRunning = false; _rmtStopped = true; connect_rmt(); #ifdef TEST_MODE if (channel == 0) { RMT.tx_conf[channel].mem_rd_rst = 1; RMT.tx_conf[channel].mem_rd_rst = 0; uint32_t *mem = FAS_RMT_MEM(channel); // Fill the buffer with a significant pattern for debugging for (uint8_t i = 0; i < PART_SIZE; i++) { *mem++ = 0x7fffffff; } for (uint8_t i = PART_SIZE; i < 2 * PART_SIZE; i++) { *mem++ = 0x3fffdfff; } // without end marker it does not loop *mem++ = 0; *mem++ = 0; // conti mode is accepted with the conf_update 1 strobe RMT.tx_conf[channel].tx_conti_mode = 1; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; RMT.tx_conf[channel].mem_tx_wrap_en = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; enable_rmt_interrupts(channel); // tx_start does not need conf_update PROBE_1_TOGGLE; // end interrupt will toggle again PROBE_1 RMT.tx_conf[channel].tx_start = 1; delay(1000); if (false) { mem--; mem--; // destroy end marker => no end interrupt, no repeat *mem++ = 0x00010001; *mem = 0x00010001; } if (true) { // just clear conti mode => causes end interrupt, no repeat RMT.tx_conf[channel].tx_conti_mode = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; } delay(1000); // actually no need to enable/disable interrupts. // and this seems to avoid some pitfalls // This runs the RMT buffer once RMT.tx_conf[channel].tx_conti_mode = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; RMT.tx_conf[channel].tx_start = 1; while (true) { delay(1000); PROBE_1_TOGGLE; } } #endif } void StepperQueue::connect_rmt() { RMT.tx_conf[channel].idle_out_lv = 0; RMT.tx_conf[channel].idle_out_en = 1; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // RMT.tx_conf[channel].mem_tx_wrap_en = 0; #ifndef __ESP32_IDF_V44__ rmt_set_pin(channel, RMT_MODE_TX, (gpio_num_t)_step_pin); #else rmt_set_gpio(channel, RMT_MODE_TX, (gpio_num_t)_step_pin, false); #endif #ifdef TEST_MODE // here gpio is 0 delay(1); PROBE_3_TOGGLE; RMT.tx_conf[channel].idle_out_lv = 1; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // here gpio is 1 delay(2); PROBE_3_TOGGLE; RMT.tx_conf[channel].idle_out_lv = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // here gpio is 0 delay(2); PROBE_3_TOGGLE; RMT.tx_conf[channel].idle_out_en = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // here gpio is 0 delay(2); PROBE_3_TOGGLE; RMT.tx_conf[channel].idle_out_lv = 1; RMT.tx_conf[channel].idle_out_en = 1; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // here gpio is 1 delay(2); PROBE_3_TOGGLE; RMT.tx_conf[channel].idle_out_lv = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; // here gpio is 0 delay(2); PROBE_3_TOGGLE; #endif } void StepperQueue::disconnect_rmt() { disable_rmt_interrupts(channel); #ifndef __ESP32_IDF_V44__ // rmt_set_pin(channel, RMT_MODE_TX, (gpio_num_t)-1); #else // rmt_set_gpio(channel, RMT_MODE_TX, GPIO_NUM_NC, false); #endif RMT.tx_conf[channel].idle_out_en = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; } void StepperQueue::startQueue_rmt() { #ifdef TRACE USBSerial.println("START"); #endif #ifdef TEST_PROBE_2 PROBE_2(LOW); #endif #ifdef TEST_PROBE_3 PROBE_3(LOW); #endif #if defined(TEST_PROBE_1) && defined(TEST_PROBE_2) && defined(TEST_PROBE_3) delay(1); PROBE_1_TOGGLE; delay(1); PROBE_1_TOGGLE; delay(1); PROBE_1_TOGGLE; delay(1); PROBE_2_TOGGLE; delay(2); PROBE_2_TOGGLE; delay(2); PROBE_3_TOGGLE; delay(3); PROBE_3_TOGGLE; delay(3); #endif rmt_tx_stop(channel); // rmt_rx_stop(channel); RMT.tx_conf[channel].mem_rd_rst = 1; RMT.tx_conf[channel].mem_rd_rst = 0; uint32_t *mem = FAS_RMT_MEM(channel); // #define TRACE #ifdef TRACE // Fill the buffer with a significant pattern for debugging // Keep it for now for (uint8_t i = 0; i < 2 * PART_SIZE; i += 2) { // mem[i] = 0x0fff8fff; // mem[i + 1] = 0x7fff8fff; } #endif // Write end marker mem[2 * PART_SIZE] = 0; mem[2 * PART_SIZE + 1] = 0; _isRunning = true; _rmtStopped = false; disable_rmt_interrupts(channel); RMT.tx_conf[channel].mem_tx_wrap_en = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; #ifdef TRACE USBSerial.print("Queue:"); USBSerial.print(read_idx); USBSerial.print('/'); USBSerial.println(next_write_idx); for (uint8_t i = 0; i < RMT_MEM_SIZE; i++) { USBSerial.print(i); USBSerial.print(' '); USBSerial.println(mem[i], HEX); } #endif // set dirpin toggle here uint8_t rp = read_idx; if (rp == next_write_idx) { // nothing to do ? // Should not happen, so bail return; } if (entry[rp & QUEUE_LEN_MASK].toggle_dir) { LL_TOGGLE_PIN(dirPin); entry[rp & QUEUE_LEN_MASK].toggle_dir = false; } bufferContainsSteps[0] = true; bufferContainsSteps[1] = true; apply_command(this, true, mem); #ifdef TRACE USBSerial.print(RMT.tx_conf[channel].val, BIN); USBSerial.println(' '); USBSerial.print(RMT.tx_conf[channel].mem_tx_wrap_en); USBSerial.println(' '); for (uint8_t i = 0; i < RMT_MEM_SIZE; i++) { USBSerial.print(i); USBSerial.print(' '); USBSerial.println(mem[i], HEX); } if (!isRunning()) { USBSerial.println("STOPPED 1"); } #endif apply_command(this, false, mem); #ifdef TRACE USBSerial.print(RMT.tx_conf[channel].val, BIN); USBSerial.print(' '); USBSerial.print(RMT.tx_conf[channel].mem_tx_wrap_en); USBSerial.println(' '); for (uint8_t i = 0; i < RMT_MEM_SIZE; i++) { USBSerial.print(i); USBSerial.print(' '); USBSerial.println(mem[i], HEX); } #endif enable_rmt_interrupts(channel); #ifdef TRACE USBSerial.print("Interrupt enable:"); USBSerial.println(RMT.int_ena.val, BIN); USBSerial.print("Interrupt status:"); USBSerial.println(RMT.int_st.val, BIN); USBSerial.print("Threshold: 0x"); USBSerial.println(RMT.tx_lim[channel].val, HEX); #endif // This starts the rmt module RMT.tx_conf[channel].tx_conti_mode = 1; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; RMT.tx_conf[channel].mem_tx_wrap_en = 0; RMT.tx_conf[channel].conf_update = 1; RMT.tx_conf[channel].conf_update = 0; PROBE_1_TOGGLE; RMT.tx_conf[channel].tx_start = 1; } void StepperQueue::forceStop_rmt() { stop_rmt(true); // and empty the buffer read_idx = next_write_idx; } bool StepperQueue::isReadyForCommands_rmt() { if (_isRunning) { return !_rmtStopped; } return true; } uint16_t StepperQueue::_getPerformedPulses_rmt() { return 0; } #endif