more C notes

This commit is contained in:
Thomas Bishop 2026-01-12 18:12:53 +00:00
parent 7fc0829cc9
commit 2ba5bfb2f3
5 changed files with 309 additions and 0 deletions

View file

@ -0,0 +1,48 @@
---
tags:
- C
---
In order to create a file that you can import into other C files you need to
create a header file.
> Remember that this isn't really a module since `.h` files and `#includes` are
> just text substition
In fact you create a `.h` header file which links to a `.c` file which actually
contains the code you want to import.
Let's say we want to create a "module" that handles connecting to WiFi:
```c
// wifi.h
#ifndef WIFI_H
#define WIFI_H
void wifi_connect(void);
#endif
```
In the `.h` file we just define the function signature and put checks (so it
doesn't get redefined).
Then the implementation file:
```c
// main/wifi.c
// Add any header files or auxiliary functions that the main (wifi_connect)
// function will require here
void wifi_connect(void) {}
```
Then in your main entrypoint:
```c
// main/main.c
#include "wifi.h"
wifi_connect()
```

View file

@ -0,0 +1,187 @@
---
tags:
- C
- ESP32
---
"RTOS" stands for "real time operating system". It is category of OSs designed
for embedded systems that need predicatable, time-sensitive responses.
"Free RTOS" is the particular implementation of RTOS on the ESP32.
It is **event-driven** because it runs multiple tasks at once rather than
running a single program from top to bottom. (In fact, as with all OSs, it is
running through task scheduling very rapidly.)
![RTOS event architecture](../img/rtos-event-architecture.png)
## Event handlers
Typically, certain events are created by the ESP32 device which you then hook
into and run functions against.
You then register **handlers** for events that you care about.
## Event groups
You use **event groups** to efficiently manage the different events and prevent
other events being blocked.
An event group is just a number with bits that you can set and clear basically
like a switch.
These are predefined as `BIT0, BIT1, ...` in
[ESP-IDF](./Programming_ESP32_with_Expressif_IoT_Development_Framework.md) as
[macros](/zk/Macros_in_C.md) for [bitwise operators](/zk/Bitwise_operators.md).
Your event handler function can update an event group when the thing it is
concerned with happens. Like toggling a switch.
This allows other processes to act in response and further looped logic to
appply.
## Walkthrough example: connecting to WiFi
> Below is a simplified example of using the event loop to connect to WiFi
### Event group
First we define our event group bits in order to sequence and track the state:
```c
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
```
These macros rename the `BIT0` and `BIT1` event group bits that ESP-IDF provides
to more meaningful names focused on what we are trying to achieve:
- connect to WiFi
- track when/if this connection fails
### Events to target
The ESP32 WiFi driver publishes a `WIFI_EVENT` when WiFi-related processes
occur. `WIFI_EVENT` is the broad category (called the "event base"), which
includes the following more specific events:
- `WIFI_EVENT_STA_START`
- WiFi on device started in station mode (ready to connect)
- `WIFI-EVENT_STA_CONNECTED`
- Device has connected to router
- `WIFI_EVENT_STA_DISCONNECTED`
- Device has disconnected from router
Another event base is `IP_EVENT` with the specific events:
- `IP_EVENT_STA_GOT_IP`
- Device has been assigned an IP by [DHCP](./DHCP.md) server
- `IP_EVENT_STA_LOST_IP`
### Register event handler
The following code registers a handler for the `WIFI_EVENT` event base, in the
form of a custom function `wifi_event_handler`:
```c
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler)
```
The second parameter captures all events in the `WIFI_EVENT` category. The third
parameter is a pointer to the function that runs when the event fires
(`wifi_event_handler`):
```c
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < MAX_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retrying connection...");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ESP_LOGI(TAG, "Connected! Got IP");
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
```
In essence:
- If WiFi connection event is triggered attempt to connect
- If WiFi disconnection event is triggered try to reconnect within retry params,
if no luck, set wifi connection event group to error
- If IP is assigned set wifi connection event group to success
> `esp_wifi_connect` is the built in WiFi connection logic from ESP-IDF from
> `esp-wifi.h` header file
### Main program logic
We have a main wrapper function `wifi_connect` that manages calls to the event
handler function and which established the event group. This would run once at
setup:
```c
void wifi_connect(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta =
{
.ssid = WIFI_SSID,
.password = WIFI_PASS,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
// Wait for connection
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi connected successfully");
} else {
ESP_LOGE(TAG, "WiFi connection failed");
}
}
```
Lots going on but basically:
- Register the WiFi event group
- Register the event handler and pass in the events we are interested in
- Store the `ssid` and `password` in the internal state of the device (these
don't need to be handed to the actual `esp_wifi_connect()` function - they are
remembered once set)
- Log success based on the event group `WIFI_CONNECTED_BIT`
> Note that we register two event handlers but pass the same function
> (`wifi_event_handler`) because it handles both the `WIFI` and `IP` event
> bases.

12
zk/Static_in_C.md Normal file
View file

@ -0,0 +1,12 @@
---
tags:
- C
---
`static` signals that the variable is private to the file it appears in. It
cannot be accessed from outside of it.
The opposite to a global variable.
Using `static` prevents naming conflicts if you have another file with the same
variable name.

40
zk/Structs_in_C.md Normal file
View file

@ -0,0 +1,40 @@
---
tags:
- C
---
A `struct` is a custom, user-defined type, in constrast to the primitive types
(`int`, `char`, `float` etc). They are known as **compound types**.
## Usage
Define the custom type:
```c
struct Person {
int age;
float height;
}
```
Then to apply the type to a variable:
```c
struct Person thomas
```
And define the sub-properties:
```c
thomas.age = 37
thomas.height = 5.8
```
Alternatively do all together:
```c
struct Person thomas = {
.age = 37,
.height = 5.8
}
```

22
zk/Typedefs_in_C.md Normal file
View file

@ -0,0 +1,22 @@
---
tags:
- C
---
A `typedef` is an alias for an existing type. It can make complex types easier
to write and provides abstraction.
The syntax:
```c
typedef existing_type new_name;
```
For example:
```c
typedef unsigned int uint_t
```
> The convention is to append `_t` to the variable name to make it easier to
> understand that it is a typedef later in the execution.