Tutorial:New AT90CANxxx/FreeRTOS Users

From ECOSat
Jump to: navigation, search

Back to Main Page

Hi all! Clay here. I'll be developing this section as part of my first coop work term. Bear with me. I'm not only writing a newbie guide for embedded programmers, but also learning how to edit Wiki as I go. I'll be updating regularly and hopefully it will help a lot of the new members trying to learn how to program the AT90CAN microprocessor for use with FreeRTOS. This is not an exhaustive tutorial, but more like a new user quick start to supplement when you just don't get what the forums and documentation are telling you. I'm not going to reinvent the wheel either. If you need more info, check out the Useful Links section!

Contents

DVK90CAN1 Development Board

This is the board you will be using to develop and debug code for many of the subsystems found in ECOSat2. This board has a lot of IO capability including keyboard, LEDs, and audio transducer. It is capable of communicating via CAN, SPI, and TWI (among other methods).Look here for the DVK90CAN1 Hardware User Guide. Below is a shot of the board itself.

Solution Explorer View

Here's a closer look at the JTAG connection for the AVR Debugger.

Solution Explorer View

A shot of the entire setup. Plug into laptop via USB and power the board with 6V 1A AC/DC converter (not shown, but plugs in to jack on bottom left of board as shown here).

Solution Explorer View

Atmel Studio 6

Fairly easy to install and setup. I'll include some setup images and screenshots to get you up and running as quick as possible so you can get "Hello World" on the go.

Learning image formatting on the fly so don't bust my chops about them just yet!!

First things first. Get the software here! Atmel Studio 6.2 Download

Start Atmel Studio 6 and select File-->Open-->Project/Solution-->(appropriate Atmel Studio 6.2 Solution File)

Once you have the solution opened, you'll see the Solution Explorer view on the right hand side of the screen. Select the appropriate c file from the solution explorer. In this case we'll open main.c.

Solution Explorer View


Next, select Build-->Build Solution.

You will see a message like the one below, or you will get error messages.

Build Message


Debugging

Below is a picture of the most used buttons. The green arrow runs your program in debug mode, and to the right of that are the step into and step over buttons.

Useful GUI Buttons


Download the FreeRTOS Viewer Extension for extra help debugging!

Below is the button that shows up on Atmel Studio once the viewer extension is installed.

FreeRTOS Viewer Button

The viewer allows you to see a lot of useful debugging info like what tasks are running, what the RTOS tick is at, and what semaphores you have running in your program. You can see all of this info once you have paused your program manually, or you've hit a breakpoint.

FreeRTOS Viewer

To set a breakpoint, just click in the left margin next to the line of code you want to stop at while the program is paused.

Breakpoint Example

The IO view of Atmel Studio is another great feature that lets you view the current contents of port registers (among other places). Below is the button and a sample of what you might see while debugging your program.

IO View Button
IO View

Now that you've got your Dev Board and software up and running, it's time to do some "Hello World"ing!

Sandboxing with Visual Studio

While researching FreeRTOS I came across some useful Youtube Tutorials. Though the examples don't directly correspond to programming the AT90CAN, they provide a useful introduction on how to use tasks, queues, semaphores, etc. To get some practical experience coding FreeRTOS, download Visual Studio Community 2013 onto your laptop so you can practice without needing a dev board! What is really awesome is that you can use VS to debug your functions and get an immediate result echoed to the console using printf() statements that would otherwise take up too much memory on the AT90CAN processor. Alternatively, you could try using Eclipse IDE with the minGW compiler. I tried it with Eclipse Luna, but it kept crashing whenever I attempted to run the demo program. I got frustrated and gave up on it, but let me know if you have better success configuring the IDE!! I found VS infinitely easier to get up and running with. Just download and install. The program automatically updates your toolchain with any missing components and voila! Ready to run! Here's how to get started with your first Visual Studio FreeRTOS demo (the way I did it at least):

First, you'll need VS Community 2013 and the latest version of FreeRTOS. Check the links in this section or the "Useful Links" section.

Once you've downloaded and installed/placed in suitable location, open the following demo:

Opening a new project/solution
Demo project path

Here's what the entire demo project file structure looks like.

Solution explorer

Set the definition to 1 (vice 0) to run the simple demo only. This will be the most useful for practicing.

Set Simple Blinky

Saleae Logic Analyzer

I'll describe the setup of the Saleae Logic Analyzer here with reference to the TWI communication section. This should give you a good start at configuring the analyzer. Once you have the Logic program installed it will bring you to the main analysis screen below:


Electrical shematic for Payload power

Fit three of the eight logic leads to SCL, SDA, and Ground:

Electrical shematic for Payload power

Click on the plus sign next to protocols and select I2C (proprietary name for TWI):

Electrical shematic for Payload power

Right click the protocol to select channels for SDA and SCL that correspond to the appropriate leads you've attached:

Electrical shematic for Payload power

Click the start simulation button when your program is running on your AT90CAN:

Electrical shematic for Payload power

And that's it! Some of the protocols need different clock rates to be set, but for the most part you should get something nicely broken down like the below TWI waveform:

Electrical shematic for Payload power

FreeRTOS

Definitely takes some time to get used to if you're like me and have maybe CSC 110 and CSC 115/16 under your belt. It's all about the tasks and scheduling!! I'll make it crystal clear for the first couple of "Hello World" tasks. If you're like me, you'll have come across Stack Exchange or AVR Freaks in your quest for knowledge. You'll also know how terribly condescending, pretentious, and downright unhelpful a lot of commenters can be to newbies.

Tasks

Here is the prototype of a task in FreeRTOS:

BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, unsigned short usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pvCreatedTask);

Looks a bit daunting doesn't it? That's what I thought!

Here is a specific example of the task I created to turn the LEDs on:

xTaskCreate(vLEDsON,(signed portCHAR *)"ON",200,NULL,1,NULL);

There, that looks a little easier, doesn't it?

xTaskCreate() is the API (FreeRTOS function) used to create the task
vLEDsON is the name of my function that will, wait for it... turn on the LEDs!
(signed portCHAR *) is the type that you will use in almost every task you create, so just use this for now
"ON" is just a descriptive name for the function that will be performed
200 is the amount of stack space used for the task. This will generally be an acceptable value for our purposes
The use of NULL in both cases is typical. Use this as the parameter to start
1 is the priority of our task. Priority increases numerically from 0 to 8???(I'll have to check)

This should get you well on your way to creating your first task in FreeRTOS!

Semaphores

Semaphores are a method of providing information to a task that lets the task know when to execute. It is generally desirable to have as short an interrupt service routine as possible to allow the processor to do some other productive work. In my keyboard case, the ISR will detect that an interrupt has been generated on the port corresponding to a certain key. Once the ISR has been invoked, its only task is to set a semaphore (just a bit) which keeps the ISR very short. The task that has been created as the ACTUAL interrupt handler is in the blocked state, but is constantly attempting to read a set semaphore. While the semaphore is not set, the task will remain blocked so that other tasks can be performed. Only when the semaphore is set does the task become unblocked and run its routine.

Mutex

A mutex is employed when there is a scarce resource that can only be accessed by one user at a time. Think of a group of people at a meeting. The scarce resource, in this case, is time to speak. Enter the mutex. In this example, our "mutex" is a talking stick. Only the person who has the talking stick may speak. In order for another person to speak, the talking stick must be given by the person currently speaking to the next person who wishes to talk. This way, only one person speaks at a time, and everything runs smoothly. For ECOSat2, the scarce resource, at least for the ACS, will be the SPI and I2C (TWI) ports which can only have one user communicating at a time. I'll include my VS mutex code below to illustrate implementation and hopefully I can come up with code for the AT90CAN soon. The following steps are a continuation from the VS Sandboxing section:

In this example, I've changed the queue size from 1 to 5 just for illustration

Queue Length

Declared my prototypes...

Prototypes

Create queue and mutex...

Create Queue and Mutex

Main function

Main Function

User1 function

User 1 Function

User2 function

User 2 Function

All that's left is to build the project...

Build Project

And press play!

Debug

You should end up with a nice console output like this:

Console Output

You can see that the user 1 task obtains the key to use the resource a few times (I introduced some task delays) and then the higher priority user 2 takes the key when the task becomes ready. I hope you found this useful for getting to know FreeRTOS/Visual Studio!

Putting it all together

Going to put my simple blinky and Interrupt code here soon. Will include the functions, ISRs and main sections. I'm hoping that here's where you'll find the most useful bits for getting down to business. Most of the online FAQ's just refer new users to the FreeRTOS Demo code when first starting out. It doesn't really help that the "blinky" LED demo is buried within a bunch of other demos and takes some time to decipher. Even though there might be a more elegant way to flash the LEDs on the DVK90CAN1, I think my code will be simple enough to get you started without reading thousands of pages of references.

Simple LED output

This program simply illustrates the use of tasks and their scheduling. In this case, both tasks are of priority 1 which means that the scheduler will evenly allocate resources to both of the tasks. I've offset the task delays (1000ms and 1050ms respectively) in order to more easily see what is happening on the LEDs. It may be easier to visualize the tasks if you toggle the LEDs instead of using delays. The argument of vTaskDelay() just happens to be in ms for ECOSat2. This will not always be the case for different processors and setups. All functions in FreeRTOS must generally not return a value and that is the reason for the infinite for loop.

void vLEDsON(void *pvParameters) {

    for(;;)
    {     
         PORTA = 0xFF; //This port is connected to the LEDs on the DVK90CAN1 dev board. A value of 0xFF will light all LEDs.
         vTaskDelay(1050); //Initiate a function delay of 1050ms. The scheduler will switch to the IDLE task and then to any higher pri task ready to run.
    }

}

void vLEDsOFF(void *pvParameters) {

    for(;;)
    {
         PORTA = 0x00; //Turn all LEDs off.
         vTaskDelay(1000); //1s function delay.
    }

}

portSHORT main(void) {

    DDRA  = 0xFF; //DDRA is the Data Direction Register for PORT A. 0xFF configures the port for output.
    xTaskCreate(vLEDsON,(signed portCHAR *)"ON",200,NULL,1,NULL); //Create LED ON task.
    xTaskCreate(vLEDsOFF,(signed portCHAR *)"OFF",200,NULL,1,NULL); //Create LED OFF task.
    vTaskStartScheduler(); //Start scheduler. This will be the same for all programs (ie. create tasks then start scheduler).
    return 0; // Should never reach this statement. Scheduler will be running perpetually.

}

Keyboard interrupt with LED toggle

In this section I've configured the DVK90CAN1 to accept an interrupt by pressing the North key on the compass keyboard. This in turn causes the ISR to create a semaphore that the handling task uses to toggle the LEDs. Simple, but time consuming without this tutorial!! (Let me know about any errors you see. The code works, but I may have some interrupt enables in a bad spot etc.)

xSemaphoreHandle xNorth_INT_SEM; //Creates a handle for your semaphores
xSemaphoreHandle xLEDnoChange;
void vLEDtoggle(void *pvParameters) // LED Toggling function {

    for(;;)
    {
         xSemaphoreTake(xNorth_INT_SEM,portMAX_DELAY); //Check to see if semaphore is set.
         EIMSK=0; //Disable all interrupts.
         PORTA=PORTA^0xAA; //Toggles previous value of PORTA.
         xSemaphoreGive(xLEDnoChange); //Sets semaphore xLEDnoChange. Our vLEDnoChange will be looking for this semaphore!!
         EIMSK=0xFF; //Enable all interrupts;
    }

}

void vLEDnoChange(void *pvParameters) // This function essentially does nothing. It is more for instruction about tasks and scheduling. When you use FreeRTOS viewer you will be able to set breakpoints and see that this function takes the semaphore from the previous function and enters the run state. {

    for(;;)
    {
        xSemaphoreTake(xLEDnoChange,0); //Looks for the semaphore bit.
        PORTA=PORTA; // Does nothing really. Everything stays the same.
    }

}


ISR(INT4_vect) //This function is called as soon as north key is pressed. Linked to INT4_vect in interrupt vector table. {

    EIMSK=0; //Disable all interrupts since we don't care about other bit settings in this simple case.
    signed portBASE_TYPE xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xNorth_INT_SEM,&xHigherPriorityTaskWoken); //Must use FromISR (ie. xSemaphoreGiveFromISR();). 
// Don't use regular FreeRTOS APIs.This function gives the semaphore to any higher pri task that may be looking for it.
// In our case, the toggle function is looking for it! if(xHigherPriorityTaskWoken==pdTRUE) //If there is a higher pri task looking for this semaphore then yield to that task. { taskYIELD(); //Saves registers and data in order to yield to higher priority task. }

}

portSHORT main(void) {

    key_init(); // This function is included and initializes the keyboard functions of the dev board.
    PORTA=0xAA; //Initialize data at PORTA.
    DDRA  = 0xFF; //DDRA is the Data Direction Register for PORT A. 0xFF configures the port for output.
    EICRB=0xFF; // The interrupt enable registers that must be set to 1 for this case.
    EIMSK=0xFF;
    xNorth_INT_SEM = NULL; //Initializing semaphore handles.
    xLEDnoChange = NULL;
    vSemaphoreCreateBinary(xLEDnoChange); //Creating semaphores.
    vSemaphoreCreateBinary(xNorth_INT_SEM);
    if(xNorth_INT_SEM!=NULL) //Check to see if semaphore was created successfully.
    {
         xTaskCreate(vLEDtoggle,(signed portCHAR *)"ON",200,NULL,2,NULL); //Create LED ON task. Notice priority 2 to ensure that interrupt handler task 
//runs before the no change function. xTaskCreate(vLEDnoChange,(signed portCHAR *)"OFF",200,NULL,1,NULL); //Create LED no change task. vTaskStartScheduler(); //Start scheduler. This will be the same for all programs (ie. create tasks then start scheduler). } return 0;

}

That's it! "Hello World", check. You should now have the basic grasp of the hardware and software needed to develop most of the subsystems onboard ECOSat2. Now get cracking on interfacing those subsystems so we can get that satellite functional and in space for years to come!

CANBUS Example with 2 boards

Now to step things up a bit. To teach myself a bit more about CANBUS, I improved a bit on my previous Interrupt and LED tutorials. This time I used the same keyboard interrupt on board 1 to send data to the LEDs of board 2 over the CANBUS. My setup pics to follow.
CAN Pinout. source: Atmel DVK90CAN1 Hardware User Manual
Just connect pins 2, 6, and 7 directly between both CAN connectors. All my code below is separated into the files that the code appears in. First, I'll describe code in the project/solution for board 1 (Transmitting board), and then I'll move on to board 2 (Receiving board).

Board 1 (Transmitter) Main.c

//In the define section...

#define MY_TAG (U32)6 //I just used this tag arbitrarily even though it is intended for the payload.

//I put the ISR just above the main function, but anywhere should do.
//Check out the subsystem-->CAN Protocol page of this Wiki for more info on the structure of the CANBUS message.

ISR(INT4_vect)
{
     can_format board_2_LED_set;
          board_2_LED_set.id=format_message_id(7,0,ACS_STATUS_TEMP_ID);
          board_2_LED_set.dlc=1;
          board_2_LED_set.ide=1;
          board_2_LED_set.packet_data[0]=0xFF;
     xQueueSend(can_transmit_queue,(void*)&board_2_LED_set,(portTickType)portMAX_DELAY);
}

portSHORT main(void) {

    //I'm not going to put in all the queues and handles here, just enable/uncomment all the ones you see in Cass' original port code. The 
    //ones that follow here are the tasks you need.
    xTaskCreate(canBusTransmit, (signed portCHAR *)"CTTH", 300,NULL,4,NULL);
    xTaskCreate(canBusTimeout, (signed portCHAR *)"CTH", 300,NULL,5,NULL);
    vTaskStartScheduler();
    return 0;

}

Board 2 (Receiver) Main.c

//In the define section...

#define MY_TAG (U32)7 //I just used this tag arbitrarily even though it is intended for the broadcast.

//In the CANBUS Tasks section for "case ACS_STATUS_TEMP_ID:"
     PORT A=receive_command.packet_data[0]; //sets LEDs (PORTA) to transferred data (in this case 0xFF)
     break;



portSHORT main(void)
{
     //I'm not going to put in all the queues,handles, and init functions here, just enable/uncomment all the ones you see in Cass' original port code.              
     //The ones you see here are the tasks you need.
     xTaskCreate(canBusHandler, (signed portCHAR *)"CMH", 300, NULL, 3, NULL);
     xTaskCreate(canBusReceive, (signed portCHAR *)"CRH", 300, NULL,6,NULL);
     vTaskStartScheduler();
     return 0;
}

Now that you've got the code compiled and downloaded we've got to get a bit crude to see what's happening. The code I've modified is intended for the satellite itself so it makes use of PORTA for things other than flashing LEDs. Because the original code is modifying PORTA frequently, we have to set a breakpoint just after we change the status of the dev board LEDs in order to see our transferred data. The following figures show the breakpoints in the receiver main.c file before and after the interrupt is triggered. The IO view shows that PORTA does indeed update to the 0xFF value that we sent from the transmitter.

Just before interrupt
Just after interrupt

SPI Comms with 2 dev boards

Ok. Now that you're a bit more familiar with the dev boards, lets get right into SPI communication. Onboard ECOSat, SPI is used by the AT90CAN subsystem processors to get temperature data from ADT7320 temperature sensors.

For the Transmitting Board:

void vSPI_TX(void *pvParameters)
{
	U8 ch=0x55;  //Data byte to be transmitted.
	DDRB|= (1<<DDB0); //Configure PORTB as an output.
	for(;;)
	{
	     Spi_enable_ss();  //Drive Slave Select low to initiate communication.
	     spi_putchar(ch);  //Get data byte ready for transmission.
	     Spi_disable_ss();  //Drive Slave Select high to end communication.
	     vTaskDelay(1000);  //Transmit once per second.
	}
}

portSHORT main(void) {
	spi_init(SPI_MASTER|SPI_MSB_FIRST|SPI_DATA_MODE_3|SPI_CLKIO_BY_128);  /*Initialize SPI with required parameters.
        I used mode 3 since that is the temp sensor mode */
	xTaskCreate(vSPI_TX,(signed portCHAR *)"Transmit_SPI",200,NULL,1,NULL);
	vTaskStartScheduler();
	return 0;
}

For the Receiving board:

void vSPI_Rx(void *pvParameters)
{
	U8 rx_byte; // Data byte to be received.
	for(;;)
        {
	     while(!(SPSR&(1<<SPIF))); //While data isn't currently being received.
	     rx_byte=SPDR; // Read data from data register.
	}
}

portSHORT main(void) {
	spi_init(SPI_SLAVE|SPI_MSB_FIRST|SPI_DATA_MODE_3|SPI_CLKIO_BY_128); //Initialize SPI configuration. Same for both boards.
	(DDRB &= 0xF8); // Set all but first 3 bits to output
	SPCR=(1<<SPE); //Set SPE to enable SPI communication.
	xTaskCreate(vSPI_Rx,(signed portCHAR *)"RxSPI",200,NULL,1,NULL);
	vTaskStartScheduler();
	return 0;
}

Alright. Now that the boards are running the code, here's a snapshot of a transmission cycle. You can see that the Slave Select (SS) gets pulled low, initiating the transfer. The next clock pulses then shift the value 0x55 out on the MOSI and in on the MISO. Since I haven't written anything to the receiver's data register, it simply sends back what the transmitter has sent it.

Analyzer Output of SPI communication

Now, this code isn't working quite perfectly at the moment. I'm still getting the slave select pulled low from time to time when not intended. Please try this out and let me know if you can get nice clean waveforms. I produced this demo with the SRACS-RTOS original port. That aside, it should give you a good start to SPI communication.

I2C/TWI

Little bit of a departure here. Not going to use the DVK90CAN for this one, but this example will get you familiar with one of the subsystems onboard ECOSat. This experiment will illustrate the use of I2C/TWI on one of the Payload boards. In this case, the AT90CAN will be used to get temperature readings from the L3GD20. Had a bit of a time trying to make sense of the readings because the register is not very well explained on the datasheet. The important thing to note is that once the code is running, the temp register will output data that shows the change in temp when heat is applied to the IC. I apologize for my lacklustre phone pic, but below is the setup I used for programming the Payload board. At top right is the debugger JTAG connection with red power cable to centre of board. The two purple wires are SDA and SCL testpoints for TWI communications.

Payload board setup


The power is connected as below with 5V at pins 4 and 8, and ground at pins 3 and 7 respectively. Only one of each need to be connected. Both connections are made with black cables in the setup photograph.

Electrical shematic for Payload power

Here's the only code I needed to modify in the Payload Atmel project (can be found on the SVN).

 In main.c:

portSHORT main(void) {
	i2c_init(); //function defined in i2cmaster.h and twimaster.c. 
	configure_GYRO(); //defined in IMU.c and IMU.h. 
	xTaskCreate(demo_IMU_GYRO, (signed portCHAR *)"Dm",300,NULL, 1, NULL);	//Simple demo task.
	vTaskStartScheduler();
	
	return 0;
}

In IMU.c:

void demo_IMU_GYRO(){
	U8 temp=NULL;
	
	for(;;)
	{
		temp=read_Register(L3GD20_GYRO,L3GD20_OUT_TEMP); //Calls read_Register which is defined earlier in IMU.c
		vTaskDelay(1000); //One second delay between temp reads.
	}
}
 


Below is a clip of an entire TWI read sequence. The nice thing about the library protocols included with Saleae's Logic Analyzer software is that it breaks down the transmission into readable chunks.

Analyzer Output of Gyro Temp Read via TWI

In the capture, the green dot indicates the start of transmission condition. This is followed by a write to the device address, 0xD4. Once the device acknowledges with a logic low, the transmitter sends a byte addressing the OUT_TEMP register, 0x26, of the gyro.


First portion of TWI Read sequence

The next part of the transmission is a repeated start by the AT90CAN followed by a read from gyro read register, 0xD5. If all is good, the gyro chip then sends the read register data which is 0x12 in this case. After getting all the data it needs, the master sends its own NAK followed by a stop condition indicating the end of transmission.

Second portion of TWI Read sequence

Useful Links

Some of these links are dispersed throughout this Wiki, but I've included them here again for ease of navigation.

Atmel Studio 6.2 Download

FreeRTOS Viewer Extension

Youtube Tutorials

Visual Studio Community 2013

DVK90CAN1 Hardware User Guide

AT90CAN128 User Manual

FreeRTOS Download

ADT7320 Temperature Sensor Data Sheet

SPI

Saleae Logic Analyzer

I2C/TWI

L3GD20 Gyro IC