Main Thread: https://esp32.com/viewtopic.php?f=13&t=15213
I cannot find the problem, so I will post my complete assembly interrupt service routine as well as a minimal main.c file. Before posting the codes, I want to explain the setup/idea.
My aim is to communicate with an external ADC (24-Bit resolution, SPI) via an ESP32. For SPI communication I am using simple bit banging method. The ADC got four channels and outputs the samples of all four channels in one bit stream, means: the first 24 bits are channel 1, the second 24 bits are channel 2 etc... To get all all channels there are 96 (24*4) serial's clocks (SCLK) needed. Making the data transfer of the samples from ESP32 to PC easy, I decided to "serialize" the samples in a ADCBuffer:
Code: Select all
ADCBUFFER[0]=Sample1_Channel1
ADCBUFFER[1]=Sample1_Channel2
ADCBUFFER[2]=Sample1_Channel3
ADCBUFFER[3]=Sample1_Channel4
ADCBUFFER[4]=Sample2_Channel1
ADCBUFFER[5]=Sample2_Channel2
ADCBUFFER[6]=Sample2_Channel3
ADCBUFFER[7]=Sample2_Channel4
.
.
.
.
ADCBUFFER[N]=SampleN_Channel4
with N=NumberOfSamplesPerChannel*4
Code: Select all
iADCBUFFER_OFFSET = SampleCounter*nCh;
for(iCh=0; iCh<nCh; iCh++){
for(iBit=23; iBit>=0; iBit--){
GPIO.out_w1tc = (1 << SPI_SCLK_PIN);
GPIO.out_w1ts = (1 << SPI_SCLK_PIN);
ADCBIT = (GPIO.in >> SPI_DOUT_PIN) & 0x1;
Sample = Sample | (ADCBIT<<iBit)
ADCBUFFER[iCh + iADCBUFFER_OFFSET]= Sample;
Sample=0;
}
SampleCounter++;
if(SampleCounter >= nSamplesPerCh){
GPIO.out_w1ts = (1 << SPI_SCLK_PIN);
SampleCounter=0;
}
In the following I will post the minimal main.c and the assembly high level interrupt routine. Especially in the assembler file I tried to explain by comments as much as possible to make understanding easier...
What is working?:
- the high level interrupt is raising and filling the data, but the transferred data (done by tcp connection on core0) contains error. For test purposes I am filling in the assembler high level interrupt routine the sample to be stored on the buffers with a constant value, in case of 24 Bit ADC I am overwriting the sample with 0xFFFFFF. One would now expect that the data received via tcp should be a constant value of 0xFFFFFF but this is not the case....so this means the error is not due to SPI communication. Most of the received data contains the expected value 0xFFFFFF but also it became another value (error)...and this happens randomly. I just edit my code in way that I can switch by a "#define..." between "High Level Interrupt" Version and "Normal Level Interrupt" Version (actually the only thing what is changing is the allocation of the interrupt ) and with "Normal Level Interrupt" the received data looks fine. Maybe the error is due to "wrong" order of memory access ? I am really not sure about that and I hope than you or another user can help me.
Now the codes:
mimimal version of main.c
Code: Select all
#define CORE_ID_HIGH_LEVEL_INT_HANDLER 1
#define CORE_ID_ADC_BUFFER_SEND 0
#define nSamplesPerCh 256
#define nCh 4
#define ADCBUFFER_SIZE 4*nSamplesPerCh*nCh
volatile int ADCBUFFER_0[nSamplesPerCh*nCh];
volatile int ADCBUFFER_1[nSamplesPerCh*nCh];
volatile int *Bufs[2];
volatile int BufferSelector = 0;
volatile int SampleCounter = 0;
volatile int prevBufferSelector = 0;
/* the adcbuffer send task is running on core 0 */
void task_send_adcbuffer(void * pvParameters){
while(1){
if(BufferSelector!=prevBufferSelector){
// BufferSelector is right shifted by 2, because in high-level interrupt handler in assembly
// is switching the BufferSelector between 0 and 4 (byte addressing!)
// bit ight shifting by 2 bits makes from 0 -> 0 and from 4 -> 1, so this is fine then for C
int ret = write(sock, Bufs[(prevBufferSelector>>2)],ADCBUFFER_SIZE );
prevBufferSelector = (BufferSelector>>2);
}
vTaskDelay(1);
}
}
void task_config_high_level_interrupt(void * arg){
gpio_config_t interrupt_pin={
.intr_type=GPIO_INTR_NEGEDGE,
.pin_bit_mask=(1ULL<<GPIO_NUM_35),
.mode=GPIO_MODE_INPUT,
.pull_up_en=GPIO_PULLUP_DISABLE,
.pull_down_en=GPIO_PULLDOWN_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&interrupt_pin));
ESP_INTR_DISABLE(31);
intr_matrix_set(CORE_ID_HIGH_LEVEL_INT_HANDLER, ETS_GPIO_INTR_SOURCE, 31);
ESP_INTR_ENABLE(31);
while(1){
vTaskDelay(1);
}
}
void app_main(){
//.....
Bufs[0]=ADCBUFFER_0;
Bufs[1]=ADCBUFFER_1;
//.....
while(1){
vTaskDelay(1);
}
}
Code: Select all
#include <xtensa/coreasm.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#include "freertos/xtensa_context.h"
#include "esp_panic.h"
#include "sdkconfig.h"
#include "soc/soc.h"
#include "soc/gpio_reg.h"
#include "soc/dport_reg.h"
#define SPI_SCLK_PIN_BITMASK (1<<14) // GPIO14 for SPI SCLK
#define DRDY_INT_PIN_BITMASK (1<<3) // GPIO35 as interrupt pin
#define SPI_DOUT_PIN 12 // GPIO12 for SPI DATA (note: needed for extui later, so bitmask just value)
#define nSamplesPerCh 1024 // Numbers of Samples per Channel to aqcuire
#define iCh a4 // reserve a4 as loop counter for the "Channel"-Loop
#define iBit a5 // reserve a5 as loop counter for the "24-Bit-Banging"-Loop
#define aSampleCounter a6 // reserve a6 as memory address of volatile variable "SampleCounter" also used in main.c
#define vSample a7 // reserve a7 for the sample value which is constructed by Sample = Sample | (1<<iBit)
#define aADCBUF a8 // reserve a8 as memory address of the ADC Buffers
#define aVar a9 // reserve a9 as "free" variable for addresses
#define vVar a10 // reserve a10 as "free" variable for values
#define temp a11 // reserve a11 as temp variable
#define aGPIO_IN_REG a12 // reserve a12 as memory address of GPIO_IN_REG (used for faster programm flow)
#define vSPI_SCLK_PIN_BITMASK a13 // reserve a13 as value of GPIO14 BitMask (used for faster programm flow)
#define aGPIO_OUT_W1TS_REG a14 // reserve a14 as memory address of GPIO_OUT_W1TS_REG (used for faster programm flow)
#define aGPIO_OUT_W1TC_REG a15 // reserve a15 as memory address of GPIO_OUT_W1TC_REG (used for faster programm flow)
#define L5_INTR_STACK_SIZE 48 // size of the level 5 interrupt stack for saving/restoring of all used Address Registers
#define L5_INTR_iCh_OFFSET 0 // a4 will be kept at stack address 0
#define L5_INTR_iBit_OFFSET 4 // a5 will be kept at stack address 4
#define L5_INTR_aSampleCounter_OFFSET 8 // a6 will be kept at stack address 8
#define L5_INTR_vSample_OFFSET 12 // ...
#define L5_INTR_aADCBUF_OFFSET 16 // ...
#define L5_INTR_aVar_OFFSET 20 // ...
#define L5_INTR_vVar_OFFSET 24 // ...
#define L5_INTR_temp_OFFSET 28 // ...
#define L5_INTR_aGPIO_IN_REG_OFFSET 32 // ...
#define L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET 36 // ...
#define L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET 40 // ...
#define L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET 44 // a15 will be kept at stack address 44
.data
_l5_intr_stack:
.space L5_INTR_STACK_SIZE // allocate the desired stack of size 48 bytes for saving/restoring of all used Address Registers
.section .iram1,"ax"
.global xt_highint5
.type xt_highint5,@function
.align 4
xt_highint5:
// save all used address registers, in this case: Address Registers (AR): a4 to a15
movi a0, _l5_intr_stack // put the memory address (=begin of the stack) to AR a0
s32i iCh, a0, L5_INTR_iCh_OFFSET // store the content of a4 (=iCh) to L5 stack with offset 0
s32i iBit, a0, L5_INTR_iBit_OFFSET // store the content of a5 (=iBit) to L5 stack with offset 4
s32i aSampleCounter, a0, L5_INTR_aSampleCounter_OFFSET // ...
s32i vSample, a0, L5_INTR_vSample_OFFSET // ...
s32i aADCBUF, a0, L5_INTR_aADCBUF_OFFSET // ...
s32i aVar, a0, L5_INTR_aVar_OFFSET // ...
s32i vVar, a0, L5_INTR_vVar_OFFSET // ...
s32i temp, a0, L5_INTR_temp_OFFSET // ...
s32i aGPIO_IN_REG, a0, L5_INTR_aGPIO_IN_REG_OFFSET // ...
s32i vSPI_SCLK_PIN_BITMASK, a0, L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET // ...
s32i aGPIO_OUT_W1TS_REG, a0, L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET // ...
s32i aGPIO_OUT_W1TC_REG, a0, L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET // store the content of a15 (=aGPIO_OUT_W1TC_REG) to L5 stack with offset 44
// cleare the interrupt status of GPIO35(used interrupt pint!)
movi aVar, GPIO_STATUS1_W1TC_REG
movi temp, DRDY_INT_PIN_BITMASK
s32i temp, aVar, 0
//----
// AR = Address Register
movi aSampleCounter, SampleCounter // put the memory address of the volatile global integer variable "SampleCounter" into aSampleCounter (AR: a6)
movi aGPIO_OUT_W1TS_REG, GPIO_OUT_W1TS_REG // put the memory address of GPIO_OUT_W1TS_REG into aGPIO_OUT_W1TS_REG (AR: a14)
movi aGPIO_OUT_W1TC_REG, GPIO_OUT_W1TC_REG // put the memory address of GPIO_OUT_W1TS_REG into aGPIO_OUT_W1TC_REG (AR: a15)
movi vSPI_SCLK_PIN_BITMASK, SPI_SCLK_PIN_BITMASK // put the GPIO14 bitmask into vSPI_SCLK_PIN_BITMASK (AR: a13)
movi aGPIO_IN_REG, GPIO_IN_REG // put the memory address of GPIO_IN_REG into aGPIO_IN_REG ()
// the following 8 assembly line are doing "aADCBUF = Bufs[BufferSelector];" where Bufs is a global
// pointer array "volatile int *Bufs[2]" pointing to two buffers which store the samples of the
// ADC called ADCBUFFER_0[N] and ADCBUFFER_1[N], with desired size of the buffer N.
// the "initial" assignment of the two pointers to the ADC buffers are done in app_main():
// Bufs[0] = ADCBUFFER_0;
// Bufs[1] = ADCBUFFER_1;
movi aVar, BufferSelector // put the memory address of volatile global integer vatriable "BufferSelector" into aVar (AR: a9)
l32i vVar, aVar, 0 // load the content of "BufferSelector to vVar (AR: a10)
memw // BufferSelector is volatile so use memw (refer to xtensa isa)
movi aVar, Bufs // put the memory address of volatile global pointer array "Bufs" into aVar (AR: a9)
add.n aVar, aVar, vVar // add to the memory address of Bufs as offset the value of BufferSelector
memw // Bufs is volatile so use memw (not sure if needed also for add.n operation?!)
l32i aADCBUF, aVar, 0 // load the content (array entry 0 or 1, depends on BufferSelector Value) to aADCBUF (AR: a8)
memw // Bufs is volatile so use memw
//----
// the follwing 8 assembly lines are doing "*(aADCBUF+SampleCounter*4)"
// multiplication by 4 is done, cause only 4 adc channels are used.
l32i temp, aSampleCounter, 0 // load the content of the SampleCounter into temp
memw // SampleCounter is volatile so use memw
slli temp, temp, 4 // multiply SampleCounter by 4 using bit shift logical immediate
memw // to be sure, use memw ?!
add.n aADCBUF, aADCBUF, temp // increase the pointer, which shows to Bufs[BufferSelector], by "SampleCounter*4"
memw // again use mewm...
//----
movi iCh, 4 // put 4 into the "Channel"-Loop counter, since in this case 4 adc channels used
loop_Channel:
movi iBit, 24 // put 24 into the "Bit-Banging"-Loop counter, since it is a 24-bit adc
loop_Bit:
s32i vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TC_REG, 0 // set GPIO14, used as SPI SCLK, to low
nop // wait 4.2ns@fCPU=240MHz
s32i vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TS_REG, 0 // set GPIO14, used as SPI SCLK, to high
nop // wait 4.2ns@fCPU=240MHz
l32i temp, aGPIO_IN_REG, 0 // load the GPIO levels of 0-31
extui temp, temp, SPI_DOUT_PIN, 1 // extract only the level of GPIO12, used as SPI DATA
addi.n iBit,iBit, -1 // decrement "Bit-Banging"-Loop counter
ssl iBit // put the "Bit-Banging"-Loop counter value into the Shift Amount Register (SAR)
memw // memw again :-)
sll temp, temp // shifting the SPI DATA (read out @GPIO12) left with amount iBit
memw // memw again :D
or vSample, vSample, temp // vSample = vSample | temp or lets say: VSample = vSample | (1<<iBit)
bnez iBit, loop_Bit // conditional branch of the "Bit-Banging"-Loop counter
s32i vSample, aADCBUF, 0 // store the "complete" Sample to the aADCBUF
memw // for use here memw
addi.n aADCBUF, aADCBUF, 4 // increment the pointer aADCBUFFER by 4 for the next sample to store
memw // memw needed ?!
addi.n iCh,iCh, -1 // decrement "Bit-Banging"-Loop counter
movi vSample, 0 // put 0 to vSample (AR: a7)
bnez iCh, loop_Channel // conditional branch of the "Channel"-Loop counter
// increment the SampleCounter
l32i vVar, aSampleCounter, 0
memw
addi.n vVar, vVar, 1
s32i vVar, aSampleCounter, 0
memw
//----
// check if the SampleCounter is equal to nSamplePerCh (desired numbers of samples per channel)
movi temp, nSamplesPerCh
bge vVar, temp, package_complete // the package is full, jump to "package_complete"
j l5_intr_handler_end // package is not full, jump to "l5_intr_handler_end"
// in the following 9 lines the BufferSelector will be switched: "BufferSelector ^= 1;
// why ?: because one of the two adc buffers ADCBUFFER_X is completed, the BufferSelector
// is switched, so the next other adc buffer can be filled until the previous adc buffer
// can be transfered... like a "Flip-Flop"-Buffer
package_complete:
movi aVar, BufferSelector // put the memory address of volatile global integer vatriable "BufferSelector" into aVar (AR: a9)
memw
l32i vVar, aVar, 0 // load the content of BufferSelector to vVar (AR: a10)
memw
movi temp, 4 // put 4 into temp (AR: a11): we have to use 4, because in assembly memory addresses are offset by 4 bytes !
xor vVar, vVar, temp // switch the BufferSelector
memw
s32i vVar, aVar, 0 // store the new BufferSelector !
memw
movi temp, 0 // put 0 into temp (AR: a11)
s32i temp, aSampleCounter, 0 // set SampleCounter to 0
memw // memw needed for sure here..
s32i vSPI_SCLK_PIN_BITMASK, aGPIO_OUT_W1TS_REG, 0 // set GPIO14, used as SPI SCLK, to high
l5_intr_handler_end:
// restore all used address registers, otherwise everything will explode :D
movi a0, _l5_intr_stack // put the memory address (=begin of the stack) to AR a0
l32i iCh, a0, L5_INTR_iCh_OFFSET // restore the saved content of a4 (=iCh)....
l32i iBit, a0, L5_INTR_iBit_OFFSET // ....
l32i aSampleCounter, a0, L5_INTR_aSampleCounter_OFFSET // ....
l32i vSample, a0, L5_INTR_vSample_OFFSET // ....
l32i aADCBUF, a0, L5_INTR_aADCBUF_OFFSET // ....
l32i aVar, a0, L5_INTR_aVar_OFFSET // ....
l32i vVar, a0, L5_INTR_vVar_OFFSET // ....
l32i temp, a0, L5_INTR_temp_OFFSET // ....
l32i aGPIO_IN_REG, a0, L5_INTR_aGPIO_IN_REG_OFFSET // ....
l32i vSPI_SCLK_PIN_BITMASK, a0, L5_INTR_vSPI_SCLK_PIN_BITMASK_OFFSET // ....
l32i aGPIO_OUT_W1TS_REG, a0, L5_INTR_aGPIO_OUT_W1TS_REG_OFFSET // ....
l32i aGPIO_OUT_W1TC_REG, a0, L5_INTR_aGPIO_OUT_W1TC_REG_OFFSET // restore the saved content of a15 (=aGPIO_OUT_W1TC_REG)
//----
rsync // better use rsync or isync here ?!
//isync
rsr a0, EXCSAVE_5 // read special register EXCSAVE (location for high level int. handler) to a0
rfi 5 // finaly return from high priority interrupt level 5.... puh...
.global ld_include_xt_highint5
ld_include_xt_highint5:
I appreciate every help !
Best regards,
opcode_x64