Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Getting video stream from USB web-camera on Arduino Due - Part 3: Watchdog timer and TFT monitor

4.71/5 (6 votes)
10 Jul 2015CPOL7 min read 24.3K   359  
Watchdog timer and TFT monitor

Introduction

Part 2 is here.

This article is mainly about TFT monitor. I'll initialize it, create necessary functions to output pixel information and write some test code. But before I start with it, let's run example from previous article and observe its output to RS-232 monitor program for some time.

Watchdog timer

The example code does so far:

1. First it runs test code for Print functions (PrintDEC, PrintBIN and PrintHEX) - this is from part 2.

2. Then every second it outputs "Hello world" - this is from part 1.

Let's observe the output:

Image 1

As you can see for some reason testing functions are executed once again, which can mean one thing only - Arduino Due restarts. If you run the example code long enough you'll find out that restarts happen periodically for the whole duration the Arduino Due is on.

This behaviour is not an error, this is a feature that is caused by Watchdog timer [1, p.267]. One might ask why do we need such feature? There are many reason for that, but imagine that you have a medical equipment controlled by a microcontroller that supports a patient life, it cannot stop because stoppage leads to patient death. And as with all digital devices in some unforeseen conditions it can hang up. That is when restart can be useful.

Watchdog restarts the system when it finished count down to zero. To prevent restart a program should either entirely switch it off or re-load watchdog timer value periodically and before it can reach zero.

No initialization code is needed as Watchdog works by default and will restart Arduino Due in 16 seconds. I will not switch it off, I rather re-load its value every second and we already have a place for it in the code where LED lights are toggled:

C++
void SysTick_Handler(void)
{
    uint32_t Pin_LEDRX;
    uint32_t Pin_LEDTX;
    
    SysTickCounter ++;
    if(1000 == SysTickCounter)
    {
        SysTickCounter = 0;
        
        Pin_LEDRX = 1 << (PIO_PC30_IDX & 0x1Fu);
        Pin_LEDTX = 1 << (PIO_PA21_IDX & 0x1Fu);
        if(PIOC->PIO_PDSR & Pin_LEDRX)
        {
            PIOC->PIO_CODR = Pin_LEDRX;    //Lights on
            PIOA->PIO_CODR = Pin_LEDTX;
        }
        else
        {
            PIOC->PIO_SODR = Pin_LEDRX;    //Lights off
            PIOA->PIO_SODR = Pin_LEDTX;
        }
        
        WDT->WDT_CR = WDT->WDT_CR | WDT_CR_KEY_PASSWD | WDT_CR_WDRSTT;    //Re-load watchdog
        PrintStr("Hello World\r\n");
    }
}

To re-load watchdog timer value bit WDRSTT is written in control register (WDT_CR) [1, p.268]. Build the project, upload to Arduino Due and observe that no restart happens anymore.

TFT monitor

As you read in part 1, my TFT monitor is a shield with resolution 320x240. It is based on SSD1289 controller. Thus if you really want to learn how to initialize it you can try to understand its initialization sequence from SSD1289 documentation. I rather will focus on pixel output functions, for that I take somebody's code on some 8-bit AVR forum and change it to work with 32-bit system. It is also useful to have shield's pin map to see correspondence with Arduino Due pins:

Image 2

It is seen from this picture that data pins are scattered across all four PIOs (Parallel input/output controllers [1, p.641]) which means that to output 16-bit pixel data several shift and output operations must be performed. This is bad as it would be really nice to have one-action (instead of several shift and output operations) output in real-time USB operation.

Let's add SSD1289.h and SSD1289.c files to the project. Add

C++
#include "SSD1289.h"

into SSD1289.c and into main file. Then open SSD1289.h where we place some macros. First macros are for command pins:

C++
#define LCD_CLR_CS()            PIOC->PIO_CODR = PIO_PC8
#define LCD_SET_CS()            PIOC->PIO_SODR = PIO_PC8

#define LCD_CLR_RS()            PIOC->PIO_CODR = PIO_PC6
#define LCD_SET_RS()            PIOC->PIO_SODR = PIO_PC6

#define LCD_CLR_WR()            PIOC->PIO_CODR = PIO_PC7
#define LCD_SET_WR()            PIOC->PIO_SODR = PIO_PC7

#define LCD_CLR_RST()           PIOC->PIO_CODR = PIO_PC9
#define LCD_SET_RST()           PIOC->PIO_SODR = PIO_PC9

CS means chip select, we can communicate to the monitor only when this pin is low.

RS defines in which mode monitor is, in data or command mode. Thus we can send either data or command.

WR activates command or data transfer into monitor.

RST means reset.

The sequence to output data or command to monitor is following: 16-bit data or command (depending on RS pin) is set on monitor pins, then WR must be cleared and then set back.

Next is the macro for 16-bit data or command output:

C++
#define LCD_SET_DB(x)  PIOA->PIO_ODSR =  ((x & PIO_PA6) << 1)\
                                       | ((x & (PIO_PA9 | PIO_PA10)) << 5);\
                       PIOB->PIO_ODSR =  ((x & PIO_PB8) << 18);\
                       PIOC->PIO_ODSR =  ((x & PIO_PC4) >> 3)\
                                       | ((x & PIO_PC3) >> 1)\
                                       | ((x & PIO_PC2) << 1)\
                                       | ((x & PIO_PC1) << 3)\
                                       | ((x & PIO_PC0) << 5);\
                       PIOD->PIO_ODSR =  ((x & (PIO_PA11 | PIO_PA12 | PIO_PA13 | PIO_PA14)) >> 11)\
                                       | ((x & PIO_PA15) >> 9)\
                                       | ((x & PIO_PD7) << 2) \
                                       | ((x & PIO_PD5) << 5);\

As you can see there are 4 output operations, 12 shift operations and some bitwise OR operations. Not very fast.

I already mentioned that output scattered across all four PIOs thus to make the above macro work, corresponding pins must be initialized in similar manner as we did in part 1 for LED pins. Let's quickly return to system_sam3xa.c file (see part 1) and insert following initialization code right after LED pins initialization and before SysTick initialization:

C++
/* TFT connection pins initialization */
uint32_t ul_pin_pos = PIO_PA7 | PIO_PA14 | PIO_PA15;
PIOA->PIO_IDR = ul_pin_pos;
PIOA->PIO_MDDR = ul_pin_pos;
PIOA->PIO_SODR = ul_pin_pos;
PIOA->PIO_OER = ul_pin_pos;
PIOA->PIO_PER = ul_pin_pos;
        
ul_pin_pos = PIO_PB26;
PIOB->PIO_IDR = ul_pin_pos;
PIOB->PIO_MDDR = ul_pin_pos;
PIOB->PIO_SODR = ul_pin_pos;
PIOB->PIO_OER = ul_pin_pos;
PIOB->PIO_PER = ul_pin_pos;
       
ul_pin_pos = PIO_PC1 | PIO_PC2 | PIO_PC3 | PIO_PC4 | PIO_PC5 | PIO_PC6 | PIO_PC7 | PIO_PC8 | PIO_PC9;
PIOC->PIO_IDR = ul_pin_pos;    
PIOC->PIO_MDDR = ul_pin_pos;
PIOC->PIO_SODR = ul_pin_pos;
PIOC->PIO_OER = ul_pin_pos;
PIOC->PIO_PER = ul_pin_pos;
       
ul_pin_pos = PIO_PD0 | PIO_PD1 | PIO_PD2 | PIO_PD3 | PIO_PD6 | PIO_PD9 | PIO_PD10;
PIOD->PIO_IDR = ul_pin_pos;
PIOD->PIO_MDDR = ul_pin_pos;
PIOD->PIO_SODR = ul_pin_pos;
PIOD->PIO_OER = ul_pin_pos;
PIOD->PIO_PER = ul_pin_pos;
     
/* Making synchronous write working for connected pins */
PIOA->PIO_OWER = PIO_PA7 | PIO_PA14 | PIO_PA15;
PIOB->PIO_OWER = PIO_PB26;
PIOC->PIO_OWER = PIO_PC1 | PIO_PC2 | PIO_PC3 | PIO_PC4 | PIO_PC5;
PIOD->PIO_OWER = PIO_PD0 | PIO_PD1 | PIO_PD2 | PIO_PD3 | PIO_PD6 | PIO_PD9 | PIO_PD10;

Pin initialization is the same as in LED initialization, the same registers are used and I think no explanation is needed. On the other hand there is "Making synchronous write working for connected pins" part at the bottom that needs some explanation.

Usually in ARM if you need to output high level, you write 1 into "set" register. If you need to output low level, you write 1 into "clear" register. So you always write 1 no matter if it is low or high level. This is very clever technique as you don't need to obtain previous register value to mask-out those bits you don't want to change. That is what you do for example in AVR microcontroller. Set registers and clear registers have the same names with only one difference in letter S or C. But for us and TFT we don't want to output first all high levels and then all low levels, we want everything at once - that is called "synchronous write". It is allowed in PIO_OWER registers and after that we use PIO_ODSR register instead of PIO_SODR (set) and PIO_CODR (clear) registers.

Save system_sam3xa.c file and return to SSD1289.h file.

Next I'll define macro for 16-bit pixel composition:

C++
#define RGB(red, green, blue)  ((uint16_t)(((red >> 3)<<11) | ((green >> 2)<<5) | (blue >> 3)))

To create a pixel three color components need to be specified. Please note that it discards least bits on color components as there is no way fitting 8+8+8 in 16-bit pixel: 3 bits of red & blue and 2 bits of green are shifted away. Usage example:

C++
RGB(100, 25, 0);

And last I declare all necessary functions:

C++
void LCD_WrCmd(uint16_t);
void LCD_WrDat(uint16_t);
void LCD_WaitMs(uint32_t ms);
void LCD_Init(void);
void LCD_SetCursor(uint16_t x, uint16_t y);
void LCD_SetArea(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);

Now we are ready to move to SSD1289.c file and implement those functions. LCD_WrCmd is used to send command to monitor, note how it uses RS bit:

C++
void LCD_WrCmd(uint16_t cmd)
{
    LCD_CLR_CS();                //Enable LCD communication
    LCD_CLR_RS();

    LCD_SET_DB(cmd);             //Set command on LCD pins
    
    LCD_CLR_WR();                //Transfer command in
    LCD_SET_WR();

    LCD_SET_CS();                //Disable LCD communication
    LCD_SET_RS();                //Back to data mode
}

LCD_WrDat is used to send data to monitor. Note that the difference between this and previous is that this one does not use RS bit as data mode is default:

C++
void LCD_WrDat(uint16_t val)
{
    LCD_CLR_CS();                //Enable LCD communication

    LCD_SET_DB(val);             //Set data on LCD pins
    
    LCD_CLR_WR();                //Transfer data in
    LCD_SET_WR();

    LCD_SET_CS();                //Disable LCD communication
}

LCD_WaitMs is needed by next LCD_Init fuction only. It makes delays in milliseconds to give monitor necessary time to execute given command. The fact that Arduino Due is running at 84MHz is used in the implementation. 84 000 ticks in 1 ms, NOP operation and loop are 5 ticks therefore 84 000/5 = 16800. Pauses here are approximate as I don't need any precision here:

void LCD_WaitMs(uint32_t ms)
{
    uint32_t i;

    while (ms-- > 0)
    {
        for (i = 0; i < 16800; ++i)
            __asm volatile("NOP");
    }
}

LCD_Init is initialization function that also resets the monitor. It sends all necessary commands with data to make monitor working. I will not explain those commands as I don't understand some of them myself and besides that is out of scope of this tutorial. Please see it in source code, there are some comments should you want to play with it.

LCD_SetCursor is important function. I will use it to set cursor (a pixel) to the beginning once I detect video frame change. New frame will always start from the beginning. This function just sends appropriate commands to perform cursor position change operation:

C++
void LCD_SetCursor(uint16_t x, uint16_t y)
{
    LCD_WrCmd(0x4E);    //Sets GDDRAM X address counter
    LCD_WrDat(x);
    
    LCD_WrCmd(0x4F);    //Sets GDDRAM Y address counter
    LCD_WrDat(y);
    
    LCD_WrCmd(0x22);    //Applies above set values
}

The last function is LCD_SetArea. It is used to specify view area where video frames are output. Once area is specified pixels can be written in one-by-one, monitor will increment to next position automatically. Once last position is reached, next pixel will be written into first position and so on.

C++
void LCD_SetArea(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    LCD_WrCmd(0x44); LCD_WrDat((x2 << 8) | x1);        //Horizontal start and end positions
    LCD_WrCmd(0x45); LCD_WrDat(y1);                    //Vertical start position
    LCD_WrCmd(0x46); LCD_WrDat(y2);                    //Vertical end position
    
    LCD_SetCursor(x1, y1);
}

Testing TFT monitor

To test the above functions I'll write test code that periodically fills the whole monitor rectangle with various solid colors. There will be an array with several pixel color information, the test code with iterate through it to pick certain color up and use it in filling. Once the array end is reached it starts all over again. Colors array looks like this:

#define WHITE        RGB(0xFF, 0xFF, 0xFF)
#define RED          RGB(0xFF, 0x00, 0x00)
#define GREEN        RGB(0x00, 0xFF, 0x00)
#define BLUE         RGB(0x00, 0x00, 0xFF)
#define BLACK        RGB(0x00, 0x00, 0x00)
uint16_t arColors[5] = {WHITE, RED, GREEN, BLUE, BLACK};

Final main file code:

#include "sam.h"
#include "UART.h"
#include "SSD1289.h"

#define WHITE        RGB(0xFF, 0xFF, 0xFF)
#define RED          RGB(0xFF, 0x00, 0x00)
#define GREEN        RGB(0x00, 0xFF, 0x00)
#define BLUE         RGB(0x00, 0x00, 0xFF)
#define BLACK        RGB(0x00, 0x00, 0x00)
uint16_t arColors[5] = {WHITE, RED, GREEN, BLUE, BLACK};

uint8_t ColorIndex;    
uint32_t SysTickCounter;

void ShowColor(void)
{
    for(uint32_t i=0; i<76800; i++)
    {
        LCD_SET_DB(arColors[ColorIndex]);
        LCD_CLR_WR();
        LCD_SET_WR();
    }
    
    ColorIndex ++;
    if(ColorIndex == 5)
        ColorIndex = 0;
}

void ToggleLEDs(void)
{
    uint32_t Pin_LEDRX = 1 << (PIO_PC30_IDX & 0x1Fu);
    uint32_t Pin_LEDTX = 1 << (PIO_PA21_IDX & 0x1Fu);
        
    if(PIOC->PIO_PDSR & Pin_LEDRX)
    {
        PIOC->PIO_CODR = Pin_LEDRX;    //Lights on
        PIOA->PIO_CODR = Pin_LEDTX;
    }
    else
    {
        PIOC->PIO_SODR = Pin_LEDRX;    //Lights off
        PIOA->PIO_SODR = Pin_LEDTX;
    }
}

void SysTick_Handler(void)
{
    SysTickCounter ++;
    if(1000 == SysTickCounter)
    {
        WDT->WDT_CR = WDT->WDT_CR | WDT_CR_KEY_PASSWD | WDT_CR_WDRSTT;    //Re-load watchdog
        
        SysTickCounter = 0;
        ToggleLEDs();
        ShowColor();
    }
}

int main(void)
{
    SystemInit();
    UART_Init();    
    LCD_Init();
    
    ColorIndex = 0;                   //Current index in arColor array
    LCD_SetArea(0, 0, 239, 319);      //Setting working area
    LCD_CLR_CS();                     //Enable LCD
    LCD_SET_RS();                     //Data write mode
    
    while(1);
}

Note that I removed test code from part 2 (printing functions) and extracted ToggleLEDs function. Build, upload and observe. It should look like my in this video.

 Conclusion

This was the last preparation article. All necessary tools are written and we are ready to deal with USB. You may want to read USB 2.0 specification a bit as there is no way I will be able to explain USB in full although I will attempt to give my short version of it.

Source code is here.

Part 4 is here.

UPDATE 10-07-2015: Reloaded source code without Debug folder.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)