XC8: .asm to C ( i.e. Lowering One's Expectations)

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
The DMA on the newer 18f parts is pretty cool. I combined the NCO (numerically controlled oscillator) with the DMA and DAC to implement a gated arbitrary waveform generator (DC to 2Mhz) that requires zero instruction cycles to run.

These parts/peripherals weren't available to me under the old MPLab, so that is the silver lining (of having to switched to C).

Unfortunately, the C setup code footprint is still at least twice as large as if I coded it in .asm, but at least it's run once and forget.

The on-chip DAC is only 5 bits, and there's a small amount of jitter on the NCO, so there is some distortion on the output.
 
Last edited:

nsaspook

Joined Aug 27, 2009
16,341
The DMA on the newer 18f parts is pretty cool. I combined the NCO (numerically controlled oscillator) with the DMA and DAC to implement a gated arbitrary waveform generator (DC to 2Mhz) that requires zero instruction cycles to run.

These parts/peripherals weren't available to me under the old MPLab, so that is the silver lining (of having to switched to C).

Unfortunately, the C setup code footprint is still at least twice as large as if I coded it in .asm, but at least it's run once and forget.

The on-chip DAC is only 5 bits, and there's a small amount of jitter on the NCO, so there is some distortion on the output.
I use DMA on most PIC18f projects with the current fav being the Q84 series because of CANFD or the K42(3) when I don't need CAN. The arbitration logic is simple but the usable memory bus bandwidth is limited because it's lacking a true memory fiber cross-connect you see on 32-bit parts.
https://raw.githubusercontent.com/nsaspook/vtouch_v2/eadogs/q43_board/q43_ntsc.X/ntsc.c

https://ww1.microchip.com/downloads...emory-Access-on-8-bit-PIC-MCU-DS90003164B.pdf
As shown in Figure 1, each DMA controller can be independently configured to move data between single or multiple addresses. The DMA controllers use the same instruction bus and data bus as the CPU for transferring data between memories. Depending on the priority set in the system arbiter, the DMA controller can move data either by utilizing unused CPU cycles or stalling the CPU. By default, the CPU has priority over DMA. In this case, the DMA steals unused cycles from the CPU to perform the read/write operations. Because of this concurrent operation between the CPU and DMA, the bandwidth of handling data is increased and the DMA module can operate in the background.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
I use DMA on most PIC18f projects with the current fav being the Q84 series because of CANFD or the K42(3) when I don't need CAN. The arbitration logic is simple but the usable memory bus bandwidth is limited because it's lacking a true memory fiber cross-connect you see on 32-bit parts.
https://raw.githubusercontent.com/nsaspook/vtouch_v2/eadogs/q43_board/q43_ntsc.X/ntsc.c

https://ww1.microchip.com/downloads...emory-Access-on-8-bit-PIC-MCU-DS90003164B.pdf
As shown in Figure 1, each DMA controller can be independently configured to move data between single or multiple addresses. The DMA controllers use the same instruction bus and data bus as the CPU for transferring data between memories. Depending on the priority set in the system arbiter, the DMA controller can move data either by utilizing unused CPU cycles or stalling the CPU. By default, the CPU has priority over DMA. In this case, the DMA steals unused cycles from the CPU to perform the read/write operations. Because of this concurrent operation between the CPU and DMA, the bandwidth of handling data is increased and the DMA module can operate in the background.
The datasheet doesn't say when "bubbles" become available, nor the maximum latency between the transaction trigger and the actual transaction.

This doesn't seem to matter in my immediate application, but I'd sure like to know -- if only for academic reasons. Any insights?
 

nsaspook

Joined Aug 27, 2009
16,341
The datasheet doesn't say when "bubbles" become available, nor the maximum latency between the transaction trigger and the actual transaction.

This doesn't seem to matter in my immediate application, but I'd sure like to know -- if only for academic reasons. Any insights?
Not much information out there about the low-level details of PIC18 System Arbitration.

https://forum.allaboutcircuits.com/threads/tv-pong-game-using-microcontroller.184377/post-1700198

https://onlinedocs.microchip.com/pr...tml?GUID-7F322DA3-6BD0-4D0C-AFBB-014970CD0FDA
The system arbiter resolves memory access between the system level selections (i.e., Main, Interrupt Service Routine) and peripheral selection (e.g., DMA and Scanner) based on user-assigned priorities. A block diagram of the system arbiter can be found below. Each of the system level and peripheral selections has its own priority selection registers. Memory access priority is resolved using the number written to the corresponding Priority registers, 0 being the highest priority selection and the maximum value being the lowest priority. All system level and peripheral level selections default to the lowest priority configuration. If the same value is in two or more Priority registers, priority is given to the higher-listed selection according to the following table.
I use a few tricks to get around 8-bit DMA shortcomings.
The remaining problem is dynamic updates to the video memory. All of the memory bandwidth (something in short supply with the simple DMA architecture seen in most 8-bit controllers) is being used by DMA so excessive processing during DMA will affect H/V timing causing tearing and loss of sync. The fix for that is to only process mainline code during the scanlines with no video around the H sync pulse. For that we need a simple task manager that uses the high/low priority interrupt structure and a 'hold' flag.

Setup a Low priority timer ISR as the 'idle' loop; This idle loop can be interrupted by High priority DMA completion and other module interrupt requests so those time critical processes get the cpu they need during the NTSC FSM. The 'idle' loop can also set a task time duration for the main task that will interrupt the main task back to the low priority idle loop during the critical timing periods. This means I can step into the main application code execution when I want it to run it and control how long I want one processing time slice to be.
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
C:
uint8_t     ble_wv_data[20];
uint8_t     ble_wv_data_index;

...
    
int8_t value;

...

ble_wv_data[ble_wv_data_index >> 1] = ((uint8_t)value) << 4;

produces the warning:

Code:
BLE.c:325:68: warning: implicit conversion loses integer precision: 'int' to 'uint8_t' (aka 'unsigned char') [-Wconversion]
            ble_wv_data[ble_wv_data_index >> 1] = ((uint8_t)value) << 4;
                                                ~ ~~~~~~~~~~~~~~~~~^~~~
but this does not:

C:
ble_wv_data[ble_wv_data_index >> 1] = ble_wv_data[ble_wv_data_index >> 1] | (uint8_t)value;
Why? Does the shift only operate on signed values?
 

nsaspook

Joined Aug 27, 2009
16,341
C:
uint8_t     ble_wv_data[20];
uint8_t     ble_wv_data_index;

...
   
int8_t value;

...

ble_wv_data[ble_wv_data_index >> 1] = ((uint8_t)value) << 4;

produces the warning:

Code:
BLE.c:325:68: warning: implicit conversion loses integer precision: 'int' to 'uint8_t' (aka 'unsigned char') [-Wconversion]
            ble_wv_data[ble_wv_data_index >> 1] = ((uint8_t)value) << 4;
                                                ~ ~~~~~~~~~~~~~~~~~^~~~
but this does not:

C:
ble_wv_data[ble_wv_data_index >> 1] = ble_wv_data[ble_wv_data_index >> 1] | (uint8_t)value;
Why? Does the shift only operate on signed values?
Default integer promotions to 16-bits.

Code:
Disassembly Listing for ip_test
Generated From:
/root/HarmonyProjects/ip_test.X/dist/default/production/ip_test.X.production.elf
May 6, 2024 3:28:54 PM

---  /tmp/xcXelNgbB/driver_tmp_1.s  ---------------------------------------------------------------------
3702  0E32     MOVLW 0x32
3704  6EF6     MOVWF 0xFF6, ACCESS
3706  0E37     MOVLW 0x37
3708  6EF7     MOVWF 0xFF7, ACCESS
370A  0E00     MOVLW 0x0
370C  6EF8     MOVWF 0xFF8, ACCESS
370E  0009     TBLRD*+
3710  C4F5     MOVFF TABLAT, __pdataCOMRAM
3714  0009     TBLRD*+
3716  C4F5     MOVFF TABLAT, _ble_wv_data_index
371A  EE01     LFSR 0, 0x501
371E  0E14     MOVLW 0x14
3720  6AEE     CLRF 0xFEE, ACCESS
3722  06E8     DECF 0xFE8, F, ACCESS
3724  E1FD     BNZ 0x3720
3726  0100     MOVLB 0x0
3728  EF96     GOTO 0x372C
---  /root/HarmonyProjects/ip_test.X/main.c  ------------------------------------------------------------
1:             
2:             
3:             #include "mcc_generated_files/mcc.h"
4:             uint8_t ble_wv_data[20];
5:             uint8_t ble_wv_data_index = 1;
6:             int8_t value = 1;
7:             
8:             /*
9:                          Main application
10:             */
11:            void main(void)
12:            {
13:            
14:            
15:                ble_wv_data[ble_wv_data_index >> 1] = (uint8_t) (((uint8_t) value) << 4);
372C  0E10     MOVLW 0x10
372E  6E01     MOVWF __mediumconst, ACCESS
16:                
17:                while (1);
3730  D7FF     BRA 0x3730
18:            }
19:            /**
20:             End of File
21:             */
https://forum.allaboutcircuits.com/...ed-to-unsigned-conversion.147958/post-1262036
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
Default integer promotions to 16-bits.
...
Understood...thanks.

Just one more thing to keep in mind.

BTW, is there something special about curly braces inside a string that initializes a const char array?

For example, "{xyz}" doesn't produce the expected code, but "\{xyz}" does.
 

nsaspook

Joined Aug 27, 2009
16,341
Understood...thanks.

Just one more thing to keep in mind.

BTW, is there something special about curly braces inside a string that initializes a const char array?

For example, "{xyz}" doesn't produce the expected code, but "\{xyz}" does.
Not sure on that one.
 

nsaspook

Joined Aug 27, 2009
16,341
I still think the hoops one needs to jump through to get good compiled code is hopelessly annoying and time consuming.

I'd not voluntarily work this way if I had other options.
1715046603759.png

What you see is the power and pitfalls of software language abstraction of machine language at the embedded level.
The programming language C is on the good end of power and pitfalls, most of the rest are worse.
 

cmartinez

Joined Jan 17, 2007
8,783
Not to distract from the subject. But, (paraphrasing Steve Jobs) the way I see it, C is like the unicycle of languages, whilst most of the rest are either bicycles or even motorbikes.

Assembly is the raw, ultimate power, but most people wouldn't know what to do with it.
 

nsaspook

Joined Aug 27, 2009
16,341
...
Assembly is the raw, ultimate power, but most people wouldn't know what to do with it.
I don't believe that.

If it was something special in the general sense, showing programming skill or power, people would be using it today for just about every task for bragging rights instead of using it when actually needed. Most people (programmers) don't need Assembly because the work of Assembly has already been done on the OS/Programming language they use. So, they, like most sensible people, delegate assembly to the compiler system that's almost as good as the best human assembly coders because coding in X or X language is only a small part of programming. Small 8-bit embedded programming is a corner case for C but IMO with the modifications to a compiler like XC8, that's optimized for the 8-bit universe and the knowledge of what it takes to optimize C source code in that 8-bit universe to make optimized assembly using that compiler The ability to easily transport that same code to more complex systems is worth the time and effort of using C over Assembly for all but the most sensitive to hardware code sections.

If all of your systems are 8-bit, of the same architect and controller type then coding in assembly is a good choice but the world of programming embedded systems is much larger than that little corner.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
I don't believe that.

If it was something special in the general sense, showing programming skill or power, people would be using it today for just about every task for bragging rights instead of using it when actually needed. Most people (programmers) don't need Assembly because the work of Assembly has already been done on the OS/Programming language they use. So, they, like most sensible people, delegate assembly to the compiler system that's almost as good as the best human assembly coders because coding in X or X language is only a small part of programming. Small 8-bit embedded programming is a corner case for C but IMO with the modifications to a compiler like XC8, that's optimized for the 8-bit universe and the knowledge of what it takes to optimize C source code in that 8-bit universe to make optimized assembly using that compiler The ability to easily transport that same code to more complex systems is worth the time and effort of using C over Assembly for all but the most sensitive to hardware code sections.

If all of your systems are 8-bit, of the same architect and controller type then coding in assembly is a good choice but the world of programming embedded systems is much larger than that little corner.
Whatever.

Still no excuse for not having a good assembler for those who choose to write .asm, whatever their reason.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
If there was a great need and demand, it would be created by someone to make a buck. That's how capitalism works.
You're right. I've been tempted to write one. I just wouldn't know how to integrate it with NetBeans.

Edit:

Technically speaking, demand doesn't drive availability. Supply does.

You can demand a time machine or an FTL spaceship, but no amount of money is going to get you one.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,334
This is stupidly funny:

C:
BLE_FLAG_NOTIFY = ~BLE_FLAG_NOTIFY;
or

C:
BLE_FLAG_NOTIFY = !BLE_FLAG_NOTIFY;
resolves to:

Code:
11C9C  A438     BTFSS ble_scan_flags, 2, ACCESS
11C9E  D002     BRA 0x1CA4
11CA0  0E01     MOVLW 0x1
11CA2  D001     BRA 0x1CA6
11CA4  0E00     MOVLW 0x0
11CA6  0AFF     XORLW 0xFF
11CA8  6E1D     MOVWF potnum, ACCESS
11CAA  461D     RLNCF potnum, F, ACCESS
11CAC  461D     RLNCF potnum, F, ACCESS
11CAE  5038     MOVF ble_scan_flags, W, ACCESS
11CB0  181D     XORWF potnum, W, ACCESS
11CB2  0BFB     ANDLW 0xFB
11CB4  181D     XORWF potnum, W, ACCESS
11CB6  6E38     MOVWF ble_scan_flags, ACCESS
but:

C:
BLE_FLAG_NOTIFY ^= 1;
resolves to:

Code:
11F5E  7438     BTG ble_scan_flags, 2, ACCESS
The World of C is insane.
 
Top