cancel
Showing results for 
Search instead for 
Did you mean: 

Does anyone actually use the register names to program?

aerospacengineer
Associate II
Posted on September 20, 2013 at 03:12

That may seem like a bit of a pretentious question, but it is not intended to be so.  I am new to STM32 products, coming from PIC and dsPIC devices.  There are some changes that I am wrapping my head around, though nothing too bad. 

I have noticed that there is a tendency to not use the actual register names to set a value, but to use the structs and the defines in the header files to accomplish the programming.  This is not bad per say, though I really dislike digging through header files as documentation.  I like to be able to use a datasheet/addendum to have all the registers and then setting a value in that register.

Perhaps I am just missing something.  I would really love to hear some thoughts on this.  Perhaps there is other documentation that I am missing.  Thanks again.  I am looking forward to getting beyond the blinking lights and really using this device for something useful.

Adam
15 REPLIES 15
Posted on September 20, 2013 at 08:55

> I have noticed that there is a tendency to not use the actual register names to set a value,

> but to use the structs and the defines in the header files to accomplish the programming. 

It's not *that* bad (more below). What *is* bad is, that most of the folks appear to use the ''standard peripheral library''. They then hide the actual registers behind macros and function calls, and use macros (with no system in naming) to redefine names of register fields. In my personal, heavily biased opinion, it is absolutely useless to harmful at times. However, what *is* useful are the examples contained therein and referring to it, so at the end of the day one needs to get used to it, even if in read-only mode...

> This is not bad per say, though I really dislike digging through header files as documentation. 

> I like to be able to use a datasheet/addendum to have all the registers and then setting a value in that register.

The headers - namely, only ONE header per device, [standard peripheral library]/Libraries/CMSIS/Device/ST/STM32xxxx/Include/stm32xxxx.h (and you can get rid of all the others (except those in [std perph lib]/Libraries/CMSIS/Include) if you don't want to use the ''library'') - luckily, enable you to use the registers in a systematic way: if the name of register in user manual is AAAx_BBB (AAA is name of peripheral, e.g. TIM, x denotes number of this peripheral, as there are more of them - note that this prevents you to use the names of registers literally as they are in the manual, although this scheme is not maintained across all the peripherals' descriptions - this is one of the many inconsistencies in the documentation you'll come across in the future, due to the ''glued up'' nature of these chips hence the documentation), then you'll use it as AAAx->BBB. That is, IMO, reasonable. And there's also an added benefit, that you can easily ''rename'' peripherals using macros to make the code more readable and potentially portable, e.g.

#define USART_TO_PC USART1

What is IMO missing in the stm32xxx.h headers are macros to define values for multi-bit fields. I started to add them there for my personal purpose, like:

// baudrate definitions

#define  SPI_CR1_BR__2          0   // fPCLK/2

#define  SPI_CR1_BR__4          1   // fPCLK/4

#define  SPI_CR1_BR__8          2   // fPCLK/8

#define  SPI_CR1_BR__16         3   // fPCLK/16

#define  SPI_CR1_BR__32         4   // fPCLK/32

#define  SPI_CR1_BR__64         5   // fPCLK/64

#define  SPI_CR1_BR__128        6   // fPCLK/128

#define  SPI_CR1_BR__256        7   // fPCLK/256

so then my programs contain things like:

#define ADC_SPI SPI1

#define OR |

    ADC_SPI->CR2 = 0 // reset value is 0x0000, so if the reserved positions require to be kept at reset value they may be written as 0

      OR ( 1     * SPI_CR2_RXDMAEN)      /* Rx Buffer DMA Enable */

      OR ( 1     * SPI_CR2_TXDMAEN)      /* Tx Buffer DMA Enable */

      OR ( 1     * SPI_CR2_SSOE   )      /* SS Output Enable */        // -------       this MUST be set, otherwise the SSI pin's state can change SPI onto slave mode !

      OR ( 0     * SPI_CR2_FRF    )      /* Frame Format (0 - normal SPI, 1 - TI mode) */

      OR ( 0     * SPI_CR2_ERRIE  )      /* Error Interrupt Enable */

      OR ( 0     * SPI_CR2_RXNEIE )      /* RX buffer Not Empty Interrupt Enable */

      OR ( 0     * SPI_CR2_TXEIE  )      /* Tx buffer Empty Interrupt Enable */

    ;

    ADC_SPI->CR1 = 0

      OR ( 1     * SPI_CR1_CPHA    )     /* Clock Phase -- shift, then sample */

      OR ( 0     * SPI_CR1_CPOL    )     /* Clock Polarity  -- CKL idle is 0 */

      OR ( 1     * SPI_CR1_MSTR    )     /* Master Selection */

      OR ( SPI_CR1_BR__8 * SPI_CR1_BR_0 )     /* Baudrate -- HCLK=160MHz -> P2CLK=80MHz -> baud=10MHz */

      OR ( 1     * SPI_CR1_SPE     )     /* SPI Enable */

// *** OFF ***      OR ( 0     * SPI_CR1_SPE     )     /* SPI Enable */

      OR ( 0     * SPI_CR1_LSBFIRST)     /* Frame Format -- 0 -> MSB first (''normal'' SPI) */

      OR ( 0     * SPI_CR1_SSI     )     /* Internal slave select */

      OR ( 0     * SPI_CR1_SSM     )     /* Software slave management */

      OR ( 0     * SPI_CR1_RXONLY  )     /* Receive only */

      OR ( SPI_CR1_DFF__16 * SPI_CR1_DFF )     /* Data Frame Format - 16-bit */

      OR ( 0     * SPI_CR1_CRCNEXT )     /* Transmit CRC next */

      OR ( 0     * SPI_CR1_CRCEN   )     /* Hardware CRC calculation enable */

      OR ( 0     * SPI_CR1_BIDIOE  )     /* Output enable in bidirectional mode */

      OR ( 0     * SPI_CR1_BIDIMODE)     /* Bidirectional data mode enable */

    ;

Have fun!

JW

John F.
Senior
Posted on September 20, 2013 at 09:25

'' Perhaps there is other documentation that I am missing.'' Have you seen the help file that comes with the library? For example, the ''stm32f4xx_dsp_stdperiph_lib_um.chm'' for the STM32F4xx. Use of the ''standard peripheral library'' allows others (and yourself later!) to quickly read and understand your code and quickly offer example code etc.

Using the library functions doesn't stop you from addressing the registers as Jan describes. I'd recommend giving the library functions a go first.

aerospacengineer
Associate II
Posted on September 20, 2013 at 13:09

I do appreciate the comments.  Like I said, it is not necessarily bad, it just makes about 20% of the STM32F40X reference manual relatively useless, and forces me to go digging through the header files. 

I did start looking at some of them and like has been said, they are relatively straight forward.  basically a bunch of pointers, defines, and structs to create more of an API type interface as opposed to a setting of registers.

I am used to doing things such as:

OSCON = 0b00000000;

Then if something is not working, I then dig into the datasheet/reference manual to figure out why.  With the API type structure, I need to check the reference manual in the registers, then go to the header file and the proper struct, then see if I have translated properly by going into the defines which then point straight to a memory value. 

I am sure that I will get used to it, I just wanted to see if there were actually people that were programming using the register names and setting bit values.  Thanks for all your answers.

Adam

aerospacengineer
Associate II
Posted on September 20, 2013 at 13:20

I really like your approach to how you are doing this.  I may have to integrate something like this in the future.

Adam

dibs
Associate II
Posted on September 20, 2013 at 14:30

I work on mcu code in team based environment, and it is always more productive to code using the libraries rather than coding on the register level. Let me give you an example:

SPI1->CR1 |= ((uint16_t)0x0400); //Can you immediately know why this line has an error?

vs

#define MYSPI SPI1

#define SPIDIR (uint16_t)SPI_Direction_Tx

SPI_BiDirectionalLineConfig(MYSPI, SPIDIR);

The second tends to be a lot more readable. Even without a comment, you can figure out what it is doing more quickly. The CMIS libraries tend to be well commented, so you can look up the definition of the functions and get a detailed explanation of what it is doing. Also, you tend to get error checking with the CMIS libraries. They will check and see if your choices for setting the registers are actually valid.

IMO, code should read like a story. No one cares how ''good'' you are if your code is unreadable by another person. Especially in a professional environment, it is critical to have code that is easily understandable and quickly understood.

As an addendum; I purposely put an error in the register level write of the SPI control register. My point there is that coding at the register level obfuscates your code. At the very least you should be swapping out all hex numbers for #define values. Then you can combine all of these register accesses into functions to be called by a higher level function. But in the end, you will have just created the libraries.

If you want some example code search for:

STM32F4xx_StdPeriph_Examples

Also, the CMIS library *.c files generally have instructions in them, explaining how to use them.

frankmeyer9
Associate II
Posted on September 20, 2013 at 15:39

No one cares how ''good'' you are if your code is unreadable by another person. Especially in a professional environment, it is critical to have code that is easily understandable and quickly understood.

 

[irony]

Not if you are a 'cowboy coder', and want to make yourself indispensable for your company ...

[/irony]

Posted on September 20, 2013 at 16:04

I see it as a matter of scalability and porting. If you have a few hundred unique registers with little commonality it makes sense to address the registers, but when you duplicate many peripherals and controllers that have a common interface, and thousands of registers, you need to provide some level abstraction. If I want to change USART assignments, or switch some code between platforms I don't want to be spending hours modifying registers hoping I caught all the uses, and differences in bit assignments. Most of the examples I posted have been mashed out in minutes.

ARM systems are typically built with orders of magnitude more complexity than PIC and other minimal silicon solutions, and are designed to run C without all the compromises and hacks you see on 8051 compilers, etc.

It's all well and good to understand the peripheral registers at a bit and function level, but it's a skill with a very short shelf life. Knowing the functional capabilities of the hardware is more important than getting stuck in the minutia, which can get focused on when required.

I say this with 30+ years experience in programming microprocessors at machine code, assembler and C level, and having worked on a number of custom SoC parts using MIPS, ARM and Sparc.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
aerospacengineer
Associate II
Posted on September 20, 2013 at 16:35

I guess that my biggest complaint is the lack of documentation.  I can swap from a register based methodology to an API based methodology, this is not necessarily an issue, but two things present themselves.  The first is that for the F4 part that I am particularly looking at, there is no Standard Peripheral Library Users Manual.  I did see in the download for the Standard Peripheral Library for the F4 parts that there is a CHM file, but on the two computers I tried this does not work as it should (it does not display the contents).  There is a nice PDF for the F2 parts.  The other issue is that these peripheral libraries do not mention the registers in the Reference Manual.  The reference manual goes to great lengths to explain a process, and then you have to try and guess when you go over to using the API styles.  When something does not work as expected, you then have to dig through the headers to find the struct to then find the defines to try and relate it back to the Reference Manual.  I do recognize that the part is more complex, but so far I see a lack of correlation between the Reference Manuals/Registers and the API documentation.

As to code portability, there are some issues with that, though with the PICs I generally was able to copy peripheral code from one chip to another as long as I was going from an 8 bit to an 8 bit or a 16 bit to a 16 bit. 

Once again, I am not against making a switch to the API style coding, I just have more documentation for register based coding.  Starting with STM for making my jump into ARM Cortex M may not have been the best for making a fast transition (TI has much better documentation), but they had a part that fit the design.  I will slog through it.  I do appreciate the responses.  From what I see, I have not missed anything, there is just a lack of good documentation to be found in a singular location.  I will have to go tearing through header files and example files to get the information I need.

Adam

dthedens23
Associate II
Posted on September 20, 2013 at 16:41

You could create your own peripheral classes in C++.

I did this for a Renesas part (whose libraries are to huge).  Used ''placement new'' to lay a peripheral accessor class right over the top of the memory mapped register set.  works well for things like UART which have the same register definition, just a different base address for each peripheral.  The compilers generate very tight code for small objects like these.