cforth
cforth copied to clipboard
ESP32 How to access UART1?
How do I access UART1 do I need to code new words to talk directly with the hardware, or are their words already available ( I have looked, but couldn't see anything obvious)?
Does forth run inside freertos, or is it bare metal?
Many thanks.
As I learn more I see there is a C-to-Forth gateway mechanism, however as I am completely new to this, I wonder if it would be possible to give a quick example?
As I learn more I see there is a C-to-Forth gateway mechanism, however as I am completely new to this, I wonder if it would be possible to give a quick example?
As I learn more I discovered there is a C-to-Forth gateway mechanism as mentioned in the ForthHub/discussion, however as I am completely new to this, I wonder if it would be possible to give a quick example?
I thought that I replied to this yesterday via email but apparently my reply didn't get added to the thread.
In each subdirectory of cforth/src/app/, for example cforth/src/app/esp32, there is a file named extend.c which contains C code that you can call from Forth. You can look in any such extend.c file for examples.
You can put your own C functions there, and you can also call functions from whatever framework happens to be used - for example from esp-idf / freertos for the ESP32 build.
At the end of extend.c there is a ccalls table that maps from C to Forth. Here's an example of a table entry:
C(i2c_write_read) //c i2c-write-read { a.wbuf i.wsize a.rbuf i.rsize i.slave i.stop -- i.err? }
That entry creates a Forth word named "i2c-write-read" that calls a C function named "i2c_write_read". The C function's prototype is
int i2c_write_read(uint8_t stop, uint8_t slave, uint8_t rsize, uint8_t *rbuf, uint8_t wsize, uint8_t *wbuf);
The stack diagram for the Forth word is:
i2c-write-read ( wbuf wsize rbuf rsize slave stop -- err? )
Note that the order is backwards - reflecting the way that arguments are actually pushed on the stack in C. Inside the { ... } list that defines the call gateway, the first character of every item must be either "i" - meaning a number, "a" - meaning an address, or "$" - meaning a string. An item beginning with "-" separates inputs from outputs. Since C functions can have only 0 or 1 return value, there can be at most one output. If an input item starts with "$", a Forth "adr len" string will be converted to a C null-terminated string before passing it to the C function.
For C functions that you define yourself in extend.c, you do not necessarily need a prototype because the definition exists before the ccalls table. For functions that you want to call from an external library, you need to provide a prototype inside extend.c (or in a .h file that it includes). You can do that by including .h files from the framework, or you can provide a simplified prototype of your own creation. Including the framework's .h file is more correct, but it can lead to a nightmare of include dependencies, so sometimes it is prudent to fake it with an abbreviated prototype like:
extern void adc1_get_voltage(void);
That prototype is not "correct" - that routine's actual return value is "int" and it has an argument of type "adc1_channel_t channel" - but it works anyway. The reason it works is because no actual C code to directly call the function is generated. Instead, the address of the function is added to an array. At run time, Forth uses information in the { ... } list to pull items from the Forth stack, marshal them as C function arguments, and call the function from the table.
For your specific problem, there is some code in esp-idf that may be helpful. Look in esp-idf/components/driver/test/test_uart.c to see how to use the UART functions that esp-idf provides. You can add entries for those functions in src/app/esp32/extend.c
I thought that I replied to this yesterday via email but apparently my reply didn't get added to the thread.
Off-topic; there have been reports of GitHub being down or misbehaving, especially in the past couple of hours. I didn't get a notification for any reply yesterday, and got only one notification for @TJ-C 's second, third, and fourth comments today. I suspect those comments are artefacts of outage.
Nice write-up, thanks!
Thanks for the detailed write up, I have now added all the UART calls and compiled successfully. Now I just have to find all those hard to find API constants to populate the values. :D
Apologies for all the repeated notifications, GitHub was very slow at the time and repeatedly told me my comment had failed. I was surprised to find they had got through.
Quick question: How do I send a null pointer from cforth to C?
Answer: According to a number of articles NULL in C is just 0, so in forth 0 constant NULL should work.
From Kolban ESP32 book We can now initialize a driver using:
uart_driver_install(uartNum, 2048, 0, 10, NULL, 0);
One of the options we can specify when initializing a driver is to supply a FreeRTOS queue handle. If we supply this, then events that are detected by the UART are then posted onto the queue. We can have tasks that are blocked watching the queue ready to process incoming events when they arrive. This allows us to perform UART data processing asynchronously. If we don't want to use a queue, we specify NULL for the queue parameter.
A null pointer is just the number 0. It is useful to distinguish (int)0 from (void *)0 (== NULL) in C source for type checking purposes, but at the core, a zero is a zero is a zero. CForth explicitly uses a cell size that is the same size as a pointer so stuff like this - and also access to memory-mapped IO devices - works correctly.
I'm struggling with passing 8-bit address pointers, which has probably got a lot to do with my lack of C and Forth skills.
All my working files are here.
Originally I was just going to take the esp-idf api and code the interface, which worked Ok until I got to:
// Communicate
C(uart_get_buffered_data_len) //c uart-buf-len@ { a.buf-len, i.uart -- i.error? }
C(uart_write_bytes) //c uart-write-bytes { i.size a.src i.uart -- i.cnt? }
C(uart_read_bytes) //c uart-read-bytes { i.tickwait i.length a.buf i.uart# -- i.cnt? }
And in forth:
Found this buffer code in one of your files, not sure if this is the correct way to configure a buffer?
#1024 constant /sp1-buf
/sp1-buf buffer: sp1-buf
: uart@
20 /sp1-buf sp1-buf uart uart-read-bytes
;
C(uart_read_bytes) //c uart-read-bytes { i.tickwait i.length a.buf i.uart# -- i.cnt? }
I also tried calling the functions in my t-board-interfaces.c
// this buffer address should be provided by forth and passed to fetch_gps_data().
// uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
// Read data from UART1.
cell fetch_gps_data(uint8_t* data) {
int len = uart_read_bytes(1, data, BUF_SIZE, 20 / portTICK_RATE_MS);
// Then we return how many bytes read or an error -1
return len;
}
C(fetch_gps_data) //c gps-data@ { a.buf -- i.cnt? }
As soon as I try to read from the uart it just crashes the system.
#1024 constant /sp1-buf
/sp1-buf buffer: sp1-buf
ok sp1-buf gps-data@
Guru Meditation Error: Core 0 panic'ed (InstrFetchProhibited). Exception was unhandled.
Core 0 register dump:
PC : 0x37616463 PS : 0x00060c30 A0 : 0x800d515a A1 : 0x3ffd2260
A2 : 0x3f40db42 A3 : 0x3f40db40 A4 : 0x3ffc7a7c A5 : 0x3ffd24d0
A6 : 0x37616463 A7 : 0x3ffd6200 A8 : 0x800d3d54 A9 : 0x3ffd2460
A10 : 0x3ffd6200 A11 : 0x3ffd24d0 A12 : 0x2d737067 A13 : 0x61746164
A14 : 0x00000440 A15 : 0x00000000 SAR : 0x00000010 EXCCAUSE: 0x00000014
EXCVADDR: 0x37616460 LBEG : 0x400d58d0 LEND : 0x400d58e9 LCOUNT : 0x00000000
Backtrace: 0x37616463:0x3ffd2260 0x400d5157:0x3ffd24d0 0x400d572b:0x3ffd2520 0x400d579d:0x3ffd2540 0x400d34ce:0x3ffd2570 0x400d2dca:0x3ffd2590 0x400d167e:0x3ffd25b0
Rebooting...
Also, you are using # in front of numbers, what does '#' do?
Thank you.
#
means decimal radix for duration of the number. I don't know what the problem is, but I suggest a few things to consider;
- PC is not aligned; should it be?
- what is the PC pointing to?
- is the buffer aligned; and should it be?
- does it do any different if you use a malloc'd buffer?
- does it do any different if you use uart_get_buffered_data_len to get the length to pass to uart_read_bytes?
- can you compare the register dump and backtrace addresses against a map file?
Thanks. It turns out putting comments in the calls[] table caused some weird behaviour resulting in the wrong function to be called.
Sorry I didn't reply earlier. You message from 4 days ago go buried in an email thread display and I missed seeing it. I forgot to mention that comment thing. The problem is that the program that scans that table to create the tccalls.fth file that controls the Forth callback is too simple and it screws up with C comments.
Hi Mitch,
You can do that by including .h files
Thanks to the explanations I found here I was able to add esp_clk_cpu_freq and esp_set_cpu_freq
For esp_clk_cpu_freq worked after using: #include "../../../ESP8266_RTOS_SDK/components/esp8266/include/esp_clk.h"
esp_set_cpu_freq was a bit more complicated, after: #include "../../../ESP8266_RTOS_SDK/components/esp8266/include/esp_system.h" It only worked whenI copied esp_err.h sdkconfig.h to the directory of esp_system.h
Since there are a few more calls to the OS on my list I would like to know what the appropriate way is to include header files that include other header files in other directories in the SDK.
NOTE: Please ignore I just found interface.h (Sorry)
Just FYI, there are these lines in src/app/esp32/targets.mk
INCS += -I$(IDF_PATH)components/esp32/include/
INCS += -I$(IDF_PATH)components/driver/include/
INCS += -I$(IDF_PATH)components/soc/include/
INCS += -I$(IDF_PATH)components/soc/esp32/include/
You can extend that list if you need other include directories to pick up other include dependencies.