Brooklyn Board: Arduino SPI Sending Packets

Alright so in the last post I talked about setting up Arduino in VS Code and programming some basic SPI protocols for the Brooklyn board but it could be applied to any Arduino. Today I am going to be talking about sending full packet structured data back and forth between a master and slave SPI device. For actually embedded chips that need to communicate complex data, this is the way to go. It allows you to send custom commands as well as variable data lengths and read and send all with one line of code from each side.

I had to do a few things first. Both the empire and Brooklyn needed to have a system of knowing whether or not they have received data from each other. This allows the code to hang and wait on data. We only want to wait on the data up until the packet is complete wich based on the bytes being received we can determine the length of the packet.

I first did this on the slave side. Currently, the SPI system operates on interrupt meaning the loop can be going going going then suddenly stop as data comes in and goes to this interrupt. This is great but honestly, I don’t need the loop running if there is no data to process. I also need some way of getting the data out of the interrupt for use and decision making in the loop. To accomplish this I created a uint8_t buffer array of 100 values that would get populated with whatever byte value was present every interrupt. The idx of the buffer would then be incremented so it’s writing to the next value in the array. Now I also have a ridx value which starts off equal to idx but is no longer equal after idx is incremented in the interrupt. So using a while loop we can make the program hang and wait for the SPI interrupt to occur. We can then read the value from the buffer using the ridx value than that gets incremented as well and the read value gets assigned to a uint8_t in the main loop. This allows us to know exactly when data arrives and we can write it to a targetted variable very easily. This process is able to repeat for multiple packets and once the idx hits 99 it will reset back to zero.

ISR (SPI_STC_vect)
{
    buffer[idx] = SPDR;
    if(idx==99){
        idx=0;
    }
    idx+=1;
}

This is what my interrupt function looks like and as you can see its fairly simple but gets the job done.

uint8_t readData(){
    while(idx==ridx){}
    uint8_t data = buffer[ridx];
    if(ridx==99){
        ridx=0;
    }
    ridx+=1;
    return(data);
}

Now, this is my function that actually waits for data to arrive. When this function gets called it will get stuck in the while loop until the interrupt occurs it will then read the most recent value from the buffer and return it after incrementing the ridx value to reset the process. If this is called lets say three times in sequence the program will wait for three bytes of data to arrive.

The next step was to actually send packet data from the master. Currently, the SPI program only sends a single byte and receives a response from the salve but now our salve is capable of waiting to receive multiple bytes before making a decision so we should take advantage of that to send something more complex.

uint8_t SPISendpacket(uint8_t SSpin, uint8_t data[]){
    int packetSum = 0;
    SPISend(SSpin,data[0]);
    packetSum+=data[0];
    SPISend(SSpin,data[1]);
    packetSum+=data[1];
    SPISend(SSpin,data[2]);
    packetSum+=data[2];
    for(int i=0;i<data[2];i++){
        packetSum+=data[3+i];
        SPISend(SSpin,data[3+i]);
    }
    ck1 = floor(packetSum / 256);
    ck2 = packetSum % 256;
    SPISend(SSpin, ck1);
    SPISend(SSpin, ck2);
    delayMicroseconds(200);
    if(receivePacket(SSpin)){
        LED(BLUE);
    }
}

This is the main function I used to send packet data from the master to the slave. This function takes two parameters the SS pin or the slave select pin as well as an array of data. It then uses a very basic SPISend function which basically is the code written yesterday for sending one byte just wrapped in a function.

uint8_t SPISend(uint8_t SSpin, uint8_t data){
    digitalWrite(SSpin, LOW);
    uint8_t resp = SPI.transfer(data);
    Serial.print(resp);
    digitalWrite(SSpin, HIGH);
    return resp;
}

Before I go more into the code let me first explain the packet structure that I am using.

First Byte – Header byte = 255

Second Byte – command byte = varies based on command for slave

Third Byte – data length byte = varies based on the amount of data to come

Fourth – i byte – variable-length data bytes = varies based on data being sent

i-2 byte – Checksum One Byte = Allows the packet to be validated

i-1 byte – Checksum Two Byte = Allows the packet to be validated

So the packet structure is fairly simple and fairly well defined so now we can get to send it. The first value of the packet is sent which is always going to be the header byte equal to 255. This basically tells the slave here’s some data and I’m a friend you can trust what I’m going to send you and it is data you should understand. The salve can then prepare to receive the data. This header value is also added to the packetSum which is the sum of every byte present in the packet, this will be used later to calculate the checksums. This process is repeated for the command byte as well as the data length byte. The data length is variable it could be 0 bytes or it could be something like 72 bytes it depends on the command and the current state of the board or what you want to do. So we need a dynamic way of reading this part of the packet which is why the previous byte was data length. This informs us how many values we have to read until we are finished reading the packet which is helpful for both the master and the salve. On the master side, we just have a for loop that iterates through the next few values determined by data length of the data array passed into the function. Every iteration it sends this data and also adds it to the packet sum. Finally, packet sum is used to calculate two checksums which are both sent. There are many different kinds of checksum with this probably being the simplest but it gets the job done and basically, if anyone of the values in the packet changes in the process of sending to the SPI slave the slave can read the checksum then calculate its own and if it differs it knows whatever packet that was sent is incorrect.

Now that data is extremely easy to send to a specific slave we need the slave to respond to the master confirming it received the data, respond with data that was asked for or return an error. Because the type of response is varied we will once again want to use a packet to communicate this data with the master. Now we cant just use the same function were used on the master side since the SPI slave can only respond when information is sent to it. However, we can use this to our advantage as well, because we know the slave should respond immediately after sending the packet whether or not it received it properly we can then wait for a response on the SPI master side which is why you see the receivedPacket function at the end of the packet sending function.

bool receivePacket(uint8_t SSpin){
    header = SPISend(SSpin,0);
    if(header==255){
        cmd = SPISend(SSpin,0);
        datalen = SPISend(SSpin,0);
        for(int i=0;i<datalen;i++){
            data[i] = SPISend(SSpin,0);
        }
        ck1 = SPISend(SSpin,0);
        ck2 = SPISend(SSpin,0);
        if(calcChecksum()){
            return true;
        }
    }
    return false;
}

This function is on the master side and is called after a slight delay on the master side to allow the salve to receive and process the packet. This function uses the knowledge of our packet structure to receive it from the slave. It basically sends empty bytes until the packet is fully read. Starting with the header, then reading cmd, then data length, etc. It finally calculates the checksum compares it t what was read the returns true or false at the end. Now for this to work the salve needs to be able to first process the packet that was sent from the master so let’s look at that code.

bool receivePacket(){
    header = readData();
    if(header==255){
        cmd = readData();
        datalen = readData();
        for(int i=0;i<datalen;i++){
            data[i] = readData();
        }
        ck1 = readData();
        ck2 = readData();
        if(calcChecksum()){
            return true;
        }
    }
    return false;
}

Oh yeah almost forget I made it pretty much identical to the function on the master side so when I make changes its much more obvious but this basically does the same thing and returns true or false after an entire packet has been received. Now in the loop we call this function and wait for the packet we can then make decisions based on the data sent.

void loop(void){
    uint8_t data[] = {RID_ENCODERSPEED,1,72};
    if(receivePacket()){
        switch(cmd){
            case CID_GETSPEED:
                LED(GREEN);
                SPISendPacket(data);
                break;
        }
    }
}

Here we receive the packet then we see if cmd or command is equal to the command value we have for GETSPEED, in this case, it’s just zero. We have a switch statement because we will eventually have many more commands that could be sent and independent decisions for each one. We then set the LED to green to indicate that the empire received the data properly for debugging purposes. Then we send back a packet response over SPI. As you can we are sending the data array you can see declare at the top of the loop. You might notice it’s missing the header byte. This is because 255 is apart of every packet so we can just include it in the send packet function which we will take a look at now.

void SPISend(uint8_t data){
    SPDR = data;
    readData();
}

void SPISendPacket(uint8_t data[]){
    int packetSum = 0;
    SPISend(255);
    packetSum+=255;
    SPISend(data[0]);
    packetSum+=data[0];
    SPISend(data[1]);
    packetSum+=data[1];
    for(int i=0;i<data[1];i++){
        packetSum+=data[2+i];
        SPISend(data[2+i]);
    }
    ck1 = floor(packetSum / 256);
    ck2 = packetSum % 256;
    SPISend(ck1);
    SPISend(ck2);
}

Now, this function is fairly simple as well. We first have the SPISend function which will basically write a byte to SPDR which if you remember will only get sent to the master once the master sends data back to the slave. We then have the read data function immediately after which will just wait for the master to send data which means the slave has also successfully written its data to the master and it can move on to the next data value to be sent. Without waiting we would have a race condition and unless the master is reading as fast as the slave is sending we would end up missing some values. Now onto sending packets using this function. This function is pretty much identical to the function we used on the master side for sending data the only difference being the different functions for sending a single byte. Once again we are sending every value from the packet and add it to a packet sum variable and then finally calculating and sending the checksum. Now on this side of the code, we don’t expect a response from master since the master always addresses the slave we never expect the master to respond. Now if a packet gets sent incorrectly and the master detects it instead of sending a received or not bak to the slave we will simply send the masters packet again asking for the data and the slave will send it again. However, the decision of whether or not to ask for it again needs to be handled by the API on a higher level to prevent loops and the need for a watchdog timer.

there are also multiple response ids. Currently 0 means there was packet error 1 means packet sent but no data needs to be sent and 2 means it is sending encoder speed values back. More of these will be added for each unique use case of data to be sent back to the master. This allows the master to understand the data being sent from the salve for either use in its program or debugging to be sent to the API level. Once the packet from the slave gets sent to the master the master then parses the packet and can make a decision with what to do with it the same way the salve does. I am still trying to decide how and where I want the decision making to be made. Pretty much every response will be sent to the API level but some responses may be used internally in the Brooklyn code meaning I need to make that decision either in the receive packet function or in the main. Both have their issues and advantages but its just something I need to think about.

But anyway that’s pretty much all of the code needed to send packets over SPI. It really wasn’t very hard just requires the master and slave to have an understanding of each other and a little bit of creative thinking on my part. If you would like to see the code I have committed it to GitHub publically. To make this work with your project you should only have to change the SS pin at the top of the master code. Also, change the LED function to work with the led on your Arduino but other than that it should be fairly universal.

I began working on sending data back and forth between the API and the Brooklyn board so I can actually start communicating with it. But Brooklyn suddenly stopped being detected as an Arduino which has happened to me before. Essentially there was an error flashing the board with my code and it ended up messing up the internal fuses in the chips. These can be reset by burning the bootloader on the Arduino but the Brooklyn board doesn’t provide easy access to its SPI pins. last time I had this problem I simply soldered some tiny wires to the female SPI holes in the board but this was a little jank and could be improved. Kirill Safin the creators of the board has a small circuit board with pogo pins that he uses to burn the bootloader so I let him know my issue and he is currently mailing me his right now. But with all the base level firmware done all that’s left is to finish the API for communication and start populating the commands that can be sent on the API and firmware side.

Leave a comment