Recent

Author Topic: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers (SOLVED)  (Read 10268 times)

mig-31

  • Sr. Member
  • ****
  • Posts: 259
I have development board with Atmel AT91SAM9G20 ARM CPU with Linux OS. This CPU has Parallel Input/Output controller with 3 lines PIOA, PIOB and PIOC. Each lines have 32 I/Os.
PIOA maps into address $FFFFF400, PIOB  - $FFFFF600 and PIOC - $FFFFF800 and amout of memory for each PIOA, PIOB and PIOC registers is 512 bytes. There is a code rewrited from C example into FreePascal.
Code: [Select]
   program led_io;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,SysUtils,BaseUnix,crt,errors,strutils
  { you can add units after this };
const
  //map consts
  PAGE_SIZE=4096;
  MASK=PAGE_SIZE-1;
  //BASE REGISTER ADDRESS OF PIOB
  //calculating page number wtih page size 4096 = $1000 PIOB_BASE= $FFFFF600 div $1000
  PIOA_BASE=$FFFFF400;
  ////BASE REGISTER ADDRESS OF PIOB
  //SHIFT CONTROL REGISTERS ADDRESS
  PIOA_PER=PIOA_BASE+$0000;
  PIOA_OER=PIOA_BASE+$0010;
  PIOA_IFDR=PIOA_BASE+$0024;
  PIOA_SODR=PIOA_BASE+$0030;
  PIOA_CODR=PIOA_BASE+$0034;
  PIOA_IDR=PIOA_BASE+$0044;
  PIOA_PSR=PIOA_BASE+$0008;
  //BASE REGISTER ADDRESS OF PIOB
  //calculating page number wtih page size 4096 = $1000 PIOB_BASE= $FFFFF600 div $1000
  PIOB_BASE=$FFFFF600;
  ////BASE REGISTER ADDRESS OF PIOB
  //SHIFT CONTROL REGISTERS ADDRESS
  PIOB_PER=PIOB_BASE+$0000;
  PIOB_OER=PIOB_BASE+$0010;
  PIOB_IFDR=PIOB_BASE+$0024;
  PIOB_SODR=PIOB_BASE+$0030;
  PIOB_CODR=PIOB_BASE+$0034;
  PIOB_IDR=PIOB_BASE+$0044;
  PIOB_PSR=PIOB_BASE+$0008;
  //calculating address due of page size 4096 = $1000 PIOB_BASE= $FFFFF600 div $1000
  PIOC_BASE=$FFFFF800;
  //SHIFT CONTROL REGISTERS ADDRESS
  PIOC_PER=PIOC_BASE+$0000;
  PIOC_OER=PIOC_BASE+$0010;
  PIOC_IFDR=PIOC_BASE+$0024;
  PIOC_SODR=PIOC_BASE+$0030;
  PIOC_CODR=PIOC_BASE+$0034;
  PIOC_IDR=PIOC_BASE+$0044;

  PIOC_PSR=PIOC_BASE+$0008;
  //
  PIOA_A00=$FFFFFFFF;
  //set 1 on PB27 on PIOB controller registers
  PIOB_B27=cardinal(1 shl 15);
  //set 1 on PC00 on PIOC controller registers
  PIOC_C00=cardinal(1 shl 31);
  //set 1 on PC00 on PIOC controller registers
  PIOC_C01=cardinal(1 shl 32);
var
  fd:cint;
  map_base:Pointer;
  i,blink_count,blink_delay:integer;
begin
  clrscr;
  //read data to setup blinking
  write('Please enter LED blink count: ');
  readln(blink_count);
  write('Please enter LED blink delay ms: ');
  readln(blink_delay);

  //get file descriptor
  fd:=fpOpen('/dev/mem',O_RdWr or O_SYNC);
  if fd=-1 then begin
    writeln('Cant get file description');
    Halt(1);
  end;
  writeln('/dev/mem has opened');

  //map 1 page memory 4kB
  map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE div 4096);
  if longint(map_base^)=-1 then begin
    writeln('Memory map error. Error: ',strerror(fpGeterrno));
    Halt(2);
  end;
  writeln('Memory mapped on address $',hexstr(map_base));

  // setting registers to blink on PB29 row A PIN 31
  //enable registr
  Longword((map_base+(PIOB_PER AND MASK))^):=PIOB_B27;
  //disable input filtr
  Longword((map_base+(PIOB_IFDR AND MASK))^):=PIOB_B27;
  //disable interrupt
  Longword((map_base+(PIOB_IDR AND MASK))^):=PIOB_B27;
  //enable output
  Longword((map_base+(PIOB_OER AND MASK))^):=PIOB_B27;

  // setting registers to blink on PC00 and PC01 row A PIN 31
  //enable registr
  Longword((map_base+(PIOC_PER AND MASK))^):=(PIOC_C00 OR PIOC_C01);
  //disable input filtr
  Longword((map_base+(PIOC_IFDR AND MASK))^):=(PIOC_C00 OR PIOC_C01);
  //disable interrupt
  Longword((map_base+(PIOC_IDR AND MASK))^):=(PIOC_C00 OR PIOC_C01);
  //enable output
  Longword((map_base+(PIOC_OER AND MASK))^):=(PIOC_C00 OR PIOC_C01);

  //blinking
  i:=0;
  write('Blinking: ');
  repeat
     write('.');
    //PB27 row A pin31
    //set output data register = blink
    Longword((map_base+(PIOA_SODR AND MASK))^):=PIOA_A00;
    sleep(blink_delay);
    //clear output data register = no blink
    Longword((map_base+(PIOA_CODR AND MASK))^):=PIOA_A00;
    sleep(blink_delay);

    //PB27 row A pin31
    //set output data register = blink
    Longword((map_base+(PIOC_CODR AND MASK))^):=PIOC_C00;
    Longword((map_base+(PIOB_SODR AND MASK))^):=PIOB_B27;
    sleep(blink_delay);
    //clear output data register = no blink
    Longword((map_base+(PIOB_CODR AND MASK))^):=PIOB_B27;
    Longword((map_base+(PIOC_SODR AND MASK))^):=PIOC_C01;
    sleep(blink_delay);
    //PC00 and PC01
    //set output data register = blink
    Longword((map_base+(PIOC_CODR AND MASK))^):=PIOC_C01;
    Longword((map_base+(PIOC_SODR AND MASK))^):=PIOC_C00;
    sleep(blink_delay);
    inc(i);
  until (i>blink_count) or (keypressed);
  writeln(' done');
  Longword((map_base+(PIOC_CODR AND MASK))^):=(PIOC_C00 OR PIOC_C01);
  Longword((map_base+(PIOB_CODR AND MASK))^):=PIOB_B27;

  //close file descriptor
  fpClose(fd);
  //unmap memory
  if fpMUnMap(map_base, PAGE_size)<>0 then Halt(fpgeterrno);

end.                           

In Linux memory maps with page size 4096 bytes ($1000), which means that I maps addresses form FFFFF000 to FFFFF1000.
But this code works only for PIOB, when the code, which controlls PIOC are commented ({},//).
If I writes value into PIOC registers, CPU is stucked.  The same situation is for PIOA This means that I can write into memory address only for PIOB: from $FFFFF600 to $FFFFF799.

In case of C example all work:
Code: [Select]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
 
#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

#define PIOB_BASE 0xfffff600UL
#define PIOC_BASE 0xfffff800UL

#define PIO_C00 ((unsigned long) 1 << 0)
#define PIO_C01 ((unsigned long) 1 << 1)
#define PIO_B27 ((unsigned long) 1 << 27)

#define PIOB_PER PIOB_BASE + 0x0000
#define PIOB_OER PIOB_BASE + 0x0010
#define PIOB_IFDR PIOB_BASE + 0x0024
#define PIOB_SODR PIOB_BASE + 0x0030
#define PIOB_CODR PIOB_BASE + 0x0034
#define PIOB_IDR PIOB_BASE + 0x0044


#define PIOC_PER PIOC_BASE + 0x0000
#define PIOC_OER PIOC_BASE + 0x0010
#define PIOC_IFDR PIOC_BASE + 0x0024
#define PIOC_SODR PIOC_BASE + 0x0030
#define PIOC_CODR PIOC_BASE + 0x0034
#define PIOC_IDR PIOC_BASE + 0x0044

int main(int argc, char **argv) {
    int fd, i, repetitions;
    void *map_base;
    if (argc > 1)
    {
    repetitions =(atoi(argv[1]));
    }
    else
    {
    printf("\nMissing argument\n");
         printf("Usage: led REPETITIONS\n");
printf("REPETITIONS: int, that specifies number of repetitions of blinking led cycles\n\n\n");
         exit(1);
    }


    if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
    {
    printf("Couldn't open /dev/mem.\n");
exit(1);
    }
    printf("/dev/mem opened.\n");
    fflush(stdout);
   
    /* Map one page */
    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PIOB_BASE  & ~MAP_MASK);
    if(map_base == (void *) -1)
    {
    printf("Couldn't get Map-Address.\n");
exit(1);
}
    printf("Memory mapped at address %p.\n", map_base);
    fflush(stdout);
   
    // Configure the registers

    *((unsigned long *) (map_base + (PIOB_PER & MAP_MASK))) = PIO_B27; //enable io
    *((unsigned long *) (map_base + (PIOB_IFDR & MAP_MASK))) = PIO_B27; //disable input filter
    *((unsigned long *) (map_base + (PIOB_IDR & MAP_MASK))) = PIO_B27;  // disable interrupt
    *((unsigned long *) (map_base + (PIOB_OER & MAP_MASK))) = PIO_B27;  // Enable output

    *((unsigned long *) (map_base + (PIOC_PER & MAP_MASK))) = PIO_C00 | PIO_C01;
    *((unsigned long *) (map_base + (PIOC_IFDR & MAP_MASK))) = PIO_C00 | PIO_C01;
    *((unsigned long *) (map_base + (PIOC_IDR & MAP_MASK))) = PIO_C00 | PIO_C01;
    *((unsigned long *) (map_base + (PIOC_OER & MAP_MASK))) = PIO_C00 | PIO_C01;

    i = 0;
    while(i < repetitions)
    {    
*((unsigned long *) (map_base + (PIOC_CODR & MAP_MASK))) = PIO_C00;
*((unsigned long *) (map_base + (PIOB_SODR & MAP_MASK))) = PIO_B27;
sleep(1);
*((unsigned long *) (map_base + (PIOB_CODR & MAP_MASK))) = PIO_B27; 
*((unsigned long *) (map_base + (PIOC_SODR & MAP_MASK))) = PIO_C01;
sleep(1);
*((unsigned long *) (map_base + (PIOC_CODR & MAP_MASK))) = PIO_C01;
*((unsigned long *) (map_base + (PIOC_SODR & MAP_MASK))) = PIO_C00;
sleep(1);
i++;
    }
    *((unsigned long *) (map_base + (PIOB_CODR & MAP_MASK))) = PIO_B27; 
    *((unsigned long *) (map_base + (PIOC_CODR & MAP_MASK))) = PIO_C00 | PIO_C01;
    if(munmap(map_base, MAP_SIZE) == -1) exit(1);
    close(fd);
    return 0;
}

I don't know why code in FreePascal doesn't work.
Thank you for any idea.
« Last Edit: September 09, 2013, 05:04:45 pm by mig-31 »
Lazarus 2.0.2 - CentOS 7.x, Mageia 7.1

Laksen

  • Hero Member
  • *****
  • Posts: 617
    • J-Software
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #1 on: August 30, 2013, 06:15:14 pm »
map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE div 4096);
should be
map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE and (not longword(MASK));

if longint(map_base^)=-1 then begin
should be
if ptrint(map_base) = -1 then begin

I think that should at least make it work

mig-31

  • Sr. Member
  • ****
  • Posts: 259
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #2 on: August 31, 2013, 12:34:18 pm »
Thank you for the answer. But I'm afraid that the result will be the same, because PIOB_BASE div 4096 =  PIOB_BASE and (not longword(MASK).

I will try it and write the result here.
Lazarus 2.0.2 - CentOS 7.x, Mageia 7.1

Laksen

  • Hero Member
  • *****
  • Posts: 617
    • J-Software
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #3 on: August 31, 2013, 12:41:00 pm »
It's not. (PIOB_BASE div 4096)*4096 =  PIOB_BASE and (not longword(MASK) would however be correct.

mig-31

  • Sr. Member
  • ****
  • Posts: 259
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #4 on: September 02, 2013, 04:11:24 pm »
I  changed
Code: [Select]
    map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE div 4096);
     to
   map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE and (not longword(MASK));

It doesn't work. I get error invalid argument, which means that value of PIOB_BASE and (not longword(MASK) is not correct.

In case of page memory for 32-bit CPU first five letters in hex value is page number, other three letters is SHIFT.
I think, that PIOB_BASE div 4096 is correct and there is a page number $FFFFFPIOB_PER AND MASK is the shift to register address PIOB_PER = $600.

Writing to register of PIOB work correctly. But when I want to write to any address of PIOC the CPU stucks. Line of code bellow doesn't work.
Code: [Select]
  Longword((map_base+(PIOC_PER AND MASK))^):=(PIOC_C00 OR PIOC_C01);

Thank for any idea.
« Last Edit: September 02, 2013, 04:13:06 pm by mig-31 »
Lazarus 2.0.2 - CentOS 7.x, Mageia 7.1

Laksen

  • Hero Member
  • *****
  • Posts: 617
    • J-Software
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #5 on: September 02, 2013, 05:05:25 pm »
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PIOB_BASE  & ~MAP_MASK);
is the same as this: (there was a parenthesis missing)
map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE and (not longword(MASK)));

"PIOB_BASE  & ~MAP_MASK" is $FFFFF000
"PIOB_BASE and (not longword(MASK))" is $FFFFF000
"PIOB_BASE div 4096" is $000FFFFF

The last parameter for fpmmap is an offset, not a page number. This means it's added in address calculations

mig-31

  • Sr. Member
  • ****
  • Posts: 259
Re: Atmel AT91SAM9G20 ARM CPU stucks after writing into some CPU registers
« Reply #6 on: September 02, 2013, 08:30:41 pm »
Now I have a jam in my mind with address calculation.  %)

After one day discovering, I read that offset in fpmmap fuction is the page offset, not offset in bytes in C example.
This means that PIOB_BASE div 4096 is the page number. And now I don't know how exactly I should get the offset to registers, for example to PIOB_IFDR. In CPU specification offset from PIOB_BASE ($FFFFF600) is $0024. Is a part code bellow correct?
Code: [Select]
    const
      PIOB_BASE=$0600;
      PIOB_IFDR=(PIOB_BASE+$0024);
    begin
      map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIOB_BASE div 4096);
      Longword((map_base+PIOB_IFDR):=PIOB_B27;
    end.
« Last Edit: September 03, 2013, 08:12:09 pm by mig-31 »
Lazarus 2.0.2 - CentOS 7.x, Mageia 7.1

mig-31

  • Sr. Member
  • ****
  • Posts: 259
The problem solved.
The problem was, that I try to write bits (32-bit control register), which is already used by another process or OS.  %).

But there is important moment. FreePascal used new call of mmap function, where the offset is the page number offset (C use old implementation of mmap function with byte offset).

Example of using fpmmap do not exactly specify offset unit (byte/page number)!!!

There is information, who interested for ARM programming under Linux using FreePascal.

Board http://www.taskit.de/en/products/portuxg20/index.htm

Code example of LED blinking on PIOA, PIOB and PIOC

Code: [Select]
program led_2;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,SysUtils,BaseUnix,crt,errors,strutils
  { you can add units after this };
const
  //map consts
  PAGE_SIZE=4096;
  //BASE REGISTER ADDRESS OF PIOB
  //calculating page number wtih page size 4096 = $1000 PIO_BASE= $FFFFF400 div $1000
  PIO_BASE=$FFFFF;
  //SHIFT TO CONTROL REGISTERS ADDRESS PIOA
  PIOA_PER=$400;
  PIOA_OER=$410;
  PIOA_IFDR=$424;
  PIOA_SODR=$430;
  PIOA_CODR=$434;
  PIOA_IDR=$444;
  PIOA_PSR=$408;
  //SHIFT TO CONTROL REGISTERS ADDRESS PIOB
  PIOB_PER=$600;
  PIOB_OER=$610;
  PIOB_IFDR=$624;
  PIOB_SODR=$630;
  PIOB_CODR=$634;
  PIOB_IDR=$644;
  PIOB_PSR=$608;
  //SHIFT TO CONTROL REGISTERS ADDRESS PIOA
  PIOC_PER=$800;
  PIOC_OER=$810;
  PIOC_IFDR=$824;
  PIOC_SODR=$830;
  PIOC_CODR=$834;
  PIOC_IDR=$844;
  PIOC_PSR=$808;
  //set 1 on PA10 on PIOA controller registers
  PIOA_A00=1 shl 10;
  //set 1 on PB27 on PIOB controller registers
  PIOB_B27=1 shl 27;
  //set 1 on PC00 on PIOC controller registers
  PIOC_C00=1 shl 0;
  //set 1 on PC00 on PIOC controller registers
  PIOC_C01=1 shl 1;
var
  fd:cint;
  map_base, addr:^Longword;
  i,blink_count,blink_delay:integer;
begin
  clrscr;
  //read data to setup blinking
  write('Please enter LED blink count: ');
  readln(blink_count);
  write('Please enter LED blink delay ms: ');
  readln(blink_delay);

  //get file descriptor
  fd:=fpOpen('/dev/mem',O_RdWr or O_SYNC);
  if fd=-1 then begin
    writeln('Cant get file description');
    Halt(1);
  end;
  writeln('/dev/mem has opened');

  //map 1 page memory 4kB
  map_base:=fpmmap(Nil,PAGE_SIZE,PROT_READ or PROT_WRITE,MAP_SHARED ,fd, PIO_BASE);
  if ptrint(map_base^)=-1 then begin
    writeln('Memory map error. Error: ',strerror(fpGeterrno));
    Halt(2);
  end;
  writeln('Memory mapped on address $',hexstr(map_base));

  // setting registers to blink on PA10 row A PIN PIOA_A00
  //enable registr
  addr:=Pointer(Longword(map_base)+PIOA_PER);
  addr^:= PIOA_A00;
  //disable input filtr
  addr:=Pointer(Longword(map_base)+PIOA_IFDR);
  addr^:=  PIOA_A00;
  //disable interrupt
  addr:=Pointer(Longword(map_base)+PIOA_IDR);
  addr^:=  PIOA_A00;
  //enable output
  addr:=Pointer(Longword(map_base)+PIOA_OER);
  addr^:=  PIOA_A00;


  // setting registers to blink on PB29 row A PIN 31
  //enable registr
  addr:=Pointer(Longword(map_base)+PIOB_PER);
  addr^:=PIOB_B27;
  //disable input filtr
  addr:=Pointer(Longword(map_base)+PIOB_IFDR);
  addr^:=PIOB_B27;
  //disable interrupt
  addr:=Pointer(Longword(map_base)+PIOB_IDR);
  addr^:=PIOB_B27;
  //enable output
  addr:=Pointer(Longword(map_base)+PIOB_OER);
  addr^:=PIOB_B27;

  // setting registers to blink on PC00 and PC01 row A PIN 31
  //enable registr
  addr:=Pointer(Longword(map_base)+PIOC_PER);
  addr^:=PIOC_C00 OR PIOC_C01;
  //disable input filtr
  addr:=Pointer(Longword(map_base)+PIOC_IFDR);
  addr^:=PIOC_C00 OR PIOC_C01;
  //disable interrupt
  addr:=Pointer(Longword(map_base)+PIOC_IDR);
  addr^:=PIOC_C00 OR PIOC_C01;
  //enable output
  addr:=Pointer(Longword(map_base)+PIOC_OER);
  addr^:=PIOC_C00 OR PIOC_C01;

  //blinking

  i:=0;
  write('Blinking: ');
  repeat
    write('.');

    //PA23 row A pin 27
    addr:=Pointer(Longword(map_base)+PIOA_SODR);
    addr^:=PIOA_A00;

    //PB27 row A pin31
    addr:=Pointer(Longword(map_base)+PIOB_SODR);
    addr^:=PIOB_B27;

    addr:=Pointer(Longword(map_base)+PIOC_SODR);
    addr^:=PIOC_C00 OR PIOC_C01;

    sleep(blink_delay);

    addr:=Pointer(Longword(map_base)+PIOA_CODR);
    addr^:=PIOA_A00;
    //clear output data register = no blink
    addr:=Pointer(Longword(map_base)+PIOB_CODR);
    addr^:=PIOB_B27;

    addr:=Pointer(Longword(map_base)+PIOC_CODR);
    addr^:=PIOC_C00 OR PIOC_C01;

    sleep(blink_delay);
    inc(i);
  until (i>blink_count) or (keypressed);
  writeln(' done');

  //close file descriptor
  fpClose(fd);
  //unmap memory
  if fpMUnMap(map_base, PAGE_size)<>0 then Halt(fpgeterrno);
end.
Lazarus 2.0.2 - CentOS 7.x, Mageia 7.1