PIC32AK, a 32-bit dsPIC

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Building some sensor systems using this fairly new controller family. So far it's been very good for brute force sensor processing. Working out all of the details with a dev board but have all of the parts for my own PCB.

https://ww1.microchip.com/downloads...K1216GC41064-Family-Data-Sheet-DS70005592.pdf

1762376504329.png1762376569536.png
1762378777037.png

Testing some DMA routines (leaving the processor free for single, double and fixed point number crunching) with a simple GLCD connected to the board @25MHz SCK on SPI1. SPI2 dedicated pins run at 50MHz.
1762376658016.png
Screen updates from graphic memory.
1762376700854.png
DMA state machine sending page commands and display data for that page.
1762376755661.png
1762376791497.png
PIC32AK DIMM in a dsPIC33A dev board for testing.
The machine hardware (DMA here with SRAM) is much like the MIPS PIC32 controllers but is simplified (no need for MMU or advanced OS capabilities) for easier coding for the expected types of programs. I can port most of the PIC32MK C code from other projects quickly.
C:
int main(void)
{
    /* Initialize all modules */
    SYS_Initialize(NULL);

    TMR1_CallbackRegister(timer_ms_tick, 0);
    TMR1_InterruptEnable();
    TMR1_Start(); // software timers counter

    init_lcd_drv(D_INIT);
    OledClearBuffer();
    wait_lcd_done();


    snprintf(buffer, 255, "Testing PIC32AK   ");
    eaDogM_WriteStringAtPos(15, 0, buffer);
    OledUpdate();
    StartTimer(TMR_TEST, 2);
    while (true) {
        static uint32_t loops=0;
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks();
        RLED_Toggle();
        if (TimerDone(TMR_TEST)) {
            snprintf(buffer, 255, "Running  %u ",loops++);
            eaDogM_WriteStringAtPos(1, 0, buffer);
            OledUpdate();
            StartTimer(TMR_TEST, 2);
        }
    }

    /* Execution should not come here during normal operation */

    return( EXIT_FAILURE);
}
C:
void OledInit(void)
{
    /* Init the memory variables used to control access to the
     ** display.
     */
    OledDvrInit();
    /*
     * init DMA
     */
#ifdef USE_DMA
#ifdef DMA_STATE_M
    DMA_ChannelCallbackRegister(DMA_CHANNEL_0, SPI1DmaChannelHandler_State, 0); // end of LCD buffer transfer interrupt function
#endif
    DMA_ChannelCallbackRegister(DMA_CHANNEL_1, CBDmaChannelHandler, 0); // end of buffer clear transfer interrupt function
#endif

    /* Clear the display.
     */
    OledClear();
}

/*
* start a GLCD update: Called in user code with contextHandle set to DMA_MAGIC for a background screen update,
* during background transfers this function is used as the callback for DMA transfer complete events to
* sequence commands and data to the GLCD via the SPI port using the dstate ENUM variable
* dstate is set to 'D_idle' when the complete set of transfers is done.
*/
void SPI1DmaChannelHandler_State(DMA_TRANSFER_EVENT event, uintptr_t contextHandle)
{
    static int32_t ipag = 0; // buffer page number
    static uint8_t* pb; // buffer page address

    // back to mainline code, GLCD updates in background using DMA and interrupts
    LCD_UNSELECT();
    if (contextHandle == DMA_MAGIC) { // re-init state machine for next GLCD update
        dstate = D_init;
    }

    switch (dstate) {
    case D_init:
        ipag = 0;
        if (disp_frame) { // select flipper buffer, only one page with PIC32A
            pb = rgbOledBmp0;
        } else {
            pb = rgbOledBmp0;
        }
        /* FALLTHRU */
    case D_page: // send the page address commands via DMA
        LCD_SELECT(); // enable the GLCD chip for SPI transfers
        dstate = D_buffer;
        lcd_moveto_xy(ipag, 0); // calculate address data nibbles and store in rgbOledBmp_page array
        /*
         * DMA_ChannelCallbackRegister and SPI setup in OledInit
         */
        LCD_CMD();
        DMA_ChannelTransfer(DMA_CHANNEL_0, (const void *) rgbOledBmp_page, (const void*) &SPI1BUF, (size_t) 4);
        break;
    case D_buffer: // send the GLCD buffer data via DMA
        ipag++;
        if (ipag <= cpagOledMax) {
            LCD_SELECT(); // enable the GLCD chip for SPI transfers
            dstate = D_page;
            LCD_DRAM();
            DMA_ChannelTransfer(DMA_CHANNEL_0, (const void *) pb, (const void*) &SPI1BUF, (size_t) ccolOledMax);
            pb += ccolOledMax;
        } else {
            dstate = D_idle;
            LCD_UNSELECT(); // all done with the GLCD
        }
        break;
    case D_idle:
    default:
        LCD_UNSELECT();
        break;
    }
}
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
The next step is the sensor mass storage buffer.
https://www.issi.com/WW/pdf/66-67WVS2M8ALL-BLL.pdf

IS66WVS2M8ALL/BLL IS67WVS2M8ALL/BLL
16Mb SerialRAM 1.8V/3.0V, 104MHZ,
SPI & QPI PROTOCOL

The display is on SPI1 25MHz sck and the SRAM is on SPI2 with a 50MHz sck

Both interfaces will be using DMA for transfers. For the serial sram, the command and address sequence is sent as a 32-bit word and the 1024 Byte write burst will DMA directly from the ADC (triggered per conversion) to the serial sram.
The PIC32A has three e Rail-to-Rail OP amps to buffer the sensor signals and to provide a low impedance source to the ADC input S/H.
Op Amps allow the user to do signal conditioning without dedicated external circuitry. Op Amps in the PIC32AK1216GC41064 device support Unity Gain mode without any external connection. The user can adjust the gain of the Op Amp by using external resistors and disabling Unity Gain mode. The Op Amp has an offset trim feature to reduce the effects of input offsets. The output signal of the Op Amp can be monitored by enabling an internal connection to ADC.
String buffer data (16 bytes) sent to SPI2 for testing. Not using DMA on SPI2 (just a interrupt driven buffer driver) for this test so you can see the 4 byte FIFO empty causing data stream pauses that don't happen with the DMA driver on SPI1.
1762468234468.png
SPI 1&2 overlap transmissions.
1762468325123.png
1762468352818.png
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Not surprisingly the SRAM chip won't run at 50MHz on a adapter, with jumper wires and no damping resistors, but it will run at 25MHz. :D
1762534651828.png1762534665352.png
1762534690040.png

Testing the ID readback, gets the correct codes on the screen for this device.
1762534753427.png
1762534851042.png
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Running the flash SPI at (25MHz) using DMA transfers directly from the ADC (continuous conversions) result register to the SPIBUF that sets the transfer timing.
1762660047734.png
DEV board pot sending a DC signal to the ADC input pin with 3548 in the result buffer.
1762660275561.png
Sample rate. It would be much faster transferring directly to the PIC32A sram but this is testing the external sram software.
SPI2 has 8X drive vs 4X for the other pins. You can see the difference below. Both 1&2 are running at 25MHz but the SPI2 signal(s) is much cleaner (- scope and wiring artifacts) at the same frequency.

1762660317613.png
1762660680259.png
SPI 1&2 both sending data using DMA while the main program loop is running.
1762660709152.png
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Something a little more useful. Using a timer to trigger samples in background DMA tasks.
1762728589700.png
Only running the ADC conversion clock at 200MHz instead of the 320MHz max clock. Slows the total process a bit but the main time issue is running the iss66 (2M words by 8 bits) SPI clock at half speed due to DEV board wiring.

1762728784128.png
SCCP2 set to 3.5us timer interrupts for ADC samples

1762728856901.png
ADC setup and single shot conversion time using a GPIO pin for tracing. The trace pin shows to total ISR run time per ADC sample.
C:
/*
* is66 setup and R/W functions
*/
#include <xc.h>
#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "definitions.h"                // SYS function prototypes

//#define USE_SRAM
//#define DMA_HALF

volatile uint8_t iss_adc_write[8] = {0x02, 0x00, 0x00, 0x00}; // 1024 bytes in each address page for sequential writes
uint8_t sram_adc_write[8];
volatile uint32_t adc_result = 0;

/*
* setup the iss66 command buffer and DMA the data to the chip
*/
void ADC_DMA_write(void)
{
    TP0_Set(); // timing GPIO trace
#ifndef USE_SRAM
    SRAM_CS_Clear();
#endif

#ifndef USE_SRAM
    TP0_Clear();
    TP0_Set();
    AD1SWTRGbits.CH6TRG = 1;
    while (AD1STATbits.CH6RDY == 0);
    TP0_Clear();
    TP0_Set();
    adc_result = AD1CH6DATA;
    memcpy((void *) &iss_adc_write[4], (const void *) &adc_result, 4);
    iss_adc_write[3]+=2; // write 256 bytes into chip  as 16-bit integers

    while (DMA_ChannelIsBusy(DMA_CHANNEL_5)) {
    };
    DMA_ChannelTransfer(DMA_CHANNEL_5, (const void *) iss_adc_write, (const void*) &SPI2BUF, (size_t) 6);
#endif

#ifndef USE_SRAM
    SRAM_CS_Set();
#endif
    TP0_Clear();
};

/*
* iss66 data reads using DMA
* on the todo list
*/
void ADC_DMA_read(void)
{

};

/*
* DMA event callback processor
* not being used, only for testing for now
*/
void SPI2DmaChannelHandler_State(DMA_TRANSFER_EVENT event, uintptr_t contextHandle)
{
#ifdef DMA_HALF
    if (event == DMA_TRANSFER_EVENT_HALF_COMPLETE) {
        adc_result = AD1CH6DATA;
        AD1SWTRGbits.CH6TRG = 1;
        while (AD1STATbits.CH6RDY == 0);
    }
#endif
}

/*
* Time trigger for ADC conversion and iss66 data writes
*/
void SCCP2_Callback_InterruptHandler(uint32_t status, uintptr_t context)
{
    ADC_DMA_write();
}

void ADC_DMA_init(void)
{
    // Select single conversion mode on channel 6 to AN6
    AD1CH6CONbits.MODE = 0;
    // Software trigger will start a conversion.
    AD1CH6CONbits.TRG1SRC = 1;
    AD1CH6CONbits.TRG2SRC = 0;
    // Use a single-ended input.
    AD1CH6CONbits.DIFF = 0;
    // Select the AN6 analog positive input/pin for the signal.
    AD1CH6CONbits.PINSEL = 6;
    // Select signal sampling time (0.5 TADs = 6.25nS).
    AD1CH6CONbits.SAMC = 0;
    // Set ADC to RUN mode.
    AD1CONbits.MODE = 2;
    // Enable ADC.
    AD1CONbits.ON = 1;
    // Wait when ADC will be ready/calibrated.
    while (AD1CONbits.ADRDY == 0);

    // Trigger channel #6 in software and wait for the result.
    AD1SWTRGbits.CH6TRG = 1;
    // Wait for a conversion ready flag.
    while (AD1STATbits.CH6RDY == 0);
    // Read result. It will clear the conversion ready flag.
    adc_result = AD1CH6DATA;

    /*
     * setup the DMA and timer background tasks
     */
    DMA_ChannelCallbackRegister(DMA_CHANNEL_5, SPI2DmaChannelHandler_State, 0);
    SCCP2_TimerCallbackRegister(SCCP2_Callback_InterruptHandler, (uintptr_t) NULL);
    SCCP2_Timer32bitPeriodSet(350); // 3.5us timer interrupts

}
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Have most of the major I/O systems running as DMA or interrupt processes so now the task is to optimize memory bandwidth by assigning BANK addresses to major buffers.
1764437752637.png
1764437776798.png
ADC calibration/stability (ADC programmed for auto-cals every hour) testing with SCL3300 XYZ tilt and XYZ accel readbacks.


Bus Matrix The Bus Matrix (BMX) arbitrates memory accesses in the event that two independent initiators are trying to access the same target.
1764438143118.png
Three SPI masters SCK clocks. 2 SRAM chip for ADC data 34MHz, 3 Display 25MHz, 4 SCL3300 IMU 8MHz 32-bit mode.
1764438557428.png
1 is the ADC conversion time for double samples.
1764436302289.png
C:
#define YRAM        0x006000
#define XRAM        0x004000

uint8_t  __attribute__((address(XRAM))) rgbOledBmp0[cbOledDispMax]; // one display buffer, no page flipping
// _0x7f019027aea0_at_address_0x4000    0x4000                   0           0xf00  (3840)

imu_cmd_t __attribute__((address(YRAM))) imu0 = {
    .id = 3,
    .tbuf32[SCA3300_TRM] = SCA3300_SWRESET_32B,
    .online = false,
    .device = IMU_SCL3300, // device type
    .cs = IMU_CS, // chip select number
    .run = false,
    .crc_error = false,
    .log_timeout = SCA_LOG_TIMEOUT,
    .update = true,
    .features = false,
    .spi_bytes = 4,
    .op.info_ptr = &sca3300_version,
    .op.imu_set_spimode = &sca3300_set_spimode,
    .op.imu_getid = &sca3300_getid,
    .op.imu_getserial = &sca3300_getserial,
    .op.imu_getdata = &sca3300_getdata,
    .acc_range = range_inc2,
    .acc_range_scl = range_inc2,
    .angles = false,
    .locked = true,
    .warn = false,
    .down = false,
};
// _0x7fe0734a8cf0_at_address_0x6000    0x6000                   0           0x45c  (1116)
Not bad for a $1.91 each controller from Mouser with tons of 5V pins.
1764439522738.png
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
12-bit DAC calibration and graphics speed testing.
1764523811438.png
USB-C power connector. DAC1 out to ADC1 in.
1764523830632.png
1764523878255.png
Lorenz Attractor
https://links.uwaterloo.ca/pmath370w14/PMATH370/lorenz Latex.pdf

C:
void LA_gfx(bool reset, bool redraw, uint32_t turns)
{
    static double x = 0.1;
    static double y = 0;
    static double z = 0;
    static double a = 10.0;
    static double b = 28.0;
    static double c = 8.0 / 3.0;
    static double t = 0.01;
    static uint32_t i = 0;

    //Iterate and update x,y and z locations
    //based upon the Lorenz equations
    if (redraw) {
        i = 0;
        return;
    }

    if (reset) {
        x = 0.1;
        y = 0;
        z = 0;
        a = 10.0;
        b = 28.0;
        c = 8.0 / 3.0;
        t = 0.01;
        i = 0;
        return;
    }

    if (i++ >= turns) {
        i = turns;
        return;
    }

    double xt = x + t * a * (y - x);
    double yt = y + t * (x * (b - z) - y);
    double zt = z + t * (x * y - c * z);
    x = xt; // + q0;
    y = yt; // + q1;
    z = zt; // + q2;

    xa = (x * 2.5) + 120;
    if (true) { // H.la_mod button in hid.h
        ya = (z * 1.5) + 50; // xz plot
    } else {
        ya = (y * 1.5) + 40; // xy plot
    }
    za = z;
    OledMoveTo(xa, ya);
    OledLineTo(xa + 1, ya + 1);
}
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
C:
        if (TimerDone(TMR_IMU_DATA)) {
            StartTimer(TMR_IMU_DATA, IMU_TICKS);
            imu0.op.imu_getdata(&imu0);
            imu0.update = false;
            getAllData(&accel, &imu0); // convert data from the IMU chip
            q0 = accel.x / 1.0f;
            q1 = accel.y / 1.0f;
            q2 = accel.z / 1.0f;
            q3 = q0;
            snprintf(buffer, IMU_BUF - 1, "%5.2f,%5.2f,%5.2f,%5.2f,%5.2f,%5.2f,%5.2f\r", 0.1f, accel.xa, accel.ya, accel.za, accel.x, accel.y, accel.z);
            UART1_Write(buffer, strlen(buffer) );
            RLED_Toggle();
        }
IMU inclination data testing with 50ms updates. With the SCL3300, all of the calculations for tilt angles are done internally to the IMU.
https://www.analog.com/en/resources/app-notes/an-1057.html
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Timing UART data processing at 921600bps on the TTL to USB module to the Linux display machine.
1764780497416.png
1764780733861.png
1764780775914.png
TP0 and serial data traces.

1ms ODR from the IMU
C:
        if (TimerDone(TMR_IMU_DATA)) {
            StartTimer(TMR_IMU_DATA, IMU_TICKS);
            TP0_Set();
            imu0.op.imu_getdata(&imu0);
            imu0.update = false;
            getAllData(&accel, &imu0); // convert data from the IMU chip
            q0 = accel.x / 1.0f;
            q1 = accel.y / 1.0f;
            q2 = accel.z / 1.0f;
            q3 = q0;
            TP0_Clear();
            TP0_Set();
            snprintf(buffer, IMU_BUF - 1, "%5.2f,%5.2f,%5.2f,%5.2f,%5.2f,%5.2f,%5.2f\r", 0.1f, accel.xa, accel.ya, accel.za, accel.x, accel.y, accel.z);
            UART1_Write(buffer, strlen(buffer));
            TP0_Clear();
            RLED_Toggle();
        }
 

Thread Starter

nsaspook

Joined Aug 27, 2009
16,257
Now to get stable naming for the USB interface devices when you have more than one USB to serial adapter.

lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 8087:800a Intel Corp. Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 8087:8002 Intel Corp. 8 channel internal hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 002: ID 0424:2660 Microchip Technology, Inc. (formerly SMSC) Hub
Bus 003 Device 004: ID 046a:0011 Cherry GmbH G83 (RS 6000) Keyboard
Bus 003 Device 006: ID 30fa:0300 USB Optical Mouse
Bus 003 Device 102: ID 2001:f103 D-Link Corp. DUB-H7 7-port USB 2.0 hub
Bus 003 Device 105: ID 05e3:0610 Genesys Logic, Inc. Hub
Bus 003 Device 106: ID 04d8:9009 Microchip Technology, Inc. ICD3
Bus 003 Device 107: ID 1d50:6089 OpenMoko, Inc. Great Scott Gadgets HackRF One SDR
Bus 003 Device 108: ID 04d8:9036 Microchip Technology, Inc. MPLAB® PICkit 5
Bus 003 Device 109: ID 04d8:9012 Microchip Technology, Inc. PICkit4
Bus 003 Device 124: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
Bus 003 Device 125: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

ls -l /dev/ttyM*
lrwxrwxrwx 1 root root 7 Dec 6 10:41 /dev/ttyMgps -> ttyUSB1
lrwxrwxrwx 1 root root 7 Dec 6 10:41 /dev/ttyMpic32ak -> ttyUSB0

/etc/udev/rules.d/99-custom-devices.rules
Code:
# USB-Serial adapter # /dev/ttyMyDevice
SUBSYSTEM=="tty", \
ATTRS{idVendor}=="0403", \
  ATTRS{idProduct}=="6001", \
  ATTRS{serial}=="FTDBHXVD", \
  SYMLINK+="ttyMgps"
SUBSYSTEM=="tty", \
  ATTRS{idProduct}=="ea60", \
  ATTRS{idVendor}=="10c4", \
  ATTRS{serial}=="0001", \
  SYMLINK+="ttyMpic32ak"
My GPS time standard and the PIC32AK IMU data stream.
GPS standard start in /etc/rc.local
gpsd -D 0 -n -s 9600 /dev/ttyMgps

In the Cube.java code
SerialPort port = SerialPort.getCommPort("ttyMpic32ak");

https://medium.com/@dynamicy/stop-t...or-stable-device-naming-on-linux-adc878f19ee9
 
Top