1.6. Creating Device Hardware Access Macros
After you verify the functionality of the peripheral registers with the bit_bang_uart test software, you can replace the IORD() and IOWR() macros and their hard-coded address parameters with register access macros. You define the register access macros for the component, under the
<my_design>/ip/<componentfolder>/inc/<component>_regs.h source code header file.
The base address, component instance name, and interrupt request (IRQ) priority are all available to HAL device drivers from system.h. You can write macros that access specific peripheral registers by name, constructed from the information provided in system.h. The macros remove the hard-coded nature of the register accesses and instead pull the register base address information out of system.h. This procedure allows automatic incorporation of any changes made to the component instance base address in the hardware design. For example, to access the UART's transmit register, the code in bit_bang_uart.c uses an IOWR() macro with a hard-coded offset (value 1). Convert this method to a device access macro that can adapt to changes in system.h automatically.
The Device Access Macros in my_uart_regs.h example (from my_uart_regs.h) defines a set of device access macros and related access masks for the UART status register.
Device Access Macros in my_uart_regs.h
#define MY_UART_STATUS_REG 2
#define IOADDR_MY_UART_STATUS(base) IO_CALC_ADDRESS_NATIVE(base, MY_UART_STATUS_REG)
#define IORD_MY_UART_STATUS(base) IORD(base, MY_UART_STATUS_REG)
#define IOWR_MY_UART_STATUS(base, data) IOWR(base, MY_UART_STATUS_REG, data)
#define MY_UART_STATUS_PE_MSK (0x1)
#define MY_UART_STATUS_PE_OFST (0)
#define MY_UART_STATUS_FE_MSK (0x2)
#define MY_UART_STATUS_FE_OFST (1)
#define MY_UART_STATUS_BRK_MSK (0x4)
#define MY_UART_STATUS_BRK_OFST (2)
#define MY_UART_STATUS_ROE_MSK (0x8)
#define MY_UART_STATUS_ROE_OFST (3)
#define MY_UART_STATUS_TOE_MSK (0x10)
#define MY_UART_STATUS_TOE_OFST (4)
#define MY_UART_STATUS_TMT_MSK (0x20)
#define MY_UART_STATUS_TMT_OFST (5)
#define MY_UART_STATUS_TRDY_MSK (0x40)
#define MY_UART_STATUS_TRDY_OFST (6)
#define MY_UART_STATUS_RRDY_MSK (0x80)
#define MY_UART_STATUS_RRDY_OFST (7)
#define MY_UART_STATUS_E_MSK (0x100)
#define MY_UART_STATUS_E_OFST (8)
#define MY_UART_STATUS_DCTS_MSK (0x400)
#define MY_UART_STATUS_DCTS_OFST (10)
#define MY_UART_STATUS_CTS_MSK (0x800)
#define MY_UART_STATUS_CTS_OFST (11)
#define MY_UART_STATUS_EOP_MSK (0x1000)
#define MY_UART_STATUS_EOP_OFST (12)
The Altera Nios II component also provides the address construction macro IO_CALC_ADDRESS_NATIVE(). The UART device access macros in nios2eds/components/altera_nios2/HAL/inc/io.h use this macro. IO_CALC_ADDRESS_NATIVE() computes the native address of a specified peripheral register. To compute this address, it adds the second parameter (offset) to the first parameter (peripheral base address). The offset is represented in system bus width units, for example, 32 bits. The IORD() and IOWR() macros translate to the Nios II assembler instructions, ldwio and stwio, respectively.
Native addressing mode is deprecated, because Altera is moving to a direct addressing model. New components should be written to use byte-enable signals. Write new device drivers for these components with direct addressing macros, such as IORD_32DIRECT(), which utilize the byte-enable signals. Offsets for direct address macros are always represented in bytes. The bit_bang_uart example application uses native addressing. The my_uart device driver also uses native addressing.
For example, the following addressing macro:
IOWR(UART1_BASE, 2, 0);
translates to the following direct addressing macro:
IOWR_32DIRECT(UART1_BASE, 8, 0);
Notice that the offset specified is now eight bytes, instead of two long words.
For more details on direct addressing macros, refer to "Writing Device Drivers" in the "Cache and Tightly-Coupled Memory" chapter in the Nios II Software Developer's Handbook.
In the BitBangUartTransmit() function in bit_bang_uart.c, you use an IORD() macro with hard-coded values to read the UART status register:
uart_status = IORD(UART1_BASE, 2);
You can achieve the same functionality by using the UART's device access macro:
uart_status = IORD_MY_UART_STATUS(UART1_BASE)
Using this macro makes the device driver code easier to write and easier to understand after it is written.
Altera recommends that you create device access macros for all of your custom component's registers, and that you create masks for each of the bits represented in those macros. These steps result in a driver that is much easier to understand; therefore, it is easier to verify the correctness of the device driver.