Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESP32 How to access UART1? #57

Open
TJ-C opened this issue Apr 1, 2020 · 14 comments
Open

ESP32 How to access UART1? #57

TJ-C opened this issue Apr 1, 2020 · 14 comments

Comments

@TJ-C
Copy link

TJ-C commented Apr 1, 2020

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.

@TJ-C
Copy link
Author

TJ-C commented Apr 2, 2020

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?

1 similar comment
@TJ-C
Copy link
Author

TJ-C commented Apr 2, 2020

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?

@TJ-C
Copy link
Author

TJ-C commented Apr 2, 2020

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?

@MitchBradley
Copy link
Owner

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

@quozl
Copy link
Collaborator

quozl commented Apr 2, 2020

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!

@TJ-C
Copy link
Author

TJ-C commented Apr 4, 2020

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.

@TJ-C
Copy link
Author

TJ-C commented Apr 4, 2020

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.

@MitchBradley
Copy link
Owner

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.

@TJ-C
Copy link
Author

TJ-C commented Apr 6, 2020

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? }

edf-idf doc

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.

@quozl
Copy link
Collaborator

quozl commented Apr 7, 2020

# 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?

@TJ-C
Copy link
Author

TJ-C commented Apr 10, 2020

Thanks. It turns out putting comments in the calls[] table caused some weird behaviour resulting in the wrong function to be called.

@MitchBradley
Copy link
Owner

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.

@Jos-Ven
Copy link
Contributor

Jos-Ven commented Dec 3, 2020

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)

@MitchBradley
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants