I have been measuring clock cycle count on the cortex m4 and would now like to do it on the cortex m7. The board I use is STM32F746ZG.
For the m4 everything worked with:
volatile unsigned int *DWT_CYCCNT;
volatile unsigned int *DWT_CONTROL;
volatile unsigned int *SCB_DEMCR;
void reset_cnt(){
DWT_CYCCNT = (volatile unsigned int *)0xE0001004; //address of the register
DWT_CONTROL = (volatile unsigned int *)0xE0001000; //address of the register
SCB_DEMCR = (volatile unsigned int *)0xE000EDFC; //address of the register
*SCB_DEMCR = *SCB_DEMCR | 0x01000000;
*DWT_CYCCNT = 0; // reset the counter
*DWT_CONTROL = 0;
}
void start_cnt(){
*DWT_CONTROL = *DWT_CONTROL | 0x00000001 ; // enable the counter
}
void stop_cnt(){
*DWT_CONTROL = *DWT_CONTROL & 0xFFFFFFFE ; // disable the counter
}
unsigned int getCycles(){
return *DWT_CYCCNT;
}
The problem is that the DWT_CTRL register isn't changed when I run on the m7 and remains 0x40000000 instead of changing to 0x40000001 so the cycle count is always zero. From what I have read in other posts it seems like you need to set the FP_LAR register to 0xC5ACCE55 to be able to change DWT_CTRL.
I added these defines (have tried both FP_LAR_PTR addresses below):
#define FP_LAR_PTR ((volatile unsigned int *) 0xe0000fb0) //according to reference
//#define FP_LAR_PTR ((volatile unsigned int *) 0xe0002fb0) //according to guy on the internet
// Lock Status Register lock status bit
#define DWT_LSR_SLK_Pos 1
#define DWT_LSR_SLK_Msk (1UL << DWT_LSR_SLK_Pos)
// Lock Status Register lock availability bit
#define DWT_LSR_SLI_Pos 0
#define DWT_LSR_SLI_Msk (1UL << DWT_LSR_SLI_Pos)
// Lock Access key, common for all
#define DWT_LAR_KEY 0xC5ACCE55
and this function:
void dwt_access_enable(unsigned int ena){
volatile unsigned int *LSR;
LSR = (volatile unsigned int *) 0xe0000fb4;
uint32_t lsr = *LSR;;
//printf("LSR: %.8X - SLI MASK: %.8X\n", lsr, DWT_LSR_SLI_Msk);
if ((lsr & DWT_LSR_SLI_Msk) != 0) {
if (ena) {
//printf("LSR: %.8X - SLKMASK: %.8X\n", lsr, DWT_LSR_SLK_Msk);
if ((lsr & DWT_LSR_SLK_Msk) != 0) { //locked: access need unlock
*FP_LAR_PTR = DWT_LAR_KEY;
printf("FP_LAR directly after change: 0x%.8X\n", *FP_LAR_PTR);
}
} else {
if ((lsr & DWT_LSR_SLK_Msk) == 0) { //unlocked
*FP_LAR_PTR = 0;
//printf("FP_LAR directly after change: 0x%.8X\n", *FP_LAR_PTR);
}
}
}
}
When I call the uncommented print I get 0xC5ACCE55 but when I printed it after the return of the function I get 0x00000000 and I have no idea why. Am I on the right track or is this completely wrong?
Edit: I think it also would be good to mention that I have tried without all the extra code in the function and only tried to change the LAR register.
BR
Gustav
Looking at the docs again, I'm now incredibly suspicious of a typo or copy-paste error in the ARM TRM. 0xe0000fb0 is given as the address of ITM_LAR, DWT_LAR and FP_LSR (and equivalently for *_LSR). Since all the other ITM registers are in page 0xe0000000, it looks an awful lot like whoever was responsible for that part of the Cortex-M7 documentation took the Cortex-M4 register definitions, added the new LAR and LSR to the ITM page, then copied them to the DWT and FPB pages updating the names but overlooking to update the addresses.
I'd bet my dinner that you're unwittingly unlocking ITM_LAR (or the real FP_LAR), and DWT_LAR is actually at 0xe0001fb0.
EDIT by dwelch
Somebody owes somebody a dinner.
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE000EDFC,0x01000000);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE0001000,0x40000001);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
PUT32(0xE0001FB0,0xC5ACCE55);
PUT32(0xE0001000,0x40000001);
hexstring(GET32(0xE0001FB4));
hexstring(GET32(0xE0001000));
hexstring(GET32(0xE0001004));
hexstring(GET32(0xE0001004));
output
00000000
00000000
00000000
00000000
00000003
40000000
00000000
00000000
00000003
40000000
00000000
00000000
00000001
40000001
0000774F
0000B311
The table in the TRM is funny looking and as the other documentation shows you add the 0xFB0 and 0xFB4 to the base, the rest of the DWT for the Cortex-M7 is 0xE0001xxx and indeed it appears that the LAR and LSR are ate 0xE0001FB0 and 0xE0001FB4.
I would advise against creating your own register definitions when they are defined as part of the CMSIS - to do so requires that both the documentation and your interpretation of it are correct. In this case it appears that the documentation is indeed incorrect, but that the CMSIS headers are correct. It is a lot easier to validate the CMSIS headers automatically than it is to verify the documentation is correct, so I would trust the CMSIS every time.
I am not sure what register FP_LAR might refer to, but your address assignment refers to ITM_LAR, but it seems more likely that you intended DWT_LAR which Cortex-M4 lacks.
Despite my advice to trust it, CMSIS 4.00 omits to define masks for DWT_LSR/SWT_LAR, but I believe they are identical to the corresponding ITM masks.
Note also that the LAR is a write-only register - any attempt to read it is meaningless.
Your code using CMSIS would be:
#include "core_cm7.h" // Applies to all Cortex-M7
void reset_cnt()
{
CoreDebug->DEMCR |= 0x01000000;
DWT->CYCCNT = 0; // reset the counter
DWT->CTRL = 0;
}
void start_cnt()
{
DWT->CTRL |= 0x00000001 ; // enable the counter
}
void stop_cnt()
{
DWT->CTRL &= 0xFFFFFFFE ; // disable the counter
}
unsigned int getCycles()
{
return DWT->CYCCNT ;
}
// Not defined in CMSIS 4.00 headers - check if defined
// to allow for possible correction in later versions
#if !defined DWT_LSR_Present_Msk
#define DWT_LSR_Present_Msk ITM_LSR_Present_Msk
#endif
#if !defined DWT_LSR_Access_Msk
#define DWT_LSR_Access_Msk ITM_LSR_Access_Msk
#endif
#define DWT_LAR_KEY 0xC5ACCE55
void dwt_access_enable( unsigned ena )
{
uint32_t lsr = DWT->LSR;;
if( (lsr & DWT_LSR_Present_Msk) != 0 )
{
if( ena )
{
if ((lsr & DWT_LSR_Access_Msk) != 0) //locked: access need unlock
{
DWT->LAR = DWT_LAR_KEY;
}
}
else
{
if ((lsr & DWT_LSR_Access_Msk) == 0) //unlocked
{
DWT->LAR = 0;
}
}
}
}
Related
Came across this question during a past interview, but got no feedback.
Since its a register, would I need to disable interrupts before accessing the register inorder to prevent data corruption? Thought of using two buffers, 32 bit and 64 bit, and sending the 32 bit buffer into a read32() and shifting it over accordingly to the 64 bit buffer. Lets just assume this is little-endian architecture.
I wrote a quick sample code on repl.it (Output does not match register value)
#include <stdio.h>
#include <string.h>
#include <stdint.h>
void read32(uint64_t *reg, uint32_t *buffer){
memcpy(buffer, reg, 4);
}
int main(void) {
//register
uint64_t reg = 0xAAAAAAAAFFFFFFFF;
//buffers
uint32_t buf_32 = 0;
uint64_t buf_64 = 0;
//read LSW
read32(®, &buf_32);
buf_64 |= buf_32; //add LSW
//read MSW
read32(®+4, &buf_32);
buf_64 |= ((uint64_t)buf_32 << 32);
printf("64 bit register value: 0x%lx\n", buf_64);
return 0;
}
Output:
64 bit register value: 0x1ffffffff
Disabling interrupts will not prevent an I/O register that may change independently of the code sequence from changing.
Often where data consistency is required between two hardware registers that are larger than the architecture width, the hardware data sheet or reference manual will advise on how to read the data - usually by specifying the order in which the registers must be read to work with hardware mechanisms that make that "safe".
In other cases the method might be dictated by the nature of the registers and their function/behaviour. For example if you have two 32bit timer/counters, with the overflow of one triggering an increment of the other, to form a 64 bit counter, then clearly the high-order counter will only change when the low-order counter overflows. In that case you can simply read the low, then the high and repeat if the low has since wrapped :
uint32_t low_word = 0, high_word = 0;
do
{
low_word = *low_reg_addr ;
high_word = *high_reg_addr ;
} while( *low_reg_addr > low_word ) ; // If low has wrapped, re-read
uint64_t full_word = (uint64_t)high_word << 32 | low_word;
So if the low-order register not wrapped after the high-order register has been read, then the data must be consistent, and the loop exit. If it has wrapped, the data may not be consistent, and must be re-read.
One mistake I see with your code is ®+4 would increment the pointer to x + 32 bytes. This isn't what you want since it should be just x + 4 bytes. You might want to consider typecasting to uint32_t. ((uint32_t *)® + 1) should increment by 4 byte. However, you will have to change your read function accordingly too.
This should work...
void read32(uint32_t *reg, uint32_t *buffer){
memcpy(buffer, reg, 4);
}
int main(void) {
//register
uint64_t reg = 0xAAAAAAAAFFFFFFFF;
//buffers
uint32_t buf_32 = 0;
uint32_t buf1_32 = 0;
uint64_t buf_64 = 0;
uint64_t *ptr = ®
read32((uint32_t *)ptr, &buf_32);
printf("32 bit register value: 0x%lx\n", buf_32);
read32((uint32_t *)ptr+1, &buf1_32);
printf("32 bit register value: 0x%lx\n", buf1_32);
buf_64 = ((uint64_t)buf1_32 << 32 )| buf_32;
printf("64 bit register value: 0x%lx\n", buf_64);
return 0;
}
This question already has answers here:
Creating a C function without compiler generated prologue/epilogue & RET instruction?
(3 answers)
Closed 3 years ago.
I'm trying to implement keyboard interrupt handler using C and QEMU. But when I execute the program my handler print only one character. After that the handler doesn't work at all.
My IDT setup:
struct IDT_entry {
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
unsigned short int offset_higherbits;
};
void setup_idt() {
struct IDT_entry IDT[256];
unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
keyboard_address = (unsigned long) keyboard_handler;
IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
IDT[0x21].selector = 0x8;
IDT[0x21].zero = 0;
IDT[0x21].type_attr = 0x8e;
IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;
/*
PIC1 PIC2
Commands 0x20 0xA0
Data 0x21 0xA1
*/
// ICW1 - init
outb(0x20, 0x11);
outb(0xA0, 0x11);
// ICW2 - reset offset address if IDT
// first 32 interrpts are reserved
outb(0x21, 0x20);
outb(0xA1, 0x28);
// ICW3 - setup cascading
outb(0x21, 0b0);
outb(0xA1, 0b0);
// ICW4 - env info
outb(0x21, 0b00000011);
outb(0xA1, 0b00000011);
// init finished
// disable IRQs except IRQ1
outb(0x21, 0xFD);
outb(0xA1, 0xff);
idt_address = (unsigned long)IDT;
idt_ptr[0] = (sizeof (struct IDT_entry) * 256) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16;
__asm__ __volatile__("lidt %0" :: "m" (*idt_ptr));
__asm__ __volatile__("sti");
}
My keyboard handler:
// Variables for printing ==
unsigned int location = 0;
char* vga = (char*)0xb8000;
char letter;
// =========================
void keyboard_handler() {
if (inb(0x64) & 0x01 && (letter = inb(0x60)) > 0) {
vga[location] = keyboard_map[letter];
vga[location+1] = 0x4;
location += 2;
}
outb(0x20, 0x20);
// __asm__ __volatile__("iret");
}
Main function (it is executed from my asm bootloader):
void kmain() {
setup_idt();
for (;;) {}
}
I think the problem is in "iret" instruction. Without it my kernel prints at least something (only one charachter, like I said before). But when I execute asm volatile("iret"); QEMU prints some garbage and then clear it after every keystroke ("SeaBios ..."). What do I have to do?
Thank you!
If you compile without optimization, asm("iret") will probably run while the stack pointer is still pointing at a saved EBP value, because -fno-omit-frame-pointer is the default and the cleanup epilogue happens after the last C statement of the function.
Or it could be pointing at other saved registers. Anyway, tricking the compiler and jumping out of an inline asm statement is never going to be safe (unless you use asm goto to maybe jump to a C label inside the function, but that doesn't solve your problem).
Also, the C calling convention allows functions to clobber EAX, ECX, EDX, and the FPU state. Even if you did manage to hack an iret into your function, it would corrupt the state of the code that was interrupted. GCC will use SSE/x87 to implement _Atomic int64_t load/store in 32-bit mode, and for copying large objects, unless you compile with -mgeneral-regs-only
Also see #MichaelPetch's answer on the linked duplicate: Creating a C function without compiler generated prologue/epilogue & RET instruction? for more interesting points, and some non-GCC info.
There are 2 solutions here:
write a pure-asm wrapper that saves the call-clobbered regs, calls your C function, then returns with iret
declare your function with __attribute__((interrupt)) to tell GCC it's an interrupt handler. The gcc manual's x86 function attributes has an example.
x86 support for that attribute is somewhat recent compared to traditionally-embedded ISAs like ARM, but modern GCC does know how emit functions that preserve all regs and end with iret. But you still need -mgeneral-regs-only.
See also https://wiki.osdev.org/Interrupt_Service_Routines#GCC_.2F_G.2B.2B which tells you the same thing as this answer.
(It also suggests an evil hack with pushad / popad; leave; iret which only works with optimzation disabled. I would not recommend that if you can possibly use a newer GCC that supports the interrupt attribute.)
The earlier parts of the wiki page cover the general problems with trying to use your own iret, so you can see what the total asm (compiler-generated + yours) would look like for your attempt.
I am working on a embedded project trying to learn some of the ins and outs of programming an embedded board. As one might have guessed, this involves writing code in C. I am not having too much trouble getting the grasp of setting things up correctly (in terms of ports/pins/etc..) but I am looking to abstract some of my code to make it a bit more readable.
For example, the following code turns on a Green LED that is on the board:
// Required CPU Speed Define
#define F_CPU 16000000
// Include the necissary library header files
#include <avr/io.h>
int main() {
DDRD |= (1 << DDD5);
PORTD |= (1 << PD5);
PORTD ^= (1 << PD5);
for(;;) { }
}
On the other hand, I can #define the names of the ports so they are more clear, but that doesn't seem like the ideal solution (and it's unfortunately the method that I am currently using).
I would like to abstract some of the setting/functionality of enabling the on board leds (which I eventually hope to extend to other concepts such as timers/interrupts/etc..).
How do I use Struct/Pointers to properly abstract this?
I am currently trying the following method, but it is falling flat, and the LED is failing to turn on:
OnBoardLED.h
typedef struct {
unsigned int dataDirectionRegister;
unsigned int portNumber;
unsigned int pinNumber;
} OnBoardLED;
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR);
void setPortNumber(OnBoardLED* led, unsigned int port);
void setPinNumber(OnBoardLED* led, unsigned int pin);
void turnOn(OnBoardLED* led);
void turnOff(OnBoardLED* led);
and
main.c
#include "inc/OnBoardLED.h"
int main(void) {
OnBoardLED greenLED;
setDataDirectionRegister(&greenLED, DDRD);
setPortNumber(&greenLED, PORTD);
setPinNumber(&greenLED, 5);
turnOn(&greenLED);
for(;;) { }
}
I know that I should be using pointers in this instance, specifically for the Data Direction Registers and the Port (so that I am properly referencing that memory location), but I do not know how to properly reference them.
What am I missing here?
Note: If needed I will post my current implementations of each function, defined in OnBoardLED.c
Edit:
OnBoardLED.c
#include "inc/OnBoardLED.h"
void setDataDirectionRegister(OnBoardLED* led, unsigned int DDR) {
led->dataDirectionRegister = DDR;
}
void setPortNumber(OnBoardLED* led, unsigned int port) {
led->portNumber = port;
}
void setPinNumber(OnBoardLED* led, unsigned int pin) {
led->pinNumber = pin;
}
void turnOn(OnBoardLED* led) {
led->dataDirectionRegister |= (1 << led->pinNumber);
led->portNumber |= (1 << led->pinNumber);
led->portNumber ^= (1 << led->pinNumber);
}
I'm not too much practical with AVR toolchain but from what I remember and understand things like DDRD are macro which map addresses from address space to register of the CPU.
So by inspecting the source code you obtain something like:
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
This means that replacing DDRD yields something like:
*(volatile uint8_t*)((0x0A) + 0)
which is a smart macro, when used as a left-hand operand allows you to store a value at that memory address, when used as a right-hand operand you can read the value of the register. But this is still a macro and macros can hide evil details like in this situation.
What happens is that your code is
led->dataDirectionRegister = *(volatile uint8_t*)((0x0A) + 0);
led->dataDirectionRegister |= (1 << led->pinNumber);
so what you are doing is just saving the value of the data direction register into the struct member and then overwrite it with the value you would like to save into the register. So nothing basically happens.
What you need is to save the address of the data direction register to be able to dereference it later when calling the turnOn method. This is easily accomplished by having a member declared as
volatile uint8_t* dataDirectionRegister;
So that you can do
void setDataDirectionRegister(OnBoardLED* led, volatile uint8_t* DDR) {
led->dataDirectionRegister = DDR;
}
and invoke it as setDataDirectionRegister(&led, &DDRD);. Notice the & operator used to obtain the address of the memory location of the register.
But now you have an address so in your turnOn method you must dereference the variable to store data into it:
void turnOn(OnBoardLED* led) {
*led->dataDirectionRegister |= (1 << led->pinNumber);
...
So let's make a concrete example to show you the problem and how to fix it:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define __SFR_OFFSET 0
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(&buffer[0] + mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define DDRD _SFR_IO8(0x0A)
uint8_t buffer[256];
int main(void) {
memset(&buffer[0], 0, 256);
DDRD = 0x40;
printf("value: %x\n", (int)DDRD);
*(volatile uint8_t*)(&buffer[0] + (0x0A + 0)) = 0x41;
printf("value: %x\n", (int)DDRD);
// now let's do what you are doing in your struct
unsigned int dataDirectionRegister = DDRD; // value of register is copied into variable
dataDirectionRegister = 0x80; // variable is changed but not real register
printf("register: %x, variable: %x\n", (int)DDRD, dataDirectionRegister);
// you must save an address to do what you need
volatile uint8_t* realDataDirectionRegister = &DDRD;
*realDataDirectionRegister = 0x80;
printf("register: %x, variable: %x\n", (int)DDRD, (int)*realDataDirectionRegister);
return 0;
}
Mind that I used a buffer as my fake memory space, since you don't have a flat memory model free to use like in a x86 architecture like you have in a microcontroller.
I have bootloader, when good jobed in lpc2xxx. But, when I copy this to lpc4078, bootloader not jump to main programm.
I tried:
1) Use #define USER_FLASH_START 0x8000
__asm void boot_jump(uint32_t address)
{
LDR SP, [R0]
LDR PC, [R0, #4]
}
main()
{
bool t = true;
while(t)
{
...WORKING CODE...
uart << "Hello, World!!!";
}
uart << "END";
__disable_irq();
__set_CONTROL(0);
__set_MSP(stack_adr);
SCB->VTOR = (USER_FLASH_START & 0x1FFFFF80);
boot_jump(USER_FLASH_START);
}
2) Use #define USER_FLASH_START 0x8000
void JumpToAppAt(unsigned int * vtbp)
{
__disable_irq();
__set_MSP(vtbp[0]); // load SP
((void (*)(void)) vtbp[1])(); // go...
}
main()
{
bool t = true;
while(t)
{
...WORKING CODE...
uart << "Hello, World!!!";
}
uart << "END";
__disable_irq();
__set_CONTROL(0);
__set_MSP(stack_adr);
SCB->VTOR = (USER_FLASH_START & 0x1FFFFF80);
JumpToAppAt((unsigned int *) USER_FLASH_START);
}
Main programm not start(.
I thought what not work MAIN_PROGRAMM and tried:
3) Use #define USER_FLASH_START 0x0, but nothing has changed.
LPC2xxx are based on ARM7 while LPC4078 is based on Cortex-M4. Regarding the reset vector, I see that you have adjusted to using the 2nd entry of the vector table. Please make sure that the addresses stored in the vector table have their LSB set to 1, i.e. bit[0] = 1, because this bit indicates Thumb instructions, and the Cortex-M4 processor only supports Thumb instructions. (reference)
New to ARM (and programming for that matter)and find the bit addressing of I/O ports to be confusing. You can define a constant at a specific port pin but must still write its bit value to set it. For example:
#define MyOutput (*((volatile unsigned long *)0x40025040)) //PF4
// But to set this bit you must write
MyOutput = 0x10;
This feels weird to me. If I address a certain pin I should be able to set it with a 1. So to keep me from forgetting that I must write its bit value I would like to make a function that does this for me. I have come up with the following, but am having trouble with the pointer syntax or pointer conversion to int I think.
int SetOutput(volatile unsigned long* PIN), int ONOFF){ //ON defined as 1, OFF defined as 0
volatile unsigned long PortBit = (PIN & 0xFF);
if (ONOFF){
return ((PortBit & 0xFF)>>2);
} else {
return 0;
}
}
//Called by
MyOutput = SetOutput(&MyOutput, ON);
Anyone have any thoughts or advice? Thank you!
Chris,
I am not a Cortex-M expert, so your MMV. But based on your description above, you are attempting to use a pretty convoluted way to modify a single bit in a peripheral register. There are two ways to normally handle it:
Use a set of macros (what I'm most familiar with). This will reduce your overhead of space and CPU time vs calling functions for each read/write of a pin, since the macros are directly converted during compile time to the exact values/operations needed.
Use the bit-band address for the PF4 register instead (never used a Cortex-M). This looks like the preferred way to do it with your architecture.
Based on the ARM Cortex-M4 Technical Reference Manual, Section 3.4, you can use the bit-band alias address to modify the PF4 bit. Details on how that works can be found in Section 3.7 of the TRM.
Based on your code above, of PF4 being bit 4 in address 0x40025040, the bit-band formula gives (taken from TRM under fair-use):
• bit_band_base is the starting address of the alias region. (0x42000000)
• byte_offset is the number of the byte in the bit-band region that contains the targeted bit. (0x00025040)
• bit_number is the bit position, 0 to 7, of the targeted bit. (0x4)
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bwo = 0x25040* 0x20 + 0x4 * 0x4 = 0x004A0810
bit_word_addr = bit_band_base + bit_word_offset
bwa = 0x42000000 + 0x4A0810 = 0x424A0810
• bit_word_offset is the position of the target bit in the bit-band memory region.
• bit_word_addr is the address of the word in the alias memory region that maps to the
targeted bit.
So
*(volatile unsigned long *)0x424A0810 = 0x1;
is identical to writing
*MyOutput |= 0x10;
If you really want to go the route of using a function and direct writes, try this instead( limits to only PF31, if PF needs to go higher than 31, implementation left to the reader ); this code includes a PC-based test #define so you can compile it with gcc on your command line.
#include <inttypes.h>
#include <stdio.h>
#define PC_TESTING 1
#if PC_TESTING
unsigned long FAKE_PFBASE;
#define PFBASE &FAKE_PFBASE
#else
#define PFBASE (volatile unsigned long *) 0x40025040
#endif
#define SUCCESS 0
#define ERROR_INVALID_PIN -1
#define ERROR_INVALID_STATE -2
typedef enum {OFF = 0, ON} ONOFF_t;
typedef enum { PF0 = 0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PF8, PF9, PF10, PF11, PF12, PF13, PF14, PF15, PF16, PF17, PF18, PF19, PF20, PF21, PF22, PF23, PF24, PF25, PF26, PF27, PF28, PF29, PF30, PF31 } PIN_t;
int SetOutput( PIN_t PIN, ONOFF_t ONOFF)
{
uint32_t mask, value;
// Implementing more than 32 pins is exercise for the reader
if (PIN > 31)
return ERROR_INVALID_PIN;
// In case someone did something wrong
if (ONOFF != OFF && ONOFF != ON)
return ERROR_INVALID_STATE;
//Broken into separate steps for ease of reading
//Select the bit of interest
mask = 1 << PIN;
//Clear the bit of interest
value = *PFBASE & ~mask; //~ is a bit-wise invert. 0x0000 0010 becomes 0xFFFF FFEF
//Set the bit of interest if that is requested
if( ON == ONOFF)
value |= mask;
*PFBASE = value;
return SUCCESS;
}
int main()
{
int success = 0;
FAKE_PFBASE = 0l;
success = SetOutput( PF4, ON);
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );
success = SetOutput(PF0, ON );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );
success = SetOutput(PF0, OFF );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );
//Error handling left to reader
success = SetOutput(33, OFF );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );
success = SetOutput(PF2, 2 );
printf (" Success = %d, *PFBASE = 0x%08x\n", success, *PFBASE );
return 0;
}
Sample output:
$ ./a.out
Success = 0, *PFBASE = 0x00000010
Success = 0, *PFBASE = 0x00000011
Success = 0, *PFBASE = 0x00000010
Success = -1, *PFBASE = 0x00000010
Success = -2, *PFBASE = 0x00000010
You can't address individual bits; the minimum addressable unit in C (and usually in hardware) is one char, i.e., typically a byte of 8 bits.
The typical approach is to write wrapper macros or functions. As for your SetOutput, it seems to be quite broken, e.g., it tries to return a value from a void function, and the 0xFF mask isolates 8 bits, not 1 (which the pin presumably is), and it never writes to the output register. If the bit 0x10 controls the pin you want, the typical way would be:
MyOutput |= 0x10; // set bit
MyOutput &= ~0x10; // unset bit
MyOutput ^= 0x10; // toggle bit
You can create macros around these as necessary. To check the state of the corresponding bit in an input register, you can use:
if (MyInput & 0x10) {
// bit is set
}