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;
}
I am working on a library for controlling the M95128-W EEPROM from an STM32 device. I have the library writing and reading back data however the first byte of each page it not as expected and seems to be fixed at 0x04.
For example I write 128 bytes across two pages starting at 0x00 address with value 0x80. When read back I get:
byte[0] = 0x04;
byte[1] = 0x80;
byte[2] = 0x80;
byte[3] = 0x80;
.......
byte[64] = 0x04;
byte[65] = 0x80;
byte[66] = 0x80;
byte[67] = 0x80;
I have debugged the SPI with a logic analyzer and confirmed the correct bytes are being sent. When using the logic analyzer on the read command the mysterios 0x04 is transmitted from the EEPROM.
Here is my code:
void FLA::write(const void* data, unsigned int dataLength, uint16_t address)
{
int pagePos = 0;
int pageCount = (dataLength + 64 - 1) / 64;
int bytePos = 0;
int startAddress = address;
while (pagePos < pageCount)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); // WP High
chipSelect();
_spi->transfer(INSTRUCTION_WREN);
chipUnselect();
uint8_t status = readRegister(INSTRUCTION_RDSR);
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
printConsole("Wrote byte to ");
printConsoleInt(startAddress + bytePos);
printConsole("with value ");
printConsoleInt(byte);
printConsole("\n");
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI);
chipUnselect();
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_RESET); //WP LOW
bool writeComplete = false;
while (writeComplete == false)
{
uint8_t status = readRegister(INSTRUCTION_RDSR);
if(status&1<<0)
{
printConsole("Waiting for write to complete....\n");
}
else
{
writeComplete = true;
printConsole("Write complete to page ");
printConsoleInt(pagePos);
printConsole("# address ");
printConsoleInt(bytePos);
printConsole("\n");
}
}
pagePos++;
address = address + 64;
}
printConsole("Finished writing all pages total bytes ");
printConsoleInt(bytePos);
printConsole("\n");
}
void FLA::read(char* returndata, unsigned int dataLength, uint16_t address)
{
chipSelect();
_spi->transfer(INSTRUCTION_READ);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address
_spi->transfer(xlow); // part 2 address
for (unsigned int i = 0; i < dataLength; i++)
returndata[i] = _spi->transfer(0x00);
chipUnselect();
}
Any suggestion or help appreciated.
UPDATES:
I have tried writing sequentially 255 bytes increasing data to check for rollover. The results are as follows:
byte[0] = 4; // Incorrect Mystery Byte
byte[1] = 1;
byte[2] = 2;
byte[3] = 3;
.......
byte[63] = 63;
byte[64] = 4; // Incorrect Mystery Byte
byte[65] = 65;
byte[66] = 66;
.......
byte[127] = 127;
byte[128] = 4; // Incorrect Mystery Byte
byte[129} = 129;
Pattern continues. I have also tried writing just 8 bytes from address 0x00 and the same problem persists so I think we can rule out rollover.
I have tried removing the debug printConsole and it has had no effect.
Here is a SPI logic trace of the write command:
And a close up of the first byte that is not working correctly:
Code can be viewed on gitlab here:
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/flash.cpp
Init code of SPI can be seen here in MX_SPI_Init()
https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp
I have another device on the SPI bus (RFM69HW RF Module) which works as expected sending and receiving data.
The explanation was actually already given by Craig Estey in his answer. You do have a rollover. You write full page and then - without cycling the CS pin - you send INSTRUCTION_WRDI command. Guess what's the binary code of this command? If you guessed that it's 4, then you're absolutely right.
Check your code here:
chipSelect();
_spi->transfer(INSTRUCTION_WRITE);
uint8_t xlow = address & 0xff;
uint8_t xhigh = (address >> 8);
_spi->transfer(xhigh); // part 1 address MSB
_spi->transfer(xlow); // part 2 address LSB
for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
{
uint8_t byte = ((uint8_t*)data)[bytePos];
_spi->transfer(byte);
// ...
bytePos ++;
}
_spi->transfer(INSTRUCTION_WRDI); // <-------------- ROLLOEVER!
chipUnselect();
With these devices, each command MUST start with cycling CS. After CS goes low, the first byte is interpreted as command. All remaining bytes - until CS is cycled again - are interpreted as data. So you cannot send multiple commands in a single "block" with CS being constantly pulled low.
Another thing is that you don't need WRDI command at all - after the write instruction is terminated (by CS going high), the WEL bit is automatically reset. See page 18 of the datasheet:
The Write Enable Latch (WEL) bit, in fact, becomes reset by any of the
following events:
• Power-up
• WRDI instruction execution
• WRSR instruction completion
• WRITE instruction completion.
Caveat: I don't have a definitive solution, just some observations and suggestions [that would be too large for a comment].
From 6.6: Each time a new data byte is shifted in, the least significant bits of the internal address counter are incremented. If more bytes are sent than will fit up to the end of the page, a condition known as “roll-over” occurs. In case of roll-over, the bytes exceeding the page size are overwritten from location 0 of the same page.
So, in your write loop code, you do: for (i = 0; i < 64; i++). This is incorrect in the general case if the LSB of address (xlow) is non-zero. You'd need to do something like: for (i = xlow % 64; i < 64; i++)
In other words, you might be getting the page boundary rollover. But, you mentioned that you're using address 0x0000, so it should work, even with the code as it exists.
I might remove the print statements from the loop as they could have an effect on the serialization timing.
I might try this with an incrementing data pattern: (e.g.) 0x01,0x02,0x03,... That way, you could see which byte is rolling over [if any].
Also, try writing a single page from address zero, and write less than the full page size (i.e. less that 64 bytes) to guarantee that you're not getting rollover.
Also, from figure 13 [the timing diagram for WRITE], it looks like once you assert chip select, the ROM wants a continuous bit stream clocked precisely, so you may have a race condition where you're not providing the data at precisely the clock edge(s) needed. You may want to use the logic analyzer to verify that the data appears exactly in sync with clock edge as required (i.e. at clock rising edge)
As you've probably already noticed, offset 0 and offset 64 are getting the 0x04. So, this adds to the notion of rollover.
Or, it could be that the first data byte of each page is being written "late" and the 0x04 is a result of that.
I don't know if your output port has a SILO so you can send data as in a traditional serial I/O port or do you have to maintain precise bit-for-bit timing (which I presume the _spi->transfer would do)
Another thing to try is to write a shorter pattern (e.g. 10 bytes) starting at a non-zero address (e.g. xhigh = 0; xlow = 4) and the incrementing pattern and see how things change.
UPDATE:
From your update, it appears to be the first byte of each page [obviously].
From the exploded view of the timing, I notice SCLK is not strictly uniform. The pulse width is slightly erratic. Since the write data is sampled on the clock rising edge, this shouldn't matter. But, I wonder where this comes from. That is, is SCLK asserted/deasserted by the software (i.e. transfer) and SCLK is connected to another GPIO pin? I'd be interested in seeing the source for the transfer function [or a disassembly].
I've just looked up SPI here: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus and it answers my own question.
From that, here is a sample transfer function:
/*
* Simultaneously transmit and receive a byte on the SPI.
*
* Polarity and phase are assumed to be both 0, i.e.:
* - input data is captured on rising edge of SCLK.
* - output data is propagated on falling edge of SCLK.
*
* Returns the received byte.
*/
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;
for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);
/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);
/* Pull the clock line high */
write_SCLK(HIGH);
/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;
/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);
/* Pull the clock line low */
write_SCLK(LOW);
}
return byte_in;
}
So, the delay times need be at least the ones the ROM needs. Hopefully, you can verify that is the case.
But, I also notice that on the problem byte, the first bit of the data appears to lag its rising clock edge. That is, I would want the data line to be stabilized before clock rising edge.
But, that assumes CPOL=0,CPHA=1. Your ROM can be programmed for that mode or CPOL=0,CPHA=0, which is the mode used by the sample code above.
This is what I see from the timing diagram. It implies that the transfer function does CPOL=0,CPHA=0:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
This is what I originally expected (CPOL=0,CPHA=1) based on something earlier in the ROM document:
SCLK
__
| |
___| |___
DATA
___
/ \
/ \
The ROM can be configured to use either CPOL=0,CPHA=0 or CPOL=1,CPHA=1. So, you may need to configure these values to match the transfer function (or vice-versa) And, verify that the transfer function's delay times are adequate for your ROM. The SDK may do all this for you, but, since you're having trouble, double checking this may be worthwhile (e.g. See Table 18 et. al. in the ROM document).
However, since the ROM seems to respond well for most byte locations, the timing may already be adequate.
One thing you might also try. Since it's the first byte that is the problem, and here I mean first byte after the LSB address byte, the memory might need some additional [and undocumented] setup time.
So, after the transfer(xlow), add a small spin loop after that before entering the data transfer loop, to give the ROM time to set up for the write burst [or read burst].
This could be confirmed by starting xlow at a non-zero value (e.g. 3) and shortening the transfer. If the problem byte tracks xlow, that's one way to verify that the setup time may be required. You'd need to use a different data value for each test to be sure you're not just reading back a stale value from a prior test.
I am new user of STM32(L476RG).I've done some work with Arduino so far. Now,I want to read ADC Value and transmit this value with UART. I setup hardware part and initial software configuration part. I want to know, this part of code in while loop is correct?
if (HAL_ADC_PollForConversion(&hadc1, 1000000) == HAL_OK)
{
ADCValue = HAL_ADC_GetValue(&hadc1);
sprintf(str, "%d", ADCValue);
HAL_UART_Transmit(&huart2,ADCValue,1,100);
}
Assuming that you callHAL_ADC_Start(&hadc1) before entering the while loop.
Basically it is OK to call you code in a while loop, but I have some remarks.
Make sure that ADCValue variable is uint32_t or at least uin16_t as the return value of HAL_ADC_GetValue is uint32_t. If the ADC's resolution is above 8 bit then 1 byte won't be enough to store the result.
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
The HAL_UART_Transmit expects an uint8_t* as a second parameter, given your code you pass a simple variable. You should use the & operator before ADCValue and cast it to uint8_t*. Also it only sends 1 byte in your code, based on the third parameter. If ADCValue is uint32_t then you should modify this parameter to 4. Also note that you send raw byte value and not ASCII. All in all:
uint32_t ADCValue;
if (HAL_ADC_PollForConversion(&hadc1, 1000000) == HAL_OK)
{
ADCValue = HAL_ADC_GetValue(&hadc1);
sprintf(str, "%d", ADCValue);
HAL_UART_Transmit(&huart2, (uint8_t*)(&ADCValue), 4, 100);
}
(&ADCValue) returns the address of ADCValue which is an uint32_t* so it should be casted to uint8_t* when passing to HAL_UART_Transmit. And as an uint32_t is 4 byte, third param should be 4.
If you want to send the str you should calculate its correct length before sending as well.
By the way here is an ADC example from this STM32 HAL ADC Tutorial.
uint32_t g_ADCValue;
int g_MeasurementNumber;
int main(void)
{
HAL_Init();
SystemClock_Config();
ConfigureADC();
HAL_ADC_Start(&g_AdcHandle);
for (;;)
{
if (HAL_ADC_PollForConversion(&g_AdcHandle, 1000000) == HAL_OK)
{
g_ADCValue = HAL_ADC_GetValue(&g_AdcHandle);
g_MeasurementNumber++;
}
}
}
What I did as the simplest working solution was to force an ADC reset:
HAL_StatusTypeDef stat = HAL_ADC_PollForConversion(&hadc1, 900);
if (stat != HAL_OK) {
HAL_ADC_Stop(&hadc1);
HAL_ADC_Start(&hadc1);
}
This should be executed earlier than just before GetValue, I had improper action then.
P.S. Sorry for poor visibility
Slightly more elegant would be stopping after GetValue function, yet I left this code as properly working.
I'm working on a communication link between a computer running Linux and a STM32F0. I want to use some kind of error detection for my packets and since the STM32F0 has CRC32 hw and I have zlib with CRC32 on Linux I thought it would be a good idea to use CRC32 for my project. The problem is that I won't get the same CRC value for the same data on the different platforms.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
int
main(void)
{
uint8_t byte0 = 0x00;
uint32_t crc0 = crc32(0L, Z_NULL, 0);
crc0 = crc32(crc0, &byte0, 1);
printf("CRC32 value of %" PRIu8 " is: %08" PRIx32 "\n", byte0, crc0);
}
outputs CRC32 value of 0 is: d202ef8d which matches the result on several online calculators.
It seems though that whatever settings I use on the STM32 I can't get the same CRC.
I have found a flowchart on how the CRC hw calculates its value in an application note from ST but I can't figure out how it's done in zlib.
Does anyone know if they are compatible?
[edit 1] They both uses the same init value and polynomial.
[edit 2] The STM32 code is relatively uninteresging since it's using the hw.
...
/* Default values are used for init value and polynomial, see edit 1 */
CRC->CR |= CRC_CR_RESET;
CRC->DR = (uint8_t)0x00;
uint32_t crc = CRC->DR;
...
The CRC32 implementation on STM32Fx seems to be not the standard CRC32 implementation you find on many online CRC calculators and the one used in zip.
STM32 implements CRC32-MPEG2 which uses big endian and no final flip mask compared to the zip CRC32 which uses little endian and a final flip mask.
I found this online calculator which supports CRC32-MPEG2.
If you are more interested on other CRC algorithms and their implementation, look at this link.
PS: The HAL driver from STM supports input in byte, half word and word formats and they seem to work fine for STM32F0x in v1.3.1
From the documentation, it appears that your STM32 code is not just uninteresting — it is rather incomplete. From the documentation, in order to use the CRC hardware you need to:
Enable the CRC peripheral clock via the RCC peripheral.
Set the CRC Data Register to the initial CRC value by configuring the Initial CRC value register (CRC_INIT).(a)
Set the I/O reverse bit order through the REV_IN[1:0] and REV_OUT bits respectively in CRC Control register (CRC_CR).(a)
Set the polynomial size and coefficients through the POLYSIZE[1:0] bits in CRC
Control register (CRC_CR) and CRC Polynomial register (CRC_POL) respectively.(b)
Reset the CRC peripheral through the Reset bit in CRC Control register (CRC_CR).
Set the data to the CRC Data register.
Read the content of the CRC Data register.
Disable the CRC peripheral clock.
Note in particular steps 2, 3, and 4, which define the CRC being computed. They say that their example has rev_in and rev_out false, but for the zlib crc, they need to be true. Depending on the way the hardware is implemented, the polynomial will likely need to reversed as well (0xedb88320UL). The initial CRC needs to be 0xffffffff, and the final CRC inverted to match the zlib crc.
I haven't tested this, but I suspect that you're not, in fact, doing an 8-bit write on the STM32.
Instead you're probably doing a write to the full width of the register (32 bits), which of course means you're computing the CRC32 of more bytes than you intended.
Disassemble the generated code to analyze the exact store instruction that is being used.
I had similar issues implementing a CRC on an STM32 CRC module where the final checksum was not matching. I was able to fix this by looking at the example code in stm32f30x_crc.c that is provided with STMCube. In the source it has a function for 8-bit CRC that used the following code
(uint8_t)(CRC_BASE) = (uint8_t) CRC_Data;
to write to the DR register. As stated previously, the line CRC->DR is defined as volatile uint32_t which will access the whole register. It would have been helpful if ST had been more explicit about accessing the DR register rather than simply saying it supports 8-bit types.
From my experience, you can not compare the CRC32 code between the STM32 CRC unit output with the online CRC32 calculator.
Please find my CRC32 calculator for STM32 in this link.
This function has been used in my project, and proved to be correct.
For reference, getting compliant crc32 using the Low Level API:
void crc_init(void) {
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_CRC);
LL_CRC_SetPolynomialCoef(CRC, LL_CRC_DEFAULT_CRC32_POLY);
LL_CRC_SetPolynomialSize(CRC, LL_CRC_POLYLENGTH_32B);
LL_CRC_SetInitialData(CRC, LL_CRC_DEFAULT_CRC_INITVALUE);
LL_CRC_SetInputDataReverseMode(CRC, LL_CRC_INDATA_REVERSE_WORD);
LL_CRC_SetOutputDataReverseMode(CRC, LL_CRC_OUTDATA_REVERSE_BIT);
}
uint32_t crc32(const char* const buf, const uint32_t len) {
uint32_t data;
uint32_t index;
LL_CRC_ResetCRCCalculationUnit(CRC);
/* Compute the CRC of Data Buffer array*/
for (index = 0; index < (len / 4); index++) {
data = (uint32_t)((buf[4 * index + 3] << 24) |
(buf[4 * index + 2] << 16) |
(buf[4 * index + 1] << 8) | buf[4 * index]);
LL_CRC_FeedData32(CRC, data);
}
/* Last bytes specific handling */
if ((len % 4) != 0) {
if (len % 4 == 1) {
LL_CRC_FeedData8(CRC, buf[4 * index]);
}
if (len % 4 == 2) {
LL_CRC_FeedData16(CRC, (uint16_t)((buf[4 * index + 1] << 8) |
buf[4 * index]));
}
if (len % 4 == 3) {
LL_CRC_FeedData16(CRC, (uint16_t)((buf[4 * index + 1] << 8) |
buf[4 * index]));
LL_CRC_FeedData8(CRC, buf[4 * index + 2]);
}
}
return LL_CRC_ReadData32(CRC) ^ 0xffffffff;
}
Tweaked from the example here
The LL_CRC_ResetCRCCalculationUnit() re-initializes the crc unit so that repeated calls to this function return the expected value, otherwise the initial value will be whatever the result of the previous crc was, instead of 0xFFFFFFFF
You probably don't need the lines setting the polynomial to the default value because they should already be initialized to these values from reset, but I've left them in for posterity.
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
}